diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs deleted file mode 100644 index 7be126c62a..0000000000 --- a/.git-blame-ignore-revs +++ /dev/null @@ -1,2 +0,0 @@ -# initial run of pre-commit -7e025f9b5d0feccfc2c9b1630f951a4256024906 diff --git a/.github/workflows/book.yml b/.github/workflows/book.yml index 617ea4c774..f4ed2f3c14 100644 --- a/.github/workflows/book.yml +++ b/.github/workflows/book.yml @@ -2,36 +2,23 @@ name: miden book on: push: - branches: - - main - - next + branches: [main, next] jobs: deploy: + name: Deploy miden-vm mdbook runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - override: true + - uses: actions/checkout@main + - name: Install katex, alerts and linkcheck + run: | + rustup update --no-self-update stable + cargo +stable install mdbook-katex mdbook-linkcheck mdbook-alerts - name: Setup mdBook uses: peaceiris/actions-mdbook@v1 with: - mdbook-version: 'latest' - - - name: Install mdbook-katex - uses: actions-rs/cargo@v1 - with: - command: install - args: mdbook-katex - - - name: Install mdbook-linkcheck - uses: actions-rs/cargo@v1 - with: - command: install - args: mdbook-linkcheck + mdbook-version: "latest" - name: Build miden book run: mdbook build docs/ diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000000..e757b42019 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,25 @@ +# Runs build related jobs. + +name: build + +on: + push: + branches: [main, next] + pull_request: + types: [opened, reopened, synchronize] + +jobs: + no-std: + name: Build for no-std + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + toolchain: [stable, nightly] + steps: + - uses: actions/checkout@main + - name: Build for no-std + run: | + rustup update --no-self-update ${{ matrix.toolchain }} + rustup target add wasm32-unknown-unknown + make build-no-std diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml new file mode 100644 index 0000000000..c890c4c265 --- /dev/null +++ b/.github/workflows/changelog.yml @@ -0,0 +1,23 @@ +# Runs changelog related jobs. +# CI job heavily inspired by: https://github.com/tarides/changelog-check-action + +name: changelog + +on: + pull_request: + types: [opened, reopened, synchronize, labeled, unlabeled] + +jobs: + changelog: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@main + with: + fetch-depth: 0 + - name: Check for changes in changelog + env: + BASE_REF: ${{ github.event.pull_request.base.ref }} + NO_CHANGELOG_LABEL: ${{ contains(github.event.pull_request.labels.*.name, 'no changelog') }} + run: ./scripts/check-changelog.sh "${{ inputs.changelog }}" + shell: bash diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 16cde14641..0000000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,139 +0,0 @@ -name: CI -on: - push: - branches: - - main - pull_request: - types: [opened, reopened, synchronize] - -jobs: - check: - name: Check Rust ${{matrix.toolchain}} on ${{matrix.os}} with ${{matrix.args}} - runs-on: ${{matrix.os}}-latest - strategy: - fail-fast: false - matrix: - toolchain: [stable, nightly] - os: [ubuntu] - args: [--all-targets --no-default-features, --all-targets, --all-targets --all-features] - steps: - - uses: actions/checkout@main - - name: Install rust - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{matrix.toolchain}} - override: true - - name: Check - uses: actions-rs/cargo@v1 - with: - command: check - args: ${{matrix.args}} - - test: - name: Test Rust ${{matrix.toolchain}} on ${{matrix.os}} with ${{matrix.args}} - runs-on: ${{matrix.os}}-latest - strategy: - fail-fast: false - matrix: - toolchain: [stable, nightly] - os: [ubuntu] - args: [--profile test-release, --profile test-release --doc] - steps: - - uses: actions/checkout@main - - name: Install rust - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{matrix.toolchain}} - override: true - - name: Test - uses: actions-rs/cargo@v1 - env: - RUSTFLAGS: -C debug-assertions - with: - command: test - args: ${{matrix.args}} --features "internals" - - # we separate the script so the CI will not require the same runner to have - # both windows and linux capabilities - test-windows: - name: Test Rust nightly on windows-2022 - # run windows check only when the target is `main`. will execute for release, push or PR. - if: github.ref_name == 'main' - runs-on: windows-2022 - strategy: - fail-fast: false - steps: - - uses: actions/checkout@main - - name: Install rust - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - override: true - - name: Test - uses: actions-rs/cargo@v1 - env: - RUSTFLAGS: -C debug-assertions - with: - command: test - args: --release --features "internals" - - clippy: - name: Clippy - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@main - - name: Install minimal stable with clippy - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: nightly - components: clippy - override: true - - - name: Clippy - uses: actions-rs/cargo@v1 - with: - command: clippy - args: --all -- -D clippy::all -D warnings - - rustfmt: - name: rustfmt - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@main - - name: Install minimal stable with rustfmt - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - components: rustfmt - override: true - - - name: rustfmt - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all -- --check - - no-std: - name: no-std - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - toolchain: [stable, nightly] - target: - - wasm32-unknown-unknown - steps: - - uses: actions/checkout@main - - name: Install rust - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{matrix.toolchain}} - override: true - - run: rustup target add wasm32-unknown-unknown - - name: Build - uses: actions-rs/cargo@v1 - with: - command: build - args: --verbose --no-default-features --target ${{ matrix.target }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000000..b06425c54e --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,53 @@ +# Runs linting related jobs. + +name: lint + +on: + push: + branches: [main, next] + pull_request: + types: [opened, reopened, synchronize] + +jobs: + clippy: + name: clippy nightly on ubuntu-latest + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@main + - name: Clippy + run: | + rustup update --no-self-update nightly + rustup +nightly component add clippy + make clippy + + rustfmt: + name: rustfmt check nightly on ubuntu-latest + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@main + - name: Rustfmt + run: | + rustup update --no-self-update nightly + rustup +nightly component add rustfmt + make format-check + + doc: + name: doc stable on ubuntu-latest + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@main + - name: Build docs + run: | + rustup update --no-self-update + make doc + + version: + name: check rust version consistency + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@main + with: + profile: minimal + override: true + - name: check rust versions + run: ./scripts/check-rust-version.sh diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000000..50548a5749 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,24 @@ +name: test + +on: + push: + branches: [main, next] + pull_request: + types: [opened, reopened, synchronize] + +jobs: + test: + name: test ${{matrix.toolchain}} on ubuntu-latest + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + toolchain: [stable, nightly] + timeout-minutes: 30 + steps: + - uses: actions/checkout@main + - uses: taiki-e/install-action@nextest + - name: Perform tests + run: | + rustup update --no-self-update ${{matrix.toolchain}} + make test diff --git a/.gitignore b/.gitignore index 4c4e243810..cc72e6e660 100644 --- a/.gitignore +++ b/.gitignore @@ -2,10 +2,6 @@ # will have compiled files and executables /target/ -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock - # These are backup files generated by rustfmt **/*.rs.bk diff --git a/CHANGELOG.md b/CHANGELOG.md index 84c7ec2281..8e53a69d70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,168 @@ # Changelog -## 0.9.0 (TBD) +## 0.11.0 (TBD) + +#### Enhancements + +- Added support for procedure annotation (attribute) syntax to Miden Assembly + +#### Changes + +- [BREAKING] Wrapped `MastForest`s in `Program` and `Library` structs in `Arc` (#1465). +- `MastForestBuilder`: use `MastNodeId` instead of MAST root to uniquely identify procedures (#1473) +- Added `miden_core::utils::sync::racy_lock` module (#1463). +- Updated `miden_core::utils` to re-export `std::sync::LazyLock` and `racy_lock::RacyLock as LazyLock` for std and no_std environments, respectively (#1463). +- Made the undocumented behavior of the VM with regard to undefined behavior of u32 operations, stricter (#1480) +- Introduced the `Emit` instruction (#1496) +- Debug instructions can be enabled in the cli `run` command using `--debug` flag (#1502) +- [BREAKING] ExecutionOptions::new constructor requires a boolean to explicitly set debug mode (#1502) +- [BREAKING] The `run` and the `prove` commands in the cli will accept `--trace` flag instead of `--tracing` (#1502) +- Migrated to new padding rule for RPO (#1343). + + +#### Fixes + +- Fixed an issue with formatting of blocks in Miden Assembly syntax +- Fixed the construction of the block hash table (#1506) +- Fixed a bug in the block stack table (#1511) +- Fixed the construction of the chiplets virtual table (#1514) + +#### Fixes + +- Decorators are now allowed in empty basic blocks (#1466) + + +## 0.10.6 (2024-09-12) - `miden-processor` crate only. + +#### Enhancements + +- Added `PartialEq`, `Eq`, `Serialize` and `Deserialize` to `AdviceMap` and `AdviceInputs` structs (#1494). + +## 0.10.5 (2024-08-21) + +#### Enhancements + +- Updated `MastForest::read_from` to deserialize without computing node hashes unnecessarily (#1453). +- Assembler: Merge contiguous basic blocks (#1454). +- Assembler: Add a threshold number of operations after which we stop merging more in the same block (#1461). + +#### Changes + +- Added `new_unsafe()` constructors to MAST node types which do not compute node hashes (#1453). +- Consolidated `BasicBlockNode` constructors and converted assert flow to `MastForestError::EmptyBasicBlock` (#1453). + +#### Fixes + +- Fixed an issue with registering non-local procedures in `MemMastForestStore` (#1462). +- Added a check for circular external node lookups in the processor (#1464). + +## 0.10.4 (2024-08-15) - `miden-processor` crate only + +#### Enhancements + +- Added support for executing `Dyn` nodes from external MAST forests (#1455). + +## 0.10.3 (2024-08-12) + +#### Enhancements + +- Added `with-debug-info` feature to `miden-stdlib` (#1445). +- Added `Assembler::add_modules_from_dir()` method (#1445). +- [BREAKING] Implemented building of multi-module kernels (#1445). + +#### Changes + +- [BREAKING] Replaced `SourceManager` parameter with `Assembler` in `Library::from_dir` (#1445). +- [BREAKING] Moved `Library` and `KernelLibrary` exports to the root of the `miden-assembly` crate. (#1445). + +## 0.10.2 (2024-08-10) + +#### Enhancements + +- Removed linear search of trace rows from `BlockHashTableRow::table_init()` (#1439). +- Exposed some pretty printing internals for `MastNode` (#1441). +- Made `KernelLibrary` impl `Clone` and `AsRef` (#1441). +- Added serialization to the `Program` struct (#1442). + +#### Changes + +- [BREAKING] Removed serialization of AST structs (#1442). + +## 0.10.0 (2024-08-06) + +#### Features + +- Added source location tracking to assembled MAST (#1419). +- Added error codes support for the `mtree_verify` instruction (#1328). +- Added support for immediate values for `lt`, `lte`, `gt`, `gte` comparison instructions (#1346). +- Added support for immediate values for `u32lt`, `u32lte`, `u32gt`, `u32gte`, `u32min` and `u32max` comparison instructions (#1358). +- Added support for the `nop` instruction, which corresponds to the VM opcode of the same name, and has the same semantics. +- Added support for the `if.false` instruction, which can be used in the same manner as `if.true` +- Added support for immediate values for `u32and`, `u32or`, `u32xor` and `u32not` bitwise instructions (#1362). +- [BREAKING] Assembler: add the ability to compile MAST libraries, and to assemble a program using compiled libraries (#1401) + +#### Enhancements + +- Changed MAST to a table-based representation (#1349). +- Introduced `MastForestStore` (#1359). +- Adjusted prover's metal acceleration code to work with 0.9 versions of the crates (#1357). +- Relaxed the parser to allow one branch of an `if.(true|false)` to be empty. +- Optimized `std::sys::truncate_stuck` procedure (#1384). +- Updated CI and Makefile to standardize it across Miden repositories (#1342). +- Add serialization/deserialization for `MastForest` (#1370). +- Updated CI to support `CHANGELOG.md` modification checking and `no changelog` label (#1406). +- Introduced `MastForestError` to enforce `MastForest` node count invariant (#1394). +- Added functions to `MastForestBuilder` to allow ensuring of nodes with fewer LOC (#1404). +- [BREAKING] Made `Assembler` single-use (#1409). +- Removed `ProcedureCache` from the assembler (#1411). +- Added functions to `MastForest` and `MastForestBuilder` to add and ensure nodes with fewer LOC (#1404, #1412). +- Added `Assembler::assemble_library()` and `Assembler::assemble_kernel()` (#1413, #1418). +- Added `miden_core::prettier::pretty_print_csv` helper, for formatting of iterators over `PrettyPrint` values as comma-separated items. +- Added source code management primitives in `miden-core` (#1419). +- Added `make test-fast` and `make test-skip-proptests` Makefile targets for faster testing during local development. +- Added `ProgramFile::read_with` constructor that takes a `SourceManager` impl to use for source management. +- Added `RowIndex(u32)` (#1408). + +#### Changed + +- When using `if.(true|false) .. end`, the parser used to emit an empty block for the branch that was elided. The parser now emits a block containing a single `nop` instruction instead. +- [BREAKING] `internals` configuration feature was renamed to `testing` (#1399). +- The `AssemblyOp` decorator now contains an optional `Location` (#1419) +- The `Assembler` now requires passing in a `Arc`, for use in rendering diagnostics. +- The `Module::parse_file` and `Module::parse_str` functions have been removed in favor of calling `Module::parser` and then using the `ModuleParser` methods. +- The `Compile` trait now requires passing a `SourceManager` reference along with the item to be compiled. +- Update minimum supported Rust version to 1.80 (#1425). + +## 0.9.2 (2024-05-22) - `stdlib` crate only + +- Skip writing MASM documentation to file when building on docs.rs (#1341). + +## 0.9.2 (2024-05-09) - `assembly` crate only + +- Remove usage of `group_vector_elements()` from `combine_blocks()` (#1331). + +## 0.9.2 (2024-04-25) - `air` and `processor` crates only + +- Allowed enabling debug mode via `ExecutionOptions` (#1316). + +## 0.9.1 (2024-04-04) + +- Added additional trait implementations to error types (#1306). + +## 0.9.0 (2024-04-03) #### Packaging + - [BREAKING] The package `miden-vm` crate was renamed from `miden` to `miden-vm`. Now the package and crate names match (#1271). #### Stdlib + - Added `init_no_padding` procedure to `std::crypto::hashes::native` (#1313). - [BREAKING] `native` module was renamed to the `pro`, `hash_memory` procedure was renamed to the `hash_memory_words` (#1368). - Added `hash_memory` procedure to `std::crypto::hashes::rpo` (#1368). #### VM Internals + - Removed unused `find_lone_leaf()` function from the Advice Provider (#1262). - [BREAKING] Changed fields type of the `StackOutputs` struct from `Vec` to `Vec` (#1268). - [BREAKING] Migrated to `miden-crypto` v0.9.0 (#1287). @@ -18,6 +170,7 @@ ## 0.8.0 (02-26-2024) #### Assembly + - Expanded capabilities of the `debug` decorator. Added `debug.mem` and `debug.local` variations (#1103). - Introduced the `emit.` assembly instruction (#1119). - Introduced the `procref.` assembly instruction (#1113). @@ -28,7 +181,8 @@ - Added the `RCombBase` instruction (#1216). #### Stdlib -- Introduced `std::utils` module with `is_empty_word` procedure. Refactored `std::collections::smt` + +- Introduced `std::utils` module with `is_empty_word` procedure. Refactored `std::collections::smt` and `std::collections::smt64` to use the procedure (#1107). - [BREAKING] Removed `checked` versions of the instructions in the `std::math::u64` module (#1142). - Introduced `clz`, `ctz`, `clo` and `cto` instructions in the `std::math::u64` module (#1179). @@ -36,6 +190,7 @@ - [BREAKING] Removed `std::collections::smt64` (#1249) #### VM Internals + - Introduced the `Event` decorator and an associated `on_event` handler on the `Host` trait (#1119). - Added methods `StackOutputs::get_stack_item()` and `StackOutputs::get_stack_word()` (#1155). - Added [Tracing](https://crates.io/crates/tracing) logger to the VM (#1139). @@ -47,12 +202,14 @@ - Increased min version of `rustc` to 1.75. #### CLI + - Introduced the `!use` command for the Miden REPL (#1162). - Introduced a `BLAKE3` hashing example (#1180). ## 0.7.0 (2023-10-11) #### Assembly + - Added ability to attach doc comments to re-exported procedures (#994). - Added support for nested modules (#992). - Added support for the arithmetic expressions in constant values (#1026). @@ -63,9 +220,11 @@ - Refactored `push` instruction so now it parses long hex string in little-endian (#1076). #### CLI + - Implemented ability to output compiled `.masb` files to disk (#1102). #### VM Internals + - Simplified range checker and removed 1 main and 1 auxiliary trace column (#949). - Migrated range checker lookups to use LogUp and reduced the number of trace columns to 2 main and 1 auxiliary (#1027). @@ -79,6 +238,7 @@ - [BREAKING] Refactored `AdviceProvider` interface into `Host` interface (#1082). #### Stdlib + - Completed `std::collections::smt` module by implementing `insert` and `set` procedures (#1036, #1038, #1046). - Added new module `std::crypto::dsa::rpo_falcon512` to support Falcon signature verification (#1000, #1094) @@ -89,6 +249,7 @@ ## 0.6.0 (2023-06-28) #### Assembly + - Added new instructions: `mtree_verify`. - [BREAKING] Refactored `adv.mem` decorator to use parameters from operand stack instead of immediate values. - [BREAKING] Refactored `mem_stream` and `adv_pipe` instructions. @@ -99,10 +260,12 @@ - Implemented procedure re-exports from modules. #### CLI + - Implemented support for all types of nondeterministic inputs (advice stack, advice map, and Merkle store). - Implemented ability to generate proofs suitable for recursion. #### Stdlib + - Added new module: `std::collections::smt` (only `smt::get` available). - Added new module: `std::collections::mmr`. - Added new module: `std::collections::smt64`. @@ -111,6 +274,7 @@ - Greatly optimized recursive STARK verifier (reduced number of cycles by 6x - 8x). #### VM Internals + - Moved test framework from `miden-vm` crate to `miden-test-utils` crate. - Updated Winterfell dependency to v0.6.4. - Added support for GPU acceleration on Apple silicon (Metal). @@ -122,16 +286,19 @@ ## 0.5.0 (2023-03-29) #### CLI + - Renamed `ProgramInfo` to `ExecutionDetails` since there is another `ProgramInfo` struct in the source code. - [BREAKING] renamed `stack_init` and `advice_tape` to `operand_stack` and `advice_stack` in input files. - Enabled specifying additional advice provider inputs (i.e., advice map and Merkle store) via the input files. #### Assembly + - Added new instructions: `is_odd`, `assert_eqw`, `mtree_merge`. - [BREAKING] Removed `mtree_cwm` instruction. - Added `breakpoint` instruction to help with debugging. #### VM Internals + - [BREAKING] Renamed `Read`, `ReadW` operations into `AdvPop`, `AdvPopW`. - [BREAKING] Replaced `AdviceSet` with `MerkleStore`. - Updated Winterfell dependency to v0.6.0. @@ -140,15 +307,18 @@ ## 0.4.0 (2023-02-27) #### Advice provider + - [BREAKING] Converted `AdviceProvider` into a trait which can be provided to the processor. - Added a decorator for interpolating polynomials over degree 2 extension field (`ext2intt`). - Added `AdviceSource` enum for greater future flexibility of advice injectors. #### CLI + - Added `debug` subcommand to enable stepping through program execution forward/backward. - Added cycle count to the output of program execution. #### Assembly + - Added support for constant declarations. - Added new instructions: `clk`, `ext2*`, `fri_ext2fold4`, `hash`, `u32checked_popcnt`, `u32unchecked_popcnt`. - [BREAKING] Renamed `rpperm` to `hperm` and `rphash` to `hmerge`. @@ -157,15 +327,18 @@ - [BREAKING] Replaced `ModuleProvider` with `Library` to improve 3rd party library support. #### Processor, Prover, and Verifier + - [BREAKING] Refactored `execute()`, `prove()`, `verify()` functions to take `StackInputs` as one of the parameters. - [BREAKING] Refactored `prove()` function to return `ExecutionProof` (which is a wrapper for `StarkProof`). - [BREAKING] Refactored `verify()` function to take `ProgramInfo`, `StackInputs`, and `ExecutionProof` as parameters and return a `u32` indicating security level of the verified proof. #### Stdlib + - Added `std::mem::memcopy` procedure for copying regions of memory. - Added `std::crypto::fri::frie2f4::verify` for verifying FRI proofs over degree 2 extension field. #### VM Internals + - [BREAKING] Migrated to Rescue Prime Optimized hash function. - Updated Winterfell backend to v0.5.1 diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000000..5bee90e488 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2873 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anstyle-parse" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[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 = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" +dependencies = [ + "term", +] + +[[package]] +name = "assert_cmd" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1835b7f27878de8525dc71410b5a31cdcc5f230aed5ba5df968e09c201b23d" +dependencies = [ + "anstyle", + "bstr", + "doc-comment", + "libc", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "backtrace-ext" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50" +dependencies = [ + "backtrace", +] + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +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 = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "blake3" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[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 = "bstr" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" +dependencies = [ + "memchr", + "regex-automata 0.4.8", + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" +version = "1.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9540e661f81799159abee814118cc139a2004b3a3aa3ea37724a1b66530b90e0" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +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.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0956a43b323ac1afaffc053ed5c4b7c1f1800bacd1683c353aabbb752515dd3" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d72166dd41634086d5803a47eb71ae740e61d84709c36f3c34110173db3961b" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + +[[package]] +name = "clipboard-win" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" +dependencies = [ + "error-code", +] + +[[package]] +name = "colorchoice" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +dependencies = [ + "libc", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + +[[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 = "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-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[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 = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "elsa" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98e71ae4df57d214182a2e5cb90230c0192c6ddfcaa05c36453d46a54713e10" +dependencies = [ + "stable_deref_trait", +] + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "error-code" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f" + +[[package]] +name = "escargot" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c000f23e9d459aef148b7267e02b03b94a0aaacf4ec64c65612f67e02f525fb6" +dependencies = [ + "log", + "once_cell", + "serde", + "serde_json", +] + +[[package]] +name = "fastrand" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "generator" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb949699c3e4df3a183b1d2142cb24277057055ed23c68ed58894f76c517223" +dependencies = [ + "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.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "is-terminal" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "is_ci" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +dependencies = [ + "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.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca" +dependencies = [ + "ascii-canvas", + "bit-set", + "ena", + "itertools 0.11.0", + "lalrpop-util", + "petgraph", + "regex", + "regex-syntax 0.8.5", + "string_cache", + "term", + "tiny-keccak", + "unicode-xid", + "walkdir", +] + +[[package]] +name = "lalrpop-util" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +dependencies = [ + "value-bag", +] + +[[package]] +name = "logtest" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3e43a8657c1d64516dcc9db8ca03826a4aceaf89d5ce1b37b59f6ff0e43026" +dependencies = [ + "lazy_static", + "log", +] + +[[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 = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "metal" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43f73953f8cbe511f021b58f18c3ce1c3d1ae13fe953293e13345bf83217f25" +dependencies = [ + "bitflags 2.6.0", + "block", + "core-graphics-types", + "foreign-types", + "log", + "objc", + "paste", +] + +[[package]] +name = "miden-air" +version = "0.10.5" +dependencies = [ + "criterion", + "miden-core", + "miden-thiserror", + "proptest", + "winter-air", + "winter-prover", + "winter-rand-utils", +] + +[[package]] +name = "miden-assembly" +version = "0.10.5" +dependencies = [ + "aho-corasick", + "lalrpop", + "lalrpop-util", + "miden-core", + "miden-miette", + "miden-thiserror", + "pretty_assertions", + "regex", + "rustc_version 0.4.1", + "smallvec", + "tracing", + "unicode-width", +] + +[[package]] +name = "miden-core" +version = "0.10.5" +dependencies = [ + "lock_api", + "loom", + "memchr", + "miden-crypto", + "miden-formatting", + "miden-miette", + "miden-thiserror", + "num-derive", + "num-traits", + "parking_lot", + "proptest", + "winter-math", + "winter-rand-utils", + "winter-utils", +] + +[[package]] +name = "miden-crypto" +version = "0.10.1" +source = "git+https://github.com/0xPolygonMiden/crypto?branch=al-rpo-new-padding-rule#c83729c46fceb4b0c18e69cec016d25df9cfb742" +dependencies = [ + "blake3", + "cc", + "glob", + "num", + "num-complex", + "rand", + "rand_core", + "sha3", + "winter-crypto", + "winter-math", + "winter-utils", +] + +[[package]] +name = "miden-formatting" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e392e0a8c34b32671012b439de35fa8987bf14f0f8aac279b97f8b8cc6e263b" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "miden-gpu" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade33603aa2eaf78c6f06fd60f4dfe22b7ae1f5606698e386baf71eb9d246d50" +dependencies = [ + "metal", + "once_cell", + "winter-math", +] + +[[package]] +name = "miden-miette" +version = "7.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c532250422d933f15b148fb81e4522a5d649c178ab420d0d596c86228da35570" +dependencies = [ + "backtrace", + "backtrace-ext", + "cfg-if", + "futures", + "indenter", + "lazy_static", + "miden-miette-derive", + "miden-thiserror", + "owo-colors", + "regex", + "rustc_version 0.2.3", + "rustversion", + "serde_json", + "spin", + "strip-ansi-escapes", + "supports-color", + "supports-hyperlinks", + "supports-unicode", + "syn", + "terminal_size", + "textwrap", + "trybuild", + "unicode-width", +] + +[[package]] +name = "miden-miette-derive" +version = "7.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cc759f0a2947acae217a2f32f722105cacc57d17d5f93bc16362142943a4edd" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "miden-processor" +version = "0.10.6" +dependencies = [ + "logtest", + "miden-air", + "miden-assembly", + "miden-core", + "miden-test-utils", + "tracing", + "winter-fri", + "winter-prover", + "winter-utils", +] + +[[package]] +name = "miden-prover" +version = "0.10.5" +dependencies = [ + "elsa", + "miden-air", + "miden-gpu", + "miden-processor", + "pollster", + "tracing", + "winter-prover", +] + +[[package]] +name = "miden-stdlib" +version = "0.10.5" +dependencies = [ + "blake3", + "criterion", + "miden-air", + "miden-assembly", + "miden-processor", + "miden-test-utils", + "num", + "num-bigint", + "pretty_assertions", + "rand", + "serde_json", + "sha2", + "sha3", + "winter-air", + "winter-fri", +] + +[[package]] +name = "miden-test-utils" +version = "0.1.0" +dependencies = [ + "miden-air", + "miden-assembly", + "miden-core", + "miden-processor", + "miden-prover", + "miden-verifier", + "pretty_assertions", + "proptest", + "test-case", + "winter-prover", + "winter-rand-utils", +] + +[[package]] +name = "miden-thiserror" +version = "1.0.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "183ff8de338956ecfde3a38573241eb7a6f3d44d73866c210e5629c07fa00253" +dependencies = [ + "miden-thiserror-impl", +] + +[[package]] +name = "miden-thiserror-impl" +version = "1.0.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee4176a0f2e7d29d2a8ee7e60b6deb14ce67a20e94c3e2c7275cdb8804e1862" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "miden-verifier" +version = "0.10.5" +dependencies = [ + "miden-air", + "miden-core", + "tracing", + "winter-verifier", +] + +[[package]] +name = "miden-vm" +version = "0.10.5" +dependencies = [ + "assert_cmd", + "blake3", + "clap", + "criterion", + "escargot", + "hex", + "miden-assembly", + "miden-core", + "miden-processor", + "miden-prover", + "miden-stdlib", + "miden-test-utils", + "miden-verifier", + "num-bigint", + "predicates", + "rand_chacha", + "rustyline", + "serde", + "serde_derive", + "serde_json", + "tracing", + "tracing-forest", + "tracing-subscriber", + "winter-fri", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "libc", +] + +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + +[[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 = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", + "objc_exception", +] + +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", +] + +[[package]] +name = "object" +version = "0.36.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "oorandom" +version = "11.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb37767f6569cd834a413442455e0f066d0d522de8630436e2a1761d9726ba56" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "pollster" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" + +[[package]] +name = "portable-atomic" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" + +[[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 = "predicates" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97" +dependencies = [ + "anstyle", + "difflib", + "float-cmp", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931" + +[[package]] +name = "predicates-tree" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b740d195ed3166cd147c8047ec98db0e22ec019eb8eeb76d343b795304fb13" +dependencies = [ + "predicates-core", + "termtree", +] + +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags 2.6.0", + "lazy_static", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax 0.8.5", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.8", + "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.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +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-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[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.23", +] + +[[package]] +name = "rustix" +version = "0.38.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +dependencies = [ + "bitflags 2.6.0", + "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" + +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "rustyline" +version = "13.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02a2d683a4ac90aeef5b1013933f6d977bd37d51ff3f4dad829d4931a7e6be86" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "clipboard-win", + "libc", + "log", + "memchr", + "nix", + "unicode-segmentation", + "unicode-width", + "utf8parse", + "winapi", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[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.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[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.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[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 = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[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 = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "string_cache" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "strip-ansi-escapes" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ff8ef943b384c414f54aefa961dd2bd853add74ec75e7ac74cf91dba62bcfa" +dependencies = [ + "vte", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "supports-color" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8775305acf21c96926c900ad056abeef436701108518cf890020387236ac5a77" +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.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +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 = "termtree" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" + +[[package]] +name = "test-case" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8" +dependencies = [ + "test-case-macros", +] + +[[package]] +name = "test-case-core" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "test-case-macros" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "test-case-core", +] + +[[package]] +name = "textwrap" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "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 = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-forest" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee40835db14ddd1e3ba414292272eddde9dad04d3d4b65509656414d1c42592f" +dependencies = [ + "ansi_term", + "smallvec", + "thiserror", + "tracing", + "tracing-subscriber", +] + +[[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.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "trybuild" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "207aa50d36c4be8d8c6ea829478be44a372c6a77669937bb39c698e52f1491e8" +dependencies = [ + "dissimilar", + "glob", + "serde", + "serde_derive", + "serde_json", + "termcolor", + "toml", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "value-bag" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101" + +[[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.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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e369bee1b05d510a7b4ed645f5faa90619e05437111783ea5848f28d97d3c2e" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + +[[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.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +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", + "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 = "web-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[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 0.59.0", +] + +[[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.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +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", +] + +[[package]] +name = "windows-targets" +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_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", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[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.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" +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.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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] + +[[package]] +name = "winter-air" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b72f12b88ebb060b52c0e9aece9bb64a9fc38daf7ba689dd5ce63271b456c883" +dependencies = [ + "libm", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-crypto" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00fbb724d2d9fbfd3aa16ea27f5e461d4fe1d74b0c9e0ed1bf79e9e2a955f4d5" +dependencies = [ + "blake3", + "sha3", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-fri" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab6077cf4c23c0411f591f4ba29378e27f26acb8cef3c51cadd93daaf6080b3" +dependencies = [ + "winter-crypto", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-math" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0e685b3b872d82e58a86519294a814b7bc7a4d3cd2c93570a7d80c0c5a1aba" +dependencies = [ + "winter-utils", +] + +[[package]] +name = "winter-maybe-async" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ce0f4161cdde50de809b3869c1cb083a09e92e949428ea28f04c0d64045875c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "winter-prover" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17e3dbae97050f58e01ed4f12906e247841575a0518632e052941a1c37468df" +dependencies = [ + "tracing", + "winter-air", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-maybe-async", + "winter-utils", +] + +[[package]] +name = "winter-rand-utils" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b827c901ab0c316d89812858ff451d60855c0a5c7ae734b098c62a28624181" +dependencies = [ + "rand", + "winter-utils", +] + +[[package]] +name = "winter-utils" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "961e81e9388877a25db1c034ba38253de2055f569633ae6a665d857a0556391b" +dependencies = [ + "rayon", +] + +[[package]] +name = "winter-verifier" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "324002ade90f21e85599d51a232a80781efc8cb46f511f8bc89f9c5a4eb9cb65" +dependencies = [ + "winter-air", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-utils", +] + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 55bb2d18ab..ac69f241ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,16 @@ members = [ ] resolver = "2" +[workspace.package] +edition = "2021" +rust-version = "1.80" +license = "MIT" +readme = "README.md" +authors = ["Miden contributors"] +homepage = "https://polygon.technology/polygon-miden" +repository = "https://github.com/0xPolygonMiden/miden-vm" +exclude = [".github/"] + [profile.optimized] inherits = "release" codegen-units = 1 @@ -22,7 +32,3 @@ inherits = "release" debug = true debug-assertions = true overflow-checks = true - -[patch.crates-io] -thiserror = { git = "https://github.com/bitwalker/thiserror", branch = "no-std" } -miette = { git = "https://github.com/bitwalker/miette", branch = "no-std" } diff --git a/LICENSE b/LICENSE index de9eed0ce1..29e2918f33 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 Polygon (previously Matic) +Copyright (c) 2024 Polygon (previously Matic) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile index f528581586..208d074474 100644 --- a/Makefile +++ b/Makefile @@ -1,27 +1,118 @@ -FEATURES_INTERNALS=--features internals +.DEFAULT_GOAL := help + +.PHONY: help +help: + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + +# -- variables -------------------------------------------------------------------------------------- + +WARNINGS=RUSTDOCFLAGS="-D warnings" +DEBUG_ASSERTIONS=RUSTFLAGS="-C debug-assertions" FEATURES_CONCURRENT_EXEC=--features concurrent,executable FEATURES_LOG_TREE=--features concurrent,executable,tracing-forest FEATURES_METAL_EXEC=--features concurrent,executable,metal -PROFILE_OPTIMIZED=--profile optimized -PROFILE_TEST=--profile test-release -bench: - cargo bench $(PROFILE_OPTIMIZED) +# -- linting -------------------------------------------------------------------------------------- + +.PHONY: clippy +clippy: ## Runs Clippy with configs + cargo +nightly clippy --workspace --all-targets --all-features -- -D warnings + + +.PHONY: fix +fix: ## Runs Fix with configs + cargo +nightly fix --allow-staged --allow-dirty --all-targets --all-features + + +.PHONY: format +format: ## Runs Format using nightly toolchain + cargo +nightly fmt --all + + +.PHONY: format-check +format-check: ## Runs Format using nightly toolchain but only in check mode + cargo +nightly fmt --all --check + + +.PHONY: lint +lint: format fix clippy ## Runs all linting tasks at once (Clippy, fixing, formatting) + +# --- docs ---------------------------------------------------------------------------------------- + +.PHONY: doc +doc: ## Generates & checks documentation + $(WARNINGS) cargo doc --all-features --keep-going --release + +.PHONY: mdbook +mdbook: ## Generates mdbook documentation + mdbook build docs/ + +# --- testing ------------------------------------------------------------------------------------- + +.PHONY: test +test: ## Runs all tests with the release profile + $(DEBUG_ASSERTIONS) cargo nextest run --cargo-profile test-release --features testing + +.PHONY: test-fast +test-fast: ## Runs all tests with the debug profile + $(DEBUG_ASSERTIONS) cargo nextest run --features testing + +.PHONY: test-skip-proptests +test-skip-proptests: ## Runs all tests, except property-based tests + $(DEBUG_ASSERTIONS) cargo nextest run --features testing -E 'not test(#*proptest)' + +.PHONY: test-loom +test-loom: ## Runs all loom-based tests + RUSTFLAGS="--cfg loom" cargo nextest run --cargo-profile test-release --features testing -E 'test(#*loom)' + +.PHONY: test-package +test-package: ## Tests specific package: make test-package package=miden-vm + $(DEBUG_ASSERTIONS) cargo nextest run --cargo-profile test-release --features testing -p $(package) + +# --- checking ------------------------------------------------------------------------------------ + +.PHONY: check +check: ## Checks all targets and features for errors without code generation + cargo check --all-targets --all-features + +# --- building ------------------------------------------------------------------------------------ + +.PHONY: build +build: ## Builds with default parameters + cargo build --release --features concurrent + +.PHONY: build-no-std +build-no-std: ## Builds without the standard library + cargo build --no-default-features --target wasm32-unknown-unknown --workspace + +# --- executable ------------------------------------------------------------------------------------ + +.PHONY: exec +exec: ## Builds an executable with optimized profile and features + cargo build --profile optimized $(FEATURES_CONCURRENT_EXEC) + +.PHONY: exec-single +exec-single: ## Builds a single-threaded executable + cargo build --profile optimized --features executable -exec: - cargo build $(PROFILE_OPTIMIZED) $(FEATURES_CONCURRENT_EXEC) +.PHONY: exec-metal +exec-metal: ## Builds an executable with Metal acceleration enabled + cargo build --profile optimized $(FEATURES_METAL_EXEC) -exec-metal: - cargo build $(PROFILE_OPTIMIZED) $(FEATURES_METAL_EXEC) +.PHONY: exec-avx2 +exec-avx2: ## Builds an executable with AVX2 acceleration enabled + RUSTFLAGS="-C target-feature=+avx2" cargo build --profile optimized $(FEATURES_CONCURRENT_EXEC) -exec-avx2: - RUSTFLAGS="-C target-feature=+avx2" cargo build $(PROFILE_OPTIMIZED) $(FEATURES_CONCURRENT_EXEC) +.PHONY: exec-sve +exec-sve: ## Builds an executable with SVE acceleration enabled + RUSTFLAGS="-C target-feature=+sve" cargo build --profile optimized $(FEATURES_CONCURRENT_EXEC) -exec-sve: - RUSTFLAGS="-C target-feature=+sve" cargo build $(PROFILE_OPTIMIZED) $(FEATURES_CONCURRENT_EXEC) +.PHONY: exec-info +exec-info: ## Builds an executable with log tree enabled + cargo build --profile optimized $(FEATURES_LOG_TREE) -exec-info: - cargo build $(PROFILE_OPTIMIZED) $(FEATURES_LOG_TREE) +# --- benchmarking -------------------------------------------------------------------------------- -test: - cargo test $(PROFILE_TEST) $(FEATURES_INTERNALS) +.PHONY: bench +bench: ## Runs benchmarks + cargo bench --profile optimized diff --git a/README.md b/README.md index 51865e4501..3a45e1775f 100644 --- a/README.md +++ b/README.md @@ -1,71 +1,83 @@ # Miden Virtual Machine - - - - +[![LICENSE](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/0xPolygonMiden/miden-vm/blob/main/LICENSE) +[![Test](https://github.com/0xPolygonMiden/miden-vm/actions/workflows/test.yml/badge.svg)](https://github.com/0xPolygonMiden/miden-vm/actions/workflows/test.yml) +[![Build](https://github.com/0xPolygonMiden/miden-vm/actions/workflows/build.yml/badge.svg)](https://github.com/0xPolygonMiden/miden-vm/actions/workflows/build.yml) +[![RUST_VERSION](https://img.shields.io/badge/rustc-1.80+-lightgray.svg)](https://www.rust-lang.org/tools/install) +[![Crates.io](https://img.shields.io/crates/v/miden-vm)](https://crates.io/crates/miden-vm) A STARK-based virtual machine. **WARNING:** This project is in an alpha stage. It has not been audited and may contain bugs and security flaws. This implementation is NOT ready for production use. +**WARNING:** For `no_std`, only the `wasm32-unknown-unknown` and `wasm32-wasip1` targets are officially supported. + ## Overview + Miden VM is a zero-knowledge virtual machine written in Rust. For any program executed on Miden VM, a STARK-based proof of execution is automatically generated. This proof can then be used by anyone to verify that the program was executed correctly without the need for re-executing the program or even knowing the contents of the program. -* If you'd like to learn more about how Miden VM works, check out the [documentation](https://0xpolygonmiden.github.io/miden-vm/). -* If you'd like to start using Miden VM, check out the [miden](miden) crate. -* If you'd like to learn more about STARKs, check out the [references](#references) section. +- If you'd like to learn more about how Miden VM works, check out the [documentation](https://0xpolygonmiden.github.io/miden-vm/). +- If you'd like to start using Miden VM, check out the [miden](miden) crate. +- If you'd like to learn more about STARKs, check out the [references](#references) section. ### Status and features -Miden VM is currently on release v0.8. In this release, most of the core features of the VM have been stabilized, and most of the STARK proof generation has been implemented. While we expect to keep making changes to the VM internals, the external interfaces should remain relatively stable, and we will do our best to minimize the amount of breaking changes going forward. + +Miden VM is currently on release v0.10. In this release, most of the core features of the VM have been stabilized, and most of the STARK proof generation has been implemented. While we expect to keep making changes to the VM internals, the external interfaces should remain relatively stable, and we will do our best to minimize the amount of breaking changes going forward. The next version of the VM is being developed in the [next](https://github.com/0xPolygonMiden/miden-vm/tree/next) branch. There is also a documentation for the latest features and changes in the next branch [documentation next branch](https://0xpolygonmiden.github.io/miden-vm/intro/main.html). #### Feature highlights + Miden VM is a fully-featured virtual machine. Despite being optimized for zero-knowledge proof generation, it provides all the features one would expect from a regular VM. To highlight a few: -* **Flow control.** Miden VM is Turing-complete and supports familiar flow control structures such as conditional statements and counter/condition-controlled loops. There are no restrictions on the maximum number of loop iterations or the depth of control flow logic. -* **Procedures.** Miden assembly programs can be broken into subroutines called *procedures*. This improves code modularity and helps reduce the size of Miden VM programs. -* **Execution contexts.** Miden VM program execution can span multiple isolated contexts, each with its own dedicated memory space. The contexts are separated into the *root context* and *user contexts*. The root context can be accessed from user contexts via customizable kernel calls. -* **Memory.** Miden VM supports read-write random-access memory. Procedures can reserve portions of global memory for easier management of local variables. -* **u32 operations.** Miden VM supports native operations with 32-bit unsigned integers. This includes basic arithmetic, comparison, and bitwise operations. -* **Cryptographic operations.** Miden assembly provides built-in instructions for computing hashes and verifying Merkle paths. These instructions use the Rescue Prime Optimized hash function (which is the native hash function of the VM). -* **External libraries.** Miden VM supports compiling programs against pre-defined libraries. The VM ships with one such library: Miden `stdlib` which adds support for such things as 64-bit unsigned integers. Developers can build other similar libraries to extend the VM's functionality in ways which fit their use cases. -* **Nondeterminism**. Unlike traditional virtual machines, Miden VM supports nondeterministic programming. This means a prover may do additional work outside of the VM and then provide execution *hints* to the VM. These hints can be used to dramatically speed up certain types of computations, as well as to supply secret inputs to the VM. -* **Customizable hosts.** Miden VM can be instantiated with user-defined hosts. These hosts are used to supply external data to the VM during execution/proof generation (via nondeterministic inputs) and can connect the VM to arbitrary data sources (e.g., a database or RPC calls). +- **Flow control.** Miden VM is Turing-complete and supports familiar flow control structures such as conditional statements and counter/condition-controlled loops. There are no restrictions on the maximum number of loop iterations or the depth of control flow logic. +- **Procedures.** Miden assembly programs can be broken into subroutines called _procedures_. This improves code modularity and helps reduce the size of Miden VM programs. +- **Execution contexts.** Miden VM program execution can span multiple isolated contexts, each with its own dedicated memory space. The contexts are separated into the _root context_ and _user contexts_. The root context can be accessed from user contexts via customizable kernel calls. +- **Memory.** Miden VM supports read-write random-access memory. Procedures can reserve portions of global memory for easier management of local variables. +- **u32 operations.** Miden VM supports native operations with 32-bit unsigned integers. This includes basic arithmetic, comparison, and bitwise operations. +- **Cryptographic operations.** Miden assembly provides built-in instructions for computing hashes and verifying Merkle paths. These instructions use the Rescue Prime Optimized hash function (which is the native hash function of the VM). +- **External libraries.** Miden VM supports compiling programs against pre-defined libraries. The VM ships with one such library: Miden `stdlib` which adds support for such things as 64-bit unsigned integers. Developers can build other similar libraries to extend the VM's functionality in ways which fit their use cases. +- **Nondeterminism**. Unlike traditional virtual machines, Miden VM supports nondeterministic programming. This means a prover may do additional work outside of the VM and then provide execution _hints_ to the VM. These hints can be used to dramatically speed up certain types of computations, as well as to supply secret inputs to the VM. +- **Customizable hosts.** Miden VM can be instantiated with user-defined hosts. These hosts are used to supply external data to the VM during execution/proof generation (via nondeterministic inputs) and can connect the VM to arbitrary data sources (e.g., a database or RPC calls). #### Planned features + In the coming months we plan to finalize the design of the VM and implement support for the following features: -* **Recursive proofs.** Miden VM will soon be able to verify a proof of its own execution. This will enable infinitely recursive proofs, an extremely useful tool for real-world applications. -* **Better debugging.** Miden VM will provide a better debugging experience including the ability to place breakpoints, better source mapping, and more complete program analysis info. -* **Faulty execution.** Miden VM will support generating proofs for programs with faulty execution (a notoriously complex task in ZK context). That is, it will be possible to prove that execution of some program resulted in an error. +- **Recursive proofs.** Miden VM will soon be able to verify a proof of its own execution. This will enable infinitely recursive proofs, an extremely useful tool for real-world applications. +- **Better debugging.** Miden VM will provide a better debugging experience including the ability to place breakpoints, better source mapping, and more complete program analysis info. +- **Faulty execution.** Miden VM will support generating proofs for programs with faulty execution (a notoriously complex task in ZK context). That is, it will be possible to prove that execution of some program resulted in an error. #### Compilation to WebAssembly. + Miden VM is written in pure Rust and can be compiled to WebAssembly. Rust's `std` standard library is enabled as feature by default for most crates. For WASM targets, one can compile with default features disabled by using `--no-default-features` flag. #### Concurrent proof generation + When compiled with `concurrent` feature enabled, the prover will generate STARK proofs using multiple threads. For benefits of concurrent proof generation check out benchmarks below. Internally, we use [rayon](https://github.com/rayon-rs/rayon) for parallel computations. To control the number of threads used to generate a STARK proof, you can use `RAYON_NUM_THREADS` environment variable. ### Project structure + The project is organized into several crates like so: -| Crate | Description | -| ------------------------ | ----------- | -| [core](core) | Contains components defining Miden VM instruction set, program structure, and a set of utility functions used by other crates. | -| [assembly](assembly) | Contains Miden assembler. The assembler is used to compile Miden assembly source code into Miden VM programs. | +| Crate | Description | +| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [core](core) | Contains components defining Miden VM instruction set, program structure, and a set of utility functions used by other crates. | +| [assembly](assembly) | Contains Miden assembler. The assembler is used to compile Miden assembly source code into Miden VM programs. | | [processor](processor) | Contains Miden VM processor. The processor is used to execute Miden programs and to generate program execution traces. These traces are then used by the Miden prover to generate proofs of correct program execution. | -| [air](air) | Contains *algebraic intermediate representation* (AIR) of Miden VM processor logic. This AIR is used by the VM during proof generation and verification processes. | -| [prover](prover) | Contains Miden VM prover. The prover is used to generate STARK proofs attesting to correct execution of Miden VM programs. Internally, the prover uses Miden processor to execute programs. | -| [verifier](verifier) | Contains a light-weight verifier which can be used to verify proofs of program execution generated by Miden VM. | -| [miden](miden) | Aggregates functionality exposed by Miden VM processor, prover, and verifier in a single place, and also provide a CLI interface for Miden VM. | -| [stdlib](stdlib) | Contains Miden standard library. The goal of Miden standard library is to provide highly-optimized and battle-tested implementations of commonly-used primitives. | -| [test-utils](test-utils) | Contains utilities for testing execution of Miden VM programs. | +| [air](air) | Contains _algebraic intermediate representation_ (AIR) of Miden VM processor logic. This AIR is used by the VM during proof generation and verification processes. | +| [prover](prover) | Contains Miden VM prover. The prover is used to generate STARK proofs attesting to correct execution of Miden VM programs. Internally, the prover uses Miden processor to execute programs. | +| [verifier](verifier) | Contains a light-weight verifier which can be used to verify proofs of program execution generated by Miden VM. | +| [miden](miden) | Aggregates functionality exposed by Miden VM processor, prover, and verifier in a single place, and also provide a CLI interface for Miden VM. | +| [stdlib](stdlib) | Contains Miden standard library. The goal of Miden standard library is to provide highly-optimized and battle-tested implementations of commonly-used primitives. | +| [test-utils](test-utils) | Contains utilities for testing execution of Miden VM programs. | ## Performance + The benchmarks below should be viewed only as a rough guide for expected future performance. The reasons for this are twofold: + 1. Not all constraints have been implemented yet, and we expect that there will be some slowdown once constraint evaluation is completed. 2. Many optimizations have not been applied yet, and we expect that there will be some speedup once we dedicate some time to performance optimizations. @@ -73,91 +85,100 @@ Overall, we don't expect the benchmarks to change significantly, but there will A few general notes on performance: -* Execution time is dominated by proof generation time. In fact, the time needed to run the program is usually under 1% of the time needed to generate the proof. -* Proof verification time is really fast. In most cases it is under 1 ms, but sometimes gets as high as 2 ms or 3 ms. -* Proof generation process is dynamically adjustable. In general, there is a trade-off between execution time, proof size, and security level (i.e. for a given security level, we can reduce proof size by increasing execution time, up to a point). -* Both proof generation and proof verification times are greatly influenced by the hash function used in the STARK protocol. In the benchmarks below, we use BLAKE3, which is a really fast hash function. +- Execution time is dominated by proof generation time. In fact, the time needed to run the program is usually under 1% of the time needed to generate the proof. +- Proof verification time is really fast. In most cases it is under 1 ms, but sometimes gets as high as 2 ms or 3 ms. +- Proof generation process is dynamically adjustable. In general, there is a trade-off between execution time, proof size, and security level (i.e. for a given security level, we can reduce proof size by increasing execution time, up to a point). +- Both proof generation and proof verification times are greatly influenced by the hash function used in the STARK protocol. In the benchmarks below, we use BLAKE3, which is a really fast hash function. ### Single-core prover performance + When executed on a single CPU core, the current version of Miden VM operates at around 20 - 25 KHz. In the benchmarks below, the VM executes a [Fibonacci calculator](miden/README.md#fibonacci-calculator) program on Apple M1 Pro CPU in a single thread. The generated proofs have a target security level of 96 bits. -| VM cycles | Execution time | Proving time | RAM consumed | Proof size | -| :-------------: | :------------: | :----------: | :-----------: | :--------: | -| 210 | 1 ms | 60 ms | 20 MB | 46 KB | -| 212 | 2 ms | 180 ms | 52 MB | 56 KB | -| 214 | 8 ms | 680 ms | 240 MB | 65 KB | -| 216 | 28 ms | 2.7 sec | 950 MB | 75 KB | -| 218 | 81 ms | 11.4 sec | 3.7 GB | 87 KB | -| 220 | 310 ms | 47.5 sec | 14 GB | 100 KB | +| VM cycles | Execution time | Proving time | RAM consumed | Proof size | +| :------------: | :------------: | :----------: | :----------: | :--------: | +| 210 | 1 ms | 60 ms | 20 MB | 46 KB | +| 212 | 2 ms | 180 ms | 52 MB | 56 KB | +| 214 | 8 ms | 680 ms | 240 MB | 65 KB | +| 216 | 28 ms | 2.7 sec | 950 MB | 75 KB | +| 218 | 81 ms | 11.4 sec | 3.7 GB | 87 KB | +| 220 | 310 ms | 47.5 sec | 14 GB | 100 KB | As can be seen from the above, proving time roughly doubles with every doubling in the number of cycles, but proof size grows much slower. We can also generate proofs at a higher security level. The cost of doing so is roughly doubling of proving time and roughly 40% increase in proof size. In the benchmarks below, the same Fibonacci calculator program was executed on Apple M1 Pro CPU at 128-bit target security level: -| VM cycles | Execution time | Proving time | RAM consumed | Proof size | -| :-------------: | :------------: | :----------: | :-----------: | :--------: | -| 210 | 1 ms | 120 ms | 30 MB | 61 KB | -| 212 | 2 ms | 460 ms | 106 MB | 77 KB | -| 214 | 8 ms | 1.4 sec | 500 MB | 90 KB | -| 216 | 27 ms | 4.9 sec | 2.0 GB | 103 KB | -| 218 | 81 ms | 20.1 sec | 8.0 GB | 121 KB | -| 220 | 310 ms | 90.3 sec | 20.0 GB | 138 KB | +| VM cycles | Execution time | Proving time | RAM consumed | Proof size | +| :------------: | :------------: | :----------: | :----------: | :--------: | +| 210 | 1 ms | 120 ms | 30 MB | 61 KB | +| 212 | 2 ms | 460 ms | 106 MB | 77 KB | +| 214 | 8 ms | 1.4 sec | 500 MB | 90 KB | +| 216 | 27 ms | 4.9 sec | 2.0 GB | 103 KB | +| 218 | 81 ms | 20.1 sec | 8.0 GB | 121 KB | +| 220 | 310 ms | 90.3 sec | 20.0 GB | 138 KB | ### Multi-core prover performance + STARK proof generation is massively parallelizable. Thus, by taking advantage of multiple CPU cores we can dramatically reduce proof generation time. For example, when executed on an 8-core CPU (Apple M1 Pro), the current version of Miden VM operates at around 140 KHz. And when executed on a 64-core CPU (Amazon Graviton 3), the VM operates at around 250 KHz. In the benchmarks below, the VM executes the same Fibonacci calculator program for 220 cycles at 96-bit target security level: | Machine | Execution time | Proving time | Execution % | Implied Frequency | | ------------------------------ | :------------: | :----------: | :---------: | :---------------: | -| Apple M1 Pro (16 threads) | 310 ms | 7.0 sec | 4.2% | 140 KHz | -| Apple M2 Max (16 threads) | 280 ms | 5.8 sec | 4.5% | 170 KHz | -| AMD Ryzen 9 5950X (16 threads) | 270 ms | 10.0 sec | 2.6% | 100 KHz | -| Amazon Graviton 3 (64 threads) | 330 ms | 3.6 sec | 8.5% | 265 KHz | +| Apple M1 Pro (16 threads) | 310 ms | 7.0 sec | 4.2% | 140 KHz | +| Apple M2 Max (16 threads) | 280 ms | 5.8 sec | 4.5% | 170 KHz | +| AMD Ryzen 9 5950X (16 threads) | 270 ms | 10.0 sec | 2.6% | 100 KHz | +| Amazon Graviton 3 (64 threads) | 330 ms | 3.6 sec | 8.5% | 265 KHz | ### Recursive proofs + Proofs in the above benchmarks are generated using BLAKE3 hash function. While this hash function is very fast, it is not very efficient to execute in Miden VM. Thus, proofs generated using BLAKE3 are not well-suited for recursive proof verification. To support efficient recursive proofs, we need to use an arithmetization-friendly hash function. Miden VM natively supports Rescue Prime Optimized (RPO), which is one such hash function. One of the downsides of arithmetization-friendly hash functions is that they are considerably slower than regular hash functions. In the benchmarks below we execute the same Fibonacci calculator program for 220 cycles at 96-bit target security level using RPO hash function instead of BLAKE3: | Machine | Execution time | Proving time | Proving time (HW) | | ------------------------------ | :------------: | :----------: | :---------------: | -| Apple M1 Pro (16 threads) | 310 ms | 94.3 sec | 42.0 sec | -| Apple M2 Max (16 threads) | 280 ms | 75.1 sec | 20.9 sec | -| AMD Ryzen 9 5950X (16 threads) | 270 ms | 59.3 sec | | -| Amazon Graviton 3 (64 threads) | 330 ms | 21.7 sec | 14.9 sec | +| Apple M1 Pro (16 threads) | 310 ms | 94.3 sec | 42.0 sec | +| Apple M2 Max (16 threads) | 280 ms | 75.1 sec | 20.9 sec | +| AMD Ryzen 9 5950X (16 threads) | 270 ms | 59.3 sec | | +| Amazon Graviton 3 (64 threads) | 330 ms | 21.7 sec | 14.9 sec | In the above, proof generation on some platforms can be hardware-accelerated. Specifically: -* On Apple M1/M2 platforms the built-in GPU is used for a part of proof generation process. -* On the Graviton platform, SVE vector extension is used to accelerate RPO computations. +- On Apple M1/M2 platforms the built-in GPU is used for a part of proof generation process. +- On the Graviton platform, SVE vector extension is used to accelerate RPO computations. ## References + Proofs of execution generated by Miden VM are based on STARKs. A STARK is a novel proof-of-computation scheme that allows you to create an efficiently verifiable proof that a computation was executed correctly. The scheme was developed by Eli Ben-Sasson, Michael Riabzev et al. at Technion - Israel Institute of Technology. STARKs do not require an initial trusted setup, and rely on very few cryptographic assumptions. Here are some resources to learn more about STARKs: -* STARKs whitepaper: [Scalable, transparent, and post-quantum secure computational integrity](https://eprint.iacr.org/2018/046) -* STARKs vs. SNARKs: [A Cambrian Explosion of Crypto Proofs](https://nakamoto.com/cambrian-explosion-of-crypto-proofs/) +- STARKs whitepaper: [Scalable, transparent, and post-quantum secure computational integrity](https://eprint.iacr.org/2018/046) +- STARKs vs. SNARKs: [A Cambrian Explosion of Crypto Proofs](https://nakamoto.com/cambrian-explosion-of-crypto-proofs/) Vitalik Buterin's blog series on zk-STARKs: -* [STARKs, part 1: Proofs with Polynomials](https://vitalik.eth.limo/general/2017/11/09/starks_part_1.html) -* [STARKs, part 2: Thank Goodness it's FRI-day](https://vitalik.eth.limo/general/2017/11/22/starks_part_2.html) -* [STARKs, part 3: Into the Weeds](https://vitalik.eth.limo/general/2018/07/21/starks_part_3.html) + +- [STARKs, part 1: Proofs with Polynomials](https://vitalik.eth.limo/general/2017/11/09/starks_part_1.html) +- [STARKs, part 2: Thank Goodness it's FRI-day](https://vitalik.eth.limo/general/2017/11/22/starks_part_2.html) +- [STARKs, part 3: Into the Weeds](https://vitalik.eth.limo/general/2018/07/21/starks_part_3.html) Alan Szepieniec's STARK tutorials: -* [Anatomy of a STARK](https://aszepieniec.github.io/stark-anatomy/) -* [BrainSTARK](https://aszepieniec.github.io/stark-brainfuck/) + +- [Anatomy of a STARK](https://aszepieniec.github.io/stark-anatomy/) +- [BrainSTARK](https://aszepieniec.github.io/stark-brainfuck/) StarkWare's STARK Math blog series: -* [STARK Math: The Journey Begins](https://medium.com/starkware/stark-math-the-journey-begins-51bd2b063c71) -* [Arithmetization I](https://medium.com/starkware/arithmetization-i-15c046390862) -* [Arithmetization II](https://medium.com/starkware/arithmetization-ii-403c3b3f4355) -* [Low Degree Testing](https://medium.com/starkware/low-degree-testing-f7614f5172db) -* [A Framework for Efficient STARKs](https://medium.com/starkware/a-framework-for-efficient-starks-19608ba06fbe) + +- [STARK Math: The Journey Begins](https://medium.com/starkware/stark-math-the-journey-begins-51bd2b063c71) +- [Arithmetization I](https://medium.com/starkware/arithmetization-i-15c046390862) +- [Arithmetization II](https://medium.com/starkware/arithmetization-ii-403c3b3f4355) +- [Low Degree Testing](https://medium.com/starkware/low-degree-testing-f7614f5172db) +- [A Framework for Efficient STARKs](https://medium.com/starkware/a-framework-for-efficient-starks-19608ba06fbe) StarkWare's STARK tutorial: - * [STARK 101](https://starkware.co/stark-101/) + +- [STARK 101](https://starkware.co/stark-101/) ## License + This project is [MIT licensed](./LICENSE). diff --git a/air/Cargo.toml b/air/Cargo.toml index b093dd9dbc..23c1937131 100644 --- a/air/Cargo.toml +++ b/air/Cargo.toml @@ -1,16 +1,17 @@ [package] name = "miden-air" -version = "0.8.0" +version = "0.10.5" description = "Algebraic intermediate representation of Miden VM processor" -authors = ["miden contributors"] +documentation = "https://docs.rs/miden-air/0.10.5" readme = "README.md" -license = "MIT" -repository = "https://github.com/0xPolygonMiden/miden-vm" -documentation = "https://docs.rs/miden-air/0.8.0" categories = ["cryptography", "no-std"] keywords = ["air", "arithmetization", "crypto", "miden"] -edition = "2021" -rust-version = "1.75" +license.workspace = true +authors.workspace = true +homepage.workspace = true +repository.workspace = true +rust-version.workspace = true +edition.workspace = true [lib] bench = false @@ -26,11 +27,12 @@ harness = false [features] default = ["std"] -std = ["vm-core/std", "winter-air/std"] -internals = [] +std = ["vm-core/std", "winter-air/std", "thiserror/std"] +testing = [] [dependencies] -vm-core = { package = "miden-core", path = "../core", version = "0.8", default-features = false } +thiserror = { package = "miden-thiserror", version = "1.0", default-features = false } +vm-core = { package = "miden-core", path = "../core", version = "0.10", default-features = false } winter-air = { package = "winter-air", version = "0.9", default-features = false } winter-prover = { package = "winter-prover", version = "0.9", default-features = false } diff --git a/air/benches/compute_op_flags.rs b/air/benches/compute_op_flags.rs index 99a89ff690..7e761fd9fb 100644 --- a/air/benches/compute_op_flags.rs +++ b/air/benches/compute_op_flags.rs @@ -1,6 +1,7 @@ +use std::time::Duration; + use criterion::{criterion_group, criterion_main, Criterion}; use miden_air::stack::op_flags::{generate_evaluation_frame, OpFlags}; -use std::time::Duration; fn compute_op_flags(c: &mut Criterion) { let mut group = c.benchmark_group("compute_op_flags"); diff --git a/air/benches/enforce_stack_constraint.rs b/air/benches/enforce_stack_constraint.rs index 097220ab94..3a5a983c06 100644 --- a/air/benches/enforce_stack_constraint.rs +++ b/air/benches/enforce_stack_constraint.rs @@ -1,3 +1,5 @@ +use std::time::Duration; + use criterion::{criterion_group, criterion_main, Criterion}; use miden_air::{ stack::{ @@ -7,7 +9,6 @@ use miden_air::{ trace::STACK_TRACE_OFFSET, Felt, FieldElement, }; -use std::time::Duration; use vm_core::{Operation, ZERO}; fn enforce_stack_constraint(c: &mut Criterion) { diff --git a/air/src/constraints/chiplets/bitwise/mod.rs b/air/src/constraints/chiplets/bitwise/mod.rs index b5450e2066..e1560acd74 100644 --- a/air/src/constraints/chiplets/bitwise/mod.rs +++ b/air/src/constraints/chiplets/bitwise/mod.rs @@ -1,3 +1,7 @@ +use alloc::vec::Vec; + +use winter_air::TransitionConstraintDegree; + use super::{EvaluationFrame, Felt, FieldElement}; use crate::{ trace::chiplets::{ @@ -8,8 +12,6 @@ use crate::{ utils::{are_equal, binary_not, is_binary, is_zero, EvaluationResult}, ONE, ZERO, }; -use alloc::vec::Vec; -use winter_air::TransitionConstraintDegree; #[cfg(test)] pub mod tests; @@ -418,16 +420,3 @@ pub fn agg_bits(row: &[E], start_idx: usize) -> E { pub const BITWISE_K0_MASK: [Felt; OP_CYCLE_LEN] = [ONE, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO]; pub const BITWISE_K1_MASK: [Felt; OP_CYCLE_LEN] = [ONE, ONE, ONE, ONE, ONE, ONE, ONE, ZERO]; - -// TEST HELPERS -// ================================================================================================ - -/// Returns the values from the bitwise periodic columns for the specified cycle row. -#[cfg(test)] -fn get_periodic_values(cycle_row: usize) -> [Felt; 2] { - match cycle_row { - 0 => [ONE, ONE], - 8 => [ZERO, ZERO], - _ => [ZERO, ONE], - } -} diff --git a/air/src/constraints/chiplets/bitwise/tests.rs b/air/src/constraints/chiplets/bitwise/tests.rs index 3609f34d08..b0dd20929c 100644 --- a/air/src/constraints/chiplets/bitwise/tests.rs +++ b/air/src/constraints/chiplets/bitwise/tests.rs @@ -1,8 +1,10 @@ +use proptest::prelude::*; +use rand_utils::rand_value; + use super::{ - enforce_constraints, get_periodic_values, EvaluationFrame, BITWISE_A_COL_IDX, - BITWISE_A_COL_RANGE, BITWISE_B_COL_IDX, BITWISE_B_COL_RANGE, BITWISE_OUTPUT_COL_IDX, - BITWISE_PREV_OUTPUT_COL_IDX, BITWISE_SELECTOR_COL_IDX, NUM_CONSTRAINTS, NUM_DECOMP_BITS, ONE, - OP_CYCLE_LEN, ZERO, + enforce_constraints, EvaluationFrame, BITWISE_A_COL_IDX, BITWISE_A_COL_RANGE, + BITWISE_B_COL_IDX, BITWISE_B_COL_RANGE, BITWISE_OUTPUT_COL_IDX, BITWISE_PREV_OUTPUT_COL_IDX, + BITWISE_SELECTOR_COL_IDX, NUM_CONSTRAINTS, NUM_DECOMP_BITS, ONE, OP_CYCLE_LEN, ZERO, }; use crate::{ trace::{ @@ -12,11 +14,8 @@ use crate::{ }, TRACE_WIDTH, }, - Felt, + Felt, RowIndex, }; -use rand_utils::rand_value; - -use proptest::prelude::*; // UNIT TESTS // ================================================================================================ @@ -29,7 +28,7 @@ fn test_bitwise_change_ops_fail() { let a = rand_value::(); let b = rand_value::(); - let cycle_row: usize = rand_value::() as usize % (OP_CYCLE_LEN - 1); + let cycle_row: RowIndex = (rand_value::() as usize % (OP_CYCLE_LEN - 1)).into(); let frame = get_test_frame_with_two_ops(BITWISE_XOR, BITWISE_AND, a, b, cycle_row); let result = get_constraint_evaluation(frame, cycle_row); @@ -45,7 +44,7 @@ fn test_bitwise_change_ops_fail() { /// cycle when the low limb of a is one. #[test] fn output_aggregation_and() { - let cycle_row = 0; + let cycle_row: RowIndex = 0.into(); // create a valid test frame manually let mut current = vec![ZERO; TRACE_WIDTH]; @@ -116,8 +115,8 @@ proptest! { #[test] fn test_bitwise_and(a in any::(), b in any::(), cycle_row in 0..(OP_CYCLE_LEN - 1)) { let expected = [ZERO; NUM_CONSTRAINTS]; - let frame = get_test_frame(BITWISE_AND, a, b, cycle_row); - let result = get_constraint_evaluation(frame, cycle_row); + let frame = get_test_frame(BITWISE_AND, a, b, cycle_row.into()); + let result = get_constraint_evaluation(frame, cycle_row.into()); assert_eq!(expected, result); } @@ -126,8 +125,8 @@ proptest! { #[test] fn test_bitwise_xor(a in any::(), b in any::(), cycle_row in 0..(OP_CYCLE_LEN - 1)) { let expected = [ZERO; NUM_CONSTRAINTS]; - let frame = get_test_frame(BITWISE_XOR, a, b, cycle_row); - let result = get_constraint_evaluation(frame, cycle_row); + let frame = get_test_frame(BITWISE_XOR, a, b, cycle_row.into()); + let result = get_constraint_evaluation(frame, cycle_row.into()); assert_eq!(expected, result); } } @@ -137,7 +136,10 @@ proptest! { /// Returns the result of Bitwise constraint evaluations on the provided frame starting at the /// specified row. -fn get_constraint_evaluation(frame: EvaluationFrame, row: usize) -> [Felt; NUM_CONSTRAINTS] { +fn get_constraint_evaluation( + frame: EvaluationFrame, + row: RowIndex, +) -> [Felt; NUM_CONSTRAINTS] { let periodic_values = get_periodic_values(row); let mut result = [ZERO; NUM_CONSTRAINTS]; @@ -151,16 +153,16 @@ fn get_constraint_evaluation(frame: EvaluationFrame, row: usize) -> [Felt; /// cycle. /// /// # Errors -/// It expects the specified `cycle_row_num` for the current row to be such that the next row will +/// It expects the specified `cycle_row` for the current row to be such that the next row will /// still be in the same cycle. It will fail if the row number input is >= OP_CYCLE_LEN - 1. pub fn get_test_frame( operation: Felt, a: u32, b: u32, - cycle_row_num: usize, + cycle_row: RowIndex, ) -> EvaluationFrame { assert!( - cycle_row_num < OP_CYCLE_LEN - 1, + cycle_row < OP_CYCLE_LEN - 1, "Failed to build test EvaluationFrame for bitwise operation. The next row would be in a new cycle." ); @@ -173,16 +175,16 @@ pub fn get_test_frame( next[BITWISE_SELECTOR_COL_IDX] = operation; // Set the input aggregation and decomposition values. - set_frame_inputs(&mut current, &mut next, a, b, cycle_row_num); + set_frame_inputs(&mut current, &mut next, a, b, cycle_row); // Compute the output for the specified operation and inputs and shift it for each row. - let (previous_shift, current_shift, next_shift) = get_row_shifts(cycle_row_num); + let (previous_shift, current_shift, next_shift) = get_row_shifts(cycle_row); let result = get_output(operation, a, b); let output_current = result >> current_shift; let output_next = result >> next_shift; // Set the previous output. - let output_prev = if cycle_row_num == 0 { + let output_prev = if cycle_row == 0 { ZERO } else { Felt::new((result >> previous_shift) as u64) @@ -202,17 +204,17 @@ pub fn get_test_frame( /// frames within a cycle. /// /// # Errors -/// It expects the specified `cycle_row_num` for the current row to be such that the next row will +/// It expects the specified `cycle_row` for the current row to be such that the next row will /// still be in the same cycle. It will fail if the row number input is >= OP_CYCLE_LEN - 1. pub fn get_test_frame_with_two_ops( op_current: Felt, op_next: Felt, a: u32, b: u32, - cycle_row_num: usize, + cycle_row: RowIndex, ) -> EvaluationFrame { assert!( - cycle_row_num < OP_CYCLE_LEN - 1, + cycle_row < OP_CYCLE_LEN - 1, "Failed to build test EvaluationFrame for bitwise operation. The next row would be in a new cycle." ); @@ -225,16 +227,16 @@ pub fn get_test_frame_with_two_ops( next[BITWISE_SELECTOR_COL_IDX] = op_next; // Set the input aggregation and decomposition values. - set_frame_inputs(&mut current, &mut next, a, b, cycle_row_num); + set_frame_inputs(&mut current, &mut next, a, b, cycle_row); // Compute the outputs for the specified operations and inputs and shift them for each row. - let (previous_shift, current_shift, next_shift) = get_row_shifts(cycle_row_num); + let (previous_shift, current_shift, next_shift) = get_row_shifts(cycle_row); let result_op_current = get_output(op_current, a, b); let output_current = result_op_current >> current_shift; let output_next = get_output(op_next, a, b) >> next_shift; // Set the previous output. - let output_prev = if cycle_row_num == 0 { + let output_prev = if cycle_row == 0 { ZERO } else { Felt::new((result_op_current >> previous_shift) as u64) @@ -249,11 +251,11 @@ pub fn get_test_frame_with_two_ops( EvaluationFrame::::from_rows(current, next) } -/// Returns the shift amount for the previous, current, and next rows, based on the `cycle_row_num`, +/// Returns the shift amount for the previous, current, and next rows, based on the `cycle_row`, /// which is the number of the `current` row within the operation cycle. -fn get_row_shifts(cycle_row_num: usize) -> (usize, usize, usize) { +fn get_row_shifts(cycle_row: RowIndex) -> (usize, usize, usize) { // Define the shift amount for output in this row and the next row. - let current_shift = NUM_DECOMP_BITS * (OP_CYCLE_LEN - cycle_row_num - 1); + let current_shift = NUM_DECOMP_BITS * (OP_CYCLE_LEN - cycle_row.as_usize() - 1); let previous_shift = current_shift + NUM_DECOMP_BITS; let next_shift = current_shift - NUM_DECOMP_BITS; @@ -262,10 +264,10 @@ fn get_row_shifts(cycle_row_num: usize) -> (usize, usize, usize) { /// Sets the input aggregation and decomposition columns in the provided current and next rows with /// the correct values corresponding to the provided inputs `a` and `b` and the specified -/// `cycle_row_num`, which is the number of the `current` row within the operation cycle. -fn set_frame_inputs(current: &mut [Felt], next: &mut [Felt], a: u32, b: u32, cycle_row_num: usize) { +/// `cycle_row`, which is the number of the `current` row within the operation cycle. +fn set_frame_inputs(current: &mut [Felt], next: &mut [Felt], a: u32, b: u32, cycle_row: RowIndex) { // Get the shift amounts for the specified rows. - let (_, current_shift, next_shift) = get_row_shifts(cycle_row_num); + let (_, current_shift, next_shift) = get_row_shifts(cycle_row); // Set the input aggregation values. let current_a = (a >> current_shift) as u64; @@ -297,3 +299,13 @@ fn get_output(operation: Felt, a: u32, b: u32) -> u32 { panic!("Test bitwise EvaluationFrame requested for unrecognized operation."); } } + +/// Returns the values from the bitwise periodic columns for the specified cycle row. +#[cfg(test)] +fn get_periodic_values(cycle_row: crate::RowIndex) -> [Felt; 2] { + match cycle_row.into() { + 0u32 => [ONE, ONE], + 8u32 => [ZERO, ZERO], + _ => [ZERO, ONE], + } +} diff --git a/air/src/constraints/chiplets/hasher/mod.rs b/air/src/constraints/chiplets/hasher/mod.rs index 8df5fca0cb..24f2edaf41 100644 --- a/air/src/constraints/chiplets/hasher/mod.rs +++ b/air/src/constraints/chiplets/hasher/mod.rs @@ -1,3 +1,5 @@ +use alloc::vec::Vec; + use super::{EvaluationFrame, Felt, FieldElement, TransitionConstraintDegree}; use crate::{ trace::chiplets::{ @@ -10,7 +12,6 @@ use crate::{ utils::{are_equal, binary_not, is_binary, EvaluationResult}, ONE, ZERO, }; -use alloc::vec::Vec; #[cfg(test)] mod tests; @@ -100,8 +101,8 @@ pub fn get_transition_constraint_count() -> usize { /// Enforces constraints for the hasher chiplet. /// -/// - The `hasher_flag` determines if the hasher chiplet is currently enabled. It should be -/// computed by the caller and set to `Felt::ONE` +/// - The `hasher_flag` determines if the hasher chiplet is currently enabled. It should be computed +/// by the caller and set to `Felt::ONE` /// - The `transition_flag` indicates whether this is the last row this chiplet's execution trace, /// and therefore the constraints should not be enforced. pub fn enforce_constraints>( diff --git a/air/src/constraints/chiplets/hasher/tests.rs b/air/src/constraints/chiplets/hasher/tests.rs index c36219a655..d1bae05b2c 100644 --- a/air/src/constraints/chiplets/hasher/tests.rs +++ b/air/src/constraints/chiplets/hasher/tests.rs @@ -1,15 +1,17 @@ +use alloc::vec::Vec; + +use rand_utils::rand_array; +use vm_core::chiplets::hasher::apply_round; +use winter_air::EvaluationFrame; + use super::{ enforce_constraints, Hasher, HASHER_NODE_INDEX_COL_IDX, HASHER_SELECTOR_COL_RANGE, HASHER_STATE_COL_RANGE, NUM_CONSTRAINTS, ONE, ZERO, }; use crate::{ trace::chiplets::hasher::{Selectors, LINEAR_HASH, STATE_WIDTH}, - Felt, TRACE_WIDTH, + Felt, RowIndex, TRACE_WIDTH, }; -use alloc::vec::Vec; -use rand_utils::rand_array; -use vm_core::chiplets::hasher::apply_round; -use winter_air::EvaluationFrame; // UNIT TESTS // ================================================================================================ @@ -20,12 +22,12 @@ use winter_air::EvaluationFrame; fn hash_round() { let expected = [ZERO; NUM_CONSTRAINTS]; - let cycle_row_num: usize = 3; + let cycle_row = 3.into(); let current_selectors = [ZERO, LINEAR_HASH[1], LINEAR_HASH[2]]; let next_selectors = current_selectors; - let frame = get_test_hashing_frame(current_selectors, next_selectors, cycle_row_num); - let result = get_constraint_evaluation(frame, cycle_row_num); + let frame = get_test_hashing_frame(current_selectors, next_selectors, cycle_row); + let result = get_constraint_evaluation(frame, cycle_row); assert_eq!(expected, result); } @@ -36,10 +38,10 @@ fn hash_round() { /// the specified row. fn get_constraint_evaluation( frame: EvaluationFrame, - cycle_row_num: usize, + cycle_row: RowIndex, ) -> [Felt; NUM_CONSTRAINTS] { let mut result = [ZERO; NUM_CONSTRAINTS]; - let periodic_values = get_test_periodic_values(cycle_row_num); + let periodic_values = get_test_periodic_values(cycle_row); enforce_constraints(&frame, &periodic_values, &mut result, ONE); @@ -47,12 +49,12 @@ fn get_constraint_evaluation( } /// Returns the values from the periodic columns for the specified cycle row. -fn get_test_periodic_values(cycle_row: usize) -> Vec { +fn get_test_periodic_values(cycle_row: RowIndex) -> Vec { // Set the periodic column values. - let mut periodic_values = match cycle_row { - 0 => vec![ZERO, ZERO, ONE], - 7 => vec![ZERO, ONE, ZERO], - 8 => vec![ONE, ZERO, ZERO], + let mut periodic_values = match cycle_row.into() { + 0u32 => vec![ZERO, ZERO, ONE], + 7u32 => vec![ZERO, ONE, ZERO], + 8u32 => vec![ONE, ZERO, ZERO], _ => vec![ZERO, ZERO, ZERO], }; @@ -70,7 +72,7 @@ fn get_test_periodic_values(cycle_row: usize) -> Vec { fn get_test_hashing_frame( current_selectors: Selectors, next_selectors: Selectors, - cycle_row_num: usize, + cycle_row: RowIndex, ) -> EvaluationFrame { let mut current = vec![ZERO; TRACE_WIDTH]; let mut next = vec![ZERO; TRACE_WIDTH]; @@ -84,7 +86,7 @@ fn get_test_hashing_frame( current[HASHER_STATE_COL_RANGE].copy_from_slice(&state); // Set the hasher state after a single permutation. - apply_round(&mut state, cycle_row_num); + apply_round(&mut state, cycle_row.into()); next[HASHER_STATE_COL_RANGE].copy_from_slice(&state); // Set the node index values to zero for hash computations. diff --git a/air/src/constraints/chiplets/memory/mod.rs b/air/src/constraints/chiplets/memory/mod.rs index b4325c4a02..1f1289be8e 100644 --- a/air/src/constraints/chiplets/memory/mod.rs +++ b/air/src/constraints/chiplets/memory/mod.rs @@ -1,3 +1,7 @@ +use alloc::vec::Vec; + +use winter_air::TransitionConstraintDegree; + use super::{EvaluationFrame, FieldElement}; use crate::{ trace::chiplets::{ @@ -7,8 +11,6 @@ use crate::{ }, utils::{binary_not, is_binary, EvaluationResult}, }; -use alloc::vec::Vec; -use winter_air::TransitionConstraintDegree; #[cfg(test)] mod tests; diff --git a/air/src/constraints/chiplets/memory/tests.rs b/air/src/constraints/chiplets/memory/tests.rs index 48f096f170..cdb34b117d 100644 --- a/air/src/constraints/chiplets/memory/tests.rs +++ b/air/src/constraints/chiplets/memory/tests.rs @@ -1,17 +1,22 @@ +use alloc::vec::Vec; + +use rand_utils::rand_value; + use super::{ EvaluationFrame, MEMORY_ADDR_COL_IDX, MEMORY_CLK_COL_IDX, MEMORY_CTX_COL_IDX, MEMORY_D0_COL_IDX, MEMORY_D1_COL_IDX, MEMORY_D_INV_COL_IDX, MEMORY_V_COL_RANGE, NUM_ELEMENTS, }; -use crate::trace::{ - chiplets::{ - memory::{Selectors, MEMORY_COPY_READ, MEMORY_INIT_READ, MEMORY_WRITE}, - MEMORY_TRACE_OFFSET, +use crate::{ + chiplets::memory, + trace::{ + chiplets::{ + memory::{Selectors, MEMORY_COPY_READ, MEMORY_INIT_READ, MEMORY_WRITE}, + MEMORY_TRACE_OFFSET, + }, + TRACE_WIDTH, }, - TRACE_WIDTH, + Felt, FieldElement, ONE, ZERO, }; -use crate::{chiplets::memory, Felt, FieldElement, ONE, ZERO}; -use alloc::vec::Vec; -use rand_utils::rand_value; // UNIT TESTS // ================================================================================================ @@ -106,7 +111,7 @@ enum MemoryTestDeltaType { /// - To test a valid write, the MemoryTestDeltaType must be Context or Address and the `old_values` /// and `new_values` must change. /// - To test a valid read, the `delta_type` must be Clock and the `old_values` and `new_values` -/// must be equal. +/// must be equal. fn get_constraint_evaluation( selectors: Selectors, delta_type: MemoryTestDeltaType, @@ -204,7 +209,7 @@ fn get_test_delta_row(delta_type: &MemoryTestDeltaType) -> Vec { // Set addr and clock in the row column to random values. row[addr_idx] = rand_value::() as u64; row[clk_idx] = rand_value::() as u64; - } + }, MemoryTestDeltaType::Address => { // Keep the context value the same in current and row rows (leave it as ZERO). // Set the row value for the address. @@ -212,12 +217,12 @@ fn get_test_delta_row(delta_type: &MemoryTestDeltaType) -> Vec { // Set clock in the row column to a random value. row[clk_idx] = rand_value::() as u64; - } + }, MemoryTestDeltaType::Clock => { // Keep the context and address values the same in the current and row rows. // Set the current and row values for the clock. row[clk_idx] = delta_value; - } + }, } row diff --git a/air/src/constraints/chiplets/mod.rs b/air/src/constraints/chiplets/mod.rs index ec2772940f..ea950755fc 100644 --- a/air/src/constraints/chiplets/mod.rs +++ b/air/src/constraints/chiplets/mod.rs @@ -1,8 +1,9 @@ +use alloc::vec::Vec; + use super::super::{ EvaluationFrame, Felt, FieldElement, TransitionConstraintDegree, CHIPLETS_OFFSET, }; use crate::utils::{are_equal, binary_not, is_binary}; -use alloc::vec::Vec; mod bitwise; mod hasher; diff --git a/air/src/constraints/mod.rs b/air/src/constraints/mod.rs index ae7a7f8f62..a376dc1fa4 100644 --- a/air/src/constraints/mod.rs +++ b/air/src/constraints/mod.rs @@ -1,9 +1,11 @@ use super::{EvaluationFrame, ExtensionOf, Felt, FieldElement}; -use crate::trace::{ - chiplets::{MEMORY_D0_COL_IDX, MEMORY_D1_COL_IDX}, - decoder::{DECODER_OP_BITS_OFFSET, DECODER_USER_OP_HELPERS_OFFSET}, +use crate::{ + trace::{ + chiplets::{MEMORY_D0_COL_IDX, MEMORY_D1_COL_IDX}, + decoder::{DECODER_OP_BITS_OFFSET, DECODER_USER_OP_HELPERS_OFFSET}, + }, + utils::binary_not, }; -use crate::utils::binary_not; pub mod chiplets; pub mod range; diff --git a/air/src/constraints/range.rs b/air/src/constraints/range.rs index e6832ab10b..76829acf78 100644 --- a/air/src/constraints/range.rs +++ b/air/src/constraints/range.rs @@ -1,3 +1,7 @@ +use alloc::vec::Vec; + +use vm_core::{ExtensionOf, ZERO}; + use crate::{ chiplets::ChipletsFrameExt, constraints::MainFrameExt, @@ -5,9 +9,6 @@ use crate::{ utils::are_equal, Assertion, EvaluationFrame, Felt, FieldElement, TransitionConstraintDegree, }; -use alloc::vec::Vec; - -use vm_core::{ExtensionOf, ZERO}; // CONSTANTS // ================================================================================================ diff --git a/air/src/constraints/stack/field_ops/mod.rs b/air/src/constraints/stack/field_ops/mod.rs index f185e255c9..cefcb38c9b 100644 --- a/air/src/constraints/stack/field_ops/mod.rs +++ b/air/src/constraints/stack/field_ops/mod.rs @@ -1,9 +1,10 @@ +use alloc::vec::Vec; + use super::{op_flags::OpFlags, EvaluationFrame, FieldElement, TransitionConstraintDegree}; use crate::{ stack::EvaluationFrameExt, utils::{are_equal, is_binary}, }; -use alloc::vec::Vec; #[cfg(test)] pub mod tests; @@ -98,8 +99,10 @@ pub fn enforce_constraints( // TRANSITION CONSTRAINT HELPERS // ================================================================================================ -/// Enforces constraints of the ADD operation. The ADD operation adds the first two elements -/// in the current trace. Therefore, the following constraints are enforced: +/// Enforces constraints of the ADD operation. +/// +/// The ADD operation adds the first two elements in the current trace. Therefore, the following +/// constraints are enforced: /// - The first element in the trace frame should be the addition of the first two elements in the /// current trace. s0` - s0 - s1 = 0. pub fn enforce_add_constraints( @@ -117,8 +120,10 @@ pub fn enforce_add_constraints( 1 } -/// Enforces constraints of the NEG operation. The NEG operation updates the top element in the -/// stack with its inverse. Therefore, the following constraints are enforced: +/// Enforces constraints of the NEG operation. +/// +/// The NEG operation updates the top element in the stack with its inverse. Therefore, the +/// following constraints are enforced: /// - The first element in the next frame should be the negation of first element in the current /// frame, therefore, their sum should be 0. s0` + s0 = 0. pub fn enforce_neg_constraints( @@ -133,8 +138,10 @@ pub fn enforce_neg_constraints( 1 } -/// Enforces constraints of the MUL operation. The MUL operation multiplies the first two elements -/// in the current trace. Therefore, the following constraints are enforced: +/// Enforces constraints of the MUL operation. +/// +/// The MUL operation multiplies the first two elements in the current trace. Therefore, the +/// following constraints are enforced: /// - The first element in the next frame should be the product of the first two elements in the /// current frame. s0` - s0 * s1 = 0 pub fn enforce_mul_constraints( @@ -152,8 +159,10 @@ pub fn enforce_mul_constraints( 1 } -/// Enforces constraints of the INV operation. The INV operation updates the top element -/// in the stack with its inverse. Therefore, the following constraints are enforced: +/// Enforces constraints of the INV operation. +/// +/// The INV operation updates the top element in the stack with its inverse. Therefore, the +/// following constraints are enforced: /// - The next element in the next frame should be the inverse of first element in the current /// frame. s0` * s0 = 1. pub fn enforce_inv_constraints( @@ -168,8 +177,10 @@ pub fn enforce_inv_constraints( 1 } -/// Enforces constraints of the INCR operation. The INCR operation increments the -/// top element in the stack by 1. Therefore, the following constraints are enforced: +/// Enforces constraints of the INCR operation. +/// +/// The INCR operation increments the top element in the stack by 1. Therefore, the following +/// constraints are enforced: /// - The next element in the next frame should be equal to the addition of first element in the /// current frame with 1. s0` - s0 - 1 = 0. pub fn enforce_incr_constraints( @@ -183,12 +194,13 @@ pub fn enforce_incr_constraints( 1 } -/// Enforces constraints of the NOT operation. The NOT operation updates the top element -/// in the stack with its bitwise not value. Therefore, the following constraints are -/// enforced: +/// Enforces constraints of the NOT operation. +/// +/// The NOT operation updates the top element in the stack with its bitwise not value. Therefore, +/// the following constraints are enforced: /// - The top element should be a binary. It is enforced as a general constraint. -/// - The first element of the next frame should be a binary not of the first element of -/// the current frame. s0` + s0 = 1. +/// - The first element of the next frame should be a binary not of the first element of the current +/// frame. s0` + s0 = 1. pub fn enforce_not_constraints( frame: &EvaluationFrame, result: &mut [E], @@ -204,10 +216,12 @@ pub fn enforce_not_constraints( 1 } -/// Enforces constraints of the AND operation. The AND operation computes the bitwise and of the -/// first two elements in the current trace. Therefore, the following constraints are enforced: -/// - The top two element in the current frame of the stack should be binary. s0^2 - s0 = 0, -/// s1^2 - s1 = 0. The top element is binary or not is enforced as a general constraint. +/// Enforces constraints of the AND operation. +/// +/// The AND operation computes the bitwise and of the first two elements in the current trace. +/// Therefore, the following constraints are enforced: +/// - The top two element in the current frame of the stack should be binary. s0^2 - s0 = 0, s1^2 - +/// s1 = 0. The top element is binary or not is enforced as a general constraint. /// - The first element of the next frame should be a binary and of the first two elements in the /// current frame. s0` - s0 * s1 = 0. pub fn enforce_and_constraints( @@ -231,10 +245,12 @@ pub fn enforce_and_constraints( 2 } -/// Enforces constraints of the OR operation. The OR operation computes the bitwise or of the -/// first two elements in the current trace. Therefore, the following constraints are enforced: -/// - The top two element in the current frame of the stack should be binary. s0^2 - s0 = 0, -/// s1^2 - s1 = 0. The top element is binary or not is enforced as a general constraint. +/// Enforces constraints of the OR operation. +/// +/// The OR operation computes the bitwise or of the first two elements in the current trace. +/// Therefore, the following constraints are enforced: +/// - The top two element in the current frame of the stack should be binary. s0^2 - s0 = 0, s1^2 - +/// s1 = 0. The top element is binary or not is enforced as a general constraint. /// - The first element of the next frame should be a binary or of the first two elements in the /// current frame. s0` - ( s0 + s1 - s0 * s1 ) = 0. pub fn enforce_or_constraints( @@ -258,8 +274,10 @@ pub fn enforce_or_constraints( 2 } -/// Enforces constraints of the EQ operation. The EQ operation checks if the top two elements in the -/// current frame are equal or not. Therefore, the following constraints are enforced: +/// Enforces constraints of the EQ operation. +/// +/// The EQ operation checks if the top two elements in the current frame are equal or not. +/// Therefore, the following constraints are enforced: /// - (s0 - s1) * s0' = 0 /// - s0` - (1 - (s0 - s1) * h0) = 0 pub fn enforce_eq_constraints( @@ -290,8 +308,10 @@ pub fn enforce_eq_constraints( 2 } -/// Enforces constraints of the EQZ operation. The EQZ operation checks if the top element in the -/// current frame is 0 or not. Therefore, the following constraints are enforced: +/// Enforces constraints of the EQZ operation. +/// +/// The EQZ operation checks if the top element in the current frame is 0 or not. Therefore, the +/// following constraints are enforced: /// - s0 * s0` = 0. /// - s0` - (1 - h0 * s0) = 0. pub fn enforce_eqz_constraints( @@ -318,8 +338,10 @@ pub fn enforce_eqz_constraints( 2 } -/// Enforces constraints of the EXPACC operation. The EXPACC operation computes a single turn of -/// exponent accumulation for the given inputs. Therefore, the following constraints are enforced: +/// Enforces constraints of the EXPACC operation. +/// +/// The EXPACC operation computes a single turn of exponent accumulation for the given inputs. +/// Therefore, the following constraints are enforced: /// - The first element in the next frame should be a binary which is enforced as a general /// constraint. /// - The exp value in the next frame should be the square of exp value in the current frame. @@ -357,9 +379,11 @@ pub fn enforce_expacc_constraints( 4 } -/// Enforces constraints of the EXT2MUL operation. The EXT2MUL operation computes the product of -/// two elements in the extension field of degree 2. Therefore, the following constraints are -/// enforced, assuming the first 4 elements of the stack in the current frame are a1, a0, b1, b0: +/// Enforces constraints of the EXT2MUL operation. +/// +/// The EXT2MUL operation computes the product of two elements in the extension field of degree 2. +/// Therefore, the following constraints are enforced, assuming the first 4 elements of the stack in +/// the current frame are a1, a0, b1, b0: /// - The first element in the next frame should be a1. /// - The second element in the next frame should be a0. /// - The third element in the next frame should be equal to (b0 + b1) * (a0 + a1) - b0 * a0. diff --git a/air/src/constraints/stack/field_ops/tests.rs b/air/src/constraints/stack/field_ops/tests.rs index 44243ade82..35ccf4ecf9 100644 --- a/air/src/constraints/stack/field_ops/tests.rs +++ b/air/src/constraints/stack/field_ops/tests.rs @@ -1,14 +1,17 @@ -use super::{ - super::{DECODER_TRACE_OFFSET, STACK_TRACE_OFFSET}, - enforce_constraints, EvaluationFrame, NUM_CONSTRAINTS, -}; -use crate::stack::op_flags::{generate_evaluation_frame, OpFlags}; -use crate::trace::decoder::USER_OP_HELPERS_OFFSET; use core::ops::Neg; + +use proptest::prelude::*; use rand_utils::rand_value; use vm_core::{Felt, FieldElement, Operation, ONE, ZERO}; -use proptest::prelude::*; +use super::{ + super::{DECODER_TRACE_OFFSET, STACK_TRACE_OFFSET}, + enforce_constraints, EvaluationFrame, NUM_CONSTRAINTS, +}; +use crate::{ + stack::op_flags::{generate_evaluation_frame, OpFlags}, + trace::decoder::USER_OP_HELPERS_OFFSET, +}; // RANDOMIZED TESTS // ================================================================================================ @@ -254,12 +257,12 @@ pub fn get_eqz_test_frame(a: u64) -> EvaluationFrame { frame.current_mut()[DECODER_TRACE_OFFSET + USER_OP_HELPERS_OFFSET] = Felt::new(rand_value::()); frame.next_mut()[STACK_TRACE_OFFSET] = ONE; - } + }, _ => { frame.current_mut()[STACK_TRACE_OFFSET] = Felt::new(a); frame.current_mut()[DECODER_TRACE_OFFSET + USER_OP_HELPERS_OFFSET] = Felt::new(a).inv(); frame.next_mut()[STACK_TRACE_OFFSET] = ZERO; - } + }, } frame diff --git a/air/src/constraints/stack/io_ops/mod.rs b/air/src/constraints/stack/io_ops/mod.rs index 7f1c64f9c9..2f81d27997 100644 --- a/air/src/constraints/stack/io_ops/mod.rs +++ b/air/src/constraints/stack/io_ops/mod.rs @@ -1,6 +1,7 @@ +use alloc::vec::Vec; + use super::{op_flags::OpFlags, EvaluationFrame, FieldElement, TransitionConstraintDegree}; use crate::{stack::EvaluationFrameExt, utils::are_equal}; -use alloc::vec::Vec; #[cfg(test)] pub mod tests; diff --git a/air/src/constraints/stack/io_ops/tests.rs b/air/src/constraints/stack/io_ops/tests.rs index d7ab9cab9e..0a8d9db207 100644 --- a/air/src/constraints/stack/io_ops/tests.rs +++ b/air/src/constraints/stack/io_ops/tests.rs @@ -1,10 +1,11 @@ +use rand_utils::rand_value; +use vm_core::{Felt, Operation, ZERO}; + use super::{enforce_constraints, EvaluationFrame, NUM_CONSTRAINTS}; use crate::stack::{ op_flags::{generate_evaluation_frame, OpFlags}, B0_COL_IDX, STACK_TRACE_OFFSET, }; -use rand_utils::rand_value; -use vm_core::{Felt, Operation, ZERO}; // UNIT TESTS // ================================================================================================ diff --git a/air/src/constraints/stack/mod.rs b/air/src/constraints/stack/mod.rs index aae92569ba..7cc021824c 100644 --- a/air/src/constraints/stack/mod.rs +++ b/air/src/constraints/stack/mod.rs @@ -1,11 +1,15 @@ +use alloc::vec::Vec; + +use vm_core::{stack::STACK_TOP_SIZE, StackOutputs}; + use super::super::{ Assertion, EvaluationFrame, Felt, FieldElement, TransitionConstraintDegree, CLK_COL_IDX, DECODER_TRACE_OFFSET, FMP_COL_IDX, ONE, STACK_AUX_TRACE_OFFSET, STACK_TRACE_OFFSET, ZERO, }; -use crate::decoder::{IS_CALL_FLAG_COL_IDX, IS_SYSCALL_FLAG_COL_IDX, USER_OP_HELPERS_OFFSET}; -use crate::utils::{are_equal, is_binary}; -use alloc::vec::Vec; -use vm_core::{stack::STACK_TOP_SIZE, StackOutputs}; +use crate::{ + decoder::{IS_CALL_FLAG_COL_IDX, IS_SYSCALL_FLAG_COL_IDX, USER_OP_HELPERS_OFFSET}, + utils::{are_equal, is_binary}, +}; pub mod field_ops; pub mod io_ops; @@ -31,14 +35,16 @@ pub const NUM_ASSERTIONS: usize = 2 * STACK_TOP_SIZE + 2; /// The number of general constraints in the stack operations. pub const NUM_GENERAL_CONSTRAINTS: usize = 17; -/// The degrees of constraints in the general stack operations. Each operation being executed -/// either shifts the stack to the left, right or doesn't effect it at all. Therefore, majority -/// of the general transitions of a stack item would be common across the operations and composite -/// flags were introduced to compute the individual stack item transition. A particular item lets -/// say at depth ith in the next stack frame can be transitioned into from ith depth (no shift op) -/// or (i+1)th depth(left shift) or (i-1)th depth(right shift) in the current frame. Therefore, the -/// VM would require only 16 general constraints to encompass all the 16 stack positions. -/// The last constraint checks if the top element in the stack is a binary or not. +/// The degrees of constraints in the general stack operations. +/// +/// Each operation being executed either shifts the stack to the left, right or doesn't effect it at +/// all. Therefore, majority of the general transitions of a stack item would be common across the +/// operations and composite flags were introduced to compute the individual stack item transition. +/// A particular item lets say at depth ith in the next stack frame can be transitioned into from +/// ith depth (no shift op) or (i+1)th depth(left shift) or (i-1)th depth(right shift) in the +/// current frame. Therefore, the VM would require only 16 general constraints to encompass all the +/// 16 stack positions. The last constraint checks if the top element in the stack is a binary or +/// not. pub const CONSTRAINT_DEGREES: [usize; NUM_GENERAL_CONSTRAINTS] = [ // Each degree are being multiplied with the respective composite flags which are of degree 7. // Therefore, all the degree would incorporate 7 in their degree calculation. diff --git a/air/src/constraints/stack/op_flags/mod.rs b/air/src/constraints/stack/op_flags/mod.rs index ce08893ffb..f0a8342583 100644 --- a/air/src/constraints/stack/op_flags/mod.rs +++ b/air/src/constraints/stack/op_flags/mod.rs @@ -1,11 +1,14 @@ +use vm_core::{Felt, FieldElement, Operation, ONE, ZERO}; + use super::{EvaluationFrame, B0_COL_IDX}; -use crate::trace::{ - decoder::{IS_LOOP_FLAG_COL_IDX, NUM_OP_BITS, OP_BITS_EXTRA_COLS_RANGE, OP_BITS_RANGE}, - stack::H0_COL_IDX, - DECODER_TRACE_OFFSET, STACK_TRACE_OFFSET, TRACE_WIDTH, +use crate::{ + trace::{ + decoder::{IS_LOOP_FLAG_COL_IDX, NUM_OP_BITS, OP_BITS_EXTRA_COLS_RANGE, OP_BITS_RANGE}, + stack::H0_COL_IDX, + DECODER_TRACE_OFFSET, STACK_TRACE_OFFSET, TRACE_WIDTH, + }, + utils::binary_not, }; -use crate::utils::binary_not; -use vm_core::{Felt, FieldElement, Operation, ONE, ZERO}; #[cfg(test)] pub mod tests; @@ -264,7 +267,7 @@ impl OpFlags { // degree 6 flags do not use the first two bits (op_bits[0], op_bits[1]) degree4_op_flags[0] = not_2_not_3; // MRUPDATE - degree4_op_flags[1] = yes_2_not_3; // PUSH + degree4_op_flags[1] = yes_2_not_3; // (unused) degree4_op_flags[2] = not_2_yes_3; // SYSCALL degree4_op_flags[3] = yes_2_yes_3; // CALL @@ -289,6 +292,7 @@ impl OpFlags { + degree5_op_flags[1] // MPVERIFY + degree5_op_flags[6] // SPAN + degree5_op_flags[7] // JOIN + + degree5_op_flags[10] // EMIT + degree4_op_flags[6] // RESPAN + degree4_op_flags[7] // HALT + degree4_op_flags[3] // CALL @@ -372,7 +376,7 @@ impl OpFlags { + degree7_op_flags[22] + degree7_op_flags[26]; - right_shift_flags[0] = f011 + degree4_op_flags[1] + movupn_flag; + right_shift_flags[0] = f011 + degree5_op_flags[11] + movupn_flag; // degree 5: PUSH right_shift_flags[1] = right_shift_flags[0] + degree6_op_flags[4]; // degree 6: U32SPLIT @@ -392,7 +396,7 @@ impl OpFlags { right_shift_flags[15] = right_shift_flags[8]; // Flag if the stack has been shifted to the right. - let right_shift = f011 + degree4_op_flags[1] + degree6_op_flags[4]; // PUSH; U32SPLIT + let right_shift = f011 + degree5_op_flags[11] + degree6_op_flags[4]; // PUSH; U32SPLIT // Flag if the stack has been shifted to the left. let left_shift = @@ -840,7 +844,7 @@ impl OpFlags { /// Operation Flag of U32ASSERT2 operation. #[inline(always)] pub fn u32assert2(&self) -> E { - self.degree6_op_flags[get_op_index(Operation::U32assert2(ZERO).op_code())] + self.degree6_op_flags[get_op_index(Operation::U32assert2(0).op_code())] } /// Operation Flag of U32ADD3 operation. @@ -866,7 +870,7 @@ impl OpFlags { /// Operation Flag of MPVERIFY operation. #[inline(always)] pub fn mpverify(&self) -> E { - self.degree5_op_flags[get_op_index(Operation::MpVerify.op_code())] + self.degree5_op_flags[get_op_index(Operation::MpVerify(0).op_code())] } /// Operation Flag of SPLIT operation. @@ -904,7 +908,7 @@ impl OpFlags { /// Operation Flag of PUSH operation. #[inline(always)] pub fn push(&self) -> E { - self.degree4_op_flags[get_op_index(Operation::Push(ONE).op_code())] + self.degree5_op_flags[get_op_index(Operation::Push(ONE).op_code())] } /// Operation Flag of CALL operation. diff --git a/air/src/constraints/stack/op_flags/tests.rs b/air/src/constraints/stack/op_flags/tests.rs index e56bbd0324..d4d9a469d0 100644 --- a/air/src/constraints/stack/op_flags/tests.rs +++ b/air/src/constraints/stack/op_flags/tests.rs @@ -1,12 +1,11 @@ -use crate::stack::op_flags::get_op_index; +use vm_core::{Operation, ONE, ZERO}; use super::{ generate_evaluation_frame, OpFlags, DECODER_TRACE_OFFSET, DEGREE_4_OPCODE_ENDS, DEGREE_4_OPCODE_STARTS, DEGREE_6_OPCODE_ENDS, DEGREE_6_OPCODE_STARTS, DEGREE_7_OPCODE_ENDS, DEGREE_7_OPCODE_STARTS, NUM_DEGREE_4_OPS, NUM_DEGREE_5_OPS, NUM_DEGREE_6_OPS, NUM_DEGREE_7_OPS, }; -use crate::trace::decoder::IS_LOOP_FLAG_COL_IDX; -use vm_core::{Operation, ONE, ZERO}; +use crate::{stack::op_flags::get_op_index, trace::decoder::IS_LOOP_FLAG_COL_IDX}; /// Asserts the op flag to ONE for degree 7 operation which is being executed in the current /// frame; assert all the other operation flags to ZERO as they are not present in the current @@ -145,7 +144,8 @@ fn degree_4_op_flags() { fn composite_flags() { // ------ no change 0 --------------------------------------------------------------------- - let op_no_change_0 = [Operation::MpVerify, Operation::Span, Operation::Halt]; + let op_no_change_0 = + [Operation::MpVerify(0), Operation::Span, Operation::Halt, Operation::Emit(42)]; for op in op_no_change_0 { // frame initialised with an op operation. let frame = generate_evaluation_frame(op.op_code().into()); @@ -169,7 +169,7 @@ fn composite_flags() { assert_eq!(op_flags.left_shift(), ZERO); assert_eq!(op_flags.top_binary(), ZERO); - if op == Operation::MpVerify { + if op == Operation::MpVerify(0) || op == Operation::Emit(42) { assert_eq!(op_flags.control_flow(), ZERO); } else if op == Operation::Span || op == Operation::Halt { assert_eq!(op_flags.control_flow(), ONE); diff --git a/air/src/constraints/stack/overflow/mod.rs b/air/src/constraints/stack/overflow/mod.rs index 4cc319711c..5e055eedd5 100644 --- a/air/src/constraints/stack/overflow/mod.rs +++ b/air/src/constraints/stack/overflow/mod.rs @@ -1,6 +1,7 @@ +use alloc::vec::Vec; + use super::{op_flags::OpFlags, EvaluationFrame, FieldElement, TransitionConstraintDegree}; use crate::stack::EvaluationFrameExt; -use alloc::vec::Vec; #[cfg(test)] pub mod tests; @@ -92,8 +93,7 @@ pub fn enforce_stack_depth_constraints( /// Enforces constraints on the overflow flag h0. Therefore, the following constraints /// are enforced: -/// - If overflow table has values, then, h0 should be set to ONE, otherwise it should -/// be ZERO. +/// - If overflow table has values, then, h0 should be set to ONE, otherwise it should be ZERO. pub fn enforce_overflow_flag_constraints( frame: &EvaluationFrame, result: &mut [E], @@ -107,8 +107,8 @@ pub fn enforce_overflow_flag_constraints( } /// Enforces constraints on the bookkeeping index `b1`. The following constraints are enforced: -/// - In the case of a right shift operation, the next b1 index should be updated with current -/// `clk` value. +/// - In the case of a right shift operation, the next b1 index should be updated with current `clk` +/// value. /// - In the case of a left shift operation, the last stack item should be set to ZERO when the /// depth of the stack is 16. pub fn enforce_overflow_index_constraints( diff --git a/air/src/constraints/stack/overflow/tests.rs b/air/src/constraints/stack/overflow/tests.rs index 5ec29aadf6..72904e6b8f 100644 --- a/air/src/constraints/stack/overflow/tests.rs +++ b/air/src/constraints/stack/overflow/tests.rs @@ -1,12 +1,15 @@ -use super::{enforce_constraints, EvaluationFrame, NUM_CONSTRAINTS}; -use crate::stack::{ - op_flags::{generate_evaluation_frame, OpFlags}, - B0_COL_IDX, B1_COL_IDX, CLK_COL_IDX, DECODER_TRACE_OFFSET, H0_COL_IDX, STACK_TRACE_OFFSET, -}; -use crate::trace::decoder::IS_CALL_FLAG_COL_IDX; use rand_utils::rand_value; use vm_core::{Felt, FieldElement, Operation, ONE, ZERO}; +use super::{enforce_constraints, EvaluationFrame, NUM_CONSTRAINTS}; +use crate::{ + stack::{ + op_flags::{generate_evaluation_frame, OpFlags}, + B0_COL_IDX, B1_COL_IDX, CLK_COL_IDX, DECODER_TRACE_OFFSET, H0_COL_IDX, STACK_TRACE_OFFSET, + }, + trace::decoder::IS_CALL_FLAG_COL_IDX, +}; + // UNIT TESTS // ================================================================================================ diff --git a/air/src/constraints/stack/stack_manipulation/mod.rs b/air/src/constraints/stack/stack_manipulation/mod.rs index 0678fe615e..fdd6d590d9 100644 --- a/air/src/constraints/stack/stack_manipulation/mod.rs +++ b/air/src/constraints/stack/stack_manipulation/mod.rs @@ -1,9 +1,10 @@ +use alloc::vec::Vec; + use super::{op_flags::OpFlags, EvaluationFrame, FieldElement, TransitionConstraintDegree}; use crate::{ stack::EvaluationFrameExt, utils::{are_equal, binary_not}, }; -use alloc::vec::Vec; #[cfg(test)] pub mod tests; @@ -76,8 +77,10 @@ pub fn enforce_constraints( // TRANSITION CONSTRAINT HELPERS // ================================================================================================ -/// Enforces constraints of the PAD operation. The PAD operation pushes a ZERO onto -/// the stack. Therefore, the following constraints are enforced: +/// Enforces constraints of the PAD operation. +/// +/// The PAD operation pushes a ZERO onto the stack. Therefore, the following constraints are +/// enforced: /// - The top element in the next frame should be ZERO. s0` = 0. pub fn enforce_pad_constraints( frame: &EvaluationFrame, @@ -90,11 +93,13 @@ pub fn enforce_pad_constraints( 1 } -/// Enforces constraints of the DUPn and MOVUPn operations. The DUPn operation copies the element -/// at depth n in the stack and pushes the copy onto the stack, whereas MOVUPn opearation moves the -/// element at depth n to the top of the stack. Therefore, the following constraints are enforced: -/// - The top element in the next frame should be equal to the element at depth n in the -/// current frame. s0` - sn = 0. +/// Enforces constraints of the DUPn and MOVUPn operations. +/// +/// The DUPn operation copies the element at depth n in the stack and pushes the copy onto the +/// stack, whereas MOVUPn opearation moves the element at depth n to the top of the stack. +/// Therefore, the following constraints are enforced: +/// - The top element in the next frame should be equal to the element at depth n in the current +/// frame. s0` - sn = 0. pub fn enforce_dup_movup_n_constraints( frame: &EvaluationFrame, result: &mut [E], @@ -163,8 +168,10 @@ pub fn enforce_dup_movup_n_constraints( 13 } -/// Enforces constraints of the SWAP operation. The SWAP operation swaps the first -/// two elements in the stack. Therefore, the following constraints are enforced: +/// Enforces constraints of the SWAP operation. +/// +/// The SWAP operation swaps the first two elements in the stack. Therefore, the following +/// constraints are enforced: /// - The first element in the current frame should be equal to the second element in the next /// frame. /// - The second element in the current frame should be equal to the first element in the next @@ -244,8 +251,8 @@ pub fn enforce_swapwx_constraints( /// Enforces constraints of the MOVDNn operation. The MOVDNn operation moves the top element /// to depth n in the stack. Therefore, the following constraints are enforced: -/// - The top element in the current frame should be equal to the element at depth n in the -/// next frame. s0 - sn` = 0. +/// - The top element in the current frame should be equal to the element at depth n in the next +/// frame. s0 - sn` = 0. pub fn enforce_movdnn_constraints( frame: &EvaluationFrame, result: &mut [E], diff --git a/air/src/constraints/stack/stack_manipulation/tests.rs b/air/src/constraints/stack/stack_manipulation/tests.rs index 262cbfa8bf..d6564bef93 100644 --- a/air/src/constraints/stack/stack_manipulation/tests.rs +++ b/air/src/constraints/stack/stack_manipulation/tests.rs @@ -1,8 +1,8 @@ -use super::{super::STACK_TRACE_OFFSET, enforce_constraints, EvaluationFrame, NUM_CONSTRAINTS}; -use crate::stack::op_flags::{generate_evaluation_frame, OpFlags}; +use proptest::prelude::*; use vm_core::{Felt, Operation, ONE, ZERO}; -use proptest::prelude::*; +use super::{super::STACK_TRACE_OFFSET, enforce_constraints, EvaluationFrame, NUM_CONSTRAINTS}; +use crate::stack::op_flags::{generate_evaluation_frame, OpFlags}; // RANDOMIZED TESTS // ================================================================================================ diff --git a/air/src/constraints/stack/system_ops/mod.rs b/air/src/constraints/stack/system_ops/mod.rs index ab87f07120..dd7477d931 100644 --- a/air/src/constraints/stack/system_ops/mod.rs +++ b/air/src/constraints/stack/system_ops/mod.rs @@ -1,6 +1,7 @@ +use alloc::vec::Vec; + use super::{op_flags::OpFlags, EvaluationFrame, FieldElement, TransitionConstraintDegree}; use crate::{stack::EvaluationFrameExt, utils::are_equal}; -use alloc::vec::Vec; #[cfg(test)] pub mod tests; @@ -73,8 +74,10 @@ pub fn enforce_assert_constraints( 1 } -/// Enforces unique constraints of the FMPADD operation. The FMPADD operation increments the top -/// element in the stack by `fmp` register value. Therefore, the following constraints are enforced: +/// Enforces unique constraints of the FMPADD operation. +/// +/// The FMPADD operation increments the top element in the stack by `fmp` register value. Therefore, +/// the following constraints are enforced: /// - The first element in the next frame should be equal to the addition of the first element in /// the current frame and the fmp value. s0` - (s0 + fmp) = 0 pub fn enforce_fmpadd_constraints( @@ -88,9 +91,10 @@ pub fn enforce_fmpadd_constraints( 1 } -/// Enforces constraints of the FMPUPDATE operation. The FMPUPDATE operation increments the fmp -/// register value by the first element value in the current trace. Therefore, the following -/// constraints are enforced: +/// Enforces constraints of the FMPUPDATE operation. +/// +/// The FMPUPDATE operation increments the fmp register value by the first element value in the +/// current trace. Therefore, the following constraints are enforced: /// - The fmp register value in the next frame should be equal to the sum of the fmp register value /// and the top stack element in the current frame. fmp` - (s0 + fmp) = 0. pub fn enforce_fmpupdate_constraints( @@ -104,8 +108,10 @@ pub fn enforce_fmpupdate_constraints( 1 } -/// Enforces constraints of the CLK operation. The CLK operation pushes the current cycle number to -/// the stack. Therefore, the following constraints are enforced: +/// Enforces constraints of the CLK operation. +/// +/// The CLK operation pushes the current cycle number to the stack. Therefore, the following +/// constraints are enforced: /// - The first element in the next frame should be equal to the current cycle number. s0' - (cycle) /// = 0. pub fn enforce_clk_constraints( diff --git a/air/src/constraints/stack/system_ops/tests.rs b/air/src/constraints/stack/system_ops/tests.rs index cd4790999f..36d821414e 100644 --- a/air/src/constraints/stack/system_ops/tests.rs +++ b/air/src/constraints/stack/system_ops/tests.rs @@ -1,11 +1,11 @@ +use proptest::prelude::*; +use vm_core::{Felt, Operation, ONE, ZERO}; + use super::{ super::{CLK_COL_IDX, FMP_COL_IDX, STACK_TRACE_OFFSET}, enforce_constraints, EvaluationFrame, NUM_CONSTRAINTS, }; use crate::stack::op_flags::{generate_evaluation_frame, OpFlags}; -use vm_core::{Felt, Operation, ONE, ZERO}; - -use proptest::prelude::*; // RANDOMIZED TESTS // ================================================================================================ diff --git a/air/src/constraints/stack/u32_ops/mod.rs b/air/src/constraints/stack/u32_ops/mod.rs index d04b4a75b1..e4d0c3fe15 100644 --- a/air/src/constraints/stack/u32_ops/mod.rs +++ b/air/src/constraints/stack/u32_ops/mod.rs @@ -1,9 +1,10 @@ +use alloc::vec::Vec; + use super::{op_flags::OpFlags, EvaluationFrame, Felt, FieldElement, TransitionConstraintDegree}; use crate::{ stack::EvaluationFrameExt, utils::{are_equal, is_binary}, }; -use alloc::vec::Vec; #[cfg(test)] pub mod tests; @@ -119,8 +120,8 @@ pub fn enforce_u32split_constraints>( /// Enforces constraints of the U32ADD operation. The U32ADD operation adds the top two /// elements in the current trace of the stack. Therefore, the following constraints are /// enforced: -/// - The aggregation of limbs from the helper registers is equal to the sum of the top two -/// element in the stack. +/// - The aggregation of limbs from the helper registers is equal to the sum of the top two element +/// in the stack. pub fn enforce_u32add_constraints>( frame: &EvaluationFrame, result: &mut [E], @@ -208,9 +209,10 @@ pub fn enforce_u32mul_constraints>( 1 } -/// Enforces constraints of the U32MADD operation. The U32MADD operation adds the third -/// element to the product of the first two elements in the current trace. Therefore, the -/// following constraints are enforced: +/// Enforces constraints of the U32MADD operation. +/// +/// The U32MADD operation adds the third element to the product of the first two elements in the +/// current trace. Therefore, the following constraints are enforced: /// - The aggregation of all the limbs in the helper registers is equal to the sum of the third /// element with the product of the first two elements in the current trace. pub fn enforce_u32madd_constraints>( @@ -230,8 +232,10 @@ pub fn enforce_u32madd_constraints>( 1 } -/// Enforces constraints of the U32DIV operation. The U32DIV operation divides the second element -/// with the first element in the current trace. Therefore, the following constraints are enforced: +/// Enforces constraints of the U32DIV operation. +/// +/// The U32DIV operation divides the second element with the first element in the current trace. +/// Therefore, the following constraints are enforced: /// - The second element in the current trace should be equal to the sum of the first element in the /// next trace with the product of the first element in the current trace and second element in /// the next trace. @@ -289,8 +293,7 @@ pub fn enforce_check_element_validity>( /// Enforces constraints of the general operation. The constaints checks if the lower 16-bits limbs /// are aggregated correctly or not. Therefore, the following constraints are enforced: /// - The aggregation of lower two lower 16-bits limbs in the helper registers is equal to the -/// second -/// element in the next row. +/// second element in the next row. /// - The aggregation of lower two upper 16-bits limbs in the helper registers is equal to the first /// element in the next row. pub fn enforce_limbs_agg>( @@ -340,12 +343,7 @@ impl> LimbCompositions { let v64 = E::from(TWO_48) * frame.user_op_helper(3) + v48; - Self { - v_hi, - v_lo, - v48, - v64, - } + Self { v_hi, v_lo, v48, v64 } } /// Returns v_hi intermediate flag value. diff --git a/air/src/constraints/stack/u32_ops/tests.rs b/air/src/constraints/stack/u32_ops/tests.rs index 0c21711b8e..8f16855c65 100644 --- a/air/src/constraints/stack/u32_ops/tests.rs +++ b/air/src/constraints/stack/u32_ops/tests.rs @@ -1,12 +1,14 @@ +use proptest::prelude::*; +use vm_core::{Felt, FieldElement, Operation, ZERO}; + use super::{ super::{DECODER_TRACE_OFFSET, STACK_TRACE_OFFSET}, enforce_constraints, EvaluationFrame, NUM_CONSTRAINTS, }; -use crate::stack::op_flags::{generate_evaluation_frame, OpFlags}; -use crate::trace::decoder::USER_OP_HELPERS_OFFSET; -use vm_core::{Felt, FieldElement, Operation, ZERO}; - -use proptest::prelude::*; +use crate::{ + stack::op_flags::{generate_evaluation_frame, OpFlags}, + trace::decoder::USER_OP_HELPERS_OFFSET, +}; // RANDOMIZED TESTS // ================================================================================================ diff --git a/air/src/errors.rs b/air/src/errors.rs index cd9ef5bdb7..af884215c3 100644 --- a/air/src/errors.rs +++ b/air/src/errors.rs @@ -1,7 +1,8 @@ -use crate::trace::MIN_TRACE_LEN; use alloc::string::String; use core::fmt::{Display, Formatter}; +use crate::trace::MIN_TRACE_LEN; + // EXECUTION ERROR // ================================================================================================ @@ -19,10 +20,10 @@ impl Display for ExecutionOptionsError { match self { ExpectedCyclesTooBig(max, expected) => { write!(f, "The expected number of cycles must be smaller than the maximum number of cycles: maximum is {max}, but expectd is {expected}") - } + }, MaxCycleNumTooSmall(max) => { write!(f, "The maximum number of cycles must be greater than the minimum number of cycles: minimum is {MIN_TRACE_LEN}, but maximum is {max}") - } + }, OtherErrors(error) => write!(f, "{error}"), } } diff --git a/air/src/lib.rs b/air/src/lib.rs index 68844a0e4c..b126c07a3a 100644 --- a/air/src/lib.rs +++ b/air/src/lib.rs @@ -23,6 +23,7 @@ pub use constraints::stack; use constraints::{chiplets, range}; pub mod trace; +pub use trace::rows::RowIndex; use trace::*; mod errors; @@ -30,19 +31,17 @@ mod options; mod proof; mod utils; -use utils::TransitionConstraintRange; - // RE-EXPORTS // ================================================================================================ - pub use errors::ExecutionOptionsError; pub use options::{ExecutionOptions, ProvingOptions}; pub use proof::{ExecutionProof, HashFunction}; +use utils::TransitionConstraintRange; pub use vm_core::{ utils::{DeserializationError, ToElements}, Felt, FieldElement, StarkField, }; -pub use winter_air::{AuxRandElements, FieldExtension}; +pub use winter_air::{AuxRandElements, FieldExtension, LagrangeKernelEvaluationFrame}; // PROCESSOR AIR // ================================================================================================ diff --git a/air/src/options.rs b/air/src/options.rs index 03ae5c203d..fbbbbb31ce 100644 --- a/air/src/options.rs +++ b/air/src/options.rs @@ -55,11 +55,7 @@ impl ProvingOptions { fri_remainder_max_degree, ); let exec_options = ExecutionOptions::default(); - Self { - exec_options, - proof_options, - hash_fn, - } + Self { exec_options, proof_options, hash_fn } } /// Creates a new preset instance of [ProvingOptions] targeting 96-bit security level. @@ -171,6 +167,7 @@ pub struct ExecutionOptions { max_cycles: u32, expected_cycles: u32, enable_tracing: bool, + enable_debugging: bool, } impl Default for ExecutionOptions { @@ -179,6 +176,7 @@ impl Default for ExecutionOptions { max_cycles: u32::MAX, expected_cycles: MIN_TRACE_LEN as u32, enable_tracing: false, + enable_debugging: false, } } } @@ -194,6 +192,7 @@ impl ExecutionOptions { max_cycles: Option, expected_cycles: u32, enable_tracing: bool, + enable_debugging: bool, ) -> Result { let max_cycles = max_cycles.unwrap_or(u32::MAX); if max_cycles < MIN_TRACE_LEN as u32 { @@ -211,30 +210,51 @@ impl ExecutionOptions { max_cycles, expected_cycles, enable_tracing, + enable_debugging, }) } - /// Enables Host to handle the `tracing` instructions. + /// Enables execution of the `trace` instructions. pub fn with_tracing(mut self) -> Self { self.enable_tracing = true; self } + /// Enables execution of programs in debug mode. + /// + /// In debug mode the VM does the following: + /// - Executes `debug` instructions (these are ignored in regular mode). + /// - Records additional info about program execution (e.g., keeps track of stack state at every + /// cycle of the VM) which enables stepping through the program forward and backward. + pub fn with_debugging(mut self) -> Self { + self.enable_debugging = true; + self + } + // PUBLIC ACCESSORS // -------------------------------------------------------------------------------------------- - /// Returns maximum number of cycles + /// Returns maximum number of cycles a program is allowed to execute for. pub fn max_cycles(&self) -> u32 { self.max_cycles } - /// Returns number of the expected cycles + /// Returns the number of cycles a program is expected to take. + /// + /// This will serve as a hint to the VM for how much memory to allocate for a program's + /// execution trace and may result in performance improvements when the number of expected + /// cycles is equal to the number of actual cycles. pub fn expected_cycles(&self) -> u32 { self.expected_cycles } - /// Returns a flag indicating whether the Host should handle `trace` instructions + /// Returns a flag indicating whether the VM should execute `trace` instructions. pub fn enable_tracing(&self) -> bool { self.enable_tracing } + + /// Returns a flag indicating whether the VM should execute a program in debug mode. + pub fn enable_debugging(&self) -> bool { + self.enable_debugging + } } diff --git a/air/src/proof.rs b/air/src/proof.rs index 62832a3f52..6b67b03630 100644 --- a/air/src/proof.rs +++ b/air/src/proof.rs @@ -1,4 +1,5 @@ use alloc::vec::Vec; + use vm_core::{ crypto::hash::{Blake3_192, Blake3_256, Hasher, Rpo256, Rpx256}, utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}, diff --git a/air/src/trace/chiplets/hasher.rs b/air/src/trace/chiplets/hasher.rs index 7367bbac2b..f369e0d2f4 100644 --- a/air/src/trace/chiplets/hasher.rs +++ b/air/src/trace/chiplets/hasher.rs @@ -1,10 +1,11 @@ //! TODO: add docs -use super::{create_range, Felt, HASHER_AUX_TRACE_OFFSET, ONE, ZERO}; use core::ops::Range; pub use vm_core::crypto::hash::{Rpo256 as Hasher, RpoDigest as Digest}; +use super::{create_range, Felt, HASHER_AUX_TRACE_OFFSET, ONE, ZERO}; + // TYPES ALIASES // ================================================================================================ diff --git a/air/src/trace/chiplets/kernel_rom.rs b/air/src/trace/chiplets/kernel_rom.rs index 28ac08f379..94c9cc9264 100644 --- a/air/src/trace/chiplets/kernel_rom.rs +++ b/air/src/trace/chiplets/kernel_rom.rs @@ -8,8 +8,8 @@ pub const TRACE_WIDTH: usize = 6; // --- OPERATION SELECTORS ------------------------------------------------------------------------ -/// Specifies a kernel procedure call operation to access a procedure in the kernel ROM. The unique -/// operation label is computed as 1 plus the combined chiplet and internal selector with the bits -/// reversed. -/// kernel ROM selector=[1, 1, 1, 0] +1=[0, 0, 0, 1] +/// Specifies a kernel procedure call operation to access a procedure in the kernel ROM. +/// +/// The unique operation label is computed as 1 plus the combined chiplet and internal selector +/// with the bits reversed: kernel ROM selector=[1, 1, 1, 0] +1=[0, 0, 0, 1]. pub const KERNEL_PROC_LABEL: Felt = Felt::new(0b1000); diff --git a/air/src/trace/chiplets/memory.rs b/air/src/trace/chiplets/memory.rs index 9990e1219b..6e531d30d1 100644 --- a/air/src/trace/chiplets/memory.rs +++ b/air/src/trace/chiplets/memory.rs @@ -9,9 +9,10 @@ pub const TRACE_WIDTH: usize = 12; /// Number of selector columns in the trace. pub const NUM_SELECTORS: usize = 2; -/// Type for Memory trace selectors. These selectors are used to define which operation and memory -/// state update (init & read / copy & read / write) is to be applied at a specific row of the -/// memory execution trace. +/// Type for Memory trace selectors. +/// +/// These selectors are used to define which operation and memory state update (init & read / copy & +/// read / write) is to be applied at a specific row of the memory execution trace. pub type Selectors = [Felt; NUM_SELECTORS]; // --- OPERATION SELECTORS ------------------------------------------------------------------------ diff --git a/air/src/trace/chiplets/mod.rs b/air/src/trace/chiplets/mod.rs index f14844f5e5..d892c67e36 100644 --- a/air/src/trace/chiplets/mod.rs +++ b/air/src/trace/chiplets/mod.rs @@ -1,7 +1,9 @@ -use super::{CHIPLETS_OFFSET, HASHER_AUX_TRACE_OFFSET}; use core::ops::Range; + use vm_core::{utils::range as create_range, Felt, ONE, ZERO}; +use super::{CHIPLETS_OFFSET, HASHER_AUX_TRACE_OFFSET}; + pub mod bitwise; pub mod hasher; pub mod kernel_rom; diff --git a/air/src/trace/decoder/mod.rs b/air/src/trace/decoder/mod.rs index 2795cc9646..77a7531d7f 100644 --- a/air/src/trace/decoder/mod.rs +++ b/air/src/trace/decoder/mod.rs @@ -1,7 +1,9 @@ -use super::DECODER_AUX_TRACE_OFFSET; use core::ops::Range; + use vm_core::{utils::range, Felt, Operation, ONE, ZERO}; +use super::DECODER_AUX_TRACE_OFFSET; + // CONSTANTS // ================================================================================================ diff --git a/air/src/trace/main_trace.rs b/air/src/trace/main_trace.rs index dc4db3ca88..5abffe7327 100644 --- a/air/src/trace/main_trace.rs +++ b/air/src/trace/main_trace.rs @@ -1,3 +1,9 @@ +#[cfg(any(test, feature = "testing"))] +use alloc::vec::Vec; +use core::ops::{Deref, Range}; + +use vm_core::{utils::range, Felt, Word, ONE, ZERO}; + use super::{ super::ColMatrix, chiplets::{ @@ -16,9 +22,7 @@ use super::{ CHIPLETS_OFFSET, CLK_COL_IDX, CTX_COL_IDX, DECODER_TRACE_OFFSET, FMP_COL_IDX, FN_HASH_OFFSET, STACK_TRACE_OFFSET, }; -use alloc::vec::Vec; -use core::ops::{Deref, Range}; -use vm_core::{utils::range, Felt, ONE, ZERO}; +use crate::RowIndex; // CONSTANTS // ================================================================================================ @@ -31,6 +35,7 @@ const DECODER_HASHER_RANGE: Range = pub struct MainTrace { columns: ColMatrix, + last_program_row: RowIndex, } impl Deref for MainTrace { @@ -42,17 +47,19 @@ impl Deref for MainTrace { } impl MainTrace { - pub fn new(main_trace: ColMatrix) -> Self { - Self { - columns: main_trace, - } + pub fn new(main_trace: ColMatrix, last_program_row: RowIndex) -> Self { + Self { columns: main_trace, last_program_row } } pub fn num_rows(&self) -> usize { self.columns.num_rows() } - #[cfg(any(test, feature = "internals"))] + pub fn last_program_row(&self) -> RowIndex { + self.last_program_row + } + + #[cfg(any(test, feature = "testing"))] pub fn get_column_range(&self, range: Range) -> Vec> { range.fold(vec![], |mut acc, col_idx| { acc.push(self.get_column(col_idx).to_vec()); @@ -64,17 +71,17 @@ impl MainTrace { // -------------------------------------------------------------------------------------------- /// Returns the value of the clk column at row i. - pub fn clk(&self, i: usize) -> Felt { + pub fn clk(&self, i: RowIndex) -> Felt { self.columns.get_column(CLK_COL_IDX)[i] } /// Returns the value of the fmp column at row i. - pub fn fmp(&self, i: usize) -> Felt { + pub fn fmp(&self, i: RowIndex) -> Felt { self.columns.get_column(FMP_COL_IDX)[i] } /// Returns the value of the ctx column at row i. - pub fn ctx(&self, i: usize) -> Felt { + pub fn ctx(&self, i: RowIndex) -> Felt { self.columns.get_column(CTX_COL_IDX)[i] } @@ -82,22 +89,22 @@ impl MainTrace { // -------------------------------------------------------------------------------------------- /// Returns the value in the block address column at the row i. - pub fn addr(&self, i: usize) -> Felt { + pub fn addr(&self, i: RowIndex) -> Felt { self.columns.get_column(DECODER_TRACE_OFFSET)[i] } /// Helper method to detect change of address. - pub fn is_addr_change(&self, i: usize) -> bool { + pub fn is_addr_change(&self, i: RowIndex) -> bool { self.addr(i) != self.addr(i + 1) } /// The i-th decoder helper register at `row`. - pub fn helper_register(&self, i: usize, row: usize) -> Felt { + pub fn helper_register(&self, i: usize, row: RowIndex) -> Felt { self.columns.get_column(DECODER_TRACE_OFFSET + USER_OP_HELPERS_OFFSET + i)[row] } /// Returns the hasher state at row i. - pub fn decoder_hasher_state(&self, i: usize) -> [Felt; NUM_HASHER_COLUMNS] { + pub fn decoder_hasher_state(&self, i: RowIndex) -> [Felt; NUM_HASHER_COLUMNS] { let mut state = [ZERO; NUM_HASHER_COLUMNS]; for (idx, col_idx) in DECODER_HASHER_RANGE.enumerate() { let column = self.columns.get_column(col_idx); @@ -107,7 +114,7 @@ impl MainTrace { } /// Returns the first half of the hasher state at row i. - pub fn decoder_hasher_state_first_half(&self, i: usize) -> [Felt; DIGEST_LEN] { + pub fn decoder_hasher_state_first_half(&self, i: RowIndex) -> Word { let mut state = [ZERO; DIGEST_LEN]; for (col, s) in state.iter_mut().enumerate() { *s = self.columns.get_column(DECODER_TRACE_OFFSET + HASHER_STATE_OFFSET + col)[i]; @@ -115,13 +122,26 @@ impl MainTrace { state } + /// Returns the second half of the hasher state at row i. + pub fn decoder_hasher_state_second_half(&self, i: RowIndex) -> Word { + const SECOND_WORD_OFFSET: usize = 4; + let mut state = [ZERO; DIGEST_LEN]; + for (col, s) in state.iter_mut().enumerate() { + *s = self + .columns + .get_column(DECODER_TRACE_OFFSET + HASHER_STATE_OFFSET + SECOND_WORD_OFFSET + col) + [i]; + } + state + } + /// Returns a specific element from the hasher state at row i. - pub fn decoder_hasher_state_element(&self, element: usize, i: usize) -> Felt { + pub fn decoder_hasher_state_element(&self, element: usize, i: RowIndex) -> Felt { self.columns.get_column(DECODER_TRACE_OFFSET + HASHER_STATE_OFFSET + element)[i + 1] } /// Returns the current function hash (i.e., root) at row i. - pub fn fn_hash(&self, i: usize) -> [Felt; DIGEST_LEN] { + pub fn fn_hash(&self, i: RowIndex) -> [Felt; DIGEST_LEN] { let mut state = [ZERO; DIGEST_LEN]; for (col, s) in state.iter_mut().enumerate() { *s = self.columns.get_column(FN_HASH_OFFSET + col)[i]; @@ -130,53 +150,53 @@ impl MainTrace { } /// Returns the `is_loop_body` flag at row i. - pub fn is_loop_body_flag(&self, i: usize) -> Felt { + pub fn is_loop_body_flag(&self, i: RowIndex) -> Felt { self.columns.get_column(DECODER_TRACE_OFFSET + IS_LOOP_BODY_FLAG_COL_IDX)[i] } /// Returns the `is_loop` flag at row i. - pub fn is_loop_flag(&self, i: usize) -> Felt { + pub fn is_loop_flag(&self, i: RowIndex) -> Felt { self.columns.get_column(DECODER_TRACE_OFFSET + IS_LOOP_FLAG_COL_IDX)[i] } /// Returns the `is_call` flag at row i. - pub fn is_call_flag(&self, i: usize) -> Felt { + pub fn is_call_flag(&self, i: RowIndex) -> Felt { self.columns.get_column(DECODER_TRACE_OFFSET + IS_CALL_FLAG_COL_IDX)[i] } /// Returns the `is_syscall` flag at row i. - pub fn is_syscall_flag(&self, i: usize) -> Felt { + pub fn is_syscall_flag(&self, i: RowIndex) -> Felt { self.columns.get_column(DECODER_TRACE_OFFSET + IS_SYSCALL_FLAG_COL_IDX)[i] } /// Returns the operation batch flags at row i. This indicates the number of op groups in /// the current batch that is being processed. - pub fn op_batch_flag(&self, i: usize) -> [Felt; NUM_OP_BATCH_FLAGS] { + pub fn op_batch_flag(&self, i: RowIndex) -> [Felt; NUM_OP_BATCH_FLAGS] { [ - self.columns.get(DECODER_TRACE_OFFSET + OP_BATCH_FLAGS_OFFSET, i), - self.columns.get(DECODER_TRACE_OFFSET + OP_BATCH_FLAGS_OFFSET + 1, i), - self.columns.get(DECODER_TRACE_OFFSET + OP_BATCH_FLAGS_OFFSET + 2, i), + self.columns.get(DECODER_TRACE_OFFSET + OP_BATCH_FLAGS_OFFSET, i.into()), + self.columns.get(DECODER_TRACE_OFFSET + OP_BATCH_FLAGS_OFFSET + 1, i.into()), + self.columns.get(DECODER_TRACE_OFFSET + OP_BATCH_FLAGS_OFFSET + 2, i.into()), ] } /// Returns the operation group count. This indicates the number of operation that remain /// to be executed in the current span block. - pub fn group_count(&self, i: usize) -> Felt { + pub fn group_count(&self, i: RowIndex) -> Felt { self.columns.get_column(DECODER_TRACE_OFFSET + GROUP_COUNT_COL_IDX)[i] } /// Returns the delta between the current and next group counts. - pub fn delta_group_count(&self, i: usize) -> Felt { + pub fn delta_group_count(&self, i: RowIndex) -> Felt { self.group_count(i) - self.group_count(i + 1) } /// Returns the `in_span` flag at row i. - pub fn is_in_span(&self, i: usize) -> Felt { + pub fn is_in_span(&self, i: RowIndex) -> Felt { self.columns.get_column(DECODER_TRACE_OFFSET + IN_SPAN_COL_IDX)[i] } /// Constructs the i-th op code value from its individual bits. - pub fn get_op_code(&self, i: usize) -> Felt { + pub fn get_op_code(&self, i: RowIndex) -> Felt { let col_b0 = self.columns.get_column(DECODER_TRACE_OFFSET + 1); let col_b1 = self.columns.get_column(DECODER_TRACE_OFFSET + 2); let col_b2 = self.columns.get_column(DECODER_TRACE_OFFSET + 3); @@ -194,18 +214,23 @@ impl MainTrace { + b6.mul_small(64) } + /// Returns an iterator of [`RowIndex`] values over the row indices of this trace. + pub fn row_iter(&self) -> impl Iterator { + (0..self.num_rows()).map(RowIndex::from) + } + /// Returns a flag indicating whether the current operation induces a left shift of the operand /// stack. - pub fn is_left_shift(&self, i: usize) -> bool { - let b0 = self.columns.get(DECODER_TRACE_OFFSET + 1, i); - let b1 = self.columns.get(DECODER_TRACE_OFFSET + 2, i); - let b2 = self.columns.get(DECODER_TRACE_OFFSET + 3, i); - let b3 = self.columns.get(DECODER_TRACE_OFFSET + 4, i); - let b4 = self.columns.get(DECODER_TRACE_OFFSET + 5, i); - let b5 = self.columns.get(DECODER_TRACE_OFFSET + 6, i); - let b6 = self.columns.get(DECODER_TRACE_OFFSET + 7, i); - let e0 = self.columns.get(DECODER_TRACE_OFFSET + OP_BITS_EXTRA_COLS_OFFSET, i); - let h5 = self.columns.get(DECODER_TRACE_OFFSET + IS_LOOP_FLAG_COL_IDX, i); + pub fn is_left_shift(&self, i: RowIndex) -> bool { + let b0 = self.columns.get(DECODER_TRACE_OFFSET + 1, i.into()); + let b1 = self.columns.get(DECODER_TRACE_OFFSET + 2, i.into()); + let b2 = self.columns.get(DECODER_TRACE_OFFSET + 3, i.into()); + let b3 = self.columns.get(DECODER_TRACE_OFFSET + 4, i.into()); + let b4 = self.columns.get(DECODER_TRACE_OFFSET + 5, i.into()); + let b5 = self.columns.get(DECODER_TRACE_OFFSET + 6, i.into()); + let b6 = self.columns.get(DECODER_TRACE_OFFSET + 7, i.into()); + let e0 = self.columns.get(DECODER_TRACE_OFFSET + OP_BITS_EXTRA_COLS_OFFSET, i.into()); + let h5 = self.columns.get(DECODER_TRACE_OFFSET + IS_LOOP_FLAG_COL_IDX, i.into()); // group with left shift effect grouped by a common prefix ([b6, b5, b4] == [ZERO, ONE, ZERO])|| @@ -221,43 +246,43 @@ impl MainTrace { /// Returns a flag indicating whether the current operation induces a right shift of the operand /// stack. - pub fn is_right_shift(&self, i: usize) -> bool { - let b0 = self.columns.get(DECODER_TRACE_OFFSET + 1, i); - let b1 = self.columns.get(DECODER_TRACE_OFFSET + 2, i); - let b2 = self.columns.get(DECODER_TRACE_OFFSET + 3, i); - let b3 = self.columns.get(DECODER_TRACE_OFFSET + 4, i); - let b4 = self.columns.get(DECODER_TRACE_OFFSET + 5, i); - let b5 = self.columns.get(DECODER_TRACE_OFFSET + 6, i); - let b6 = self.columns.get(DECODER_TRACE_OFFSET + 7, i); + pub fn is_right_shift(&self, i: RowIndex) -> bool { + let b0 = self.columns.get(DECODER_TRACE_OFFSET + 1, i.into()); + let b1 = self.columns.get(DECODER_TRACE_OFFSET + 2, i.into()); + let b2 = self.columns.get(DECODER_TRACE_OFFSET + 3, i.into()); + let b3 = self.columns.get(DECODER_TRACE_OFFSET + 4, i.into()); + let b4 = self.columns.get(DECODER_TRACE_OFFSET + 5, i.into()); + let b5 = self.columns.get(DECODER_TRACE_OFFSET + 6, i.into()); + let b6 = self.columns.get(DECODER_TRACE_OFFSET + 7, i.into()); // group with right shift effect grouped by a common prefix [b6, b5, b4] == [ZERO, ONE, ONE]|| // u32SPLIT 100_1000 ([b6, b5, b4, b3, b2, b1, b0] == [ONE, ZERO, ZERO, ONE, ZERO, ZERO, ZERO]) || - // PUSH i.e., 110_0100 - ([b6, b5, b4, b3, b2, b1, b0] == [ONE, ONE, ZERO, ZERO, ONE, ZERO, ZERO]) + // PUSH i.e., 101_1011 + ([b6, b5, b4, b3, b2, b1, b0] == [ONE, ZERO, ONE, ONE, ZERO, ONE, ONE]) } // STACK COLUMNS // -------------------------------------------------------------------------------------------- /// Returns the value of the stack depth column at row i. - pub fn stack_depth(&self, i: usize) -> Felt { + pub fn stack_depth(&self, i: RowIndex) -> Felt { self.columns.get_column(STACK_TRACE_OFFSET + B0_COL_IDX)[i] } /// Returns the element at row i in a given stack trace column. - pub fn stack_element(&self, column: usize, i: usize) -> Felt { + pub fn stack_element(&self, column: usize, i: RowIndex) -> Felt { self.columns.get_column(STACK_TRACE_OFFSET + column)[i] } /// Returns the address of the top element in the stack overflow table at row i. - pub fn parent_overflow_address(&self, i: usize) -> Felt { + pub fn parent_overflow_address(&self, i: RowIndex) -> Felt { self.columns.get_column(STACK_TRACE_OFFSET + B1_COL_IDX)[i] } /// Returns a flag indicating whether the overflow stack is non-empty. - pub fn is_non_empty_overflow(&self, i: usize) -> bool { + pub fn is_non_empty_overflow(&self, i: RowIndex) -> bool { let b0 = self.columns.get_column(STACK_TRACE_OFFSET + B0_COL_IDX)[i]; let h0 = self.columns.get_column(STACK_TRACE_OFFSET + H0_COL_IDX)[i]; (b0 - Felt::new(16)) * h0 == ONE @@ -267,37 +292,37 @@ impl MainTrace { // -------------------------------------------------------------------------------------------- /// Returns chiplet column number 0 at row i. - pub fn chiplet_selector_0(&self, i: usize) -> Felt { + pub fn chiplet_selector_0(&self, i: RowIndex) -> Felt { self.columns.get_column(CHIPLETS_OFFSET)[i] } /// Returns chiplet column number 1 at row i. - pub fn chiplet_selector_1(&self, i: usize) -> Felt { + pub fn chiplet_selector_1(&self, i: RowIndex) -> Felt { self.columns.get_column(CHIPLETS_OFFSET + 1)[i] } /// Returns chiplet column number 2 at row i. - pub fn chiplet_selector_2(&self, i: usize) -> Felt { + pub fn chiplet_selector_2(&self, i: RowIndex) -> Felt { self.columns.get_column(CHIPLETS_OFFSET + 2)[i] } /// Returns chiplet column number 3 at row i. - pub fn chiplet_selector_3(&self, i: usize) -> Felt { + pub fn chiplet_selector_3(&self, i: RowIndex) -> Felt { self.columns.get_column(CHIPLETS_OFFSET + 3)[i] } /// Returns chiplet column number 4 at row i. - pub fn chiplet_selector_4(&self, i: usize) -> Felt { + pub fn chiplet_selector_4(&self, i: RowIndex) -> Felt { self.columns.get_column(CHIPLETS_OFFSET + 4)[i] } /// Returns `true` if a row is part of the hash chiplet. - pub fn is_hash_row(&self, i: usize) -> bool { + pub fn is_hash_row(&self, i: RowIndex) -> bool { self.chiplet_selector_0(i) == ZERO } /// Returns the (full) state of the hasher chiplet at row i. - pub fn chiplet_hasher_state(&self, i: usize) -> [Felt; STATE_WIDTH] { + pub fn chiplet_hasher_state(&self, i: RowIndex) -> [Felt; STATE_WIDTH] { let mut state = [ZERO; STATE_WIDTH]; for (idx, col_idx) in HASHER_STATE_COL_RANGE.enumerate() { let column = self.columns.get_column(col_idx); @@ -307,74 +332,74 @@ impl MainTrace { } /// Returns the hasher's node index column at row i - pub fn chiplet_node_index(&self, i: usize) -> Felt { - self.columns.get(HASHER_NODE_INDEX_COL_IDX, i) + pub fn chiplet_node_index(&self, i: RowIndex) -> Felt { + self.columns.get(HASHER_NODE_INDEX_COL_IDX, i.into()) } /// Returns `true` if a row is part of the bitwise chiplet. - pub fn is_bitwise_row(&self, i: usize) -> bool { + pub fn is_bitwise_row(&self, i: RowIndex) -> bool { self.chiplet_selector_0(i) == ONE && self.chiplet_selector_1(i) == ZERO } /// Returns the bitwise column holding the aggregated value of input `a` at row i. - pub fn chiplet_bitwise_a(&self, i: usize) -> Felt { + pub fn chiplet_bitwise_a(&self, i: RowIndex) -> Felt { self.columns.get_column(BITWISE_A_COL_IDX)[i] } /// Returns the bitwise column holding the aggregated value of input `b` at row i. - pub fn chiplet_bitwise_b(&self, i: usize) -> Felt { + pub fn chiplet_bitwise_b(&self, i: RowIndex) -> Felt { self.columns.get_column(BITWISE_B_COL_IDX)[i] } /// Returns the bitwise column holding the aggregated value of the output at row i. - pub fn chiplet_bitwise_z(&self, i: usize) -> Felt { + pub fn chiplet_bitwise_z(&self, i: RowIndex) -> Felt { self.columns.get_column(BITWISE_OUTPUT_COL_IDX)[i] } /// Returns `true` if a row is part of the memory chiplet. - pub fn is_memory_row(&self, i: usize) -> bool { + pub fn is_memory_row(&self, i: RowIndex) -> bool { self.chiplet_selector_0(i) == ONE && self.chiplet_selector_1(i) == ONE && self.chiplet_selector_2(i) == ZERO } /// Returns the i-th row of the chiplet column containing memory context. - pub fn chiplet_memory_ctx(&self, i: usize) -> Felt { + pub fn chiplet_memory_ctx(&self, i: RowIndex) -> Felt { self.columns.get_column(MEMORY_CTX_COL_IDX)[i] } /// Returns the i-th row of the chiplet column containing memory address. - pub fn chiplet_memory_addr(&self, i: usize) -> Felt { + pub fn chiplet_memory_addr(&self, i: RowIndex) -> Felt { self.columns.get_column(MEMORY_ADDR_COL_IDX)[i] } /// Returns the i-th row of the chiplet column containing clock cycle. - pub fn chiplet_memory_clk(&self, i: usize) -> Felt { + pub fn chiplet_memory_clk(&self, i: RowIndex) -> Felt { self.columns.get_column(MEMORY_CLK_COL_IDX)[i] } /// Returns the i-th row of the chiplet column containing the zeroth memory value element. - pub fn chiplet_memory_value_0(&self, i: usize) -> Felt { + pub fn chiplet_memory_value_0(&self, i: RowIndex) -> Felt { self.columns.get_column(MEMORY_V_COL_RANGE.start)[i] } /// Returns the i-th row of the chiplet column containing the first memory value element. - pub fn chiplet_memory_value_1(&self, i: usize) -> Felt { + pub fn chiplet_memory_value_1(&self, i: RowIndex) -> Felt { self.columns.get_column(MEMORY_V_COL_RANGE.start + 1)[i] } /// Returns the i-th row of the chiplet column containing the second memory value element. - pub fn chiplet_memory_value_2(&self, i: usize) -> Felt { + pub fn chiplet_memory_value_2(&self, i: RowIndex) -> Felt { self.columns.get_column(MEMORY_V_COL_RANGE.start + 2)[i] } /// Returns the i-th row of the chiplet column containing the third memory value element. - pub fn chiplet_memory_value_3(&self, i: usize) -> Felt { + pub fn chiplet_memory_value_3(&self, i: RowIndex) -> Felt { self.columns.get_column(MEMORY_V_COL_RANGE.start + 3)[i] } /// Returns `true` if a row is part of the kernel chiplet. - pub fn is_kernel_row(&self, i: usize) -> bool { + pub fn is_kernel_row(&self, i: RowIndex) -> bool { self.chiplet_selector_0(i) == ONE && self.chiplet_selector_1(i) == ONE && self.chiplet_selector_2(i) == ONE @@ -382,31 +407,31 @@ impl MainTrace { } /// Returns the i-th row of the kernel chiplet `addr` column. - pub fn chiplet_kernel_addr(&self, i: usize) -> Felt { + pub fn chiplet_kernel_addr(&self, i: RowIndex) -> Felt { self.columns.get_column(CHIPLETS_OFFSET + 5)[i] } /// Returns the i-th row of the chiplet column containing the zeroth element of the kernel /// procedure root. - pub fn chiplet_kernel_root_0(&self, i: usize) -> Felt { + pub fn chiplet_kernel_root_0(&self, i: RowIndex) -> Felt { self.columns.get_column(CHIPLETS_OFFSET + 6)[i] } /// Returns the i-th row of the chiplet column containing the first element of the kernel /// procedure root. - pub fn chiplet_kernel_root_1(&self, i: usize) -> Felt { + pub fn chiplet_kernel_root_1(&self, i: RowIndex) -> Felt { self.columns.get_column(CHIPLETS_OFFSET + 7)[i] } /// Returns the i-th row of the chiplet column containing the second element of the kernel /// procedure root. - pub fn chiplet_kernel_root_2(&self, i: usize) -> Felt { + pub fn chiplet_kernel_root_2(&self, i: RowIndex) -> Felt { self.columns.get_column(CHIPLETS_OFFSET + 8)[i] } /// Returns the i-th row of the chiplet column containing the third element of the kernel /// procedure root. - pub fn chiplet_kernel_root_3(&self, i: usize) -> Felt { + pub fn chiplet_kernel_root_3(&self, i: RowIndex) -> Felt { self.columns.get_column(CHIPLETS_OFFSET + 9)[i] } @@ -415,8 +440,8 @@ impl MainTrace { /// Returns `true` if the hasher chiplet flags indicate the initialization of verifying /// a Merkle path to an old node during Merkle root update procedure (MRUPDATE). - pub fn f_mv(&self, i: usize) -> bool { - (i % HASH_CYCLE_LEN == 0) + pub fn f_mv(&self, i: RowIndex) -> bool { + (i.as_usize() % HASH_CYCLE_LEN == 0) && self.chiplet_selector_0(i) == ZERO && self.chiplet_selector_1(i) == ONE && self.chiplet_selector_2(i) == ONE @@ -425,8 +450,8 @@ impl MainTrace { /// Returns `true` if the hasher chiplet flags indicate the continuation of verifying /// a Merkle path to an old node during Merkle root update procedure (MRUPDATE). - pub fn f_mva(&self, i: usize) -> bool { - (i % HASH_CYCLE_LEN == HASH_CYCLE_LEN - 1) + pub fn f_mva(&self, i: RowIndex) -> bool { + (i.as_usize() % HASH_CYCLE_LEN == HASH_CYCLE_LEN - 1) && self.chiplet_selector_0(i) == ZERO && self.chiplet_selector_1(i) == ONE && self.chiplet_selector_2(i) == ONE @@ -435,8 +460,8 @@ impl MainTrace { /// Returns `true` if the hasher chiplet flags indicate the initialization of verifying /// a Merkle path to a new node during Merkle root update procedure (MRUPDATE). - pub fn f_mu(&self, i: usize) -> bool { - (i % HASH_CYCLE_LEN == 0) + pub fn f_mu(&self, i: RowIndex) -> bool { + (i.as_usize() % HASH_CYCLE_LEN == 0) && self.chiplet_selector_0(i) == ZERO && self.chiplet_selector_1(i) == ONE && self.chiplet_selector_2(i) == ONE @@ -445,8 +470,8 @@ impl MainTrace { /// Returns `true` if the hasher chiplet flags indicate the continuation of verifying /// a Merkle path to a new node during Merkle root update procedure (MRUPDATE). - pub fn f_mua(&self, i: usize) -> bool { - (i % HASH_CYCLE_LEN == HASH_CYCLE_LEN - 1) + pub fn f_mua(&self, i: RowIndex) -> bool { + (i.as_usize() % HASH_CYCLE_LEN == HASH_CYCLE_LEN - 1) && self.chiplet_selector_0(i) == ZERO && self.chiplet_selector_1(i) == ONE && self.chiplet_selector_2(i) == ONE diff --git a/air/src/trace/mod.rs b/air/src/trace/mod.rs index f61e1cae05..62c7a910f5 100644 --- a/air/src/trace/mod.rs +++ b/air/src/trace/mod.rs @@ -1,10 +1,12 @@ use core::ops::Range; + use vm_core::utils::range; pub mod chiplets; pub mod decoder; pub mod main_trace; pub mod range; +pub mod rows; pub mod stack; // CONSTANTS diff --git a/air/src/trace/rows.rs b/air/src/trace/rows.rs new file mode 100644 index 0000000000..25f1f9e338 --- /dev/null +++ b/air/src/trace/rows.rs @@ -0,0 +1,319 @@ +use core::{ + fmt::{Display, Formatter}, + ops::{Add, AddAssign, Bound, Index, IndexMut, Mul, RangeBounds, Sub, SubAssign}, +}; + +use vm_core::Felt; + +/// Represents the types of errors that can occur when converting from and into [`RowIndex`] and +/// using its operations. +#[derive(Debug, thiserror::Error, PartialEq, Eq)] +pub enum RowIndexError { + #[error("value is too large to be converted into RowIndex: {0}")] + InvalidSize(T), +} + +// ROW INDEX +// ================================================================================================ + +/// A newtype wrapper around a usize value representing a step in the execution trace. +#[derive(Debug, Copy, Clone, Eq, Ord, PartialOrd)] +pub struct RowIndex(u32); + +impl RowIndex { + pub fn as_usize(&self) -> usize { + self.0 as usize + } + + pub fn as_u32(&self) -> u32 { + self.0 + } +} + +impl Display for RowIndex { + fn fmt(&self, f: &mut Formatter) -> core::fmt::Result { + write!(f, "{}", self.0) + } +} + +// FROM ROW INDEX +// ================================================================================================ + +impl From for u32 { + fn from(step: RowIndex) -> u32 { + step.0 + } +} + +impl From for u64 { + fn from(step: RowIndex) -> u64 { + step.0 as u64 + } +} + +impl From for usize { + fn from(step: RowIndex) -> usize { + step.0 as usize + } +} + +impl From for Felt { + fn from(step: RowIndex) -> Felt { + Felt::from(step.0) + } +} + +// INTO ROW INDEX +// ================================================================================================ + +/// Converts a usize value into a [`RowIndex`]. +/// +/// # Panics +/// +/// This function will panic if the number represented by the usize is greater than the maximum +/// [`RowIndex`] value, `u32::MAX`. +impl From for RowIndex { + fn from(value: usize) -> Self { + let value = u32::try_from(value).map_err(|_| RowIndexError::InvalidSize(value)).unwrap(); + value.into() + } +} + +/// Converts a u64 value into a [`RowIndex`]. +/// +/// # Panics +/// +/// This function will panic if the number represented by the u64 is greater than the maximum +/// [`RowIndex`] value, `u32::MAX`. +impl TryFrom for RowIndex { + type Error = RowIndexError; + + fn try_from(value: u64) -> Result { + let value = u32::try_from(value).map_err(|_| RowIndexError::InvalidSize(value))?; + Ok(RowIndex::from(value)) + } +} + +impl From for RowIndex { + fn from(value: u32) -> Self { + Self(value) + } +} + +/// Converts an i32 value into a [`RowIndex`]. +/// +/// # Panics +/// +/// This function will panic if the number represented by the i32 is less than 0. +impl From for RowIndex { + fn from(value: i32) -> Self { + let value = u32::try_from(value).map_err(|_| RowIndexError::InvalidSize(value)).unwrap(); + RowIndex(value) + } +} + +// ROW INDEX OPS +// ================================================================================================ + +/// Subtracts a usize from a [`RowIndex`]. +/// +/// # Panics +/// +/// This function will panic if the number represented by the usize is greater than the maximum +/// [`RowIndex`] value, `u32::MAX`. +impl Sub for RowIndex { + type Output = RowIndex; + + fn sub(self, rhs: usize) -> Self::Output { + let rhs = u32::try_from(rhs).map_err(|_| RowIndexError::InvalidSize(rhs)).unwrap(); + RowIndex(self.0 - rhs) + } +} + +impl SubAssign for RowIndex { + fn sub_assign(&mut self, rhs: u32) { + self.0 -= rhs; + } +} + +impl Sub for RowIndex { + type Output = usize; + + fn sub(self, rhs: RowIndex) -> Self::Output { + (self.0 - rhs.0) as usize + } +} + +impl RowIndex { + pub fn saturating_sub(self, rhs: u32) -> Self { + RowIndex(self.0.saturating_sub(rhs)) + } + + pub fn max(self, other: RowIndex) -> Self { + RowIndex(self.0.max(other.0)) + } +} + +/// Adds a usize to a [`RowIndex`]. +/// +/// # Panics +/// +/// This function will panic if the number represented by the usize is greater than the maximum +/// [`RowIndex`] value, `u32::MAX`. +impl Add for RowIndex { + type Output = RowIndex; + + fn add(self, rhs: usize) -> Self::Output { + let rhs = u32::try_from(rhs).map_err(|_| RowIndexError::InvalidSize(rhs)).unwrap(); + RowIndex(self.0 + rhs) + } +} + +impl Add for u32 { + type Output = RowIndex; + + fn add(self, rhs: RowIndex) -> Self::Output { + RowIndex(self + rhs.0) + } +} + +/// Adds a usize value to a RowIndex in place. +/// +/// # Panics +/// +/// This function will panic if the number represented by the usize is greater than the maximum +/// [`RowIndex`] value, `u32::MAX`. +impl AddAssign for RowIndex { + fn add_assign(&mut self, rhs: usize) { + let rhs: RowIndex = rhs.into(); + self.0 += rhs.0; + } +} + +impl Mul for usize { + type Output = RowIndex; + + fn mul(self, rhs: RowIndex) -> Self::Output { + (self * rhs.0 as usize).into() + } +} + +// ROW INDEX EQUALITY AND ORDERING +// ================================================================================================ + +impl PartialEq for RowIndex { + fn eq(&self, rhs: &RowIndex) -> bool { + self.0 == rhs.0 + } +} + +impl PartialEq for RowIndex { + fn eq(&self, rhs: &usize) -> bool { + self.0 == u32::try_from(*rhs).map_err(|_| RowIndexError::InvalidSize(*rhs)).unwrap() + } +} + +impl PartialEq for i32 { + fn eq(&self, rhs: &RowIndex) -> bool { + *self as u32 == u32::from(*rhs) + } +} + +impl PartialOrd for RowIndex { + fn partial_cmp(&self, rhs: &usize) -> Option { + let rhs = u32::try_from(*rhs).map_err(|_| RowIndexError::InvalidSize(*rhs)).unwrap(); + self.0.partial_cmp(&rhs) + } +} + +impl Index for [T] { + type Output = T; + fn index(&self, i: RowIndex) -> &Self::Output { + &self[i.0 as usize] + } +} + +impl IndexMut for [T] { + fn index_mut(&mut self, i: RowIndex) -> &mut Self::Output { + &mut self[i.0 as usize] + } +} + +impl RangeBounds for RowIndex { + fn start_bound(&self) -> Bound<&Self> { + Bound::Included(self) + } + fn end_bound(&self) -> Bound<&Self> { + Bound::Included(self) + } +} + +// TESTS +// ================================================================================================ +#[cfg(test)] +mod tests { + use alloc::collections::BTreeMap; + + #[test] + fn row_index_conversions() { + use super::RowIndex; + // Into + let _: RowIndex = 5.into(); + let _: RowIndex = 5u32.into(); + let _: RowIndex = (5usize).into(); + + // From + let _: u32 = RowIndex(5).into(); + let _: u64 = RowIndex(5).into(); + let _: usize = RowIndex(5).into(); + } + + #[test] + fn row_index_ops() { + use super::RowIndex; + + // Equality + assert_eq!(RowIndex(5), 5); + assert_eq!(RowIndex(5), RowIndex(5)); + assert!(RowIndex(5) == RowIndex(5)); + assert!(RowIndex(5) >= RowIndex(5)); + assert!(RowIndex(6) >= RowIndex(5)); + assert!(RowIndex(5) > RowIndex(4)); + assert!(RowIndex(5) <= RowIndex(5)); + assert!(RowIndex(4) <= RowIndex(5)); + assert!(RowIndex(5) < RowIndex(6)); + + // Arithmetic + assert_eq!(RowIndex(5) + 3, 8); + assert_eq!(RowIndex(5) - 3, 2); + assert_eq!(3 + RowIndex(5), 8); + assert_eq!(2 * RowIndex(5), 10); + + // Add assign + let mut step = RowIndex(5); + step += 5; + assert_eq!(step, 10); + } + + #[test] + fn row_index_range() { + use super::RowIndex; + let mut tree: BTreeMap = BTreeMap::new(); + tree.insert(RowIndex(0), 0); + tree.insert(RowIndex(1), 1); + tree.insert(RowIndex(2), 2); + let acc = + tree.range(RowIndex::from(0)..RowIndex::from(tree.len())) + .fold(0, |acc, (key, val)| { + assert_eq!(*key, RowIndex::from(acc)); + assert_eq!(*val, acc); + acc + 1 + }); + assert_eq!(acc, 3); + } + + #[test] + fn row_index_display() { + assert_eq!(format!("{}", super::RowIndex(5)), "5"); + } +} diff --git a/air/src/trace/stack/mod.rs b/air/src/trace/stack/mod.rs index 653dfcfb71..f4dbf4035d 100644 --- a/air/src/trace/stack/mod.rs +++ b/air/src/trace/stack/mod.rs @@ -1,4 +1,5 @@ use core::ops::Range; + use vm_core::utils::range; // CONSTANTS diff --git a/air/src/utils.rs b/air/src/utils.rs index 70a29a9d38..955fabba21 100644 --- a/air/src/utils.rs +++ b/air/src/utils.rs @@ -1,9 +1,10 @@ use alloc::vec::Vec; use core::ops::Range; -use super::FieldElement; use vm_core::utils::range as create_range; +use super::FieldElement; + // BASIC CONSTRAINT OPERATORS // ================================================================================================ @@ -69,11 +70,7 @@ impl TransitionConstraintRange { let range_checker = create_range(stack.end, range_checker_len); let chiplets = create_range(range_checker.end, chiplets_len); - Self { - stack, - range_checker, - chiplets, - } + Self { stack, range_checker, chiplets } } } @@ -91,9 +88,10 @@ macro_rules! select_result_range { // ================================================================================================ #[cfg(test)] mod tests { - use super::TransitionConstraintRange; use vm_core::utils::range as create_range; + use super::TransitionConstraintRange; + #[test] fn transition_constraint_ranges() { let sys_constraints_len = 1; diff --git a/assembly/Cargo.toml b/assembly/Cargo.toml index 1aafccb42f..da461bc398 100644 --- a/assembly/Cargo.toml +++ b/assembly/Cargo.toml @@ -1,16 +1,17 @@ [package] name = "miden-assembly" -version = "0.8.0" +version = "0.10.5" description = "Miden VM assembly language" -authors = ["miden contributors"] +documentation = "https://docs.rs/miden-assembly/0.10.5" readme = "README.md" -license = "MIT" -repository = "https://github.com/0xPolygonMiden/miden-vm" -documentation = "https://docs.rs/miden-assembly/0.8.0" categories = ["compilers", "no-std"] keywords = ["assembler", "assembly", "language", "miden"] -edition = "2021" -rust-version = "1.76" +license.workspace = true +authors.workspace = true +homepage.workspace = true +repository.workspace = true +rust-version.workspace = true +edition.workspace = true [lib] bench = false @@ -18,45 +19,29 @@ doctest = false [features] default = ["std"] -std = [ - "aho-corasick/std", - "vm-core/std", - "miette/fancy", - "thiserror/std", - "miette/std", -] +std = ["aho-corasick/std", "miette/fancy", "miette/std", "thiserror/std", "vm-core/std"] testing = ["dep:regex"] [dependencies] aho-corasick = { version = "1.1", default-features = false } lalrpop-util = { version = "0.20", default-features = false } -miette = { version = "7.1.0", git = "https://github.com/bitwalker/miette", branch = "no-std", default-features = false, features = [ +miette = { package = "miden-miette", version = "7.1", default-features = false, features = [ "fancy-no-syscall", - "derive", + "derive" ] } -regex = { version = "1.10", optional = true, default-features = false, features = [ - "unicode", - "perf", -] } -smallvec = { version = "1.13", features = [ - "union", - "const_generics", - "const_new", -] } -tracing = { version = "0.1", default-features = false, features = [ - "attributes", -] } -thiserror = { version = "1.0", git = "https://github.com/bitwalker/thiserror", branch = "no-std", default-features = false } +regex = { version = "1.10", optional = true, default-features = false, features = ["unicode", "perf"] } +smallvec = { version = "1.13", features = ["union", "const_generics", "const_new"] } +thiserror = { package = "miden-thiserror", version = "1.0", default-features = false } +tracing = { version = "0.1", default-features = false, features = ["attributes"] } unicode-width = { version = "0.1", features = ["no_std"] } -vm-core = { package = "miden-core", path = "../core", version = "0.8", default-features = false } +vm-core = { package = "miden-core", path = "../core", version = "0.10", default-features = false, features = [ + "diagnostics", +] } [dev-dependencies] pretty_assertions = "1.4" -regex = { version = "1.10", default-features = false, features = [ - "unicode", - "perf", -] } +regex = { version = "1.10", default-features = false, features = [ "unicode", "perf"] } [build-dependencies] lalrpop = { version = "0.20", default-features = false } -rustc_version = "0.2" +rustc_version = "0.4" diff --git a/assembly/README.md b/assembly/README.md index 9a76a116a8..2814bb79b5 100644 --- a/assembly/README.md +++ b/assembly/README.md @@ -28,10 +28,10 @@ use miden_assembly::Assembler; let assembler = Assembler::default(); // Emit a program which pushes values 3 and 5 onto the stack and adds them -let program = assembler.assemble("begin push.3 push.5 add end").unwrap(); +let program = assembler.assemble_program("begin push.3 push.5 add end").unwrap(); // Emit a program from some source code on disk (requires the `std` feature) -let program = assembler.assemble(&Path::new("./example.masm")).unwrap(); +let program = assembler.assemble_program(&Path::new("./example.masm")).unwrap(); ``` > [!NOTE] @@ -119,7 +119,7 @@ Programs compiled by this assembler will be able to make calls to the `foo` procedure by executing the `syscall` instruction, like so: ```rust -assembler.assemble(" +assembler.assemble_program(" begin syscall.foo end @@ -169,7 +169,7 @@ let assembler = Assembler::default() .unwrap(); // Assemble our program -assembler.assemble(" +assembler.assemble_program(" begin push.1.2 syscall.foo diff --git a/assembly/src/assembler/basic_block_builder.rs b/assembly/src/assembler/basic_block_builder.rs new file mode 100644 index 0000000000..1113e1fead --- /dev/null +++ b/assembly/src/assembler/basic_block_builder.rs @@ -0,0 +1,235 @@ +use alloc::{borrow::Borrow, string::ToString, vec::Vec}; + +use vm_core::{ + mast::{DecoratorId, MastNodeId}, + AdviceInjector, AssemblyOp, Decorator, Operation, +}; + +use super::{mast_forest_builder::MastForestBuilder, BodyWrapper, DecoratorList, ProcedureContext}; +use crate::{ast::Instruction, AssemblyError, Span}; + +// BASIC BLOCK BUILDER +// ================================================================================================ + +/// A helper struct for constructing basic blocks while compiling procedure bodies. +/// +/// Operations and decorators can be added to a basic block builder via various `add_*()` and +/// `push_*()` methods, and then basic blocks can be extracted from the builder via `extract_*()` +/// methods. +/// +/// The same basic block builder can be used to construct many blocks. It is expected that when the +/// last basic block in a procedure's body is constructed [`Self::try_into_basic_block`] will be +/// used. +#[derive(Debug)] +pub struct BasicBlockBuilder<'a> { + ops: Vec, + decorators: DecoratorList, + epilogue: Vec, + last_asmop_pos: usize, + mast_forest_builder: &'a mut MastForestBuilder, +} + +/// Constructors +impl<'a> BasicBlockBuilder<'a> { + /// Returns a new [`BasicBlockBuilder`] instantiated with the specified optional wrapper. + /// + /// If the wrapper is provided, the prologue of the wrapper is immediately appended to the + /// vector of span operations. The epilogue of the wrapper is appended to the list of operations + /// upon consumption of the builder via the [`Self::try_into_basic_block`] method. + pub(super) fn new( + wrapper: Option, + mast_forest_builder: &'a mut MastForestBuilder, + ) -> Self { + match wrapper { + Some(wrapper) => Self { + ops: wrapper.prologue, + decorators: Vec::new(), + epilogue: wrapper.epilogue, + last_asmop_pos: 0, + mast_forest_builder, + }, + None => Self { + ops: Default::default(), + decorators: Default::default(), + epilogue: Default::default(), + last_asmop_pos: 0, + mast_forest_builder, + }, + } + } +} + +/// Accessors +impl<'a> BasicBlockBuilder<'a> { + /// Returns a reference to the internal [`MastForestBuilder`]. + pub fn mast_forest_builder(&self) -> &MastForestBuilder { + self.mast_forest_builder + } + + /// Returns a mutable reference to the internal [`MastForestBuilder`]. + pub fn mast_forest_builder_mut(&mut self) -> &mut MastForestBuilder { + self.mast_forest_builder + } +} + +/// Operations +impl<'a> BasicBlockBuilder<'a> { + /// Adds the specified operation to the list of basic block operations. + pub fn push_op(&mut self, op: Operation) { + self.ops.push(op); + } + + /// Adds the specified sequence of operations to the list of basic block operations. + pub fn push_ops(&mut self, ops: I) + where + I: IntoIterator, + O: Borrow, + { + self.ops.extend(ops.into_iter().map(|o| *o.borrow())); + } + + /// Adds the specified operation n times to the list of basic block operations. + pub fn push_op_many(&mut self, op: Operation, n: usize) { + let new_len = self.ops.len() + n; + self.ops.resize(new_len, op); + } +} + +/// Decorators +impl<'a> BasicBlockBuilder<'a> { + /// Add the specified decorator to the list of basic block decorators. + pub fn push_decorator(&mut self, decorator: Decorator) -> Result<(), AssemblyError> { + let decorator_id = self.mast_forest_builder.ensure_decorator(decorator)?; + self.decorators.push((self.ops.len(), decorator_id)); + + Ok(()) + } + + /// Adds the specified advice injector to the list of basic block decorators. + pub fn push_advice_injector(&mut self, injector: AdviceInjector) -> Result<(), AssemblyError> { + self.push_decorator(Decorator::Advice(injector)) + } + + /// Adds an AsmOp decorator to the list of basic block decorators. + /// + /// This indicates that the provided instruction should be tracked and the cycle count for + /// this instruction will be computed when the call to set_instruction_cycle_count() is made. + pub fn track_instruction( + &mut self, + instruction: &Span, + proc_ctx: &ProcedureContext, + ) -> Result<(), AssemblyError> { + let span = instruction.span(); + let location = proc_ctx.source_manager().location(span).ok(); + let context_name = proc_ctx.name().to_string(); + let num_cycles = 0; + let op = instruction.to_string(); + let should_break = instruction.should_break(); + let op = AssemblyOp::new(location, context_name, num_cycles, op, should_break); + self.push_decorator(Decorator::AsmOp(op))?; + self.last_asmop_pos = self.decorators.len() - 1; + + Ok(()) + } + + /// Computes the number of cycles elapsed since the last invocation of track_instruction() + /// and updates the related AsmOp decorator to include this cycle count. + /// + /// If the cycle count is 0, the original decorator is removed from the list. This can happen + /// for instructions which do not contribute any operations to the span block - e.g., exec, + /// call, and syscall. + pub fn set_instruction_cycle_count(&mut self) { + // get the last asmop decorator and the cycle at which it was added + let (op_start, assembly_op_id) = + self.decorators.get_mut(self.last_asmop_pos).expect("no asmop decorator"); + + let assembly_op = &mut self.mast_forest_builder[*assembly_op_id]; + assert!(matches!(assembly_op, Decorator::AsmOp(_))); + + // compute the cycle count for the instruction + let cycle_count = self.ops.len() - *op_start; + + // if the cycle count is 0, remove the decorator; otherwise update its cycle count + if cycle_count == 0 { + self.decorators.remove(self.last_asmop_pos); + } else if let Decorator::AsmOp(assembly_op) = assembly_op { + assembly_op.set_num_cycles(cycle_count as u8) + } + } +} + +/// Span Constructors +impl<'a> BasicBlockBuilder<'a> { + /// Creates and returns a new basic block node from the operations and decorators currently in + /// this builder. + /// + /// If there are no operations however, then no node is created, the decorators are left + /// untouched and `None` is returned. Use [`Self::drain_decorators`] to retrieve the decorators + /// in this case. + /// + /// This consumes all operations in the builder, but does not touch the operations in the + /// epilogue of the builder. + pub fn make_basic_block(&mut self) -> Result, AssemblyError> { + if !self.ops.is_empty() { + let ops = self.ops.drain(..).collect(); + let decorators = if !self.decorators.is_empty() { + Some(self.decorators.drain(..).collect()) + } else { + None + }; + + let basic_block_node_id = self.mast_forest_builder.ensure_block(ops, decorators)?; + + Ok(Some(basic_block_node_id)) + } else { + Ok(None) + } + } + + /// Creates and returns a new basic block node from the operations and decorators currently in + /// this builder. If there are no operations however, we return the decorators that were + /// accumulated up until this point. If the builder is empty, then no node is created and + /// `Nothing` is returned. + /// + /// The main differences with [`Self::make_basic_block`] are: + /// - Operations contained in the epilogue of the builder are appended to the list of ops which + /// go into the new BASIC BLOCK node. + /// - The builder is consumed in the process. + /// - Hence, any remaining decorators if no basic block was created are drained and returned. + pub fn try_into_basic_block(mut self) -> Result { + self.ops.append(&mut self.epilogue); + + if let Some(basic_block_node_id) = self.make_basic_block()? { + Ok(BasicBlockOrDecorators::BasicBlock(basic_block_node_id)) + } else if let Some(decorator_ids) = self.drain_decorators() { + Ok(BasicBlockOrDecorators::Decorators(decorator_ids)) + } else { + Ok(BasicBlockOrDecorators::Nothing) + } + } + + /// Drains and returns the decorators in the builder, if any. + /// + /// This should only be called after [`Self::make_basic_block`], when no blocks were created. + /// In other words, there MUST NOT be any operations left in the builder when this is called. + /// + /// # Panics + /// + /// Panics if there are still operations left in the builder. + pub fn drain_decorators(&mut self) -> Option> { + assert!(self.ops.is_empty()); + if !self.decorators.is_empty() { + Some(self.decorators.drain(..).map(|(_, decorator_id)| decorator_id).collect()) + } else { + None + } + } +} + +/// Holds either the node id of a basic block, or a list of decorators that are currently not +/// attached to any node. +pub enum BasicBlockOrDecorators { + BasicBlock(MastNodeId), + Decorators(Vec), + Nothing, +} diff --git a/assembly/src/assembler/context.rs b/assembly/src/assembler/context.rs deleted file mode 100644 index 2ca4d53c67..0000000000 --- a/assembly/src/assembler/context.rs +++ /dev/null @@ -1,280 +0,0 @@ -use alloc::{boxed::Box, sync::Arc}; - -use super::{procedure::CallSet, ArtifactKind, GlobalProcedureIndex, Procedure}; -use crate::{ - ast::{FullyQualifiedProcedureName, Visibility}, - diagnostics::SourceFile, - AssemblyError, LibraryPath, RpoDigest, SourceSpan, Span, Spanned, -}; -use vm_core::code_blocks::CodeBlock; - -// ASSEMBLY CONTEXT -// ================================================================================================ - -/// An [AssemblyContext] is used to store configuration and state pertaining to the current -/// compilation of a module/procedure by an [crate::Assembler]. -/// -/// The context specifies context-specific configuration, the type of artifact being compiled, -/// the current module being compiled, and the current procedure being compiled. -/// -/// To provide a custom context, you must compile by invoking the -/// [crate::Assembler::compile_in_context] API, which will use the provided context in place of -/// the default one generated internally by the other `compile`-like APIs. -#[derive(Default)] -pub struct AssemblyContext { - /// What kind of artifact are we assembling - kind: ArtifactKind, - /// When true, promote warning diagnostics to errors - warnings_as_errors: bool, - /// When true, this permits calls to refer to procedures which are not locally available, - /// as long as they are referenced by MAST root, and not by name. As long as the MAST for those - /// roots is present when the code is executed, this works fine. However, if the VM tries to - /// execute a program with such calls, and the MAST is not available, the program will trap. - allow_phantom_calls: bool, - /// The current procedure being compiled - current_procedure: Option, - /// The fully-qualified module path which should be compiled. - /// - /// If unset, it defaults to the module which represents the specified `kind`, i.e. if the kind - /// is executable, we compile the executable module, and so on. - /// - /// When set, the module graph is traversed from the given module only, so any code unreachable - /// from this module is not considered for compilation. - root: Option, -} - -/// Builders -impl AssemblyContext { - pub fn new(kind: ArtifactKind) -> Self { - Self { - kind, - ..Default::default() - } - } - - /// Returns a new [AssemblyContext] for a non-executable kernel modules. - pub fn for_kernel(path: &LibraryPath) -> Self { - Self::new(ArtifactKind::Kernel).with_root(path.clone()) - } - - /// Returns a new [AssemblyContext] for library modules. - pub fn for_library(path: &LibraryPath) -> Self { - Self::new(ArtifactKind::Library).with_root(path.clone()) - } - - /// Returns a new [AssemblyContext] for an executable module. - pub fn for_program(path: &LibraryPath) -> Self { - Self::new(ArtifactKind::Executable).with_root(path.clone()) - } - - fn with_root(mut self, path: LibraryPath) -> Self { - self.root = Some(path); - self - } - - /// When true, all warning diagnostics are promoted to errors - #[inline(always)] - pub fn set_warnings_as_errors(&mut self, yes: bool) { - self.warnings_as_errors = yes; - } - - #[inline] - pub(super) fn set_current_procedure(&mut self, context: ProcedureContext) { - self.current_procedure = Some(context); - } - - #[inline] - pub(super) fn take_current_procedure(&mut self) -> Option { - self.current_procedure.take() - } - - #[inline] - pub(super) fn unwrap_current_procedure(&self) -> &ProcedureContext { - self.current_procedure.as_ref().expect("missing current procedure context") - } - - #[inline] - pub(super) fn unwrap_current_procedure_mut(&mut self) -> &mut ProcedureContext { - self.current_procedure.as_mut().expect("missing current procedure context") - } - - /// Enables phantom calls when compiling with this context. - /// - /// # Panics - /// - /// This function will panic if you attempt to enable phantom calls for a kernel-mode context, - /// as non-local procedure calls are not allowed in kernel modules. - pub fn with_phantom_calls(mut self, allow_phantom_calls: bool) -> Self { - assert!( - !self.is_kernel() || !allow_phantom_calls, - "kernel modules cannot have phantom calls enabled" - ); - self.allow_phantom_calls = allow_phantom_calls; - self - } - - /// Returns true if this context is used for compiling a kernel. - pub fn is_kernel(&self) -> bool { - matches!(self.kind, ArtifactKind::Kernel) - } - - /// Returns true if this context is used for compiling an executable. - pub fn is_executable(&self) -> bool { - matches!(self.kind, ArtifactKind::Executable) - } - - /// Returns the type of artifact to produce with this context - pub fn kind(&self) -> ArtifactKind { - self.kind - } - - /// Returns true if this context treats warning diagnostics as errors - #[inline(always)] - pub fn warnings_as_errors(&self) -> bool { - self.warnings_as_errors - } - - /// Registers a "phantom" call to the procedure with the specified MAST root. - /// - /// A phantom call indicates that code for the procedure is not available. Executing a phantom - /// call will result in a runtime error. However, the VM may be able to execute a program with - /// phantom calls as long as the branches containing them are not taken. - /// - /// # Errors - /// Returns an error if phantom calls are not allowed in this assembly context. - pub fn register_phantom_call( - &mut self, - mast_root: Span, - ) -> Result<(), AssemblyError> { - if !self.allow_phantom_calls { - let source_file = self.unwrap_current_procedure().source_file().clone(); - let (span, digest) = mast_root.into_parts(); - Err(AssemblyError::PhantomCallsNotAllowed { - span, - source_file, - digest, - }) - } else { - Ok(()) - } - } - - /// Registers a call to an externally-defined procedure which we have previously compiled. - /// - /// The call set of the callee is added to the call set of the procedure we are currently - /// compiling, to reflect that all of the code reachable from the callee is by extension - /// reachable by the caller. - pub fn register_external_call( - &mut self, - callee: &Procedure, - inlined: bool, - ) -> Result<(), AssemblyError> { - let context = self.unwrap_current_procedure_mut(); - - // If we call the callee, it's callset is by extension part of our callset - context.extend_callset(callee.callset().iter().cloned()); - - // If the callee is not being inlined, add it to our callset - if !inlined { - context.insert_callee(callee.mast_root()); - } - - Ok(()) - } -} - -// PROCEDURE CONTEXT -// ================================================================================================ - -pub(super) struct ProcedureContext { - span: SourceSpan, - source_file: Option>, - gid: GlobalProcedureIndex, - name: FullyQualifiedProcedureName, - visibility: Visibility, - num_locals: u16, - callset: CallSet, -} - -impl ProcedureContext { - pub(super) fn new( - gid: GlobalProcedureIndex, - name: FullyQualifiedProcedureName, - visibility: Visibility, - ) -> Self { - Self { - span: name.span(), - source_file: None, - gid, - name, - visibility, - num_locals: 0, - callset: Default::default(), - } - } - - pub(super) fn with_span(mut self, span: SourceSpan) -> Self { - self.span = span; - self - } - - pub(super) fn with_source_file(mut self, source_file: Option>) -> Self { - self.source_file = source_file; - self - } - - pub(super) fn with_num_locals(mut self, num_locals: u16) -> Self { - self.num_locals = num_locals; - self - } - - pub(crate) fn insert_callee(&mut self, callee: RpoDigest) { - self.callset.insert(callee); - } - - pub(crate) fn extend_callset(&mut self, callees: I) - where - I: IntoIterator, - { - self.callset.extend(callees); - } - - pub fn num_locals(&self) -> u16 { - self.num_locals - } - - pub fn id(&self) -> GlobalProcedureIndex { - self.gid - } - - pub fn name(&self) -> &FullyQualifiedProcedureName { - &self.name - } - - #[allow(unused)] - pub fn module(&self) -> &LibraryPath { - &self.name.module - } - - pub fn source_file(&self) -> Option> { - self.source_file.clone() - } - - pub fn is_kernel(&self) -> bool { - self.visibility.is_syscall() - } - - pub fn into_procedure(self, code: CodeBlock) -> Box { - let procedure = Procedure::new(self.name, self.visibility, self.num_locals as u32, code) - .with_span(self.span) - .with_source_file(self.source_file) - .with_callset(self.callset); - Box::new(procedure) - } -} - -impl Spanned for ProcedureContext { - fn span(&self) -> SourceSpan { - self.span - } -} diff --git a/assembly/src/assembler/id.rs b/assembly/src/assembler/id.rs index ce01ca8c9d..8fc4114f76 100644 --- a/assembly/src/assembler/id.rs +++ b/assembly/src/assembler/id.rs @@ -16,10 +16,10 @@ use crate::ast::ProcedureIndex; /// /// /// In addition to the [super::ModuleGraph], these indices are also used with an instance of a -/// [super::ProcedureCache]. This is because the [super::ModuleGraph] and [super::ProcedureCache] -/// instances are paired, i.e. the [super::ModuleGraph] stores the syntax trees and call graph -/// analysis for a program, while the [super::ProcedureCache] caches the compiled -/// [super::Procedure]s for the same program, as derived from the corresponding graph. +/// [super::MastForestBuilder]. This is because the [super::ModuleGraph] and +/// [super::MastForestBuilder] instances are paired, i.e. the [super::ModuleGraph] stores the syntax +/// trees and call graph analysis for a program, while the [super::MastForestBuilder] caches the +/// compiled [super::Procedure]s for the same program, as derived from the corresponding graph. /// /// This is intended for use when we are doing global inter-procedural analysis on a (possibly /// growable) set of modules. It is expected that the index of a module in the set, as well as the @@ -54,3 +54,11 @@ impl ModuleIndex { self.0 as usize } } + +impl core::ops::Add for ModuleIndex { + type Output = GlobalProcedureIndex; + + fn add(self, rhs: ProcedureIndex) -> Self::Output { + GlobalProcedureIndex { module: self, index: rhs } + } +} diff --git a/assembly/src/assembler/instruction/adv_ops.rs b/assembly/src/assembler/instruction/adv_ops.rs index a361bdf500..86a38c1fa8 100644 --- a/assembly/src/assembler/instruction/adv_ops.rs +++ b/assembly/src/assembler/instruction/adv_ops.rs @@ -1,7 +1,8 @@ -use super::{validate_param, SpanBuilder}; -use crate::{ast::AdviceInjectorNode, AssemblyError, ADVICE_READ_LIMIT}; use vm_core::Operation; +use super::{validate_param, BasicBlockBuilder}; +use crate::{ast::AdviceInjectorNode, AssemblyError, ADVICE_READ_LIMIT}; + // NON-DETERMINISTIC (ADVICE) INPUTS // ================================================================================================ @@ -12,9 +13,9 @@ use vm_core::Operation; /// # Errors /// Returns an error if the specified number of values to pushed is smaller than 1 or greater /// than 16. -pub fn adv_push(span: &mut SpanBuilder, n: u8) -> Result<(), AssemblyError> { +pub fn adv_push(block_builder: &mut BasicBlockBuilder, n: u8) -> Result<(), AssemblyError> { validate_param(n, 1..=ADVICE_READ_LIMIT)?; - span.push_op_many(Operation::AdvPop, n as usize); + block_builder.push_op_many(Operation::AdvPop, n as usize); Ok(()) } @@ -22,6 +23,9 @@ pub fn adv_push(span: &mut SpanBuilder, n: u8) -> Result<(), AssemblyError> { // ================================================================================================ /// Appends advice injector decorator to the span. -pub fn adv_inject(span: &mut SpanBuilder, injector: &AdviceInjectorNode) { - span.push_advice_injector(injector.into()); +pub fn adv_inject( + block_builder: &mut BasicBlockBuilder, + injector: &AdviceInjectorNode, +) -> Result<(), AssemblyError> { + block_builder.push_advice_injector(injector.into()) } diff --git a/assembly/src/assembler/instruction/crypto_ops.rs b/assembly/src/assembler/instruction/crypto_ops.rs index 7852a432ac..54e6a2acff 100644 --- a/assembly/src/assembler/instruction/crypto_ops.rs +++ b/assembly/src/assembler/instruction/crypto_ops.rs @@ -1,6 +1,8 @@ -use super::SpanBuilder; use vm_core::{AdviceInjector, Felt, Operation::*}; +use super::BasicBlockBuilder; +use crate::AssemblyError; + // HASHING // ================================================================================================ @@ -23,7 +25,7 @@ use vm_core::{AdviceInjector, Felt, Operation::*}; /// 3. Drop D and B to achieve our result [C, ...] /// /// This operation takes 20 VM cycles. -pub(super) fn hash(span: &mut SpanBuilder) { +pub(super) fn hash(block_builder: &mut BasicBlockBuilder) { #[rustfmt::skip] let ops = [ // add 4 elements to the stack to be used as the capacity elements for the RPO permutation. @@ -48,7 +50,7 @@ pub(super) fn hash(span: &mut SpanBuilder) { // Drop 4 elements (the capacity portion) Drop, Drop, Drop, Drop, ]; - span.push_ops(ops); + block_builder.push_ops(ops); } /// Appends HPERM and stack manipulation operations to the span block as required to compute a @@ -70,7 +72,7 @@ pub(super) fn hash(span: &mut SpanBuilder) { /// 4. Drop F and D to return our result [E, ...]. /// /// This operation takes 16 VM cycles. -pub(super) fn hmerge(span: &mut SpanBuilder) { +pub(super) fn hmerge(block_builder: &mut BasicBlockBuilder) { #[rustfmt::skip] let ops = [ // Add 4 elements to the stack to prepare the capacity portion for the RPO permutation @@ -93,7 +95,7 @@ pub(super) fn hmerge(span: &mut SpanBuilder) { // Drop 4 elements (the capacity portion) Drop, Drop, Drop, Drop, ]; - span.push_ops(ops); + block_builder.push_ops(ops); } // MERKLE TREES @@ -111,21 +113,23 @@ pub(super) fn hmerge(span: &mut SpanBuilder) { /// - root of the tree, 4 elements. /// /// This operation takes 9 VM cycles. -pub(super) fn mtree_get(span: &mut SpanBuilder) { +pub(super) fn mtree_get(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyError> { // stack: [d, i, R, ...] // pops the value of the node we are looking for from the advice stack - read_mtree_node(span); + read_mtree_node(block_builder)?; #[rustfmt::skip] let ops = [ // verify the node V for root R with depth d and index i // => [V, d, i, R, ...] - MpVerify, + MpVerify(0), // move d, i back to the top of the stack and are dropped since they are // no longer needed => [V, R, ...] MovUp4, Drop, MovUp4, Drop, ]; - span.push_ops(ops); + block_builder.push_ops(ops); + + Ok(()) } /// Appends the MRUPDATE op with a parameter of "false" and stack manipulations to the span block @@ -141,11 +145,11 @@ pub(super) fn mtree_get(span: &mut SpanBuilder) { /// - new root of the tree after the update, 4 elements /// /// This operation takes 29 VM cycles. -pub(super) fn mtree_set(span: &mut SpanBuilder) { +pub(super) fn mtree_set(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyError> { // stack: [d, i, R_old, V_new, ...] // stack: [V_old, R_new, ...] (29 cycles) - update_mtree(span); + update_mtree(block_builder) } /// Creates a new Merkle tree in the advice provider by combining trees with the specified roots. @@ -161,31 +165,18 @@ pub(super) fn mtree_set(span: &mut SpanBuilder) { /// It is not checked whether the provided roots exist as Merkle trees in the advide providers. /// /// This operation takes 16 VM cycles. -pub(super) fn mtree_merge(span: &mut SpanBuilder) { +pub(super) fn mtree_merge(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyError> { // stack input: [R_rhs, R_lhs, ...] // stack output: [R_merged, ...] // invoke the advice provider function to merge 2 Merkle trees defined by the roots on the top // of the operand stack - span.push_advice_injector(AdviceInjector::MerkleNodeMerge); + block_builder.push_advice_injector(AdviceInjector::MerkleNodeMerge)?; // perform the `hmerge`, updating the operand stack - hmerge(span); -} + hmerge(block_builder); -/// Verifies if the node value `V`, on depth `d` and index `i` opens to the root `R` of a Merkle -/// tree by appending a [Operation::MpVerify]. The stack is expected to be arranged as follows -/// (from the top): -/// - node value `V`, 4 elements -/// - depth of the node `d`, 1 element -/// - index of the node `i`, 1 element -/// - root of the tree `R`, 4 elements -/// -/// After the operation is executed, the stack remains unchanged. -/// -/// This operation takes 1 VM cycle. -pub(super) fn mtree_verify(span: &mut SpanBuilder) { - span.push_op(MpVerify); + Ok(()) } // MERKLE TREES - HELPERS @@ -208,31 +199,33 @@ pub(super) fn mtree_verify(span: &mut SpanBuilder) { /// - new value of the node, 4 elements (only in the case of mtree_set) /// /// This operation takes 4 VM cycles. -fn read_mtree_node(span: &mut SpanBuilder) { +fn read_mtree_node(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyError> { // The stack should be arranged in the following way: [d, i, R, ...] so that the decorator // can fetch the node value from the root. In the `mtree.get` operation we have the stack in // the following format: [d, i, R], whereas in the case of `mtree.set` we would also have the // new node value post the tree root: [d, i, R, V_new] // // pops the value of the node we are looking for from the advice stack - span.push_advice_injector(AdviceInjector::MerkleNodeToStack); + block_builder.push_advice_injector(AdviceInjector::MerkleNodeToStack)?; // pops the old node value from advice the stack => MPVERIFY: [V_old, d, i, R, ...] // MRUPDATE: [V_old, d, i, R, V_new, ...] - span.push_op_many(AdvPop, 4); + block_builder.push_op_many(AdvPop, 4); + + Ok(()) } /// Update a node in the merkle tree. This operation will always copy the tree into a new instance, /// and perform the mutation on the copied tree. /// /// This operation takes 29 VM cycles. -fn update_mtree(span: &mut SpanBuilder) { +fn update_mtree(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyError> { // stack: [d, i, R_old, V_new, ...] // output: [R_new, R_old, V_new, V_old, ...] // Inject the old node value onto the stack for the call to MRUPDATE. // stack: [V_old, d, i, R_old, V_new, ...] (4 cycles) - read_mtree_node(span); + read_mtree_node(block_builder)?; #[rustfmt::skip] let ops = [ @@ -294,5 +287,7 @@ fn update_mtree(span: &mut SpanBuilder) { ]; // stack: [V_old, R_new, ...] (25 cycles) - span.push_ops(ops); + block_builder.push_ops(ops); + + Ok(()) } diff --git a/assembly/src/assembler/instruction/env_ops.rs b/assembly/src/assembler/instruction/env_ops.rs index 0818602496..2026af71f5 100644 --- a/assembly/src/assembler/instruction/env_ops.rs +++ b/assembly/src/assembler/instruction/env_ops.rs @@ -1,7 +1,8 @@ -use super::{mem_ops::local_to_absolute_addr, push_felt, AssemblyContext, SpanBuilder}; -use crate::{AssemblyError, Felt, Spanned}; use vm_core::Operation::*; +use super::{mem_ops::local_to_absolute_addr, push_felt, BasicBlockBuilder}; +use crate::{assembler::ProcedureContext, AssemblyError, Felt, SourceSpan}; + // CONSTANT INPUTS // ================================================================================================ @@ -10,11 +11,11 @@ use vm_core::Operation::*; /// In cases when the immediate value is 0, `PUSH` operation is replaced with `PAD`. Also, in cases /// when immediate value is 1, `PUSH` operation is replaced with `PAD INCR` because in most cases /// this will be more efficient than doing a `PUSH`. -pub fn push_one(imm: T, span: &mut SpanBuilder) +pub fn push_one(imm: T, block_builder: &mut BasicBlockBuilder) where T: Into, { - push_felt(span, imm.into()); + push_felt(block_builder, imm.into()); } /// Appends `PUSH` operations to the span block to push two or more provided constant values onto @@ -23,11 +24,11 @@ where /// In cases when the immediate value is 0, `PUSH` operation is replaced with `PAD`. Also, in cases /// when immediate value is 1, `PUSH` operation is replaced with `PAD INCR` because in most cases /// this will be more efficient than doing a `PUSH`. -pub fn push_many(imms: &[T], span: &mut SpanBuilder) +pub fn push_many(imms: &[T], block_builder: &mut BasicBlockBuilder) where T: Into + Copy, { - imms.iter().for_each(|imm| push_felt(span, (*imm).into())); + imms.iter().for_each(|imm| push_felt(block_builder, (*imm).into())); } // ENVIRONMENT INPUTS @@ -39,11 +40,11 @@ where /// # Errors /// Returns an error if index is greater than the number of procedure locals. pub fn locaddr( - span: &mut SpanBuilder, + block_builder: &mut BasicBlockBuilder, index: u16, - context: &AssemblyContext, + proc_ctx: &ProcedureContext, ) -> Result<(), AssemblyError> { - local_to_absolute_addr(span, index, context.unwrap_current_procedure().num_locals()) + local_to_absolute_addr(block_builder, index, proc_ctx.num_locals()) } /// Appends CALLER operation to the span which puts the hash of the function which initiated the @@ -51,14 +52,17 @@ pub fn locaddr( /// /// # Errors /// Returns an error if the instruction is being executed outside of kernel context. -pub fn caller(span: &mut SpanBuilder, context: &AssemblyContext) -> Result<(), AssemblyError> { - let current_procedure = context.unwrap_current_procedure(); - if !current_procedure.is_kernel() { +pub fn caller( + block_builder: &mut BasicBlockBuilder, + proc_ctx: &ProcedureContext, + source_span: SourceSpan, +) -> Result<(), AssemblyError> { + if !proc_ctx.is_kernel() { return Err(AssemblyError::CallerOutsideOfKernel { - span: current_procedure.span(), - source_file: current_procedure.source_file(), + span: source_span, + source_file: proc_ctx.source_manager().get(source_span.source_id()).ok(), }); } - span.push_op(Caller); + block_builder.push_op(Caller); Ok(()) } diff --git a/assembly/src/assembler/instruction/ext2_ops.rs b/assembly/src/assembler/instruction/ext2_ops.rs index 9bf8470731..6cc3ae88a4 100644 --- a/assembly/src/assembler/instruction/ext2_ops.rs +++ b/assembly/src/assembler/instruction/ext2_ops.rs @@ -1,12 +1,14 @@ -use super::SpanBuilder; use vm_core::{AdviceInjector::Ext2Inv, Operation::*}; +use super::BasicBlockBuilder; +use crate::AssemblyError; + /// Given a stack in the following initial configuration [b1, b0, a1, a0, ...] where a = (a0, a1) /// and b = (b0, b1) represent elements in the extension field of degree 2, this series of /// operations outputs the result c = (c1, c0) where c1 = a1 + b1 and c0 = a0 + b0. /// /// This operation takes 5 VM cycles. -pub fn ext2_add(span: &mut SpanBuilder) { +pub fn ext2_add(block_builder: &mut BasicBlockBuilder) { #[rustfmt::skip] let ops = [ Swap, // [b0, b1, a1, a0, ...] @@ -15,7 +17,7 @@ pub fn ext2_add(span: &mut SpanBuilder) { MovDn2, // [b1, a1, a0+b0, ...] Add // [b1+a1, a0+b0, ...] ]; - span.push_ops(ops); + block_builder.push_ops(ops); } /// Given a stack in the following initial configuration [b1, b0, a1, a0, ...] where a = (a0, a1) @@ -23,7 +25,7 @@ pub fn ext2_add(span: &mut SpanBuilder) { /// operations outputs the result c = (c1, c0) where c1 = a1 - b1 and c0 = a0 - b0. /// /// This operation takes 7 VM cycles. -pub fn ext2_sub(span: &mut SpanBuilder) { +pub fn ext2_sub(block_builder: &mut BasicBlockBuilder) { #[rustfmt::skip] let ops = [ Neg, // [-b1, b0, a1, a0, ...] @@ -34,7 +36,7 @@ pub fn ext2_sub(span: &mut SpanBuilder) { MovDn2, // [-b1, a1, a0-b0, ...] Add // [a1-b1, a0-b0, ...] ]; - span.push_ops(ops); + block_builder.push_ops(ops); } /// Given a stack with initial configuration given by [b1, b0, a1, a0, ...] where a = (a0, a1) and @@ -42,8 +44,8 @@ pub fn ext2_sub(span: &mut SpanBuilder) { /// outputs the product c = (c1, c0) where c0 = a0b0 - 2(a1b1) and c1 = (a0 + a1)(b0 + b1) - a0b0 /// /// This operation takes 3 VM cycles. -pub fn ext2_mul(span: &mut SpanBuilder) { - span.push_ops([Ext2Mul, Drop, Drop]); +pub fn ext2_mul(block_builder: &mut BasicBlockBuilder) { + block_builder.push_ops([Ext2Mul, Drop, Drop]); } /// Given a stack in the following initial configuration [b1, b0, a1, a0, ...] where a = (a0, a1) @@ -51,8 +53,8 @@ pub fn ext2_mul(span: &mut SpanBuilder) { /// operations outputs the result c = (c1, c0) where c = a * b^-1. /// /// This operation takes 11 VM cycles. -pub fn ext2_div(span: &mut SpanBuilder) { - span.push_advice_injector(Ext2Inv); +pub fn ext2_div(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyError> { + block_builder.push_advice_injector(Ext2Inv)?; #[rustfmt::skip] let ops = [ AdvPop, // [b0', b1, b0, a1, a0, ...] @@ -67,7 +69,9 @@ pub fn ext2_div(span: &mut SpanBuilder) { Drop, // [b0', a1*b1', a0*b0'...] Drop // [a1*b1', a0*b0'...] ]; - span.push_ops(ops); + block_builder.push_ops(ops); + + Ok(()) } /// Given a stack with initial configuration given by [a1, a0, ...] where a = (a0, a1) represents @@ -75,7 +79,7 @@ pub fn ext2_div(span: &mut SpanBuilder) { /// [-a1, -a0, ...] /// /// This operation takes 4 VM cycles. -pub fn ext2_neg(span: &mut SpanBuilder) { +pub fn ext2_neg(block_builder: &mut BasicBlockBuilder) { #[rustfmt::skip] let ops = [ Neg, // [a1, a0, ...] @@ -83,7 +87,7 @@ pub fn ext2_neg(span: &mut SpanBuilder) { Neg, // [-a0, -a1, ...] Swap // [-a1, -a0, ...] ]; - span.push_ops(ops); + block_builder.push_ops(ops); } /// Given an invertible quadratic extension field element on the stack, this routine computes @@ -111,8 +115,8 @@ pub fn ext2_neg(span: &mut SpanBuilder) { /// assert b = (1, 0) | (1, 0) is the multiplicative identity of extension field. /// /// This operation takes 8 VM cycles. -pub fn ext2_inv(span: &mut SpanBuilder) { - span.push_advice_injector(Ext2Inv); +pub fn ext2_inv(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyError> { + block_builder.push_advice_injector(Ext2Inv)?; #[rustfmt::skip] let ops = [ AdvPop, // [a0', a1, a0, ...] @@ -124,5 +128,7 @@ pub fn ext2_inv(span: &mut SpanBuilder) { MovUp2, // [1, a1', a0', ...] Assert(0), // [a1', a0', ...] ]; - span.push_ops(ops); + block_builder.push_ops(ops); + + Ok(()) } diff --git a/assembly/src/assembler/instruction/field_ops.rs b/assembly/src/assembler/instruction/field_ops.rs index 40c34d3bed..74063bb489 100644 --- a/assembly/src/assembler/instruction/field_ops.rs +++ b/assembly/src/assembler/instruction/field_ops.rs @@ -1,9 +1,11 @@ -use super::{validate_param, AssemblyContext, SpanBuilder}; +use vm_core::{AdviceInjector, FieldElement, Operation::*}; + +use super::{validate_param, BasicBlockBuilder}; use crate::{ + assembler::ProcedureContext, diagnostics::{RelatedError, Report}, AssemblyError, Felt, Span, MAX_EXP_BITS, ONE, ZERO, }; -use vm_core::{AdviceInjector, FieldElement, Operation::*}; /// Field element representing TWO in the base field of the VM. const TWO: Felt = Felt::new(2); @@ -14,7 +16,7 @@ const TWO: Felt = Felt::new(2); /// Asserts that the top two words in the stack are equal. /// /// VM cycles: 11 cycles -pub fn assertw(span_builder: &mut SpanBuilder, err_code: u32) { +pub fn assertw(span_builder: &mut BasicBlockBuilder, err_code: u32) { span_builder.push_ops([ MovUp4, Eq, @@ -39,7 +41,7 @@ pub fn assertw(span_builder: &mut SpanBuilder, err_code: u32) { /// - else if imm = 1: INCR /// - else if imm = 2: INCR INCR /// - otherwise: PUSH(imm) ADD -pub fn add_imm(span_builder: &mut SpanBuilder, imm: Felt) { +pub fn add_imm(span_builder: &mut BasicBlockBuilder, imm: Felt) { if imm == ZERO { span_builder.push_op(Noop); } else if imm == ONE { @@ -55,7 +57,7 @@ pub fn add_imm(span_builder: &mut SpanBuilder, imm: Felt) { /// stack. Specifically, the sequences are: /// - if imm = 0: NOOP /// - otherwise: PUSH(-imm) ADD -pub fn sub_imm(span_builder: &mut SpanBuilder, imm: Felt) { +pub fn sub_imm(span_builder: &mut BasicBlockBuilder, imm: Felt) { if imm == ZERO { span_builder.push_op(Noop); } else { @@ -68,7 +70,7 @@ pub fn sub_imm(span_builder: &mut SpanBuilder, imm: Felt) { /// - if imm = 0: DROP PAD /// - else if imm = 1: NOOP /// - otherwise: PUSH(imm) MUL -pub fn mul_imm(span_builder: &mut SpanBuilder, imm: Felt) { +pub fn mul_imm(span_builder: &mut BasicBlockBuilder, imm: Felt) { if imm == ZERO { span_builder.push_ops([Drop, Pad]); } else if imm == ONE { @@ -87,13 +89,14 @@ pub fn mul_imm(span_builder: &mut SpanBuilder, imm: Felt) { /// # Errors /// Returns an error if the immediate value is ZERO. pub fn div_imm( - span_builder: &mut SpanBuilder, - ctx: &mut AssemblyContext, + span_builder: &mut BasicBlockBuilder, + proc_ctx: &mut ProcedureContext, imm: Span, ) -> Result<(), AssemblyError> { if imm == ZERO { - let source_file = ctx.unwrap_current_procedure().source_file(); - let error = Report::new(crate::parser::ParsingError::DivisionByZero { span: imm.span() }); + let source_span = imm.span(); + let source_file = proc_ctx.source_manager().get(source_span.source_id()).ok(); + let error = Report::new(crate::parser::ParsingError::DivisionByZero { span: source_span }); return Err(if let Some(source_file) = source_file { AssemblyError::Other(RelatedError::new(error.with_source_code(source_file))) } else { @@ -114,14 +117,14 @@ pub fn div_imm( /// top of the stack. /// /// VM cycles: 16 cycles -pub fn pow2(span_builder: &mut SpanBuilder) { +pub fn pow2(span_builder: &mut BasicBlockBuilder) { append_pow2_op(span_builder); } /// Appends relevant operations to the span_builder block for the computation of power of 2. /// /// VM cycles: 16 cycles -pub fn append_pow2_op(span_builder: &mut SpanBuilder) { +pub fn append_pow2_op(span_builder: &mut BasicBlockBuilder) { // push base 2 onto the stack: [exp, ...] -> [2, exp, ...] span_builder.push_op(Push(2_u8.into())); // introduce initial value of acc onto the stack: [2, exp, ...] -> [1, 2, exp, ...] @@ -149,7 +152,7 @@ pub fn append_pow2_op(span_builder: &mut SpanBuilder) { /// /// # Errors /// Returns an error if num_pow_bits is greater than 64. -pub fn exp(span_builder: &mut SpanBuilder, num_pow_bits: u8) -> Result<(), AssemblyError> { +pub fn exp(span_builder: &mut BasicBlockBuilder, num_pow_bits: u8) -> Result<(), AssemblyError> { validate_param(num_pow_bits, 0..=MAX_EXP_BITS)?; // arranging the stack to prepare it for expacc instruction. @@ -178,7 +181,7 @@ pub fn exp(span_builder: &mut SpanBuilder, num_pow_bits: u8) -> Result<(), Assem /// - pow = 6: 10 cycles /// - pow = 7: 12 cycles /// - pow > 7: 9 + Ceil(log2(pow)) -pub fn exp_imm(span_builder: &mut SpanBuilder, pow: Felt) -> Result<(), AssemblyError> { +pub fn exp_imm(span_builder: &mut BasicBlockBuilder, pow: Felt) -> Result<(), AssemblyError> { if pow.as_int() <= 7 { perform_exp_for_small_power(span_builder, pow.as_int()); Ok(()) @@ -210,38 +213,38 @@ pub fn exp_imm(span_builder: &mut SpanBuilder, pow: Felt) -> Result<(), Assembly /// - pow = 5: 8 cycles /// - pow = 6: 10 cycles /// - pow = 7: 12 cycles -fn perform_exp_for_small_power(span_builder: &mut SpanBuilder, pow: u64) { +fn perform_exp_for_small_power(span_builder: &mut BasicBlockBuilder, pow: u64) { match pow { 0 => { span_builder.push_op(Drop); span_builder.push_op(Pad); span_builder.push_op(Incr); - } + }, 1 => span_builder.push_op(Noop), // TODO: show warning? 2 => { span_builder.push_op(Dup0); span_builder.push_op(Mul); - } + }, 3 => { span_builder.push_op_many(Dup0, 2); span_builder.push_op_many(Mul, 2); - } + }, 4 => { span_builder.push_op_many(Dup0, 3); span_builder.push_op_many(Mul, 3); - } + }, 5 => { span_builder.push_op_many(Dup0, 4); span_builder.push_op_many(Mul, 4); - } + }, 6 => { span_builder.push_op_many(Dup0, 5); span_builder.push_op_many(Mul, 5); - } + }, 7 => { span_builder.push_op_many(Dup0, 6); span_builder.push_op_many(Mul, 6); - } + }, _ => unreachable!("pow must be less than 8"), } } @@ -256,13 +259,13 @@ fn perform_exp_for_small_power(span_builder: &mut SpanBuilder, pow: u64) { /// /// # Errors /// Returns an error if the logarithm argument (top stack element) equals ZERO. -pub fn ilog2(span: &mut SpanBuilder) { - span.push_advice_injector(AdviceInjector::ILog2); - span.push_op(AdvPop); // [ilog2, n, ...] +pub fn ilog2(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyError> { + block_builder.push_advice_injector(AdviceInjector::ILog2)?; + block_builder.push_op(AdvPop); // [ilog2, n, ...] // compute the power-of-two for the value given in the advice tape (17 cycles) - span.push_op(Dup0); - append_pow2_op(span); + block_builder.push_op(Dup0); + append_pow2_op(block_builder); // => [pow2, ilog2, n, ...] #[rustfmt::skip] @@ -286,7 +289,9 @@ pub fn ilog2(span: &mut SpanBuilder) { // => [ilog2, ...] ]; - span.push_ops(ops); + block_builder.push_ops(ops); + + Ok(()) } // COMPARISON OPERATIONS @@ -296,7 +301,7 @@ pub fn ilog2(span: &mut SpanBuilder) { /// and the provided immediate value. Specifically, the sequences are: /// - if imm = 0: EQZ /// - otherwise: PUSH(imm) EQ -pub fn eq_imm(span_builder: &mut SpanBuilder, imm: Felt) { +pub fn eq_imm(span_builder: &mut BasicBlockBuilder, imm: Felt) { if imm == ZERO { span_builder.push_op(Eqz); } else { @@ -308,7 +313,7 @@ pub fn eq_imm(span_builder: &mut SpanBuilder, imm: Felt) { /// and the provided immediate value. Specifically, the sequences are: /// - if imm = 0: EQZ NOT /// - otherwise: PUSH(imm) EQ NOT -pub fn neq_imm(span_builder: &mut SpanBuilder, imm: Felt) { +pub fn neq_imm(span_builder: &mut BasicBlockBuilder, imm: Felt) { if imm == ZERO { span_builder.push_ops([Eqz, Not]); } else { @@ -319,7 +324,7 @@ pub fn neq_imm(span_builder: &mut SpanBuilder, imm: Felt) { /// Appends a sequence of operations to check equality between two words at the top of the stack. /// /// This operation takes 15 VM cycles. -pub fn eqw(span_builder: &mut SpanBuilder) { +pub fn eqw(span_builder: &mut BasicBlockBuilder) { span_builder.push_ops([ // duplicate first pair of for comparison(4th elements of each word) in reverse order // to avoid using dup.8 after stack shifting(dup.X where X > 7, takes more VM cycles ) @@ -334,7 +339,7 @@ pub fn eqw(span_builder: &mut SpanBuilder) { /// of 1 is pushed onto the stack if a < b. Otherwise, 0 is pushed. /// /// This operation takes 14 VM cycles. -pub fn lt(span_builder: &mut SpanBuilder) { +pub fn lt(span_builder: &mut BasicBlockBuilder) { // Split both elements into high and low bits // 3 cycles split_elements(span_builder); @@ -358,7 +363,7 @@ pub fn lt(span_builder: &mut SpanBuilder) { /// A value of 1 is pushed onto the stack if a <= b. Otherwise, 0 is pushed. /// /// This operation takes 15 VM cycles. -pub fn lte(span_builder: &mut SpanBuilder) { +pub fn lte(span_builder: &mut BasicBlockBuilder) { // Split both elements into high and low bits // 3 cycles split_elements(span_builder); @@ -382,7 +387,7 @@ pub fn lte(span_builder: &mut SpanBuilder) { /// of 1 is pushed onto the stack if a > b. Otherwise, 0 is pushed. /// /// This operation takes 15 VM cycles. -pub fn gt(span_builder: &mut SpanBuilder) { +pub fn gt(span_builder: &mut BasicBlockBuilder) { // Split both elements into high and low bits // 3 cycles split_elements(span_builder); @@ -406,7 +411,7 @@ pub fn gt(span_builder: &mut SpanBuilder) { /// A value of 1 is pushed onto the stack if a >= b. Otherwise, 0 is pushed. /// /// This operation takes 16 VM cycles. -pub fn gte(span_builder: &mut SpanBuilder) { +pub fn gte(span_builder: &mut BasicBlockBuilder) { // Split both elements into high and low bits // 3 cycles split_elements(span_builder); @@ -428,7 +433,7 @@ pub fn gte(span_builder: &mut SpanBuilder) { /// Checks if the top element in the stack is an odd number or not. /// /// Vm cycles: 5 -pub fn is_odd(span_builder: &mut SpanBuilder) { +pub fn is_odd(span_builder: &mut BasicBlockBuilder) { span_builder.push_ops([U32split, Drop, Pad, Incr, U32and]); } @@ -441,7 +446,7 @@ pub fn is_odd(span_builder: &mut SpanBuilder) { /// After these operations, the stack state will be: [a_hi, a_lo, b_hi, b_lo, ...]. /// /// This operation takes 3 cycles. -fn split_elements(span_builder: &mut SpanBuilder) { +fn split_elements(span_builder: &mut BasicBlockBuilder) { // stack: [b, a, ...] => [b_hi, b_lo, a, ...] span_builder.push_op(U32split); // => [a, b_hi, b_lo, ...] @@ -460,7 +465,7 @@ fn split_elements(span_builder: &mut SpanBuilder) { /// The resulting stack after this operation is: [eq_flag, lt_flag, ...]. /// /// This operation takes 3 cycles. -fn check_lt_and_eq(span_builder: &mut SpanBuilder) { +fn check_lt_and_eq(span_builder: &mut BasicBlockBuilder) { // calculate a - b // stack: [b, a, ...] => [underflow_flag, result, ...] span_builder.push_op(U32sub); @@ -494,7 +499,7 @@ fn check_lt_and_eq(span_builder: &mut SpanBuilder) { /// - hi_flag_lt: 1 if a's high-bit values were less than b's (a_hi < b_hi); 0 otherwise /// /// This operation takes 6 cycles. -fn check_lt_high_bits(span_builder: &mut SpanBuilder) { +fn check_lt_high_bits(span_builder: &mut BasicBlockBuilder) { // reorder the stack to check a_hi < b_hi span_builder.push_op(MovUp2); @@ -516,7 +521,7 @@ fn check_lt_high_bits(span_builder: &mut SpanBuilder) { /// condition will be true if the underflow flag is set. /// /// This operation takes 3 cycles. -fn check_lt(span_builder: &mut SpanBuilder) { +fn check_lt(span_builder: &mut BasicBlockBuilder) { // calculate a - b // stack: [b, a, ...] => [underflow_flag, result, ...] span_builder.push_op(U32sub); @@ -538,7 +543,7 @@ fn check_lt(span_builder: &mut SpanBuilder) { /// - high-bit comparison flag: 1 if the lt/gt condition being checked was true; 0 otherwise /// /// This function takes 2 cycles. -fn set_result(span_builder: &mut SpanBuilder) { +fn set_result(span_builder: &mut BasicBlockBuilder) { // check if high bits are equal AND low bit comparison condition was true span_builder.push_op(And); @@ -556,7 +561,7 @@ fn set_result(span_builder: &mut SpanBuilder) { /// there was no underflow and the result is 0. /// /// This function takes 4 cycles. -fn check_lte(span_builder: &mut SpanBuilder) { +fn check_lte(span_builder: &mut BasicBlockBuilder) { // calculate a - b // stack: [b, a, ...] => [underflow_flag, result, ...] span_builder.push_op(U32sub); @@ -585,7 +590,7 @@ fn check_lte(span_builder: &mut SpanBuilder) { /// - hi_flag_gt: 1 if a's high-bit values were greater than b's (a_hi > b_hi); 0 otherwise /// /// This function takes 7 cycles. -fn check_gt_high_bits(span_builder: &mut SpanBuilder) { +fn check_gt_high_bits(span_builder: &mut BasicBlockBuilder) { // reorder the stack to check b_hi < a_hi span_builder.push_ops([Swap, MovDn2]); diff --git a/assembly/src/assembler/instruction/mem_ops.rs b/assembly/src/assembler/instruction/mem_ops.rs index e6775ffa0a..cdfbdc79c5 100644 --- a/assembly/src/assembler/instruction/mem_ops.rs +++ b/assembly/src/assembler/instruction/mem_ops.rs @@ -1,7 +1,10 @@ -use super::{push_felt, push_u32_value, validate_param, AssemblyContext, SpanBuilder}; -use crate::AssemblyError; +use alloc::string::ToString; + use vm_core::{Felt, Operation::*}; +use super::{push_felt, push_u32_value, validate_param, BasicBlockBuilder}; +use crate::{assembler::ProcedureContext, diagnostics::Report, AssemblyError}; + // INSTRUCTION PARSERS // ================================================================================================ @@ -20,8 +23,8 @@ use vm_core::{Felt, Operation::*}; /// Returns an error if we are reading from local memory and local memory index is greater than /// the number of procedure locals. pub fn mem_read( - span: &mut SpanBuilder, - context: &AssemblyContext, + block_builder: &mut BasicBlockBuilder, + proc_ctx: &ProcedureContext, addr: Option, is_local: bool, is_single: bool, @@ -29,10 +32,10 @@ pub fn mem_read( // if the address was provided as an immediate value, put it onto the stack if let Some(addr) = addr { if is_local { - let num_locals = context.unwrap_current_procedure().num_locals(); - local_to_absolute_addr(span, addr as u16, num_locals)?; + let num_locals = proc_ctx.num_locals(); + local_to_absolute_addr(block_builder, addr as u16, num_locals)?; } else { - push_u32_value(span, addr); + push_u32_value(block_builder, addr); } } else { assert!(!is_local, "local always contains addr value"); @@ -40,9 +43,9 @@ pub fn mem_read( // load from the memory address on top of the stack if is_single { - span.push_op(MLoad); + block_builder.push_op(MLoad); } else { - span.push_op(MLoadW); + block_builder.push_op(MLoadW); } Ok(()) @@ -71,24 +74,23 @@ pub fn mem_read( /// Returns an error if we are writing to local memory and local memory index is greater than /// the number of procedure locals. pub fn mem_write_imm( - span: &mut SpanBuilder, - context: &AssemblyContext, + block_builder: &mut BasicBlockBuilder, + proc_ctx: &ProcedureContext, addr: u32, is_local: bool, is_single: bool, ) -> Result<(), AssemblyError> { if is_local { - let num_locals = context.unwrap_current_procedure().num_locals(); - local_to_absolute_addr(span, addr as u16, num_locals)?; + local_to_absolute_addr(block_builder, addr as u16, proc_ctx.num_locals())?; } else { - push_u32_value(span, addr); + push_u32_value(block_builder, addr); } if is_single { - span.push_op(MStore); - span.push_op(Drop); + block_builder.push_op(MStore); + block_builder.push_op(Drop); } else { - span.push_op(MStoreW); + block_builder.push_op(MStoreW); } Ok(()) @@ -108,15 +110,25 @@ pub fn mem_write_imm( /// # Errors /// Returns an error if index is greater than the number of procedure locals. pub fn local_to_absolute_addr( - span: &mut SpanBuilder, + block_builder: &mut BasicBlockBuilder, index: u16, num_proc_locals: u16, ) -> Result<(), AssemblyError> { + if num_proc_locals == 0 { + return Err(AssemblyError::Other( + Report::msg( + "number of procedure locals was not set (or set to 0), but local values were used" + .to_string(), + ) + .into(), + )); + } + let max = num_proc_locals - 1; validate_param(index, 0..=max)?; - push_felt(span, -Felt::from(max - index)); - span.push_op(FmpAdd); + push_felt(block_builder, -Felt::from(max - index)); + block_builder.push_op(FmpAdd); Ok(()) } diff --git a/assembly/src/assembler/instruction/mod.rs b/assembly/src/assembler/instruction/mod.rs index 949e3dd61c..49e8bec164 100644 --- a/assembly/src/assembler/instruction/mod.rs +++ b/assembly/src/assembler/instruction/mod.rs @@ -1,10 +1,10 @@ -use super::{ - ast::InvokeKind, Assembler, AssemblyContext, CodeBlock, Felt, Instruction, Operation, - SpanBuilder, ONE, ZERO, -}; -use crate::{diagnostics::Report, utils::bound_into_included_u64, AssemblyError}; use core::ops::RangeBounds; -use vm_core::Decorator; + +use miette::miette; +use vm_core::{mast::MastNodeId, Decorator, ONE, ZERO}; + +use super::{ast::InvokeKind, Assembler, BasicBlockBuilder, Felt, Operation, ProcedureContext}; +use crate::{ast::Instruction, utils::bound_into_included_u64, AssemblyError, Span}; mod adv_ops; mod crypto_ops; @@ -21,22 +21,22 @@ use self::u32_ops::U32OpMode::*; impl Assembler { pub(super) fn compile_instruction( &self, - instruction: &Instruction, - span_builder: &mut SpanBuilder, - ctx: &mut AssemblyContext, - ) -> Result, AssemblyError> { + instruction: &Span, + block_builder: &mut BasicBlockBuilder, + proc_ctx: &mut ProcedureContext, + ) -> Result, AssemblyError> { // if the assembler is in debug mode, start tracking the instruction about to be executed; // this will allow us to map the instruction to the sequence of operations which were // executed as a part of this instruction. if self.in_debug_mode() { - span_builder.track_instruction(instruction, ctx); + block_builder.track_instruction(instruction, proc_ctx)?; } - let result = self.compile_instruction_impl(instruction, span_builder, ctx)?; + let result = self.compile_instruction_impl(instruction, block_builder, proc_ctx)?; // compute and update the cycle count of the instruction which just finished executing if self.in_debug_mode() { - span_builder.set_instruction_cycle_count(); + block_builder.set_instruction_cycle_count(); } Ok(result) @@ -44,352 +44,401 @@ impl Assembler { fn compile_instruction_impl( &self, - instruction: &Instruction, - span_builder: &mut SpanBuilder, - ctx: &mut AssemblyContext, - ) -> Result, AssemblyError> { + instruction: &Span, + block_builder: &mut BasicBlockBuilder, + proc_ctx: &mut ProcedureContext, + ) -> Result, AssemblyError> { use Operation::*; - match instruction { - Instruction::Assert => span_builder.push_op(Assert(0)), + match &**instruction { + Instruction::Nop => block_builder.push_op(Noop), + Instruction::Assert => block_builder.push_op(Assert(0)), Instruction::AssertWithError(err_code) => { - span_builder.push_op(Assert(err_code.expect_value())) - } - Instruction::AssertEq => span_builder.push_ops([Eq, Assert(0)]), + block_builder.push_op(Assert(err_code.expect_value())) + }, + Instruction::AssertEq => block_builder.push_ops([Eq, Assert(0)]), Instruction::AssertEqWithError(err_code) => { - span_builder.push_ops([Eq, Assert(err_code.expect_value())]) - } - Instruction::AssertEqw => field_ops::assertw(span_builder, 0), + block_builder.push_ops([Eq, Assert(err_code.expect_value())]) + }, + Instruction::AssertEqw => field_ops::assertw(block_builder, 0), Instruction::AssertEqwWithError(err_code) => { - field_ops::assertw(span_builder, err_code.expect_value()) - } - Instruction::Assertz => span_builder.push_ops([Eqz, Assert(0)]), + field_ops::assertw(block_builder, err_code.expect_value()) + }, + Instruction::Assertz => block_builder.push_ops([Eqz, Assert(0)]), Instruction::AssertzWithError(err_code) => { - span_builder.push_ops([Eqz, Assert(err_code.expect_value())]) - } - - Instruction::Add => span_builder.push_op(Add), - Instruction::AddImm(imm) => field_ops::add_imm(span_builder, imm.expect_value()), - Instruction::Sub => span_builder.push_ops([Neg, Add]), - Instruction::SubImm(imm) => field_ops::sub_imm(span_builder, imm.expect_value()), - Instruction::Mul => span_builder.push_op(Mul), - Instruction::MulImm(imm) => field_ops::mul_imm(span_builder, imm.expect_value()), - Instruction::Div => span_builder.push_ops([Inv, Mul]), + block_builder.push_ops([Eqz, Assert(err_code.expect_value())]) + }, + + Instruction::Add => block_builder.push_op(Add), + Instruction::AddImm(imm) => field_ops::add_imm(block_builder, imm.expect_value()), + Instruction::Sub => block_builder.push_ops([Neg, Add]), + Instruction::SubImm(imm) => field_ops::sub_imm(block_builder, imm.expect_value()), + Instruction::Mul => block_builder.push_op(Mul), + Instruction::MulImm(imm) => field_ops::mul_imm(block_builder, imm.expect_value()), + Instruction::Div => block_builder.push_ops([Inv, Mul]), Instruction::DivImm(imm) => { - field_ops::div_imm(span_builder, ctx, imm.expect_spanned_value())?; - } - Instruction::Neg => span_builder.push_op(Neg), - Instruction::Inv => span_builder.push_op(Inv), - Instruction::Incr => span_builder.push_op(Incr), - - Instruction::Pow2 => field_ops::pow2(span_builder), - Instruction::Exp => field_ops::exp(span_builder, 64)?, - Instruction::ExpImm(pow) => field_ops::exp_imm(span_builder, pow.expect_value())?, - Instruction::ExpBitLength(num_pow_bits) => field_ops::exp(span_builder, *num_pow_bits)?, - Instruction::ILog2 => field_ops::ilog2(span_builder), - - Instruction::Not => span_builder.push_op(Not), - Instruction::And => span_builder.push_op(And), - Instruction::Or => span_builder.push_op(Or), - Instruction::Xor => span_builder.push_ops([Dup0, Dup2, Or, MovDn2, And, Not, And]), - - Instruction::Eq => span_builder.push_op(Eq), - Instruction::EqImm(imm) => field_ops::eq_imm(span_builder, imm.expect_value()), - Instruction::Eqw => field_ops::eqw(span_builder), - Instruction::Neq => span_builder.push_ops([Eq, Not]), - Instruction::NeqImm(imm) => field_ops::neq_imm(span_builder, imm.expect_value()), - Instruction::Lt => field_ops::lt(span_builder), - Instruction::Lte => field_ops::lte(span_builder), - Instruction::Gt => field_ops::gt(span_builder), - Instruction::Gte => field_ops::gte(span_builder), - Instruction::IsOdd => field_ops::is_odd(span_builder), + field_ops::div_imm(block_builder, proc_ctx, imm.expect_spanned_value())?; + }, + Instruction::Neg => block_builder.push_op(Neg), + Instruction::Inv => block_builder.push_op(Inv), + Instruction::Incr => block_builder.push_op(Incr), + + Instruction::Pow2 => field_ops::pow2(block_builder), + Instruction::Exp => field_ops::exp(block_builder, 64)?, + Instruction::ExpImm(pow) => field_ops::exp_imm(block_builder, pow.expect_value())?, + Instruction::ExpBitLength(num_pow_bits) => { + field_ops::exp(block_builder, *num_pow_bits)? + }, + Instruction::ILog2 => field_ops::ilog2(block_builder)?, + + Instruction::Not => block_builder.push_op(Not), + Instruction::And => block_builder.push_op(And), + Instruction::Or => block_builder.push_op(Or), + Instruction::Xor => block_builder.push_ops([Dup0, Dup2, Or, MovDn2, And, Not, And]), + + Instruction::Eq => block_builder.push_op(Eq), + Instruction::EqImm(imm) => field_ops::eq_imm(block_builder, imm.expect_value()), + Instruction::Eqw => field_ops::eqw(block_builder), + Instruction::Neq => block_builder.push_ops([Eq, Not]), + Instruction::NeqImm(imm) => field_ops::neq_imm(block_builder, imm.expect_value()), + Instruction::Lt => field_ops::lt(block_builder), + Instruction::Lte => field_ops::lte(block_builder), + Instruction::Gt => field_ops::gt(block_builder), + Instruction::Gte => field_ops::gte(block_builder), + Instruction::IsOdd => field_ops::is_odd(block_builder), // ----- ext2 instructions ------------------------------------------------------------ - Instruction::Ext2Add => ext2_ops::ext2_add(span_builder), - Instruction::Ext2Sub => ext2_ops::ext2_sub(span_builder), - Instruction::Ext2Mul => ext2_ops::ext2_mul(span_builder), - Instruction::Ext2Div => ext2_ops::ext2_div(span_builder), - Instruction::Ext2Neg => ext2_ops::ext2_neg(span_builder), - Instruction::Ext2Inv => ext2_ops::ext2_inv(span_builder), + Instruction::Ext2Add => ext2_ops::ext2_add(block_builder), + Instruction::Ext2Sub => ext2_ops::ext2_sub(block_builder), + Instruction::Ext2Mul => ext2_ops::ext2_mul(block_builder), + Instruction::Ext2Div => ext2_ops::ext2_div(block_builder)?, + Instruction::Ext2Neg => ext2_ops::ext2_neg(block_builder), + Instruction::Ext2Inv => ext2_ops::ext2_inv(block_builder)?, // ----- u32 manipulation ------------------------------------------------------------- - Instruction::U32Test => span_builder.push_ops([Dup0, U32split, Swap, Drop, Eqz]), - Instruction::U32TestW => u32_ops::u32testw(span_builder), - Instruction::U32Assert => span_builder.push_ops([Pad, U32assert2(ZERO), Drop]), + Instruction::U32Test => block_builder.push_ops([Dup0, U32split, Swap, Drop, Eqz]), + Instruction::U32TestW => u32_ops::u32testw(block_builder), + Instruction::U32Assert => block_builder.push_ops([Pad, U32assert2(0), Drop]), Instruction::U32AssertWithError(err_code) => { - span_builder.push_ops([Pad, U32assert2(Felt::from(err_code.expect_value())), Drop]) - } - Instruction::U32Assert2 => span_builder.push_op(U32assert2(ZERO)), + block_builder.push_ops([Pad, U32assert2(err_code.expect_value()), Drop]) + }, + Instruction::U32Assert2 => block_builder.push_op(U32assert2(0)), Instruction::U32Assert2WithError(err_code) => { - span_builder.push_op(U32assert2(Felt::from(err_code.expect_value()))) - } - Instruction::U32AssertW => u32_ops::u32assertw(span_builder, ZERO), + block_builder.push_op(U32assert2(err_code.expect_value())) + }, + Instruction::U32AssertW => u32_ops::u32assertw(block_builder, 0), Instruction::U32AssertWWithError(err_code) => { - u32_ops::u32assertw(span_builder, Felt::from(err_code.expect_value())) - } + u32_ops::u32assertw(block_builder, err_code.expect_value()) + }, - Instruction::U32Cast => span_builder.push_ops([U32split, Drop]), - Instruction::U32Split => span_builder.push_op(U32split), + Instruction::U32Cast => block_builder.push_ops([U32split, Drop]), + Instruction::U32Split => block_builder.push_op(U32split), - Instruction::U32OverflowingAdd => u32_ops::u32add(span_builder, Overflowing, None), + Instruction::U32OverflowingAdd => u32_ops::u32add(block_builder, Overflowing, None), Instruction::U32OverflowingAddImm(v) => { - u32_ops::u32add(span_builder, Overflowing, Some(v.expect_value())) - } - Instruction::U32WrappingAdd => u32_ops::u32add(span_builder, Wrapping, None), + u32_ops::u32add(block_builder, Overflowing, Some(v.expect_value())) + }, + Instruction::U32WrappingAdd => u32_ops::u32add(block_builder, Wrapping, None), Instruction::U32WrappingAddImm(v) => { - u32_ops::u32add(span_builder, Wrapping, Some(v.expect_value())) - } - Instruction::U32OverflowingAdd3 => span_builder.push_op(U32add3), - Instruction::U32WrappingAdd3 => span_builder.push_ops([U32add3, Drop]), + u32_ops::u32add(block_builder, Wrapping, Some(v.expect_value())) + }, + Instruction::U32OverflowingAdd3 => block_builder.push_op(U32add3), + Instruction::U32WrappingAdd3 => block_builder.push_ops([U32add3, Drop]), - Instruction::U32OverflowingSub => u32_ops::u32sub(span_builder, Overflowing, None), + Instruction::U32OverflowingSub => u32_ops::u32sub(block_builder, Overflowing, None), Instruction::U32OverflowingSubImm(v) => { - u32_ops::u32sub(span_builder, Overflowing, Some(v.expect_value())) - } - Instruction::U32WrappingSub => u32_ops::u32sub(span_builder, Wrapping, None), + u32_ops::u32sub(block_builder, Overflowing, Some(v.expect_value())) + }, + Instruction::U32WrappingSub => u32_ops::u32sub(block_builder, Wrapping, None), Instruction::U32WrappingSubImm(v) => { - u32_ops::u32sub(span_builder, Wrapping, Some(v.expect_value())) - } + u32_ops::u32sub(block_builder, Wrapping, Some(v.expect_value())) + }, - Instruction::U32OverflowingMul => u32_ops::u32mul(span_builder, Overflowing, None), + Instruction::U32OverflowingMul => u32_ops::u32mul(block_builder, Overflowing, None), Instruction::U32OverflowingMulImm(v) => { - u32_ops::u32mul(span_builder, Overflowing, Some(v.expect_value())) - } - Instruction::U32WrappingMul => u32_ops::u32mul(span_builder, Wrapping, None), + u32_ops::u32mul(block_builder, Overflowing, Some(v.expect_value())) + }, + Instruction::U32WrappingMul => u32_ops::u32mul(block_builder, Wrapping, None), Instruction::U32WrappingMulImm(v) => { - u32_ops::u32mul(span_builder, Wrapping, Some(v.expect_value())) - } - Instruction::U32OverflowingMadd => span_builder.push_op(U32madd), - Instruction::U32WrappingMadd => span_builder.push_ops([U32madd, Drop]), + u32_ops::u32mul(block_builder, Wrapping, Some(v.expect_value())) + }, + Instruction::U32OverflowingMadd => block_builder.push_op(U32madd), + Instruction::U32WrappingMadd => block_builder.push_ops([U32madd, Drop]), - Instruction::U32Div => u32_ops::u32div(span_builder, ctx, None)?, + Instruction::U32Div => u32_ops::u32div(block_builder, proc_ctx, None)?, Instruction::U32DivImm(v) => { - u32_ops::u32div(span_builder, ctx, Some(v.expect_spanned_value()))? - } - Instruction::U32Mod => u32_ops::u32mod(span_builder, ctx, None)?, + u32_ops::u32div(block_builder, proc_ctx, Some(v.expect_spanned_value()))? + }, + Instruction::U32Mod => u32_ops::u32mod(block_builder, proc_ctx, None)?, Instruction::U32ModImm(v) => { - u32_ops::u32mod(span_builder, ctx, Some(v.expect_spanned_value()))? - } - Instruction::U32DivMod => u32_ops::u32divmod(span_builder, ctx, None)?, + u32_ops::u32mod(block_builder, proc_ctx, Some(v.expect_spanned_value()))? + }, + Instruction::U32DivMod => u32_ops::u32divmod(block_builder, proc_ctx, None)?, Instruction::U32DivModImm(v) => { - u32_ops::u32divmod(span_builder, ctx, Some(v.expect_spanned_value()))? - } - Instruction::U32And => span_builder.push_op(U32and), - Instruction::U32Or => span_builder.push_ops([Dup1, Dup1, U32and, Neg, Add, Add]), - Instruction::U32Xor => span_builder.push_op(U32xor), - Instruction::U32Not => u32_ops::u32not(span_builder), - Instruction::U32Shl => u32_ops::u32shl(span_builder, None)?, - Instruction::U32ShlImm(v) => u32_ops::u32shl(span_builder, Some(v.expect_value()))?, - Instruction::U32Shr => u32_ops::u32shr(span_builder, None)?, - Instruction::U32ShrImm(v) => u32_ops::u32shr(span_builder, Some(v.expect_value()))?, - Instruction::U32Rotl => u32_ops::u32rotl(span_builder, None)?, - Instruction::U32RotlImm(v) => u32_ops::u32rotl(span_builder, Some(v.expect_value()))?, - Instruction::U32Rotr => u32_ops::u32rotr(span_builder, None)?, - Instruction::U32RotrImm(v) => u32_ops::u32rotr(span_builder, Some(v.expect_value()))?, - Instruction::U32Popcnt => u32_ops::u32popcnt(span_builder), - Instruction::U32Clz => u32_ops::u32clz(span_builder), - Instruction::U32Ctz => u32_ops::u32ctz(span_builder), - Instruction::U32Clo => u32_ops::u32clo(span_builder), - Instruction::U32Cto => u32_ops::u32cto(span_builder), - Instruction::U32Lt => u32_ops::u32lt(span_builder), - Instruction::U32Lte => u32_ops::u32lte(span_builder), - Instruction::U32Gt => u32_ops::u32gt(span_builder), - Instruction::U32Gte => u32_ops::u32gte(span_builder), - Instruction::U32Min => u32_ops::u32min(span_builder), - Instruction::U32Max => u32_ops::u32max(span_builder), + u32_ops::u32divmod(block_builder, proc_ctx, Some(v.expect_spanned_value()))? + }, + Instruction::U32And => block_builder.push_op(U32and), + Instruction::U32Or => block_builder.push_ops([Dup1, Dup1, U32and, Neg, Add, Add]), + Instruction::U32Xor => block_builder.push_op(U32xor), + Instruction::U32Not => u32_ops::u32not(block_builder), + Instruction::U32Shl => u32_ops::u32shl(block_builder, None)?, + Instruction::U32ShlImm(v) => u32_ops::u32shl(block_builder, Some(v.expect_value()))?, + Instruction::U32Shr => u32_ops::u32shr(block_builder, None)?, + Instruction::U32ShrImm(v) => u32_ops::u32shr(block_builder, Some(v.expect_value()))?, + Instruction::U32Rotl => u32_ops::u32rotl(block_builder, None)?, + Instruction::U32RotlImm(v) => u32_ops::u32rotl(block_builder, Some(v.expect_value()))?, + Instruction::U32Rotr => u32_ops::u32rotr(block_builder, None)?, + Instruction::U32RotrImm(v) => u32_ops::u32rotr(block_builder, Some(v.expect_value()))?, + Instruction::U32Popcnt => u32_ops::u32popcnt(block_builder), + Instruction::U32Clz => u32_ops::u32clz(block_builder)?, + Instruction::U32Ctz => u32_ops::u32ctz(block_builder)?, + Instruction::U32Clo => u32_ops::u32clo(block_builder)?, + Instruction::U32Cto => u32_ops::u32cto(block_builder)?, + Instruction::U32Lt => u32_ops::u32lt(block_builder), + Instruction::U32Lte => u32_ops::u32lte(block_builder), + Instruction::U32Gt => u32_ops::u32gt(block_builder), + Instruction::U32Gte => u32_ops::u32gte(block_builder), + Instruction::U32Min => u32_ops::u32min(block_builder), + Instruction::U32Max => u32_ops::u32max(block_builder), // ----- stack manipulation ----------------------------------------------------------- - Instruction::Drop => span_builder.push_op(Drop), - Instruction::DropW => span_builder.push_ops([Drop; 4]), - Instruction::PadW => span_builder.push_ops([Pad; 4]), - Instruction::Dup0 => span_builder.push_op(Dup0), - Instruction::Dup1 => span_builder.push_op(Dup1), - Instruction::Dup2 => span_builder.push_op(Dup2), - Instruction::Dup3 => span_builder.push_op(Dup3), - Instruction::Dup4 => span_builder.push_op(Dup4), - Instruction::Dup5 => span_builder.push_op(Dup5), - Instruction::Dup6 => span_builder.push_op(Dup6), - Instruction::Dup7 => span_builder.push_op(Dup7), - Instruction::Dup8 => span_builder.push_ops([Pad, Dup9, Add]), - Instruction::Dup9 => span_builder.push_op(Dup9), - Instruction::Dup10 => span_builder.push_ops([Pad, Dup11, Add]), - Instruction::Dup11 => span_builder.push_op(Dup11), - Instruction::Dup12 => span_builder.push_ops([Pad, Dup13, Add]), - Instruction::Dup13 => span_builder.push_op(Dup13), - Instruction::Dup14 => span_builder.push_ops([Pad, Dup15, Add]), - Instruction::Dup15 => span_builder.push_op(Dup15), - Instruction::DupW0 => span_builder.push_ops([Dup3; 4]), - Instruction::DupW1 => span_builder.push_ops([Dup7; 4]), - Instruction::DupW2 => span_builder.push_ops([Dup11; 4]), - Instruction::DupW3 => span_builder.push_ops([Dup15; 4]), - Instruction::Swap1 => span_builder.push_op(Swap), - Instruction::Swap2 => span_builder.push_ops([Swap, MovUp2]), - Instruction::Swap3 => span_builder.push_ops([MovDn2, MovUp3]), - Instruction::Swap4 => span_builder.push_ops([MovDn3, MovUp4]), - Instruction::Swap5 => span_builder.push_ops([MovDn4, MovUp5]), - Instruction::Swap6 => span_builder.push_ops([MovDn5, MovUp6]), - Instruction::Swap7 => span_builder.push_ops([MovDn6, MovUp7]), - Instruction::Swap8 => span_builder.push_ops([MovDn7, MovUp8]), - Instruction::Swap9 => span_builder.push_ops([MovDn8, SwapDW, Swap, SwapDW, MovUp8]), + Instruction::Drop => block_builder.push_op(Drop), + Instruction::DropW => block_builder.push_ops([Drop; 4]), + Instruction::PadW => block_builder.push_ops([Pad; 4]), + Instruction::Dup0 => block_builder.push_op(Dup0), + Instruction::Dup1 => block_builder.push_op(Dup1), + Instruction::Dup2 => block_builder.push_op(Dup2), + Instruction::Dup3 => block_builder.push_op(Dup3), + Instruction::Dup4 => block_builder.push_op(Dup4), + Instruction::Dup5 => block_builder.push_op(Dup5), + Instruction::Dup6 => block_builder.push_op(Dup6), + Instruction::Dup7 => block_builder.push_op(Dup7), + Instruction::Dup8 => block_builder.push_ops([Pad, Dup9, Add]), + Instruction::Dup9 => block_builder.push_op(Dup9), + Instruction::Dup10 => block_builder.push_ops([Pad, Dup11, Add]), + Instruction::Dup11 => block_builder.push_op(Dup11), + Instruction::Dup12 => block_builder.push_ops([Pad, Dup13, Add]), + Instruction::Dup13 => block_builder.push_op(Dup13), + Instruction::Dup14 => block_builder.push_ops([Pad, Dup15, Add]), + Instruction::Dup15 => block_builder.push_op(Dup15), + Instruction::DupW0 => block_builder.push_ops([Dup3; 4]), + Instruction::DupW1 => block_builder.push_ops([Dup7; 4]), + Instruction::DupW2 => block_builder.push_ops([Dup11; 4]), + Instruction::DupW3 => block_builder.push_ops([Dup15; 4]), + Instruction::Swap1 => block_builder.push_op(Swap), + Instruction::Swap2 => block_builder.push_ops([Swap, MovUp2]), + Instruction::Swap3 => block_builder.push_ops([MovDn2, MovUp3]), + Instruction::Swap4 => block_builder.push_ops([MovDn3, MovUp4]), + Instruction::Swap5 => block_builder.push_ops([MovDn4, MovUp5]), + Instruction::Swap6 => block_builder.push_ops([MovDn5, MovUp6]), + Instruction::Swap7 => block_builder.push_ops([MovDn6, MovUp7]), + Instruction::Swap8 => block_builder.push_ops([MovDn7, MovUp8]), + Instruction::Swap9 => block_builder.push_ops([MovDn8, SwapDW, Swap, SwapDW, MovUp8]), Instruction::Swap10 => { - span_builder.push_ops([MovDn8, SwapDW, Swap, MovUp2, SwapDW, MovUp8]) - } + block_builder.push_ops([MovDn8, SwapDW, Swap, MovUp2, SwapDW, MovUp8]) + }, Instruction::Swap11 => { - span_builder.push_ops([MovDn8, SwapDW, MovDn2, MovUp3, SwapDW, MovUp8]) - } + block_builder.push_ops([MovDn8, SwapDW, MovDn2, MovUp3, SwapDW, MovUp8]) + }, Instruction::Swap12 => { - span_builder.push_ops([MovDn8, SwapDW, MovDn3, MovUp4, SwapDW, MovUp8]) - } + block_builder.push_ops([MovDn8, SwapDW, MovDn3, MovUp4, SwapDW, MovUp8]) + }, Instruction::Swap13 => { - span_builder.push_ops([MovDn8, SwapDW, MovDn4, MovUp5, SwapDW, MovUp8]) - } + block_builder.push_ops([MovDn8, SwapDW, MovDn4, MovUp5, SwapDW, MovUp8]) + }, Instruction::Swap14 => { - span_builder.push_ops([MovDn8, SwapDW, MovDn5, MovUp6, SwapDW, MovUp8]) - } + block_builder.push_ops([MovDn8, SwapDW, MovDn5, MovUp6, SwapDW, MovUp8]) + }, Instruction::Swap15 => { - span_builder.push_ops([MovDn8, SwapDW, MovDn6, MovUp7, SwapDW, MovUp8]) - } - Instruction::SwapW1 => span_builder.push_op(SwapW), - Instruction::SwapW2 => span_builder.push_op(SwapW2), - Instruction::SwapW3 => span_builder.push_op(SwapW3), - Instruction::SwapDw => span_builder.push_op(SwapDW), - Instruction::MovUp2 => span_builder.push_op(MovUp2), - Instruction::MovUp3 => span_builder.push_op(MovUp3), - Instruction::MovUp4 => span_builder.push_op(MovUp4), - Instruction::MovUp5 => span_builder.push_op(MovUp5), - Instruction::MovUp6 => span_builder.push_op(MovUp6), - Instruction::MovUp7 => span_builder.push_op(MovUp7), - Instruction::MovUp8 => span_builder.push_op(MovUp8), - Instruction::MovUp9 => span_builder.push_ops([SwapDW, Swap, SwapDW, MovUp8]), - Instruction::MovUp10 => span_builder.push_ops([SwapDW, MovUp2, SwapDW, MovUp8]), - Instruction::MovUp11 => span_builder.push_ops([SwapDW, MovUp3, SwapDW, MovUp8]), - Instruction::MovUp12 => span_builder.push_ops([SwapDW, MovUp4, SwapDW, MovUp8]), - Instruction::MovUp13 => span_builder.push_ops([SwapDW, MovUp5, SwapDW, MovUp8]), - Instruction::MovUp14 => span_builder.push_ops([SwapDW, MovUp6, SwapDW, MovUp8]), - Instruction::MovUp15 => span_builder.push_ops([SwapDW, MovUp7, SwapDW, MovUp8]), - Instruction::MovUpW2 => span_builder.push_ops([SwapW, SwapW2]), - Instruction::MovUpW3 => span_builder.push_ops([SwapW, SwapW2, SwapW3]), - Instruction::MovDn2 => span_builder.push_op(MovDn2), - Instruction::MovDn3 => span_builder.push_op(MovDn3), - Instruction::MovDn4 => span_builder.push_op(MovDn4), - Instruction::MovDn5 => span_builder.push_op(MovDn5), - Instruction::MovDn6 => span_builder.push_op(MovDn6), - Instruction::MovDn7 => span_builder.push_op(MovDn7), - Instruction::MovDn8 => span_builder.push_op(MovDn8), - Instruction::MovDn9 => span_builder.push_ops([MovDn8, SwapDW, Swap, SwapDW]), - Instruction::MovDn10 => span_builder.push_ops([MovDn8, SwapDW, MovDn2, SwapDW]), - Instruction::MovDn11 => span_builder.push_ops([MovDn8, SwapDW, MovDn3, SwapDW]), - Instruction::MovDn12 => span_builder.push_ops([MovDn8, SwapDW, MovDn4, SwapDW]), - Instruction::MovDn13 => span_builder.push_ops([MovDn8, SwapDW, MovDn5, SwapDW]), - Instruction::MovDn14 => span_builder.push_ops([MovDn8, SwapDW, MovDn6, SwapDW]), - Instruction::MovDn15 => span_builder.push_ops([MovDn8, SwapDW, MovDn7, SwapDW]), - Instruction::MovDnW2 => span_builder.push_ops([SwapW2, SwapW]), - Instruction::MovDnW3 => span_builder.push_ops([SwapW3, SwapW2, SwapW]), - - Instruction::CSwap => span_builder.push_op(CSwap), - Instruction::CSwapW => span_builder.push_op(CSwapW), - Instruction::CDrop => span_builder.push_ops([CSwap, Drop]), - Instruction::CDropW => span_builder.push_ops([CSwapW, Drop, Drop, Drop, Drop]), + block_builder.push_ops([MovDn8, SwapDW, MovDn6, MovUp7, SwapDW, MovUp8]) + }, + Instruction::SwapW1 => block_builder.push_op(SwapW), + Instruction::SwapW2 => block_builder.push_op(SwapW2), + Instruction::SwapW3 => block_builder.push_op(SwapW3), + Instruction::SwapDw => block_builder.push_op(SwapDW), + Instruction::MovUp2 => block_builder.push_op(MovUp2), + Instruction::MovUp3 => block_builder.push_op(MovUp3), + Instruction::MovUp4 => block_builder.push_op(MovUp4), + Instruction::MovUp5 => block_builder.push_op(MovUp5), + Instruction::MovUp6 => block_builder.push_op(MovUp6), + Instruction::MovUp7 => block_builder.push_op(MovUp7), + Instruction::MovUp8 => block_builder.push_op(MovUp8), + Instruction::MovUp9 => block_builder.push_ops([SwapDW, Swap, SwapDW, MovUp8]), + Instruction::MovUp10 => block_builder.push_ops([SwapDW, MovUp2, SwapDW, MovUp8]), + Instruction::MovUp11 => block_builder.push_ops([SwapDW, MovUp3, SwapDW, MovUp8]), + Instruction::MovUp12 => block_builder.push_ops([SwapDW, MovUp4, SwapDW, MovUp8]), + Instruction::MovUp13 => block_builder.push_ops([SwapDW, MovUp5, SwapDW, MovUp8]), + Instruction::MovUp14 => block_builder.push_ops([SwapDW, MovUp6, SwapDW, MovUp8]), + Instruction::MovUp15 => block_builder.push_ops([SwapDW, MovUp7, SwapDW, MovUp8]), + Instruction::MovUpW2 => block_builder.push_ops([SwapW, SwapW2]), + Instruction::MovUpW3 => block_builder.push_ops([SwapW, SwapW2, SwapW3]), + Instruction::MovDn2 => block_builder.push_op(MovDn2), + Instruction::MovDn3 => block_builder.push_op(MovDn3), + Instruction::MovDn4 => block_builder.push_op(MovDn4), + Instruction::MovDn5 => block_builder.push_op(MovDn5), + Instruction::MovDn6 => block_builder.push_op(MovDn6), + Instruction::MovDn7 => block_builder.push_op(MovDn7), + Instruction::MovDn8 => block_builder.push_op(MovDn8), + Instruction::MovDn9 => block_builder.push_ops([MovDn8, SwapDW, Swap, SwapDW]), + Instruction::MovDn10 => block_builder.push_ops([MovDn8, SwapDW, MovDn2, SwapDW]), + Instruction::MovDn11 => block_builder.push_ops([MovDn8, SwapDW, MovDn3, SwapDW]), + Instruction::MovDn12 => block_builder.push_ops([MovDn8, SwapDW, MovDn4, SwapDW]), + Instruction::MovDn13 => block_builder.push_ops([MovDn8, SwapDW, MovDn5, SwapDW]), + Instruction::MovDn14 => block_builder.push_ops([MovDn8, SwapDW, MovDn6, SwapDW]), + Instruction::MovDn15 => block_builder.push_ops([MovDn8, SwapDW, MovDn7, SwapDW]), + Instruction::MovDnW2 => block_builder.push_ops([SwapW2, SwapW]), + Instruction::MovDnW3 => block_builder.push_ops([SwapW3, SwapW2, SwapW]), + + Instruction::CSwap => block_builder.push_op(CSwap), + Instruction::CSwapW => block_builder.push_op(CSwapW), + Instruction::CDrop => block_builder.push_ops([CSwap, Drop]), + Instruction::CDropW => block_builder.push_ops([CSwapW, Drop, Drop, Drop, Drop]), // ----- input / output instructions -------------------------------------------------- - Instruction::Push(imm) => env_ops::push_one(imm.expect_value(), span_builder), - Instruction::PushU8(imm) => env_ops::push_one(*imm, span_builder), - Instruction::PushU16(imm) => env_ops::push_one(*imm, span_builder), - Instruction::PushU32(imm) => env_ops::push_one(*imm, span_builder), - Instruction::PushFelt(imm) => env_ops::push_one(*imm, span_builder), - Instruction::PushWord(imms) => env_ops::push_many(imms, span_builder), - Instruction::PushU8List(imms) => env_ops::push_many(imms, span_builder), - Instruction::PushU16List(imms) => env_ops::push_many(imms, span_builder), - Instruction::PushU32List(imms) => env_ops::push_many(imms, span_builder), - Instruction::PushFeltList(imms) => env_ops::push_many(imms, span_builder), - Instruction::Sdepth => span_builder.push_op(SDepth), - Instruction::Caller => env_ops::caller(span_builder, ctx)?, - Instruction::Clk => span_builder.push_op(Clk), - Instruction::AdvPipe => span_builder.push_op(Pipe), - Instruction::AdvPush(n) => adv_ops::adv_push(span_builder, n.expect_value())?, - Instruction::AdvLoadW => span_builder.push_op(AdvPopW), - - Instruction::MemStream => span_builder.push_op(MStream), - Instruction::Locaddr(v) => env_ops::locaddr(span_builder, v.expect_value(), ctx)?, - Instruction::MemLoad => mem_ops::mem_read(span_builder, ctx, None, false, true)?, + Instruction::Push(imm) => env_ops::push_one(imm.expect_value(), block_builder), + Instruction::PushU8(imm) => env_ops::push_one(*imm, block_builder), + Instruction::PushU16(imm) => env_ops::push_one(*imm, block_builder), + Instruction::PushU32(imm) => env_ops::push_one(*imm, block_builder), + Instruction::PushFelt(imm) => env_ops::push_one(*imm, block_builder), + Instruction::PushWord(imms) => env_ops::push_many(imms, block_builder), + Instruction::PushU8List(imms) => env_ops::push_many(imms, block_builder), + Instruction::PushU16List(imms) => env_ops::push_many(imms, block_builder), + Instruction::PushU32List(imms) => env_ops::push_many(imms, block_builder), + Instruction::PushFeltList(imms) => env_ops::push_many(imms, block_builder), + Instruction::Sdepth => block_builder.push_op(SDepth), + Instruction::Caller => env_ops::caller(block_builder, proc_ctx, instruction.span())?, + Instruction::Clk => block_builder.push_op(Clk), + Instruction::AdvPipe => block_builder.push_op(Pipe), + Instruction::AdvPush(n) => adv_ops::adv_push(block_builder, n.expect_value())?, + Instruction::AdvLoadW => block_builder.push_op(AdvPopW), + + Instruction::MemStream => block_builder.push_op(MStream), + Instruction::Locaddr(v) => env_ops::locaddr(block_builder, v.expect_value(), proc_ctx)?, + Instruction::MemLoad => mem_ops::mem_read(block_builder, proc_ctx, None, false, true)?, Instruction::MemLoadImm(v) => { - mem_ops::mem_read(span_builder, ctx, Some(v.expect_value()), false, true)? - } - Instruction::MemLoadW => mem_ops::mem_read(span_builder, ctx, None, false, false)?, + mem_ops::mem_read(block_builder, proc_ctx, Some(v.expect_value()), false, true)? + }, + Instruction::MemLoadW => { + mem_ops::mem_read(block_builder, proc_ctx, None, false, false)? + }, Instruction::MemLoadWImm(v) => { - mem_ops::mem_read(span_builder, ctx, Some(v.expect_value()), false, false)? - } - Instruction::LocLoad(v) => { - mem_ops::mem_read(span_builder, ctx, Some(v.expect_value() as u32), true, true)? - } - Instruction::LocLoadW(v) => { - mem_ops::mem_read(span_builder, ctx, Some(v.expect_value() as u32), true, false)? - } - Instruction::MemStore => span_builder.push_ops([MStore, Drop]), - Instruction::MemStoreW => span_builder.push_ops([MStoreW]), + mem_ops::mem_read(block_builder, proc_ctx, Some(v.expect_value()), false, false)? + }, + Instruction::LocLoad(v) => mem_ops::mem_read( + block_builder, + proc_ctx, + Some(v.expect_value() as u32), + true, + true, + )?, + Instruction::LocLoadW(v) => mem_ops::mem_read( + block_builder, + proc_ctx, + Some(v.expect_value() as u32), + true, + false, + )?, + Instruction::MemStore => block_builder.push_ops([MStore, Drop]), + Instruction::MemStoreW => block_builder.push_ops([MStoreW]), Instruction::MemStoreImm(v) => { - mem_ops::mem_write_imm(span_builder, ctx, v.expect_value(), false, true)? - } + mem_ops::mem_write_imm(block_builder, proc_ctx, v.expect_value(), false, true)? + }, Instruction::MemStoreWImm(v) => { - mem_ops::mem_write_imm(span_builder, ctx, v.expect_value(), false, false)? - } - Instruction::LocStore(v) => { - mem_ops::mem_write_imm(span_builder, ctx, v.expect_value() as u32, true, true)? - } - Instruction::LocStoreW(v) => { - mem_ops::mem_write_imm(span_builder, ctx, v.expect_value() as u32, true, false)? - } - - Instruction::AdvInject(injector) => adv_ops::adv_inject(span_builder, injector), + mem_ops::mem_write_imm(block_builder, proc_ctx, v.expect_value(), false, false)? + }, + Instruction::LocStore(v) => mem_ops::mem_write_imm( + block_builder, + proc_ctx, + v.expect_value() as u32, + true, + true, + )?, + Instruction::LocStoreW(v) => mem_ops::mem_write_imm( + block_builder, + proc_ctx, + v.expect_value() as u32, + true, + false, + )?, + + Instruction::AdvInject(injector) => adv_ops::adv_inject(block_builder, injector)?, // ----- cryptographic instructions --------------------------------------------------- - Instruction::Hash => crypto_ops::hash(span_builder), - Instruction::HPerm => span_builder.push_op(HPerm), - Instruction::HMerge => crypto_ops::hmerge(span_builder), - Instruction::MTreeGet => crypto_ops::mtree_get(span_builder), - Instruction::MTreeSet => crypto_ops::mtree_set(span_builder), - Instruction::MTreeMerge => crypto_ops::mtree_merge(span_builder), - Instruction::MTreeVerify => crypto_ops::mtree_verify(span_builder), + Instruction::Hash => crypto_ops::hash(block_builder), + Instruction::HPerm => block_builder.push_op(HPerm), + Instruction::HMerge => crypto_ops::hmerge(block_builder), + Instruction::MTreeGet => crypto_ops::mtree_get(block_builder)?, + Instruction::MTreeSet => crypto_ops::mtree_set(block_builder)?, + Instruction::MTreeMerge => crypto_ops::mtree_merge(block_builder)?, + Instruction::MTreeVerify => block_builder.push_op(MpVerify(0)), + Instruction::MTreeVerifyWithError(err_code) => { + block_builder.push_op(MpVerify(err_code.expect_value())) + }, // ----- STARK proof verification ----------------------------------------------------- - Instruction::FriExt2Fold4 => span_builder.push_op(FriE2F4), - Instruction::RCombBase => span_builder.push_op(RCombBase), + Instruction::FriExt2Fold4 => block_builder.push_op(FriE2F4), + Instruction::RCombBase => block_builder.push_op(RCombBase), // ----- exec/call instructions ------------------------------------------------------- - Instruction::Exec(ref callee) => return self.invoke(InvokeKind::Exec, callee, ctx), - Instruction::Call(ref callee) => return self.invoke(InvokeKind::Call, callee, ctx), + Instruction::Exec(ref callee) => { + return self + .invoke( + InvokeKind::Exec, + callee, + proc_ctx, + block_builder.mast_forest_builder_mut(), + ) + .map(Into::into); + }, + Instruction::Call(ref callee) => { + return self + .invoke( + InvokeKind::Call, + callee, + proc_ctx, + block_builder.mast_forest_builder_mut(), + ) + .map(Into::into); + }, Instruction::SysCall(ref callee) => { - return self.invoke(InvokeKind::SysCall, callee, ctx) - } - Instruction::DynExec => return self.dynexec(), - Instruction::DynCall => return self.dyncall(), - Instruction::ProcRef(ref callee) => self.procref(callee, ctx, span_builder)?, + return self + .invoke( + InvokeKind::SysCall, + callee, + proc_ctx, + block_builder.mast_forest_builder_mut(), + ) + .map(Into::into); + }, + Instruction::DynExec => return self.dynexec(block_builder.mast_forest_builder_mut()), + Instruction::DynCall => return self.dyncall(block_builder.mast_forest_builder_mut()), + Instruction::ProcRef(ref callee) => self.procref(callee, proc_ctx, block_builder)?, // ----- debug decorators ------------------------------------------------------------- Instruction::Breakpoint => { if self.in_debug_mode() { - span_builder.push_op(Noop); - span_builder.track_instruction(instruction, ctx); + block_builder.push_op(Noop); + block_builder.track_instruction(instruction, proc_ctx)?; } - } + }, Instruction::Debug(options) => { if self.in_debug_mode() { - span_builder.push_decorator(Decorator::Debug( + block_builder.push_decorator(Decorator::Debug( options.clone().try_into().expect("unresolved constant"), - )) + ))?; } - } + }, // ----- emit instruction ------------------------------------------------------------- Instruction::Emit(event_id) => { - span_builder.push_decorator(Decorator::Event(event_id.expect_value())); - } + block_builder.push_op(Operation::Emit(event_id.expect_value())); + }, // ----- trace instruction ------------------------------------------------------------ Instruction::Trace(trace_id) => { - span_builder.push_decorator(Decorator::Trace(trace_id.expect_value())); - } + block_builder.push_decorator(Decorator::Trace(trace_id.expect_value()))?; + }, } Ok(None) @@ -404,7 +453,7 @@ impl Assembler { /// /// When the value is 0, PUSH operation is replaced with PAD. When the value is 1, PUSH operation /// is replaced with PAD INCR because in most cases this will be more efficient than doing a PUSH. -fn push_u32_value(span_builder: &mut SpanBuilder, value: u32) { +fn push_u32_value(span_builder: &mut BasicBlockBuilder, value: u32) { use Operation::*; if value == 0 { @@ -422,7 +471,7 @@ fn push_u32_value(span_builder: &mut SpanBuilder, value: u32) { /// /// When the value is 0, PUSH operation is replaced with PAD. When the value is 1, PUSH operation /// is replaced with PAD INCR because in most cases this will be more efficient than doing a PUSH. -fn push_felt(span_builder: &mut SpanBuilder, value: Felt) { +fn push_felt(span_builder: &mut BasicBlockBuilder, value: Felt) { use Operation::*; if value == ZERO { @@ -447,10 +496,10 @@ where let min = bound_into_included_u64(range.start_bound(), true); let max = bound_into_included_u64(range.end_bound(), false); AssemblyError::Other( - Report::msg(format!( + miette!( "parameter value must be greater than or equal to {min} and \ less than or equal to {max}, but was {value}", - )) + ) .into(), ) }) diff --git a/assembly/src/assembler/instruction/procedures.rs b/assembly/src/assembler/instruction/procedures.rs index 1949d75e3a..8316422095 100644 --- a/assembly/src/assembler/instruction/procedures.rs +++ b/assembly/src/assembler/instruction/procedures.rs @@ -1,137 +1,96 @@ -use super::{Assembler, AssemblyContext, CodeBlock, Operation, SpanBuilder}; +use smallvec::SmallVec; +use vm_core::mast::MastNodeId; + +use super::{Assembler, BasicBlockBuilder, Operation}; use crate::{ + assembler::{mast_forest_builder::MastForestBuilder, ProcedureContext}, ast::{InvocationTarget, InvokeKind}, - AssemblyError, RpoDigest, SourceSpan, Span, Spanned, + AssemblyError, RpoDigest, }; -use smallvec::SmallVec; - /// Procedure Invocation impl Assembler { + /// Returns the [`MastNodeId`] of the invoked procedure specified by `callee`. + /// + /// For example, given `exec.f`, this method would return the procedure body id of `f`. If the + /// only representation of `f` that we have is its MAST root, then this method will also insert + /// a [`core::mast::ExternalNode`] that wraps `f`'s MAST root and return the corresponding id. pub(super) fn invoke( &self, kind: InvokeKind, callee: &InvocationTarget, - context: &mut AssemblyContext, - ) -> Result, AssemblyError> { - let span = callee.span(); - let digest = self.resolve_target(kind, callee, context)?; - self.invoke_mast_root(kind, span, digest, context) + proc_ctx: &ProcedureContext, + mast_forest_builder: &mut MastForestBuilder, + ) -> Result { + let invoked_proc_node_id = + self.resolve_target(kind, callee, proc_ctx, mast_forest_builder)?; + + match kind { + InvokeKind::ProcRef | InvokeKind::Exec => Ok(invoked_proc_node_id), + InvokeKind::Call => mast_forest_builder.ensure_call(invoked_proc_node_id), + InvokeKind::SysCall => mast_forest_builder.ensure_syscall(invoked_proc_node_id), + } } - fn invoke_mast_root( + /// Creates a new DYN block for the dynamic code execution and return. + pub(super) fn dynexec( &self, - kind: InvokeKind, - span: SourceSpan, - mast_root: RpoDigest, - context: &mut AssemblyContext, - ) -> Result, AssemblyError> { - // Get the procedure from the assembler - let cache = &self.procedure_cache; - let current_source_file = context.unwrap_current_procedure().source_file(); - - // If the procedure is cached, register the call to ensure the callset - // is updated correctly. Otherwise, register a phantom call. - match cache.get_by_mast_root(&mast_root) { - Some(proc) if matches!(kind, InvokeKind::SysCall) => { - // Verify if this is a syscall, that the callee is a kernel procedure - // - // NOTE: The assembler is expected to know the full set of all kernel - // procedures at this point, so if we can't identify the callee as a - // kernel procedure, it is a definite error. - if !proc.visibility().is_syscall() { - return Err(AssemblyError::InvalidSysCallTarget { - span, - source_file: current_source_file, - callee: proc.fully_qualified_name().clone(), - }); - } - let maybe_kernel_path = proc.path(); - self.module_graph - .find_module(maybe_kernel_path) - .ok_or_else(|| AssemblyError::InvalidSysCallTarget { - span, - source_file: current_source_file.clone(), - callee: proc.fully_qualified_name().clone(), - }) - .and_then(|module| { - if module.is_kernel() { - Ok(()) - } else { - Err(AssemblyError::InvalidSysCallTarget { - span, - source_file: current_source_file.clone(), - callee: proc.fully_qualified_name().clone(), - }) - } - })?; - context.register_external_call(&proc, false)?; - } - Some(proc) => context.register_external_call(&proc, false)?, - None if matches!(kind, InvokeKind::SysCall) => { - return Err(AssemblyError::UnknownSysCallTarget { - span, - source_file: current_source_file, - callee: mast_root, - }); - } - None => context.register_phantom_call(Span::new(span, mast_root))?, - } + mast_forest_builder: &mut MastForestBuilder, + ) -> Result, AssemblyError> { + let dyn_node_id = mast_forest_builder.ensure_dyn()?; - let block = match kind { - // For `exec`, we use a PROXY block to reflect that the root is - // conceptually inlined at this location - InvokeKind::Exec => CodeBlock::new_proxy(mast_root), - // For `call`, we just use the corresponding CALL block - InvokeKind::Call => CodeBlock::new_call(mast_root), - // For `syscall`, we just use the corresponding SYSCALL block - InvokeKind::SysCall => CodeBlock::new_syscall(mast_root), - }; - Ok(Some(block)) + Ok(Some(dyn_node_id)) } - pub(super) fn dynexec(&self) -> Result, AssemblyError> { - // create a new DYN block for the dynamic code execution and return - Ok(Some(CodeBlock::new_dyn())) - } + /// Creates a new CALL block whose target is DYN. + pub(super) fn dyncall( + &self, + mast_forest_builder: &mut MastForestBuilder, + ) -> Result, AssemblyError> { + let dyn_call_node_id = { + let dyn_node_id = mast_forest_builder.ensure_dyn()?; + mast_forest_builder.ensure_call(dyn_node_id)? + }; - pub(super) fn dyncall(&self) -> Result, AssemblyError> { - // create a new CALL block whose target is DYN - Ok(Some(CodeBlock::new_dyncall())) + Ok(Some(dyn_call_node_id)) } pub(super) fn procref( &self, callee: &InvocationTarget, - context: &mut AssemblyContext, - span_builder: &mut SpanBuilder, + proc_ctx: &mut ProcedureContext, + block_builder: &mut BasicBlockBuilder, ) -> Result<(), AssemblyError> { - let span = callee.span(); - let digest = self.resolve_target(InvokeKind::Exec, callee, context)?; - self.procref_mast_root(span, digest, context, span_builder) + let mast_root = { + let proc_body_id = self.resolve_target( + InvokeKind::ProcRef, + callee, + proc_ctx, + block_builder.mast_forest_builder_mut(), + )?; + // Note: it's ok to `unwrap()` here since `proc_body_id` was returned from + // `mast_forest_builder` + block_builder + .mast_forest_builder() + .get_mast_node(proc_body_id) + .unwrap() + .digest() + }; + + self.procref_mast_root(mast_root, block_builder) } fn procref_mast_root( &self, - span: SourceSpan, mast_root: RpoDigest, - context: &mut AssemblyContext, - span_builder: &mut SpanBuilder, + block_builder: &mut BasicBlockBuilder, ) -> Result<(), AssemblyError> { - // Add the root to the callset to be able to use dynamic instructions - // with the referenced procedure later - let cache = &self.procedure_cache; - match cache.get_by_mast_root(&mast_root) { - Some(proc) => context.register_external_call(&proc, false)?, - None => context.register_phantom_call(Span::new(span, mast_root))?, - } - // Create an array with `Push` operations containing root elements let ops = mast_root .iter() .map(|elem| Operation::Push(*elem)) .collect::>(); - span_builder.push_ops(ops); + block_builder.push_ops(ops); Ok(()) } } diff --git a/assembly/src/assembler/instruction/u32_ops.rs b/assembly/src/assembler/instruction/u32_ops.rs index 487ad73b45..8826d05939 100644 --- a/assembly/src/assembler/instruction/u32_ops.rs +++ b/assembly/src/assembler/instruction/u32_ops.rs @@ -1,12 +1,13 @@ -use super::{field_ops::append_pow2_op, push_u32_value, validate_param, SpanBuilder}; -use crate::{ - diagnostics::{RelatedError, Report}, - AssemblyContext, AssemblyError, Span, MAX_U32_ROTATE_VALUE, MAX_U32_SHIFT_VALUE, -}; use vm_core::{ AdviceInjector, Felt, Operation::{self, *}, - ZERO, +}; + +use super::{field_ops::append_pow2_op, push_u32_value, validate_param, BasicBlockBuilder}; +use crate::{ + assembler::ProcedureContext, + diagnostics::{RelatedError, Report}, + AssemblyError, Span, MAX_U32_ROTATE_VALUE, MAX_U32_SHIFT_VALUE, }; /// This enum is intended to determine the mode of operation passed to the parsing function @@ -23,7 +24,7 @@ pub enum U32OpMode { /// /// Implemented by executing DUP U32SPLIT SWAP DROP EQZ on each element in the word /// and combining the results using AND operation (total of 23 VM cycles) -pub fn u32testw(span_builder: &mut SpanBuilder) { +pub fn u32testw(span_builder: &mut BasicBlockBuilder) { #[rustfmt::skip] let ops = [ // Test the fourth element @@ -45,7 +46,7 @@ pub fn u32testw(span_builder: &mut SpanBuilder) { /// /// Implemented by executing `U32ASSERT2` on each pair of elements in the word. /// Total of 6 VM cycles. -pub fn u32assertw(span_builder: &mut SpanBuilder, err_code: Felt) { +pub fn u32assertw(span_builder: &mut BasicBlockBuilder, err_code: u32) { #[rustfmt::skip] let ops = [ // Test the first and the second elements @@ -76,7 +77,7 @@ pub fn u32assertw(span_builder: &mut SpanBuilder, err_code: Felt) { /// - u32wrapping_add.b: 3 cycles /// - u32overflowing_add: 1 cycles /// - u32overflowing_add.b: 2 cycles -pub fn u32add(span_builder: &mut SpanBuilder, op_mode: U32OpMode, imm: Option) { +pub fn u32add(span_builder: &mut BasicBlockBuilder, op_mode: U32OpMode, imm: Option) { handle_arithmetic_operation(span_builder, U32add, op_mode, imm); } @@ -90,7 +91,7 @@ pub fn u32add(span_builder: &mut SpanBuilder, op_mode: U32OpMode, imm: Option) { +pub fn u32sub(span_builder: &mut BasicBlockBuilder, op_mode: U32OpMode, imm: Option) { handle_arithmetic_operation(span_builder, U32sub, op_mode, imm); } @@ -104,7 +105,7 @@ pub fn u32sub(span_builder: &mut SpanBuilder, op_mode: U32OpMode, imm: Option) { +pub fn u32mul(span_builder: &mut BasicBlockBuilder, op_mode: U32OpMode, imm: Option) { handle_arithmetic_operation(span_builder, U32mul, op_mode, imm); } @@ -116,11 +117,11 @@ pub fn u32mul(span_builder: &mut SpanBuilder, op_mode: U32OpMode, imm: Option>, ) -> Result<(), AssemblyError> { - handle_division(span_builder, ctx, imm)?; + handle_division(span_builder, proc_ctx, imm)?; span_builder.push_op(Drop); Ok(()) } @@ -133,11 +134,11 @@ pub fn u32div( /// - 5 cycles if b is 1 /// - 4 cycles if b is not 1 pub fn u32mod( - span_builder: &mut SpanBuilder, - ctx: &AssemblyContext, + span_builder: &mut BasicBlockBuilder, + proc_ctx: &ProcedureContext, imm: Option>, ) -> Result<(), AssemblyError> { - handle_division(span_builder, ctx, imm)?; + handle_division(span_builder, proc_ctx, imm)?; span_builder.push_ops([Swap, Drop]); Ok(()) } @@ -150,11 +151,11 @@ pub fn u32mod( /// - 3 cycles if b is 1 /// - 2 cycles if b is not 1 pub fn u32divmod( - span_builder: &mut SpanBuilder, - ctx: &AssemblyContext, + span_builder: &mut BasicBlockBuilder, + proc_ctx: &ProcedureContext, imm: Option>, ) -> Result<(), AssemblyError> { - handle_division(span_builder, ctx, imm) + handle_division(span_builder, proc_ctx, imm) } // BITWISE OPERATIONS @@ -166,12 +167,12 @@ pub fn u32divmod( /// subtracting the element, flips the bits of the original value to perform a bitwise NOT. /// /// This takes 5 VM cycles. -pub fn u32not(span_builder: &mut SpanBuilder) { +pub fn u32not(span_builder: &mut BasicBlockBuilder) { #[rustfmt::skip] let ops = [ // Perform the operation Push(Felt::from(u32::MAX)), - U32assert2(ZERO), + U32assert2(0), Swap, U32sub, @@ -189,7 +190,7 @@ pub fn u32not(span_builder: &mut SpanBuilder) { /// VM cycles per mode: /// - u32shl: 18 cycles /// - u32shl.b: 3 cycles -pub fn u32shl(span_builder: &mut SpanBuilder, imm: Option) -> Result<(), AssemblyError> { +pub fn u32shl(span_builder: &mut BasicBlockBuilder, imm: Option) -> Result<(), AssemblyError> { prepare_bitwise::(span_builder, imm)?; if imm != Some(0) { span_builder.push_ops([U32mul, Drop]); @@ -205,7 +206,7 @@ pub fn u32shl(span_builder: &mut SpanBuilder, imm: Option) -> Result<(), Ass /// VM cycles per mode: /// - u32shr: 18 cycles /// - u32shr.b: 3 cycles -pub fn u32shr(span_builder: &mut SpanBuilder, imm: Option) -> Result<(), AssemblyError> { +pub fn u32shr(span_builder: &mut BasicBlockBuilder, imm: Option) -> Result<(), AssemblyError> { prepare_bitwise::(span_builder, imm)?; if imm != Some(0) { span_builder.push_ops([U32div, Drop]); @@ -221,7 +222,7 @@ pub fn u32shr(span_builder: &mut SpanBuilder, imm: Option) -> Result<(), Ass /// VM cycles per mode: /// - u32rotl: 18 cycles /// - u32rotl.b: 3 cycles -pub fn u32rotl(span_builder: &mut SpanBuilder, imm: Option) -> Result<(), AssemblyError> { +pub fn u32rotl(span_builder: &mut BasicBlockBuilder, imm: Option) -> Result<(), AssemblyError> { prepare_bitwise::(span_builder, imm)?; if imm != Some(0) { span_builder.push_ops([U32mul, Add]); @@ -235,32 +236,32 @@ pub fn u32rotl(span_builder: &mut SpanBuilder, imm: Option) -> Result<(), As /// b is the shift amount, then adding the overflow limb to the shifted limb. /// /// VM cycles per mode: -/// - u32rotr: 22 cycles +/// - u32rotr: 23 cycles /// - u32rotr.b: 3 cycles -pub fn u32rotr(span_builder: &mut SpanBuilder, imm: Option) -> Result<(), AssemblyError> { +pub fn u32rotr(span_builder: &mut BasicBlockBuilder, imm: Option) -> Result<(), AssemblyError> { match imm { Some(0) => { // if rotation is performed by 0, do nothing (Noop) span_builder.push_op(Noop); - return Ok(()); - } + }, Some(imm) => { validate_param(imm, 1..=MAX_U32_ROTATE_VALUE)?; span_builder.push_op(Push(Felt::new(1 << (32 - imm)))); - } + span_builder.push_ops([U32mul, Add]); + }, None => { span_builder.push_ops([Push(Felt::new(32)), Swap, U32sub, Drop]); append_pow2_op(span_builder); - } + span_builder.push_ops([Mul, U32split, Add]); + }, } - span_builder.push_ops([U32mul, Add]); Ok(()) } /// Translates u32popcnt assembly instructions to VM operations. /// /// This operation takes 33 cycles. -pub fn u32popcnt(span_builder: &mut SpanBuilder) { +pub fn u32popcnt(span_builder: &mut BasicBlockBuilder) { #[rustfmt::skip] let ops = [ // i = i - ((i >> 1) & 0x55555555); @@ -296,12 +297,13 @@ pub fn u32popcnt(span_builder: &mut SpanBuilder) { /// leading zeros of the value using non-deterministic technique (i.e. it takes help of advice /// provider). /// -/// This operation takes 37 VM cycles. -pub fn u32clz(span: &mut SpanBuilder) { - span.push_advice_injector(AdviceInjector::U32Clz); - span.push_op(AdvPop); // [clz, n, ...] +/// This operation takes 42 VM cycles. +pub fn u32clz(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyError> { + block_builder.push_advice_injector(AdviceInjector::U32Clz)?; + block_builder.push_op(AdvPop); // [clz, n, ...] - calculate_clz(span); + verify_clz(block_builder); + Ok(()) } /// Translates `u32ctz` assembly instruction to VM operations. `u32ctz` counts the number of @@ -309,23 +311,25 @@ pub fn u32clz(span: &mut SpanBuilder) { /// provider). /// /// This operation takes 34 VM cycles. -pub fn u32ctz(span: &mut SpanBuilder) { - span.push_advice_injector(AdviceInjector::U32Ctz); - span.push_op(AdvPop); // [ctz, n, ...] +pub fn u32ctz(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyError> { + block_builder.push_advice_injector(AdviceInjector::U32Ctz)?; + block_builder.push_op(AdvPop); // [ctz, n, ...] - calculate_ctz(span); + verify_ctz(block_builder); + Ok(()) } /// Translates `u32clo` assembly instruction to VM operations. `u32clo` counts the number of /// leading ones of the value using non-deterministic technique (i.e. it takes help of advice /// provider). /// -/// This operation takes 36 VM cycles. -pub fn u32clo(span: &mut SpanBuilder) { - span.push_advice_injector(AdviceInjector::U32Clo); - span.push_op(AdvPop); // [clo, n, ...] +/// This operation takes 41 VM cycles. +pub fn u32clo(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyError> { + block_builder.push_advice_injector(AdviceInjector::U32Clo)?; + block_builder.push_op(AdvPop); // [clo, n, ...] - calculate_clo(span); + verify_clo(block_builder); + Ok(()) } /// Translates `u32cto` assembly instruction to VM operations. `u32cto` counts the number of @@ -333,11 +337,12 @@ pub fn u32clo(span: &mut SpanBuilder) { /// provider). /// /// This operation takes 33 VM cycles. -pub fn u32cto(span: &mut SpanBuilder) { - span.push_advice_injector(AdviceInjector::U32Cto); - span.push_op(AdvPop); // [cto, n, ...] +pub fn u32cto(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyError> { + block_builder.push_advice_injector(AdviceInjector::U32Cto)?; + block_builder.push_op(AdvPop); // [cto, n, ...] - calculate_cto(span); + verify_cto(block_builder); + Ok(()) } /// Specifically handles these specific inputs per the spec. @@ -346,45 +351,45 @@ pub fn u32cto(span: &mut SpanBuilder) { /// - Overflowing: does not check if the inputs are u32 values; overflow or underflow bits are /// pushed onto the stack. fn handle_arithmetic_operation( - span_builder: &mut SpanBuilder, + block_builder: &mut BasicBlockBuilder, op: Operation, op_mode: U32OpMode, imm: Option, ) { if let Some(imm) = imm { - push_u32_value(span_builder, imm); + push_u32_value(block_builder, imm); } - span_builder.push_op(op); + block_builder.push_op(op); // in the wrapping mode, drop high 32 bits if matches!(op_mode, U32OpMode::Wrapping) { - span_builder.push_op(Drop); + block_builder.push_op(Drop); } } /// Handles common parts of u32div, u32mod, and u32divmod operations, including handling of /// immediate parameters. fn handle_division( - span_builder: &mut SpanBuilder, - ctx: &AssemblyContext, + block_builder: &mut BasicBlockBuilder, + proc_ctx: &ProcedureContext, imm: Option>, ) -> Result<(), AssemblyError> { if let Some(imm) = imm { if imm == 0 { - let source_file = ctx.unwrap_current_procedure().source_file(); - let error = - Report::new(crate::parser::ParsingError::DivisionByZero { span: imm.span() }); + let imm_span = imm.span(); + let source_file = proc_ctx.source_manager().get(imm_span.source_id()).ok(); + let error = Report::new(crate::parser::ParsingError::DivisionByZero { span: imm_span }); return if let Some(source_file) = source_file { Err(AssemblyError::Other(RelatedError::new(error.with_source_code(source_file)))) } else { Err(AssemblyError::Other(RelatedError::new(error))) }; } - push_u32_value(span_builder, imm.into_inner()); + push_u32_value(block_builder, imm.into_inner()); } - span_builder.push_op(U32div); + block_builder.push_op(U32div); Ok(()) } @@ -394,21 +399,21 @@ fn handle_division( /// Mutate the first two elements of the stack from `[b, a, ..]` into `[2^b, a, ..]`, with `b` /// either as a provided immediate value, or as an element that already exists in the stack. fn prepare_bitwise( - span_builder: &mut SpanBuilder, + block_builder: &mut BasicBlockBuilder, imm: Option, ) -> Result<(), AssemblyError> { match imm { Some(0) => { // if shift/rotation is performed by 0, do nothing (Noop) - span_builder.push_op(Noop); - } + block_builder.push_op(Noop); + }, Some(imm) => { validate_param(imm, 1..=MAX_VALUE)?; - span_builder.push_op(Push(Felt::new(1 << imm))); - } + block_builder.push_op(Push(Felt::new(1 << imm))); + }, None => { - append_pow2_op(span_builder); - } + append_pow2_op(block_builder); + }, } Ok(()) } @@ -448,44 +453,50 @@ fn prepare_bitwise( /// /// `[clz, n, ... ] -> [clz, ... ]` /// -/// VM cycles: 36 -fn calculate_clz(span: &mut SpanBuilder) { +/// VM cycles: 42 +fn verify_clz(block_builder: &mut BasicBlockBuilder) { // [clz, n, ...] #[rustfmt::skip] let ops_group_1 = [ - Swap, Push(32u8.into()), Dup2, Neg, Add // [32 - clz, n, clz, ...] + Push(32u8.into()), Dup1, Neg, Add // [32 - clz, clz, n, ...] ]; - span.push_ops(ops_group_1); + block_builder.push_ops(ops_group_1); - append_pow2_op(span); // [pow2(32 - clz), n, clz, ...] + append_pow2_op(block_builder); // [pow2(32 - clz), clz, n, ...] #[rustfmt::skip] let ops_group_2 = [ - Push(Felt::new(u32::MAX as u64 + 1)), // [2^32, pow2(32 - clz), n, clz, ...] - - Dup1, Neg, Add, // [2^32 - pow2(32 - clz), pow2(32 - clz), n, clz, ...] - // `2^32 - pow2(32 - clz)` is equal to `clz` leading ones and `32 - clz` - // zeros: - // 1111111111...1110000...0 - // └─ `clz` ones ─┘ - - Swap, Push(2u8.into()), U32div, Drop, // [pow2(32 - clz) / 2, 2^32 - pow2(32 - clz), n, clz, ...] - // pow2(32 - clz) / 2 is equal to `clz` leading - // zeros, `1` one and all other zeros. - - Swap, Dup1, Add, // [bit_mask, pow2(32 - clz) / 2, n, clz, ...] - // 1111111111...111000...0 <-- bitmask - // └─ clz ones ─┘│ - // └─ additional one - - MovUp2, U32and, // [m, pow2(32 - clz) / 2, clz] - // If calcualtion of `clz` is correct, m should be equal to - // pow2(32 - clz) / 2 - - Eq, Assert(0) // [clz, ...] + // 1. Obtain a mask for all `32 - clz` trailing bits + // + // #=> [2^(32 - clz) - 1, clz, n] + Push(1u8.into()), Neg, Add, + // 2. Compute a value that represents setting the first non-zero bit to 1, i.e. if there + // are 2 leading zeros, this would set the 3rd most significant bit to 1, with all other + // bits set to zero. + // + // NOTE: This first step is an intermediate computation. + // + // #=> [(2^(32 - clz) - 1) / 2, clz, n, ...] + Push(2u8.into()), U32div, Drop, + // Save the intermediate result of dividing by 2 for reuse in the next step + // + // #=> [((2^(32 - clz) - 1) / 2) + 1, (2^(32 - clz) - 1) / 2, clz, n, ...] + Dup0, Incr, + // 3. Obtain a mask for `clz + 1` leading bits + // + // #=> [u32::MAX - (2^(32 - clz) - 1 / 2), ((2^(32 - clz) - 1) / 2) + 1, clz, n, ...] + Push(u32::MAX.into()), MovUp2, Neg, Add, + // 4. Set zero flag if input was zero, and apply the mask to the input value + // + // #=> [n & mask, (2^(32 - clz) - 1 / 2) + 1, clz, is_zero] + Dup3, Eqz, MovDn3, MovUp4, U32and, + // 6. Assert that the masked input, and the mask representing `clz` leading zeros, followed + // by at least one trailing one, if `clz < 32`, are equal; OR that the input was zero if `clz` + // is 32. + Eq, MovUp2, Or, Assert(0), ]; - span.push_ops(ops_group_2); + block_builder.push_ops(ops_group_2); } /// Appends relevant operations to the span block for the correctness check of the `U32Clo` @@ -523,44 +534,44 @@ fn calculate_clz(span: &mut SpanBuilder) { /// /// `[clo, n, ... ] -> [clo, ... ]` /// -/// VM cycles: 35 -fn calculate_clo(span: &mut SpanBuilder) { +/// VM cycle: 40 +fn verify_clo(block_builder: &mut BasicBlockBuilder) { // [clo, n, ...] #[rustfmt::skip] let ops_group_1 = [ - Swap, Push(32u8.into()), Dup2, Neg, Add // [32 - clo, n, clo, ...] + Push(32u8.into()), Dup1, Neg, Add // [32 - clo, clo, n, ...] ]; - span.push_ops(ops_group_1); + block_builder.push_ops(ops_group_1); - append_pow2_op(span); // [pow2(32 - clo), n, clo, ...] + append_pow2_op(block_builder); // [pow2(32 - clo), clo, n, ...] #[rustfmt::skip] let ops_group_2 = [ - Push(Felt::new(u32::MAX as u64 + 1)), // [2^32, pow2(32 - clo), n, clo, ...] - - Dup1, Neg, Add, // [2^32 - pow2(32 - clo), pow2(32 - clo), n, clo, ...] - // `2^32 - pow2(32 - clo)` is equal to `clo` leading ones and `32 - clo` - // zeros: - // 11111111...1110000...0 - // └─ clo ones ─┘ - - Swap, Push(2u8.into()), U32div, Drop, // [pow2(32 - clo) / 2, 2^32 - pow2(32 - clo), n, clo, ...] - // pow2(32 - clo) / 2 is equal to `clo` leading - // zeros, `1` one and all other zeros. - - Dup1, Add, // [bit_mask, 2^32 - pow2(32 - clo), n, clo, ...] - // 111111111...111000...0 <-- bitmask - // └─ clo ones ─┘│ - // └─ additional one - - MovUp2, U32and, // [m, 2^32 - pow2(32 - clo), clo] - // If calcualtion of `clo` is correct, m should be equal to - // 2^32 - pow2(32 - clo) - - Eq, Assert(0) // [clo, ...] + // 1. Obtain a mask for all `32 - clo` trailing bits + // + // #=> [2^(32 - clo) - 1, clo, n] + Push(1u8.into()), Neg, Add, + // 2. Obtain a mask for `32 - clo - 1` trailing bits + // + // #=> [(2^(32 - clo) - 1) / 2, 2^(32 - clo) - 1, clo, n] + Dup0, Push(2u8.into()), U32div, Drop, + // 3. Invert the mask from Step 2, to get one that covers `clo + 1` leading bits + // + // #=> [u32::MAX - ((2^(32 - clo) - 1) / 2), 2^(32 - clo) - 1, clo, n] + Push(u32::MAX.into()), Swap, Neg, Add, + // 4. Apply the mask to the input value + // + // #=> [n & mask, 2^(32 - clo) - 1, clo] + MovUp3, U32and, + // 5. Invert the mask from Step 1, to get one that covers `clo` leading bits + // + // #=> [u32::MAX - 2^(32 - clo) - 1, n & mask, clo] + Push(u32::MAX.into()), MovUp2, Neg, Add, + // 6. Assert that the masked input, and the mask representing `clo` leading ones, are equal + Eq, Assert(0), ]; - span.push_ops(ops_group_2); + block_builder.push_ops(ops_group_2); } /// Appends relevant operations to the span block for the correctness check of the `U32Ctz` @@ -599,15 +610,15 @@ fn calculate_clo(span: &mut SpanBuilder) { /// `[ctz, n, ... ] -> [ctz, ... ]` /// /// VM cycles: 33 -fn calculate_ctz(span: &mut SpanBuilder) { +fn verify_ctz(block_builder: &mut BasicBlockBuilder) { // [ctz, n, ...] #[rustfmt::skip] let ops_group_1 = [ Swap, Dup1, // [ctz, n, ctz, ...] ]; - span.push_ops(ops_group_1); + block_builder.push_ops(ops_group_1); - append_pow2_op(span); // [pow2(ctz), n, ctz, ...] + append_pow2_op(block_builder); // [pow2(ctz), n, ctz, ...] #[rustfmt::skip] let ops_group_2 = [ @@ -634,7 +645,7 @@ fn calculate_ctz(span: &mut SpanBuilder) { Eq, Assert(0), // [ctz, ...] ]; - span.push_ops(ops_group_2); + block_builder.push_ops(ops_group_2); } /// Appends relevant operations to the span block for the correctness check of the `U32Cto` @@ -673,15 +684,15 @@ fn calculate_ctz(span: &mut SpanBuilder) { /// `[cto, n, ... ] -> [cto, ... ]` /// /// VM cycles: 32 -fn calculate_cto(span: &mut SpanBuilder) { +fn verify_cto(block_builder: &mut BasicBlockBuilder) { // [cto, n, ...] #[rustfmt::skip] let ops_group_1 = [ Swap, Dup1, // [cto, n, cto, ...] ]; - span.push_ops(ops_group_1); + block_builder.push_ops(ops_group_1); - append_pow2_op(span); // [pow2(cto), n, cto, ...] + append_pow2_op(block_builder); // [pow2(cto), n, cto, ...] #[rustfmt::skip] let ops_group_2 = [ @@ -708,77 +719,89 @@ fn calculate_cto(span: &mut SpanBuilder) { Eq, Assert(0), // [cto, ...] ]; - span.push_ops(ops_group_2); + block_builder.push_ops(ops_group_2); } // COMPARISON OPERATIONS // ================================================================================================ -/// Translates u32lt assembly instructions to VM operations. +/// Translates u32lt assembly instruction to VM operations. /// -/// This operation takes 3 cycles. -pub fn u32lt(span_builder: &mut SpanBuilder) { - compute_lt(span_builder); +/// This operation takes: +/// - 3 cycles without immediate value. +/// - 4 cycles with immediate value. +pub fn u32lt(block_builder: &mut BasicBlockBuilder) { + compute_lt(block_builder); } -/// Translates u32lte assembly instructions to VM operations. +/// Translates u32lte assembly instruction to VM operations. /// -/// This operation takes 5 cycles. -pub fn u32lte(span_builder: &mut SpanBuilder) { +/// This operation takes: +/// - 5 cycles without immediate value. +/// - 6 cycles with immediate value. +pub fn u32lte(block_builder: &mut BasicBlockBuilder) { // Compute the lt with reversed number to get a gt check - span_builder.push_op(Swap); - compute_lt(span_builder); + block_builder.push_op(Swap); + compute_lt(block_builder); // Flip the final results to get the lte results. - span_builder.push_op(Not); + block_builder.push_op(Not); } -/// Translates u32gt assembly instructions to VM operations. +/// Translates u32gt assembly instruction to VM operations. /// -/// This operation takes 4 cycles. -pub fn u32gt(span_builder: &mut SpanBuilder) { +/// This operation takes: +/// - 4 cycles without immediate value. +/// - 5 cycles with immediate value. +pub fn u32gt(block_builder: &mut BasicBlockBuilder) { // Reverse the numbers so we can get a gt check. - span_builder.push_op(Swap); + block_builder.push_op(Swap); - compute_lt(span_builder); + compute_lt(block_builder); } -/// Translates u32gte assembly instructions to VM operations. +/// Translates u32gte assembly instruction to VM operations. /// -/// This operation takes 4 cycles. -pub fn u32gte(span_builder: &mut SpanBuilder) { - compute_lt(span_builder); +/// This operation takes: +/// - 4 cycles without immediate value. +/// - 5 cycles with immediate value. +pub fn u32gte(block_builder: &mut BasicBlockBuilder) { + compute_lt(block_builder); // Flip the final results to get the gte results. - span_builder.push_op(Not); + block_builder.push_op(Not); } -/// Translates u32min assembly instructions to VM operations. +/// Translates u32min assembly instruction to VM operations. /// /// Specifically, we subtract the top value from the second to the top value (U32SUB), check the /// underflow flag (EQZ), and perform a conditional swap (CSWAP) to have the max number in front. /// Then we finally drop the top element to keep the min. /// -/// This operation takes 8 cycles. -pub fn u32min(span_builder: &mut SpanBuilder) { - compute_max_and_min(span_builder); +/// This operation takes: +/// - 8 cycles without immediate value. +/// - 9 cycles with immediate value. +pub fn u32min(block_builder: &mut BasicBlockBuilder) { + compute_max_and_min(block_builder); // Drop the max and keep the min - span_builder.push_op(Drop); + block_builder.push_op(Drop); } -/// Translates u32max assembly instructions to VM operations. +/// Translates u32max assembly instruction to VM operations. /// /// Specifically, we subtract the top value from the second to the top value (U32SUB), check the /// underflow flag (EQZ), and perform a conditional swap (CSWAP) to have the max number in front. /// Then we finally drop the 2nd element to keep the max. /// -/// This operation takes 9 cycles. -pub fn u32max(span_builder: &mut SpanBuilder) { - compute_max_and_min(span_builder); +/// This operation takes: +/// - 9 cycles without immediate value. +/// - 10 cycles with immediate value. +pub fn u32max(block_builder: &mut BasicBlockBuilder) { + compute_max_and_min(block_builder); // Drop the min and keep the max - span_builder.push_ops([Swap, Drop]); + block_builder.push_ops([Swap, Drop]); } // COMPARISON OPERATIONS - HELPERS @@ -786,8 +809,8 @@ pub fn u32max(span_builder: &mut SpanBuilder) { /// Inserts the VM operations to check if the second element is less than /// the top element. This takes 3 cycles. -fn compute_lt(span_builder: &mut SpanBuilder) { - span_builder.push_ops([ +fn compute_lt(block_builder: &mut BasicBlockBuilder) { + block_builder.push_ops([ U32sub, Swap, Drop, // Perform the operations ]) } @@ -795,12 +818,12 @@ fn compute_lt(span_builder: &mut SpanBuilder) { /// Duplicate the top two elements in the stack and determine the min and max between them. /// /// The maximum number will be at the top of the stack and minimum will be at the 2nd index. -fn compute_max_and_min(span_builder: &mut SpanBuilder) { +fn compute_max_and_min(block_builder: &mut BasicBlockBuilder) { // Copy top two elements of the stack. - span_builder.push_ops([Dup1, Dup1]); + block_builder.push_ops([Dup1, Dup1]); #[rustfmt::skip] - span_builder.push_ops([ + block_builder.push_ops([ U32sub, Swap, Drop, // Check the underflow flag, if it's zero diff --git a/assembly/src/assembler/mast_forest_builder.rs b/assembly/src/assembler/mast_forest_builder.rs new file mode 100644 index 0000000000..dc320c9ca0 --- /dev/null +++ b/assembly/src/assembler/mast_forest_builder.rs @@ -0,0 +1,632 @@ +use alloc::{ + collections::{BTreeMap, BTreeSet}, + vec::Vec, +}; +use core::ops::{Index, IndexMut}; + +use vm_core::{ + crypto::hash::{Blake3Digest, Blake3_256, Digest, RpoDigest}, + mast::{DecoratorId, MastForest, MastNode, MastNodeId}, + Decorator, DecoratorList, Operation, +}; + +use super::{GlobalProcedureIndex, Procedure}; +use crate::AssemblyError; + +// CONSTANTS +// ================================================================================================ + +/// Constant that decides how many operation batches disqualify a procedure from inlining. +const PROCEDURE_INLINING_THRESHOLD: usize = 32; + +// MAST FOREST BUILDER +// ================================================================================================ + +/// Builder for a [`MastForest`]. +/// +/// The purpose of the builder is to ensure that the underlying MAST forest contains as little +/// information as possible needed to adequately describe the logical MAST forest. Specifically: +/// - The builder ensures that only one copy of nodes that have the same MAST root and decorators is +/// added to the MAST forest (i.e., two nodes that have the same MAST root and decorators will +/// have the same [`MastNodeId`]). +/// - The builder tries to merge adjacent basic blocks and eliminate the source block whenever this +/// does not have an impact on other nodes in the forest. +#[derive(Clone, Debug, Default)] +pub struct MastForestBuilder { + /// The MAST forest being built by this builder; this MAST forest is up-to-date - i.e., all + /// nodes added to the MAST forest builder are also immediately added to the underlying MAST + /// forest. + mast_forest: MastForest, + /// A map of all procedures added to the MAST forest indexed by their global procedure ID. + /// This includes all local, exported, and re-exported procedures. In case multiple procedures + /// with the same digest are added to the MAST forest builder, only the first procedure is + /// added to the map, and all subsequent insertions are ignored. + procedures: BTreeMap, + /// A map from procedure MAST root to its global procedure index. Similar to the `procedures` + /// map, this map contains only the first inserted procedure for procedures with the same MAST + /// root. + proc_gid_by_mast_root: BTreeMap, + /// A map of MAST node eq hashes to their corresponding positions in the MAST forest. + node_id_by_hash: BTreeMap, + /// The reverse mapping of `node_id_by_hash`. This map caches the eq hashes of all nodes (for + /// performance reasons). + hash_by_node_id: BTreeMap, + /// A map of decorator hashes to their corresponding positions in the MAST forest. + decorator_id_by_hash: BTreeMap, DecoratorId>, + /// A set of IDs for basic blocks which have been merged into a bigger basic blocks. This is + /// used as a candidate set of nodes that may be eliminated if the are not referenced by any + /// other node in the forest and are not a root of any procedure. + merged_basic_block_ids: BTreeSet, +} + +impl MastForestBuilder { + /// Removes the unused nodes that were created as part of the assembly process, and returns the + /// resulting MAST forest. + /// + /// It also returns the map from old node IDs to new node IDs; or `None` if the `MastForest` was + /// unchanged. Any [`MastNodeId`] used in reference to the old [`MastForest`] should be remapped + /// using this map. + pub fn build(mut self) -> (MastForest, Option>) { + let nodes_to_remove = get_nodes_to_remove(self.merged_basic_block_ids, &self.mast_forest); + let id_remappings = self.mast_forest.remove_nodes(&nodes_to_remove); + + (self.mast_forest, id_remappings) + } +} + +/// Takes the set of MAST node ids (all basic blocks) that were merged as part of the assembly +/// process (i.e. they were contiguous and were merged into a single basic block), and returns the +/// subset of nodes that can be removed from the MAST forest. +/// +/// Specifically, MAST node ids can be reused, so merging a basic block doesn't mean it should be +/// removed (specifically in the case where another node refers to it). Hence, we cycle through all +/// nodes of the forest and only mark for removal those nodes that are not referenced by any node. +/// We also ensure that procedure roots are not removed. +fn get_nodes_to_remove( + merged_node_ids: BTreeSet, + mast_forest: &MastForest, +) -> BTreeSet { + // make sure not to remove procedure roots + let mut nodes_to_remove: BTreeSet = merged_node_ids + .iter() + .filter(|&&mast_node_id| !mast_forest.is_procedure_root(mast_node_id)) + .copied() + .collect(); + + for node in mast_forest.nodes() { + match node { + MastNode::Join(node) => { + if nodes_to_remove.contains(&node.first()) { + nodes_to_remove.remove(&node.first()); + } + if nodes_to_remove.contains(&node.second()) { + nodes_to_remove.remove(&node.second()); + } + }, + MastNode::Split(node) => { + if nodes_to_remove.contains(&node.on_true()) { + nodes_to_remove.remove(&node.on_true()); + } + if nodes_to_remove.contains(&node.on_false()) { + nodes_to_remove.remove(&node.on_false()); + } + }, + MastNode::Loop(node) => { + if nodes_to_remove.contains(&node.body()) { + nodes_to_remove.remove(&node.body()); + } + }, + MastNode::Call(node) => { + if nodes_to_remove.contains(&node.callee()) { + nodes_to_remove.remove(&node.callee()); + } + }, + MastNode::Block(_) | MastNode::Dyn(_) | MastNode::External(_) => (), + } + } + + nodes_to_remove +} + +// ------------------------------------------------------------------------------------------------ +/// Public accessors +impl MastForestBuilder { + /// Returns a reference to the procedure with the specified [`GlobalProcedureIndex`], or None + /// if such a procedure is not present in this MAST forest builder. + #[inline(always)] + pub fn get_procedure(&self, gid: GlobalProcedureIndex) -> Option<&Procedure> { + self.procedures.get(&gid) + } + + /// Returns a reference to the procedure with the specified MAST root, or None + /// if such a procedure is not present in this MAST forest builder. + #[inline(always)] + pub fn find_procedure_by_mast_root(&self, mast_root: &RpoDigest) -> Option<&Procedure> { + self.proc_gid_by_mast_root + .get(mast_root) + .and_then(|gid| self.get_procedure(*gid)) + } + + /// Returns the [`MastNode`] for the provided MAST node ID, or None if a node with this ID is + /// not present in this MAST forest builder. + pub fn get_mast_node(&self, id: MastNodeId) -> Option<&MastNode> { + self.mast_forest.get_node_by_id(id) + } +} + +// ------------------------------------------------------------------------------------------------ +/// Procedure insertion +impl MastForestBuilder { + /// Inserts a procedure into this MAST forest builder. + /// + /// If the procedure with the same ID already exists in this forest builder, this will have + /// no effect. + pub fn insert_procedure( + &mut self, + gid: GlobalProcedureIndex, + procedure: Procedure, + ) -> Result<(), AssemblyError> { + // Check if an entry is already in this cache slot. + // + // If there is already a cache entry, but it conflicts with what we're trying to cache, + // then raise an error. + if self.procedures.contains_key(&gid) { + // The global procedure index and the MAST root resolve to an already cached version of + // this procedure, or an alias of it, nothing to do. + // + // TODO: We should emit a warning for this, because while it is not an error per se, it + // does reflect that we're doing work we don't need to be doing. However, emitting a + // warning only makes sense if this is controllable by the user, and it isn't yet + // clear whether this edge case will ever happen in practice anyway. + return Ok(()); + } + + // We don't have a cache entry yet, but we do want to make sure we don't have a conflicting + // cache entry with the same MAST root: + if let Some(cached) = self.find_procedure_by_mast_root(&procedure.mast_root()) { + // Handle the case where a procedure with no locals is lowered to a MastForest + // consisting only of an `External` node to another procedure which has one or more + // locals. This will result in the calling procedure having the same digest as the + // callee, but the two procedures having mismatched local counts. When this occurs, + // we want to use the procedure with non-zero local count as the definition, and treat + // the other procedure as an alias, which can be referenced like any other procedure, + // but the MAST returned for it will be that of the "real" definition. + let cached_locals = cached.num_locals(); + let procedure_locals = procedure.num_locals(); + let mismatched_locals = cached_locals != procedure_locals; + let is_valid = + !mismatched_locals || core::cmp::min(cached_locals, procedure_locals) == 0; + if !is_valid { + return Err(AssemblyError::ConflictingDefinitions { + first: cached.fully_qualified_name().clone(), + second: procedure.fully_qualified_name().clone(), + }); + } + } + + self.mast_forest.make_root(procedure.body_node_id()); + self.proc_gid_by_mast_root.insert(procedure.mast_root(), gid); + self.procedures.insert(gid, procedure); + + Ok(()) + } +} + +// ------------------------------------------------------------------------------------------------ +/// Joining nodes +impl MastForestBuilder { + /// Builds a tree of `JOIN` operations to combine the provided MAST node IDs. + pub fn join_nodes(&mut self, node_ids: Vec) -> Result { + debug_assert!(!node_ids.is_empty(), "cannot combine empty MAST node id list"); + + let mut node_ids = self.merge_contiguous_basic_blocks(node_ids)?; + + // build a binary tree of blocks joining them using JOIN blocks + while node_ids.len() > 1 { + let last_mast_node_id = if node_ids.len() % 2 == 0 { None } else { node_ids.pop() }; + + let mut source_node_ids = Vec::new(); + core::mem::swap(&mut node_ids, &mut source_node_ids); + + let mut source_mast_node_iter = source_node_ids.drain(0..); + while let (Some(left), Some(right)) = + (source_mast_node_iter.next(), source_mast_node_iter.next()) + { + let join_mast_node_id = self.ensure_join(left, right)?; + + node_ids.push(join_mast_node_id); + } + if let Some(mast_node_id) = last_mast_node_id { + node_ids.push(mast_node_id); + } + } + + Ok(node_ids.remove(0)) + } + + /// Returns a list of [`MastNodeId`]s built from merging the contiguous basic blocks + /// found in the provided list of [`MastNodeId`]s. + fn merge_contiguous_basic_blocks( + &mut self, + node_ids: Vec, + ) -> Result, AssemblyError> { + let mut merged_node_ids = Vec::with_capacity(node_ids.len()); + let mut contiguous_basic_block_ids: Vec = Vec::new(); + + for mast_node_id in node_ids { + if self.mast_forest[mast_node_id].is_basic_block() { + contiguous_basic_block_ids.push(mast_node_id); + } else { + merged_node_ids.extend(self.merge_basic_blocks(&contiguous_basic_block_ids)?); + contiguous_basic_block_ids.clear(); + + merged_node_ids.push(mast_node_id); + } + } + + merged_node_ids.extend(self.merge_basic_blocks(&contiguous_basic_block_ids)?); + + Ok(merged_node_ids) + } + + /// Creates a new basic block by appending all operations and decorators in the provided list of + /// basic blocks (which are assumed to be contiguous). + /// + /// # Panics + /// - Panics if a provided [`MastNodeId`] doesn't refer to a basic block node. + fn merge_basic_blocks( + &mut self, + contiguous_basic_block_ids: &[MastNodeId], + ) -> Result, AssemblyError> { + if contiguous_basic_block_ids.is_empty() { + return Ok(Vec::new()); + } + if contiguous_basic_block_ids.len() == 1 { + return Ok(contiguous_basic_block_ids.to_vec()); + } + + let mut operations: Vec = Vec::new(); + let mut decorators = DecoratorList::new(); + + let mut merged_basic_blocks: Vec = Vec::new(); + + for &basic_block_id in contiguous_basic_block_ids { + // It is safe to unwrap here, since we already checked that all IDs in + // `contiguous_basic_block_ids` are `BasicBlockNode`s + let basic_block_node = + self.mast_forest[basic_block_id].get_basic_block().unwrap().clone(); + + // check if the block should be merged with other blocks + if should_merge( + self.mast_forest.is_procedure_root(basic_block_id), + basic_block_node.num_op_batches(), + ) { + for &(op_idx, decorator) in basic_block_node.decorators() { + decorators.push((op_idx + operations.len(), decorator)); + } + for batch in basic_block_node.op_batches() { + operations.extend_from_slice(batch.ops()); + } + } else { + // if we don't want to merge this block, we flush the buffer of operations into a + // new block, and add the un-merged block after it + if !operations.is_empty() { + let block_ops = core::mem::take(&mut operations); + let block_decorators = core::mem::take(&mut decorators); + let merged_basic_block_id = + self.ensure_block(block_ops, Some(block_decorators))?; + + merged_basic_blocks.push(merged_basic_block_id); + } + merged_basic_blocks.push(basic_block_id); + } + } + + // Mark the removed basic blocks as merged + self.merged_basic_block_ids.extend(contiguous_basic_block_ids.iter()); + + if !operations.is_empty() || !decorators.is_empty() { + let merged_basic_block = self.ensure_block(operations, Some(decorators))?; + merged_basic_blocks.push(merged_basic_block); + } + + Ok(merged_basic_blocks) + } +} + +// ------------------------------------------------------------------------------------------------ +/// Node inserters +impl MastForestBuilder { + /// Adds a decorator to the forest, and returns the [`Decorator`] associated with it. + pub fn ensure_decorator(&mut self, decorator: Decorator) -> Result { + let decorator_hash = decorator.eq_hash(); + + if let Some(decorator_id) = self.decorator_id_by_hash.get(&decorator_hash) { + // decorator already exists in the forest; return previously assigned id + Ok(*decorator_id) + } else { + let new_decorator_id = self.mast_forest.add_decorator(decorator)?; + self.decorator_id_by_hash.insert(decorator_hash, new_decorator_id); + + Ok(new_decorator_id) + } + } + + /// Adds a node to the forest, and returns the [`MastNodeId`] associated with it. + /// + /// Note that only one copy of nodes that have the same MAST root and decorators is added to the + /// MAST forest; two nodes that have the same MAST root and decorators will have the same + /// [`MastNodeId`]. + pub fn ensure_node(&mut self, node: MastNode) -> Result { + let node_hash = self.eq_hash_for_node(&node); + + if let Some(node_id) = self.node_id_by_hash.get(&node_hash) { + // node already exists in the forest; return previously assigned id + Ok(*node_id) + } else { + let new_node_id = self.mast_forest.add_node(node)?; + self.node_id_by_hash.insert(node_hash, new_node_id); + self.hash_by_node_id.insert(new_node_id, node_hash); + + Ok(new_node_id) + } + } + + /// Adds a basic block node to the forest, and returns the [`MastNodeId`] associated with it. + pub fn ensure_block( + &mut self, + operations: Vec, + decorators: Option, + ) -> Result { + let block = MastNode::new_basic_block(operations, decorators)?; + self.ensure_node(block) + } + + /// Adds a join node to the forest, and returns the [`MastNodeId`] associated with it. + pub fn ensure_join( + &mut self, + left_child: MastNodeId, + right_child: MastNodeId, + ) -> Result { + let join = MastNode::new_join(left_child, right_child, &self.mast_forest)?; + self.ensure_node(join) + } + + /// Adds a split node to the forest, and returns the [`MastNodeId`] associated with it. + pub fn ensure_split( + &mut self, + if_branch: MastNodeId, + else_branch: MastNodeId, + ) -> Result { + let split = MastNode::new_split(if_branch, else_branch, &self.mast_forest)?; + self.ensure_node(split) + } + + /// Adds a loop node to the forest, and returns the [`MastNodeId`] associated with it. + pub fn ensure_loop(&mut self, body: MastNodeId) -> Result { + let loop_node = MastNode::new_loop(body, &self.mast_forest)?; + self.ensure_node(loop_node) + } + + /// Adds a call node to the forest, and returns the [`MastNodeId`] associated with it. + pub fn ensure_call(&mut self, callee: MastNodeId) -> Result { + let call = MastNode::new_call(callee, &self.mast_forest)?; + self.ensure_node(call) + } + + /// Adds a syscall node to the forest, and returns the [`MastNodeId`] associated with it. + pub fn ensure_syscall(&mut self, callee: MastNodeId) -> Result { + let syscall = MastNode::new_syscall(callee, &self.mast_forest)?; + self.ensure_node(syscall) + } + + /// Adds a dyn node to the forest, and returns the [`MastNodeId`] associated with it. + pub fn ensure_dyn(&mut self) -> Result { + self.ensure_node(MastNode::new_dyn()) + } + + /// Adds an external node to the forest, and returns the [`MastNodeId`] associated with it. + pub fn ensure_external(&mut self, mast_root: RpoDigest) -> Result { + self.ensure_node(MastNode::new_external(mast_root)) + } + + pub fn set_before_enter(&mut self, node_id: MastNodeId, decorator_ids: Vec) { + self.mast_forest[node_id].set_before_enter(decorator_ids); + + let new_node_hash = self.eq_hash_for_node(&self[node_id]); + self.hash_by_node_id.insert(node_id, new_node_hash); + } + + pub fn set_after_exit(&mut self, node_id: MastNodeId, decorator_ids: Vec) { + self.mast_forest[node_id].set_after_exit(decorator_ids); + + let new_node_hash = self.eq_hash_for_node(&self[node_id]); + self.hash_by_node_id.insert(node_id, new_node_hash); + } +} + +/// Helpers +impl MastForestBuilder { + fn eq_hash_for_node(&self, node: &MastNode) -> EqHash { + match node { + MastNode::Block(node) => { + let mut bytes_to_hash = Vec::new(); + + for &(idx, decorator_id) in node.decorators() { + bytes_to_hash.extend(idx.to_le_bytes()); + bytes_to_hash.extend(self[decorator_id].eq_hash().as_bytes()); + } + + // Add any `Assert` or `U32assert2` opcodes present, since these are not included in + // the MAST root. + for (op_idx, op) in node.operations().enumerate() { + if let Operation::U32assert2(inner_value) + | Operation::Assert(inner_value) + | Operation::MpVerify(inner_value) = op + { + let op_idx: u32 = op_idx + .try_into() + .expect("there are more than 2^{32}-1 operations in basic block"); + + // we include the opcode to differentiate between `Assert` and `U32assert2` + bytes_to_hash.push(op.op_code()); + // we include the operation index to distinguish between basic blocks that + // would have the same assert instructions, but in a different order + bytes_to_hash.extend(op_idx.to_le_bytes()); + bytes_to_hash.extend(inner_value.to_le_bytes()); + } + } + + if bytes_to_hash.is_empty() { + EqHash::new(node.digest()) + } else { + let decorator_root = Blake3_256::hash(&bytes_to_hash); + EqHash::with_decorator_root(node.digest(), decorator_root) + } + }, + MastNode::Join(node) => self.eq_hash_from_parts( + node.before_enter(), + node.after_exit(), + &[node.first(), node.second()], + node.digest(), + ), + MastNode::Split(node) => self.eq_hash_from_parts( + node.before_enter(), + node.after_exit(), + &[node.on_true(), node.on_false()], + node.digest(), + ), + MastNode::Loop(node) => self.eq_hash_from_parts( + node.before_enter(), + node.after_exit(), + &[node.body()], + node.digest(), + ), + MastNode::Call(node) => self.eq_hash_from_parts( + node.before_enter(), + node.after_exit(), + &[node.callee()], + node.digest(), + ), + MastNode::Dyn(node) => { + self.eq_hash_from_parts(node.before_enter(), node.after_exit(), &[], node.digest()) + }, + MastNode::External(node) => { + self.eq_hash_from_parts(node.before_enter(), node.after_exit(), &[], node.digest()) + }, + } + } + + fn eq_hash_from_parts( + &self, + before_enter_ids: &[DecoratorId], + after_exit_ids: &[DecoratorId], + children_ids: &[MastNodeId], + node_digest: RpoDigest, + ) -> EqHash { + let pre_decorator_hash_bytes = + before_enter_ids.iter().flat_map(|&id| self[id].eq_hash().as_bytes()); + let post_decorator_hash_bytes = + after_exit_ids.iter().flat_map(|&id| self[id].eq_hash().as_bytes()); + + // Reminder: the `EqHash`'s decorator root will be `None` if and only if there are no + // decorators attached to the node, and all children have no decorator roots (meaning that + // there are no decorators in all the descendants). + if pre_decorator_hash_bytes.clone().next().is_none() + && post_decorator_hash_bytes.clone().next().is_none() + && children_ids + .iter() + .filter_map(|child_id| self.hash_by_node_id[child_id].decorator_root) + .next() + .is_none() + { + EqHash::new(node_digest) + } else { + let children_decorator_roots = children_ids + .iter() + .filter_map(|child_id| self.hash_by_node_id[child_id].decorator_root) + .flat_map(|decorator_root| decorator_root.as_bytes()); + let decorator_bytes_to_hash: Vec = pre_decorator_hash_bytes + .chain(post_decorator_hash_bytes) + .chain(children_decorator_roots) + .collect(); + + let decorator_root = Blake3_256::hash(&decorator_bytes_to_hash); + EqHash::with_decorator_root(node_digest, decorator_root) + } + } +} + +impl Index for MastForestBuilder { + type Output = MastNode; + + #[inline(always)] + fn index(&self, node_id: MastNodeId) -> &Self::Output { + &self.mast_forest[node_id] + } +} + +impl Index for MastForestBuilder { + type Output = Decorator; + + #[inline(always)] + fn index(&self, decorator_id: DecoratorId) -> &Self::Output { + &self.mast_forest[decorator_id] + } +} + +impl IndexMut for MastForestBuilder { + #[inline(always)] + fn index_mut(&mut self, decorator_id: DecoratorId) -> &mut Self::Output { + &mut self.mast_forest[decorator_id] + } +} + +// EQ HASH +// ================================================================================================ + +/// Represents the hash used to test for equality between [`MastNode`]s. +/// +/// The decorator root will be `None` if and only if there are no decorators attached to the node, +/// and all children have no decorator roots (meaning that there are no decorators in all the +/// descendants). +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +struct EqHash { + mast_root: RpoDigest, + decorator_root: Option>, +} + +impl EqHash { + fn new(mast_root: RpoDigest) -> Self { + Self { mast_root, decorator_root: None } + } + + fn with_decorator_root(mast_root: RpoDigest, decorator_root: Blake3Digest<32>) -> Self { + Self { + mast_root, + decorator_root: Some(decorator_root), + } + } +} + +// HELPER FUNCTIONS +// ================================================================================================ + +/// Determines if we want to merge a block with other blocks. Currently, this works as follows: +/// - If the block is a procedure, we merge it only if the number of operation batches is smaller +/// then the threshold (currently set at 32). The reasoning is based on an estimate of the the +/// runtime penalty of not inlining the procedure. We assume that this penalty is roughly 3 extra +/// nodes in the MAST and so would require 3 additional hashes at runtime. Since hashing each +/// operation batch requires 1 hash, this basically implies that if the runtime penalty is more +/// than 10%, we inline the block, but if it is less than 10% we accept the penalty to make +/// deserialization faster. +/// - If the block is not a procedure, we always merge it because: (1) if it is a large block, it is +/// likely to be unique and, thus, the original block will be orphaned and removed later; (2) if +/// it is a small block, there is a large run-time benefit for inlining it. +fn should_merge(is_procedure: bool, num_op_batches: usize) -> bool { + if is_procedure { + num_op_batches < PROCEDURE_INLINING_THRESHOLD + } else { + true + } +} diff --git a/assembly/src/assembler/mod.rs b/assembly/src/assembler/mod.rs index 09d21b2f76..6575056e9b 100644 --- a/assembly/src/assembler/mod.rs +++ b/assembly/src/assembler/mod.rs @@ -1,62 +1,41 @@ +use alloc::{collections::BTreeMap, sync::Arc, vec::Vec}; + +use basic_block_builder::BasicBlockOrDecorators; +use mast_forest_builder::MastForestBuilder; +use module_graph::{ProcedureWrapper, WrappedModule}; +use vm_core::{ + crypto::hash::RpoDigest, + debuginfo::SourceSpan, + mast::{DecoratorId, MastNodeId}, + DecoratorList, Felt, Kernel, Operation, Program, +}; + use crate::{ - ast::{ - self, Export, FullyQualifiedProcedureName, Instruction, InvocationTarget, InvokeKind, - ModuleKind, ProcedureIndex, - }, - diagnostics::{tracing::instrument, Report}, + ast::{self, Export, InvocationTarget, InvokeKind, ModuleKind, QualifiedProcedureName}, + diagnostics::Report, + library::{KernelLibrary, Library}, sema::SemanticAnalysisError, - AssemblyError, Compile, CompileOptions, Felt, Library, LibraryNamespace, LibraryPath, - RpoDigest, Spanned, ONE, ZERO, -}; -use alloc::{boxed::Box, sync::Arc, vec::Vec}; -use vm_core::{ - code_blocks::CodeBlock, CodeBlockTable, Decorator, DecoratorList, Kernel, Operation, Program, + AssemblyError, Compile, CompileOptions, LibraryNamespace, LibraryPath, SourceManager, Spanned, }; -mod context; +mod basic_block_builder; mod id; mod instruction; +mod mast_forest_builder; mod module_graph; mod procedure; -mod span_builder; + #[cfg(test)] mod tests; -pub use self::context::AssemblyContext; -pub use self::id::{GlobalProcedureIndex, ModuleIndex}; -pub(crate) use self::module_graph::ProcedureCache; -pub use self::procedure::Procedure; - -use self::context::ProcedureContext; -use self::module_graph::{CallerInfo, ModuleGraph, ResolvedTarget}; -use self::span_builder::SpanBuilder; - -// ARTIFACT KIND -// ================================================================================================ - -/// Represents the type of artifact produced by an [Assembler]. -#[derive(Default, Copy, Clone, PartialEq, Eq, Hash)] -#[non_exhaustive] -pub enum ArtifactKind { - /// Produce an executable program. - /// - /// This is the default artifact produced by the assembler, and is the only artifact that is - /// useful on its own. - #[default] - Executable, - /// Produce a MAST library - /// - /// The assembler will produce MAST in binary form which can be packaged and distributed. - /// These artifacts can then be loaded by the VM with an executable program that references - /// the contents of the library, without having to compile them together. - Library, - /// Produce a MAST kernel module - /// - /// The assembler will produce MAST for a kernel module, which is essentially the same as - /// [crate::Library], however additional constraints are imposed on compilation to ensure that - /// the produced kernel is valid. - Kernel, -} +use self::{ + basic_block_builder::BasicBlockBuilder, + module_graph::{CallerInfo, ModuleGraph, ResolvedTarget}, +}; +pub use self::{ + id::{GlobalProcedureIndex, ModuleIndex}, + procedure::{Procedure, ProcedureContext}, +}; // ASSEMBLER // ================================================================================================ @@ -69,72 +48,64 @@ pub enum ArtifactKind { /// Depending on your needs, there are multiple ways of using the assembler, and whether or not you /// want to provide a custom kernel. /// -/// By default, an empty kernel is provided. However, you may provide your own using -/// [Assembler::with_kernel] or [Assembler::with_kernel_from_source]. -/// ///
/// Programs compiled with an empty kernel cannot use the `syscall` instruction. ///
/// -/// * If you have a single executable module you want to compile, just call [Assembler::compile] or -/// [Assembler::compile_ast], depending on whether you have source code in raw or parsed form. -/// +/// * If you have a single executable module you want to compile, just call +/// [Assembler::assemble_program]. /// * If you want to link your executable to a few other modules that implement supporting /// procedures, build the assembler with them first, using the various builder methods on /// [Assembler], e.g. [Assembler::with_module], [Assembler::with_library], etc. Then, call -/// [Assembler::compile] or [Assembler::compile_ast] to get your compiled program. +/// [Assembler::assemble_program] to get your compiled program. +#[derive(Clone)] pub struct Assembler { - /// The global [ModuleGraph] for this assembler. All new [AssemblyContext]s inherit this graph - /// as a baseline. - module_graph: Box, - /// The global procedure cache for this assembler. - procedure_cache: ProcedureCache, + /// The source manager to use for compilation and source location information + source_manager: Arc, + /// The global [ModuleGraph] for this assembler. + module_graph: ModuleGraph, /// Whether to treat warning diagnostics as errors warnings_as_errors: bool, /// Whether the assembler enables extra debugging information. in_debug_mode: bool, - /// Whether the assembler allows unknown invocation targets in compiled code. - allow_phantom_calls: bool, } impl Default for Assembler { fn default() -> Self { + let source_manager = Arc::new(crate::DefaultSourceManager::default()); + let module_graph = ModuleGraph::new(source_manager.clone()); Self { - module_graph: Default::default(), - procedure_cache: Default::default(), + source_manager, + module_graph, warnings_as_errors: false, in_debug_mode: false, - allow_phantom_calls: true, } } } -/// Builder +// ------------------------------------------------------------------------------------------------ +/// Constructors impl Assembler { /// Start building an [Assembler] - pub fn new() -> Self { - Self::default() - } - - /// Start building an [Assembler] with the given [Kernel]. - pub fn with_kernel(kernel: Kernel) -> Self { - let mut assembler = Self::new(); - assembler.module_graph.set_kernel(None, kernel); - assembler + pub fn new(source_manager: Arc) -> Self { + let module_graph = ModuleGraph::new(source_manager.clone()); + Self { + source_manager, + module_graph, + warnings_as_errors: false, + in_debug_mode: false, + } } - /// Start building an [Assembler], with a kernel given by compiling the given source module. - /// - /// # Errors - /// Returns an error if compiling kernel source results in an error. - pub fn with_kernel_from_module(module: impl Compile) -> Result { - let mut assembler = Self::new(); - let opts = CompileOptions::for_kernel(); - let module = module.compile_with_options(opts)?; - let (kernel_index, kernel) = assembler.assemble_kernel_module(module)?; - assembler.module_graph.set_kernel(Some(kernel_index), kernel); - - Ok(assembler) + /// Start building an [`Assembler`] with a kernel defined by the provided [KernelLibrary]. + pub fn with_kernel(source_manager: Arc, kernel_lib: KernelLibrary) -> Self { + let (kernel, kernel_module, _) = kernel_lib.into_parts(); + let module_graph = ModuleGraph::with_kernel(source_manager.clone(), kernel, kernel_module); + Self { + source_manager, + module_graph, + ..Default::default() + } } /// Sets the default behavior of this assembler with regard to warning diagnostics. @@ -151,10 +122,9 @@ impl Assembler { self } - /// Sets whether to allow phantom calls. - pub fn with_phantom_calls(mut self, yes: bool) -> Self { - self.allow_phantom_calls = yes; - self + /// Sets the debug mode flag of the assembler + pub fn set_debug_mode(&mut self, yes: bool) { + self.in_debug_mode = yes; } /// Adds `module` to the module graph of the assembler. @@ -204,68 +174,72 @@ impl Assembler { )); } - let module = module.compile_with_options(options)?; + let module = module.compile_with_options(&self.source_manager, options)?; assert_eq!(module.kind(), kind, "expected module kind to match compilation options"); - self.module_graph.add_module(module)?; + self.module_graph.add_ast_module(module)?; Ok(()) } - /// Adds the library to provide modules for the compilation. - pub fn with_library(mut self, library: &L) -> Result - where - L: ?Sized + Library + 'static, - { - self.add_library(library)?; + /// Adds all modules (defined by ".masm" files) from the specified directory to the module + /// of this assembler graph. + /// + /// The modules will be added under the specified namespace, but otherwise preserving the + /// structure of the directory. Any module named `mod.masm` will be added using parent + /// directory path For example, if `namespace` = "ns", modules from the ~/masm directory + /// will be added as follows: + /// + /// - ~/masm/foo.masm -> "ns::foo" + /// - ~/masm/bar/mod.masm -> "ns::bar" + /// - ~/masm/bar/baz.masm -> "ns::bar::baz" + #[cfg(feature = "std")] + pub fn add_modules_from_dir( + &mut self, + namespace: crate::LibraryNamespace, + dir: &std::path::Path, + ) -> Result<(), Report> { + for module in crate::parser::read_modules_from_dir(namespace, dir, &self.source_manager)? { + self.module_graph.add_ast_module(module)?; + } - Ok(self) + Ok(()) } - /// Adds the library to provide modules for the compilation. - pub fn add_library(&mut self, library: &L) -> Result<(), Report> - where - L: ?Sized + Library + 'static, - { - let namespace = library.root_ns(); - library.modules().try_for_each(|module| { - if !module.is_in_namespace(namespace) { - return Err(Report::new(AssemblyError::InconsistentNamespace { - expected: namespace.clone(), - actual: module.namespace().clone(), - })); - } - - self.add_module(module)?; - - Ok(()) - }) + /// Adds the compiled library to provide modules for the compilation. + /// + /// We only current support adding non-vendored libraries - that is, the source code of exported + /// procedures is not included in the program that compiles against the library. The library's + /// source code is instead expected to be loaded in the processor at execution time. Hence, all + /// calls to library procedures will be compiled down to a [`vm_core::mast::ExternalNode`] (i.e. + /// a reference to the procedure's MAST root). This means that when executing a program compiled + /// against a library, the processor will not be able to differentiate procedures with the same + /// MAST root but different decorators. + /// + /// Hence, it is not recommended to export two procedures that have the same MAST root (i.e. are + /// identical except for their decorators). Note however that we don't expect this scenario to + /// be frequent in practice. For example, this could occur when APIs are being renamed and/or + /// moved between modules, and for some deprecation period, the same is exported under both its + /// old and new paths. Or possibly with common small functions that are implemented by the main + /// program and one of its dependencies. + pub fn add_library(&mut self, library: impl AsRef) -> Result<(), Report> { + self.module_graph + .add_compiled_modules(library.as_ref().module_infos()) + .map_err(Report::from)?; + Ok(()) } - /// Adds a library bundle to provide modules for the compilation. - pub fn with_libraries<'a, I, L>(mut self, libraries: I) -> Result - where - L: ?Sized + Library + 'static, - I: IntoIterator, - { - self.add_libraries(libraries)?; + /// Adds the compiled library to provide modules for the compilation. + /// + /// See [`Self::add_library`] for more detailed information. + pub fn with_library(mut self, library: impl AsRef) -> Result { + self.add_library(library)?; Ok(self) } - - /// Adds a library bundle to provide modules for the compilation. - pub fn add_libraries<'a, I, L>(&mut self, libraries: I) -> Result<(), Report> - where - L: ?Sized + Library + 'static, - I: IntoIterator, - { - for library in libraries { - self.add_library(library)?; - } - Ok(()) - } } -/// Queries +// ------------------------------------------------------------------------------------------------ +/// Public Accessors impl Assembler { /// Returns true if this assembler promotes warning diagnostics as errors by default. pub fn warnings_as_errors(&self) -> bool { @@ -284,15 +258,9 @@ impl Assembler { self.module_graph.kernel() } - /// Returns true if this assembler was instantiated with phantom calls enabled. - pub fn allow_phantom_calls(&self) -> bool { - self.allow_phantom_calls - } - - #[cfg(any(test, feature = "testing"))] - #[doc(hidden)] - pub fn procedure_cache(&self) -> &ProcedureCache { - &self.procedure_cache + /// Returns a link to the source manager used by this assembler. + pub fn source_manager(&self) -> Arc { + self.source_manager.clone() } #[cfg(any(test, feature = "testing"))] @@ -302,365 +270,305 @@ impl Assembler { } } +// ------------------------------------------------------------------------------------------------ /// Compilation/Assembly impl Assembler { - /// Compiles the provided module into a [Program]. The resulting program can be executed - /// on Miden VM. + /// Assembles a set of modules into a [Library]. /// /// # Errors /// - /// Returns an error if parsing or compilation of the specified program fails. - pub fn assemble(&mut self, source: impl Compile) -> Result { - let mut context = AssemblyContext::default(); - context.set_warnings_as_errors(self.warnings_as_errors); - - self.assemble_in_context(source, &mut context) - } + /// Returns an error if parsing or compilation of the specified modules fails. + pub fn assemble_library( + mut self, + modules: impl IntoIterator, + ) -> Result { + let ast_module_indices = + modules.into_iter().try_fold(Vec::default(), |mut acc, module| { + module + .compile_with_options(&self.source_manager, CompileOptions::for_library()) + .and_then(|module| { + self.module_graph.add_ast_module(module).map_err(Report::from) + }) + .map(move |module_id| { + acc.push(module_id); + acc + }) + })?; - /// Like [Assembler::compile], but also takes an [AssemblyContext] to configure the assembler. - pub fn assemble_in_context( - &mut self, - source: impl Compile, - context: &mut AssemblyContext, - ) -> Result { - let opts = CompileOptions { - warnings_as_errors: context.warnings_as_errors(), - ..CompileOptions::default() - }; - self.assemble_with_options_in_context(source, opts, context) - } + self.module_graph.recompute()?; - /// Compiles the provided module into a [Program] using the provided options. - /// - /// The resulting program can be executed on Miden VM. - /// - /// # Errors - /// - /// Returns an error if parsing or compilation of the specified program fails, or the options - /// are invalid. - pub fn assemble_with_options( - &mut self, - source: impl Compile, - options: CompileOptions, - ) -> Result { - let mut context = AssemblyContext::default(); - context.set_warnings_as_errors(options.warnings_as_errors); + let mut mast_forest_builder = MastForestBuilder::default(); - self.assemble_with_options_in_context(source, options, &mut context) - } + let mut exports = { + let mut exports = BTreeMap::new(); - /// Like [Assembler::compile_with_opts], but additionally uses the provided [AssemblyContext] - /// to configure the assembler. - #[instrument("assemble_with_opts_in_context", skip_all)] - pub fn assemble_with_options_in_context( - &mut self, - source: impl Compile, - options: CompileOptions, - context: &mut AssemblyContext, - ) -> Result { - if options.kind != ModuleKind::Executable { - return Err(Report::msg( - "invalid compile options: assemble_with_opts_in_context requires that the kind be 'executable'", - )); - } + for module_idx in ast_module_indices { + // Note: it is safe to use `unwrap_ast()` here, since all of the modules contained + // in `ast_module_indices` are in AST form by definition. + let ast_module = self.module_graph[module_idx].unwrap_ast().clone(); - let program = source.compile_with_options(CompileOptions { - // Override the module name so that we always compile the executable - // module as #exec - path: Some(LibraryPath::from(LibraryNamespace::Exec)), - ..options - })?; - assert!(program.is_executable()); + for (proc_idx, fqn) in ast_module.exported_procedures() { + let gid = module_idx + proc_idx; + self.compile_subgraph(gid, &mut mast_forest_builder)?; - // Remove any previously compiled executable module and clean up graph - let prev_program = self.module_graph.find_module_index(program.path()); - if let Some(module_index) = prev_program { - self.module_graph.remove_module(module_index); - self.procedure_cache.remove_module(module_index); - } + let proc_root_node_id = mast_forest_builder + .get_procedure(gid) + .expect("compilation succeeded but root not found in cache") + .body_node_id(); + exports.insert(fqn, proc_root_node_id); + } + } - // Recompute graph with executable module, and start compiling - let module_index = self.module_graph.add_module(program)?; - self.module_graph.recompute()?; + exports + }; - // Find the executable entrypoint - let entrypoint = self.module_graph[module_index] - .index_of(|p| p.is_main()) - .map(|index| GlobalProcedureIndex { - module: module_index, - index, - }) - .ok_or(SemanticAnalysisError::MissingEntrypoint)?; + // TODO: show a warning if library exports are empty? + let (mast_forest, id_remappings) = mast_forest_builder.build(); + if let Some(id_remappings) = id_remappings { + for (_proc_name, node_id) in exports.iter_mut() { + if let Some(&new_node_id) = id_remappings.get(node_id) { + *node_id = new_node_id; + } + } + } - self.compile_program(entrypoint, context) + Ok(Library::new(mast_forest.into(), exports)?) } - /// Compile and assembles all procedures in the specified module, adding them to the procedure - /// cache. + /// Assembles the provided module into a [KernelLibrary] intended to be used as a Kernel. /// - /// Returns a vector of procedure digests for all exported procedures in the module. + /// # Errors /// - /// The provided context is used to determine what type of module to assemble, i.e. either - /// a kernel or library module. - pub fn assemble_module( - &mut self, - module: impl Compile, - options: CompileOptions, - context: &mut AssemblyContext, - ) -> Result, Report> { - match context.kind() { - _ if options.kind.is_executable() => { - return Err(Report::msg( - "invalid compile options: expected configuration for library or kernel module ", - )) - } - ArtifactKind::Executable => { - return Err(Report::msg( - "invalid context: expected context configured for library or kernel modules", - )) - } - ArtifactKind::Kernel if !options.kind.is_kernel() => { - return Err(Report::msg( - "invalid context: cannot assemble a kernel from a module compiled as a library", - )) - } - ArtifactKind::Library if !options.kind.is_library() => { - return Err(Report::msg( - "invalid context: cannot assemble a library from a module compiled as a kernel", - )) - } - ArtifactKind::Kernel | ArtifactKind::Library => (), - } + /// Returns an error if parsing or compilation of the specified modules fails. + pub fn assemble_kernel(mut self, module: impl Compile) -> Result { + let options = CompileOptions { + kind: ModuleKind::Kernel, + warnings_as_errors: self.warnings_as_errors, + path: Some(LibraryPath::from(LibraryNamespace::Kernel)), + }; - // Compile module - let module = module.compile_with_options(options)?; + let module = module.compile_with_options(&self.source_manager, options)?; + let module_idx = self.module_graph.add_ast_module(module)?; - // Recompute graph with the provided module, and start assembly - let module_id = self.module_graph.add_module(module)?; self.module_graph.recompute()?; - self.assemble_graph(context)?; - self.get_module_exports(module_id) - } + let mut mast_forest_builder = MastForestBuilder::default(); - /// Compiles the given kernel module, returning both the compiled kernel and its index in the - /// graph. - fn assemble_kernel_module( - &mut self, - module: Box, - ) -> Result<(ModuleIndex, Kernel), Report> { - if !module.is_kernel() { - return Err(Report::msg(format!("expected kernel module, got {}", module.kind()))); - } + // Note: it is safe to use `unwrap_ast()` here, since all modules looped over are + // AST (we just added them to the module graph) + let ast_module = self.module_graph[module_idx].unwrap_ast().clone(); - let mut context = AssemblyContext::for_kernel(module.path()); - context.set_warnings_as_errors(self.warnings_as_errors); + let mut exports = ast_module + .exported_procedures() + .map(|(proc_idx, fqn)| { + let gid = module_idx + proc_idx; + self.compile_subgraph(gid, &mut mast_forest_builder)?; - let kernel_index = self.module_graph.add_module(module)?; - self.module_graph.recompute()?; - let kernel_module = self.module_graph[kernel_index].clone(); - let mut kernel = Vec::new(); - for (index, _syscall) in kernel_module - .procedures() - .enumerate() - .filter(|(_, p)| p.visibility().is_syscall()) - { - let gid = GlobalProcedureIndex { - module: kernel_index, - index: ProcedureIndex::new(index), - }; - let compiled = self.compile_subgraph(gid, false, &mut context)?; - kernel.push(compiled.code().hash()); + let proc_root_node_id = mast_forest_builder + .get_procedure(gid) + .expect("compilation succeeded but root not found in cache") + .body_node_id(); + Ok((fqn, proc_root_node_id)) + }) + .collect::, Report>>()?; + + // TODO: show a warning if library exports are empty? + let (mast_forest, id_remappings) = mast_forest_builder.build(); + if let Some(id_remappings) = id_remappings { + for (_proc_name, node_id) in exports.iter_mut() { + if let Some(&new_node_id) = id_remappings.get(node_id) { + *node_id = new_node_id; + } + } } - - Kernel::new(&kernel) - .map(|kernel| (kernel_index, kernel)) - .map_err(|err| Report::new(AssemblyError::Kernel(err))) + let library = Library::new(mast_forest.into(), exports)?; + Ok(library.try_into()?) } - /// Get the set of procedure roots for all exports of the given module + /// Compiles the provided module into a [`Program`]. The resulting program can be executed on + /// Miden VM. /// - /// Returns an error if the provided Miden Assembly is invalid. - fn get_module_exports(&mut self, module: ModuleIndex) -> Result, Report> { - assert!(self.module_graph.contains_module(module), "invalid module index"); - - let mut exports = Vec::new(); - for (index, procedure) in self.module_graph[module].procedures().enumerate() { - // Only add exports to the code block table, locals will - // be added if they are in the call graph rooted at those - // procedures - if !procedure.visibility().is_exported() { - continue; - } - let gid = match procedure { - Export::Procedure(_) => GlobalProcedureIndex { - module, - index: ProcedureIndex::new(index), - }, - Export::Alias(ref alias) => { - self.module_graph.find(alias.source_file(), alias.target())? - } - }; - let proc = self.procedure_cache.get(gid).unwrap_or_else(|| match procedure { - Export::Procedure(ref proc) => { - panic!( - "compilation apparently succeeded, but did not find a \ - entry in the procedure cache for '{}'", - proc.name() - ) - } - Export::Alias(ref alias) => { - panic!( - "compilation apparently succeeded, but did not find a \ - entry in the procedure cache for alias '{}', i.e. '{}'", - alias.name(), - alias.target() - ); - } - }); + /// # Errors + /// + /// Returns an error if parsing or compilation of the specified program fails, or if the source + /// doesn't have an entrypoint. + pub fn assemble_program(mut self, source: impl Compile) -> Result { + let options = CompileOptions { + kind: ModuleKind::Executable, + warnings_as_errors: self.warnings_as_errors, + path: Some(LibraryPath::from(LibraryNamespace::Exec)), + }; - exports.push(proc.code().hash()); - } + let program = source.compile_with_options(&self.source_manager, options)?; + assert!(program.is_executable()); - Ok(exports) - } + // Recompute graph with executable module, and start compiling + let ast_module_index = self.module_graph.add_ast_module(program)?; + self.module_graph.recompute()?; - /// Compile the provided [Module] into a [Program]. - /// - /// Returns an error if the provided Miden Assembly is invalid. - fn compile_program( - &mut self, - entrypoint: GlobalProcedureIndex, - context: &mut AssemblyContext, - ) -> Result { - // Raise an error if we are called with an invalid entrypoint - assert!(self.module_graph[entrypoint].name().is_main()); + // Find the executable entrypoint Note: it is safe to use `unwrap_ast()` here, since this is + // the module we just added, which is in AST representation. + let entrypoint = self.module_graph[ast_module_index] + .unwrap_ast() + .index_of(|p| p.is_main()) + .map(|index| GlobalProcedureIndex { module: ast_module_index, index }) + .ok_or(SemanticAnalysisError::MissingEntrypoint)?; // Compile the module graph rooted at the entrypoint - let entry = self.compile_subgraph(entrypoint, true, context)?; - - // Construct the code block table by taking the call set of the - // executable entrypoint and adding the code blocks of all those - // procedures to the table. - let mut code_blocks = CodeBlockTable::default(); - for callee in entry.callset().iter() { - let code_block = self - .procedure_cache - .get_by_mast_root(callee) - .map(|p| p.code().clone()) - .ok_or(AssemblyError::UndefinedCallSetProcedure { digest: *callee })?; - code_blocks.insert(code_block); - } - - let body = entry.code().clone(); - Ok(Program::with_kernel(body, self.module_graph.kernel().clone(), code_blocks)) - } - - /// Compile all of the uncompiled procedures in the module graph, placing them - /// in the procedure cache once compiled. - /// - /// Returns an error if any of the provided Miden Assembly is invalid. - fn assemble_graph(&mut self, context: &mut AssemblyContext) -> Result<(), Report> { - let mut worklist = self.module_graph.topological_sort().to_vec(); - assert!(!worklist.is_empty()); - self.process_graph_worklist(&mut worklist, context, None).map(|_| ()) + let mut mast_forest_builder = MastForestBuilder::default(); + self.compile_subgraph(entrypoint, &mut mast_forest_builder)?; + let entry_node_id = mast_forest_builder + .get_procedure(entrypoint) + .expect("compilation succeeded but root not found in cache") + .body_node_id(); + + // in case the node IDs changed, update the entrypoint ID to the new value + let (mast_forest, id_remappings) = mast_forest_builder.build(); + let entry_node_id = id_remappings + .map(|id_remappings| id_remappings[&entry_node_id]) + .unwrap_or(entry_node_id); + + Ok(Program::with_kernel( + mast_forest.into(), + entry_node_id, + self.module_graph.kernel().clone(), + )) } /// Compile the uncompiled procedure in the module graph which are members of the subgraph - /// rooted at `root`, placing them in the procedure cache once compiled. + /// rooted at `root`, placing them in the MAST forest builder once compiled. /// /// Returns an error if any of the provided Miden Assembly is invalid. fn compile_subgraph( &mut self, root: GlobalProcedureIndex, - is_entrypoint: bool, - context: &mut AssemblyContext, - ) -> Result, Report> { - let mut worklist = self.module_graph.topological_sort_from_root(root).map_err(|cycle| { - let iter = cycle.into_node_ids(); - let mut nodes = Vec::with_capacity(iter.len()); - for node in iter { - let module = self.module_graph[node.module].path(); - let proc = self.module_graph[node].name(); - nodes.push(format!("{}::{}", module, proc)); - } - AssemblyError::Cycle { nodes } - })?; + mast_forest_builder: &mut MastForestBuilder, + ) -> Result<(), Report> { + let mut worklist: Vec = self + .module_graph + .topological_sort_from_root(root) + .map_err(|cycle| { + let iter = cycle.into_node_ids(); + let mut nodes = Vec::with_capacity(iter.len()); + for node in iter { + let module = self.module_graph[node.module].path(); + let proc = self.module_graph.get_procedure_unsafe(node); + nodes.push(format!("{}::{}", module, proc.name())); + } + AssemblyError::Cycle { nodes } + })? + .into_iter() + .filter(|&gid| self.module_graph.get_procedure_unsafe(gid).is_ast()) + .collect(); assert!(!worklist.is_empty()); - let compiled = if is_entrypoint { - self.process_graph_worklist(&mut worklist, context, Some(root))? - } else { - let _ = self.process_graph_worklist(&mut worklist, context, None)?; - self.procedure_cache.get(root) - }; - - Ok(compiled.expect("compilation succeeded but root not found in cache")) + self.process_graph_worklist(&mut worklist, mast_forest_builder) } + /// Compiles all procedures in the `worklist`. fn process_graph_worklist( &mut self, worklist: &mut Vec, - context: &mut AssemblyContext, - entrypoint: Option, - ) -> Result>, Report> { + mast_forest_builder: &mut MastForestBuilder, + ) -> Result<(), Report> { // Process the topological ordering in reverse order (bottom-up), so that // each procedure is compiled with all of its dependencies fully compiled - let mut compiled_entrypoint = None; while let Some(procedure_gid) = worklist.pop() { // If we have already compiled this procedure, do not recompile - if let Some(proc) = self.procedure_cache.get(procedure_gid) { - self.module_graph.register_mast_root(procedure_gid, proc.mast_root())?; + if let Some(proc) = mast_forest_builder.get_procedure(procedure_gid) { + self.module_graph.register_procedure_root(procedure_gid, proc.mast_root())?; continue; } - let is_entry = entrypoint == Some(procedure_gid); - // Fetch procedure metadata from the graph - let module = &self.module_graph[procedure_gid.module]; - let ast = &module[procedure_gid.index]; - let num_locals = ast.num_locals(); - let name = FullyQualifiedProcedureName { - span: ast.span(), - module: module.path().clone(), - name: ast.name().clone(), + let module = match &self.module_graph[procedure_gid.module] { + WrappedModule::Ast(ast_module) => ast_module, + // Note: if the containing module is in `Info` representation, there is nothing to + // compile. + WrappedModule::Info(_) => continue, }; - let pctx = ProcedureContext::new(procedure_gid, name, ast.visibility()) - .with_num_locals(num_locals as u16) - .with_span(ast.span()) - .with_source_file(ast.source_file()); - - // Compile this procedure - let procedure = self.compile_procedure(pctx, context)?; - - // Cache the compiled procedure, unless it's the program entrypoint - if is_entry { - compiled_entrypoint = Some(Arc::from(procedure)); - } else { - // Make the MAST root available to all dependents - let digest = procedure.mast_root(); - self.module_graph.register_mast_root(procedure_gid, digest)?; - - self.procedure_cache.insert(procedure_gid, Arc::from(procedure))?; + + let export = &module[procedure_gid.index]; + match export { + Export::Procedure(proc) => { + let num_locals = proc.num_locals(); + let name = QualifiedProcedureName { + span: proc.span(), + module: module.path().clone(), + name: proc.name().clone(), + }; + let pctx = ProcedureContext::new( + procedure_gid, + name, + proc.visibility(), + module.is_kernel(), + self.source_manager.clone(), + ) + .with_num_locals(num_locals) + .with_span(proc.span()); + + // Compile this procedure + let procedure = self.compile_procedure(pctx, mast_forest_builder)?; + // TODO: if a re-exported procedure with the same MAST root had been previously + // added to the builder, this will result in unreachable nodes added to the + // MAST forest. This is because while we won't insert a duplicate node for the + // procedure body node itself, all nodes that make up the procedure body would + // be added to the forest. + + // Cache the compiled procedure + self.module_graph + .register_procedure_root(procedure_gid, procedure.mast_root())?; + mast_forest_builder.insert_procedure(procedure_gid, procedure)?; + }, + Export::Alias(proc_alias) => { + let name = QualifiedProcedureName { + span: proc_alias.span(), + module: module.path().clone(), + name: proc_alias.name().clone(), + }; + let pctx = ProcedureContext::new( + procedure_gid, + name, + ast::Visibility::Public, + module.is_kernel(), + self.source_manager.clone(), + ) + .with_span(proc_alias.span()); + + let proc_node_id = self.resolve_target( + InvokeKind::ProcRef, + &proc_alias.target().into(), + &pctx, + mast_forest_builder, + )?; + let proc_mast_root = + mast_forest_builder.get_mast_node(proc_node_id).unwrap().digest(); + + let procedure = pctx.into_procedure(proc_mast_root, proc_node_id); + + // Make the MAST root available to all dependents + self.module_graph.register_procedure_root(procedure_gid, proc_mast_root)?; + mast_forest_builder.insert_procedure(procedure_gid, procedure)?; + }, } } - Ok(compiled_entrypoint) + Ok(()) } /// Compiles a single Miden Assembly procedure to its MAST representation. fn compile_procedure( &self, - procedure: ProcedureContext, - context: &mut AssemblyContext, - ) -> Result, Report> { + mut proc_ctx: ProcedureContext, + mast_forest_builder: &mut MastForestBuilder, + ) -> Result { // Make sure the current procedure context is available during codegen - let gid = procedure.id(); - let num_locals = procedure.num_locals(); - context.set_current_procedure(procedure); + let gid = proc_ctx.id(); + let num_locals = proc_ctx.num_locals(); - let proc = self.module_graph[gid].unwrap_procedure(); - let code = if num_locals > 0 { + let wrapper_proc = self.module_graph.get_procedure_unsafe(gid); + let proc = wrapper_proc.unwrap_ast().unwrap_procedure(); + let proc_body_id = if num_locals > 0 { // for procedures with locals, we need to update fmp register before and after the // procedure body is executed. specifically: // - to allocate procedure locals we need to increment fmp by the number of locals @@ -670,183 +578,280 @@ impl Assembler { prologue: vec![Operation::Push(num_locals), Operation::FmpUpdate], epilogue: vec![Operation::Push(-num_locals), Operation::FmpUpdate], }; - self.compile_body(proc.iter(), context, Some(wrapper))? + self.compile_body(proc.iter(), &mut proc_ctx, Some(wrapper), mast_forest_builder)? } else { - self.compile_body(proc.iter(), context, None)? + self.compile_body(proc.iter(), &mut proc_ctx, None, mast_forest_builder)? }; - let pctx = context.take_current_procedure().unwrap(); - Ok(pctx.into_procedure(code)) + let proc_body_node = mast_forest_builder + .get_mast_node(proc_body_id) + .expect("no MAST node for compiled procedure"); + Ok(proc_ctx.into_procedure(proc_body_node.digest(), proc_body_id)) } fn compile_body<'a, I>( &self, body: I, - context: &mut AssemblyContext, + proc_ctx: &mut ProcedureContext, wrapper: Option, - ) -> Result + mast_forest_builder: &mut MastForestBuilder, + ) -> Result where I: Iterator, { use ast::Op; - let mut blocks: Vec = Vec::new(); - let mut span = SpanBuilder::new(wrapper); + let mut body_node_ids: Vec = Vec::new(); + let mut block_builder = BasicBlockBuilder::new(wrapper, mast_forest_builder); for op in body { match op { Op::Inst(inst) => { - if let Some(block) = self.compile_instruction(inst, &mut span, context)? { - span.extract_span_into(&mut blocks); - blocks.push(block); + if let Some(node_id) = + self.compile_instruction(inst, &mut block_builder, proc_ctx)? + { + if let Some(basic_block_id) = block_builder.make_basic_block()? { + body_node_ids.push(basic_block_id); + } else if let Some(decorator_ids) = block_builder.drain_decorators() { + block_builder + .mast_forest_builder_mut() + .set_before_enter(node_id, decorator_ids); + } + + body_node_ids.push(node_id); } - } - - Op::If { - then_blk, else_blk, .. - } => { - span.extract_span_into(&mut blocks); + }, - let then_blk = self.compile_body(then_blk.iter(), context, None)?; - // else is an exception because it is optional; hence, will have to be replaced - // by noop span - let else_blk = if else_blk.is_empty() { - CodeBlock::new_span(vec![Operation::Noop]) - } else { - self.compile_body(else_blk.iter(), context, None)? - }; + Op::If { then_blk, else_blk, .. } => { + if let Some(basic_block_id) = block_builder.make_basic_block()? { + body_node_ids.push(basic_block_id); + } - let block = CodeBlock::new_split(then_blk, else_blk); + let then_blk = self.compile_body( + then_blk.iter(), + proc_ctx, + None, + block_builder.mast_forest_builder_mut(), + )?; + let else_blk = self.compile_body( + else_blk.iter(), + proc_ctx, + None, + block_builder.mast_forest_builder_mut(), + )?; + + let split_node_id = + block_builder.mast_forest_builder_mut().ensure_split(then_blk, else_blk)?; + if let Some(decorator_ids) = block_builder.drain_decorators() { + block_builder + .mast_forest_builder_mut() + .set_before_enter(split_node_id, decorator_ids) + } - blocks.push(block); - } + body_node_ids.push(split_node_id); + }, Op::Repeat { count, body, .. } => { - span.extract_span_into(&mut blocks); - - let block = self.compile_body(body.iter(), context, None)?; + if let Some(basic_block_id) = block_builder.make_basic_block()? { + body_node_ids.push(basic_block_id); + } - for _ in 0..*count { - blocks.push(block.clone()); + let repeat_node_id = self.compile_body( + body.iter(), + proc_ctx, + None, + block_builder.mast_forest_builder_mut(), + )?; + + if let Some(decorator_ids) = block_builder.drain_decorators() { + // Attach the decorators before the first instance of the repeated node + let mut first_repeat_node = + block_builder.mast_forest_builder_mut()[repeat_node_id].clone(); + first_repeat_node.set_before_enter(decorator_ids); + let first_repeat_node_id = block_builder + .mast_forest_builder_mut() + .ensure_node(first_repeat_node)?; + + body_node_ids.push(first_repeat_node_id); + for _ in 0..(*count - 1) { + body_node_ids.push(repeat_node_id); + } + } else { + for _ in 0..*count { + body_node_ids.push(repeat_node_id); + } } - } + }, Op::While { body, .. } => { - span.extract_span_into(&mut blocks); + if let Some(basic_block_id) = block_builder.make_basic_block()? { + body_node_ids.push(basic_block_id); + } - let block = self.compile_body(body.iter(), context, None)?; - let block = CodeBlock::new_loop(block); + let loop_node_id = { + let loop_body_node_id = self.compile_body( + body.iter(), + proc_ctx, + None, + block_builder.mast_forest_builder_mut(), + )?; + block_builder.mast_forest_builder_mut().ensure_loop(loop_body_node_id)? + }; + if let Some(decorator_ids) = block_builder.drain_decorators() { + block_builder + .mast_forest_builder_mut() + .set_before_enter(loop_node_id, decorator_ids) + } - blocks.push(block); - } + body_node_ids.push(loop_node_id); + }, } } - span.extract_final_span_into(&mut blocks); - Ok(if blocks.is_empty() { - CodeBlock::new_span(vec![Operation::Noop]) + let maybe_post_decorators: Option> = + match block_builder.try_into_basic_block()? { + BasicBlockOrDecorators::BasicBlock(basic_block_id) => { + body_node_ids.push(basic_block_id); + None + }, + BasicBlockOrDecorators::Decorators(decorator_ids) => { + // the procedure body ends with a list of decorators + Some(decorator_ids) + }, + BasicBlockOrDecorators::Nothing => None, + }; + + let procedure_body_id = if body_node_ids.is_empty() { + // We cannot allow only decorators in a procedure body, since decorators don't change + // the MAST digest of a node. Hence, two empty procedures with different decorators + // would look the same to the `MastForestBuilder`. + if maybe_post_decorators.is_some() { + return Err(AssemblyError::EmptyProcedureBodyWithDecorators { + span: proc_ctx.span(), + source_file: proc_ctx.source_manager().get(proc_ctx.span().source_id()).ok(), + })?; + } + + mast_forest_builder.ensure_block(vec![Operation::Noop], None)? } else { - combine_blocks(blocks) - }) + mast_forest_builder.join_nodes(body_node_ids)? + }; + + // Make sure that any post decorators are added at the end of the procedure body + if let Some(post_decorator_ids) = maybe_post_decorators { + mast_forest_builder.set_after_exit(procedure_body_id, post_decorator_ids); + } + + Ok(procedure_body_id) } + /// Resolves the specified target to the corresponding procedure root [`MastNodeId`]. + /// + /// If no [`MastNodeId`] exists for that procedure root, we wrap the root in an + /// [`crate::mast::ExternalNode`], and return the resulting [`MastNodeId`]. pub(super) fn resolve_target( &self, kind: InvokeKind, target: &InvocationTarget, - context: &AssemblyContext, - ) -> Result { - let current_proc = context.unwrap_current_procedure(); + proc_ctx: &ProcedureContext, + mast_forest_builder: &mut MastForestBuilder, + ) -> Result { let caller = CallerInfo { span: target.span(), - source_file: current_proc.source_file(), - module: current_proc.id().module, + module: proc_ctx.id().module, kind, }; let resolved = self.module_graph.resolve_target(&caller, target)?; match resolved { - ResolvedTarget::Phantom(digest) | ResolvedTarget::Cached { digest, .. } => Ok(digest), - ResolvedTarget::Exact { gid } | ResolvedTarget::Resolved { gid, .. } => Ok(self - .procedure_cache - .get(gid) - .map(|p| p.mast_root()) - .expect("expected callee to have been compiled already")), + ResolvedTarget::Phantom(mast_root) => self.ensure_valid_procedure_mast_root( + kind, + target.span(), + mast_root, + mast_forest_builder, + ), + ResolvedTarget::Exact { gid } | ResolvedTarget::Resolved { gid, .. } => { + match mast_forest_builder.get_procedure(gid) { + Some(proc) => Ok(proc.body_node_id()), + // We didn't find the procedure in our current MAST forest. We still need to + // check if it exists in one of a library dependency. + None => match self.module_graph.get_procedure_unsafe(gid) { + ProcedureWrapper::Info(p) => self.ensure_valid_procedure_mast_root( + kind, + target.span(), + p.digest, + mast_forest_builder, + ) + , + ProcedureWrapper::Ast(_) => panic!("AST procedure {gid:?} exits in the module graph but not in the MastForestBuilder"), + }, + } + }, } } -} -/// Contains a set of operations which need to be executed before and after a sequence of AST -/// nodes (i.e., code body). -struct BodyWrapper { - prologue: Vec, - epilogue: Vec, -} - -fn combine_blocks(mut blocks: Vec) -> CodeBlock { - debug_assert!(!blocks.is_empty(), "cannot combine empty block list"); - // merge consecutive Span blocks. - let mut merged_blocks: Vec = Vec::with_capacity(blocks.len()); - // Keep track of all the consecutive Span blocks and are merged together when - // there is a discontinuity. - let mut contiguous_spans: Vec = Vec::new(); - - blocks.drain(0..).for_each(|block| { - if block.is_span() { - contiguous_spans.push(block); - } else { - if !contiguous_spans.is_empty() { - merged_blocks.push(combine_spans(&mut contiguous_spans)); - } - merged_blocks.push(block); + /// Verifies the validity of the MAST root as a procedure root hash, and returns the ID of the + /// [`core::mast::ExternalNode`] that wraps it. + fn ensure_valid_procedure_mast_root( + &self, + kind: InvokeKind, + span: SourceSpan, + mast_root: RpoDigest, + mast_forest_builder: &mut MastForestBuilder, + ) -> Result { + // Get the procedure from the assembler + let current_source_file = self.source_manager.get(span.source_id()).ok(); + + // If the procedure is cached and is a system call, ensure that the call is valid. + match mast_forest_builder.find_procedure_by_mast_root(&mast_root) { + Some(proc) if matches!(kind, InvokeKind::SysCall) => { + // Verify if this is a syscall, that the callee is a kernel procedure + // + // NOTE: The assembler is expected to know the full set of all kernel + // procedures at this point, so if we can't identify the callee as a + // kernel procedure, it is a definite error. + if !proc.visibility().is_syscall() { + return Err(AssemblyError::InvalidSysCallTarget { + span, + source_file: current_source_file, + callee: proc.fully_qualified_name().clone(), + }); + } + let maybe_kernel_path = proc.path(); + self.module_graph + .find_module(maybe_kernel_path) + .ok_or_else(|| AssemblyError::InvalidSysCallTarget { + span, + source_file: current_source_file.clone(), + callee: proc.fully_qualified_name().clone(), + }) + .and_then(|module| { + // Note: this module is guaranteed to be of AST variant, since we have the + // AST of a procedure contained in it (i.e. `proc`). Hence, it must be that + // the entire module is in AST representation as well. + if module.unwrap_ast().is_kernel() { + Ok(()) + } else { + Err(AssemblyError::InvalidSysCallTarget { + span, + source_file: current_source_file.clone(), + callee: proc.fully_qualified_name().clone(), + }) + } + })?; + }, + Some(_) | None => (), } - }); - if !contiguous_spans.is_empty() { - merged_blocks.push(combine_spans(&mut contiguous_spans)); - } - - // build a binary tree of blocks joining them using JOIN blocks - let mut blocks = merged_blocks; - while blocks.len() > 1 { - let last_block = if blocks.len() % 2 == 0 { None } else { blocks.pop() }; - let mut source_blocks = Vec::new(); - core::mem::swap(&mut blocks, &mut source_blocks); - - let mut source_block_iter = source_blocks.drain(0..); - while let (Some(left), Some(right)) = (source_block_iter.next(), source_block_iter.next()) { - blocks.push(CodeBlock::new_join([left, right])); - } - if let Some(block) = last_block { - blocks.push(block); - } + mast_forest_builder.ensure_external(mast_root) } - - debug_assert!(!blocks.is_empty(), "no blocks"); - blocks.remove(0) } -/// Combines a vector of SPAN blocks into a single SPAN block. -/// -/// # Panics -/// Panics if any of the provided blocks is not a SPAN block. -fn combine_spans(spans: &mut Vec) -> CodeBlock { - if spans.len() == 1 { - return spans.remove(0); - } +// HELPERS +// ================================================================================================ - let mut ops = Vec::::new(); - let mut decorators = DecoratorList::new(); - spans.drain(0..).for_each(|block| { - if let CodeBlock::Span(span) = block { - for decorator in span.decorators() { - decorators.push((decorator.0 + ops.len(), decorator.1.clone())); - } - for batch in span.op_batches() { - ops.extend_from_slice(batch.ops()); - } - } else { - panic!("CodeBlock was expected to be a Span Block, got {block:?}."); - } - }); - CodeBlock::new_span_with_decorators(ops, decorators) +/// Contains a set of operations which need to be executed before and after a sequence of AST +/// nodes (i.e., code body). +struct BodyWrapper { + prologue: Vec, + epilogue: Vec, } diff --git a/assembly/src/assembler/module_graph/analysis/rewrite_check.rs b/assembly/src/assembler/module_graph/analysis/rewrite_check.rs index 0baddf44b2..df0c800d07 100644 --- a/assembly/src/assembler/module_graph/analysis/rewrite_check.rs +++ b/assembly/src/assembler/module_graph/analysis/rewrite_check.rs @@ -1,4 +1,3 @@ -use alloc::sync::Arc; use core::ops::ControlFlow; use crate::{ @@ -7,19 +6,21 @@ use crate::{ ModuleIndex, ResolvedTarget, }, ast::{visit::Visit, InvocationTarget, InvokeKind, Module}, - diagnostics::SourceFile, AssemblyError, Spanned, }; +// MAYBE REWRITE CHECK +// ================================================================================================ + /// [MaybeRewriteCheck] is a simple analysis pass over a [Module], that looks for evidence that new /// information has been found that would result in at least one rewrite to the module body. /// -/// This pass is intended for modules that were already added to a [ModuleGraph], and so have been -/// rewritten at least once before. When new modules are added to the graph, the introduction of -/// those modules may allow us to resolve invocation targets that were previously unresolvable, or -/// that resolved as phantoms due to missing definitions. When that occurs, we want to go back and -/// rewrite all of the modules that can be further refined as a result of that additional -/// information. +/// This pass is intended for modules that were already added to a [super::super::ModuleGraph], and +/// so have been rewritten at least once before. When new modules are added to the graph, the +/// introduction of those modules may allow us to resolve invocation targets that were previously +/// unresolvable, or that resolved as phantoms due to missing definitions. When that occurs, we +/// want to go back and rewrite all of the modules that can be further refined as a result of that +/// additional information. pub struct MaybeRewriteCheck<'a, 'b: 'a> { resolver: &'a NameResolver<'b>, } @@ -32,11 +33,7 @@ impl<'a, 'b: 'a> MaybeRewriteCheck<'a, 'b> { /// Run the analysis, returning either a boolean answer, or an error that was found during /// analysis. pub fn check(&self, module_id: ModuleIndex, module: &Module) -> Result { - let mut visitor = RewriteCheckVisitor { - resolver: self.resolver, - module_id, - source_file: module.source_file(), - }; + let mut visitor = RewriteCheckVisitor { resolver: self.resolver, module_id }; match visitor.visit_module(module) { ControlFlow::Break(result) => result, ControlFlow::Continue(_) => Ok(false), @@ -44,10 +41,12 @@ impl<'a, 'b: 'a> MaybeRewriteCheck<'a, 'b> { } } +// REWRITE CHECK VISITOR +// ================================================================================================ + struct RewriteCheckVisitor<'a, 'b: 'a> { resolver: &'a NameResolver<'b>, module_id: ModuleIndex, - source_file: Option>, } impl<'a, 'b: 'a> RewriteCheckVisitor<'a, 'b> { @@ -58,7 +57,6 @@ impl<'a, 'b: 'a> RewriteCheckVisitor<'a, 'b> { ) -> ControlFlow> { let caller = CallerInfo { span: target.span(), - source_file: self.source_file.clone(), module: self.module_id, kind, }; @@ -67,14 +65,7 @@ impl<'a, 'b: 'a> RewriteCheckVisitor<'a, 'b> { Ok(ResolvedTarget::Resolved { .. }) => ControlFlow::Break(Ok(true)), Ok(ResolvedTarget::Exact { .. } | ResolvedTarget::Phantom(_)) => { ControlFlow::Continue(()) - } - Ok(ResolvedTarget::Cached { .. }) => { - if let InvocationTarget::MastRoot(_) = target { - ControlFlow::Continue(()) - } else { - ControlFlow::Break(Ok(true)) - } - } + }, } } } diff --git a/assembly/src/assembler/module_graph/callgraph.rs b/assembly/src/assembler/module_graph/callgraph.rs index 6209890c2f..aa8584f6fa 100644 --- a/assembly/src/assembler/module_graph/callgraph.rs +++ b/assembly/src/assembler/module_graph/callgraph.rs @@ -3,7 +3,7 @@ use alloc::{ vec::Vec, }; -use crate::assembler::{GlobalProcedureIndex, ModuleIndex}; +use crate::assembler::GlobalProcedureIndex; /// Represents the inability to construct a topological ordering of the nodes in a [CallGraph] /// due to a cycle in the graph, which can happen due to recursion. @@ -80,19 +80,6 @@ impl CallGraph { callees.push(callee); } - /// Removes all edges to/from a procedure in `module` - /// - /// NOTE: If a procedure that is removed has predecessors (callers) in the graph, this will - /// remove those edges, and the graph will be incomplete and not reflect the "true" call graph. - /// In practice, we are recomputing the graph after making such modifications, so this a - /// temporary state of affairs - still, it is important to be aware of this behavior. - pub fn remove_edges_for_module(&mut self, module: ModuleIndex) { - for (_, out_edges) in self.nodes.iter_mut() { - out_edges.retain(|gid| gid.module != module); - } - self.nodes.retain(|gid, _| gid.module != module); - } - /// Removes the edge between `caller` and `callee` from the graph pub fn remove_edge(&mut self, caller: GlobalProcedureIndex, callee: GlobalProcedureIndex) { if let Some(out_edges) = self.nodes.get_mut(&caller) { @@ -247,7 +234,6 @@ impl CallGraph { #[cfg(test)] mod tests { use super::*; - use crate::{ assembler::{GlobalProcedureIndex, ModuleIndex}, ast::ProcedureIndex, @@ -258,30 +244,12 @@ mod tests { const P1: ProcedureIndex = ProcedureIndex::const_new(1); const P2: ProcedureIndex = ProcedureIndex::const_new(2); const P3: ProcedureIndex = ProcedureIndex::const_new(3); - const A1: GlobalProcedureIndex = GlobalProcedureIndex { - module: A, - index: P1, - }; - const A2: GlobalProcedureIndex = GlobalProcedureIndex { - module: A, - index: P2, - }; - const A3: GlobalProcedureIndex = GlobalProcedureIndex { - module: A, - index: P3, - }; - const B1: GlobalProcedureIndex = GlobalProcedureIndex { - module: B, - index: P1, - }; - const B2: GlobalProcedureIndex = GlobalProcedureIndex { - module: B, - index: P2, - }; - const B3: GlobalProcedureIndex = GlobalProcedureIndex { - module: B, - index: P3, - }; + const A1: GlobalProcedureIndex = GlobalProcedureIndex { module: A, index: P1 }; + const A2: GlobalProcedureIndex = GlobalProcedureIndex { module: A, index: P2 }; + const A3: GlobalProcedureIndex = GlobalProcedureIndex { module: A, index: P3 }; + const B1: GlobalProcedureIndex = GlobalProcedureIndex { module: B, index: P1 }; + const B2: GlobalProcedureIndex = GlobalProcedureIndex { module: B, index: P2 }; + const B3: GlobalProcedureIndex = GlobalProcedureIndex { module: B, index: P3 }; #[test] fn callgraph_add_edge() { diff --git a/assembly/src/assembler/module_graph/debug.rs b/assembly/src/assembler/module_graph/debug.rs index 9858e8ebff..bf584fad1b 100644 --- a/assembly/src/assembler/module_graph/debug.rs +++ b/assembly/src/assembler/module_graph/debug.rs @@ -1,7 +1,7 @@ -use super::*; - use core::fmt; +use super::*; + impl fmt::Debug for ModuleGraph { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("ModuleGraph") @@ -17,54 +17,98 @@ struct DisplayModuleGraph<'a>(&'a ModuleGraph); impl<'a> fmt::Debug for DisplayModuleGraph<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_set() - .entries(self.0.modules.iter().enumerate().flat_map(|(index, m)| { - m.procedures().enumerate().filter_map(move |(i, export)| { - if matches!(export, Export::Alias(_)) { - None - } else { - let gid = GlobalProcedureIndex { - module: ModuleIndex::new(index), - index: ProcedureIndex::new(i), - }; - let out_edges = self.0.callgraph.out_edges(gid); - Some(DisplayModuleGraphNodeWithEdges { gid, out_edges }) - } - }) + .entries(self.0.modules.iter().enumerate().flat_map(|(module_index, m)| { + match m { + WrappedModule::Ast(m) => m + .procedures() + .enumerate() + .filter_map(move |(i, export)| { + if matches!(export, Export::Alias(_)) { + None + } else { + let gid = GlobalProcedureIndex { + module: ModuleIndex::new(module_index), + index: ProcedureIndex::new(i), + }; + let out_edges = self.0.callgraph.out_edges(gid); + Some(DisplayModuleGraphNodeWithEdges { gid, out_edges }) + } + }) + .collect::>(), + WrappedModule::Info(m) => m + .procedures() + .map(|(proc_index, _proc)| { + let gid = GlobalProcedureIndex { + module: ModuleIndex::new(module_index), + index: proc_index, + }; + + let out_edges = self.0.callgraph.out_edges(gid); + DisplayModuleGraphNodeWithEdges { gid, out_edges } + }) + .collect::>(), + } })) .finish() } } #[doc(hidden)] -struct DisplayModuleGraphNodes<'a>(&'a Vec>); +struct DisplayModuleGraphNodes<'a>(&'a Vec); impl<'a> fmt::Debug for DisplayModuleGraphNodes<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_list() - .entries(self.0.iter().enumerate().flat_map(|(index, m)| { - m.procedures().enumerate().filter_map(move |(i, export)| { - if matches!(export, Export::Alias(_)) { - None - } else { - Some(DisplayModuleGraphNode { - module: ModuleIndex::new(index), - index: ProcedureIndex::new(i), + .entries(self.0.iter().enumerate().flat_map(|(module_index, m)| { + let module_index = ModuleIndex::new(module_index); + + match m { + WrappedModule::Ast(m) => m + .procedures() + .enumerate() + .filter_map(move |(proc_index, export)| { + if matches!(export, Export::Alias(_)) { + None + } else { + Some(DisplayModuleGraphNode { + module: module_index, + index: ProcedureIndex::new(proc_index), + path: m.path(), + proc_name: export.name(), + ty: GraphNodeType::Ast, + }) + } + }) + .collect::>(), + WrappedModule::Info(m) => m + .procedures() + .map(|(proc_index, proc)| DisplayModuleGraphNode { + module: module_index, + index: proc_index, path: m.path(), - proc: export, + proc_name: &proc.name, + ty: GraphNodeType::Compiled, }) - } - }) + .collect::>(), + } })) .finish() } } +#[derive(Debug)] +enum GraphNodeType { + Ast, + Compiled, +} + #[doc(hidden)] struct DisplayModuleGraphNode<'a> { module: ModuleIndex, index: ProcedureIndex, path: &'a LibraryPath, - proc: &'a Export, + proc_name: &'a ProcedureName, + ty: GraphNodeType, } impl<'a> fmt::Debug for DisplayModuleGraphNode<'a> { @@ -72,7 +116,8 @@ impl<'a> fmt::Debug for DisplayModuleGraphNode<'a> { f.debug_struct("Node") .field("id", &format_args!("{}:{}", &self.module.as_usize(), &self.index.as_usize())) .field("module", &self.path) - .field("name", &self.proc.name()) + .field("name", &self.proc_name) + .field("type", &self.ty) .finish() } } diff --git a/assembly/src/assembler/module_graph/mod.rs b/assembly/src/assembler/module_graph/mod.rs index 4b1e48a7aa..c96617a314 100644 --- a/assembly/src/assembler/module_graph/mod.rs +++ b/assembly/src/assembler/module_graph/mod.rs @@ -2,46 +2,150 @@ mod analysis; mod callgraph; mod debug; mod name_resolver; -mod phantom; -mod procedure_cache; mod rewrites; -pub use self::callgraph::{CallGraph, CycleError}; -pub use self::name_resolver::{CallerInfo, ResolvedTarget}; -pub use self::procedure_cache::ProcedureCache; - -use alloc::{ - borrow::Cow, - boxed::Box, - collections::{BTreeMap, BTreeSet}, - sync::Arc, - vec::Vec, -}; +use alloc::{boxed::Box, collections::BTreeMap, sync::Arc, vec::Vec}; use core::ops::Index; use smallvec::{smallvec, SmallVec}; -use vm_core::Kernel; +use vm_core::{crypto::hash::RpoDigest, Kernel}; -use self::{ - analysis::MaybeRewriteCheck, name_resolver::NameResolver, phantom::PhantomCall, - rewrites::ModuleRewriter, +use self::{analysis::MaybeRewriteCheck, name_resolver::NameResolver, rewrites::ModuleRewriter}; +pub use self::{ + callgraph::{CallGraph, CycleError}, + name_resolver::{CallerInfo, ResolvedTarget}, }; use super::{GlobalProcedureIndex, ModuleIndex}; use crate::{ ast::{ - Export, FullyQualifiedProcedureName, InvocationTarget, Module, Procedure, ProcedureIndex, - ProcedureName, ResolvedProcedure, + Export, InvocationTarget, InvokeKind, Module, ProcedureIndex, ProcedureName, + ResolvedProcedure, }, - diagnostics::{RelatedLabel, SourceFile}, - AssemblyError, LibraryPath, RpoDigest, Spanned, + library::{ModuleInfo, ProcedureInfo}, + AssemblyError, LibraryNamespace, LibraryPath, SourceManager, Spanned, }; +// WRAPPER STRUCTS +// ================================================================================================ + +/// Wraps all supported representations of a procedure in the module graph. +/// +/// Currently, there are two supported representations: +/// - `Ast`: wraps a procedure for which we have access to the entire AST, +/// - `Info`: stores the procedure's name and digest (resulting from previously compiled +/// procedures). +pub enum ProcedureWrapper<'a> { + Ast(&'a Export), + Info(&'a ProcedureInfo), +} + +impl<'a> ProcedureWrapper<'a> { + /// Returns the name of the procedure. + pub fn name(&self) -> &ProcedureName { + match self { + Self::Ast(p) => p.name(), + Self::Info(p) => &p.name, + } + } + + /// Returns the wrapped procedure if in the `Ast` representation, or panics otherwise. + /// + /// # Panics + /// - Panics if the wrapped procedure is not in the `Ast` representation. + pub fn unwrap_ast(&self) -> &Export { + match self { + Self::Ast(proc) => proc, + Self::Info(_) => panic!("expected AST procedure, but was compiled"), + } + } + + /// Returns true if the wrapped procedure is in the `Ast` representation. + pub fn is_ast(&self) -> bool { + matches!(self, Self::Ast(_)) + } +} + +/// Wraps all supported representations of a module in the module graph. +/// +/// Currently, there are two supported representations: +/// - `Ast`: wraps a module for which we have access to the entire AST, +/// - `Info`: stores only the necessary information about a module (resulting from previously +/// compiled modules). +#[derive(Clone)] +pub enum WrappedModule { + Ast(Arc), + Info(ModuleInfo), +} + +impl WrappedModule { + /// Returns the library path of the wrapped module. + pub fn path(&self) -> &LibraryPath { + match self { + Self::Ast(m) => m.path(), + Self::Info(m) => m.path(), + } + } + + /// Returns the wrapped module if in the `Ast` representation, or panics otherwise. + /// + /// # Panics + /// - Panics if the wrapped module is not in the `Ast` representation. + pub fn unwrap_ast(&self) -> &Arc { + match self { + Self::Ast(module) => module, + Self::Info(_) => { + panic!("expected module to be in AST representation, but was compiled") + }, + } + } + + /// Returns the wrapped module if in the `Info` representation, or panics otherwise. + /// + /// # Panics + /// - Panics if the wrapped module is not in the `Info` representation. + pub fn unwrap_info(&self) -> &ModuleInfo { + match self { + Self::Ast(_) => { + panic!("expected module to be compiled, but was in AST representation") + }, + Self::Info(module) => module, + } + } + + /// Resolves `name` to a procedure within the local scope of this module. + pub fn resolve(&self, name: &ProcedureName) -> Option { + match self { + WrappedModule::Ast(module) => module.resolve(name), + WrappedModule::Info(module) => { + module.get_procedure_digest_by_name(name).map(ResolvedProcedure::MastRoot) + }, + } + } +} + +/// Wraps modules that are pending in the [`ModuleGraph`]. +#[derive(Clone)] +pub enum PendingWrappedModule { + Ast(Box), + Info(ModuleInfo), +} + +impl PendingWrappedModule { + /// Returns the library path of the wrapped module. + pub fn path(&self) -> &LibraryPath { + match self { + Self::Ast(m) => m.path(), + Self::Info(m) => m.path(), + } + } +} + // MODULE GRAPH // ================================================================================================ -#[derive(Default, Clone)] +#[derive(Clone)] pub struct ModuleGraph { - modules: Vec>, + modules: Vec, /// The set of modules pending additional processing before adding them to the graph. /// /// When adding a set of inter-dependent modules to the graph, we process them as a group, so @@ -50,31 +154,57 @@ pub struct ModuleGraph { /// /// Once added to the graph, modules become immutable, and any additional modules added after /// that must by definition only depend on modules in the graph, and not be depended upon. - #[allow(clippy::vec_box)] - pending: Vec>, + pending: Vec, /// The global call graph of calls, not counting those that are performed directly via MAST /// root. callgraph: CallGraph, - /// The computed topological ordering of the call graph - topo: Vec, /// The set of MAST roots which have procedure definitions in this graph. There can be /// multiple procedures bound to the same root due to having identical code. - roots: BTreeMap>, - /// The set of procedures in this graph which have known MAST roots - digests: BTreeMap, - /// The set of procedures which have no known definition in the graph, aka "phantom calls". - /// Since we know the hash of these functions, we can proceed with compilation, but in some - /// contexts we wish to disallow them and raise an error if any such calls are present. - /// - /// When we merge graphs, we attempt to resolve phantoms by attempting to find definitions in - /// the opposite graph. - phantoms: BTreeSet, + procedures_by_mast_root: BTreeMap>, kernel_index: Option, kernel: Kernel, + source_manager: Arc, } -/// Construction +// ------------------------------------------------------------------------------------------------ +/// Constructors impl ModuleGraph { + /// Instantiate a new [ModuleGraph], using the provided [SourceManager] to resolve source info. + pub fn new(source_manager: Arc) -> Self { + Self { + modules: Default::default(), + pending: Default::default(), + callgraph: Default::default(), + procedures_by_mast_root: Default::default(), + kernel_index: None, + kernel: Default::default(), + source_manager, + } + } + + /// Adds all module infos to the graph. + pub fn add_compiled_modules( + &mut self, + module_infos: impl IntoIterator, + ) -> Result, AssemblyError> { + let module_indices: Vec = module_infos + .into_iter() + .map(|module| self.add_module(PendingWrappedModule::Info(module))) + .collect::>()?; + + self.recompute()?; + + // Register all procedures as roots + for &module_index in module_indices.iter() { + for (proc_index, proc) in self[module_index].unwrap_info().clone().procedures() { + let gid = module_index + proc_index; + self.register_procedure_root(gid, proc.digest)?; + } + } + + Ok(module_indices) + } + /// Add `module` to the graph. /// /// NOTE: This operation only adds a module to the graph, but does not perform the @@ -92,13 +222,15 @@ impl ModuleGraph { /// /// This function will panic if the number of modules exceeds the maximum representable /// [ModuleIndex] value, `u16::MAX`. - pub fn add_module(&mut self, module: Box) -> Result { + pub fn add_ast_module(&mut self, module: Box) -> Result { + self.add_module(PendingWrappedModule::Ast(module)) + } + + fn add_module(&mut self, module: PendingWrappedModule) -> Result { let is_duplicate = self.is_pending(module.path()) || self.find_module_index(module.path()).is_some(); if is_duplicate { - return Err(AssemblyError::DuplicateModule { - path: module.path().clone(), - }); + return Err(AssemblyError::DuplicateModule { path: module.path().clone() }); } let module_id = self.next_module_id(); @@ -106,57 +238,6 @@ impl ModuleGraph { Ok(module_id) } - /// Remove a module from the graph by discarding any edges involving that module. We do not - /// remove the module from the node set by default, so as to preserve the stability of indices - /// in the graph. However, we do remove the module from the set if it is the most recently - /// added module, as that matches the most common case of compiling multiple programs in a row, - /// where we discard the executable module each time. - pub fn remove_module(&mut self, index: ModuleIndex) { - use alloc::collections::btree_map::Entry; - - // If the given index is a pending module, we just remove it from the pending set and call - // it a day - let pending_offset = self.modules.len(); - if index.as_usize() >= pending_offset { - self.pending.remove(index.as_usize() - pending_offset); - return; - } - - self.callgraph.remove_edges_for_module(index); - - // We remove all nodes from the topological sort that belong to the given module. The - // resulting sort is still valid, but may change the next time it is computed - self.topo.retain(|gid| gid.module != index); - - // Remove any cached procedure roots for the given module - for (gid, digest) in self.digests.iter() { - if gid.module != index { - continue; - } - if let Entry::Occupied(mut entry) = self.roots.entry(*digest) { - if entry.get().iter().all(|gid| gid.module == index) { - entry.remove(); - } else { - entry.get_mut().retain(|gid| gid.module != index); - } - } - } - self.digests.retain(|gid, _| gid.module != index); - self.roots.retain(|_, gids| !gids.is_empty()); - - // Handle removing the kernel module - if self.kernel_index == Some(index) { - self.kernel_index = None; - self.kernel = Default::default(); - } - - // If the module being removed comes last in the node set, remove it from the set to avoid - // growing the set unnecessarily over time. - if index.as_usize() == self.modules.len().saturating_sub(1) { - self.modules.pop(); - } - } - fn is_pending(&self, path: &LibraryPath) -> bool { self.pending.iter().any(|m| m.path() == path) } @@ -167,47 +248,46 @@ impl ModuleGraph { } } +// ------------------------------------------------------------------------------------------------ /// Kernels impl ModuleGraph { - pub(super) fn set_kernel(&mut self, kernel_index: Option, kernel: Kernel) { - self.kernel_index = kernel_index; - self.kernel = kernel; + /// Returns a new [ModuleGraph] instantiated from the provided kernel and kernel info module. + /// + /// Note: it is assumed that kernel and kernel_module are consistent, but this is not checked. + /// + /// TODO: consider passing `KerneLibrary` into this constructor as a parameter instead. + pub(super) fn with_kernel( + source_manager: Arc, + kernel: Kernel, + kernel_module: ModuleInfo, + ) -> Self { + assert!(!kernel.is_empty()); + assert_eq!(kernel_module.path(), &LibraryPath::from(LibraryNamespace::Kernel)); + + // add kernel module to the graph + // TODO: simplify this to avoid using Self::add_compiled_modules() + let mut graph = Self::new(source_manager); + let module_indexes = graph + .add_compiled_modules([kernel_module]) + .expect("failed to add kernel module to the module graph"); + assert_eq!(module_indexes[0], ModuleIndex::new(0), "kernel should be the first module"); + + graph.kernel_index = Some(module_indexes[0]); + graph.kernel = kernel; + + graph } pub fn kernel(&self) -> &Kernel { &self.kernel } - #[allow(unused)] - pub fn kernel_index(&self) -> Option { - self.kernel_index - } - pub fn has_nonempty_kernel(&self) -> bool { self.kernel_index.is_some() || !self.kernel.is_empty() } - - #[allow(unused)] - pub fn is_kernel_procedure_root(&self, digest: &RpoDigest) -> bool { - self.kernel.contains_proc(*digest) - } - - #[allow(unused)] - pub fn is_kernel_procedure(&self, name: &ProcedureName) -> bool { - self.kernel_index - .map(|index| self[index].resolve(name).is_some()) - .unwrap_or(false) - } - - #[allow(unused)] - pub fn is_kernel_procedure_fully_qualified(&self, name: &FullyQualifiedProcedureName) -> bool { - self.find_module_index(&name.module) - .filter(|module_index| self.kernel_index == Some(*module_index)) - .map(|module_index| self[module_index].resolve(&name.name).is_some()) - .unwrap_or(false) - } } +// ------------------------------------------------------------------------------------------------ /// Analysis impl ModuleGraph { /// Recompute the module graph. @@ -261,9 +341,6 @@ impl ModuleGraph { return Ok(()); } - // Remove previous topological sort, since it is no longer valid - self.topo.clear(); - // Visit all of the pending modules, assigning them ids, and adding them to the module // graph after rewriting any calls to use absolute paths let high_water_mark = self.modules.len(); @@ -272,18 +349,26 @@ impl ModuleGraph { let module_id = ModuleIndex::new(high_water_mark + pending_index); // Apply module to call graph - for (index, procedure) in pending_module.procedures().enumerate() { - let procedure_id = ProcedureIndex::new(index); - let global_id = GlobalProcedureIndex { - module: module_id, - index: procedure_id, - }; - - // Ensure all entrypoints and exported symbols are represented in the call graph, - // even if they have no edges, we need them in the graph for the topological sort - if matches!(procedure, Export::Procedure(_)) { - self.callgraph.get_or_insert_node(global_id); - } + match pending_module { + PendingWrappedModule::Ast(pending_module) => { + for (index, _) in pending_module.procedures().enumerate() { + let procedure_id = ProcedureIndex::new(index); + let global_id = + GlobalProcedureIndex { module: module_id, index: procedure_id }; + + // Ensure all entrypoints and exported symbols are represented in the call + // graph, even if they have no edges, we need them + // in the graph for the topological sort + self.callgraph.get_or_insert_node(global_id); + } + }, + PendingWrappedModule::Info(pending_module) => { + for (proc_index, _procedure) in pending_module.procedures() { + let global_id = + GlobalProcedureIndex { module: module_id, index: proc_index }; + self.callgraph.get_or_insert_node(global_id); + } + }, } } @@ -291,84 +376,101 @@ impl ModuleGraph { // before they are added to the graph let mut resolver = NameResolver::new(self); for module in pending.iter() { - resolver.push_pending(module); + if let PendingWrappedModule::Ast(module) = module { + resolver.push_pending(module); + } } - let mut phantoms = BTreeSet::default(); let mut edges = Vec::new(); - let mut finished = Vec::>::new(); - - // Visit all of the newly-added modules and perform any rewrites - for (pending_index, mut module) in pending.into_iter().enumerate() { - let module_id = ModuleIndex::new(high_water_mark + pending_index); - - let mut rewriter = ModuleRewriter::new(&resolver); - rewriter.apply(module_id, &mut module)?; - - // Gather the phantom calls found while rewriting the module - phantoms.extend(rewriter.phantoms()); - - for (index, procedure) in module.procedures().enumerate() { - let procedure_id = ProcedureIndex::new(index); - let gid = GlobalProcedureIndex { - module: module_id, - index: procedure_id, - }; - - for invoke in procedure.invoked() { - let caller = CallerInfo { - span: invoke.span(), - source_file: module.source_file(), - module: module_id, - kind: invoke.kind, - }; - if let Some(callee) = - resolver.resolve_target(&caller, &invoke.target)?.into_global_id() - { - edges.push((gid, callee)); + let mut finished: Vec = Vec::new(); + + // Visit all of the newly-added modules and perform any rewrites to AST modules. + for (pending_index, module) in pending.into_iter().enumerate() { + match module { + PendingWrappedModule::Ast(mut ast_module) => { + let module_id = ModuleIndex::new(high_water_mark + pending_index); + + let mut rewriter = ModuleRewriter::new(&resolver); + rewriter.apply(module_id, &mut ast_module)?; + + for (index, procedure) in ast_module.procedures().enumerate() { + let procedure_id = ProcedureIndex::new(index); + let gid = GlobalProcedureIndex { module: module_id, index: procedure_id }; + + // Add edge to the call graph to represent dependency on aliased procedures + if let Export::Alias(ref alias) = procedure { + let caller = CallerInfo { + span: alias.span(), + module: module_id, + kind: InvokeKind::ProcRef, + }; + let target = alias.target().into(); + if let Some(callee) = + resolver.resolve_target(&caller, &target)?.into_global_id() + { + edges.push((gid, callee)); + } + } + + // Add edges to all transitive dependencies of this procedure due to calls + for invoke in procedure.invoked() { + let caller = CallerInfo { + span: invoke.span(), + module: module_id, + kind: invoke.kind, + }; + if let Some(callee) = + resolver.resolve_target(&caller, &invoke.target)?.into_global_id() + { + edges.push((gid, callee)); + } + } } - } - } - finished.push(Arc::from(module)); + finished.push(WrappedModule::Ast(Arc::new(*ast_module))) + }, + PendingWrappedModule::Info(module) => { + finished.push(WrappedModule::Info(module)); + }, + } } // Release the graph again drop(resolver); // Extend the graph with all of the new additions - self.phantoms.extend(phantoms); self.modules.append(&mut finished); edges .into_iter() - .for_each(|(callee, caller)| self.callgraph.add_edge(callee, caller)); + .for_each(|(caller, callee)| self.callgraph.add_edge(caller, callee)); - // Visit all of the modules in the base module graph, and modify them if any of the - // pending modules allow additional information to be inferred (such as the absolute path - // of imports, etc) + // Visit all of the (AST) modules in the base module graph, and modify them if any of the + // pending modules allow additional information to be inferred (such as the absolute path of + // imports, etc) for module_index in 0..high_water_mark { let module_id = ModuleIndex::new(module_index); let module = self.modules[module_id.as_usize()].clone(); - // Re-analyze the module, and if we needed to clone-on-write, the new module will be - // returned. Otherwise, `Ok(None)` indicates that the module is unchanged, and `Err` - // indicates that re-analysis has found an issue with this module. - if let Some(new_module) = self.reanalyze_module(module_id, module)? { - self.modules[module_id.as_usize()] = new_module; + if let WrappedModule::Ast(module) = module { + // Re-analyze the module, and if we needed to clone-on-write, the new module will be + // returned. Otherwise, `Ok(None)` indicates that the module is unchanged, and `Err` + // indicates that re-analysis has found an issue with this module. + if let Some(new_module) = self.reanalyze_module(module_id, module)? { + self.modules[module_id.as_usize()] = WrappedModule::Ast(new_module); + } } } // Make sure the graph is free of cycles - let topo = self.callgraph.toposort().map_err(|cycle| { + self.callgraph.toposort().map_err(|cycle| { let iter = cycle.into_node_ids(); let mut nodes = Vec::with_capacity(iter.len()); for node in iter { let module = self[node.module].path(); - let proc = self[node].name(); - nodes.push(format!("{}::{}", module, proc)); + let proc = self.get_procedure_unsafe(node); + nodes.push(format!("{}::{}", module, proc.name())); } AssemblyError::Cycle { nodes } })?; - self.topo = topo; Ok(()) } @@ -387,8 +489,6 @@ impl ModuleGraph { let mut rewriter = ModuleRewriter::new(&resolver); rewriter.apply(module_id, &mut module)?; - self.phantoms.extend(rewriter.phantoms()); - Ok(Some(Arc::from(module))) } else { Ok(None) @@ -396,18 +496,9 @@ impl ModuleGraph { } } +// ------------------------------------------------------------------------------------------------ /// Accessors/Queries impl ModuleGraph { - /// Get a slice representing the topological ordering of this graph. - /// - /// The slice is ordered such that when a node is encountered, all of its dependencies come - /// after it in the slice. Thus, by walking the slice in reverse, we visit the leaves of the - /// graph before any of the dependents of those leaves. We use this property to resolve MAST - /// roots for the entire program, bottom-up. - pub fn topological_sort(&self) -> &[GlobalProcedureIndex] { - self.topo.as_slice() - } - /// Compute the topological sort of the callgraph rooted at `caller` pub fn topological_sort_from_root( &self, @@ -416,53 +507,29 @@ impl ModuleGraph { self.callgraph.toposort_caller(caller) } - /// Fetch a [Module] by [ModuleIndex] - #[allow(unused)] - pub fn get_module(&self, id: ModuleIndex) -> Option> { - self.modules.get(id.as_usize()).cloned() - } - - /// Fetch a [Module] by [ModuleIndex] - pub fn contains_module(&self, id: ModuleIndex) -> bool { - self.modules.get(id.as_usize()).is_some() - } - - /// Fetch a [Export] by [GlobalProcedureIndex] - #[allow(unused)] - pub fn get_procedure(&self, id: GlobalProcedureIndex) -> Option<&Export> { - self.modules.get(id.module.as_usize()).and_then(|m| m.get(id.index)) - } - - /// Fetches a [Procedure] by [RpoDigest]. + /// Fetch a [WrapperProcedure] by [GlobalProcedureIndex]. /// - /// NOTE: This implicitly chooses the first definition for a procedure if the same digest is - /// shared for multiple definitions. - #[allow(unused)] - pub fn get_procedure_by_digest(&self, digest: &RpoDigest) -> Option<&Procedure> { - self.roots - .get(digest) - .and_then(|indices| match self.get_procedure(indices[0])? { - Export::Procedure(ref proc) => Some(proc), - Export::Alias(_) => None, - }) + /// # Panics + /// - Panics if index is invalid. + pub fn get_procedure_unsafe(&self, id: GlobalProcedureIndex) -> ProcedureWrapper { + match &self.modules[id.module.as_usize()] { + WrappedModule::Ast(m) => ProcedureWrapper::Ast(&m[id.index]), + WrappedModule::Info(m) => { + ProcedureWrapper::Info(m.get_procedure_by_index(id.index).unwrap()) + }, + } } + /// Returns a procedure index which corresponds to the provided procedure digest. + /// + /// Note that there can be many procedures with the same digest - due to having the same code, + /// and/or using different decorators which don't affect the MAST root. This method returns an + /// arbitrary one. pub fn get_procedure_index_by_digest( &self, - digest: &RpoDigest, + procedure_digest: &RpoDigest, ) -> Option { - self.roots.get(digest).map(|indices| indices[0]) - } - - /// Look up the [RpoDigest] associated with the given [GlobalProcedureIndex], if one is known - /// at this point in time. - pub fn get_mast_root(&self, id: GlobalProcedureIndex) -> Option<&RpoDigest> { - self.digests.get(&id) - } - - #[allow(unused)] - pub fn callees(&self, gid: GlobalProcedureIndex) -> &[GlobalProcedureIndex] { - self.callgraph.out_edges(gid) + self.procedures_by_mast_root.get(procedure_digest).map(|indices| indices[0]) } /// Resolves `target` from the perspective of `caller`. @@ -475,7 +542,7 @@ impl ModuleGraph { resolver.resolve_target(caller, target) } - /// Registers a [RpoDigest] as corresponding to a given [GlobalProcedureIndex]. + /// Registers a [MastNodeId] as corresponding to a given [GlobalProcedureIndex]. /// /// # SAFETY /// @@ -483,171 +550,43 @@ impl ModuleGraph { /// procedure. It is fine if there are multiple procedures with the same digest, but it _must_ /// be the case that if a given digest is specified, it can be used as if it was the definition /// of the referenced procedure, i.e. they are referentially transparent. - pub(crate) fn register_mast_root( + pub(crate) fn register_procedure_root( &mut self, id: GlobalProcedureIndex, - digest: RpoDigest, + procedure_mast_root: RpoDigest, ) -> Result<(), AssemblyError> { use alloc::collections::btree_map::Entry; - match self.roots.entry(digest) { + match self.procedures_by_mast_root.entry(procedure_mast_root) { Entry::Occupied(ref mut entry) => { let prev_id = entry.get()[0]; if prev_id != id { - // Multiple procedures with the same root, but incompatible - let prev = &self.modules[prev_id.module.as_usize()][prev_id.index]; - let current = &self.modules[id.module.as_usize()][id.index]; - if prev.num_locals() != current.num_locals() { - let prev_module = self.modules[prev_id.module.as_usize()].path(); - let prev_name = FullyQualifiedProcedureName { - span: prev.span(), - module: prev_module.clone(), - name: prev.name().clone(), - }; - let current_module = self.modules[id.module.as_usize()].path(); - let current_name = FullyQualifiedProcedureName { - span: current.span(), - module: current_module.clone(), - name: current.name().clone(), - }; - return Err(AssemblyError::ConflictingDefinitions { - first: prev_name, - second: current_name, - }); - } - // Multiple procedures with the same root, but compatible entry.get_mut().push(id); } - } + }, Entry::Vacant(entry) => { entry.insert(smallvec![id]); - } - } - - match self.digests.entry(id) { - Entry::Occupied(ref entry) => { - assert_eq!( - entry.get(), - &digest, - "attempted to register the same procedure with different digests!" - ); - } - Entry::Vacant(entry) => { - entry.insert(digest); - } + }, } Ok(()) } - /// Resolves a [FullyQualifiedProcedureName] to its defining [Procedure]. - pub fn find( - &self, - source_file: Option>, - name: &FullyQualifiedProcedureName, - ) -> Result { - let mut next = Cow::Borrowed(name); - let mut caller = source_file.clone(); - loop { - let module_index = self.find_module_index(&next.module).ok_or_else(|| { - AssemblyError::UndefinedModule { - span: next.span(), - source_file: caller.clone(), - path: name.module.clone(), - } - })?; - let module = &self.modules[module_index.as_usize()]; - match module.resolve(&next.name) { - Some(ResolvedProcedure::Local(index)) => { - let id = GlobalProcedureIndex { - module: module_index, - index: index.into_inner(), - }; - break Ok(id); - } - Some(ResolvedProcedure::External(fqn)) => { - // If we see that we're about to enter an infinite resolver loop because of a - // recursive alias, return an error - if name == &fqn { - break Err(AssemblyError::RecursiveAlias { - source_file: caller.clone(), - name: name.clone(), - }); - } - next = Cow::Owned(fqn); - caller = module.source_file(); - } - None => { - // No such procedure known to `module` - break Err(AssemblyError::Failed { - labels: vec![RelatedLabel::error("undefined procedure") - .with_source_file(source_file) - .with_labeled_span(next.span(), "unable to resolve this reference")], - }); - } - } - } - } - /// Resolve a [LibraryPath] to a [ModuleIndex] in this graph pub fn find_module_index(&self, name: &LibraryPath) -> Option { self.modules.iter().position(|m| m.path() == name).map(ModuleIndex::new) } /// Resolve a [LibraryPath] to a [Module] in this graph - pub fn find_module(&self, name: &LibraryPath) -> Option> { + pub fn find_module(&self, name: &LibraryPath) -> Option { self.modules.iter().find(|m| m.path() == name).cloned() } - - /// Returns an iterator over the set of [Module]s in this graph, and their indices - #[allow(unused)] - pub fn modules(&self) -> impl Iterator)> + '_ { - self.modules - .iter() - .enumerate() - .map(|(idx, m)| (ModuleIndex::new(idx), m.clone())) - } - - /// Like [modules], but returns a reference to the module, rather than an owned pointer - #[allow(unused)] - pub fn modules_by_ref(&self) -> impl Iterator + '_ { - self.modules - .iter() - .enumerate() - .map(|(idx, m)| (ModuleIndex::new(idx), m.as_ref())) - } - - /// Returns an iterator over the set of [Procedure]s in this graph, and their indices - #[allow(unused)] - pub fn procedures(&self) -> impl Iterator + '_ { - self.modules_by_ref().flat_map(|(module_index, module)| { - module.procedures().enumerate().filter_map(move |(index, p)| { - let index = ProcedureIndex::new(index); - let id = GlobalProcedureIndex { - module: module_index, - index, - }; - match p { - Export::Procedure(ref p) => Some((id, p)), - Export::Alias(_) => None, - } - }) - }) - } } impl Index for ModuleGraph { - type Output = Arc; + type Output = WrappedModule; fn index(&self, index: ModuleIndex) -> &Self::Output { self.modules.index(index.as_usize()) } } - -impl Index for ModuleGraph { - type Output = Export; - - fn index(&self, index: GlobalProcedureIndex) -> &Self::Output { - self.modules[index.module.as_usize()].index(index.index) - } -} diff --git a/assembly/src/assembler/module_graph/name_resolver.rs b/assembly/src/assembler/module_graph/name_resolver.rs index 244bd69f06..f802f40c99 100644 --- a/assembly/src/assembler/module_graph/name_resolver.rs +++ b/assembly/src/assembler/module_graph/name_resolver.rs @@ -1,13 +1,13 @@ -use alloc::{borrow::Cow, collections::BTreeSet, sync::Arc, vec::Vec}; +use alloc::{borrow::Cow, collections::BTreeSet, vec::Vec}; -use super::ModuleGraph; +use super::{ModuleGraph, WrappedModule}; use crate::{ assembler::{GlobalProcedureIndex, ModuleIndex}, ast::{ - FullyQualifiedProcedureName, Ident, InvocationTarget, InvokeKind, Module, ProcedureName, + Ident, InvocationTarget, InvokeKind, Module, ProcedureName, QualifiedProcedureName, ResolvedProcedure, }, - diagnostics::{RelatedLabel, SourceFile}, + diagnostics::RelatedLabel, library::{LibraryNamespace, LibraryPath}, AssemblyError, RpoDigest, SourceSpan, Span, Spanned, }; @@ -21,7 +21,6 @@ use crate::{ /// include in name resolution in order to be able to fully resolve all names for a given set of /// modules. struct ThinModule { - source_file: Option>, path: LibraryPath, resolver: crate::ast::LocalNameResolver, } @@ -34,8 +33,6 @@ struct ThinModule { pub struct CallerInfo { /// The source span of the caller pub span: SourceSpan, - /// The source file corresponding to `span`, if available - pub source_file: Option>, /// The "where", i.e. index of the caller's module in the [ModuleGraph]. pub module: ModuleIndex, /// The "how", i.e. how the callee is being invoked. @@ -48,12 +45,6 @@ pub struct CallerInfo { /// Represents the output of the [NameResolver] when it resolves a procedure name. #[derive(Debug)] pub enum ResolvedTarget { - /// The callee is available in the procedure cache, so we know its exact hash. - Cached { - digest: RpoDigest, - /// If the procedure was compiled from source, this is its identifier in the [ModuleGraph] - gid: Option, - }, /// The callee was resolved to a known procedure in the [ModuleGraph] Exact { gid: GlobalProcedureIndex }, /// The callee was resolved to a concrete procedure definition, and can be referenced as @@ -73,7 +64,6 @@ impl ResolvedTarget { pub fn into_global_id(self) -> Option { match self { ResolvedTarget::Exact { gid } | ResolvedTarget::Resolved { gid, .. } => Some(gid), - ResolvedTarget::Cached { gid, .. } => gid, ResolvedTarget::Phantom(_) => None, } } @@ -103,10 +93,7 @@ pub struct NameResolver<'a> { impl<'a> NameResolver<'a> { /// Create a new [NameResolver] for the provided [ModuleGraph]. pub fn new(graph: &'a ModuleGraph) -> Self { - Self { - graph, - pending: vec![], - } + Self { graph, pending: vec![] } } /// Add a module to the set of "pending" modules this resolver will consult when doing @@ -121,15 +108,75 @@ impl<'a> NameResolver<'a> { /// have not yet processed to the resolver, as we resolve names for each module in the set. pub fn push_pending(&mut self, module: &Module) { self.pending.push(ThinModule { - source_file: module.source_file(), path: module.path().clone(), resolver: module.resolver(), }); } + /// Resolve `target`, a possibly-resolved callee identifier, to a [ResolvedTarget], using + /// `caller` as the context. + pub fn resolve_target( + &self, + caller: &CallerInfo, + target: &InvocationTarget, + ) -> Result { + match target { + InvocationTarget::MastRoot(mast_root) => { + match self.graph.get_procedure_index_by_digest(mast_root) { + None => Ok(ResolvedTarget::Phantom(mast_root.into_inner())), + Some(gid) => Ok(ResolvedTarget::Exact { gid }), + } + }, + InvocationTarget::ProcedureName(ref callee) => self.resolve(caller, callee), + InvocationTarget::ProcedurePath { ref name, module: ref imported_module } => { + match self.resolve_import(caller, imported_module) { + Some(imported_module) => { + let fqn = QualifiedProcedureName { + span: target.span(), + module: imported_module.into_inner().clone(), + name: name.clone(), + }; + let gid = self.find(caller, &fqn)?; + let path = self.module_path(gid.module); + let pending_offset = self.graph.modules.len(); + let name = if gid.module.as_usize() >= pending_offset { + self.pending[gid.module.as_usize() - pending_offset] + .resolver + .get_name(gid.index) + .clone() + } else { + self.graph.get_procedure_unsafe(gid).name().clone() + }; + Ok(ResolvedTarget::Resolved { + gid, + target: InvocationTarget::AbsoluteProcedurePath { name, path }, + }) + }, + None => Err(AssemblyError::UndefinedModule { + span: caller.span, + source_file: self.graph.source_manager.get(caller.span.source_id()).ok(), + path: LibraryPath::new_from_components( + LibraryNamespace::User(imported_module.clone().into_inner()), + [], + ), + }), + } + }, + InvocationTarget::AbsoluteProcedurePath { ref name, ref path } => { + let fqn = QualifiedProcedureName { + span: target.span(), + module: path.clone(), + name: name.clone(), + }; + let gid = self.find(caller, &fqn)?; + Ok(ResolvedTarget::Exact { gid }) + }, + } + } + /// Resolver `callee` to a [ResolvedTarget], using `caller` as the context in which `callee` /// should be resolved. - pub fn resolve( + fn resolve( &self, caller: &CallerInfo, callee: &ProcedureName, @@ -140,55 +187,41 @@ impl<'a> NameResolver<'a> { module: self.graph.kernel_index.unwrap(), index: index.into_inner(), }; - match self.graph.get_mast_root(gid) { - Some(digest) => Ok(ResolvedTarget::Cached { - digest: *digest, - gid: Some(gid), - }), - None => Ok(ResolvedTarget::Exact { gid }), - } - } + Ok(ResolvedTarget::Exact { gid }) + }, Some(ResolvedProcedure::Local(index)) => { let gid = GlobalProcedureIndex { module: caller.module, index: index.into_inner(), }; - match self.graph.get_mast_root(gid) { - Some(digest) => Ok(ResolvedTarget::Cached { - digest: *digest, - gid: Some(gid), - }), - None => Ok(ResolvedTarget::Exact { gid }), - } - } + Ok(ResolvedTarget::Exact { gid }) + }, Some(ResolvedProcedure::External(ref fqn)) => { let gid = self.find(caller, fqn)?; - match self.graph.get_mast_root(gid) { - Some(digest) => Ok(ResolvedTarget::Cached { - digest: *digest, - gid: Some(gid), - }), - None => { - let path = self.module_path(gid.module); - let pending_offset = self.graph.modules.len(); - let name = if gid.module.as_usize() >= pending_offset { - self.pending[gid.module.as_usize() - pending_offset] - .resolver - .get_name(gid.index) - .clone() - } else { - self.graph[gid].name().clone() - }; - Ok(ResolvedTarget::Resolved { - gid, - target: InvocationTarget::AbsoluteProcedurePath { name, path }, - }) - } + let path = self.module_path(gid.module); + let pending_offset = self.graph.modules.len(); + let name = if gid.module.as_usize() >= pending_offset { + self.pending[gid.module.as_usize() - pending_offset] + .resolver + .get_name(gid.index) + .clone() + } else { + self.graph.get_procedure_unsafe(gid).name().clone() + }; + Ok(ResolvedTarget::Resolved { + gid, + target: InvocationTarget::AbsoluteProcedurePath { name, path }, + }) + }, + Some(ResolvedProcedure::MastRoot(ref digest)) => { + match self.graph.get_procedure_index_by_digest(digest) { + Some(gid) => Ok(ResolvedTarget::Exact { gid }), + None => Ok(ResolvedTarget::Phantom(*digest)), } - } + }, None => Err(AssemblyError::Failed { labels: vec![RelatedLabel::error("undefined procedure") - .with_source_file(caller.source_file.clone()) + .with_source_file(self.graph.source_manager.get(caller.span.source_id()).ok()) .with_labeled_span(caller.span, "unable to resolve this name locally")], }), } @@ -196,91 +229,18 @@ impl<'a> NameResolver<'a> { /// Resolve `name`, the name of an imported module, to a [LibraryPath], using `caller` as the /// context. - pub fn resolve_import(&self, caller: &CallerInfo, name: &Ident) -> Option> { + fn resolve_import(&self, caller: &CallerInfo, name: &Ident) -> Option> { let pending_offset = self.graph.modules.len(); if caller.module.as_usize() >= pending_offset { self.pending[caller.module.as_usize() - pending_offset] .resolver .resolve_import(name) } else { - self.graph[caller.module] - .resolve_import(name) - .map(|import| Span::new(import.span(), import.path())) - } - } - - /// Resolve `target`, a possibly-resolved callee identifier, to a [ResolvedTarget], using - /// `caller` as the context. - pub fn resolve_target( - &self, - caller: &CallerInfo, - target: &InvocationTarget, - ) -> Result { - match target { - InvocationTarget::MastRoot(mast_root) => { - match self.graph.get_procedure_index_by_digest(mast_root) { - None => Ok(ResolvedTarget::Phantom(mast_root.into_inner())), - Some(gid) => Ok(ResolvedTarget::Exact { gid }), - } - } - InvocationTarget::ProcedureName(ref callee) => self.resolve(caller, callee), - InvocationTarget::ProcedurePath { - ref name, - module: ref imported_module, - } => match self.resolve_import(caller, imported_module) { - Some(imported_module) => { - let fqn = FullyQualifiedProcedureName { - span: target.span(), - module: imported_module.into_inner().clone(), - name: name.clone(), - }; - let gid = self.find(caller, &fqn)?; - match self.graph.get_mast_root(gid) { - Some(digest) => Ok(ResolvedTarget::Cached { - digest: *digest, - gid: Some(gid), - }), - None => { - let path = self.module_path(gid.module); - let pending_offset = self.graph.modules.len(); - let name = if gid.module.as_usize() >= pending_offset { - self.pending[gid.module.as_usize() - pending_offset] - .resolver - .get_name(gid.index) - .clone() - } else { - self.graph[gid].name().clone() - }; - Ok(ResolvedTarget::Resolved { - gid, - target: InvocationTarget::AbsoluteProcedurePath { name, path }, - }) - } - } - } - None => Err(AssemblyError::UndefinedModule { - span: target.span(), - source_file: caller.source_file.clone(), - path: LibraryPath::new_from_components( - LibraryNamespace::User(imported_module.clone().into_inner()), - [], - ), - }), - }, - InvocationTarget::AbsoluteProcedurePath { ref name, ref path } => { - let fqn = FullyQualifiedProcedureName { - span: target.span(), - module: path.clone(), - name: name.clone(), - }; - let gid = self.find(caller, &fqn)?; - match self.graph.get_mast_root(gid) { - Some(digest) => Ok(ResolvedTarget::Cached { - digest: *digest, - gid: Some(gid), - }), - None => Ok(ResolvedTarget::Exact { gid }), - } + match &self.graph[caller.module] { + WrappedModule::Ast(module) => module + .resolve_import(name) + .map(|import| Span::new(import.span(), import.path())), + WrappedModule::Info(_) => None, } } } @@ -313,20 +273,21 @@ impl<'a> NameResolver<'a> { } } - /// Resolve `name` to its concrete definition, returning the corresponding + /// Resolve `callee` to its concrete definition, returning the corresponding /// [GlobalProcedureIndex]. /// /// If an error occurs during resolution, or the name cannot be resolved, `Err` is returned. - pub fn find( + fn find( &self, caller: &CallerInfo, - callee: &FullyQualifiedProcedureName, + callee: &QualifiedProcedureName, ) -> Result { - // If the caller is a syscall, set the invoke kind to exec until we have resolved the - // procedure, then verify that it is in the kernel module + // If the caller is a syscall, set the invoke kind to `ProcRef` until we have resolved the + // procedure, then verify that it is in the kernel module. This bypasses validation until + // after resolution let mut current_caller = if matches!(caller.kind, InvokeKind::SysCall) { let mut caller = caller.clone(); - caller.kind = InvokeKind::Exec; + caller.kind = InvokeKind::ProcRef; Cow::Owned(caller) } else { Cow::Borrowed(caller) @@ -336,8 +297,12 @@ impl<'a> NameResolver<'a> { loop { let module_index = self.find_module_index(¤t_callee.module).ok_or_else(|| { AssemblyError::UndefinedModule { - span: current_callee.span(), - source_file: current_caller.source_file.clone(), + span: current_caller.span, + source_file: self + .graph + .source_manager + .get(current_caller.span.source_id()) + .ok(), path: current_callee.module.clone(), } })?; @@ -351,13 +316,17 @@ impl<'a> NameResolver<'a> { if matches!(current_caller.kind, InvokeKind::SysCall if self.graph.kernel_index != Some(module_index)) { break Err(AssemblyError::InvalidSysCallTarget { - span: current_callee.span(), - source_file: current_caller.source_file.clone(), + span: current_caller.span, + source_file: self + .graph + .source_manager + .get(current_caller.span.source_id()) + .ok(), callee: current_callee.into_owned(), }); } break Ok(id); - } + }, Some(ResolvedProcedure::External(fqn)) => { // If we see that we're about to enter an infinite // resolver loop because of a recursive alias, return @@ -366,35 +335,61 @@ impl<'a> NameResolver<'a> { break Err(AssemblyError::Failed { labels: vec![ RelatedLabel::error("recursive alias") - .with_source_file(self.module_source(module_index)) + .with_source_file(self.graph.source_manager.get(fqn.span().source_id()).ok()) .with_labeled_span(fqn.span(), "occurs because this import causes import resolution to loop back on itself"), RelatedLabel::advice("recursive alias") - .with_source_file(caller.source_file.clone()) + .with_source_file(self.graph.source_manager.get(caller.span.source_id()).ok()) .with_labeled_span(caller.span, "as a result of resolving this procedure reference"), ], }); } - let source_file = self - .find_module_index(&fqn.module) - .and_then(|index| self.module_source(index)); current_caller = Cow::Owned(CallerInfo { span: fqn.span(), - source_file, module: module_index, kind: current_caller.kind, }); current_callee = Cow::Owned(fqn); - } + }, + Some(ResolvedProcedure::MastRoot(ref digest)) => { + if let Some(id) = self.graph.get_procedure_index_by_digest(digest) { + break Ok(id); + } + // This is a phantom procedure - we know its root, but do not have its + // definition + break Err(AssemblyError::Failed { + labels: vec![ + RelatedLabel::error("undefined procedure") + .with_source_file( + self.graph.source_manager.get(caller.span.source_id()).ok(), + ) + .with_labeled_span( + caller.span, + "unable to resolve this reference to its definition", + ), + RelatedLabel::error("name resolution cannot proceed") + .with_source_file( + self.graph + .source_manager + .get(current_callee.span().source_id()) + .ok(), + ) + .with_labeled_span( + current_callee.span(), + "this name cannot be resolved", + ), + ], + }); + }, None if matches!(current_caller.kind, InvokeKind::SysCall) => { if self.graph.has_nonempty_kernel() { // No kernel, so this invoke is invalid anyway break Err(AssemblyError::Failed { labels: vec![ RelatedLabel::error("undefined kernel procedure") - .with_source_file(caller.source_file.clone()) + .with_source_file(self.graph.source_manager.get(caller.span.source_id()).ok()) .with_labeled_span(caller.span, "unable to resolve this reference to a procedure in the current kernel"), RelatedLabel::error("invalid syscall") - .with_source_file(self.module_source(module_index)) + .with_source_file(self.graph.source_manager.get(current_callee.span().source_id()).ok()) .with_labeled_span( current_callee.span(), "this name cannot be resolved, because the assembler has an empty kernel", @@ -406,10 +401,10 @@ impl<'a> NameResolver<'a> { break Err(AssemblyError::Failed { labels: vec![ RelatedLabel::error("undefined kernel procedure") - .with_source_file(caller.source_file.clone()) + .with_source_file(self.graph.source_manager.get(caller.span.source_id()).ok()) .with_labeled_span(caller.span, "unable to resolve this reference to a procedure in the current kernel"), RelatedLabel::error("name resolution cannot proceed") - .with_source_file(self.module_source(module_index)) + .with_source_file(self.graph.source_manager.get(current_callee.span().source_id()).ok()) .with_labeled_span( current_callee.span(), "this name cannot be resolved", @@ -417,32 +412,39 @@ impl<'a> NameResolver<'a> { ] }); } - } + }, None => { // No such procedure known to `module` break Err(AssemblyError::Failed { labels: vec![ RelatedLabel::error("undefined procedure") - .with_source_file(caller.source_file.clone()) + .with_source_file( + self.graph.source_manager.get(caller.span.source_id()).ok(), + ) .with_labeled_span( caller.span, "unable to resolve this reference to its definition", ), RelatedLabel::error("name resolution cannot proceed") - .with_source_file(self.module_source(module_index)) + .with_source_file( + self.graph + .source_manager + .get(current_callee.span().source_id()) + .ok(), + ) .with_labeled_span( current_callee.span(), "this name cannot be resolved", ), ], }); - } + }, } } } /// Resolve a [LibraryPath] to a [ModuleIndex] in this graph - pub fn find_module_index(&self, name: &LibraryPath) -> Option { + fn find_module_index(&self, name: &LibraryPath) -> Option { self.graph .modules .iter() @@ -452,16 +454,6 @@ impl<'a> NameResolver<'a> { .map(ModuleIndex::new) } - fn module_source(&self, module: ModuleIndex) -> Option> { - let pending_offset = self.graph.modules.len(); - let module_index = module.as_usize(); - if module_index >= pending_offset { - self.pending[module_index - pending_offset].source_file.clone() - } else { - self.graph[module].source_file() - } - } - fn module_path(&self, module: ModuleIndex) -> LibraryPath { let pending_offset = self.graph.modules.len(); let module_index = module.as_usize(); diff --git a/assembly/src/assembler/module_graph/phantom.rs b/assembly/src/assembler/module_graph/phantom.rs deleted file mode 100644 index bf4956426f..0000000000 --- a/assembly/src/assembler/module_graph/phantom.rs +++ /dev/null @@ -1,45 +0,0 @@ -use alloc::sync::Arc; - -use crate::{diagnostics::SourceFile, RpoDigest, SourceSpan, Spanned}; - -/// Represents a call to a procedure for which we do not have an implementation. -/// -/// Such calls are still valid, as at runtime they may be supplied to the VM, but we are limited -/// in how much we can reason about such procedures, so we represent them and handle them -/// explicitly. -#[derive(Clone)] -pub struct PhantomCall { - /// The source span associated with the call - pub span: SourceSpan, - /// The source file corresponding to `span`, if available - #[allow(dead_code)] - pub source_file: Option>, - /// The MAST root of the callee - pub callee: RpoDigest, -} - -impl Spanned for PhantomCall { - fn span(&self) -> SourceSpan { - self.span - } -} - -impl Eq for PhantomCall {} - -impl PartialEq for PhantomCall { - fn eq(&self, other: &Self) -> bool { - self.callee.eq(&other.callee) - } -} - -impl Ord for PhantomCall { - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - self.callee.cmp(&other.callee) - } -} - -impl PartialOrd for PhantomCall { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} diff --git a/assembly/src/assembler/module_graph/procedure_cache.rs b/assembly/src/assembler/module_graph/procedure_cache.rs deleted file mode 100644 index 0980c566ad..0000000000 --- a/assembly/src/assembler/module_graph/procedure_cache.rs +++ /dev/null @@ -1,403 +0,0 @@ -use alloc::{ - collections::{BTreeMap, VecDeque}, - sync::Arc, - vec::Vec, -}; -use core::{fmt, ops::Index}; - -use crate::{ - assembler::{GlobalProcedureIndex, ModuleIndex, Procedure}, - ast::{FullyQualifiedProcedureName, ProcedureIndex}, - AssemblyError, LibraryPath, RpoDigest, -}; - -// PROCEDURE CACHE -// ================================================================================================ - -/// The [ProcedureCache] is responsible for caching the MAST of compiled procedures. -/// -/// Once cached, subsequent compilations will use the cached MAST artifacts, rather than -/// recompiling the same procedures again and again. -/// -/// # Usage -/// -/// The procedure cache is intimately tied to a [ModuleGraph], which effectively acts as a cache -/// for the MASM syntax tree, and associates each procedure with a unique [GlobalProcedureIndex] -/// which acts as the cache key for the corresponding [ProcedureCache]. -/// -/// This also is how we avoid serving cached artifacts when the syntax tree of a module is modified -/// and recompiled - the old module will be removed from the [ModuleGraph] and the new version will -/// be added as a new module, getting new [GlobalProcedureIndex]s for each of its procedures as a -/// result. -/// -/// As a result of this design choice, a unique [ProcedureCache] is associated with each context in -/// play during compilation: the global assembler context has its own cache, and each -/// [AssemblyContext] has its own cache. -#[derive(Default)] -pub struct ProcedureCache { - cache: Vec>>>, - /// This is always the same length as `cache` - modules: Vec>, - by_mast_root: BTreeMap, -} - -/// When indexing by [ModuleIndex], we return the [LibraryPath] of the [Module] -/// to which that cache slot belongs. -impl Index for ProcedureCache { - type Output = LibraryPath; - fn index(&self, id: ModuleIndex) -> &Self::Output { - self.modules[id.as_usize()].as_ref().expect("attempted to index an empty cache") - } -} - -/// When indexing by [GlobalProcedureIndex], we return the cached [Procedure] -impl Index for ProcedureCache { - type Output = Arc; - fn index(&self, id: GlobalProcedureIndex) -> &Self::Output { - self.cache[id.module.as_usize()][id.index.as_usize()] - .as_ref() - .expect("attempted to index an empty cache slot") - } -} - -impl ProcedureCache { - /// Returns true if the cache is empty. - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// Returns the number of procedures in the cache. - pub fn len(&self) -> usize { - self.cache.iter().map(|m| m.iter().filter_map(|p| p.as_deref()).count()).sum() - } - - /// Searches for a procedure in the cache using `predicate`. - #[allow(unused)] - pub fn find(&self, mut predicate: F) -> Option> - where - F: FnMut(&Procedure) -> bool, - { - self.cache.iter().find_map(|m| { - m.iter().filter_map(|p| p.as_ref()).find_map(|p| { - if predicate(p) { - Some(p.clone()) - } else { - None - } - }) - }) - } - - /// Searches for a procedure in the cache using `predicate`, starting from procedures with the - /// highest [ModuleIndex] to lowest. - #[allow(unused)] - pub fn rfind(&self, mut predicate: F) -> Option> - where - F: FnMut(&Procedure) -> bool, - { - self.cache.iter().rev().find_map(|m| { - m.iter().filter_map(|p| p.as_ref()).find_map(|p| { - if predicate(p) { - Some(p.clone()) - } else { - None - } - }) - }) - } - - /// Looks up a procedure by its MAST root. - pub fn get_by_mast_root(&self, digest: &RpoDigest) -> Option> { - self.by_mast_root.get(digest).copied().map(|index| self[index].clone()) - } - - /// Looks up a procedure by its fully-qualified name. - /// - /// NOTE: If a procedure with the same name is cached twice, this will return the version with - /// the highest [ModuleIndex]. - #[allow(unused)] - pub fn get_by_name(&self, name: &FullyQualifiedProcedureName) -> Option> { - self.rfind(|p| p.fully_qualified_name() == name) - } - - /// Returns the procedure with the given [GlobalProcedureIndex], if it is cached. - pub fn get(&self, id: GlobalProcedureIndex) -> Option> { - self.cache - .get(id.module.as_usize()) - .and_then(|m| m.get(id.index.as_usize()).and_then(|p| p.clone())) - } - - /// Returns true if the procedure with the given [GlobalProcedureIndex] is cached. - #[allow(unused)] - pub fn contains_key(&self, id: GlobalProcedureIndex) -> bool { - self.cache - .get(id.module.as_usize()) - .map(|m| m.get(id.index.as_usize()).is_some()) - .unwrap_or(false) - } - - /// Returns true if the procedure with the given MAST root is cached. - #[allow(unused)] - pub fn contains_mast_root(&self, hash: &RpoDigest) -> bool { - self.by_mast_root.contains_key(hash) - } - - /// Returns an iterator over the non-empty entries in the cache - #[cfg(test)] - pub fn entries(&self) -> impl Iterator> + '_ { - self.cache.iter().flat_map(|m| m.iter().filter_map(|p| p.clone())) - } - - /// Inserts the given [Procedure] into this cache, using the [GlobalProcedureIndex] as the - /// cache key. - /// - /// # Errors - /// - /// This operation will fail under the following conditions: - /// - The cache slot for the given [GlobalProcedureIndex] is occupied with a conflicting - /// definition. - /// - A procedure with the same MAST root is already in the cache, but the two procedures have - /// differing metadata (such as the number of locals, etc). - pub fn insert( - &mut self, - id: GlobalProcedureIndex, - procedure: Arc, - ) -> Result<(), AssemblyError> { - let mast_root = procedure.mast_root(); - - // Make sure we can index to the cache slot for this procedure - self.ensure_cache_slot_exists(id, procedure.path()); - - // Check if an entry is already in this cache slot. - // - // If there is already a cache entry, but it conflicts with what we're trying to cache, - // then raise an error. - if let Some(cached) = self.get(id) { - if cached.mast_root() != mast_root || cached.num_locals() != procedure.num_locals() { - return Err(AssemblyError::ConflictingDefinitions { - first: cached.fully_qualified_name().clone(), - second: procedure.fully_qualified_name().clone(), - }); - } - - // The global procedure index and the MAST root resolve to an already cached version of - // this procedure, nothing to do. - // - // TODO: We should emit a warning for this, because while it is not an error per se, it - // does reflect that we're doing work we don't need to be doing. However, emitting a - // warning only makes sense if this is controllable by the user, and it isn't yet - // clear whether this edge case will ever happen in practice anyway. - return Ok(()); - } - - // We don't have a cache entry yet, but we do want to make sure we don't have a conflicting - // cache entry with the same MAST root: - if let Some(cached) = self.get_by_mast_root(&mast_root) { - // Sanity check - assert_eq!(cached.mast_root(), mast_root); - - if cached.num_locals() != procedure.num_locals() { - return Err(AssemblyError::ConflictingDefinitions { - first: cached.fully_qualified_name().clone(), - second: procedure.fully_qualified_name().clone(), - }); - } - - // We have a previously cached version of an equivalent procedure, just under a - // different [GlobalProcedureIndex], so insert the cached procedure into the slot for - // `id`, but skip inserting a record in the MAST root lookup table - self.cache[id.module.as_usize()][id.index.as_usize()] = Some(procedure); - return Ok(()); - } - - // This is a new entry, so record both the cache entry and the MAST root mapping - self.cache[id.module.as_usize()][id.index.as_usize()] = Some(procedure); - self.by_mast_root.insert(mast_root, id); - - Ok(()) - } - - /// This removes any entries in the cache for procedures in `module` - pub fn remove_module(&mut self, module: ModuleIndex) { - let index = module.as_usize(); - if let Some(slots) = self.cache.get_mut(index) { - slots.clear(); - } - if let Some(path) = self.modules.get_mut(index) { - *path = None; - } - self.by_mast_root.retain(|_digest, gid| gid.module != module); - } - - fn ensure_cache_slot_exists(&mut self, id: GlobalProcedureIndex, module: &LibraryPath) { - let min_cache_len = id.module.as_usize() + 1; - let min_module_len = id.index.as_usize() + 1; - - if self.cache.len() < min_cache_len { - self.cache.resize(min_cache_len, Vec::default()); - self.modules.resize(min_cache_len, None); - } - - // If this is the first entry for this module index, record the path to the module for - // future queries - let module_name = &mut self.modules[id.module.as_usize()]; - if module_name.is_none() { - *module_name = Some(module.clone()); - } - - let module_cache = &mut self.cache[id.module.as_usize()]; - if module_cache.len() < min_module_len { - module_cache.resize(min_module_len, None); - } - } -} - -impl IntoIterator for ProcedureCache { - type Item = (GlobalProcedureIndex, Arc); - type IntoIter = IntoIter; - - fn into_iter(self) -> Self::IntoIter { - let empty = self.is_empty(); - let pos = (0, 0); - IntoIter { - empty, - pos, - cache: VecDeque::from_iter(self.cache.into_iter().map(VecDeque::from)), - } - } -} - -pub struct IntoIter { - cache: VecDeque>>>, - pos: (usize, usize), - empty: bool, -} - -impl Iterator for IntoIter { - type Item = (GlobalProcedureIndex, Arc); - - fn next(&mut self) -> Option { - if self.empty { - return None; - } - - loop { - let (module, index) = self.pos; - if let Some(slot) = self.cache[module].pop_front() { - self.pos.1 += 1; - if let Some(procedure) = slot { - let gid = GlobalProcedureIndex { - module: ModuleIndex::new(module), - index: ProcedureIndex::new(index), - }; - break Some((gid, procedure)); - } - continue; - } - - // We've reached the end of this module cache - self.cache.pop_front(); - self.pos.0 += 1; - - // Check if we've reached the end of the overall cache - if self.cache.is_empty() { - self.empty = true; - break None; - } - } - } -} - -impl fmt::Debug for ProcedureCache { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("ProcedureCache") - .field("modules", &DisplayCachedModules(self)) - .finish() - } -} - -#[doc(hidden)] -struct DisplayCachedModules<'a>(&'a ProcedureCache); - -impl<'a> fmt::Debug for DisplayCachedModules<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let roots = &self.0.by_mast_root; - f.debug_map() - .entries(self.0.modules.iter().enumerate().zip(self.0.cache.iter()).filter_map( - |((index, path), slots)| { - path.as_ref().map(|path| { - ( - ModuleSlot { - index, - module: path, - }, - DisplayCachedProcedures { - roots, - module: index, - slots: slots.as_slice(), - }, - ) - }) - }, - )) - .finish() - } -} - -#[doc(hidden)] -struct DisplayCachedProcedures<'a> { - roots: &'a BTreeMap, - slots: &'a [Option>], - module: usize, -} - -impl<'a> fmt::Debug for DisplayCachedProcedures<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_set() - .entries(self.slots.iter().enumerate().filter_map(|(index, p)| { - p.as_deref().map(|p| ProcedureSlot { - roots: self.roots, - module: self.module, - index, - procedure: p, - }) - })) - .finish() - } -} - -// NOTE: Clippy thinks these fields are dead because it doesn't recognize that they are used by the -// `debug_map` implementation. -#[derive(Debug)] -#[allow(dead_code)] -struct ModuleSlot<'a> { - index: usize, - module: &'a LibraryPath, -} - -#[doc(hidden)] -struct ProcedureSlot<'a> { - roots: &'a BTreeMap, - module: usize, - index: usize, - procedure: &'a Procedure, -} - -impl<'a> fmt::Debug for ProcedureSlot<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let id = GlobalProcedureIndex { - module: ModuleIndex::new(self.module), - index: ProcedureIndex::new(self.index), - }; - let digest = self - .roots - .iter() - .find_map(|(hash, gid)| if gid == &id { Some(hash) } else { None }) - .expect("missing root for cache entry"); - f.debug_struct("CacheEntry") - .field("index", &self.index) - .field("key", digest) - .field("procedure", self.procedure) - .finish() - } -} diff --git a/assembly/src/assembler/module_graph/rewrites/module.rs b/assembly/src/assembler/module_graph/rewrites/module.rs index 51145e593b..4d5048b706 100644 --- a/assembly/src/assembler/module_graph/rewrites/module.rs +++ b/assembly/src/assembler/module_graph/rewrites/module.rs @@ -1,19 +1,21 @@ -use alloc::{collections::BTreeSet, sync::Arc}; +use alloc::collections::BTreeSet; use core::ops::ControlFlow; use crate::{ assembler::{ - module_graph::{CallerInfo, NameResolver, PhantomCall}, + module_graph::{CallerInfo, NameResolver}, ModuleIndex, ResolvedTarget, }, ast::{ visit::{self, VisitMut}, - InvocationTarget, Invoke, InvokeKind, Module, Procedure, + AliasTarget, InvocationTarget, Invoke, InvokeKind, Module, Procedure, }, - diagnostics::SourceFile, - AssemblyError, Span, Spanned, + AssemblyError, SourceSpan, Spanned, }; +// MODULE REWRITE CHECK +// ================================================================================================ + /// A [ModuleRewriter] handles applying all of the module-wide rewrites to a [Module] that is being /// added to a [ModuleGraph]. These rewrites include: /// @@ -23,9 +25,8 @@ use crate::{ pub struct ModuleRewriter<'a, 'b: 'a> { resolver: &'a NameResolver<'b>, module_id: ModuleIndex, + span: SourceSpan, invoked: BTreeSet, - phantoms: BTreeSet, - source_file: Option>, } impl<'a, 'b: 'a> ModuleRewriter<'a, 'b> { @@ -34,9 +35,8 @@ impl<'a, 'b: 'a> ModuleRewriter<'a, 'b> { Self { resolver, module_id: ModuleIndex::new(u16::MAX as usize), + span: Default::default(), invoked: Default::default(), - phantoms: Default::default(), - source_file: None, } } @@ -47,7 +47,7 @@ impl<'a, 'b: 'a> ModuleRewriter<'a, 'b> { module: &mut Module, ) -> Result<(), AssemblyError> { self.module_id = module_id; - self.source_file = module.source_file(); + self.span = module.span(); if let ControlFlow::Break(err) = self.visit_mut_module(module) { return Err(err); @@ -56,11 +56,6 @@ impl<'a, 'b: 'a> ModuleRewriter<'a, 'b> { Ok(()) } - /// Take the set of accumulated phantom calls out of this rewriter - pub fn phantoms(&mut self) -> BTreeSet { - core::mem::take(&mut self.phantoms) - } - fn rewrite_target( &mut self, kind: InvokeKind, @@ -68,42 +63,19 @@ impl<'a, 'b: 'a> ModuleRewriter<'a, 'b> { ) -> ControlFlow { let caller = CallerInfo { span: target.span(), - source_file: self.source_file.clone(), module: self.module_id, kind, }; match self.resolver.resolve_target(&caller, target) { Err(err) => return ControlFlow::Break(err), - Ok(ResolvedTarget::Cached { digest, .. }) => { - *target = InvocationTarget::MastRoot(Span::new(target.span(), digest)); - self.invoked.insert(Invoke { - kind, - target: target.clone(), - }); - } - Ok(ResolvedTarget::Phantom(callee)) => { - let call = PhantomCall { - span: target.span(), - source_file: self.source_file.clone(), - callee, - }; - self.phantoms.insert(call); - } + Ok(ResolvedTarget::Phantom(_)) => (), Ok(ResolvedTarget::Exact { .. }) => { - self.invoked.insert(Invoke { - kind, - target: target.clone(), - }); - } - Ok(ResolvedTarget::Resolved { - target: new_target, .. - }) => { + self.invoked.insert(Invoke { kind, target: target.clone() }); + }, + Ok(ResolvedTarget::Resolved { target: new_target, .. }) => { *target = new_target; - self.invoked.insert(Invoke { - kind, - target: target.clone(), - }); - } + self.invoked.insert(Invoke { kind, target: target.clone() }); + }, } ControlFlow::Continue(()) @@ -130,4 +102,14 @@ impl<'a, 'b: 'a> VisitMut for ModuleRewriter<'a, 'b> { ) -> ControlFlow { self.rewrite_target(InvokeKind::Exec, target) } + fn visit_mut_alias_target(&mut self, target: &mut AliasTarget) -> ControlFlow { + if matches!(target, AliasTarget::MastRoot(_)) { + return ControlFlow::Continue(()); + } + let mut invoke_target = (target as &AliasTarget).into(); + self.rewrite_target(InvokeKind::ProcRef, &mut invoke_target)?; + // This will always succeed, as the original target is qualified by construction + *target = AliasTarget::try_from(invoke_target).unwrap(); + ControlFlow::Continue(()) + } } diff --git a/assembly/src/assembler/procedure.rs b/assembly/src/assembler/procedure.rs index 0d0ba911df..167a25806e 100644 --- a/assembly/src/assembler/procedure.rs +++ b/assembly/src/assembler/procedure.rs @@ -1,55 +1,159 @@ -use alloc::{collections::BTreeSet, sync::Arc}; +use alloc::sync::Arc; +use vm_core::mast::MastNodeId; + +use super::GlobalProcedureIndex; use crate::{ - ast::{FullyQualifiedProcedureName, ProcedureName, Visibility}, - diagnostics::SourceFile, - LibraryPath, RpoDigest, SourceSpan, Spanned, + ast::{ProcedureName, QualifiedProcedureName, Visibility}, + diagnostics::{SourceManager, SourceSpan, Spanned}, + LibraryPath, RpoDigest, }; -use vm_core::code_blocks::CodeBlock; -pub type CallSet = BTreeSet; +// PROCEDURE CONTEXT +// ================================================================================================ + +/// Information about a procedure currently being compiled. +pub struct ProcedureContext { + source_manager: Arc, + gid: GlobalProcedureIndex, + span: SourceSpan, + name: QualifiedProcedureName, + visibility: Visibility, + is_kernel: bool, + num_locals: u16, +} + +// ------------------------------------------------------------------------------------------------ +/// Constructors +impl ProcedureContext { + pub fn new( + gid: GlobalProcedureIndex, + name: QualifiedProcedureName, + visibility: Visibility, + is_kernel: bool, + source_manager: Arc, + ) -> Self { + Self { + source_manager, + gid, + span: name.span(), + name, + visibility, + is_kernel, + num_locals: 0, + } + } + + pub fn with_num_locals(mut self, num_locals: u16) -> Self { + self.num_locals = num_locals; + self + } + + pub fn with_span(mut self, span: SourceSpan) -> Self { + self.span = span; + self + } +} + +// ------------------------------------------------------------------------------------------------ +/// Public accessors +impl ProcedureContext { + pub fn id(&self) -> GlobalProcedureIndex { + self.gid + } + + pub fn name(&self) -> &QualifiedProcedureName { + &self.name + } + + pub fn num_locals(&self) -> u16 { + self.num_locals + } + + #[allow(unused)] + pub fn module(&self) -> &LibraryPath { + &self.name.module + } + + /// Returns true if the procedure is being assembled for a kernel. + pub fn is_kernel(&self) -> bool { + self.is_kernel + } + + #[inline(always)] + pub fn source_manager(&self) -> &dyn SourceManager { + self.source_manager.as_ref() + } +} + +// ------------------------------------------------------------------------------------------------ +/// State mutators +impl ProcedureContext { + /// Transforms this procedure context into a [Procedure]. + /// + /// The passed-in `mast_root` defines the MAST root of the procedure's body while + /// `mast_node_id` specifies the ID of the procedure's body node in the MAST forest in + /// which the procedure is defined. Note that if the procedure is re-exported (i.e., the body + /// of the procedure is defined in some other MAST forest) `mast_node_id` will point to a + /// single `External` node. + /// + ///
+ /// `mast_root` and `mast_node_id` must be consistent. That is, the node located in the MAST + /// forest under `mast_node_id` must have the digest equal to the `mast_root`. + ///
+ pub fn into_procedure(self, mast_root: RpoDigest, mast_node_id: MastNodeId) -> Procedure { + Procedure::new(self.name, self.visibility, self.num_locals as u32, mast_root, mast_node_id) + .with_span(self.span) + } +} + +impl Spanned for ProcedureContext { + fn span(&self) -> SourceSpan { + self.span + } +} // PROCEDURE // ================================================================================================ -/// A compiled Miden Assembly procedure, consisting of MAST and basic metadata. +/// A compiled Miden Assembly procedure, consisting of MAST info and basic metadata. /// /// Procedure metadata includes: /// -/// * Fully-qualified path of the procedure in Miden Assembly (if known). -/// * Number of procedure locals to allocate. -/// * The visibility of the procedure (e.g. public/private/syscall) -/// * The set of MAST roots invoked by this procedure. -/// * The original source span and file of the procedure (if available). +/// - Fully-qualified path of the procedure in Miden Assembly (if known). +/// - Number of procedure locals to allocate. +/// - The visibility of the procedure (e.g. public/private/syscall) +/// - The set of MAST roots invoked by this procedure. +/// - The original source span and file of the procedure (if available). #[derive(Clone, Debug)] pub struct Procedure { span: SourceSpan, - source_file: Option>, - path: FullyQualifiedProcedureName, + path: QualifiedProcedureName, visibility: Visibility, num_locals: u32, - /// The MAST for this procedure - code: CodeBlock, - /// The set of MAST roots called by this procedure - callset: CallSet, + /// The MAST root of the procedure. + mast_root: RpoDigest, + /// The MAST node id which resolves to the above MAST root. + body_node_id: MastNodeId, } -/// Builder +// ------------------------------------------------------------------------------------------------ +/// Constructors impl Procedure { - pub(crate) fn new( - path: FullyQualifiedProcedureName, + fn new( + path: QualifiedProcedureName, visibility: Visibility, num_locals: u32, - code: CodeBlock, + mast_root: RpoDigest, + body_node_id: MastNodeId, ) -> Self { Self { span: SourceSpan::default(), - source_file: None, path, visibility, num_locals, - code, - callset: Default::default(), + mast_root, + body_node_id, } } @@ -57,27 +161,19 @@ impl Procedure { self.span = span; self } - - pub(crate) fn with_source_file(mut self, source_file: Option>) -> Self { - self.source_file = source_file; - self - } - - pub(crate) fn with_callset(mut self, callset: CallSet) -> Self { - self.callset = callset; - self - } } -/// Metadata +// ------------------------------------------------------------------------------------------------ +/// Public accessors impl Procedure { /// Returns a reference to the name of this procedure + #[allow(unused)] pub fn name(&self) -> &ProcedureName { &self.path.name } /// Returns a reference to the fully-qualified name of this procedure - pub fn fully_qualified_name(&self) -> &FullyQualifiedProcedureName { + pub fn fully_qualified_name(&self) -> &QualifiedProcedureName { &self.path } @@ -91,12 +187,6 @@ impl Procedure { &self.path.module } - /// Returns a reference to the Miden Assembly source file from which this - /// procedure was compiled, if available. - pub fn source_file(&self) -> Option> { - self.source_file.clone() - } - /// Returns the number of memory locals reserved by the procedure. pub fn num_locals(&self) -> u32 { self.num_locals @@ -104,18 +194,12 @@ impl Procedure { /// Returns the root of this procedure's MAST. pub fn mast_root(&self) -> RpoDigest { - self.code.hash() - } - - /// Returns a reference to the MAST of this procedure. - pub fn code(&self) -> &CodeBlock { - &self.code + self.mast_root } - /// Returns a reference to a set of all procedures (identified by their MAST roots) which may - /// be called during the execution of this procedure. - pub fn callset(&self) -> &CallSet { - &self.callset + /// Returns a reference to the MAST node ID of this procedure. + pub fn body_node_id(&self) -> MastNodeId { + self.body_node_id } } diff --git a/assembly/src/assembler/span_builder.rs b/assembly/src/assembler/span_builder.rs deleted file mode 100644 index 349b5d1393..0000000000 --- a/assembly/src/assembler/span_builder.rs +++ /dev/null @@ -1,147 +0,0 @@ -use super::{AssemblyContext, BodyWrapper, Decorator, DecoratorList, Instruction}; -use alloc::{borrow::Borrow, string::ToString, vec::Vec}; -use vm_core::{code_blocks::CodeBlock, AdviceInjector, AssemblyOp, Operation}; - -// SPAN BUILDER -// ================================================================================================ - -/// A helper struct for constructing SPAN blocks while compiling procedure bodies. -/// -/// Operations and decorators can be added to a span builder via various `add_*()` and `push_*()` -/// methods, and then SPAN blocks can be extracted from the builder via `extract_*()` methods. -/// -/// The same span builder can be used to construct many blocks. It is expected that when the last -/// SPAN block in a procedure's body is constructed `extract_final_span_into()` will be used. -#[derive(Default)] -pub struct SpanBuilder { - ops: Vec, - decorators: DecoratorList, - epilogue: Vec, - last_asmop_pos: usize, -} - -/// Constructors -impl SpanBuilder { - /// Returns a new [SpanBuilder] instantiated with the specified optional wrapper. - /// - /// If the wrapper is provided, the prologue of the wrapper is immediately appended to the - /// vector of span operations. The epilogue of the wrapper is appended to the list of - /// operations upon consumption of the builder via `extract_final_span_into()` method. - pub(super) fn new(wrapper: Option) -> Self { - match wrapper { - Some(wrapper) => Self { - ops: wrapper.prologue, - decorators: Vec::new(), - epilogue: wrapper.epilogue, - last_asmop_pos: 0, - }, - None => Self::default(), - } - } -} - -/// Operations -impl SpanBuilder { - /// Adds the specified operation to the list of span operations. - pub fn push_op(&mut self, op: Operation) { - self.ops.push(op); - } - - /// Adds the specified sequence of operations to the list of span operations. - pub fn push_ops(&mut self, ops: I) - where - I: IntoIterator, - O: Borrow, - { - self.ops.extend(ops.into_iter().map(|o| *o.borrow())); - } - - /// Adds the specified operation n times to the list of span operations. - pub fn push_op_many(&mut self, op: Operation, n: usize) { - let new_len = self.ops.len() + n; - self.ops.resize(new_len, op); - } -} - -/// Decorators -impl SpanBuilder { - /// Add ths specified decorator to the list of span decorators. - pub fn push_decorator(&mut self, decorator: Decorator) { - self.decorators.push((self.ops.len(), decorator)); - } - - /// Adds the specified advice injector to the list of span decorators. - pub fn push_advice_injector(&mut self, injector: AdviceInjector) { - self.push_decorator(Decorator::Advice(injector)); - } - - /// Adds an AsmOp decorator to the list of span decorators. - /// - /// This indicates that the provided instruction should be tracked and the cycle count for - /// this instruction will be computed when the call to set_instruction_cycle_count() is made. - pub fn track_instruction(&mut self, instruction: &Instruction, ctx: &AssemblyContext) { - let context_name = ctx.unwrap_current_procedure().name().to_string(); - let num_cycles = 0; - let op = instruction.to_string(); - let should_break = instruction.should_break(); - let op = AssemblyOp::new(context_name, num_cycles, op, should_break); - self.push_decorator(Decorator::AsmOp(op)); - self.last_asmop_pos = self.decorators.len() - 1; - } - - /// Computes the number of cycles elapsed since the last invocation of track_instruction() - /// and updates the related AsmOp decorator to include this cycle count. - /// - /// If the cycle count is 0, the original decorator is removed from the list. This can happen - /// for instructions which do not contribute any operations to the span block - e.g., exec, - /// call, and syscall. - pub fn set_instruction_cycle_count(&mut self) { - // get the last asmop decorator and the cycle at which it was added - let (op_start, assembly_op) = - self.decorators.get_mut(self.last_asmop_pos).expect("no asmop decorator"); - assert!(matches!(assembly_op, Decorator::AsmOp(_))); - - // compute the cycle count for the instruction - let cycle_count = self.ops.len() - *op_start; - - // if the cycle count is 0, remove the decorator; otherwise update its cycle count - if cycle_count == 0 { - self.decorators.remove(self.last_asmop_pos); - } else if let Decorator::AsmOp(assembly_op) = assembly_op { - assembly_op.set_num_cycles(cycle_count as u8) - } - } -} - -/// Span Constructors -impl SpanBuilder { - /// Creates a new SPAN block from the operations and decorators currently in this builder and - /// appends the block to the provided target. - /// - /// This consumes all operations and decorators in the builder, but does not touch the - /// operations in the epilogue of the builder. - pub fn extract_span_into(&mut self, target: &mut Vec) { - if !self.ops.is_empty() { - let ops = self.ops.drain(..).collect(); - let decorators = self.decorators.drain(..).collect(); - target.push(CodeBlock::new_span_with_decorators(ops, decorators)); - } else if !self.decorators.is_empty() { - // this is a bug in the assembler. we shouldn't have decorators added without their - // associated operations - // TODO: change this to an error or allow decorators in empty span blocks - unreachable!("decorators in an empty SPAN block") - } - } - - /// Creates a new SPAN block from the operations and decorators currently in this builder and - /// appends the block to the provided target. - /// - /// The main differences from the `extract_span_int()` method above are: - /// - Operations contained in the epilogue of the span builder are appended to the list of ops - /// which go into the new SPAN block. - /// - The span builder is consumed in the process. - pub fn extract_final_span_into(mut self, target: &mut Vec) { - self.ops.append(&mut self.epilogue); - self.extract_span_into(target); - } -} diff --git a/assembly/src/assembler/tests.rs b/assembly/src/assembler/tests.rs index 0f8a3dd785..cb45e88ca8 100644 --- a/assembly/src/assembler/tests.rs +++ b/assembly/src/assembler/tests.rs @@ -1,81 +1,63 @@ -use alloc::{boxed::Box, vec::Vec}; +use alloc::vec::Vec; -use super::{combine_blocks, Assembler, CodeBlock, Library, Operation}; +use pretty_assertions::assert_eq; +use vm_core::{ + assert_matches, + crypto::hash::RpoDigest, + mast::{MastForest, MastNode}, + Program, +}; + +use super::{Assembler, Operation}; use crate::{ - ast::{Module, ModuleKind}, - LibraryNamespace, Version, + assembler::mast_forest_builder::MastForestBuilder, diagnostics::Report, testing::TestContext, }; // TESTS // ================================================================================================ #[test] -fn nested_blocks() { - const MODULE: &str = "foo::bar"; +fn nested_blocks() -> Result<(), Report> { const KERNEL: &str = r#" export.foo add end"#; - const PROCEDURE: &str = r#" + const MODULE: &str = "foo::bar"; + const MODULE_PROCEDURE: &str = r#" export.baz push.29 end"#; - pub struct DummyLibrary { - namespace: LibraryNamespace, - #[allow(clippy::vec_box)] - modules: Vec>, - dependencies: Vec, - } - - impl Default for DummyLibrary { - fn default() -> Self { - let ast = - Module::parse_str(MODULE.parse().unwrap(), ModuleKind::Library, PROCEDURE).unwrap(); - let namespace = ast.namespace().clone(); - Self { - namespace, - modules: vec![ast], - dependencies: Vec::new(), - } - } - } - - impl Library for DummyLibrary { - fn root_ns(&self) -> &LibraryNamespace { - &self.namespace - } - - fn version(&self) -> &Version { - const MIN: Version = Version::min(); - &MIN - } - - fn modules(&self) -> impl ExactSizeIterator + '_ { - self.modules.iter().map(|m| m.as_ref()) - } - - fn dependencies(&self) -> &[LibraryNamespace] { - &self.dependencies - } - } - - let mut assembler = Assembler::with_kernel_from_module(KERNEL) - .unwrap() - .with_library(&DummyLibrary::default()) - .unwrap(); + let context = TestContext::new(); + let assembler = { + let kernel_lib = Assembler::new(context.source_manager()).assemble_kernel(KERNEL).unwrap(); + + let dummy_module = + context.parse_module_with_path(MODULE.parse().unwrap(), MODULE_PROCEDURE)?; + let dummy_library = Assembler::new(context.source_manager()) + .assemble_library([dummy_module]) + .unwrap(); + + let mut assembler = Assembler::with_kernel(context.source_manager(), kernel_lib); + assembler.add_library(dummy_library).unwrap(); - // the assembler should have a single kernel proc in its cache before the compilation of the - // source - assert_eq!(assembler.procedure_cache().len(), 1); + assembler + }; + + // The expected `MastForest` for the program (that we will build by hand) + let mut expected_mast_forest_builder = MastForestBuilder::default(); // fetch the kernel digest and store into a syscall block - let syscall = assembler - .procedure_cache() - .entries() - .next() - .map(|p| CodeBlock::new_syscall(p.mast_root())) - .unwrap(); + // + // Note: this assumes the current internal implementation detail that `assembler.mast_forest` + // contains the MAST nodes for the kernel after a call to + // `Assembler::with_kernel_from_module()`. + let syscall_foo_node_id = { + let kernel_foo_node_id = + expected_mast_forest_builder.ensure_block(vec![Operation::Add], None).unwrap(); + + expected_mast_forest_builder.ensure_syscall(kernel_foo_node_id).unwrap() + }; let program = r#" use.foo::bar @@ -113,38 +95,266 @@ fn nested_blocks() { syscall.foo end"#; - let program = assembler.assemble(program).unwrap(); + let program = assembler.assemble_program(program).unwrap(); + + // basic block representing foo::bar.baz procedure + let exec_foo_bar_baz_node_id = expected_mast_forest_builder + .ensure_block(vec![Operation::Push(29_u32.into())], None) + .unwrap(); + + let before = expected_mast_forest_builder + .ensure_block(vec![Operation::Push(2u32.into())], None) + .unwrap(); + + let r#true1 = expected_mast_forest_builder + .ensure_block(vec![Operation::Push(3u32.into())], None) + .unwrap(); + let r#false1 = expected_mast_forest_builder + .ensure_block(vec![Operation::Push(5u32.into())], None) + .unwrap(); + let r#if1 = expected_mast_forest_builder.ensure_split(r#true1, r#false1).unwrap(); + + let r#true3 = expected_mast_forest_builder + .ensure_block(vec![Operation::Push(7u32.into())], None) + .unwrap(); + let r#false3 = expected_mast_forest_builder + .ensure_block(vec![Operation::Push(11u32.into())], None) + .unwrap(); + let r#true2 = expected_mast_forest_builder.ensure_split(r#true3, r#false3).unwrap(); + + let r#while = { + let body_node_id = expected_mast_forest_builder + .ensure_block( + vec![ + Operation::Push(17u32.into()), + Operation::Push(19u32.into()), + Operation::Push(23u32.into()), + ], + None, + ) + .unwrap(); - let exec_bar = assembler - .procedure_cache() - .get_by_name(&"#exec::bar".parse().unwrap()) - .map(|p| CodeBlock::new_proxy(p.code().hash())) + expected_mast_forest_builder.ensure_loop(body_node_id).unwrap() + }; + let push_13_basic_block_id = expected_mast_forest_builder + .ensure_block(vec![Operation::Push(13u32.into())], None) .unwrap(); - let exec_foo_bar_baz = assembler - .procedure_cache() - .get_by_name(&"foo::bar::baz".parse().unwrap()) - .map(|p| CodeBlock::new_proxy(p.code().hash())) + let r#false2 = expected_mast_forest_builder + .ensure_join(push_13_basic_block_id, r#while) .unwrap(); + let nested = expected_mast_forest_builder.ensure_split(r#true2, r#false2).unwrap(); + + let combined_node_id = expected_mast_forest_builder + .join_nodes(vec![before, r#if1, nested, exec_foo_bar_baz_node_id, syscall_foo_node_id]) + .unwrap(); + + let mut expected_mast_forest = expected_mast_forest_builder.build().0; + expected_mast_forest.make_root(combined_node_id); + let expected_program = Program::new(expected_mast_forest.into(), combined_node_id); + assert_eq!(expected_program.hash(), program.hash()); + + // also check that the program has the right number of procedures (which excludes the dummy + // library and kernel) + assert_eq!(program.num_procedures(), 3); + + Ok(()) +} + +/// Ensures that the arguments of `emit` do indeed modify the digest of a basic block +#[test] +fn emit_instruction_digest() { + let context = TestContext::new(); + + let program_source = r#" + proc.foo + emit.1 + end + + proc.bar + emit.2 + end + + begin + # specific impl irrelevant + exec.foo + exec.bar + end + "#; + + let program = context.assemble(program_source).unwrap(); + + let procedure_digests: Vec = program.mast_forest().procedure_digests().collect(); + + // foo, bar and entrypoint + assert_eq!(3, procedure_digests.len()); + + // Ensure that foo, bar and entrypoint all have different digests + assert_ne!(procedure_digests[0], procedure_digests[1]); + assert_ne!(procedure_digests[0], procedure_digests[2]); + assert_ne!(procedure_digests[1], procedure_digests[2]); +} + +/// Since `foo` and `bar` have the same body, we only expect them to be added once to the program. +#[test] +fn duplicate_procedure() { + let context = TestContext::new(); + + let program_source = r#" + proc.foo + add + mul + end + + proc.bar + add + mul + end + + begin + # specific impl irrelevant + exec.foo + exec.bar + end + "#; + + let program = context.assemble(program_source).unwrap(); + assert_eq!(program.num_procedures(), 2); +} + +#[test] +fn distinguish_grandchildren_correctly() { + let context = TestContext::new(); + + let program_source = r#" + begin + if.true + while.true + trace.1234 + push.1 + end + end + + if.true + while.true + push.1 + end + end + end + "#; + + let program = context.assemble(program_source).unwrap(); + + let join_node = match &program.mast_forest()[program.entrypoint()] { + MastNode::Join(node) => node, + _ => panic!("expected join node"), + }; + + // Make sure that both `if.true` blocks compile down to a different MAST node. + assert_ne!(join_node.first(), join_node.second()); +} + +/// Ensures that equal MAST nodes don't get added twice to a MAST forest +#[test] +fn duplicate_nodes() { + let context = TestContext::new().with_debug_info(false); + + let program_source = r#" + begin + if.true + mul + else + if.true add else mul end + end + end + "#; + + let program = context.assemble(program_source).unwrap(); + + let mut expected_mast_forest = MastForest::new(); + + let mul_basic_block_id = expected_mast_forest.add_block(vec![Operation::Mul], None).unwrap(); + + let add_basic_block_id = expected_mast_forest.add_block(vec![Operation::Add], None).unwrap(); + + // inner split: `if.true add else mul end` + let inner_split_id = + expected_mast_forest.add_split(add_basic_block_id, mul_basic_block_id).unwrap(); + + // root: outer split + let root_id = expected_mast_forest.add_split(mul_basic_block_id, inner_split_id).unwrap(); + + expected_mast_forest.make_root(root_id); - let before = CodeBlock::new_span(vec![Operation::Push(2u32.into())]); + let expected_program = Program::new(expected_mast_forest.into(), root_id); - let r#true = CodeBlock::new_span(vec![Operation::Push(3u32.into())]); - let r#false = CodeBlock::new_span(vec![Operation::Push(5u32.into())]); - let r#if = CodeBlock::new_split(r#true, r#false); + assert_eq!(expected_program, program); +} + +#[test] +fn explicit_fully_qualified_procedure_references() -> Result<(), Report> { + const BAR_NAME: &str = "foo::bar"; + const BAR: &str = r#" + export.bar + add + end"#; + const BAZ_NAME: &str = "foo::baz"; + const BAZ: &str = r#" + export.baz + exec.::foo::bar::bar + end"#; + + let context = TestContext::default(); + let bar = context.parse_module_with_path(BAR_NAME.parse().unwrap(), BAR)?; + let baz = context.parse_module_with_path(BAZ_NAME.parse().unwrap(), BAZ)?; + let library = context.assemble_library([bar, baz]).unwrap(); + + let assembler = Assembler::new(context.source_manager()).with_library(&library).unwrap(); + + let program = r#" + begin + exec.::foo::baz::baz + end"#; + + assert_matches!(assembler.assemble_program(program), Ok(_)); + Ok(()) +} + +#[test] +fn re_exports() -> Result<(), Report> { + const BAR_NAME: &str = "foo::bar"; + const BAR: &str = r#" + export.bar + add + end"#; + + const BAZ_NAME: &str = "foo::baz"; + const BAZ: &str = r#" + use.foo::bar + + export.bar::bar - let r#true = CodeBlock::new_span(vec![Operation::Push(7u32.into())]); - let r#false = CodeBlock::new_span(vec![Operation::Push(11u32.into())]); - let r#true = CodeBlock::new_split(r#true, r#false); + export.baz + push.1 push.2 add + end"#; - let r#while = - CodeBlock::new_join([exec_bar, CodeBlock::new_span(vec![Operation::Push(23u32.into())])]); - let r#while = CodeBlock::new_loop(r#while); - let span = CodeBlock::new_span(vec![Operation::Push(13u32.into())]); - let r#false = CodeBlock::new_join([span, r#while]); - let nested = CodeBlock::new_split(r#true, r#false); + let context = TestContext::new(); + let bar = context.parse_module_with_path(BAR_NAME.parse().unwrap(), BAR)?; + let baz = context.parse_module_with_path(BAZ_NAME.parse().unwrap(), BAZ)?; + let library = context.assemble_library([bar, baz]).unwrap(); - let combined = combine_blocks(vec![before, r#if, nested, exec_foo_bar_baz, syscall]); + let assembler = Assembler::new(context.source_manager()).with_library(&library).unwrap(); + + let program = r#" + use.foo::baz + + begin + push.1 push.2 + exec.baz::baz + push.3 push.4 + exec.baz::bar + end"#; - assert_eq!(combined.hash(), program.hash()); + assert_matches!(assembler.assemble_program(program), Ok(_)); + Ok(()) } diff --git a/assembly/src/ast/attribute/meta.rs b/assembly/src/ast/attribute/meta.rs new file mode 100644 index 0000000000..29973cf752 --- /dev/null +++ b/assembly/src/ast/attribute/meta.rs @@ -0,0 +1,253 @@ +mod expr; +mod kv; +mod list; + +use alloc::{collections::BTreeMap, string::String, sync::Arc, vec::Vec}; +use core::fmt; + +pub use self::{expr::MetaExpr, kv::MetaKeyValue, list::MetaList}; +use crate::{ast::Ident, parser::HexEncodedValue, Felt, SourceSpan, Span}; + +/// Represents the metadata provided as arguments to an attribute. +#[derive(Clone, PartialEq, Eq)] +pub enum Meta { + /// Represents empty metadata, e.g. `@foo` + Unit, + /// A list of metadata expressions, e.g. `@foo(a, "some text", 0x01)` + /// + /// The list should always have at least one element, and this is guaranteed by the parser. + List(Vec), + /// A set of uniquely-named metadata expressions, e.g. `@foo(letter = a, text = "some text")` + /// + /// The set should always have at least one key-value pair, and this is guaranteed by the + /// parser. + KeyValue(BTreeMap), +} +impl Meta { + /// Borrow the metadata without unwrapping the specific type + /// + /// Returns `None` if there is no meaningful metadata + #[inline] + pub fn borrow(&self) -> Option> { + match self { + Self::Unit => None, + Self::List(ref list) => Some(BorrowedMeta::List(list)), + Self::KeyValue(ref kv) => Some(BorrowedMeta::KeyValue(kv)), + } + } +} +impl FromIterator for Meta { + #[inline] + fn from_iter>(iter: T) -> Self { + let mut iter = iter.into_iter(); + match iter.next() { + None => Self::Unit, + Some(MetaItem::Expr(expr)) => Self::List( + core::iter::once(expr) + .chain(iter.map(|item| match item { + MetaItem::Expr(expr) => expr, + MetaItem::KeyValue(..) => unsafe { core::hint::unreachable_unchecked() }, + })) + .collect(), + ), + Some(MetaItem::KeyValue(k, v)) => Self::KeyValue( + core::iter::once((k, v)) + .chain(iter.map(|item| match item { + MetaItem::KeyValue(k, v) => (k, v), + MetaItem::Expr(_) => unsafe { core::hint::unreachable_unchecked() }, + })) + .collect(), + ), + } + } +} + +impl FromIterator for Meta { + #[inline] + fn from_iter>(iter: T) -> Self { + Self::List(iter.into_iter().collect()) + } +} + +impl FromIterator<(Ident, MetaExpr)> for Meta { + #[inline] + fn from_iter>(iter: T) -> Self { + Self::KeyValue(iter.into_iter().collect()) + } +} + +impl<'a> FromIterator<(&'a str, MetaExpr)> for Meta { + #[inline] + fn from_iter(iter: T) -> Self + where + T: IntoIterator, + { + Self::KeyValue( + iter.into_iter() + .map(|(k, v)| { + let k = Ident::new_unchecked(Span::new(SourceSpan::UNKNOWN, Arc::from(k))); + (k, v) + }) + .collect(), + ) + } +} + +impl From for Meta +where + Meta: FromIterator, + I: IntoIterator, +{ + #[inline] + fn from(iter: I) -> Self { + Self::from_iter(iter) + } +} + +/// Represents a reference to the metadata for an [super::Attribute] +/// +/// See [Meta] for what metadata is represented, and its syntax. +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum BorrowedMeta<'a> { + /// A list of metadata expressions + List(&'a [MetaExpr]), + /// A list of uniquely-named metadata expressions + KeyValue(&'a BTreeMap), +} +impl fmt::Debug for BorrowedMeta<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::List(items) => write!(f, "{items:#?}"), + Self::KeyValue(items) => write!(f, "{items:#?}"), + } + } +} + +/// Represents a single metadata item provided as an argument to an attribute. +/// +/// For example, the `foo` attribute in `@foo(bar, baz)` has two metadata items, both of `Expr` +/// type, which compose a ` +#[derive(Clone, PartialEq, Eq)] +pub enum MetaItem { + /// A metadata expression, e.g. `"some text"` in `@foo("some text")` + /// + /// This represents the element type for `Meta::List`-based attributes. + Expr(MetaExpr), + /// A named metadata expression, e.g. `letter = a` in `@foo(letter = a)` + /// + /// This represents the element type for `Meta::KeyValue`-based attributes. + KeyValue(Ident, MetaExpr), +} + +impl MetaItem { + /// Unwrap this item to extract the contained [MetaExpr]. + /// + /// Panics if this item is not the `Expr` variant. + #[inline] + #[track_caller] + pub fn unwrap_expr(self) -> MetaExpr { + match self { + Self::Expr(expr) => expr, + Self::KeyValue(..) => unreachable!("tried to unwrap key-value as expression"), + } + } + + /// Unwrap this item to extract the contained key-value pair. + /// + /// Panics if this item is not the `KeyValue` variant. + #[inline] + #[track_caller] + pub fn unwrap_key_value(self) -> (Ident, MetaExpr) { + match self { + Self::KeyValue(k, v) => (k, v), + Self::Expr(_) => unreachable!("tried to unwrap expression as key-value"), + } + } +} + +impl From for MetaItem { + fn from(value: Ident) -> Self { + Self::Expr(MetaExpr::Ident(value)) + } +} + +impl From<&str> for MetaItem { + fn from(value: &str) -> Self { + Self::Expr(MetaExpr::String(Ident::new_unchecked(Span::new( + SourceSpan::UNKNOWN, + Arc::from(value), + )))) + } +} + +impl From for MetaItem { + fn from(value: String) -> Self { + Self::Expr(MetaExpr::String(Ident::new_unchecked(Span::new( + SourceSpan::UNKNOWN, + Arc::from(value.into_boxed_str()), + )))) + } +} + +impl From for MetaItem { + fn from(value: u8) -> Self { + Self::Expr(MetaExpr::Int(Span::new(SourceSpan::UNKNOWN, HexEncodedValue::U8(value)))) + } +} + +impl From for MetaItem { + fn from(value: u16) -> Self { + Self::Expr(MetaExpr::Int(Span::new(SourceSpan::UNKNOWN, HexEncodedValue::U16(value)))) + } +} + +impl From for MetaItem { + fn from(value: u32) -> Self { + Self::Expr(MetaExpr::Int(Span::new(SourceSpan::UNKNOWN, HexEncodedValue::U32(value)))) + } +} + +impl From for MetaItem { + fn from(value: Felt) -> Self { + Self::Expr(MetaExpr::Int(Span::new(SourceSpan::UNKNOWN, HexEncodedValue::Felt(value)))) + } +} + +impl From<[Felt; 4]> for MetaItem { + fn from(value: [Felt; 4]) -> Self { + Self::Expr(MetaExpr::Int(Span::new(SourceSpan::UNKNOWN, HexEncodedValue::Word(value)))) + } +} + +impl From<(Ident, V)> for MetaItem +where + V: Into, +{ + fn from(entry: (Ident, V)) -> Self { + let (key, value) = entry; + Self::KeyValue(key, value.into()) + } +} + +impl From<(&str, V)> for MetaItem +where + V: Into, +{ + fn from(entry: (&str, V)) -> Self { + let (key, value) = entry; + let key = Ident::new_unchecked(Span::new(SourceSpan::UNKNOWN, Arc::from(key))); + Self::KeyValue(key, value.into()) + } +} + +impl From<(String, V)> for MetaItem +where + V: Into, +{ + fn from(entry: (String, V)) -> Self { + let (key, value) = entry; + let key = + Ident::new_unchecked(Span::new(SourceSpan::UNKNOWN, Arc::from(key.into_boxed_str()))); + Self::KeyValue(key, value.into()) + } +} diff --git a/assembly/src/ast/attribute/meta/expr.rs b/assembly/src/ast/attribute/meta/expr.rs new file mode 100644 index 0000000000..8c92899b25 --- /dev/null +++ b/assembly/src/ast/attribute/meta/expr.rs @@ -0,0 +1,86 @@ +use alloc::{string::String, sync::Arc}; + +use crate::{ast::Ident, parser::HexEncodedValue, prettier, Felt, SourceSpan, Span, Spanned}; + +/// Represents a metadata expression of an [crate::ast::Attribute] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum MetaExpr { + /// An identifier/keyword, e.g. `inline` + Ident(Ident), + /// A decimal or hexadecimal integer value + Int(Span), + /// A quoted string or identifier + String(Ident), +} + +impl prettier::PrettyPrint for MetaExpr { + fn render(&self) -> prettier::Document { + use prettier::*; + + match self { + Self::Ident(id) => text(id), + Self::Int(value) => text(value), + Self::String(id) => text(format!("\"{}\"", id.as_str().escape_default())), + } + } +} + +impl From for MetaExpr { + fn from(value: Ident) -> Self { + Self::Ident(value) + } +} + +impl From<&str> for MetaExpr { + fn from(value: &str) -> Self { + Self::String(Ident::new_unchecked(Span::new(SourceSpan::UNKNOWN, Arc::from(value)))) + } +} + +impl From for MetaExpr { + fn from(value: String) -> Self { + Self::String(Ident::new_unchecked(Span::new( + SourceSpan::UNKNOWN, + Arc::from(value.into_boxed_str()), + ))) + } +} + +impl From for MetaExpr { + fn from(value: u8) -> Self { + Self::Int(Span::new(SourceSpan::UNKNOWN, HexEncodedValue::U8(value))) + } +} + +impl From for MetaExpr { + fn from(value: u16) -> Self { + Self::Int(Span::new(SourceSpan::UNKNOWN, HexEncodedValue::U16(value))) + } +} + +impl From for MetaExpr { + fn from(value: u32) -> Self { + Self::Int(Span::new(SourceSpan::UNKNOWN, HexEncodedValue::U32(value))) + } +} + +impl From for MetaExpr { + fn from(value: Felt) -> Self { + Self::Int(Span::new(SourceSpan::UNKNOWN, HexEncodedValue::Felt(value))) + } +} + +impl From<[Felt; 4]> for MetaExpr { + fn from(value: [Felt; 4]) -> Self { + Self::Int(Span::new(SourceSpan::UNKNOWN, HexEncodedValue::Word(value))) + } +} + +impl Spanned for MetaExpr { + fn span(&self) -> SourceSpan { + match self { + Self::Ident(spanned) | Self::String(spanned) => spanned.span(), + Self::Int(spanned) => spanned.span(), + } + } +} diff --git a/assembly/src/ast/attribute/meta/kv.rs b/assembly/src/ast/attribute/meta/kv.rs new file mode 100644 index 0000000000..69ed38936b --- /dev/null +++ b/assembly/src/ast/attribute/meta/kv.rs @@ -0,0 +1,135 @@ +use alloc::collections::BTreeMap; +use core::borrow::Borrow; + +use super::MetaExpr; +use crate::{ast::Ident, SourceSpan, Spanned}; + +/// Represents the metadata of a key-value [crate::ast::Attribute], i.e. `@props(key = value)` +#[derive(Clone)] +pub struct MetaKeyValue { + pub span: SourceSpan, + /// The name of the key-value dictionary + pub name: Ident, + /// The set of key-value pairs provided as arguments to this attribute + pub items: BTreeMap, +} + +impl Spanned for MetaKeyValue { + #[inline(always)] + fn span(&self) -> SourceSpan { + self.span + } +} + +impl MetaKeyValue { + pub fn new(name: Ident, items: I) -> Self + where + I: IntoIterator, + K: Into, + V: Into, + { + let items = items.into_iter().map(|(k, v)| (k.into(), v.into())).collect(); + Self { span: SourceSpan::default(), name, items } + } + + pub fn with_span(mut self, span: SourceSpan) -> Self { + self.span = span; + self + } + + /// Get the name of this metadata as a string + #[inline] + pub fn name(&self) -> &str { + self.name.as_str() + } + + /// Get the name of this metadata as an [Ident] + #[inline] + pub fn id(&self) -> Ident { + self.name.clone() + } + + /// Returns true if this metadata contains an entry for `key` + pub fn contains_key(&self, key: &Q) -> bool + where + Ident: Borrow + Ord, + Q: ?Sized + Ord, + { + self.items.contains_key(key) + } + + /// Returns the value associated with `key`, if present in this metadata + pub fn get(&self, key: &Q) -> Option<&MetaExpr> + where + Ident: Borrow + Ord, + Q: ?Sized + Ord, + { + self.items.get(key) + } + + /// Inserts a new key-value entry in this metadata + pub fn insert(&mut self, key: impl Into, value: impl Into) { + self.items.insert(key.into(), value.into()); + } + + /// Removes the entry associated with `key`, if present in this metadata, and returns it + pub fn remove(&mut self, key: &Q) -> Option + where + Ident: Borrow + Ord, + Q: ?Sized + Ord, + { + self.items.remove(key) + } + + /// Get an entry in the key-value map of this metadata for `key` + pub fn entry( + &mut self, + key: Ident, + ) -> alloc::collections::btree_map::Entry<'_, Ident, MetaExpr> { + self.items.entry(key) + } + + /// Get an iterator over the the key-value items of this metadata + #[inline] + pub fn iter(&self) -> impl Iterator { + self.items.iter() + } +} + +impl IntoIterator for MetaKeyValue { + type Item = (Ident, MetaExpr); + type IntoIter = alloc::collections::btree_map::IntoIter; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + self.items.into_iter() + } +} + +impl Eq for MetaKeyValue {} + +impl PartialEq for MetaKeyValue { + fn eq(&self, other: &Self) -> bool { + self.name == other.name && self.items == other.items + } +} + +impl PartialOrd for MetaKeyValue { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for MetaKeyValue { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.name.cmp(&other.name).then_with(|| self.items.cmp(&other.items)) + } +} + +impl core::hash::Hash for MetaKeyValue { + fn hash(&self, state: &mut H) { + self.name.hash(state); + self.items.hash(state); + } +} diff --git a/assembly/src/ast/attribute/meta/list.rs b/assembly/src/ast/attribute/meta/list.rs new file mode 100644 index 0000000000..99cb6e61ed --- /dev/null +++ b/assembly/src/ast/attribute/meta/list.rs @@ -0,0 +1,102 @@ +use alloc::vec::Vec; + +use super::MetaExpr; +use crate::{ast::Ident, SourceSpan, Spanned}; + +/// Represents the metadata of a named list [crate::ast::Attribute], i.e. `@name(item0, .., itemN)` +#[derive(Clone)] +pub struct MetaList { + pub span: SourceSpan, + /// The identifier used as the name of this attribute + pub name: Ident, + /// The list of items representing the value of this attribute - will always contain at least + /// one element when parsed. + pub items: Vec, +} + +impl Spanned for MetaList { + #[inline(always)] + fn span(&self) -> SourceSpan { + self.span + } +} + +impl MetaList { + pub fn new(name: Ident, items: I) -> Self + where + I: IntoIterator, + { + Self { + span: SourceSpan::default(), + name, + items: items.into_iter().collect(), + } + } + + pub fn with_span(mut self, span: SourceSpan) -> Self { + self.span = span; + self + } + + /// Get the name of this attribute as a string + pub fn name(&self) -> &str { + self.name.as_str() + } + + /// Get the name of this attribute as an [Ident] + pub fn id(&self) -> Ident { + self.name.clone() + } + + /// Returns true if the metadata list is empty + #[inline] + pub fn is_empty(&self) -> bool { + self.items.is_empty() + } + + /// Returns the number of items in the metadata list + #[inline] + pub fn len(&self) -> usize { + self.items.len() + } + + /// Get the metadata list as a slice + #[inline] + pub fn as_slice(&self) -> &[MetaExpr] { + self.items.as_slice() + } + + /// Get the metadata list as a mutable slice + #[inline] + pub fn as_mut_slice(&mut self) -> &mut [MetaExpr] { + self.items.as_mut_slice() + } +} + +impl Eq for MetaList {} + +impl PartialEq for MetaList { + fn eq(&self, other: &Self) -> bool { + self.name == other.name && self.items == other.items + } +} + +impl PartialOrd for MetaList { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for MetaList { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.name.cmp(&other.name).then_with(|| self.items.cmp(&other.items)) + } +} + +impl core::hash::Hash for MetaList { + fn hash(&self, state: &mut H) { + self.name.hash(state); + self.items.hash(state); + } +} diff --git a/assembly/src/ast/attribute/mod.rs b/assembly/src/ast/attribute/mod.rs new file mode 100644 index 0000000000..3c7937ba5c --- /dev/null +++ b/assembly/src/ast/attribute/mod.rs @@ -0,0 +1,246 @@ +mod meta; +mod set; + +use core::fmt; + +pub use self::{ + meta::{BorrowedMeta, Meta, MetaExpr, MetaItem, MetaKeyValue, MetaList}, + set::{AttributeSet, AttributeSetEntry}, +}; +use crate::{ast::Ident, prettier, SourceSpan, Spanned}; + +/// An [Attribute] represents some named metadata attached to a Miden Assembly procedure. +/// +/// An attribute has no predefined structure per se, but syntactically there are three types: +/// +/// * Marker attributes, i.e. just a name and no associated data. Attributes of this type are used +/// to "mark" the item they are attached to with some unique trait or behavior implied by the +/// name. For example, `@inline`. NOTE: `@inline()` is not valid syntax. +/// +/// * List attributes, i.e. a name and one or more comma-delimited expressions. Attributes of this +/// type are used for cases where you want to parameterize a marker-like trait. To use a Rust +/// example, `#[derive(Trait)]` is a list attribute, where `derive` is the marker, but we want to +/// instruct whatever processes derives, what traits it needs to derive. The equivalent syntax in +/// Miden Assembly would be `@derive(Trait)`. Lists must always have at least one item. +/// +/// * Key-value attributes, i.e. a name and a value. Attributes of this type are used to attach +/// named properties to an item. For example, `@storage(offset = 1)`. Possible value types are: +/// bare identifiers, decimal or hexadecimal integers, and quoted strings. +/// +/// There are no restrictions on what attributes can exist or be used. However, there are a set of +/// attributes that the assembler knows about, and acts on, which will be stripped during assembly. +/// Any remaining attributes we don't explicitly handle in the assembler, will be passed along as +/// metadata attached to the procedures in the MAST output by the assembler. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Attribute { + /// A named behavior, trait or action; e.g. `@inline` + Marker(Ident), + /// A parameterized behavior, trait or action; e.g. `@inline(always)` or `@derive(Foo, Bar)` + List(MetaList), + /// A named property; e.g. `@props(key = "value")`, `@props(a = 1, b = 0x1)` + KeyValue(MetaKeyValue), +} + +impl Attribute { + /// Create a new [Attribute] with the given metadata. + /// + /// The metadata value must be convertible to [Meta]. + /// + /// For marker attributes, you can either construct the `Marker` variant directly, or pass + /// either `Meta::Unit` or `None` as the metadata argument. + /// + /// If the metadata is empty, a `Marker` attribute will be produced, otherwise the type depends + /// on the metadata. If the metadata is _not_ key-value shaped, a `List` is produced, otherwise + /// a `KeyValue`. + pub fn new(name: Ident, metadata: impl Into) -> Self { + let metadata = metadata.into(); + match metadata { + Meta::Unit => Self::Marker(name), + Meta::List(items) => Self::List(MetaList { span: Default::default(), name, items }), + Meta::KeyValue(items) => { + Self::KeyValue(MetaKeyValue { span: Default::default(), name, items }) + }, + } + } + + /// Create a new [Attribute] from an metadata-producing iterator. + /// + /// If the iterator is empty, a `Marker` attribute will be produced, otherwise the type depends + /// on the metadata. If the metadata is _not_ key-value shaped, a `List` is produced, otherwise + /// a `KeyValue`. + pub fn from_iter(name: Ident, metadata: I) -> Self + where + Meta: FromIterator, + I: IntoIterator, + { + Self::new(name, Meta::from_iter(metadata)) + } + + /// Set the source location for this attribute + pub fn with_span(self, span: SourceSpan) -> Self { + match self { + Self::Marker(id) => Self::Marker(id.with_span(span)), + Self::List(list) => Self::List(list.with_span(span)), + Self::KeyValue(kv) => Self::KeyValue(kv.with_span(span)), + } + } + + /// Get the name of this attribute as a string + pub fn name(&self) -> &str { + match self { + Self::Marker(id) => id.as_str(), + Self::List(list) => list.name(), + Self::KeyValue(kv) => kv.name(), + } + } + + /// Get the name of this attribute as an [Ident] + pub fn id(&self) -> Ident { + match self { + Self::Marker(id) => id.clone(), + Self::List(list) => list.id(), + Self::KeyValue(kv) => kv.id(), + } + } + + /// Returns true if this is a marker attribute + pub fn is_marker(&self) -> bool { + matches!(self, Self::Marker(_)) + } + + /// Returns true if this is a list attribute + pub fn is_list(&self) -> bool { + matches!(self, Self::List(_)) + } + + /// Returns true if this is a key-value attribute + pub fn is_key_value(&self) -> bool { + matches!(self, Self::KeyValue(_)) + } + + /// Get the metadata for this attribute + /// + /// Returns `None` if this is a marker attribute, and thus has no metadata + pub fn metadata(&self) -> Option> { + match self { + Self::Marker(_) => None, + Self::List(ref list) => Some(BorrowedMeta::List(&list.items)), + Self::KeyValue(ref kv) => Some(BorrowedMeta::KeyValue(&kv.items)), + } + } +} + +impl fmt::Debug for Attribute { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Marker(id) => f.debug_tuple("Marker").field(&id).finish(), + Self::List(meta) => f + .debug_struct("List") + .field("name", &meta.name) + .field("items", &meta.items) + .finish(), + Self::KeyValue(meta) => f + .debug_struct("KeyValue") + .field("name", &meta.name) + .field("items", &meta.items) + .finish(), + } + } +} + +impl fmt::Display for Attribute { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use prettier::PrettyPrint; + self.pretty_print(f) + } +} + +impl prettier::PrettyPrint for Attribute { + fn render(&self) -> prettier::Document { + use prettier::*; + let doc = text(format!("@{}", &self.name())); + match self { + Self::Marker(_) => doc, + Self::List(meta) => { + let singleline_items = meta + .items + .iter() + .map(|item| item.render()) + .reduce(|acc, item| acc + const_text(", ") + item) + .unwrap_or(Document::Empty); + let multiline_items = indent( + 4, + nl() + meta + .items + .iter() + .map(|item| item.render()) + .reduce(|acc, item| acc + nl() + item) + .unwrap_or(Document::Empty), + ) + nl(); + doc + const_text("(") + (singleline_items | multiline_items) + const_text(")") + }, + Self::KeyValue(meta) => { + let singleline_items = meta + .items + .iter() + .map(|(k, v)| text(k) + const_text(" = ") + v.render()) + .reduce(|acc, item| acc + const_text(", ") + item) + .unwrap_or(Document::Empty); + let multiline_items = indent( + 4, + nl() + meta + .items + .iter() + .map(|(k, v)| text(k) + const_text(" = ") + v.render()) + .reduce(|acc, item| acc + nl() + item) + .unwrap_or(Document::Empty), + ) + nl(); + doc + const_text("(") + (singleline_items | multiline_items) + const_text(")") + }, + } + } +} + +impl Spanned for Attribute { + fn span(&self) -> SourceSpan { + match self { + Self::Marker(id) => id.span(), + Self::List(list) => list.span(), + Self::KeyValue(kv) => kv.span(), + } + } +} + +impl From for Attribute { + fn from(value: Ident) -> Self { + Self::Marker(value) + } +} + +impl From<(K, V)> for Attribute +where + K: Into, + V: Into, +{ + fn from(kv: (K, V)) -> Self { + let (key, value) = kv; + Self::List(MetaList { + span: SourceSpan::default(), + name: key.into(), + items: vec![value.into()], + }) + } +} + +impl From for Attribute { + fn from(value: MetaList) -> Self { + Self::List(value) + } +} + +impl From for Attribute { + fn from(value: MetaKeyValue) -> Self { + Self::KeyValue(value) + } +} diff --git a/assembly/src/ast/attribute/set.rs b/assembly/src/ast/attribute/set.rs new file mode 100644 index 0000000000..bbf69698a5 --- /dev/null +++ b/assembly/src/ast/attribute/set.rs @@ -0,0 +1,239 @@ +use alloc::vec::Vec; +use core::fmt; + +use super::*; +use crate::ast::Ident; + +/// An [AttributeSet] provides storage and access to all of the attributes attached to a Miden +/// Assembly item, e.g. procedure definition. +/// +/// Attributes are uniqued by name, so if you attempt to add multiple attributes with the same name, +/// the last write wins. In Miden Assembly syntax, multiple key-value attributes are merged +/// automatically, and a syntax error is only generated when keys conflict. All other attribute +/// types produce an error if they are declared multiple times on the same item. +#[derive(Default, Clone, PartialEq, Eq)] +pub struct AttributeSet { + /// The attributes in this set. + /// + /// The [AttributeSet] structure has map-like semantics, so why are we using a vector here? + /// + /// * We expect attributes to be relatively rare, with no more than a handful on the same item + /// at any given time. + /// * A vector is much more space and time efficient to search for small numbers of items + /// * We can acheive map-like semantics without O(N) complexity by keeping the vector sorted by + /// the attribute name, and using binary search to search it. This gives us O(1) best-case + /// performance, and O(log N) in the worst case. + attrs: Vec, +} + +impl AttributeSet { + /// Create a new [AttributeSet] from `attrs` + /// + /// If the input attributes have duplicate entries for the same name, only one will be selected, + /// but it is unspecified which. + pub fn new(attrs: I) -> Self + where + I: IntoIterator, + { + let mut this = Self { attrs: attrs.into_iter().collect() }; + this.attrs.sort_by_key(|attr| attr.id()); + this.attrs.dedup_by_key(|attr| attr.id()); + this + } + + /// Returns true if there are no attributes in this set + #[inline] + pub fn is_empty(&self) -> bool { + self.attrs.is_empty() + } + + /// Returns the number of attributes in this set + #[inline] + pub fn len(&self) -> usize { + self.attrs.len() + } + + /// Check if this set has an attributed named `name` + pub fn has(&self, name: impl AsRef) -> bool { + self.get(name).is_some() + } + + /// Get the attribute named `name`, if one is present. + pub fn get(&self, name: impl AsRef) -> Option<&Attribute> { + let name = name.as_ref(); + match self.attrs.binary_search_by_key(&name, |attr| attr.name()) { + Ok(index) => self.attrs.get(index), + Err(_) => None, + } + } + + /// Get a mutable reference to the attribute named `name`, if one is present. + pub fn get_mut(&mut self, name: impl AsRef) -> Option<&mut Attribute> { + let name = name.as_ref(); + match self.attrs.binary_search_by_key(&name, |attr| attr.name()) { + Ok(index) => self.attrs.get_mut(index), + Err(_) => None, + } + } + + /// Get an iterator over the attributes in this set + #[inline] + pub fn iter(&self) -> core::slice::Iter<'_, Attribute> { + self.attrs.iter() + } + + /// Get a mutable iterator over the attributes in this set + #[inline] + pub fn iter_mut(&mut self) -> core::slice::IterMut<'_, Attribute> { + self.attrs.iter_mut() + } + + /// Insert `attr` in the attribute set, replacing any existing attribute with the same name + /// + /// Returns true if the insertion was new, or false if the insertion replaced an existing entry. + pub fn insert(&mut self, attr: Attribute) -> bool { + let name = attr.name(); + match self.attrs.binary_search_by_key(&name, |attr| attr.name()) { + Ok(index) => { + // Replace existing attribute + self.attrs[index] = attr; + false + }, + Err(index) => { + self.attrs.insert(index, attr); + true + }, + } + } + + /// Insert `attr` in the attribute set, but only if there is no existing attribute with the same + /// name. + /// + /// Returns `Err` with `attr` if there is already an existing attribute with the same name. + pub fn insert_new(&mut self, attr: Attribute) -> Result<(), Attribute> { + if self.has(attr.name()) { + Err(attr) + } else { + self.insert(attr); + Ok(()) + } + } + + /// Removes the attribute named `name`, if present. + pub fn remove(&mut self, name: impl AsRef) -> Option { + let name = name.as_ref(); + match self.attrs.binary_search_by_key(&name, |attr| attr.name()) { + Ok(index) => Some(self.attrs.remove(index)), + Err(_) => None, + } + } + + /// Gets the given key's corresponding entry in the set for in-place modfication + pub fn entry(&mut self, key: Ident) -> AttributeSetEntry<'_> { + match self.attrs.binary_search_by_key(&key.as_str(), |attr| attr.name()) { + Ok(index) => AttributeSetEntry::occupied(self, index), + Err(index) => AttributeSetEntry::vacant(self, key, index), + } + } + + /// Clear all attributes from the set + #[inline] + pub fn clear(&mut self) { + self.attrs.clear(); + } +} + +impl fmt::Debug for AttributeSet { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut builder = f.debug_map(); + for attr in self.iter() { + match attr.metadata() { + None => { + builder.entry(&attr.name(), &"None"); + }, + Some(meta) => { + builder.entry(&attr.name(), &meta); + }, + } + } + builder.finish() + } +} + +impl FromIterator for AttributeSet { + #[inline] + fn from_iter>(iter: T) -> Self { + Self::new(iter) + } +} + +impl Extend for AttributeSet { + fn extend>(&mut self, iter: T) { + for attr in iter { + self.insert(attr); + } + } +} + +/// Represents an entry under a specific key in a [AttributeSet] +pub enum AttributeSetEntry<'a> { + /// The entry is currently occupied with a value + Occupied(AttributeSetOccupiedEntry<'a>), + /// The entry is currently vacant + Vacant(AttributeSetVacantEntry<'a>), +} +impl<'a> AttributeSetEntry<'a> { + fn occupied(set: &'a mut AttributeSet, index: usize) -> Self { + Self::Occupied(AttributeSetOccupiedEntry { set, index }) + } + + fn vacant(set: &'a mut AttributeSet, key: Ident, index: usize) -> Self { + Self::Vacant(AttributeSetVacantEntry { set, key, index }) + } +} + +#[doc(hidden)] +pub struct AttributeSetOccupiedEntry<'a> { + set: &'a mut AttributeSet, + index: usize, +} +impl<'a> AttributeSetOccupiedEntry<'a> { + #[inline] + pub fn get(&self) -> &Attribute { + &self.set.attrs[self.index] + } + + #[inline] + pub fn get_mut(&mut self) -> &mut Attribute { + &mut self.set.attrs[self.index] + } + + pub fn insert(self, attr: Attribute) { + if attr.name() != self.get().name() { + self.set.insert(attr); + } else { + self.set.attrs[self.index] = attr; + } + } + + #[inline] + pub fn remove(self) -> Attribute { + self.set.attrs.remove(self.index) + } +} + +#[doc(hidden)] +pub struct AttributeSetVacantEntry<'a> { + set: &'a mut AttributeSet, + key: Ident, + index: usize, +} +impl<'a> AttributeSetVacantEntry<'a> { + pub fn insert(self, attr: Attribute) { + if self.key != attr.id() { + self.set.insert(attr); + } else { + self.set.attrs.insert(self.index, attr); + } + } +} diff --git a/assembly/src/ast/block.rs b/assembly/src/ast/block.rs index 61e37bcb4d..6f880ef91b 100644 --- a/assembly/src/ast/block.rs +++ b/assembly/src/ast/block.rs @@ -2,12 +2,12 @@ use alloc::vec::Vec; use core::fmt; use super::Op; -use crate::{ - ast::AstSerdeOptions, ByteReader, ByteWriter, Deserializable, DeserializationError, - Serializable, SourceSpan, Spanned, -}; +use crate::{SourceSpan, Spanned}; -/// Represents a basic block in Miden Assembly syntax +// BASIC BLOCK +// ================================================================================================ + +/// Represents a basic block in Miden Assembly syntax. /// /// Blocks can be nested, see [Op] for details. #[derive(Clone, Default)] @@ -17,17 +17,17 @@ pub struct Block { } impl Block { - /// Create a new [Block] + /// Creates a new [Block]. pub fn new(span: SourceSpan, body: Vec) -> Self { Self { span, body } } - /// Append `op` to this block + /// Appends `op` to this block. pub fn push(&mut self, op: Op) { self.body.push(op); } - /// Get the number of ops in this block + /// Returns the number of ops in this block. /// /// NOTE: The count does not include nested ops, /// only those at the root of the block. @@ -40,51 +40,17 @@ impl Block { self.body.is_empty() } - /// Get an iterator for the operations in this block + /// Returns an iterator for the operations in this block. pub fn iter(&self) -> core::slice::Iter<'_, Op> { self.body.iter() } - /// Get a mutable iterator for the operations in this block + /// Returns a mutable iterator for the operations in this block. pub fn iter_mut(&mut self) -> core::slice::IterMut<'_, Op> { self.body.iter_mut() } } -/// Serialization -impl Block { - /// Serialize this block to `target` with `options` - pub fn write_into_with_options(&self, target: &mut W, options: AstSerdeOptions) { - if options.debug_info { - self.span.write_into(target); - } - target.write_u16(self.body.len() as u16); - for op in self.body.iter() { - op.write_into_with_options(target, options); - } - } - - /// Deserialize this block from `source` with `options` - pub fn read_from_with_options( - source: &mut R, - options: AstSerdeOptions, - ) -> Result { - let span = if options.debug_info { - SourceSpan::read_from(source)? - } else { - SourceSpan::default() - }; - - let body_len = source.read_u16()? as usize; - let mut body = Vec::with_capacity(body_len); - for _ in 0..body_len { - let op = Op::read_from_with_options(source, options)?; - body.push(op); - } - Ok(Self { span, body }) - } -} - impl fmt::Debug for Block { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_list().entries(&self.body).finish() @@ -93,10 +59,18 @@ impl fmt::Debug for Block { impl crate::prettier::PrettyPrint for Block { fn render(&self) -> crate::prettier::Document { - use crate::prettier::*; + use crate::{ast::Instruction, prettier::*, Span}; + + // If a block is empty, pretty-print it with a `nop` instruction + let default_body = [Op::Inst(Span::new(self.span, Instruction::Nop))]; + let body = match self.body.as_slice() { + [] => default_body.as_slice().iter(), + body => body.iter(), + } + .map(PrettyPrint::render) + .reduce(|acc, doc| acc + nl() + doc); - let body = self.body.iter().map(PrettyPrint::render).reduce(|acc, doc| acc + nl() + doc); - body.map(|body| indent(4, body)).unwrap_or(Document::Empty) + body.map(|body| indent(4, nl() + body)).unwrap_or(Document::Empty) } } diff --git a/assembly/src/ast/constants.rs b/assembly/src/ast/constants.rs index c1bb7b9697..f019e32760 100644 --- a/assembly/src/ast/constants.rs +++ b/assembly/src/ast/constants.rs @@ -1,9 +1,10 @@ use alloc::{boxed::Box, string::String}; use core::fmt; -use crate::{ast::Ident, parser::ParsingError, Felt, SourceSpan, Span, Spanned}; use vm_core::FieldElement; +use crate::{ast::Ident, parser::ParsingError, Felt, SourceSpan, Span, Spanned}; + // CONSTANT // ================================================================================================ @@ -22,12 +23,7 @@ pub struct Constant { impl Constant { /// Creates a new [Constant] from the given source span, name, and value. pub fn new(span: SourceSpan, name: Ident, value: ConstantExpr) -> Self { - Self { - span, - docs: None, - name, - value, - } + Self { span, docs: None, name, value } } /// Adds documentation to this constant declaration. @@ -142,22 +138,22 @@ impl ConstantExpr { match op { ConstantOp::Add => { Ok(Self::Literal(Span::new(span, lhs + rhs))) - } + }, ConstantOp::Sub => { Ok(Self::Literal(Span::new(span, lhs - rhs))) - } + }, ConstantOp::Mul => { Ok(Self::Literal(Span::new(span, lhs * rhs))) - } + }, ConstantOp::Div => { Ok(Self::Literal(Span::new(span, lhs / rhs))) - } + }, ConstantOp::IntDiv => Ok(Self::Literal(Span::new( span, Felt::new(lhs.as_int() / rhs.as_int()), ))), } - } + }, lhs => Ok(Self::BinaryOp { span, op, @@ -165,7 +161,7 @@ impl ConstantExpr { rhs: Box::new(Self::Literal(rhs)), }), } - } + }, rhs => { let lhs = Self::into_inner(lhs).try_fold()?; Ok(Self::BinaryOp { @@ -174,18 +170,13 @@ impl ConstantExpr { lhs: Box::new(lhs), rhs: Box::new(rhs), }) - } + }, } } else { let lhs = Self::into_inner(lhs).try_fold()?; - Ok(Self::BinaryOp { - span, - op, - lhs: Box::new(lhs), - rhs, - }) + Ok(Self::BinaryOp { span, op, lhs: Box::new(lhs), rhs }) } - } + }, } } @@ -212,18 +203,8 @@ impl PartialEq for ConstantExpr { (Self::Literal(l), Self::Literal(y)) => l == y, (Self::Var(l), Self::Var(y)) => l == y, ( - Self::BinaryOp { - op: lop, - lhs: llhs, - rhs: lrhs, - .. - }, - Self::BinaryOp { - op: rop, - lhs: rlhs, - rhs: rrhs, - .. - }, + Self::BinaryOp { op: lop, lhs: llhs, rhs: lrhs, .. }, + Self::BinaryOp { op: rop, lhs: rlhs, rhs: rrhs, .. }, ) => lop == rop && llhs == rlhs && lrhs == rrhs, _ => false, } @@ -235,12 +216,9 @@ impl fmt::Debug for ConstantExpr { match self { Self::Literal(ref lit) => fmt::Debug::fmt(&**lit, f), Self::Var(ref name) => fmt::Debug::fmt(&**name, f), - Self::BinaryOp { - ref op, - ref lhs, - ref rhs, - .. - } => f.debug_tuple(op.name()).field(lhs).field(rhs).finish(), + Self::BinaryOp { ref op, ref lhs, ref rhs, .. } => { + f.debug_tuple(op.name()).field(lhs).field(rhs).finish() + }, } } } @@ -256,7 +234,7 @@ impl crate::prettier::PrettyPrint for ConstantExpr { let single_line = lhs.render() + display(op) + rhs.render(); let multi_line = lhs.render() + nl() + (display(op)) + rhs.render(); single_line | multi_line - } + }, } } } diff --git a/assembly/src/ast/ident.rs b/assembly/src/ast/ident.rs index e481ffbc37..0f7e594c8a 100644 --- a/assembly/src/ast/ident.rs +++ b/assembly/src/ast/ident.rs @@ -5,10 +5,7 @@ use core::{ str::FromStr, }; -use crate::{ - ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable, SourceSpan, Span, - Spanned, -}; +use crate::{SourceSpan, Span, Spanned}; /// Represents the types of errors that can occur when parsing/validating an [Ident] #[derive(Debug, thiserror::Error, PartialEq, Eq)] @@ -183,63 +180,6 @@ impl FromStr for Ident { fn from_str(s: &str) -> Result { Self::validate(s)?; let name = Arc::from(s.to_string().into_boxed_str()); - Ok(Self { - span: SourceSpan::default(), - name, - }) - } -} - -/// Serialization -impl Ident { - pub fn write_into_with_options( - &self, - target: &mut W, - options: crate::ast::AstSerdeOptions, - ) { - if options.debug_info { - self.span.write_into(target); - } - target.write_usize(self.name.as_bytes().len()); - target.write_bytes(self.name.as_bytes()); - } - - pub fn read_from_with_options( - source: &mut R, - options: crate::ast::AstSerdeOptions, - ) -> Result { - let span = if options.debug_info { - SourceSpan::read_from(source)? - } else { - SourceSpan::default() - }; - let nlen = source.read_usize()?; - let name = source.read_slice(nlen)?; - let name = core::str::from_utf8(name) - .map_err(|e| DeserializationError::InvalidValue(e.to_string()))?; - name.parse::() - .map_err(|e| DeserializationError::InvalidValue(e.to_string())) - .map(|id| id.with_span(span)) - } -} - -impl Serializable for Ident { - fn write_into(&self, target: &mut W) { - self.span.write_into(target); - target.write_usize(self.name.as_bytes().len()); - target.write_bytes(self.name.as_bytes()); - } -} - -impl Deserializable for Ident { - fn read_from(source: &mut R) -> Result { - let span = SourceSpan::read_from(source)?; - let nlen = source.read_usize()?; - let name = source.read_slice(nlen)?; - let name = core::str::from_utf8(name) - .map_err(|e| DeserializationError::InvalidValue(e.to_string()))?; - name.parse::() - .map_err(|e| DeserializationError::InvalidValue(e.to_string())) - .map(|id| id.with_span(span)) + Ok(Self { span: SourceSpan::default(), name }) } } diff --git a/assembly/src/ast/imports.rs b/assembly/src/ast/imports.rs index 308d5d9764..26b7318d43 100644 --- a/assembly/src/ast/imports.rs +++ b/assembly/src/ast/imports.rs @@ -1,10 +1,6 @@ use core::fmt; -use crate::{ - ast::{AstSerdeOptions, Ident}, - ByteReader, ByteWriter, Deserializable, DeserializationError, LibraryNamespace, LibraryPath, - Serializable, SourceSpan, Spanned, -}; +use crate::{ast::Ident, LibraryNamespace, LibraryPath, SourceSpan, Spanned}; // IMPORT // ================================================================================================ @@ -47,39 +43,6 @@ impl Import { } } -/// Serialization -impl Import { - /// Serializes this import to `target` with `options` - pub fn write_into_with_options(&self, target: &mut W, options: AstSerdeOptions) { - if options.debug_info { - self.span.write_into(target); - } - self.name.write_into_with_options(target, options); - self.path.write_into(target); - } - - /// Deserializes this import from `source` with `options`. - pub fn read_from_with_options( - source: &mut R, - options: AstSerdeOptions, - ) -> Result { - let span = if options.debug_info { - SourceSpan::read_from(source)? - } else { - SourceSpan::default() - }; - - let name = Ident::read_from_with_options(source, options)?; - let path = LibraryPath::read_from(source)?; - Ok(Self { - span, - name, - path, - uses: 0, - }) - } -} - impl fmt::Debug for Import { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Import") diff --git a/assembly/src/ast/instruction/advice.rs b/assembly/src/ast/instruction/advice.rs index b9052777c5..c0f13cbeb9 100644 --- a/assembly/src/ast/instruction/advice.rs +++ b/assembly/src/ast/instruction/advice.rs @@ -1,30 +1,8 @@ -use alloc::string::ToString; use core::fmt; -use crate::{ - ast::{ImmU8, MAX_STACK_WORD_OFFSET}, - ByteReader, ByteWriter, Deserializable, DeserializationError, Felt, Serializable, Span, ZERO, -}; use vm_core::AdviceInjector; -// CONSTANTS -// ================================================================================================ - -const PUSH_U64DIV: u8 = 0; -const PUSH_EXT2INTT: u8 = 1; -const PUSH_SMTGET: u8 = 2; -const PUSH_SMTSET: u8 = 3; -const PUSH_SMTPEEK: u8 = 4; -const PUSH_MAPVAL: u8 = 5; -const PUSH_MAPVAL_IMM: u8 = 6; -const PUSH_MAPVALN: u8 = 7; -const PUSH_MAPVALN_IMM: u8 = 8; -const PUSH_MTNODE: u8 = 9; -const INSERT_MEM: u8 = 10; -const INSERT_HDWORD: u8 = 11; -const INSERT_HDWORD_IMM: u8 = 12; -const INSERT_HPERM: u8 = 13; -const PUSH_SIG: u8 = 14; +use crate::{ast::ImmU8, Felt, ZERO}; // ADVICE INJECTOR NODE // ================================================================================================ @@ -35,34 +13,22 @@ const PUSH_SIG: u8 = 14; /// - Push new data onto the advice stack. /// - Insert new data into the advice map. #[derive(Clone, PartialEq, Eq, Debug)] -#[repr(u8)] pub enum AdviceInjectorNode { - PushU64Div = PUSH_U64DIV, - PushExt2intt = PUSH_EXT2INTT, - PushSmtGet = PUSH_SMTGET, - PushSmtSet = PUSH_SMTSET, - PushSmtPeek = PUSH_SMTPEEK, - PushMapVal = PUSH_MAPVAL, - PushMapValImm { offset: ImmU8 } = PUSH_MAPVAL_IMM, - PushMapValN = PUSH_MAPVALN, - PushMapValNImm { offset: ImmU8 } = PUSH_MAPVALN_IMM, - PushMtNode = PUSH_MTNODE, - InsertMem = INSERT_MEM, - InsertHdword = INSERT_HDWORD, - InsertHdwordImm { domain: ImmU8 } = INSERT_HDWORD_IMM, - InsertHperm = INSERT_HPERM, - PushSignature { kind: SignatureKind } = PUSH_SIG, -} - -impl AdviceInjectorNode { - fn tag(&self) -> u8 { - // SAFETY: This is safe because we have given this enum a primitive representation with - // #[repr(u8)], with the first field of the underlying union-of-structs the discriminant. - // - // See the section on "accessing the numeric value of the discriminant" - // here: https://doc.rust-lang.org/std/mem/fn.discriminant.html - unsafe { *<*const _>::from(self).cast::() } - } + PushU64Div, + PushExt2intt, + PushSmtGet, + PushSmtSet, + PushSmtPeek, + PushMapVal, + PushMapValImm { offset: ImmU8 }, + PushMapValN, + PushMapValNImm { offset: ImmU8 }, + PushMtNode, + InsertMem, + InsertHdword, + InsertHdwordImm { domain: ImmU8 }, + InsertHperm, + PushSignature { kind: SignatureKind }, } impl From<&AdviceInjectorNode> for AdviceInjector { @@ -74,24 +40,14 @@ impl From<&AdviceInjectorNode> for AdviceInjector { PushSmtGet => Self::SmtGet, PushSmtSet => Self::SmtSet, PushSmtPeek => Self::SmtPeek, - PushMapVal => Self::MapValueToStack { - include_len: false, - key_offset: 0, - }, - PushMapValImm { - offset: ImmU8::Value(offset), - } => Self::MapValueToStack { + PushMapVal => Self::MapValueToStack { include_len: false, key_offset: 0 }, + PushMapValImm { offset: ImmU8::Value(offset) } => Self::MapValueToStack { include_len: false, key_offset: offset.into_inner() as usize, }, PushMapValImm { offset } => panic!("unresolved constant '{offset}'"), - PushMapValN => Self::MapValueToStack { - include_len: true, - key_offset: 0, - }, - PushMapValNImm { - offset: ImmU8::Value(offset), - } => Self::MapValueToStack { + PushMapValN => Self::MapValueToStack { include_len: true, key_offset: 0 }, + PushMapValNImm { offset: ImmU8::Value(offset) } => Self::MapValueToStack { include_len: true, key_offset: offset.into_inner() as usize, }, @@ -99,16 +55,12 @@ impl From<&AdviceInjectorNode> for AdviceInjector { PushMtNode => Self::MerkleNodeToStack, InsertMem => Self::MemToMap, InsertHdword => Self::HdwordToMap { domain: ZERO }, - InsertHdwordImm { - domain: ImmU8::Value(domain), - } => Self::HdwordToMap { - domain: Felt::from(domain.into_inner()), + InsertHdwordImm { domain: ImmU8::Value(domain) } => { + Self::HdwordToMap { domain: Felt::from(domain.into_inner()) } }, InsertHdwordImm { domain } => panic!("unresolved constant '{domain}'"), InsertHperm => Self::HpermToMap, - PushSignature { kind } => Self::SigToStack { - kind: (*kind).into(), - }, + PushSignature { kind } => Self::SigToStack { kind: (*kind).into() }, } } } @@ -141,93 +93,6 @@ impl fmt::Display for AdviceInjectorNode { } } -// SERIALIZATION / DESERIALIZATION -// ================================================================================================ - -impl Serializable for AdviceInjectorNode { - fn write_into(&self, target: &mut W) { - target.write_u8(self.tag()); - match self { - Self::PushU64Div - | Self::PushExt2intt - | Self::PushSmtGet - | Self::PushSmtSet - | Self::PushSmtPeek - | Self::PushMapVal - | Self::PushMapValN - | Self::PushMtNode - | Self::InsertMem - | Self::InsertHdword - | Self::InsertHperm => (), - Self::PushMapValImm { - offset: ImmU8::Value(offset), - } => { - target.write_u8(offset.into_inner()); - } - Self::PushMapValImm { offset } => panic!("unresolved constant '{offset}'"), - Self::PushMapValNImm { - offset: ImmU8::Value(offset), - } => { - target.write_u8(offset.into_inner()); - } - Self::PushMapValNImm { offset } => panic!("unresolved constant '{offset}'"), - Self::InsertHdwordImm { - domain: ImmU8::Value(domain), - } => { - target.write_u8(domain.into_inner()); - } - Self::InsertHdwordImm { domain } => panic!("unresolved constant '{domain}'"), - Self::PushSignature { kind } => kind.write_into(target), - } - } -} - -impl Deserializable for AdviceInjectorNode { - fn read_from(source: &mut R) -> Result { - match source.read_u8()? { - PUSH_U64DIV => Ok(Self::PushU64Div), - PUSH_EXT2INTT => Ok(Self::PushExt2intt), - PUSH_SMTGET => Ok(Self::PushSmtGet), - PUSH_SMTSET => Ok(Self::PushSmtSet), - PUSH_SMTPEEK => Ok(Self::PushSmtPeek), - PUSH_MAPVAL => Ok(Self::PushMapVal), - PUSH_MAPVAL_IMM => { - let offset = source.read_u8()?; - if offset > MAX_STACK_WORD_OFFSET { - return Err(DeserializationError::InvalidValue("invalid offset".to_string())); - } - Ok(Self::PushMapValImm { - offset: ImmU8::Value(Span::unknown(offset)), - }) - } - PUSH_MAPVALN => Ok(Self::PushMapValN), - PUSH_MAPVALN_IMM => { - let offset = source.read_u8()?; - if offset > MAX_STACK_WORD_OFFSET { - return Err(DeserializationError::InvalidValue("invalid offset".to_string())); - } - Ok(Self::PushMapValNImm { - offset: ImmU8::Value(Span::unknown(offset)), - }) - } - PUSH_MTNODE => Ok(Self::PushMtNode), - INSERT_MEM => Ok(Self::InsertMem), - INSERT_HDWORD => Ok(Self::InsertHdword), - INSERT_HDWORD_IMM => { - let domain = source.read_u8()?; - Ok(Self::InsertHdwordImm { - domain: ImmU8::Value(Span::unknown(domain)), - }) - } - INSERT_HPERM => Ok(Self::InsertHperm), - PUSH_SIG => Ok(Self::PushSignature { - kind: SignatureKind::read_from(source)?, - }), - val => Err(DeserializationError::InvalidValue(val.to_string())), - } - } -} - /// A newtype wrapper for [vm_core::SignatureKind] #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[repr(u8)] @@ -249,18 +114,3 @@ impl fmt::Display for SignatureKind { write!(f, "{kind}") } } - -impl Serializable for SignatureKind { - fn write_into(&self, target: &mut W) { - target.write_u8(*self as u8); - } -} - -impl Deserializable for SignatureKind { - fn read_from(source: &mut R) -> Result { - match source.read_u8()? { - 0 => Ok(Self::RpoFalcon512), - val => Err(DeserializationError::InvalidValue(val.to_string())), - } - } -} diff --git a/assembly/src/ast/instruction/debug.rs b/assembly/src/ast/instruction/debug.rs index 763622775a..92dc3c5049 100644 --- a/assembly/src/ast/instruction/debug.rs +++ b/assembly/src/ast/instruction/debug.rs @@ -1,47 +1,20 @@ -use alloc::string::ToString; use core::fmt; -use crate::{ - ast::{ImmU16, ImmU32, ImmU8}, - ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable, -}; - -// CONSTANTS -// ================================================================================================ - -const STACK_ALL: u8 = 0; -const STACK_TOP: u8 = 1; -const MEM_ALL: u8 = 2; -const MEM_INTERVAL: u8 = 3; -const LOCAL_INTERVAL: u8 = 4; -const LOCAL_RANGE_FROM: u8 = 5; -const LOCAL_ALL: u8 = 6; +use crate::ast::{ImmU16, ImmU32, ImmU8}; // DEBUG OPTIONS // ================================================================================================ /// A proxy for [vm_core::DebugOptions], but with [super::Immediate] values. #[derive(Clone, Debug, Eq, PartialEq)] -#[repr(u8)] pub enum DebugOptions { - StackAll = STACK_ALL, - StackTop(ImmU8) = STACK_TOP, - MemAll = MEM_ALL, - MemInterval(ImmU32, ImmU32) = MEM_INTERVAL, - LocalInterval(ImmU16, ImmU16) = LOCAL_INTERVAL, - LocalRangeFrom(ImmU16) = LOCAL_RANGE_FROM, - LocalAll = LOCAL_ALL, -} - -impl DebugOptions { - fn tag(&self) -> u8 { - // SAFETY: This is safe because we have given this enum a primitive representation with - // #[repr(u8)], with the first field of the underlying union-of-structs the discriminant. - // - // See the section on "accessing the numeric value of the discriminant" - // here: https://doc.rust-lang.org/std/mem/fn.discriminant.html - unsafe { *<*const _>::from(self).cast::() } - } + StackAll, + StackTop(ImmU8), + MemAll, + MemInterval(ImmU32, ImmU32), + LocalInterval(ImmU16, ImmU16), + LocalRangeFrom(ImmU16), + LocalAll, } impl crate::prettier::PrettyPrint for DebugOptions { @@ -60,12 +33,12 @@ impl TryFrom for vm_core::DebugOptions { DebugOptions::MemAll => Ok(Self::MemAll), DebugOptions::MemInterval(ImmU32::Value(start), ImmU32::Value(end)) => { Ok(Self::MemInterval(start.into_inner(), end.into_inner())) - } + }, DebugOptions::LocalInterval(ImmU16::Value(start), ImmU16::Value(end)) => { let start = start.into_inner(); let end = end.into_inner(); Ok(Self::LocalInterval(start, end, end - start)) - } + }, _ => Err(()), } } @@ -82,72 +55,7 @@ impl fmt::Display for DebugOptions { Self::LocalRangeFrom(start) => write!(f, "local.{start}"), Self::LocalInterval(start, end) => { write!(f, "local.{start}.{end}") - } - } - } -} - -impl Serializable for DebugOptions { - fn write_into(&self, target: &mut W) { - target.write_u8(self.tag()); - match self { - Self::StackAll | Self::MemAll | Self::LocalAll => (), - Self::StackTop(ImmU8::Value(n)) => { - target.write_u8(n.into_inner()); - } - Self::MemInterval(ImmU32::Value(n), ImmU32::Value(m)) => { - target.write_u32(n.into_inner()); - target.write_u32(m.into_inner()); - } - Self::LocalRangeFrom(ImmU16::Value(start)) => { - let start = start.into_inner(); - target.write_u16(start); - } - Self::LocalInterval(ImmU16::Value(start), ImmU16::Value(end)) => { - let start = start.into_inner(); - let end = end.into_inner(); - target.write_u16(start); - target.write_u16(end); - target.write_u16(end - start); - } - options => unimplemented!("unimplemented debug options: {options}"), - } - } -} - -impl Deserializable for DebugOptions { - fn read_from(source: &mut R) -> Result { - match source.read_u8()? { - STACK_ALL => Ok(Self::StackAll), - STACK_TOP => { - let n = source.read_u8()?; - if n == 0 { - return Err(DeserializationError::InvalidValue(n.to_string())); - } - Ok(Self::StackTop(n.into())) - } - MEM_ALL => Ok(Self::MemAll), - MEM_INTERVAL => { - let n = source.read_u32()?; - let m = source.read_u32()?; - Ok(Self::MemInterval(n.into(), m.into())) - } - LOCAL_INTERVAL => { - let n = source.read_u16()?; - let m = source.read_u16()?; - source.read_u16()?; - match (n, m) { - (0, u16::MAX) => Ok(Self::LocalAll), - (n, u16::MAX) => Ok(Self::LocalRangeFrom(n.into())), - (n, m) => Ok(Self::LocalInterval(n.into(), m.into())), - } - } - LOCAL_RANGE_FROM => { - let n = source.read_u16()?; - Ok(Self::LocalRangeFrom(n.into())) - } - LOCAL_ALL => Ok(Self::LocalAll), - val => Err(DeserializationError::InvalidValue(val.to_string())), + }, } } } diff --git a/assembly/src/ast/instruction/deserialize.rs b/assembly/src/ast/instruction/deserialize.rs deleted file mode 100644 index cda8820d8d..0000000000 --- a/assembly/src/ast/instruction/deserialize.rs +++ /dev/null @@ -1,317 +0,0 @@ -use crate::{ - ast::{AdviceInjectorNode, DebugOptions, Instruction, InvocationTarget, OpCode}, - ByteReader, Deserializable, DeserializationError, Felt, MAX_PUSH_INPUTS, -}; - -impl Deserializable for Instruction { - fn read_from(source: &mut R) -> Result { - let opcode = OpCode::read_from(source)?; - - match opcode { - OpCode::Assert => Ok(Self::Assert), - OpCode::AssertWithError => Ok(Self::AssertWithError(source.read_u32()?.into())), - OpCode::AssertEq => Ok(Self::AssertEq), - OpCode::AssertEqWithError => Ok(Self::AssertEqWithError(source.read_u32()?.into())), - OpCode::AssertEqw => Ok(Self::AssertEqw), - OpCode::AssertEqwWithError => Ok(Self::AssertEqwWithError(source.read_u32()?.into())), - OpCode::Assertz => Ok(Self::Assertz), - OpCode::AssertzWithError => Ok(Self::AssertzWithError(source.read_u32()?.into())), - OpCode::Add => Ok(Self::Add), - OpCode::AddImm => Ok(Self::AddImm(Felt::read_from(source)?.into())), - OpCode::Sub => Ok(Self::Sub), - OpCode::SubImm => Ok(Self::SubImm(Felt::read_from(source)?.into())), - OpCode::Mul => Ok(Self::Mul), - OpCode::MulImm => Ok(Self::MulImm(Felt::read_from(source)?.into())), - OpCode::Div => Ok(Self::Div), - OpCode::DivImm => Ok(Self::DivImm(Felt::read_from(source)?.into())), - OpCode::Neg => Ok(Self::Neg), - OpCode::ILog2 => Ok(Self::ILog2), - OpCode::Inv => Ok(Self::Inv), - OpCode::Incr => Ok(Self::Incr), - OpCode::Pow2 => Ok(Self::Pow2), - OpCode::Exp => Ok(Self::Exp), - OpCode::ExpImm => Ok(Self::ExpImm(Felt::read_from(source)?.into())), - OpCode::ExpBitLength => Ok(Self::ExpBitLength(source.read_u8()?)), - OpCode::Not => Ok(Self::Not), - OpCode::And => Ok(Self::And), - OpCode::Or => Ok(Self::Or), - OpCode::Xor => Ok(Self::Xor), - OpCode::Eq => Ok(Self::Eq), - OpCode::EqImm => Ok(Self::EqImm(Felt::read_from(source)?.into())), - OpCode::Neq => Ok(Self::Neq), - OpCode::NeqImm => Ok(Self::NeqImm(Felt::read_from(source)?.into())), - OpCode::Eqw => Ok(Self::Eqw), - OpCode::Lt => Ok(Self::Lt), - OpCode::Lte => Ok(Self::Lte), - OpCode::Gt => Ok(Self::Gt), - OpCode::Gte => Ok(Self::Gte), - OpCode::IsOdd => Ok(Self::IsOdd), - - // ----- ext2 operations -------------------------------------------------------------- - OpCode::Ext2Add => Ok(Self::Ext2Add), - OpCode::Ext2Sub => Ok(Self::Ext2Sub), - OpCode::Ext2Mul => Ok(Self::Ext2Mul), - OpCode::Ext2Div => Ok(Self::Ext2Div), - OpCode::Ext2Neg => Ok(Self::Ext2Neg), - OpCode::Ext2Inv => Ok(Self::Ext2Inv), - - // ----- u32 manipulation ------------------------------------------------------------- - OpCode::U32Test => Ok(Self::U32Test), - OpCode::U32TestW => Ok(Self::U32TestW), - OpCode::U32Assert => Ok(Self::U32Assert), - OpCode::U32AssertWithError => Ok(Self::U32AssertWithError(source.read_u32()?.into())), - OpCode::U32Assert2 => Ok(Self::U32Assert2), - OpCode::U32Assert2WithError => Ok(Self::U32Assert2WithError(source.read_u32()?.into())), - OpCode::U32AssertW => Ok(Self::U32AssertW), - OpCode::U32AssertWWithError => Ok(Self::U32AssertWWithError(source.read_u32()?.into())), - OpCode::U32Split => Ok(Self::U32Split), - OpCode::U32Cast => Ok(Self::U32Cast), - OpCode::U32WrappingAdd => Ok(Self::U32WrappingAdd), - OpCode::U32WrappingAddImm => Ok(Self::U32WrappingAddImm(source.read_u32()?.into())), - OpCode::U32OverflowingAdd => Ok(Self::U32OverflowingAdd), - OpCode::U32OverflowingAddImm => { - Ok(Self::U32OverflowingAddImm(source.read_u32()?.into())) - } - OpCode::U32OverflowingAdd3 => Ok(Self::U32OverflowingAdd3), - OpCode::U32WrappingAdd3 => Ok(Self::U32WrappingAdd3), - OpCode::U32WrappingSub => Ok(Self::U32WrappingSub), - OpCode::U32WrappingSubImm => Ok(Self::U32WrappingSubImm(source.read_u32()?.into())), - OpCode::U32OverflowingSub => Ok(Self::U32OverflowingSub), - OpCode::U32OverflowingSubImm => { - Ok(Self::U32OverflowingSubImm(source.read_u32()?.into())) - } - OpCode::U32WrappingMul => Ok(Self::U32WrappingMul), - OpCode::U32WrappingMulImm => Ok(Self::U32WrappingMulImm(source.read_u32()?.into())), - OpCode::U32OverflowingMul => Ok(Self::U32OverflowingMul), - OpCode::U32OverflowingMulImm => { - Ok(Self::U32OverflowingMulImm(source.read_u32()?.into())) - } - OpCode::U32OverflowingMadd => Ok(Self::U32OverflowingMadd), - OpCode::U32WrappingMadd => Ok(Self::U32WrappingMadd), - OpCode::U32Div => Ok(Self::U32Div), - OpCode::U32DivImm => Ok(Self::U32DivImm(source.read_u32()?.into())), - OpCode::U32Mod => Ok(Self::U32Mod), - OpCode::U32ModImm => Ok(Self::U32ModImm(source.read_u32()?.into())), - OpCode::U32DivMod => Ok(Self::U32DivMod), - OpCode::U32DivModImm => Ok(Self::U32DivModImm(source.read_u32()?.into())), - OpCode::U32And => Ok(Self::U32And), - OpCode::U32Or => Ok(Self::U32Or), - OpCode::U32Xor => Ok(Self::U32Xor), - OpCode::U32Not => Ok(Self::U32Not), - OpCode::U32Shr => Ok(Self::U32Shr), - OpCode::U32ShrImm => Ok(Self::U32ShrImm(source.read_u8()?.into())), - OpCode::U32Shl => Ok(Self::U32Shl), - OpCode::U32ShlImm => Ok(Self::U32ShlImm(source.read_u8()?.into())), - OpCode::U32Rotr => Ok(Self::U32Rotr), - OpCode::U32RotrImm => Ok(Self::U32RotrImm(source.read_u8()?.into())), - OpCode::U32Rotl => Ok(Self::U32Rotl), - OpCode::U32RotlImm => Ok(Self::U32RotlImm(source.read_u8()?.into())), - OpCode::U32Popcnt => Ok(Self::U32Popcnt), - OpCode::U32Ctz => Ok(Self::U32Ctz), - OpCode::U32Clz => Ok(Self::U32Clz), - OpCode::U32Clo => Ok(Self::U32Clo), - OpCode::U32Cto => Ok(Self::U32Cto), - OpCode::U32Lt => Ok(Self::U32Lt), - OpCode::U32Lte => Ok(Self::U32Lte), - OpCode::U32Gt => Ok(Self::U32Gt), - OpCode::U32Gte => Ok(Self::U32Gte), - OpCode::U32Min => Ok(Self::U32Min), - OpCode::U32Max => Ok(Self::U32Max), - - // ----- stack manipulation ----------------------------------------------------------- - OpCode::Drop => Ok(Self::Drop), - OpCode::DropW => Ok(Self::DropW), - OpCode::PadW => Ok(Self::PadW), - OpCode::Dup0 => Ok(Self::Dup0), - OpCode::Dup1 => Ok(Self::Dup1), - OpCode::Dup2 => Ok(Self::Dup2), - OpCode::Dup3 => Ok(Self::Dup3), - OpCode::Dup4 => Ok(Self::Dup4), - OpCode::Dup5 => Ok(Self::Dup5), - OpCode::Dup6 => Ok(Self::Dup6), - OpCode::Dup7 => Ok(Self::Dup7), - OpCode::Dup8 => Ok(Self::Dup8), - OpCode::Dup9 => Ok(Self::Dup9), - OpCode::Dup10 => Ok(Self::Dup10), - OpCode::Dup11 => Ok(Self::Dup11), - OpCode::Dup12 => Ok(Self::Dup12), - OpCode::Dup13 => Ok(Self::Dup13), - OpCode::Dup14 => Ok(Self::Dup14), - OpCode::Dup15 => Ok(Self::Dup15), - OpCode::DupW0 => Ok(Self::DupW0), - OpCode::DupW1 => Ok(Self::DupW1), - OpCode::DupW2 => Ok(Self::DupW2), - OpCode::DupW3 => Ok(Self::DupW3), - OpCode::Swap1 => Ok(Self::Swap1), - OpCode::Swap2 => Ok(Self::Swap2), - OpCode::Swap3 => Ok(Self::Swap3), - OpCode::Swap4 => Ok(Self::Swap4), - OpCode::Swap5 => Ok(Self::Swap5), - OpCode::Swap6 => Ok(Self::Swap6), - OpCode::Swap7 => Ok(Self::Swap7), - OpCode::Swap8 => Ok(Self::Swap8), - OpCode::Swap9 => Ok(Self::Swap9), - OpCode::Swap10 => Ok(Self::Swap10), - OpCode::Swap11 => Ok(Self::Swap11), - OpCode::Swap12 => Ok(Self::Swap12), - OpCode::Swap13 => Ok(Self::Swap13), - OpCode::Swap14 => Ok(Self::Swap14), - OpCode::Swap15 => Ok(Self::Swap15), - OpCode::SwapW1 => Ok(Self::SwapW1), - OpCode::SwapW2 => Ok(Self::SwapW2), - OpCode::SwapW3 => Ok(Self::SwapW3), - OpCode::SwapDW => Ok(Self::SwapDw), - OpCode::MovUp2 => Ok(Self::MovUp2), - OpCode::MovUp3 => Ok(Self::MovUp3), - OpCode::MovUp4 => Ok(Self::MovUp4), - OpCode::MovUp5 => Ok(Self::MovUp5), - OpCode::MovUp6 => Ok(Self::MovUp6), - OpCode::MovUp7 => Ok(Self::MovUp7), - OpCode::MovUp8 => Ok(Self::MovUp8), - OpCode::MovUp9 => Ok(Self::MovUp9), - OpCode::MovUp10 => Ok(Self::MovUp10), - OpCode::MovUp11 => Ok(Self::MovUp11), - OpCode::MovUp12 => Ok(Self::MovUp12), - OpCode::MovUp13 => Ok(Self::MovUp13), - OpCode::MovUp14 => Ok(Self::MovUp14), - OpCode::MovUp15 => Ok(Self::MovUp15), - OpCode::MovUpW2 => Ok(Self::MovUpW2), - OpCode::MovUpW3 => Ok(Self::MovUpW3), - OpCode::MovDn2 => Ok(Self::MovDn2), - OpCode::MovDn3 => Ok(Self::MovDn3), - OpCode::MovDn4 => Ok(Self::MovDn4), - OpCode::MovDn5 => Ok(Self::MovDn5), - OpCode::MovDn6 => Ok(Self::MovDn6), - OpCode::MovDn7 => Ok(Self::MovDn7), - OpCode::MovDn8 => Ok(Self::MovDn8), - OpCode::MovDn9 => Ok(Self::MovDn9), - OpCode::MovDn10 => Ok(Self::MovDn10), - OpCode::MovDn11 => Ok(Self::MovDn11), - OpCode::MovDn12 => Ok(Self::MovDn12), - OpCode::MovDn13 => Ok(Self::MovDn13), - OpCode::MovDn14 => Ok(Self::MovDn14), - OpCode::MovDn15 => Ok(Self::MovDn15), - OpCode::MovDnW2 => Ok(Self::MovDnW2), - OpCode::MovDnW3 => Ok(Self::MovDnW3), - OpCode::CSwap => Ok(Self::CSwap), - OpCode::CSwapW => Ok(Self::CSwapW), - OpCode::CDrop => Ok(Self::CDrop), - OpCode::CDropW => Ok(Self::CDropW), - - // ----- input / output operations ---------------------------------------------------- - OpCode::PushU8 => Ok(Self::PushU8(source.read_u8()?)), - OpCode::PushU16 => Ok(Self::PushU16(source.read_u16()?)), - OpCode::PushU32 => Ok(Self::PushU32(source.read_u32()?)), - OpCode::PushFelt => Ok(Self::PushFelt(Felt::read_from(source)?)), - OpCode::PushWord => Ok(Self::PushWord([ - Felt::read_from(source)?, - Felt::read_from(source)?, - Felt::read_from(source)?, - Felt::read_from(source)?, - ])), - OpCode::PushU8List => { - let length = parse_num_push_params(source)?; - (0..length) - .map(|_| source.read_u8()) - .collect::>() - .map(Self::PushU8List) - } - OpCode::PushU16List => { - let length = parse_num_push_params(source)?; - (0..length) - .map(|_| source.read_u16()) - .collect::>() - .map(Self::PushU16List) - } - OpCode::PushU32List => { - let length = parse_num_push_params(source)?; - (0..length) - .map(|_| source.read_u32()) - .collect::>() - .map(Self::PushU32List) - } - OpCode::PushFeltList => { - let length = parse_num_push_params(source)?; - (0..length) - .map(|_| Felt::read_from(source)) - .collect::>() - .map(Self::PushFeltList) - } - - OpCode::Locaddr => Ok(Self::Locaddr(source.read_u16()?.into())), - OpCode::Sdepth => Ok(Self::Sdepth), - OpCode::Caller => Ok(Self::Caller), - OpCode::Clk => Ok(Self::Clk), - - OpCode::MemLoad => Ok(Self::MemLoad), - OpCode::MemLoadImm => Ok(Self::MemLoadImm(source.read_u32()?.into())), - OpCode::MemLoadW => Ok(Self::MemLoadW), - OpCode::MemLoadWImm => Ok(Self::MemLoadWImm(source.read_u32()?.into())), - OpCode::LocLoad => Ok(Self::LocLoad(source.read_u16()?.into())), - OpCode::LocLoadW => Ok(Self::LocLoadW(source.read_u16()?.into())), - OpCode::MemStore => Ok(Self::MemStore), - OpCode::MemStoreImm => Ok(Self::MemStoreImm(source.read_u32()?.into())), - OpCode::LocStore => Ok(Self::LocStore(source.read_u16()?.into())), - OpCode::MemStoreW => Ok(Self::MemStoreW), - OpCode::MemStoreWImm => Ok(Self::MemStoreWImm(source.read_u32()?.into())), - OpCode::LocStoreW => Ok(Self::LocStoreW(source.read_u16()?.into())), - - OpCode::MemStream => Ok(Self::MemStream), - OpCode::AdvPipe => Ok(Self::AdvPipe), - - OpCode::AdvPush => Ok(Self::AdvPush(source.read_u8()?.into())), - OpCode::AdvLoadW => Ok(Self::AdvLoadW), - - OpCode::AdvInject => Ok(Self::AdvInject(AdviceInjectorNode::read_from(source)?)), - - // ----- cryptographic operations ----------------------------------------------------- - OpCode::Hash => Ok(Self::Hash), - OpCode::HMerge => Ok(Self::HMerge), - OpCode::HPerm => Ok(Self::HPerm), - OpCode::MTreeGet => Ok(Self::MTreeGet), - OpCode::MTreeSet => Ok(Self::MTreeSet), - OpCode::MTreeMerge => Ok(Self::MTreeMerge), - OpCode::MTreeVerify => Ok(Self::MTreeVerify), - - // ----- STARK proof verification ----------------------------------------------------- - OpCode::FriExt2Fold4 => Ok(Self::FriExt2Fold4), - OpCode::RCombBase => Ok(Self::RCombBase), - - // ----- exec / call ------------------------------------------------------------------ - OpCode::Exec => InvocationTarget::read_from(source).map(Self::Exec), - OpCode::Call => InvocationTarget::read_from(source).map(Self::Call), - OpCode::SysCall => InvocationTarget::read_from(source).map(Self::SysCall), - OpCode::DynExec => Ok(Self::DynExec), - OpCode::DynCall => Ok(Self::DynCall), - OpCode::ProcRef => InvocationTarget::read_from(source).map(Self::ProcRef), - - // ----- debugging -------------------------------------------------------------------- - OpCode::Debug => Ok(Self::Debug(DebugOptions::read_from(source)?)), - - // ----- event decorators ------------------------------------------------------------- - OpCode::Emit => Ok(Self::Emit(source.read_u32()?.into())), - OpCode::Trace => Ok(Self::Trace(source.read_u32()?.into())), - - // ----- control flow ----------------------------------------------------------------- - // control flow instructions should be parsed as a part of Op::read_from() and we - // should never get here - OpCode::IfElse => unreachable!(), - OpCode::Repeat => unreachable!(), - OpCode::While => unreachable!(), - } - } -} - -// HELPER FUNCTIONS -// ================================================================================================ - -fn parse_num_push_params(source: &mut R) -> Result { - use alloc::string::ToString; - - let length = source.read_u8()? as usize; - if !(1..=MAX_PUSH_INPUTS).contains(&length) { - Err(DeserializationError::InvalidValue("invalid push values argument".to_string())) - } else { - Ok(length as u8) - } -} diff --git a/assembly/src/ast/instruction/mod.rs b/assembly/src/ast/instruction/mod.rs index e5bba8e65a..a88441d89f 100644 --- a/assembly/src/ast/instruction/mod.rs +++ b/assembly/src/ast/instruction/mod.rs @@ -1,26 +1,24 @@ pub mod advice; pub mod debug; -mod deserialize; -mod opcode; mod print; -mod serialize; - -pub use self::advice::AdviceInjectorNode; -pub use self::debug::DebugOptions; -pub use self::opcode::OpCode; use alloc::vec::Vec; +pub use self::{advice::AdviceInjectorNode, debug::DebugOptions}; use crate::{ ast::{immediate::*, InvocationTarget}, Felt, Word, }; +// INSTRUCTION +// ================================================================================================ + /// Represents the set of primitive instructions in Miden Assembly syntax. /// /// NOTE: For control flow instructions, see [crate::ast::Op]. #[derive(Clone, PartialEq, Eq, Debug)] pub enum Instruction { + Nop, Assert, AssertWithError(ErrorCode), AssertEq, @@ -60,7 +58,7 @@ pub enum Instruction { Gte, IsOdd, - // ----- ext2 operations ---------------------------------------------------------------------- + // ----- ext2 operations --------------------------------------------------------------------- Ext2Add, Ext2Sub, Ext2Mul, @@ -68,7 +66,7 @@ pub enum Instruction { Ext2Neg, Ext2Inv, - // ----- u32 manipulation --------------------------------------------------------------------- + // ----- u32 manipulation -------------------------------------------------------------------- U32Test, U32TestW, U32Assert, @@ -125,7 +123,7 @@ pub enum Instruction { U32Min, U32Max, - // ----- stack manipulation ------------------------------------------------------------------- + // ----- stack manipulation ------------------------------------------------------------------ Drop, DropW, PadW, @@ -205,7 +203,7 @@ pub enum Instruction { CDrop, CDropW, - // ----- input / output operations ------------------------------------------------------------ + // ----- input / output operations ----------------------------------------------------------- Push(ImmFelt), PushU8(u8), PushU16(u16), @@ -243,7 +241,7 @@ pub enum Instruction { AdvInject(AdviceInjectorNode), - // ----- cryptographic operations ------------------------------------------------------------- + // ----- cryptographic operations ------------------------------------------------------------ Hash, HMerge, HPerm, @@ -251,12 +249,13 @@ pub enum Instruction { MTreeSet, MTreeMerge, MTreeVerify, + MTreeVerifyWithError(ErrorCode), - // ----- STARK proof verification ------------------------------------------------------------- + // ----- STARK proof verification ------------------------------------------------------------ FriExt2Fold4, RCombBase, - // ----- exec / call -------------------------------------------------------------------------- + // ----- exec / call ------------------------------------------------------------------------- Exec(InvocationTarget), Call(InvocationTarget), SysCall(InvocationTarget), @@ -264,11 +263,11 @@ pub enum Instruction { DynCall, ProcRef(InvocationTarget), - // ----- debug decorators --------------------------------------------------------------------- + // ----- debug decorators -------------------------------------------------------------------- Breakpoint, Debug(DebugOptions), - // ----- event decorators --------------------------------------------------------------------- + // ----- event decorators -------------------------------------------------------------------- Emit(ImmU32), Trace(ImmU32), } diff --git a/assembly/src/ast/instruction/opcode.rs b/assembly/src/ast/instruction/opcode.rs deleted file mode 100644 index fb206ca72b..0000000000 --- a/assembly/src/ast/instruction/opcode.rs +++ /dev/null @@ -1,311 +0,0 @@ -use crate::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; -use alloc::string::ToString; - -/// NOTE: If the number or order of variants in this enumeration changes, then the version number -/// of the serialized format must be incremented, and explicit handling must be present to -/// translate from the old values to the new values. The recommended approach would be to have -/// separate enums, one for each distinct version of the format, with a set of translations working -/// upwards from the lowest supported version. -/// -/// However, since serialized MASM is likely going away in favor of MAST, this may be a non-issue -/// soon anyway. -#[repr(u8)] -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum OpCode { - Assert = 0, - AssertWithError, - AssertEq, - AssertEqWithError, - AssertEqw, - AssertEqwWithError, - Assertz, - AssertzWithError, - Add, - AddImm, - Sub, - SubImm, - Mul, - MulImm, - Div, - DivImm, - Neg, - Inv, - Incr, - Pow2, - Exp, - ExpImm, - ExpBitLength, - ILog2, - Not, - And, - Or, - Xor, - Eq, - EqImm, - Neq, - NeqImm, - Eqw, - Lt, - Lte, - Gt, - Gte, - IsOdd, - - // ----- ext2 operations ---------------------------------------------------------------------- - Ext2Add, - Ext2Sub, - Ext2Mul, - Ext2Div, - Ext2Neg, - Ext2Inv, - - // ----- u32 manipulation --------------------------------------------------------------------- - U32Test, - U32TestW, - U32Assert, - U32AssertWithError, - U32Assert2, - U32Assert2WithError, - U32AssertW, - U32AssertWWithError, - U32Split, - U32Cast, - U32WrappingAdd, - U32WrappingAddImm, - U32OverflowingAdd, - U32OverflowingAddImm, - U32OverflowingAdd3, - U32WrappingAdd3, - U32WrappingSub, - U32WrappingSubImm, - U32OverflowingSub, - U32OverflowingSubImm, - U32WrappingMul, - U32WrappingMulImm, - U32OverflowingMul, - U32OverflowingMulImm, - U32OverflowingMadd, - U32WrappingMadd, - U32Div, - U32DivImm, - U32Mod, - U32ModImm, - U32DivMod, - U32DivModImm, - U32And, - U32Or, - U32Xor, - U32Not, - U32Shr, - U32ShrImm, - U32Shl, - U32ShlImm, - U32Rotr, - U32RotrImm, - U32Rotl, - U32RotlImm, - U32Popcnt, - U32Clz, - U32Ctz, - U32Clo, - U32Cto, - U32Lt, - U32Lte, - U32Gt, - U32Gte, - U32Min, - U32Max, - - // ----- stack manipulation ------------------------------------------------------------------- - Drop, - DropW, - PadW, - Dup0, - Dup1, - Dup2, - Dup3, - Dup4, - Dup5, - Dup6, - Dup7, - Dup8, - Dup9, - Dup10, - Dup11, - Dup12, - Dup13, - Dup14, - Dup15, - DupW0, - DupW1, - DupW2, - DupW3, - Swap1, - Swap2, - Swap3, - Swap4, - Swap5, - Swap6, - Swap7, - Swap8, - Swap9, - Swap10, - Swap11, - Swap12, - Swap13, - Swap14, - Swap15, - SwapW1, - SwapW2, - SwapW3, - SwapDW, - MovUp2, - MovUp3, - MovUp4, - MovUp5, - MovUp6, - MovUp7, - MovUp8, - MovUp9, - MovUp10, - MovUp11, - MovUp12, - MovUp13, - MovUp14, - MovUp15, - MovUpW2, - MovUpW3, - MovDn2, - MovDn3, - MovDn4, - MovDn5, - MovDn6, - MovDn7, - MovDn8, - MovDn9, - MovDn10, - MovDn11, - MovDn12, - MovDn13, - MovDn14, - MovDn15, - MovDnW2, - MovDnW3, - CSwap, - CSwapW, - CDrop, - CDropW, - - // ----- input / output operations ------------------------------------------------------------ - PushU8, - PushU16, - PushU32, - PushFelt, - PushWord, - PushU8List, - PushU16List, - PushU32List, - PushFeltList, - - Locaddr, - Sdepth, - Caller, - Clk, - - MemLoad, - MemLoadImm, - MemLoadW, - MemLoadWImm, - LocLoad, - LocLoadW, - MemStore, - MemStoreImm, - LocStore, - MemStoreW, - MemStoreWImm, - LocStoreW, - - MemStream, - AdvPipe, - - AdvPush, - AdvLoadW, - - AdvInject, - - // ----- cryptographic operations ------------------------------------------------------------- - Hash, - HMerge, - HPerm, - MTreeGet, - MTreeSet, - MTreeMerge, - MTreeVerify, - - // ----- STARK proof verification ------------------------------------------------------------- - FriExt2Fold4, - RCombBase, - - // ----- exec / call -------------------------------------------------------------------------- - Exec, - Call, - SysCall, - DynExec, - DynCall, - ProcRef, - - // ----- debugging ---------------------------------------------------------------------------- - Debug, - - // ----- event decorators --------------------------------------------------------------------- - Emit, - Trace, - - // ----- control flow ------------------------------------------------------------------------- - IfElse, - Repeat, - While, - // NOTE: If any further variants are added here, make sure you update the `MAX_DISCRIMINANT` - // constant -} - -impl OpCode { - const MAX_DISCRIMINANT: u8 = Self::While as u8; -} - -impl Serializable for OpCode { - fn write_into(&self, target: &mut W) { - target.write_u8(*self as u8); - } -} - -impl Deserializable for OpCode { - fn read_from(source: &mut R) -> Result { - let value = source.read_u8()?; - if value > Self::MAX_DISCRIMINANT { - return Err(DeserializationError::InvalidValue( - "could not read a valid opcode".to_string(), - )); - } - - // SAFETY: This is guaranteed safe for the following reasons: - // - // * OpCode is defined as repr(u8), giving it a stable representation equivalent to a u8 - // integer value - // - // * We have specified the discriminants for all of the OpCode variants. Specifically, we - // explicitly set the first variant to `0`, and each subsequent variant is incremented by - // 1. Thus the range from the first variant to the last variant is closed, and all integers - // in that range are valid discriminant values. - // - // * In Rust, constructing a repr(u*) fieldless enum from an integer is always valid if the - // integer value corresponds to a valid discriminant value, which as we've outlined above, - // is guaranteed to be true for all values <= OpCode::MAX_DISCRIMINANT - // - // NOTE: This safety property holds only so long as the number of variants does no - // _decrease_. It should be noted that it will be safe, but not correct, if the order of - // variants changes, or additional variants are added, without corresponding changes to the - // serialization code. - unsafe { Ok(core::mem::transmute::(value)) } - } -} diff --git a/assembly/src/ast/instruction/print.rs b/assembly/src/ast/instruction/print.rs index 99358b4132..b7ca11c37c 100644 --- a/assembly/src/ast/instruction/print.rs +++ b/assembly/src/ast/instruction/print.rs @@ -1,32 +1,32 @@ +use super::Instruction; use crate::{ ast::{Immediate, InvocationTarget}, prettier::{Document, PrettyPrint}, DisplayHex, Span, }; -use super::Instruction; - impl PrettyPrint for Instruction { fn render(&self) -> Document { use crate::prettier::*; match self { + Self::Nop => const_text("nop"), Self::Assert => const_text("assert"), Self::AssertWithError(err_code) => { flatten(const_text("assert.err") + const_text("=") + display(err_code)) - } + }, Self::AssertEq => const_text("assert_eq"), Self::AssertEqWithError(err_code) => { flatten(const_text("assert_eq.err") + const_text("=") + display(err_code)) - } + }, Self::AssertEqw => const_text("assert_eqw"), Self::AssertEqwWithError(err_code) => { flatten(const_text("assert_eqw.err") + const_text("=") + display(err_code)) - } + }, Self::Assertz => const_text("assertz"), Self::AssertzWithError(err_code) => { flatten(const_text("assertz.err") + const_text("=") + display(err_code)) - } + }, Self::Add => const_text("add"), Self::AddImm(value) => inst_with_felt_imm("add", value), Self::Sub => const_text("sub"), @@ -72,15 +72,15 @@ impl PrettyPrint for Instruction { Self::U32Assert => const_text("u32assert"), Self::U32AssertWithError(err_code) => { flatten(const_text("u32assert.err") + const_text("=") + display(err_code)) - } + }, Self::U32Assert2 => const_text("u32assert2"), Self::U32Assert2WithError(err_code) => { flatten(const_text("u32assert2.err") + const_text("=") + display(err_code)) - } + }, Self::U32AssertW => const_text("u32assertw"), Self::U32AssertWWithError(err_code) => { flatten(const_text("u32assertw.err") + const_text("=") + display(err_code)) - } + }, Self::U32Split => const_text("u32split"), Self::U32Cast => const_text("u32cast"), Self::U32WrappingAdd => const_text("u32wrapping_add"), @@ -216,7 +216,7 @@ impl PrettyPrint for Instruction { Self::PushU32(value) => inst_with_imm("push", value), Self::PushFelt(value) => { inst_with_felt_imm("push", &Immediate::Value(Span::unknown(*value))) - } + }, Self::PushWord(values) => inst_with_pretty_felt_params("push", values), Self::PushU8List(values) => inst_with_pretty_params("push", values), Self::PushU16List(values) => inst_with_pretty_params("push", values), @@ -258,6 +258,9 @@ impl PrettyPrint for Instruction { Self::MTreeSet => const_text("mtree_set"), Self::MTreeMerge => const_text("mtree_merge"), Self::MTreeVerify => const_text("mtree_verify"), + Self::MTreeVerifyWithError(err_code) => { + flatten(const_text("mtree_verify.err") + const_text("=") + display(err_code)) + }, // ----- STARK proof verification ----------------------------------------------------- Self::FriExt2Fold4 => const_text("fri_ext2fold4"), @@ -271,56 +274,54 @@ impl PrettyPrint for Instruction { ), Self::Exec(InvocationTarget::ProcedureName(name)) => { flatten(const_text("exec") + const_text(".") + text(name)) - } + }, Self::Exec(InvocationTarget::ProcedurePath { name, module }) => { const_text("exec") + const_text(".") + text(format!("{}::{}", module, name)) - } + }, Self::Exec(InvocationTarget::AbsoluteProcedurePath { name, path }) => { - const_text("exec") + const_text(".") + text(format!("{}::{}", path.last(), name)) - } + const_text("exec") + const_text(".") + text(format!("::{}::{}", path, name)) + }, Self::Call(InvocationTarget::MastRoot(root)) => { const_text("call") + const_text(".") + text(format!("{:#x}", DisplayHex(root.as_bytes().as_slice()))) - } + }, Self::Call(InvocationTarget::ProcedureName(name)) => { flatten(const_text("call") + const_text(".") + text(name)) - } + }, Self::Call(InvocationTarget::ProcedurePath { name, module }) => { const_text("call") + const_text(".") + text(format!("{}::{}", module, name)) - } + }, Self::Call(InvocationTarget::AbsoluteProcedurePath { name, path }) => { - const_text("call") + const_text(".") + text(format!("{}::{}", path.last(), name)) - } + const_text("call") + const_text(".") + text(format!("::{}::{}", path, name)) + }, Self::SysCall(InvocationTarget::MastRoot(root)) => { const_text("syscall") + const_text(".") + text(format!("{:#x}", DisplayHex(root.as_bytes().as_slice()))) - } + }, Self::SysCall(InvocationTarget::ProcedureName(name)) => { flatten(const_text("syscall") + const_text(".") + text(format!("{}", name))) - } + }, Self::SysCall(InvocationTarget::ProcedurePath { name, module }) => { const_text("syscall") + const_text(".") + text(format!("{}::{}", module, name)) - } + }, Self::SysCall(InvocationTarget::AbsoluteProcedurePath { name, path }) => { - const_text("syscall") + const_text(".") + text(format!("{}::{}", path.last(), name)) - } + const_text("syscall") + const_text(".") + text(format!("::{}::{}", path, name)) + }, Self::DynExec => const_text("dynexec"), Self::DynCall => const_text("dyncall"), Self::ProcRef(InvocationTarget::MastRoot(_)) => { panic!("invalid procref instruction: expected name not MAST root") - } + }, Self::ProcRef(InvocationTarget::ProcedureName(name)) => { flatten(const_text("procref") + const_text(".") + text(name)) - } + }, Self::ProcRef(InvocationTarget::ProcedurePath { name, module }) => flatten( const_text("procref") + const_text(".") + text(format!("{}::{}", module, name)), ), Self::ProcRef(InvocationTarget::AbsoluteProcedurePath { name, path }) => flatten( - const_text("procref") - + const_text(".") - + text(format!("{}::{}", path.last(), name)), + const_text("procref") + const_text(".") + text(format!("::{}::{}", path, name)), ), // ----- debug decorators ------------------------------------------------------------- @@ -393,11 +394,15 @@ fn inst_with_pretty_params(inst: &'static str, params: &[P]) -> single_line | multi_line } +// TESTS +// ================================================================================================ + #[cfg(test)] mod tests { - use crate::{ast::*, Felt, Span}; use vm_core::crypto::hash::Rpo256; + use crate::{ast::*, Felt, Span}; + #[test] fn test_instruction_display() { let instruction = format!("{}", Instruction::Assert); diff --git a/assembly/src/ast/instruction/serialize.rs b/assembly/src/ast/instruction/serialize.rs deleted file mode 100644 index c95ed13e66..0000000000 --- a/assembly/src/ast/instruction/serialize.rs +++ /dev/null @@ -1,435 +0,0 @@ -use crate::{ - ast::{Instruction, OpCode}, - ByteWriter, Serializable, -}; - -impl Serializable for Instruction { - fn write_into(&self, target: &mut W) { - match self { - Self::Assert => OpCode::Assert.write_into(target), - Self::AssertWithError(err_code) => { - OpCode::AssertWithError.write_into(target); - target.write_u32(err_code.expect_value()); - } - Self::AssertEq => OpCode::AssertEq.write_into(target), - Self::AssertEqWithError(err_code) => { - OpCode::AssertEqWithError.write_into(target); - target.write_u32(err_code.expect_value()); - } - Self::AssertEqw => OpCode::AssertEqw.write_into(target), - Self::AssertEqwWithError(err_code) => { - OpCode::AssertEqwWithError.write_into(target); - target.write_u32(err_code.expect_value()); - } - Self::Assertz => OpCode::Assertz.write_into(target), - Self::AssertzWithError(err_code) => { - OpCode::AssertzWithError.write_into(target); - target.write_u32(err_code.expect_value()); - } - Self::Add => OpCode::Add.write_into(target), - Self::AddImm(v) => { - OpCode::AddImm.write_into(target); - v.expect_value().write_into(target); - } - Self::Sub => OpCode::Sub.write_into(target), - Self::SubImm(v) => { - OpCode::SubImm.write_into(target); - v.expect_value().write_into(target); - } - Self::Mul => OpCode::Mul.write_into(target), - Self::MulImm(v) => { - OpCode::MulImm.write_into(target); - v.expect_value().write_into(target); - } - Self::Div => OpCode::Div.write_into(target), - Self::DivImm(v) => { - OpCode::DivImm.write_into(target); - v.expect_value().write_into(target); - } - Self::Neg => OpCode::Neg.write_into(target), - Self::ILog2 => OpCode::ILog2.write_into(target), - Self::Inv => OpCode::Inv.write_into(target), - Self::Incr => OpCode::Incr.write_into(target), - Self::Pow2 => OpCode::Pow2.write_into(target), - Self::Exp => OpCode::Exp.write_into(target), - Self::ExpImm(v) => { - OpCode::ExpImm.write_into(target); - v.expect_value().write_into(target); - } - Self::ExpBitLength(v) => { - OpCode::ExpBitLength.write_into(target); - target.write_u8(*v); - } - Self::Not => OpCode::Not.write_into(target), - Self::And => OpCode::And.write_into(target), - Self::Or => OpCode::Or.write_into(target), - Self::Xor => OpCode::Xor.write_into(target), - Self::Eq => OpCode::Eq.write_into(target), - Self::EqImm(v) => { - OpCode::EqImm.write_into(target); - v.expect_value().write_into(target); - } - Self::Neq => OpCode::Neq.write_into(target), - Self::NeqImm(v) => { - OpCode::NeqImm.write_into(target); - v.expect_value().write_into(target); - } - Self::Eqw => OpCode::Eqw.write_into(target), - Self::Lt => OpCode::Lt.write_into(target), - Self::Lte => OpCode::Lte.write_into(target), - Self::Gt => OpCode::Gt.write_into(target), - Self::Gte => OpCode::Gte.write_into(target), - Self::IsOdd => OpCode::IsOdd.write_into(target), - - // ----- ext2 operations -------------------------------------------------------------- - Self::Ext2Add => OpCode::Ext2Add.write_into(target), - Self::Ext2Sub => OpCode::Ext2Sub.write_into(target), - Self::Ext2Mul => OpCode::Ext2Mul.write_into(target), - Self::Ext2Div => OpCode::Ext2Div.write_into(target), - Self::Ext2Neg => OpCode::Ext2Neg.write_into(target), - Self::Ext2Inv => OpCode::Ext2Inv.write_into(target), - - // ----- u32 operations --------------------------------------------------------------- - Self::U32Test => OpCode::U32Test.write_into(target), - Self::U32TestW => OpCode::U32TestW.write_into(target), - Self::U32Assert => OpCode::U32Assert.write_into(target), - Self::U32AssertWithError(err_code) => { - OpCode::U32AssertWithError.write_into(target); - target.write_u32(err_code.expect_value()); - } - Self::U32Assert2 => OpCode::U32Assert2.write_into(target), - Self::U32Assert2WithError(err_code) => { - OpCode::U32Assert2WithError.write_into(target); - target.write_u32(err_code.expect_value()); - } - Self::U32AssertW => OpCode::U32AssertW.write_into(target), - Self::U32AssertWWithError(err_code) => { - OpCode::U32AssertWWithError.write_into(target); - target.write_u32(err_code.expect_value()); - } - Self::U32Split => OpCode::U32Split.write_into(target), - Self::U32Cast => OpCode::U32Cast.write_into(target), - Self::U32WrappingAdd => OpCode::U32WrappingAdd.write_into(target), - Self::U32WrappingAddImm(v) => { - OpCode::U32WrappingAddImm.write_into(target); - target.write_u32(v.expect_value()); - } - Self::U32OverflowingAdd => OpCode::U32OverflowingAdd.write_into(target), - Self::U32OverflowingAddImm(v) => { - OpCode::U32OverflowingAddImm.write_into(target); - target.write_u32(v.expect_value()); - } - Self::U32OverflowingAdd3 => OpCode::U32OverflowingAdd3.write_into(target), - Self::U32WrappingAdd3 => OpCode::U32WrappingAdd3.write_into(target), - Self::U32WrappingSub => OpCode::U32WrappingSub.write_into(target), - Self::U32WrappingSubImm(v) => { - OpCode::U32WrappingSubImm.write_into(target); - target.write_u32(v.expect_value()); - } - Self::U32OverflowingSub => OpCode::U32OverflowingSub.write_into(target), - Self::U32OverflowingSubImm(v) => { - OpCode::U32OverflowingSubImm.write_into(target); - target.write_u32(v.expect_value()); - } - Self::U32WrappingMul => OpCode::U32WrappingMul.write_into(target), - Self::U32WrappingMulImm(v) => { - OpCode::U32WrappingMulImm.write_into(target); - target.write_u32(v.expect_value()); - } - Self::U32OverflowingMul => OpCode::U32OverflowingMul.write_into(target), - Self::U32OverflowingMulImm(v) => { - OpCode::U32OverflowingMulImm.write_into(target); - target.write_u32(v.expect_value()); - } - Self::U32OverflowingMadd => OpCode::U32OverflowingMadd.write_into(target), - Self::U32WrappingMadd => OpCode::U32WrappingMadd.write_into(target), - Self::U32Div => OpCode::U32Div.write_into(target), - Self::U32DivImm(v) => { - OpCode::U32DivImm.write_into(target); - target.write_u32(v.expect_value()); - } - Self::U32Mod => OpCode::U32Mod.write_into(target), - Self::U32ModImm(v) => { - OpCode::U32ModImm.write_into(target); - target.write_u32(v.expect_value()); - } - Self::U32DivMod => OpCode::U32DivMod.write_into(target), - Self::U32DivModImm(v) => { - OpCode::U32DivModImm.write_into(target); - target.write_u32(v.expect_value()); - } - Self::U32And => OpCode::U32And.write_into(target), - Self::U32Or => OpCode::U32Or.write_into(target), - Self::U32Xor => OpCode::U32Xor.write_into(target), - Self::U32Not => OpCode::U32Not.write_into(target), - Self::U32Shr => OpCode::U32Shr.write_into(target), - Self::U32ShrImm(v) => { - OpCode::U32ShrImm.write_into(target); - target.write_u8(v.expect_value()); - } - Self::U32Shl => OpCode::U32Shl.write_into(target), - Self::U32ShlImm(v) => { - OpCode::U32ShlImm.write_into(target); - target.write_u8(v.expect_value()); - } - Self::U32Rotr => OpCode::U32Rotr.write_into(target), - Self::U32RotrImm(v) => { - OpCode::U32RotrImm.write_into(target); - target.write_u8(v.expect_value()); - } - Self::U32Rotl => OpCode::U32Rotl.write_into(target), - Self::U32RotlImm(v) => { - OpCode::U32RotlImm.write_into(target); - target.write_u8(v.expect_value()); - } - Self::U32Popcnt => OpCode::U32Popcnt.write_into(target), - Self::U32Clz => OpCode::U32Clz.write_into(target), - Self::U32Ctz => OpCode::U32Ctz.write_into(target), - Self::U32Clo => OpCode::U32Clo.write_into(target), - Self::U32Cto => OpCode::U32Cto.write_into(target), - Self::U32Lt => OpCode::U32Lt.write_into(target), - Self::U32Lte => OpCode::U32Lte.write_into(target), - Self::U32Gt => OpCode::U32Gt.write_into(target), - Self::U32Gte => OpCode::U32Gte.write_into(target), - Self::U32Min => OpCode::U32Min.write_into(target), - Self::U32Max => OpCode::U32Max.write_into(target), - - // ----- stack manipulation ----------------------------------------------------------- - Self::Drop => OpCode::Drop.write_into(target), - Self::DropW => OpCode::DropW.write_into(target), - Self::PadW => OpCode::PadW.write_into(target), - Self::Dup0 => OpCode::Dup0.write_into(target), - Self::Dup1 => OpCode::Dup1.write_into(target), - Self::Dup2 => OpCode::Dup2.write_into(target), - Self::Dup3 => OpCode::Dup3.write_into(target), - Self::Dup4 => OpCode::Dup4.write_into(target), - Self::Dup5 => OpCode::Dup5.write_into(target), - Self::Dup6 => OpCode::Dup6.write_into(target), - Self::Dup7 => OpCode::Dup7.write_into(target), - Self::Dup8 => OpCode::Dup8.write_into(target), - Self::Dup9 => OpCode::Dup9.write_into(target), - Self::Dup10 => OpCode::Dup10.write_into(target), - Self::Dup11 => OpCode::Dup11.write_into(target), - Self::Dup12 => OpCode::Dup12.write_into(target), - Self::Dup13 => OpCode::Dup13.write_into(target), - Self::Dup14 => OpCode::Dup14.write_into(target), - Self::Dup15 => OpCode::Dup15.write_into(target), - Self::DupW0 => OpCode::DupW0.write_into(target), - Self::DupW1 => OpCode::DupW1.write_into(target), - Self::DupW2 => OpCode::DupW2.write_into(target), - Self::DupW3 => OpCode::DupW3.write_into(target), - Self::Swap1 => OpCode::Swap1.write_into(target), - Self::Swap2 => OpCode::Swap2.write_into(target), - Self::Swap3 => OpCode::Swap3.write_into(target), - Self::Swap4 => OpCode::Swap4.write_into(target), - Self::Swap5 => OpCode::Swap5.write_into(target), - Self::Swap6 => OpCode::Swap6.write_into(target), - Self::Swap7 => OpCode::Swap7.write_into(target), - Self::Swap8 => OpCode::Swap8.write_into(target), - Self::Swap9 => OpCode::Swap9.write_into(target), - Self::Swap10 => OpCode::Swap10.write_into(target), - Self::Swap11 => OpCode::Swap11.write_into(target), - Self::Swap12 => OpCode::Swap12.write_into(target), - Self::Swap13 => OpCode::Swap13.write_into(target), - Self::Swap14 => OpCode::Swap14.write_into(target), - Self::Swap15 => OpCode::Swap15.write_into(target), - Self::SwapW1 => OpCode::SwapW1.write_into(target), - Self::SwapW2 => OpCode::SwapW2.write_into(target), - Self::SwapW3 => OpCode::SwapW3.write_into(target), - Self::SwapDw => OpCode::SwapDW.write_into(target), - Self::MovUp2 => OpCode::MovUp2.write_into(target), - Self::MovUp3 => OpCode::MovUp3.write_into(target), - Self::MovUp4 => OpCode::MovUp4.write_into(target), - Self::MovUp5 => OpCode::MovUp5.write_into(target), - Self::MovUp6 => OpCode::MovUp6.write_into(target), - Self::MovUp7 => OpCode::MovUp7.write_into(target), - Self::MovUp8 => OpCode::MovUp8.write_into(target), - Self::MovUp9 => OpCode::MovUp9.write_into(target), - Self::MovUp10 => OpCode::MovUp10.write_into(target), - Self::MovUp11 => OpCode::MovUp11.write_into(target), - Self::MovUp12 => OpCode::MovUp12.write_into(target), - Self::MovUp13 => OpCode::MovUp13.write_into(target), - Self::MovUp14 => OpCode::MovUp14.write_into(target), - Self::MovUp15 => OpCode::MovUp15.write_into(target), - Self::MovUpW2 => OpCode::MovUpW2.write_into(target), - Self::MovUpW3 => OpCode::MovUpW3.write_into(target), - Self::MovDn2 => OpCode::MovDn2.write_into(target), - Self::MovDn3 => OpCode::MovDn3.write_into(target), - Self::MovDn4 => OpCode::MovDn4.write_into(target), - Self::MovDn5 => OpCode::MovDn5.write_into(target), - Self::MovDn6 => OpCode::MovDn6.write_into(target), - Self::MovDn7 => OpCode::MovDn7.write_into(target), - Self::MovDn8 => OpCode::MovDn8.write_into(target), - Self::MovDn9 => OpCode::MovDn9.write_into(target), - Self::MovDn10 => OpCode::MovDn10.write_into(target), - Self::MovDn11 => OpCode::MovDn11.write_into(target), - Self::MovDn12 => OpCode::MovDn12.write_into(target), - Self::MovDn13 => OpCode::MovDn13.write_into(target), - Self::MovDn14 => OpCode::MovDn14.write_into(target), - Self::MovDn15 => OpCode::MovDn15.write_into(target), - Self::MovDnW2 => OpCode::MovDnW2.write_into(target), - Self::MovDnW3 => OpCode::MovDnW3.write_into(target), - Self::CSwap => OpCode::CSwap.write_into(target), - Self::CSwapW => OpCode::CSwapW.write_into(target), - Self::CDrop => OpCode::CDrop.write_into(target), - Self::CDropW => OpCode::CDropW.write_into(target), - - // ----- input / output operations ---------------------------------------------------- - Self::Push(imm) => { - OpCode::PushFelt.write_into(target); - imm.expect_value().write_into(target); - } - Self::PushU8(value) => { - OpCode::PushU8.write_into(target); - target.write_u8(*value); - } - Self::PushU16(value) => { - OpCode::PushU16.write_into(target); - target.write_u16(*value); - } - Self::PushU32(value) => { - OpCode::PushU32.write_into(target); - target.write_u32(*value); - } - Self::PushFelt(value) => { - OpCode::PushFelt.write_into(target); - value.write_into(target); - } - Self::PushWord(values) => { - OpCode::PushWord.write_into(target); - values.iter().for_each(|&v| v.write_into(target)); - } - Self::PushU8List(values) => { - OpCode::PushU8List.write_into(target); - target.write_u8(values.len() as u8); - values.iter().for_each(|&v| target.write_u8(v)); - } - Self::PushU16List(values) => { - OpCode::PushU16List.write_into(target); - target.write_u8(values.len() as u8); - values.iter().for_each(|&v| target.write_u16(v)); - } - Self::PushU32List(values) => { - OpCode::PushU32List.write_into(target); - target.write_u8(values.len() as u8); - values.iter().for_each(|&v| target.write_u32(v)); - } - Self::PushFeltList(values) => { - OpCode::PushFeltList.write_into(target); - target.write_u8(values.len() as u8); - values.iter().for_each(|&v| v.write_into(target)); - } - Self::Locaddr(v) => { - OpCode::Locaddr.write_into(target); - target.write_u16(v.expect_value()); - } - Self::Sdepth => OpCode::Sdepth.write_into(target), - Self::Caller => OpCode::Caller.write_into(target), - Self::Clk => OpCode::Clk.write_into(target), - - Self::MemLoad => OpCode::MemLoad.write_into(target), - Self::MemLoadImm(v) => { - OpCode::MemLoadImm.write_into(target); - target.write_u32(v.expect_value()); - } - Self::MemLoadW => OpCode::MemLoadW.write_into(target), - Self::MemLoadWImm(v) => { - OpCode::MemLoadWImm.write_into(target); - target.write_u32(v.expect_value()); - } - Self::LocLoad(v) => { - OpCode::LocLoad.write_into(target); - target.write_u16(v.expect_value()); - } - Self::LocLoadW(v) => { - OpCode::LocLoadW.write_into(target); - target.write_u16(v.expect_value()); - } - Self::MemStore => OpCode::MemStore.write_into(target), - Self::MemStoreImm(v) => { - OpCode::MemStoreImm.write_into(target); - target.write_u32(v.expect_value()); - } - Self::LocStore(v) => { - OpCode::LocStore.write_into(target); - target.write_u16(v.expect_value()); - } - Self::MemStoreW => OpCode::MemStoreW.write_into(target), - Self::MemStoreWImm(v) => { - OpCode::MemStoreWImm.write_into(target); - target.write_u32(v.expect_value()); - } - Self::LocStoreW(v) => { - OpCode::LocStoreW.write_into(target); - target.write_u16(v.expect_value()); - } - - Self::MemStream => OpCode::MemStream.write_into(target), - Self::AdvPipe => OpCode::AdvPipe.write_into(target), - - Self::AdvPush(v) => { - OpCode::AdvPush.write_into(target); - target.write_u8(v.expect_value()); - } - Self::AdvLoadW => OpCode::AdvLoadW.write_into(target), - - Self::AdvInject(injector) => { - OpCode::AdvInject.write_into(target); - injector.write_into(target); - } - - // ----- cryptographic operations ----------------------------------------------------- - Self::Hash => OpCode::Hash.write_into(target), - Self::HMerge => OpCode::HMerge.write_into(target), - Self::HPerm => OpCode::HPerm.write_into(target), - Self::MTreeGet => OpCode::MTreeGet.write_into(target), - Self::MTreeSet => OpCode::MTreeSet.write_into(target), - Self::MTreeMerge => OpCode::MTreeMerge.write_into(target), - Self::MTreeVerify => OpCode::MTreeVerify.write_into(target), - - // ----- STARK proof verification ----------------------------------------------------- - Self::FriExt2Fold4 => OpCode::FriExt2Fold4.write_into(target), - Self::RCombBase => OpCode::RCombBase.write_into(target), - - // ----- exec / call ------------------------------------------------------------------ - Self::Exec(ref callee) => { - OpCode::Exec.write_into(target); - callee.write_into(target); - } - Self::Call(ref callee) => { - OpCode::Call.write_into(target); - callee.write_into(target); - } - Self::SysCall(ref callee) => { - OpCode::SysCall.write_into(target); - callee.write_into(target); - } - Self::DynExec => OpCode::DynExec.write_into(target), - Self::DynCall => OpCode::DynCall.write_into(target), - Self::ProcRef(ref callee) => { - OpCode::ProcRef.write_into(target); - callee.write_into(target); - } - - // ----- debug decorators ------------------------------------------------------------- - Self::Breakpoint => { - // this is a transparent instruction and will not be encoded into the library - } - - Self::Debug(options) => { - OpCode::Debug.write_into(target); - options.write_into(target); - } - - // ----- event decorators ------------------------------------------------------------- - Self::Emit(event_id) => { - OpCode::Emit.write_into(target); - target.write_u32(event_id.expect_value()); - } - Self::Trace(trace_id) => { - OpCode::Trace.write_into(target); - target.write_u32(trace_id.expect_value()); - } - } - } -} diff --git a/assembly/src/ast/invocation_target.rs b/assembly/src/ast/invocation_target.rs index 244e9e7800..25208b6b3e 100644 --- a/assembly/src/ast/invocation_target.rs +++ b/assembly/src/ast/invocation_target.rs @@ -1,7 +1,8 @@ +use core::fmt; + use crate::{ - ast::{AstSerdeOptions, Ident, ProcedureName}, - ByteReader, ByteWriter, Deserializable, DeserializationError, LibraryPath, RpoDigest, - Serializable, SourceSpan, Span, Spanned, + ast::{Ident, ProcedureName}, + LibraryPath, RpoDigest, SourceSpan, Span, Spanned, }; // INVOKE @@ -14,6 +15,7 @@ pub enum InvokeKind { Exec = 0, Call, SysCall, + ProcRef, } /// Represents a specific invocation @@ -51,23 +53,19 @@ impl Invoke { /// /// All other combinations will result in an error. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -#[repr(u8)] pub enum InvocationTarget { /// An absolute procedure reference, but opaque in that we do not know where the callee is /// defined. However, it does not actually matter, we consider such references to be _a priori_ /// valid. - MastRoot(Span) = 0, + MastRoot(Span), /// A locally-defined procedure. - ProcedureName(ProcedureName) = 1, + ProcedureName(ProcedureName), /// A context-sensitive procedure path, which references the name of an import in the /// containing module. - ProcedurePath { name: ProcedureName, module: Ident } = 2, + ProcedurePath { name: ProcedureName, module: Ident }, /// A fully-resolved procedure path, which refers to a specific externally-defined procedure /// with its full path. - AbsoluteProcedurePath { - name: ProcedureName, - path: LibraryPath, - } = 3, + AbsoluteProcedurePath { name: ProcedureName, path: LibraryPath }, } impl Spanned for InvocationTarget { @@ -77,78 +75,31 @@ impl Spanned for InvocationTarget { Self::ProcedureName(ref spanned) => spanned.span(), Self::ProcedurePath { ref name, .. } | Self::AbsoluteProcedurePath { ref name, .. } => { name.span() - } + }, } } } -impl InvocationTarget { - fn tag(&self) -> u8 { - // SAFETY: This is safe because we have given this enum a primitive representation with - // #[repr(u8)], with the first field of the underlying union-of-structs the discriminant. - // - // See the section on "accessing the numeric value of the discriminant" - // here: https://doc.rust-lang.org/std/mem/fn.discriminant.html - unsafe { *<*const _>::from(self).cast::() } - } -} +impl crate::prettier::PrettyPrint for InvocationTarget { + fn render(&self) -> crate::prettier::Document { + use vm_core::utils::DisplayHex; + + use crate::prettier::*; -impl Serializable for InvocationTarget { - fn write_into(&self, target: &mut W) { - target.write_u8(self.tag()); match self { - Self::MastRoot(spanned) => { - spanned.write_into(target, AstSerdeOptions::new(false, true)) - } - Self::ProcedureName(name) => { - name.write_into_with_options(target, AstSerdeOptions::new(false, true)) - } - Self::ProcedurePath { name, module } => { - name.write_into_with_options(target, AstSerdeOptions::new(false, true)); - module.write_into(target); - } + Self::MastRoot(digest) => display(DisplayHex(digest.as_bytes().as_slice())), + Self::ProcedureName(name) => display(name), + Self::ProcedurePath { name, module } => display(format_args!("{}::{}", module, name)), Self::AbsoluteProcedurePath { name, path } => { - name.write_into_with_options(target, AstSerdeOptions::new(false, true)); - path.write_into(target); - } + display(format_args!("::{}::{}", path, name)) + }, } } } +impl fmt::Display for InvocationTarget { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use crate::prettier::PrettyPrint; -impl Deserializable for InvocationTarget { - fn read_from(source: &mut R) -> Result { - match source.read_u8()? { - 0 => { - let root = Span::::read_from(source, AstSerdeOptions::new(false, true))?; - Ok(Self::MastRoot(root)) - } - 1 => { - let name = ProcedureName::read_from_with_options( - source, - AstSerdeOptions::new(false, true), - )?; - Ok(Self::ProcedureName(name)) - } - 2 => { - let name = ProcedureName::read_from_with_options( - source, - AstSerdeOptions::new(false, true), - )?; - let module = Ident::read_from(source)?; - Ok(Self::ProcedurePath { name, module }) - } - 3 => { - let name = ProcedureName::read_from_with_options( - source, - AstSerdeOptions::new(false, true), - )?; - let path = LibraryPath::read_from(source)?; - Ok(Self::AbsoluteProcedurePath { name, path }) - } - n => Err(DeserializationError::InvalidValue(format!( - "{} is not a valid invocation target type", - n - ))), - } + self.pretty_print(f) } } diff --git a/assembly/src/ast/mod.rs b/assembly/src/ast/mod.rs index dc04c151e0..a606d9e906 100644 --- a/assembly/src/ast/mod.rs +++ b/assembly/src/ast/mod.rs @@ -1,5 +1,6 @@ //! Abstract syntax tree (AST) components of Miden programs, modules, and procedures. +mod attribute; mod block; mod constants; mod form; @@ -11,26 +12,28 @@ mod invocation_target; mod module; mod op; mod procedure; -mod serde; #[cfg(test)] mod tests; pub mod visit; -pub use self::block::Block; -pub use self::constants::{Constant, ConstantExpr, ConstantOp}; -pub use self::form::Form; -pub use self::ident::{CaseKindError, Ident, IdentError}; -pub use self::immediate::{ErrorCode, ImmFelt, ImmU16, ImmU32, ImmU8, Immediate}; -pub use self::imports::Import; -pub use self::instruction::{ - advice::SignatureKind, AdviceInjectorNode, DebugOptions, Instruction, OpCode, +pub use self::{ + attribute::{ + Attribute, AttributeSet, AttributeSetEntry, BorrowedMeta, Meta, MetaExpr, MetaItem, + MetaKeyValue, MetaList, + }, + block::Block, + constants::{Constant, ConstantExpr, ConstantOp}, + form::Form, + ident::{CaseKindError, Ident, IdentError}, + immediate::{ErrorCode, ImmFelt, ImmU16, ImmU32, ImmU8, Immediate}, + imports::Import, + instruction::{advice::SignatureKind, AdviceInjectorNode, DebugOptions, Instruction}, + invocation_target::{InvocationTarget, Invoke, InvokeKind}, + module::{Module, ModuleKind}, + op::Op, + procedure::*, + visit::{Visit, VisitMut}, }; -pub use self::invocation_target::{InvocationTarget, Invoke, InvokeKind}; -pub use self::module::{Module, ModuleKind}; -pub use self::op::Op; -pub use self::procedure::*; -pub use self::serde::AstSerdeOptions; -pub use self::visit::{Visit, VisitMut}; pub(crate) type SmallOpsVec = smallvec::SmallVec<[Op; 1]>; diff --git a/assembly/src/ast/module.rs b/assembly/src/ast/module.rs index 1a5b6af251..38d7e87cc6 100644 --- a/assembly/src/ast/module.rs +++ b/assembly/src/ast/module.rs @@ -1,15 +1,14 @@ -use alloc::{ - boxed::Box, - string::{String, ToString}, - sync::Arc, - vec::Vec, -}; +use alloc::{boxed::Box, string::String, sync::Arc, vec::Vec}; use core::fmt; -use super::{Export, Import, LocalNameResolver, ProcedureIndex, ProcedureName, ResolvedProcedure}; +use super::{ + Export, Import, LocalNameResolver, ProcedureIndex, ProcedureName, QualifiedProcedureName, + ResolvedProcedure, +}; use crate::{ - ast::{AstSerdeOptions, Ident}, + ast::{AliasTarget, Ident}, diagnostics::{Report, SourceFile}, + parser::ModuleParser, sema::SemanticAnalysisError, ByteReader, ByteWriter, Deserializable, DeserializationError, LibraryNamespace, LibraryPath, Serializable, SourceSpan, Span, Spanned, @@ -46,8 +45,8 @@ pub enum ModuleKind { /// A kernel is like a library module, but is special in a few ways: /// /// * Its code always executes in the root context, so it is stateful in a way that normal - /// libraries cannot replicate. This can be used to provide core services that would otherwise - /// not be possible to implement. + /// libraries cannot replicate. This can be used to provide core services that would + /// otherwise not be possible to implement. /// /// * The procedures exported from the kernel may be the target of the `syscall` instruction, /// and in fact _must_ be called that way. @@ -108,12 +107,6 @@ impl Deserializable for ModuleKind { pub struct Module { /// The span covering the entire definition of this module. span: SourceSpan, - /// If available/known, the source contents from which this module was parsed. This is used - /// to provide rich diagnostics output during semantic analysis. - /// - /// In cases where this file is not available, diagnostics will revert to a simple form with - /// a helpful message, but without source code snippets. - source_file: Option>, /// The documentation associated with this module. /// /// Module documentation is provided in Miden Assembly as a documentation comment starting on @@ -133,6 +126,18 @@ pub struct Module { pub(crate) procedures: Vec, } +/// Constants +impl Module { + /// File extension for a Assembly Module. + pub const FILE_EXTENSION: &'static str = "masm"; + + /// Name of the root module. + pub const ROOT: &'static str = "mod"; + + /// File name of the root module. + pub const ROOT_FILENAME: &'static str = "mod.masm"; +} + /// Construction impl Module { /// Creates a new [Module] with the specified `kind` and fully-qualified path, e.g. @@ -140,7 +145,6 @@ impl Module { pub fn new(kind: ModuleKind, path: LibraryPath) -> Self { Self { span: Default::default(), - source_file: None, docs: None, path, kind, @@ -159,14 +163,6 @@ impl Module { Self::new(ModuleKind::Executable, LibraryNamespace::Exec.into()) } - /// Builds this [Module] with the given source file in which it was defined. - /// - /// When a source file is given, diagnostics will contain source code snippets. - pub fn with_source_file(mut self, source_file: Option>) -> Self { - self.source_file = source_file; - self - } - /// Specifies the source span in the source file in which this module was defined, that covers /// the full definition of this module. pub fn with_span(mut self, span: SourceSpan) -> Self { @@ -174,11 +170,6 @@ impl Module { self } - /// Like [Module::with_source_file], but does not require ownership of the [Module]. - pub fn set_source_file(&mut self, source_file: Arc) { - self.source_file = Some(source_file); - } - /// Sets the [LibraryPath] for this module pub fn set_path(&mut self, path: LibraryPath) { self.path = path; @@ -203,16 +194,11 @@ impl Module { /// previous definition pub fn define_procedure(&mut self, export: Export) -> Result<(), SemanticAnalysisError> { if self.is_kernel() && matches!(export, Export::Alias(_)) { - return Err(SemanticAnalysisError::ReexportFromKernel { - span: export.span(), - }); + return Err(SemanticAnalysisError::ReexportFromKernel { span: export.span() }); } if let Some(prev) = self.resolve(export.name()) { let prev_span = prev.span(); - Err(SemanticAnalysisError::SymbolConflict { - span: export.span(), - prev_span, - }) + Err(SemanticAnalysisError::SymbolConflict { span: export.span(), prev_span }) } else { self.procedures.push(export); Ok(()) @@ -224,18 +210,12 @@ impl Module { pub fn define_import(&mut self, import: Import) -> Result<(), SemanticAnalysisError> { if let Some(prev_import) = self.resolve_import(&import.name) { let prev_span = prev_import.span; - return Err(SemanticAnalysisError::ImportConflict { - span: import.span, - prev_span, - }); + return Err(SemanticAnalysisError::ImportConflict { span: import.span, prev_span }); } if let Some(prev_defined) = self.procedures.iter().find(|e| e.name().eq(&import.name)) { let prev_span = prev_defined.span(); - return Err(SemanticAnalysisError::SymbolConflict { - span: import.span, - prev_span, - }); + return Err(SemanticAnalysisError::SymbolConflict { span: import.span, prev_span }); } self.imports.push(import); @@ -246,26 +226,6 @@ impl Module { /// Parsing impl Module { - /// Parse a [Module], `name`, of the given [ModuleKind], from `path`. - #[cfg(feature = "std")] - pub fn parse_file

(name: LibraryPath, kind: ModuleKind, path: P) -> Result, Report> - where - P: AsRef, - { - let mut parser = Self::parser(kind); - parser.parse_file(name, path) - } - - /// Parse a [Module], `name`, of the given [ModuleKind], from `source`. - pub fn parse_str( - name: LibraryPath, - kind: ModuleKind, - source: impl ToString, - ) -> Result, Report> { - let mut parser = Self::parser(kind); - parser.parse_str(name, source) - } - /// Parse a [Module], `name`, of the given [ModuleKind], from `source_file`. pub fn parse( name: LibraryPath, @@ -277,28 +237,13 @@ impl Module { } /// Get a [ModuleParser] for parsing modules of the provided [ModuleKind] - /// - /// This is mostly useful when you want tighter control over the parser configuration, otherwise - /// it is generally more convenient to use [Module::parse_file] or [Module::parse_str] for most - /// use cases. - pub fn parser(kind: ModuleKind) -> crate::parser::ModuleParser { - crate::parser::ModuleParser::new(kind) + pub fn parser(kind: ModuleKind) -> ModuleParser { + ModuleParser::new(kind) } } /// Metadata impl Module { - /// Get the source code for this module, if available - /// - /// The source code will not be available in the following situations: - /// - /// * The module was constructed in-memory via AST structures, and not derived from source code. - /// * The module was serialized without debug info, and then deserialized. Without debug info, - /// the source code is lost when round-tripping through serialization. - pub fn source_file(&self) -> Option> { - self.source_file.clone() - } - /// Get the name of this specific module, i.e. the last component of the [LibraryPath] that /// represents the fully-qualified name of the module, e.g. `u64` in `std::math::u64` pub fn name(&self) -> &str { @@ -364,6 +309,26 @@ impl Module { self.procedures.iter_mut() } + /// Returns procedures exported from this module. + /// + /// Each exported procedure is represented by its local procedure index and a fully qualified + /// name. + pub fn exported_procedures( + &self, + ) -> impl Iterator + '_ { + self.procedures.iter().enumerate().filter_map(|(proc_idx, p)| { + // skip un-exported procedures + if !p.visibility().is_exported() { + return None; + } + + let proc_idx = ProcedureIndex::new(proc_idx); + let fqn = QualifiedProcedureName::new(self.path().clone(), p.name().clone()); + + Some((proc_idx, fqn)) + }) + } + /// Get an iterator over the imports declared in this module. /// /// See [Import] for details on what information is available for imports. @@ -371,7 +336,7 @@ impl Module { self.imports.iter() } - /// Same as [imports], but returns mutable references to each import. + /// Same as [Self::imports], but returns mutable references to each import. pub fn imports_mut(&mut self) -> core::slice::IterMut<'_, Import> { self.imports.iter_mut() } @@ -379,8 +344,9 @@ impl Module { /// Get an iterator over the "dependencies" of a module, i.e. what library namespaces we expect /// to find imported procedures in. /// - /// For example, if we have imported `std::math::u64`, then we would expect to import that - /// module from a [crate::Library] with the namespace `std`. + /// For example, if we have imported `std::math::u64`, then we would expect to find a library + /// on disk named `std.masl`, although that isn't a strict requirement. This notion of + /// dependencies may go away with future packaging-related changed. pub fn dependencies(&self) -> impl Iterator { self.import_paths().map(|import| import.namespace()) } @@ -417,8 +383,13 @@ impl Module { match &self.procedures[index.as_usize()] { Export::Procedure(ref proc) => { Some(ResolvedProcedure::Local(Span::new(proc.name().span(), index))) - } - Export::Alias(ref alias) => Some(ResolvedProcedure::External(alias.target.clone())), + }, + Export::Alias(ref alias) => match alias.target() { + AliasTarget::MastRoot(digest) => Some(ResolvedProcedure::MastRoot(**digest)), + AliasTarget::ProcedurePath(path) | AliasTarget::AbsoluteProcedurePath(path) => { + Some(ResolvedProcedure::External(path.clone())) + }, + }, } } @@ -430,8 +401,14 @@ impl Module { ResolvedProcedure::Local(Span::new(p.name().span(), ProcedureIndex::new(i))), ), Export::Alias(ref p) => { - (p.name().clone(), ResolvedProcedure::External(p.target.clone())) - } + let target = match p.target() { + AliasTarget::MastRoot(digest) => ResolvedProcedure::MastRoot(**digest), + AliasTarget::ProcedurePath(path) | AliasTarget::AbsoluteProcedurePath(path) => { + ResolvedProcedure::External(path.clone()) + }, + }; + (p.name().clone(), target) + }, })) .with_imports( self.imports @@ -490,163 +467,6 @@ impl PartialEq for Module { } } -/// Serialization -impl Module { - /// Serialization this module to `target`, using `options`. - pub fn write_into_with_options(&self, target: &mut W, options: AstSerdeOptions) { - options.write_into(target); - if options.debug_info { - self.span.write_into(target); - if let Some(source_file) = self.source_file.as_ref() { - target.write_u8(1); - let source_name = source_file.name(); - let source_bytes = source_file.inner().as_bytes(); - target.write_usize(source_name.as_bytes().len()); - target.write_bytes(source_name.as_bytes()); - target.write_usize(source_bytes.len()); - target.write_bytes(source_bytes); - } else { - target.write_u8(0); - } - } - self.kind.write_into(target); - self.path.write_into(target); - if options.serialize_imports { - target.write_usize(self.imports.len()); - for import in self.imports.iter() { - import.write_into_with_options(target, options); - } - } - target.write_usize(self.procedures.len()); - for export in self.procedures.iter() { - export.write_into_with_options(target, options); - } - } - - /// Returns byte representation of this [Module]. - /// - /// The serde options are serialized as header information for the purposes of deserialization. - pub fn to_bytes(&self, options: AstSerdeOptions) -> Vec { - let mut target = Vec::::with_capacity(256); - self.write_into_with_options(&mut target, options); - target - } - - /// Returns a [Module] struct deserialized from the provided bytes. - /// - /// Assumes that the module was encoded using [Module::write_into] or - /// [Module::write_into_with_options] - pub fn from_bytes(bytes: &[u8]) -> Result { - let mut source = crate::SliceReader::new(bytes); - Self::read_from(&mut source) - } - - /// Writes this [Module] to the provided file path - #[cfg(feature = "std")] - pub fn write_to_file

(&self, path: P) -> std::io::Result<()> - where - P: AsRef, - { - let path = path.as_ref(); - if let Some(dir) = path.parent() { - std::fs::create_dir_all(dir)?; - } - - // 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(|| match std::fs::File::create(path) { - Ok(ref mut file) => { - let options = AstSerdeOptions { - serialize_imports: true, - debug_info: true, - }; - self.write_into_with_options(file, options); - Ok(()) - } - Err(err) => Err(err), - }) - .map_err(|p| { - 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), - } - })? - } -} - -impl Serializable for Module { - fn write_into(&self, target: &mut W) { - self.write_into_with_options(target, AstSerdeOptions::new(true, true)) - } -} - -impl Deserializable for Module { - /// Deserialize a [Module] from `source` - /// - /// Assumes that the module was encoded using [Serializable::write_into] or - /// [Module::write_into_with_options] - fn read_from(source: &mut R) -> Result { - let options = AstSerdeOptions::read_from(source)?; - let (span, source_file) = if options.debug_info { - let span = SourceSpan::read_from(source)?; - match source.read_u8()? { - 0 => (span, None), - 1 => { - let nlen = source.read_usize()?; - let source_name = core::str::from_utf8(source.read_slice(nlen)?) - .map(|s| s.to_string()) - .map_err(|e| DeserializationError::InvalidValue(e.to_string()))?; - let clen = source.read_usize()?; - let source_content = core::str::from_utf8(source.read_slice(clen)?) - .map_err(|e| DeserializationError::InvalidValue(e.to_string()))?; - let source_file = - Arc::new(SourceFile::new(source_name, source_content.to_string())); - (span, Some(source_file)) - } - n => { - return Err(DeserializationError::InvalidValue(format!( - "invalid option tag: '{n}'" - ))); - } - } - } else { - (SourceSpan::default(), None) - }; - let kind = ModuleKind::read_from(source)?; - let path = LibraryPath::read_from(source)?; - let imports = if options.serialize_imports { - let num_imports = source.read_usize()?; - let mut imports = Vec::with_capacity(num_imports); - for _ in 0..num_imports { - let import = Import::read_from_with_options(source, options)?; - imports.push(import); - } - imports - } else { - Vec::new() - }; - let num_procedures = source.read_usize()?; - let mut procedures = Vec::with_capacity(num_procedures); - for _ in 0..num_procedures { - let export = Export::read_from_with_options(source, options)?; - procedures.push(export.with_source_file(source_file.clone())); - } - Ok(Self { - span, - source_file, - docs: None, - path, - kind, - imports, - procedures, - }) - } -} - /// Debug representation of this module impl fmt::Debug for Module { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { diff --git a/assembly/src/ast/op.rs b/assembly/src/ast/op.rs index 82f3ba42b7..99b710d7cd 100644 --- a/assembly/src/ast/op.rs +++ b/assembly/src/ast/op.rs @@ -1,10 +1,7 @@ use core::fmt; use super::{Block, Instruction}; -use crate::{ - ast::AstSerdeOptions, ByteReader, ByteWriter, Deserializable, DeserializationError, - Serializable, SourceSpan, Span, Spanned, -}; +use crate::{SourceSpan, Span, Spanned}; /// Represents the Miden Assembly instruction set syntax /// @@ -38,94 +35,12 @@ pub enum Op { Inst(Span) = 3, } -/// Serialization -impl Op { - pub fn write_into_with_options(&self, target: &mut W, options: AstSerdeOptions) { - if options.debug_info { - self.span().write_into(target); - } - target.write_u8(self.tag()); - match self { - Self::If { - ref then_blk, - ref else_blk, - .. - } => { - then_blk.write_into_with_options(target, options); - else_blk.write_into_with_options(target, options); - } - Self::While { ref body, .. } => { - body.write_into_with_options(target, options); - } - Self::Repeat { - count, ref body, .. - } => { - target.write_u32(*count); - body.write_into_with_options(target, options); - } - Self::Inst(ref inst) => { - (**inst).write_into(target); - } - } - } - - pub fn read_from_with_options( - source: &mut R, - options: AstSerdeOptions, - ) -> Result { - let span = if options.debug_info { - SourceSpan::read_from(source)? - } else { - SourceSpan::default() - }; - match source.read_u8()? { - 0 => { - let then_blk = Block::read_from_with_options(source, options)?; - let else_blk = Block::read_from_with_options(source, options)?; - Ok(Self::If { - span, - then_blk, - else_blk, - }) - } - 1 => { - let body = Block::read_from_with_options(source, options)?; - Ok(Self::While { span, body }) - } - 2 => { - let count = source.read_u32()?; - let body = Block::read_from_with_options(source, options)?; - Ok(Self::Repeat { span, count, body }) - } - 3 => { - let inst = Instruction::read_from(source)?; - Ok(Self::Inst(Span::new(span, inst))) - } - n => Err(DeserializationError::InvalidValue(format!("{n} is not a valid op tag"))), - } - } - - fn tag(&self) -> u8 { - // SAFETY: This is safe because we have given this enum a - // primitive representation with #[repr(u8)], with the first - // field of the underlying union-of-structs the discriminant. - // - // See the section on "accessing the numeric value of the discriminant" - // here: https://doc.rust-lang.org/std/mem/fn.discriminant.html - unsafe { *<*const _>::from(self).cast::() } - } -} - impl crate::prettier::PrettyPrint for Op { fn render(&self) -> crate::prettier::Document { use crate::prettier::*; match self { - Self::If { - ref then_blk, - ref else_blk, - .. - } => { + Self::If { ref then_blk, ref else_blk, .. } => { text("if.true") + nl() + then_blk.render() @@ -135,13 +50,13 @@ impl crate::prettier::PrettyPrint for Op { + else_blk.render() + nl() + text("end") - } + }, Self::While { ref body, .. } => { text("while.true") + nl() + body.render() + nl() + text("end") - } - Self::Repeat { - count, ref body, .. - } => display(format!("repeat.{count}")) + nl() + body.render() + nl() + text("end"), + }, + Self::Repeat { count, ref body, .. } => { + display(format!("repeat.{count}")) + nl() + body.render() + nl() + text("end") + }, Self::Inst(ref inst) => inst.render(), } } @@ -150,17 +65,13 @@ impl crate::prettier::PrettyPrint for Op { impl fmt::Debug for Op { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - Self::If { - ref then_blk, - ref else_blk, - .. - } => f.debug_struct("If").field("then", then_blk).field("else", else_blk).finish(), + Self::If { ref then_blk, ref else_blk, .. } => { + f.debug_struct("If").field("then", then_blk).field("else", else_blk).finish() + }, Self::While { ref body, .. } => f.debug_tuple("While").field(body).finish(), - Self::Repeat { - ref count, - ref body, - .. - } => f.debug_struct("Repeat").field("count", count).field("body", body).finish(), + Self::Repeat { ref count, ref body, .. } => { + f.debug_struct("Repeat").field("count", count).field("body", body).finish() + }, Self::Inst(ref inst) => fmt::Debug::fmt(&**inst, f), } } @@ -172,29 +83,13 @@ impl PartialEq for Op { fn eq(&self, other: &Self) -> bool { match (self, other) { ( - Self::If { - then_blk: lt, - else_blk: le, - .. - }, - Self::If { - then_blk: rt, - else_blk: re, - .. - }, + Self::If { then_blk: lt, else_blk: le, .. }, + Self::If { then_blk: rt, else_blk: re, .. }, ) => lt == rt && le == re, (Self::While { body: lbody, .. }, Self::While { body: rbody, .. }) => lbody == rbody, ( - Self::Repeat { - count: lcount, - body: lbody, - .. - }, - Self::Repeat { - count: rcount, - body: rbody, - .. - }, + Self::Repeat { count: lcount, body: lbody, .. }, + Self::Repeat { count: rcount, body: rbody, .. }, ) => lcount == rcount && lbody == rbody, (Self::Inst(l), Self::Inst(r)) => l == r, _ => false, diff --git a/assembly/src/ast/procedure/alias.rs b/assembly/src/ast/procedure/alias.rs index d462e64346..1e6427b4de 100644 --- a/assembly/src/ast/procedure/alias.rs +++ b/assembly/src/ast/procedure/alias.rs @@ -1,9 +1,11 @@ -use alloc::{string::String, sync::Arc}; +use alloc::string::String; +use core::fmt; -use super::{FullyQualifiedProcedureName, ProcedureName}; +use super::{ProcedureName, QualifiedProcedureName}; use crate::{ - ast::AstSerdeOptions, diagnostics::SourceFile, ByteReader, ByteWriter, DeserializationError, - SourceSpan, Span, Spanned, + ast::InvocationTarget, + diagnostics::{SourceSpan, Span, Spanned}, + RpoDigest, }; // PROCEDURE ALIAS @@ -17,28 +19,22 @@ use crate::{ /// the caller is in the current module, or in another module. #[derive(Debug, Clone, PartialEq, Eq)] pub struct ProcedureAlias { - /// The source file in which this alias was defined, if available - source_file: Option>, /// The documentation attached to this procedure docs: Option>, - /// The name of the re-exported procedure. + /// The name of this procedure name: ProcedureName, - /// The fully-qualified name of the imported procedure + /// The underlying procedure being aliased. /// - /// NOTE: This is fully-qualified from the perspective of the containing [Module], but may not - /// be fully-resolved to the concrete definition until compilation time. - pub(crate) target: FullyQualifiedProcedureName, + /// Alias targets are context-sensitive, depending on how they were defined and what stage of + /// compilation we're in. See [AliasTarget] for semantics of each target type, but they closely + /// correspond to [InvocationTarget]. + target: AliasTarget, } impl ProcedureAlias { /// Creates a new procedure alias called `name`, which resolves to `target`. - pub fn new(name: ProcedureName, target: FullyQualifiedProcedureName) -> Self { - Self { - docs: None, - source_file: None, - name, - target, - } + pub fn new(name: ProcedureName, target: AliasTarget) -> Self { + Self { docs: None, name, target } } /// Adds documentation to this procedure alias. @@ -47,17 +43,6 @@ impl ProcedureAlias { self } - /// Adds source code to this declaration, so that we can render source snippets in diagnostics. - pub fn with_source_file(mut self, source_file: Option>) -> Self { - self.source_file = source_file; - self - } - - /// Returns the source file associated with this declaration. - pub fn source_file(&self) -> Option> { - self.source_file.clone() - } - /// Returns the documentation associated with this declaration. pub fn docs(&self) -> Option<&Span> { self.docs.as_ref() @@ -67,35 +52,38 @@ impl ProcedureAlias { /// /// If the procedure is simply re-exported with the same name, this will be equivalent to /// `self.target().name` + #[inline] pub fn name(&self) -> &ProcedureName { &self.name } - /// Returns the fully-qualified procedure name of the aliased procedure. - pub fn target(&self) -> &FullyQualifiedProcedureName { + /// Returns the target of this procedure alias + #[inline] + pub fn target(&self) -> &AliasTarget { &self.target } -} -/// Serialization -impl ProcedureAlias { - pub fn write_into_with_options(&self, target: &mut W, options: AstSerdeOptions) { - self.name.write_into_with_options(target, options); - self.target.write_into_with_options(target, options); - } - - pub fn read_from_with_options( - source: &mut R, - options: AstSerdeOptions, - ) -> Result { - let name = ProcedureName::read_from_with_options(source, options)?; - let target = FullyQualifiedProcedureName::read_from_with_options(source, options)?; - Ok(Self { - source_file: None, - docs: None, - name, - target, - }) + /// Returns a mutable reference to the target of this procedure alias + #[inline] + pub fn target_mut(&mut self) -> &mut AliasTarget { + &mut self.target + } + + /// Returns true if this procedure uses an absolute target path + #[inline] + pub fn is_absolute(&self) -> bool { + matches!(self.target, AliasTarget::MastRoot(_) | AliasTarget::AbsoluteProcedurePath(_)) + } + + /// Returns true if this alias uses a different name than the target procedure + #[inline] + pub fn is_renamed(&self) -> bool { + match self.target() { + AliasTarget::MastRoot(_) => true, + AliasTarget::ProcedurePath(fqn) | AliasTarget::AbsoluteProcedurePath(fqn) => { + fqn.name != self.name + }, + } } } @@ -118,16 +106,124 @@ impl crate::prettier::PrettyPrint for ProcedureAlias { .unwrap_or_default(); } - if self.target.name == self.name { - doc += display(format_args!("export.{}::{}", self.target.module.last(), &self.name)); - } else { - doc += display(format_args!( - "export.{}::{}->{}", - self.target.module.last(), - &self.target.name, - &self.name - )); - } + doc += const_text("export."); + doc += match &self.target { + target @ AliasTarget::MastRoot(_) => display(format_args!("{}->{}", target, self.name)), + target => { + let prefix = if self.is_absolute() { "::" } else { "" }; + if self.is_renamed() { + display(format_args!("{}{}->{}", prefix, target, &self.name)) + } else { + display(format_args!("{}{}", prefix, target)) + } + }, + }; doc } } + +/// A fully-qualified external procedure that is the target of a procedure alias +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum AliasTarget { + /// An alias of the procedure whose root is the given digest + /// + /// Corresponds to [`InvocationTarget::MastRoot`] + MastRoot(Span), + /// An alias of `name`, imported from `module` + /// + /// Corresponds to [`InvocationTarget::ProcedurePath`] + ProcedurePath(QualifiedProcedureName), + /// An alias of a procedure with the given absolute, fully-qualified path + /// + /// Corresponds to [InvocationTarget::AbsoluteProcedurePath] + AbsoluteProcedurePath(QualifiedProcedureName), +} + +impl Spanned for AliasTarget { + fn span(&self) -> SourceSpan { + match self { + Self::MastRoot(spanned) => spanned.span(), + Self::ProcedurePath(spanned) | Self::AbsoluteProcedurePath(spanned) => spanned.span(), + } + } +} + +impl From> for AliasTarget { + fn from(digest: Span) -> Self { + Self::MastRoot(digest) + } +} + +impl TryFrom for AliasTarget { + type Error = InvocationTarget; + + fn try_from(target: InvocationTarget) -> Result { + let span = target.span(); + match target { + InvocationTarget::MastRoot(digest) => Ok(Self::MastRoot(digest)), + InvocationTarget::ProcedurePath { name, module } => { + let ns = crate::LibraryNamespace::from_ident_unchecked(module); + let module = crate::LibraryPath::new_from_components(ns, []); + Ok(Self::ProcedurePath(QualifiedProcedureName { span, module, name })) + }, + InvocationTarget::AbsoluteProcedurePath { name, path: module } => { + Ok(Self::AbsoluteProcedurePath(QualifiedProcedureName { span, module, name })) + }, + target @ InvocationTarget::ProcedureName(_) => Err(target), + } + } +} + +impl From<&AliasTarget> for InvocationTarget { + fn from(target: &AliasTarget) -> Self { + match target { + AliasTarget::MastRoot(digest) => Self::MastRoot(*digest), + AliasTarget::ProcedurePath(ref fqn) => { + let name = fqn.name.clone(); + let module = fqn.module.last_component().to_ident(); + Self::ProcedurePath { name, module } + }, + AliasTarget::AbsoluteProcedurePath(ref fqn) => Self::AbsoluteProcedurePath { + name: fqn.name.clone(), + path: fqn.module.clone(), + }, + } + } +} +impl From for InvocationTarget { + fn from(target: AliasTarget) -> Self { + match target { + AliasTarget::MastRoot(digest) => Self::MastRoot(digest), + AliasTarget::ProcedurePath(fqn) => { + let name = fqn.name; + let module = fqn.module.last_component().to_ident(); + Self::ProcedurePath { name, module } + }, + AliasTarget::AbsoluteProcedurePath(fqn) => { + Self::AbsoluteProcedurePath { name: fqn.name, path: fqn.module } + }, + } + } +} + +impl crate::prettier::PrettyPrint for AliasTarget { + fn render(&self) -> crate::prettier::Document { + use vm_core::utils::DisplayHex; + + use crate::prettier::*; + + match self { + Self::MastRoot(digest) => display(DisplayHex(digest.as_bytes().as_slice())), + Self::ProcedurePath(fqn) => display(fqn), + Self::AbsoluteProcedurePath(fqn) => display(format_args!("::{}", fqn)), + } + } +} + +impl fmt::Display for AliasTarget { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use crate::prettier::PrettyPrint; + + self.pretty_print(f) + } +} diff --git a/assembly/src/ast/procedure/mod.rs b/assembly/src/ast/procedure/mod.rs index f0175fcaec..8878018cf4 100644 --- a/assembly/src/ast/procedure/mod.rs +++ b/assembly/src/ast/procedure/mod.rs @@ -5,18 +5,19 @@ mod name; mod procedure; mod resolver; -pub use self::alias::ProcedureAlias; -pub use self::id::ProcedureIndex; -pub use self::name::{FullyQualifiedProcedureName, ProcedureName}; -pub use self::procedure::{Procedure, Visibility}; -pub use self::resolver::{LocalNameResolver, ResolvedProcedure}; - +use alloc::string::String; + +pub use self::{ + alias::{AliasTarget, ProcedureAlias}, + id::ProcedureIndex, + name::{ProcedureName, QualifiedProcedureName}, + procedure::{Procedure, Visibility}, + resolver::{LocalNameResolver, ResolvedProcedure}, +}; use crate::{ - ast::{AstSerdeOptions, Invoke}, - diagnostics::SourceFile, - ByteReader, ByteWriter, DeserializationError, SourceSpan, Span, Spanned, + ast::{AttributeSet, Invoke}, + SourceSpan, Span, Spanned, }; -use alloc::{string::String, sync::Arc}; // EXPORT // ================================================================================================ @@ -26,12 +27,11 @@ use alloc::{string::String, sync::Arc}; /// Currently only procedures (either locally-defined or re-exported) are exportable, but in the /// future this may be expanded. #[derive(Debug, Clone, PartialEq, Eq)] -#[repr(u8)] pub enum Export { /// A locally-defined procedure. - Procedure(Procedure) = 0, + Procedure(Procedure), /// An alias for an externally-defined procedure, i.e. a re-exported import. - Alias(ProcedureAlias) = 1, + Alias(ProcedureAlias), } impl Export { @@ -43,23 +43,6 @@ impl Export { } } - /// Adds the source file in which this export was defined, which will allow diagnostics to - /// contain source snippets when emitted. - pub fn with_source_file(self, source_file: Option>) -> Self { - match self { - Self::Procedure(proc) => Self::Procedure(proc.with_source_file(source_file)), - Self::Alias(alias) => Self::Alias(alias.with_source_file(source_file)), - } - } - - /// Returns the source file in which this export was defined. - pub fn source_file(&self) -> Option> { - match self { - Self::Procedure(ref proc) => proc.source_file(), - Self::Alias(ref alias) => alias.source_file(), - } - } - /// Returns the name of the exported procedure. pub fn name(&self) -> &ProcedureName { match self { @@ -76,6 +59,14 @@ impl Export { } } + /// Returns the attributes for this procedure. + pub fn attributes(&self) -> Option<&AttributeSet> { + match self { + Self::Procedure(ref proc) => Some(proc.attributes()), + Self::Alias(_) => None, + } + } + /// Returns the visibility of this procedure (e.g. public or private). /// /// See [Visibility] for more details on what visibilities are supported. @@ -122,39 +113,6 @@ impl Export { } } -/// Serialization -impl Export { - pub fn write_into_with_options(&self, target: &mut W, options: AstSerdeOptions) { - target.write_u8(self.tag()); - match self { - Self::Procedure(ref proc) => proc.write_into_with_options(target, options), - Self::Alias(ref proc) => proc.write_into_with_options(target, options), - } - } - - pub fn read_from_with_options( - source: &mut R, - options: AstSerdeOptions, - ) -> Result { - match source.read_u8()? { - 0 => Procedure::read_from_with_options(source, options).map(Self::Procedure), - 1 => ProcedureAlias::read_from_with_options(source, options).map(Self::Alias), - n => { - Err(DeserializationError::InvalidValue(format!("invalid procedure kind tag: {n}"))) - } - } - } - - fn tag(&self) -> u8 { - // SAFETY: This is safe because we have given this enum a primitive representation with - // #[repr(u8)], with the first field of the underlying union-of-structs the discriminant. - // - // See the section on "accessing the numeric value of the discriminant" - // here: https://doc.rust-lang.org/std/mem/fn.discriminant.html - unsafe { *<*const _>::from(self).cast::() } - } -} - impl crate::prettier::PrettyPrint for Export { fn render(&self) -> crate::prettier::Document { match self { diff --git a/assembly/src/ast/procedure/name.rs b/assembly/src/ast/procedure/name.rs index 405aee3f67..f7979bd429 100644 --- a/assembly/src/ast/procedure/name.rs +++ b/assembly/src/ast/procedure/name.rs @@ -6,19 +6,21 @@ use core::{ }; use crate::{ - ast::{AstSerdeOptions, CaseKindError, Ident, IdentError}, + ast::{CaseKindError, Ident, IdentError}, diagnostics::{IntoDiagnostic, Report}, - ByteReader, ByteWriter, Deserializable, DeserializationError, LibraryPath, Serializable, - SourceSpan, Span, Spanned, + LibraryNamespace, LibraryPath, SourceSpan, Span, Spanned, }; -// FULLY QUALIFIED PROCEDURE NAME +// QUALIFIED PROCEDURE NAME // ================================================================================================ -/// Represents a fully-qualified procedure name, e.g. `std::math::u64::add`, parsed into it's +/// Represents a qualified procedure name, e.g. `std::math::u64::add`, parsed into it's /// constituent [LibraryPath] and [ProcedureName] components. +/// +/// A qualified procedure name can be context-sensitive, i.e. the module path might refer +/// to an imported #[derive(Clone)] -pub struct FullyQualifiedProcedureName { +pub struct QualifiedProcedureName { /// The source span associated with this identifier. pub span: SourceSpan, /// The module path for this procedure. @@ -27,8 +29,8 @@ pub struct FullyQualifiedProcedureName { pub name: ProcedureName, } -impl FullyQualifiedProcedureName { - /// Create a new [FullyQualifiedProcedureName] with the given fully-qualified module path +impl QualifiedProcedureName { + /// Create a new [QualifiedProcedureName] with the given fully-qualified module path /// and procedure name. pub fn new(module: LibraryPath, name: ProcedureName) -> Self { Self { @@ -37,9 +39,14 @@ impl FullyQualifiedProcedureName { name, } } + + /// Returns the namespace of this fully-qualified procedure name. + pub fn namespace(&self) -> &LibraryNamespace { + self.module.namespace() + } } -impl FromStr for FullyQualifiedProcedureName { +impl FromStr for QualifiedProcedureName { type Err = Report; fn from_str(s: &str) -> Result { @@ -49,44 +56,44 @@ impl FromStr for FullyQualifiedProcedureName { let name = name.parse::().into_diagnostic()?; let path = path.parse::().into_diagnostic()?; Ok(Self::new(path, name)) - } + }, } } } -impl Eq for FullyQualifiedProcedureName {} +impl Eq for QualifiedProcedureName {} -impl PartialEq for FullyQualifiedProcedureName { +impl PartialEq for QualifiedProcedureName { fn eq(&self, other: &Self) -> bool { self.name == other.name && self.module == other.module } } -impl Ord for FullyQualifiedProcedureName { +impl Ord for QualifiedProcedureName { fn cmp(&self, other: &Self) -> core::cmp::Ordering { self.module.cmp(&other.module).then_with(|| self.name.cmp(&other.name)) } } -impl PartialOrd for FullyQualifiedProcedureName { +impl PartialOrd for QualifiedProcedureName { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } -impl From for miette::SourceSpan { - fn from(fqn: FullyQualifiedProcedureName) -> Self { +impl From for miette::SourceSpan { + fn from(fqn: QualifiedProcedureName) -> Self { fqn.span.into() } } -impl Spanned for FullyQualifiedProcedureName { +impl Spanned for QualifiedProcedureName { fn span(&self) -> SourceSpan { self.span } } -impl fmt::Debug for FullyQualifiedProcedureName { +impl fmt::Debug for QualifiedProcedureName { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("FullyQualifiedProcedureName") .field("module", &self.module) @@ -95,36 +102,17 @@ impl fmt::Debug for FullyQualifiedProcedureName { } } -impl fmt::Display for FullyQualifiedProcedureName { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}::{}", &self.module, &self.name) - } -} +impl crate::prettier::PrettyPrint for QualifiedProcedureName { + fn render(&self) -> vm_core::prettier::Document { + use crate::prettier::*; -/// Serialization -impl FullyQualifiedProcedureName { - /// Serialize to `target` using `options` - pub fn write_into_with_options(&self, target: &mut W, options: AstSerdeOptions) { - if options.debug_info { - self.span.write_into(target); - } - self.module.write_into(target); - self.name.write_into_with_options(target, options); + display(self) } +} - /// Deserialize from `source` using `options` - pub fn read_from_with_options( - source: &mut R, - options: AstSerdeOptions, - ) -> Result { - let span = if options.debug_info { - SourceSpan::read_from(source)? - } else { - SourceSpan::default() - }; - let module = LibraryPath::read_from(source)?; - let name = ProcedureName::read_from_with_options(source, options)?; - Ok(Self { span, module, name }) +impl fmt::Display for QualifiedProcedureName { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}::{}", &self.module, &self.name) } } @@ -211,6 +199,11 @@ impl ProcedureName { pub fn is_main(&self) -> bool { self.0.as_str() == Self::MAIN_PROC_NAME } + + /// Returns a string reference for this procedure name. + pub fn as_str(&self) -> &str { + self.as_ref() + } } impl Eq for ProcedureName {} @@ -309,9 +302,9 @@ impl FromStr for ProcedureName { } let tok = &s[1..pos]; break Ok(Arc::from(tok.to_string().into_boxed_str())); - } + }, c if c.is_alphanumeric() => continue, - '_' | '$' | '-' | '!' | '?' => continue, + '_' | '$' | '-' | '!' | '?' | '<' | '>' | ':' | '.' => continue, _ => break Err(IdentError::InvalidChars), } } else { @@ -328,55 +321,10 @@ impl FromStr for ProcedureName { } else { Ok(Arc::from(s.to_string().into_boxed_str())) } - } + }, Some((_, c)) if c.is_ascii_uppercase() => Err(IdentError::Casing(CaseKindError::Snake)), Some(_) => Err(IdentError::InvalidChars), }?; Ok(Self(Ident::new_unchecked(Span::unknown(raw)))) } } - -/// Serialization -impl ProcedureName { - pub fn write_into_with_options( - &self, - target: &mut W, - options: crate::ast::AstSerdeOptions, - ) { - if options.debug_info { - self.span().write_into(target); - } - target.write_usize(self.0.as_bytes().len()); - target.write_bytes(self.0.as_bytes()); - } - - pub fn read_from_with_options( - source: &mut R, - options: crate::ast::AstSerdeOptions, - ) -> Result { - let span = if options.debug_info { - SourceSpan::read_from(source)? - } else { - SourceSpan::default() - }; - let nlen = source.read_usize()?; - let name = source.read_slice(nlen)?; - let name = core::str::from_utf8(name) - .map_err(|e| DeserializationError::InvalidValue(e.to_string()))?; - name.parse::() - .map_err(|e| DeserializationError::InvalidValue(e.to_string())) - .map(|id| id.with_span(span)) - } -} - -impl Serializable for ProcedureName { - fn write_into(&self, target: &mut W) { - self.write_into_with_options(target, Default::default()) - } -} - -impl Deserializable for ProcedureName { - fn read_from(source: &mut R) -> Result { - Self::read_from_with_options(source, Default::default()) - } -} diff --git a/assembly/src/ast/procedure/procedure.rs b/assembly/src/ast/procedure/procedure.rs index fe31506977..70a0ac6a9f 100644 --- a/assembly/src/ast/procedure/procedure.rs +++ b/assembly/src/ast/procedure/procedure.rs @@ -1,12 +1,10 @@ -use alloc::{collections::BTreeSet, string::String, sync::Arc}; +use alloc::{collections::BTreeSet, string::String}; use core::fmt; use super::ProcedureName; use crate::{ - ast::{AstSerdeOptions, Block, Invoke}, - diagnostics::SourceFile, - ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable, SourceSpan, Span, - Spanned, + ast::{Attribute, AttributeSet, Block, Invoke}, + SourceSpan, Span, Spanned, }; // PROCEDURE VISIBILITY @@ -47,23 +45,6 @@ impl Visibility { } } -impl Serializable for Visibility { - fn write_into(&self, target: &mut W) { - target.write_u8(*self as u8) - } -} - -impl Deserializable for Visibility { - fn read_from(source: &mut R) -> Result { - match source.read_u8()? { - 0 => Ok(Self::Public), - 1 => Ok(Self::Syscall), - 2 => Ok(Self::Private), - n => Err(DeserializationError::InvalidValue(format!("invalid visibility tag: {n}"))), - } - } -} - // PROCEDURE // ================================================================================================ @@ -72,10 +53,10 @@ impl Deserializable for Visibility { pub struct Procedure { /// The source span of the full procedure body span: SourceSpan, - /// The source file in which this procedure was defined, if available - source_file: Option>, /// The documentation attached to this procedure docs: Option>, + /// The attributes attached to this procedure + attrs: AttributeSet, /// The local name of this procedure name: ProcedureName, /// The visibility of this procedure (i.e. whether it is exported or not) @@ -101,8 +82,8 @@ impl Procedure { ) -> Self { Self { span, - source_file: None, docs: None, + attrs: Default::default(), name, visibility, num_locals, @@ -117,18 +98,15 @@ impl Procedure { self } - /// Adds source code to this procedure definition so we can render source snippets - /// in diagnostics. - pub fn with_source_file(mut self, source_file: Option>) -> Self { - self.source_file = source_file; + /// Adds attributes to this procedure definition + pub fn with_attributes(mut self, attrs: I) -> Self + where + I: IntoIterator, + { + self.attrs.extend(attrs); self } - /// Like [Procedure::with_source_file], but does not require ownership of the procedure. - pub fn set_source_file(&mut self, source_file: Arc) { - self.source_file = Some(source_file); - } - /// Modifies the visibility of this procedure. /// /// This is made crate-local as the visibility of a procedure is virtually always determined @@ -142,11 +120,6 @@ impl Procedure { /// Metadata impl Procedure { - /// Returns the source file associated with this procedure. - pub fn source_file(&self) -> Option> { - self.source_file.clone() - } - /// Returns the name of this procedure within its containing module. pub fn name(&self) -> &ProcedureName { &self.name @@ -173,6 +146,30 @@ impl Procedure { self.docs.as_ref() } + /// Get the attributes attached to this procedure + #[inline] + pub fn attributes(&self) -> &AttributeSet { + &self.attrs + } + + /// Get the attributes attached to this procedure, mutably + #[inline] + pub fn attributes_mut(&mut self) -> &mut AttributeSet { + &mut self.attrs + } + + /// Returns true if this procedure has an attribute named `name` + #[inline] + pub fn has_attribute(&self, name: impl AsRef) -> bool { + self.attrs.has(name) + } + + /// Returns the attribute named `name`, if present + #[inline] + pub fn get_attribute(&self, name: impl AsRef) -> Option<&Attribute> { + self.attrs.get(name) + } + /// Returns a reference to the [Block] containing the body of this procedure. pub fn body(&self) -> &Block { &self.body @@ -231,50 +228,11 @@ where *self = Self::Empty; } result - } + }, } } } -/// Serialization -impl Procedure { - pub fn write_into_with_options(&self, target: &mut W, options: AstSerdeOptions) { - if options.debug_info { - self.span.write_into(target); - } - self.name.write_into_with_options(target, options); - self.visibility.write_into(target); - target.write_u16(self.num_locals); - self.body.write_into_with_options(target, options); - } - - pub fn read_from_with_options( - source: &mut R, - options: AstSerdeOptions, - ) -> Result { - let span = if options.debug_info { - SourceSpan::read_from(source)? - } else { - SourceSpan::default() - }; - - let name = ProcedureName::read_from_with_options(source, options)?; - let visibility = Visibility::read_from(source)?; - let num_locals = source.read_u16()?; - let body = Block::read_from_with_options(source, options)?; - Ok(Self { - span, - source_file: None, - docs: None, - name, - visibility, - num_locals, - invoked: Default::default(), - body, - }) - } -} - impl Spanned for Procedure { fn span(&self) -> SourceSpan { self.span @@ -294,6 +252,15 @@ impl crate::prettier::PrettyPrint for Procedure { .unwrap_or(Document::Empty); } + if !self.attrs.is_empty() { + doc = self + .attrs + .iter() + .map(|attr| attr.render()) + .reduce(|acc, attr| acc + nl() + attr) + .unwrap_or(Document::Empty); + } + doc += display(self.visibility) + const_text(".") + display(&self.name); if self.num_locals > 0 { doc += const_text(".") + display(self.num_locals); @@ -309,6 +276,7 @@ impl fmt::Debug for Procedure { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Procedure") .field("docs", &self.docs) + .field("attrs", &self.attrs) .field("name", &self.name) .field("visibility", &self.visibility) .field("num_locals", &self.num_locals) @@ -326,6 +294,7 @@ impl PartialEq for Procedure { && self.visibility == other.visibility && self.num_locals == other.num_locals && self.body == other.body + && self.attrs == other.attrs && self.docs == other.docs } } diff --git a/assembly/src/ast/procedure/resolver.rs b/assembly/src/ast/procedure/resolver.rs index 828db60085..1a75df0818 100644 --- a/assembly/src/ast/procedure/resolver.rs +++ b/assembly/src/ast/procedure/resolver.rs @@ -1,7 +1,8 @@ -use super::{FullyQualifiedProcedureName, ProcedureIndex, ProcedureName}; -use crate::{ast::Ident, LibraryPath, SourceSpan, Span, Spanned}; use alloc::{collections::BTreeMap, vec::Vec}; +use super::{ProcedureIndex, ProcedureName, QualifiedProcedureName}; +use crate::{ast::Ident, LibraryPath, RpoDigest, SourceSpan, Span, Spanned}; + // RESOLVED PROCEDURE // ================================================================================================ @@ -11,14 +12,17 @@ pub enum ResolvedProcedure { /// The name was resolved to a procedure definition in the same module at the given index Local(Span), /// The name was resolved to a procedure exported from another module - External(FullyQualifiedProcedureName), + External(QualifiedProcedureName), + /// The name was resolved to a procedure with a known MAST root + MastRoot(RpoDigest), } impl Spanned for ResolvedProcedure { fn span(&self) -> SourceSpan { match self { - Self::Local(ref spanned) => spanned.span(), - Self::External(ref spanned) => spanned.span(), + ResolvedProcedure::Local(p) => p.span(), + ResolvedProcedure::External(p) => p.span(), + ResolvedProcedure::MastRoot(_) => SourceSpan::default(), } } } diff --git a/assembly/src/ast/serde.rs b/assembly/src/ast/serde.rs deleted file mode 100644 index 8802fbb118..0000000000 --- a/assembly/src/ast/serde.rs +++ /dev/null @@ -1,44 +0,0 @@ -//! Serialization and deserialization of Abstract syntax trees (ASTs). -//! -//! Structs in this module are used to serialize and deserialize ASTs into a binary format. - -use crate::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; - -/// Serialization options -/// Used to enable or disable serialization of parts of the AST. Serialization options are -/// serialized along with the AST to make the serialization format self-contained. -#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] -pub struct AstSerdeOptions { - pub serialize_imports: bool, - /// Include source spans and file paths in the output - pub debug_info: bool, -} - -impl AstSerdeOptions { - pub const fn new(serialize_imports: bool, debug_info: bool) -> Self { - Self { - serialize_imports, - debug_info, - } - } - - pub fn with_debug_info(mut self, yes: bool) -> Self { - self.debug_info = yes; - self - } -} - -impl Serializable for AstSerdeOptions { - fn write_into(&self, target: &mut W) { - target.write_bool(self.serialize_imports); - target.write_bool(self.debug_info); - } -} - -impl Deserializable for AstSerdeOptions { - fn read_from(source: &mut R) -> Result { - let serialize_imports = source.read_bool()?; - let debug_info = source.read_bool()?; - Ok(Self::new(serialize_imports, debug_info)) - } -} diff --git a/assembly/src/ast/tests.rs b/assembly/src/ast/tests.rs index 7a6e987a36..7fff35c587 100644 --- a/assembly/src/ast/tests.rs +++ b/assembly/src/ast/tests.rs @@ -1,5 +1,7 @@ use alloc::{string::ToString, sync::Arc, vec::Vec}; +use pretty_assertions::assert_eq; + use crate::{ assert_diagnostic, assert_diagnostic_lines, ast::*, @@ -9,10 +11,14 @@ use crate::{ Felt, Span, }; -use pretty_assertions::assert_eq; +macro_rules! id { + ($name:ident) => { + Ident::new(stringify!($name)).unwrap() + }; +} macro_rules! inst { - ($inst:ident ($value:expr)) => { + ($inst:ident($value:expr)) => { Op::Inst(Span::unknown(Instruction::$inst($value))) }; @@ -35,10 +41,7 @@ macro_rules! exec { Ident::new_unchecked(Span::unknown(Arc::from(name.to_string().into_boxed_str()))); let name = ProcedureName::new_unchecked(name); - inst!(Exec(InvocationTarget::ProcedurePath { - name, - module: module.parse().unwrap(), - })) + inst!(Exec(InvocationTarget::ProcedurePath { name, module: module.parse().unwrap() })) }}; } @@ -109,10 +112,7 @@ macro_rules! if_true { macro_rules! while_true { ($body:expr) => { - Op::While { - span: Default::default(), - body: $body, - } + Op::While { span: Default::default(), body: $body } }; } @@ -151,7 +151,20 @@ macro_rules! proc { ))) }; - ($docs:expr, $name:ident, $num_locals:literal, $body:expr) => { + ([$($attr:expr),*], $name:ident, $num_locals:literal, $body:expr) => { + Form::Procedure(Export::Procedure( + Procedure::new( + Default::default(), + Visibility::Private, + stringify!($name).parse().expect("invalid procedure name"), + $num_locals, + $body, + ) + .with_attributes([$($attr),*]), + )) + }; + + ($docs:literal, $name:ident, $num_locals:literal, $body:expr) => { Form::Procedure(Export::Procedure( Procedure::new( Default::default(), @@ -163,6 +176,20 @@ macro_rules! proc { .with_docs(Some(Span::unknown($docs.to_string()))), )) }; + + ($docs:literal, [$($attr:expr),*], $name:ident, $num_locals:literal, $body:expr) => { + Form::Procedure(Export::Procedure( + Procedure::new( + Default::default(), + Visibility::Private, + stringify!($name).parse().expect("invalid procedure name"), + $num_locals, + $body, + ) + .with_docs($docs) + .with_attributes([$($attr),*]), + )) + }; } macro_rules! export { @@ -210,7 +237,7 @@ macro_rules! assert_forms { {}", crate::diagnostics::reporting::PrintDiagnostic::new_without_color(report) ); - } + }, } }; } @@ -288,9 +315,9 @@ macro_rules! assert_program_diagnostic_lines { /// Tests the AST parsing #[test] fn test_ast_parsing_program_simple() -> Result<(), Report> { - let mut context = TestContext::new(); + let context = TestContext::new(); - let source = source_file!("begin push.0 assertz add.1 end"); + let source = source_file!(&context, "begin push.0 assertz add.1 end"); let forms = module!(begin!(inst!(PushU8(0)), inst!(Assertz), inst!(Incr))); assert_eq!(context.parse_forms(source)?, forms); @@ -300,9 +327,10 @@ fn test_ast_parsing_program_simple() -> Result<(), Report> { #[test] fn test_ast_parsing_program_push() -> Result<(), Report> { - let mut context = TestContext::new(); + let context = TestContext::new(); let source = source_file!( + &context, r#" begin push.10 push.500 push.70000 push.5000000000 @@ -338,11 +366,11 @@ fn test_ast_parsing_program_push() -> Result<(), Report> { assert_eq!(context.parse_forms(source)?, forms); // Push a hexadecimal string containing more than 4 values - let source_too_long = source_file!("begin push.0x00000000000000001000000000000000200000000000000030000000000000004000000000000000"); + let source_too_long = source_file!(&context, "begin push.0x00000000000000001000000000000000200000000000000030000000000000004000000000000000"); assert_parse_diagnostic!(source_too_long, "long hex strings must contain exactly 64 digits"); // Push a hexadecimal string containing less than 4 values - let source_too_long = source_file!("begin push.0x00000000000000001000000000000000"); + let source_too_long = source_file!(&context, "begin push.0x00000000000000001000000000000000"); assert_parse_diagnostic!(source_too_long, "expected 2, 4, 8, 16, or 64 hex digits"); Ok(()) @@ -350,9 +378,10 @@ fn test_ast_parsing_program_push() -> Result<(), Report> { #[test] fn test_ast_parsing_program_u32() -> Result<(), Report> { - let mut context = TestContext::new(); + let context = TestContext::new(); let source = source_file!( + &context, r#" begin push.3 @@ -385,9 +414,10 @@ fn test_ast_parsing_program_u32() -> Result<(), Report> { #[test] fn test_ast_parsing_program_proc() -> Result<(), Report> { - let mut context = TestContext::new(); + let context = TestContext::new(); let source = source_file!( + &context, r#" proc.foo.1 loc_load.0 @@ -413,8 +443,9 @@ fn test_ast_parsing_program_proc() -> Result<(), Report> { #[test] fn test_ast_parsing_module() -> Result<(), Report> { - let mut context = TestContext::new(); + let context = TestContext::new(); let source = source_file!( + &context, r#" export.foo.1 loc_load.0 @@ -427,8 +458,8 @@ fn test_ast_parsing_module() -> Result<(), Report> { #[test] fn test_ast_parsing_adv_ops() -> Result<(), Report> { - let mut context = TestContext::new(); - let source = source_file!("begin adv_push.1 adv_loadw end"); + let context = TestContext::new(); + let source = source_file!(&context, "begin adv_push.1 adv_loadw end"); let forms = module!(begin!(inst!(AdvPush(1u8.into())), inst!(AdvLoadW))); assert_eq!(context.parse_forms(source)?, forms); Ok(()) @@ -438,9 +469,11 @@ fn test_ast_parsing_adv_ops() -> Result<(), Report> { fn test_ast_parsing_adv_injection() -> Result<(), Report> { use super::AdviceInjectorNode::*; - let mut context = TestContext::new(); - let source = - source_file!("begin adv.push_u64div adv.push_mapval adv.push_smtget adv.insert_mem end"); + let context = TestContext::new(); + let source = source_file!( + &context, + "begin adv.push_u64div adv.push_mapval adv.push_smtget adv.insert_mem end" + ); let forms = module!(begin!( inst!(AdvInject(PushU64Div)), inst!(AdvInject(PushMapVal)), @@ -453,8 +486,8 @@ fn test_ast_parsing_adv_injection() -> Result<(), Report> { #[test] fn test_ast_parsing_bitwise_counters() -> Result<(), Report> { - let mut context = TestContext::new(); - let source = source_file!("begin u32clz u32ctz u32clo u32cto end"); + let context = TestContext::new(); + let source = source_file!(&context, "begin u32clz u32ctz u32clo u32cto end"); let forms = module!(begin!(inst!(U32Clz), inst!(U32Ctz), inst!(U32Clo), inst!(U32Cto))); assert_eq!(context.parse_forms(source)?, forms); @@ -463,8 +496,8 @@ fn test_ast_parsing_bitwise_counters() -> Result<(), Report> { #[test] fn test_ast_parsing_ilog2() -> Result<(), Report> { - let mut context = TestContext::new(); - let source = source_file!("begin push.8 ilog2 end"); + let context = TestContext::new(); + let source = source_file!(&context, "begin push.8 ilog2 end"); let forms = module!(begin!(inst!(PushU8(8)), inst!(ILog2))); assert_eq!(context.parse_forms(source)?, forms); @@ -473,8 +506,9 @@ fn test_ast_parsing_ilog2() -> Result<(), Report> { #[test] fn test_ast_parsing_use() -> Result<(), Report> { - let mut context = TestContext::new(); + let context = TestContext::new(); let source = source_file!( + &context, r#" use.std::abc::foo begin @@ -489,8 +523,9 @@ fn test_ast_parsing_use() -> Result<(), Report> { #[test] fn test_ast_parsing_module_nested_if() -> Result<(), Report> { - let mut context = TestContext::new(); + let context = TestContext::new(); let source = source_file!( + &context, r#" proc.foo push.1 @@ -513,14 +548,17 @@ fn test_ast_parsing_module_nested_if() -> Result<(), Report> { 0, block!( inst!(PushU8(1)), - if_true!(block!( - inst!(PushU8(0)), - inst!(PushU8(1)), - if_true!( - block!(inst!(PushU8(0)), inst!(Sub)), - block!(inst!(PushU8(1)), inst!(Sub)) - ) - )) + if_true!( + block!( + inst!(PushU8(0)), + inst!(PushU8(1)), + if_true!( + block!(inst!(PushU8(0)), inst!(Sub)), + block!(inst!(PushU8(1)), inst!(Sub)) + ) + ), + block!(inst!(Nop)) + ) ) )); assert_eq!(context.parse_forms(source)?, forms); @@ -529,8 +567,9 @@ fn test_ast_parsing_module_nested_if() -> Result<(), Report> { #[test] fn test_ast_parsing_module_sequential_if() -> Result<(), Report> { - let mut context = TestContext::new(); + let context = TestContext::new(); let source = source_file!( + &context, r#" proc.foo push.1 @@ -553,7 +592,7 @@ fn test_ast_parsing_module_sequential_if() -> Result<(), Report> { 0, block!( inst!(PushU8(1)), - if_true!(block!(inst!(PushU8(5)), inst!(PushU8(1)))), + if_true!(block!(inst!(PushU8(5)), inst!(PushU8(1))), block!(inst!(Nop))), if_true!(block!(inst!(PushU8(0)), inst!(Sub)), block!(inst!(PushU8(1)), inst!(Sub))) ) )); @@ -563,9 +602,10 @@ fn test_ast_parsing_module_sequential_if() -> Result<(), Report> { } #[test] -fn parsed_while_if_body() { - let mut context = TestContext::new(); +fn test_ast_parsing_while_if_body() { + let context = TestContext::new(); let source = source_file!( + &context, "\ begin push.1 @@ -585,20 +625,80 @@ fn parsed_while_if_body() { inst!(PushU8(1)), while_true!(block!(inst!(Mul))), inst!(Add), - if_true!(block!(inst!(Div))), + if_true!(block!(inst!(Div)), block!(inst!(Nop))), inst!(Mul) )); assert_forms!(context, source, forms); } +#[test] +fn test_ast_parsing_attributes() -> Result<(), Report> { + let context = TestContext::new(); + + let source = source_file!( + &context, + r#" + # Simple marker attribute + @inline + proc.foo.1 + loc_load.0 + end + + # List attribute + @inline(always) + proc.bar.2 + padw + end + + # Key value attributes of various kinds + @numbers(decimal = 1, hex = 0xdeadbeef) + @props(name = baz) + @props(string = "not a valid quoted identifier") + proc.baz.2 + padw + end + + begin + exec.foo + exec.bar + exec.baz + end"# + ); + + let inline = Attribute::Marker(id!(inline)); + let inline_always = Attribute::List(MetaList::new(id!(inline), [MetaExpr::Ident(id!(always))])); + let numbers = Attribute::new( + id!(numbers), + [(id!(decimal), MetaExpr::from(1u8)), (id!(hex), MetaExpr::from(0xdeadbeefu32))], + ); + let props = Attribute::new( + id!(props), + [ + (id!(name), MetaExpr::from(id!(baz))), + (id!(string), MetaExpr::from("not a valid quoted identifier")), + ], + ); + + let forms = module!( + proc!([inline], foo, 1, block!(inst!(LocLoad(0u16.into())))), + proc!([inline_always], bar, 2, block!(inst!(PadW))), + proc!([numbers, props], baz, 2, block!(inst!(PadW))), + begin!(exec!(foo), exec!(bar), exec!(baz)) + ); + assert_eq!(context.parse_forms(source)?, forms); + + Ok(()) +} + // PROCEDURE IMPORTS // ================================================================================================ #[test] fn test_missing_import() { - let mut context = TestContext::new(); + let context = TestContext::new(); let source = source_file!( + &context, r#" begin exec.u64::add @@ -626,7 +726,9 @@ fn test_missing_import() { #[test] fn test_use_in_proc_body() { + let context = TestContext::default(); let source = source_file!( + &context, r#" export.foo.1 loc_load.0 @@ -650,7 +752,8 @@ fn test_use_in_proc_body() { #[test] fn test_unterminated_proc() { - let source = source_file!("proc.foo add mul begin push.1 end"); + let context = TestContext::default(); + let source = source_file!(&context, "proc.foo add mul begin push.1 end"); assert_parse_diagnostic_lines!( source, @@ -666,7 +769,8 @@ fn test_unterminated_proc() { #[test] fn test_unterminated_if() { - let source = source_file!("proc.foo add mul if.true add.2 begin push.1 end"); + let context = TestContext::default(); + let source = source_file!(&context, "proc.foo add mul if.true add.2 begin push.1 end"); assert_parse_diagnostic_lines!( source, @@ -685,8 +789,9 @@ fn test_unterminated_if() { #[test] fn test_ast_parsing_simple_docs() -> Result<(), Report> { - let mut context = TestContext::new(); + let context = TestContext::new(); let source = source_file!( + &context, r#" #! proc doc export.foo.1 @@ -701,9 +806,10 @@ fn test_ast_parsing_simple_docs() -> Result<(), Report> { #[test] fn test_ast_parsing_module_docs_valid() { - let mut context = TestContext::new(); + let context = TestContext::new(); let source = source_file!( + &context, "\ #! Test documentation for the whole module in parsing test. Lorem ipsum dolor sit amet, #! consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. @@ -780,8 +886,9 @@ end" #[test] fn test_ast_parsing_module_docs_fail() { - let mut context = TestContext::new(); + let context = TestContext::new(); let source = source_file!( + &context, "\ #! module doc @@ -810,6 +917,7 @@ fn test_ast_parsing_module_docs_fail() { ); let source = source_file!( + &context, "\ #! proc doc export.foo.1 @@ -836,6 +944,7 @@ fn test_ast_parsing_module_docs_fail() { ); let source = source_file!( + &context, "\ #! module doc @@ -859,6 +968,7 @@ fn test_ast_parsing_module_docs_fail() { ); let source = source_file!( + &context, "\ export.foo.1 loc_load.0 @@ -884,6 +994,7 @@ fn test_ast_parsing_module_docs_fail() { ); let source = source_file!( + &context, "\ #! module doc @@ -911,6 +1022,7 @@ fn test_ast_parsing_module_docs_fail() { ); let source = source_file!( + &context, "\ #! proc doc export.foo.1 @@ -940,7 +1052,9 @@ fn test_ast_parsing_module_docs_fail() { #[test] fn assert_parsing_line_unmatched_begin() { + let context = TestContext::default(); let source = source_file!( + &context, "\ begin push.1.2 @@ -961,7 +1075,9 @@ fn assert_parsing_line_unmatched_begin() { #[test] fn assert_parsing_line_extra_param() { + let context = TestContext::default(); let source = source_file!( + &context, "\ begin add.1.2 @@ -983,7 +1099,9 @@ fn assert_parsing_line_extra_param() { #[test] fn assert_parsing_line_invalid_op() { + let context = TestContext::default(); let source = source_file!( + &context, "\ begin repeat.3 @@ -1033,7 +1151,9 @@ fn assert_parsing_line_invalid_op() { #[test] fn assert_parsing_line_unexpected_token() { + let context = TestContext::default(); let source = source_file!( + &context, "\ proc.foo add @@ -1050,256 +1170,6 @@ fn assert_parsing_line_unexpected_token() { " : ^|^", " : `-- found a mul here", " `----", - r#" help: expected "begin", or "const", or "export", or "proc", or "use", or end of file, or doc comment"# + r#" help: expected "@", or "begin", or "const", or "export", or "proc", or "use", or end of file, or doc comment"# ); } - -// SERIALIZATION AND DESERIALIZATION TESTS -// ================================================================================================ - -#[cfg(feature = "nope")] -mod serialization { - - #[test] - fn test_ast_program_serde_simple() { - let source = "begin push.0xabc234 push.0 assertz end"; - assert_correct_program_serialization(source, true); - } - - #[test] - fn test_ast_program_serde_local_procs() { - let source = "\ - proc.foo.1 - loc_load.0 - end - proc.bar.2 - padw - end - begin - exec.foo - exec.bar - end"; - assert_correct_program_serialization(source, true); - } - - #[test] - fn test_ast_program_serde_exported_procs() { - let source = "\ - export.foo.1 - loc_load.0 - end - export.bar.2 - padw - end"; - assert_correct_module_serialization(source, true); - } - - #[test] - fn test_ast_program_serde_control_flow() { - let source = "\ - begin - repeat.3 - push.1 - push.0.1 - end - - if.true - and - loc_store.0 - else - padw - end - - while.true - push.5.7 - u32wrapping_add - loc_store.1 - push.0 - end - - repeat.3 - push.2 - u32overflowing_mul - end - - end"; - assert_correct_program_serialization(source, true); - } - - #[test] - fn test_ast_program_serde_imports_serialized() { - let source = "\ - use.std::math::u64 - use.std::crypto::fri - - begin - push.0 - push.1 - exec.u64::wrapping_add - end"; - assert_correct_program_serialization(source, true); - } - - #[test] - fn test_ast_program_serde_imports_not_serialized() { - let source = "\ - use.std::math::u64 - use.std::crypto::fri - - begin - push.0 - push.1 - exec.u64::wrapping_add - end"; - assert_correct_program_serialization(source, false); - } - - #[test] - fn test_ast_module_serde_imports_serialized() { - let source = "\ - use.std::math::u64 - use.std::crypto::fri - - proc.foo.2 - push.0 - push.1 - exec.u64::wrapping_add - end"; - assert_correct_module_serialization(source, true); - } - - #[test] - fn test_ast_module_serde_imports_not_serialized() { - let source = "\ - use.std::math::u64 - use.std::crypto::fri - - proc.foo.2 - push.0 - push.1 - exec.u64::wrapping_add - end"; - assert_correct_module_serialization(source, false); - } - - #[test] - fn test_repeat_with_constant_count() { - let source = "\ - const.A=3 - const.B=A*3+5 - - begin - repeat.A - push.1 - end - - repeat.B - push.0 - end - end"; - - assert_correct_program_serialization(source, false); - - let nodes: Vec = vec![ - Node::Repeat { - times: 3, - body: CodeBody::new(vec![Node::Instruction(Instruction::PushU8(1))]), - }, - Node::Repeat { - times: 14, - body: CodeBody::new(vec![Node::Instruction(Instruction::PushU8(0))]), - }, - ]; - - assert_program_output(source, BTreeMap::new(), nodes); - } - - /// Clears the module's imports. - /// - /// Serialization of imports is optional, so if they are not serialized, then they have to be - /// cleared before testing for equality - fn clear_imports_module(module: &mut ModuleAst) { - module.clear_imports(); - } - - /// Clears the program's imports. - /// - /// Serialization of imports is optional, so if they are not serialized, then they have to be - /// cleared before testing for equality - fn clear_imports_program(program: &mut ProgramAst) { - program.clear_imports(); - } - - fn assert_correct_program_serialization(source: &str, serialize_imports: bool) { - let program = ProgramAst::parse(source).unwrap(); - - // assert the correct program serialization - let program_serialized = program.to_bytes(AstSerdeOptions::new(serialize_imports)); - let mut program_deserialized = - ProgramAst::from_bytes(program_serialized.as_slice()).unwrap(); - let mut clear_program = clear_procs_loc_program(program.clone()); - if !serialize_imports { - clear_imports_program(&mut clear_program); - } - assert_eq!(clear_program, program_deserialized); - - // assert the correct locations serialization - let mut locations = Vec::new(); - program.write_source_locations(&mut locations); - - // assert empty locations - { - let mut locations = program_deserialized.source_locations(); - let start = locations.next().unwrap(); - assert_eq!(start, &SourceLocation::default()); - assert!(locations.next().is_none()); - } - - program_deserialized - .load_source_locations(&mut SliceReader::new(&locations)) - .unwrap(); - - let program_deserialized = if !serialize_imports { - program_deserialized.with_import_info(program.import_info().clone()) - } else { - program_deserialized - }; - - assert_eq!(program, program_deserialized); - } - - fn assert_correct_module_serialization(source: &str, serialize_imports: bool) { - let module = ModuleAst::parse(source).unwrap(); - let module_serialized = module.to_bytes(AstSerdeOptions::new(serialize_imports)); - let mut module_deserialized = ModuleAst::from_bytes(module_serialized.as_slice()).unwrap(); - let mut clear_module = clear_procs_loc_module(module.clone()); - if !serialize_imports { - clear_imports_module(&mut clear_module); - } - assert_eq!(clear_module, module_deserialized); - - // assert the correct locations serialization - let mut locations = Vec::new(); - module.write_source_locations(&mut locations); - - // assert module locations are empty - module_deserialized.procs().iter().for_each(|m| { - let mut locations = m.source_locations(); - let start = locations.next().unwrap(); - assert_eq!(start, &SourceLocation::default()); - assert!(locations.next().is_none()); - }); - - module_deserialized - .load_source_locations(&mut SliceReader::new(&locations)) - .unwrap(); - - module_deserialized = if !serialize_imports { - module_deserialized.with_import_info(module.import_info().clone()) - } else { - module_deserialized - }; - - assert_eq!(module, module_deserialized); - } -} diff --git a/assembly/src/ast/visit.rs b/assembly/src/ast/visit.rs index 5c621e1687..b9d60cbe65 100644 --- a/assembly/src/ast/visit.rs +++ b/assembly/src/ast/visit.rs @@ -68,7 +68,7 @@ use crate::{ast::*, Felt, Span}; /// information you need at the parent. It is just important to be aware that this is one of the /// elements placed in the hands of the visitor implementation. /// -/// The methods of this trait all return [core::ops::ControlFlow], which can be used to break out +/// The methods of this trait all return [core::ops::ControlFlow], which can be used to break out /// of the traversal early via `ControlFlow::Break`. The `T` type parameter of this trait controls /// what the value associated with an early return will be. In most cases, the default of `()` is /// all you need - but in some cases it can be useful to return an error or other value, that @@ -119,6 +119,9 @@ pub trait Visit { fn visit_invoke_target(&mut self, target: &InvocationTarget) -> ControlFlow { visit_invoke_target(self, target) } + fn visit_alias_target(&mut self, target: &AliasTarget) -> ControlFlow { + visit_alias_target(self, target) + } fn visit_immediate_u8(&mut self, imm: &Immediate) -> ControlFlow { visit_immediate_u8(self, imm) } @@ -185,6 +188,9 @@ where fn visit_invoke_target(&mut self, target: &InvocationTarget) -> ControlFlow { (**self).visit_invoke_target(target) } + fn visit_alias_target(&mut self, target: &AliasTarget) -> ControlFlow { + (**self).visit_alias_target(target) + } fn visit_immediate_u8(&mut self, imm: &Immediate) -> ControlFlow { (**self).visit_immediate_u8(imm) } @@ -242,11 +248,11 @@ where } #[inline(always)] -pub fn visit_procedure_alias(_visitor: &mut V, _alias: &ProcedureAlias) -> ControlFlow +pub fn visit_procedure_alias(visitor: &mut V, alias: &ProcedureAlias) -> ControlFlow where V: ?Sized + Visit, { - ControlFlow::Continue(()) + visitor.visit_alias_target(alias.target()) } pub fn visit_block(visitor: &mut V, block: &Block) -> ControlFlow @@ -264,14 +270,10 @@ where V: ?Sized + Visit, { match op { - Op::If { - ref then_blk, - ref else_blk, - .. - } => { + Op::If { ref then_blk, ref else_blk, .. } => { visitor.visit_block(then_blk)?; visitor.visit_block(else_blk) - } + }, Op::While { ref body, .. } | Op::Repeat { ref body, .. } => visitor.visit_block(body), Op::Inst(ref inst) => visitor.visit_inst(inst), } @@ -294,7 +296,8 @@ where | AssertzWithError(ref code) | U32AssertWithError(ref code) | U32Assert2WithError(ref code) - | U32AssertWWithError(ref code) => visitor.visit_immediate_error_code(code), + | U32AssertWWithError(ref code) + | MTreeVerifyWithError(ref code) => visitor.visit_immediate_error_code(code), AddImm(ref imm) | SubImm(ref imm) | MulImm(ref imm) | DivImm(ref imm) | ExpImm(ref imm) | EqImm(ref imm) | NeqImm(ref imm) | Push(ref imm) => visitor.visit_immediate_felt(imm), U32WrappingAddImm(ref imm) @@ -318,11 +321,11 @@ where SysCall(ref target) => visitor.visit_syscall(target), ProcRef(ref target) => visitor.visit_procref(target), Debug(ref options) => visitor.visit_debug_options(Span::new(span, options)), - Assert | AssertEq | AssertEqw | Assertz | Add | Sub | Mul | Div | Neg | ILog2 | Inv - | Incr | Pow2 | Exp | ExpBitLength(_) | Not | And | Or | Xor | Eq | Neq | Eqw | Lt - | Lte | Gt | Gte | IsOdd | Ext2Add | Ext2Sub | Ext2Mul | Ext2Div | Ext2Neg | Ext2Inv - | U32Test | U32TestW | U32Assert | U32Assert2 | U32AssertW | U32Split | U32Cast - | U32WrappingAdd | U32OverflowingAdd | U32OverflowingAdd3 | U32WrappingAdd3 + Nop | Assert | AssertEq | AssertEqw | Assertz | Add | Sub | Mul | Div | Neg | ILog2 + | Inv | Incr | Pow2 | Exp | ExpBitLength(_) | Not | And | Or | Xor | Eq | Neq | Eqw + | Lt | Lte | Gt | Gte | IsOdd | Ext2Add | Ext2Sub | Ext2Mul | Ext2Div | Ext2Neg + | Ext2Inv | U32Test | U32TestW | U32Assert | U32Assert2 | U32AssertW | U32Split + | U32Cast | U32WrappingAdd | U32OverflowingAdd | U32OverflowingAdd3 | U32WrappingAdd3 | U32WrappingSub | U32OverflowingSub | U32WrappingMul | U32OverflowingMul | U32OverflowingMadd | U32WrappingMadd | U32Div | U32Mod | U32DivMod | U32And | U32Or | U32Xor | U32Not | U32Shr | U32Shl | U32Rotr | U32Rotl | U32Popcnt | U32Clz | U32Ctz @@ -355,7 +358,7 @@ where | AdviceInjectorNode::PushMapValNImm { offset: ref imm } | AdviceInjectorNode::InsertHdwordImm { domain: ref imm } => { visitor.visit_immediate_u8(imm) - } + }, AdviceInjectorNode::PushU64Div | AdviceInjectorNode::PushExt2intt | AdviceInjectorNode::PushSmtGet @@ -381,14 +384,14 @@ where DebugOptions::MemInterval(ref imm1, ref imm2) => { visitor.visit_immediate_u32(imm1)?; visitor.visit_immediate_u32(imm2) - } + }, DebugOptions::LocalInterval(ref imm1, ref imm2) => { visitor.visit_immediate_u16(imm1)?; visitor.visit_immediate_u16(imm2) - } + }, DebugOptions::StackAll | DebugOptions::MemAll | DebugOptions::LocalAll => { ControlFlow::Continue(()) - } + }, } } @@ -432,6 +435,14 @@ where ControlFlow::Continue(()) } +#[inline(always)] +pub fn visit_alias_target(_visitor: &mut V, _target: &AliasTarget) -> ControlFlow +where + V: ?Sized + Visit, +{ + ControlFlow::Continue(()) +} + #[inline(always)] pub fn visit_immediate_u8(_visitor: &mut V, _imm: &Immediate) -> ControlFlow where @@ -490,7 +501,7 @@ where /// do not need to visit any of the children. It is just important to be aware that this is one of /// the elements placed in the hands of the visitor implementation. /// -/// The methods of this trait all return [core::ops::ControlFlow], which can be used to break out +/// The methods of this trait all return [core::ops::ControlFlow], which can be used to break out /// of the traversal early via `ControlFlow::Break`. The `T` type parameter of this trait controls /// what the value associated with an early return will be. In most cases, the default of `()` is /// all you need - but in some cases it can be useful to return an error or other value, that @@ -544,6 +555,9 @@ pub trait VisitMut { fn visit_mut_invoke_target(&mut self, target: &mut InvocationTarget) -> ControlFlow { visit_mut_invoke_target(self, target) } + fn visit_mut_alias_target(&mut self, target: &mut AliasTarget) -> ControlFlow { + visit_mut_alias_target(self, target) + } fn visit_mut_immediate_u8(&mut self, imm: &mut Immediate) -> ControlFlow { visit_mut_immediate_u8(self, imm) } @@ -613,6 +627,9 @@ where fn visit_mut_invoke_target(&mut self, target: &mut InvocationTarget) -> ControlFlow { (**self).visit_mut_invoke_target(target) } + fn visit_mut_alias_target(&mut self, target: &mut AliasTarget) -> ControlFlow { + (**self).visit_mut_alias_target(target) + } fn visit_mut_immediate_u8(&mut self, imm: &mut Immediate) -> ControlFlow { (**self).visit_mut_immediate_u8(imm) } @@ -671,13 +688,13 @@ where #[inline(always)] pub fn visit_mut_procedure_alias( - _visitor: &mut V, - _alias: &mut ProcedureAlias, + visitor: &mut V, + alias: &mut ProcedureAlias, ) -> ControlFlow where V: ?Sized + VisitMut, { - ControlFlow::Continue(()) + visitor.visit_mut_alias_target(alias.target_mut()) } pub fn visit_mut_block(visitor: &mut V, block: &mut Block) -> ControlFlow @@ -695,17 +712,13 @@ where V: ?Sized + VisitMut, { match op { - Op::If { - ref mut then_blk, - ref mut else_blk, - .. - } => { + Op::If { ref mut then_blk, ref mut else_blk, .. } => { visitor.visit_mut_block(then_blk)?; visitor.visit_mut_block(else_blk) - } + }, Op::While { ref mut body, .. } | Op::Repeat { ref mut body, .. } => { visitor.visit_mut_block(body) - } + }, Op::Inst(ref mut inst) => visitor.visit_mut_inst(inst), } } @@ -733,11 +746,12 @@ where | AssertzWithError(ref mut code) | U32AssertWithError(ref mut code) | U32Assert2WithError(ref mut code) - | U32AssertWWithError(ref mut code) => visitor.visit_mut_immediate_error_code(code), + | U32AssertWWithError(ref mut code) + | MTreeVerifyWithError(ref mut code) => visitor.visit_mut_immediate_error_code(code), AddImm(ref mut imm) | SubImm(ref mut imm) | MulImm(ref mut imm) | DivImm(ref mut imm) | ExpImm(ref mut imm) | EqImm(ref mut imm) | NeqImm(ref mut imm) | Push(ref mut imm) => { visitor.visit_mut_immediate_felt(imm) - } + }, U32WrappingAddImm(ref mut imm) | U32OverflowingAddImm(ref mut imm) | U32WrappingSubImm(ref mut imm) @@ -759,11 +773,11 @@ where SysCall(ref mut target) => visitor.visit_mut_syscall(target), ProcRef(ref mut target) => visitor.visit_mut_procref(target), Debug(ref mut options) => visitor.visit_mut_debug_options(Span::new(span, options)), - Assert | AssertEq | AssertEqw | Assertz | Add | Sub | Mul | Div | Neg | ILog2 | Inv - | Incr | Pow2 | Exp | ExpBitLength(_) | Not | And | Or | Xor | Eq | Neq | Eqw | Lt - | Lte | Gt | Gte | IsOdd | Ext2Add | Ext2Sub | Ext2Mul | Ext2Div | Ext2Neg | Ext2Inv - | U32Test | U32TestW | U32Assert | U32Assert2 | U32AssertW | U32Split | U32Cast - | U32WrappingAdd | U32OverflowingAdd | U32OverflowingAdd3 | U32WrappingAdd3 + Nop | Assert | AssertEq | AssertEqw | Assertz | Add | Sub | Mul | Div | Neg | ILog2 + | Inv | Incr | Pow2 | Exp | ExpBitLength(_) | Not | And | Or | Xor | Eq | Neq | Eqw + | Lt | Lte | Gt | Gte | IsOdd | Ext2Add | Ext2Sub | Ext2Mul | Ext2Div | Ext2Neg + | Ext2Inv | U32Test | U32TestW | U32Assert | U32Assert2 | U32AssertW | U32Split + | U32Cast | U32WrappingAdd | U32OverflowingAdd | U32OverflowingAdd3 | U32WrappingAdd3 | U32WrappingSub | U32OverflowingSub | U32WrappingMul | U32OverflowingMul | U32OverflowingMadd | U32WrappingMadd | U32Div | U32Mod | U32DivMod | U32And | U32Or | U32Xor | U32Not | U32Shr | U32Shl | U32Rotr | U32Rotl | U32Popcnt | U32Clz | U32Ctz @@ -792,15 +806,11 @@ where V: ?Sized + VisitMut, { match node.into_inner() { - AdviceInjectorNode::PushMapValImm { - offset: ref mut imm, - } - | AdviceInjectorNode::PushMapValNImm { - offset: ref mut imm, - } - | AdviceInjectorNode::InsertHdwordImm { - domain: ref mut imm, - } => visitor.visit_mut_immediate_u8(imm), + AdviceInjectorNode::PushMapValImm { offset: ref mut imm } + | AdviceInjectorNode::PushMapValNImm { offset: ref mut imm } + | AdviceInjectorNode::InsertHdwordImm { domain: ref mut imm } => { + visitor.visit_mut_immediate_u8(imm) + }, AdviceInjectorNode::PushU64Div | AdviceInjectorNode::PushExt2intt | AdviceInjectorNode::PushSmtGet @@ -829,14 +839,14 @@ where DebugOptions::MemInterval(ref mut imm1, ref mut imm2) => { visitor.visit_mut_immediate_u32(imm1)?; visitor.visit_mut_immediate_u32(imm2) - } + }, DebugOptions::LocalInterval(ref mut imm1, ref mut imm2) => { visitor.visit_mut_immediate_u16(imm1)?; visitor.visit_mut_immediate_u16(imm2) - } + }, DebugOptions::StackAll | DebugOptions::MemAll | DebugOptions::LocalAll => { ControlFlow::Continue(()) - } + }, } } @@ -883,6 +893,14 @@ where ControlFlow::Continue(()) } +#[inline(always)] +pub fn visit_mut_alias_target(_visitor: &mut V, _target: &mut AliasTarget) -> ControlFlow +where + V: ?Sized + VisitMut, +{ + ControlFlow::Continue(()) +} + #[inline(always)] pub fn visit_mut_immediate_u8(_visitor: &mut V, _imm: &mut Immediate) -> ControlFlow where diff --git a/assembly/src/compile.rs b/assembly/src/compile.rs index 5495f981fb..1d2fda3a45 100644 --- a/assembly/src/compile.rs +++ b/assembly/src/compile.rs @@ -6,9 +6,14 @@ use alloc::{ vec::Vec, }; +use miette::miette; + use crate::{ ast::{Module, ModuleKind}, - diagnostics::{IntoDiagnostic, NamedSource, Report, SourceCode, SourceFile, WrapErr}, + diagnostics::{ + IntoDiagnostic, NamedSource, Report, SourceCode, SourceContent, SourceFile, SourceManager, + WrapErr, + }, library::{LibraryNamespace, LibraryPath}, }; @@ -84,17 +89,17 @@ impl Options { /// This trait is meant to be implemented by any type that can be compiled to a [Module], /// to allow methods which expect a [Module] to accept things like: /// -/// * A [Module] which was previously compiled or read from a [crate::Library]. +/// * A [Module] which was previously parsed or deserialized /// * A string representing the source code of a [Module]. /// * A path to a file containing the source code of a [Module]. -/// * A vector of [Form]s comprising the contents of a [Module]. +/// * A vector of [crate::ast::Form]s comprising the contents of a [Module]. pub trait Compile: Sized { /// Compile (or convert) `self` into an executable [Module]. /// - /// See [Compile::compile_with_opts] for more details. + /// See [Compile::compile_with_options()] for more details. #[inline] - fn compile(self) -> Result, Report> { - self.compile_with_options(Options::default()) + fn compile(self, source_manager: &dyn SourceManager) -> Result, Report> { + self.compile_with_options(source_manager, Options::default()) } /// Compile (or convert) `self` into a [Module] using the provided `options`. @@ -104,7 +109,11 @@ pub trait Compile: Sized { /// an executable module). /// /// See the documentation for [Options] to see how compilation can be configured. - fn compile_with_options(self, options: Options) -> Result, Report>; + fn compile_with_options( + self, + source_manager: &dyn SourceManager, + options: Options, + ) -> Result, Report>; } // COMPILE IMPLEMENTATIONS FOR MODULES @@ -112,20 +121,32 @@ pub trait Compile: Sized { impl Compile for Module { #[inline(always)] - fn compile_with_options(self, options: Options) -> Result, Report> { - Box::new(self).compile_with_options(options) + fn compile_with_options( + self, + source_manager: &dyn SourceManager, + options: Options, + ) -> Result, Report> { + Box::new(self).compile_with_options(source_manager, options) } } impl<'a> Compile for &'a Module { #[inline(always)] - fn compile_with_options(self, options: Options) -> Result, Report> { - Box::new(self.clone()).compile_with_options(options) + fn compile_with_options( + self, + source_manager: &dyn SourceManager, + options: Options, + ) -> Result, Report> { + Box::new(self.clone()).compile_with_options(source_manager, options) } } impl Compile for Box { - fn compile_with_options(mut self, options: Options) -> Result, Report> { + fn compile_with_options( + mut self, + _source_manager: &dyn SourceManager, + options: Options, + ) -> Result, Report> { let actual = self.kind(); if actual == options.kind { if let Some(path) = options.path { @@ -133,18 +154,22 @@ impl Compile for Box { } Ok(self) } else { - Err(Report::msg(format!( + Err(miette!( "compilation failed: expected a {} module, but got a {actual} module", options.kind - ))) + )) } } } impl Compile for Arc { #[inline(always)] - fn compile_with_options(self, options: Options) -> Result, Report> { - Box::new(Arc::unwrap_or_clone(self)).compile_with_options(options) + fn compile_with_options( + self, + source_manager: &dyn SourceManager, + options: Options, + ) -> Result, Report> { + Box::new(Arc::unwrap_or_clone(self)).compile_with_options(source_manager, options) } } @@ -152,10 +177,15 @@ impl Compile for Arc { // ------------------------------------------------------------------------------------------------ impl Compile for Arc { - fn compile_with_options(self, options: Options) -> Result, Report> { + fn compile_with_options( + self, + source_manager: &dyn SourceManager, + options: Options, + ) -> Result, Report> { + let source_file = source_manager.copy_into(&self); let path = match options.path { Some(path) => path, - None => self + None => source_file .name() .parse::() .into_diagnostic() @@ -163,46 +193,72 @@ impl Compile for Arc { }; let mut parser = Module::parser(options.kind); parser.set_warnings_as_errors(options.warnings_as_errors); - parser.parse(path, self) + parser.parse(path, source_file) } } impl<'a> Compile for &'a str { #[inline(always)] - fn compile_with_options(self, options: Options) -> Result, Report> { - self.to_string().compile_with_options(options) + fn compile_with_options( + self, + source_manager: &dyn SourceManager, + options: Options, + ) -> Result, Report> { + self.to_string().into_boxed_str().compile_with_options(source_manager, options) } } impl<'a> Compile for &'a String { #[inline(always)] - fn compile_with_options(self, options: Options) -> Result, Report> { - self.clone().compile_with_options(options) + fn compile_with_options( + self, + source_manager: &dyn SourceManager, + options: Options, + ) -> Result, Report> { + self.clone().into_boxed_str().compile_with_options(source_manager, options) } } impl Compile for String { - fn compile_with_options(self, options: Options) -> Result, Report> { + fn compile_with_options( + self, + source_manager: &dyn SourceManager, + options: Options, + ) -> Result, Report> { + self.into_boxed_str().compile_with_options(source_manager, options) + } +} + +impl Compile for Box { + fn compile_with_options( + self, + source_manager: &dyn SourceManager, + options: Options, + ) -> Result, Report> { + let path = options.path.unwrap_or_else(|| { + LibraryPath::from(match options.kind { + ModuleKind::Library => LibraryNamespace::Anon, + ModuleKind::Executable => LibraryNamespace::Exec, + ModuleKind::Kernel => LibraryNamespace::Kernel, + }) + }); + let name = Arc::::from(path.path().into_owned().into_boxed_str()); let mut parser = Module::parser(options.kind); parser.set_warnings_as_errors(options.warnings_as_errors); - if let Some(path) = options.path { - let source = Arc::new(SourceFile::new(path.path(), self)); - return parser.parse(path, source); - } - let path = LibraryPath::from(match options.kind { - ModuleKind::Library => LibraryNamespace::Anon, - ModuleKind::Executable => LibraryNamespace::Exec, - ModuleKind::Kernel => LibraryNamespace::Kernel, - }); - let source = Arc::new(SourceFile::new(path.path(), self)); - parser.parse(path, source) + let content = SourceContent::new(name.clone(), self); + let source_file = source_manager.load_from_raw_parts(name, content); + parser.parse(path, source_file) } } impl<'a> Compile for Cow<'a, str> { #[inline(always)] - fn compile_with_options(self, options: Options) -> Result, Report> { - self.into_owned().compile_with_options(options) + fn compile_with_options( + self, + source_manager: &dyn SourceManager, + options: Options, + ) -> Result, Report> { + self.into_owned().into_boxed_str().compile_with_options(source_manager, options) } } @@ -210,27 +266,51 @@ impl<'a> Compile for Cow<'a, str> { // ------------------------------------------------------------------------------------------------ impl<'a> Compile for &'a [u8] { - #[inline(always)] - fn compile_with_options(self, options: Options) -> Result, Report> { + #[inline] + fn compile_with_options( + self, + source_manager: &dyn SourceManager, + options: Options, + ) -> Result, Report> { core::str::from_utf8(self) .map_err(|err| { - Report::from(crate::parser::ParsingError::from(err)).with_source_code(self.to_vec()) + Report::from(crate::parser::ParsingError::from_utf8_error(Default::default(), err)) + .with_source_code(self.to_vec()) }) .wrap_err("parsing failed: invalid source code") - .and_then(|source| source.compile_with_options(options)) + .and_then(|source| source.compile_with_options(source_manager, options)) } } impl Compile for Vec { - #[inline(always)] - fn compile_with_options(self, options: Options) -> Result, Report> { + #[inline] + fn compile_with_options( + self, + source_manager: &dyn SourceManager, + options: Options, + ) -> Result, Report> { String::from_utf8(self) .map_err(|err| { - let error = crate::parser::ParsingError::from(err.utf8_error()); + let error = crate::parser::ParsingError::from_utf8_error( + Default::default(), + err.utf8_error(), + ); Report::from(error).with_source_code(err.into_bytes()) }) .wrap_err("parsing failed: invalid source code") - .and_then(|source| source.compile_with_options(options)) + .and_then(|source| { + source.into_boxed_str().compile_with_options(source_manager, options) + }) + } +} +impl Compile for Box<[u8]> { + #[inline(always)] + fn compile_with_options( + self, + source_manager: &dyn SourceManager, + options: Options, + ) -> Result, Report> { + Vec::from(self).compile_with_options(source_manager, options) } } @@ -238,14 +318,31 @@ impl Compile for NamedSource where T: SourceCode + AsRef<[u8]>, { - fn compile_with_options(self, options: Options) -> Result, Report> { - let content = String::from_utf8(self.inner().as_ref().to_vec()) + fn compile_with_options( + self, + source_manager: &dyn SourceManager, + options: Options, + ) -> Result, Report> { + let path = match options.path { + Some(path) => path, + None => self + .name() + .parse::() + .into_diagnostic() + .wrap_err("cannot compile module as it has an invalid path/name")?, + }; + let content = core::str::from_utf8(self.inner().as_ref()) .map_err(|err| { - let error = crate::parser::ParsingError::from(err.utf8_error()); - Report::from(error).with_source_code(err.into_bytes()) + let error = crate::parser::ParsingError::from_utf8_error(Default::default(), err); + Report::from(error) }) .wrap_err("parsing failed: expected source code to be valid utf-8")?; - Arc::new(SourceFile::new(self.name(), content)).compile_with_options(options) + let name = Arc::::from(self.name()); + let content = SourceContent::new(name.clone(), content.to_string().into_boxed_str()); + let source_file = source_manager.load_from_raw_parts(name, content); + let mut parser = Module::parser(options.kind); + parser.set_warnings_as_errors(options.warnings_as_errors); + parser.parse(path, source_file) } } @@ -254,10 +351,17 @@ where #[cfg(feature = "std")] impl<'a> Compile for &'a std::path::Path { - fn compile_with_options(self, options: Options) -> Result, Report> { - use crate::{ast::Ident, library::PathError}; + fn compile_with_options( + self, + source_manager: &dyn SourceManager, + options: Options, + ) -> Result, Report> { use std::path::Component; + use vm_core::debuginfo::SourceManagerExt; + + use crate::{ast::Ident, library::PathError}; + let path = match options.path { Some(path) => path, None => { @@ -290,17 +394,25 @@ impl<'a> Compile for &'a std::path::Path { Ok::<(), Report>(()) })?; LibraryPath::new_from_components(ns, parts) - } + }, }; + let source_file = source_manager + .load_file(self) + .into_diagnostic() + .wrap_err("source manager is unable to load file")?; let mut parser = Module::parser(options.kind); - parser.parse_file(path, self) + parser.parse(path, source_file) } } #[cfg(feature = "std")] impl Compile for std::path::PathBuf { #[inline(always)] - fn compile_with_options(self, options: Options) -> Result, Report> { - self.as_path().compile_with_options(options) + fn compile_with_options( + self, + source_manager: &dyn SourceManager, + options: Options, + ) -> Result, Report> { + self.as_path().compile_with_options(source_manager, options) } } diff --git a/assembly/src/diagnostics.rs b/assembly/src/diagnostics.rs index 0df83a786d..45a5222730 100644 --- a/assembly/src/diagnostics.rs +++ b/assembly/src/diagnostics.rs @@ -1,13 +1,12 @@ +use alloc::{borrow::Cow, boxed::Box, sync::Arc, vec::Vec}; +use core::{fmt, ops::Range}; + pub use miette::{ self, Diagnostic, IntoDiagnostic, LabeledSpan, NamedSource, Report, Result, Severity, SourceCode, WrapErr, }; pub use tracing; - -use alloc::{borrow::Cow, boxed::Box, sync::Arc, vec::Vec}; -use core::{fmt, ops::Range}; - -pub type SourceFile = NamedSource; +pub use vm_core::debuginfo::*; // LABEL // ================================================================================================ @@ -24,16 +23,13 @@ pub struct Label { impl Label { /// Construct a label for the given range of bytes, expressible as any type which can be - /// converted to a [`Range`], e.g. [SourceSpan]. + /// converted to a [`Range`], e.g. [miette::SourceSpan]. pub fn at(range: R) -> Self where Range: From, { let range = Range::::from(range); - Self { - span: range.into(), - label: None, - } + Self { span: range.into(), label: None } } /// Construct a label which points to a specific offset in the source file. @@ -87,12 +83,12 @@ impl From

( - path: P, - namespace: LibraryNamespace, - version: Version, - ) -> Result - where - P: AsRef, - { - let path = path.as_ref(); - if !path.is_dir() { - return Err(Report::msg(format!( - "the provided path '{}' is not a valid directory", - path.display() - ))); - } - - // mod.masm is not allowed in the root directory - if path.join("mod.masm").exists() { - return Err(Report::msg("mod.masm is not allowed in the root directory")); - } - - let library = Self { - namespace, - version, - modules: Default::default(), - dependencies: Default::default(), - }; - library.load_modules_from_dir(path) - } - - /// Read a library from a file. - #[instrument(name = "read_library_file", fields(path = %path.as_ref().display()))] - pub fn read_from_file

(path: P) -> Result - where - P: AsRef, - { - use vm_core::utils::ReadAdapter; - - let path = path.as_ref(); - let mut file = fs::File::open(path)?; - let mut adapter = ReadAdapter::new(&mut file); - ::read_from(&mut adapter).map_err(|error| { - LibraryError::DeserializationFailed { - path: path.to_string_lossy().into_owned(), - error, - } - }) - } - - /// Write the library to a target director, using its namespace as file name and the - /// appropriate extension. - pub fn write_to_dir

(&self, dir_path: P) -> io::Result<()> - where - P: AsRef, - { - fs::create_dir_all(&dir_path)?; - let mut path = dir_path.as_ref().join(self.namespace.as_ref()); - path.set_extension(Self::LIBRARY_EXTENSION); - - // NOTE: We catch panics due to i/o errors here due to the fact - // that the ByteWriter trait does not provide fallible APIs, so - // WriteAdapter will panic if the underlying writes fail. This - // needs to be addressed in winterfall at some point - std::panic::catch_unwind(|| { - let mut file = fs::File::create(path)?; - self.write_into(&mut file); - Ok(()) - }) - .map_err(|p| { - match p.downcast::() { - // SAFETY: It is guaranteed safe to read Box - Ok(err) => unsafe { core::ptr::read(&*err) }, - Err(err) => std::panic::resume_unwind(err), - } - })? - } - - /// Read the contents (modules) of this library from `dir`, returning any errors that occur - /// while traversing the file system. - /// - /// Errors may also be returned if traversal discovers issues with the library, such as - /// invalid names, etc. - /// - /// Returns the set of modules that were parsed - fn load_modules_from_dir(mut self, dir: &Path) -> Result { - use crate::diagnostics::WrapErr; - use alloc::collections::btree_map::Entry; - - let mut modules = BTreeMap::default(); - let mut dependencies = BTreeSet::default(); - - let walker = WalkLibrary::new(self.namespace.clone(), dir) - .into_diagnostic() - .wrap_err_with(|| format!("failed to load library from '{}'", dir.display()))?; - for entry in walker { - let LibraryEntry { - mut name, - source_path, - } = entry?; - if name.last() == MaslLibrary::MOD { - name.pop(); - } - // Parse module at the given path - let ast = ast::Module::parse_file(name.clone(), ModuleKind::Library, &source_path)?; - // Add dependencies of this module to the global set - for path in ast.import_paths() { - let ns = path.namespace(); - if ns != &self.namespace { - dependencies.insert(ns.clone()); - } - } - match modules.entry(name) { - Entry::Occupied(ref entry) => { - return Err(LibraryError::DuplicateModulePath(entry.key().clone())) - .into_diagnostic(); - } - Entry::Vacant(entry) => { - entry.insert(Arc::from(ast)); - } - } - } - - self.modules.extend(modules.into_values()); - self.dependencies.extend(dependencies); - - self.validate()?; - - Ok(self) - } - } -} - -#[cfg(feature = "std")] -struct LibraryEntry { - name: super::LibraryPath, - source_path: std::path::PathBuf, -} - -#[cfg(feature = "std")] -struct WalkLibrary<'a> { - namespace: LibraryNamespace, - root: &'a std::path::Path, - stack: alloc::collections::VecDeque>, -} - -#[cfg(feature = "std")] -impl<'a> WalkLibrary<'a> { - fn new(namespace: LibraryNamespace, path: &'a std::path::Path) -> std::io::Result { - use alloc::collections::VecDeque; - - let stack = VecDeque::from_iter(std::fs::read_dir(path)?); - - Ok(Self { - namespace, - root: path, - stack, - }) - } - - fn next_entry( - &mut self, - entry: &std::fs::DirEntry, - ty: &std::fs::FileType, - ) -> Result, crate::diagnostics::Report> { - use crate::{ - diagnostics::{IntoDiagnostic, Report}, - LibraryPath, - }; - use std::{ffi::OsStr, fs}; - - if ty.is_dir() { - let dir = entry.path(); - self.stack.extend(fs::read_dir(dir).into_diagnostic()?); - return Ok(None); - } - - let mut file_path = entry.path(); - let is_module = file_path - .extension() - .map(|ext| ext == AsRef::::as_ref(MaslLibrary::MODULE_EXTENSION)) - .unwrap_or(false); - if !is_module { - return Ok(None); - } - - // Remove the file extension, and the root prefix, leaving us - // with a namespace-relative path - file_path.set_extension(""); - if file_path.is_dir() { - return Err(Report::msg(format!( - "file and directory with same name are not allowed: {}", - file_path.display() - ))); - } - let relative_path = file_path - .strip_prefix(self.root) - .expect("expected path to be a child of the root directory"); - - // Construct a [LibraryPath] from the path components, after validating them - let mut libpath = LibraryPath::from(self.namespace.clone()); - for component in relative_path.iter() { - let component = component.to_str().ok_or_else(|| { - let p = entry.path(); - Report::msg(format!("{} is an invalid directory entry", p.display())) - })?; - libpath.push(component).into_diagnostic()?; - } - Ok(Some(LibraryEntry { - name: libpath, - source_path: entry.path(), - })) - } -} - -#[cfg(feature = "std")] -impl<'a> Iterator for WalkLibrary<'a> { - type Item = Result; - fn next(&mut self) -> Option { - use crate::diagnostics::IntoDiagnostic; - loop { - let entry = self - .stack - .pop_front()? - .and_then(|entry| entry.file_type().map(|ft| (entry, ft))) - .into_diagnostic(); - - match entry { - Ok((ref entry, ref file_type)) => { - match self.next_entry(entry, file_type).transpose() { - None => continue, - result => break result, - } - } - Err(err) => break Some(Err(err)), - } - } - } -} - -impl Serializable for MaslLibrary { - #[inline] - fn write_into(&self, target: &mut W) { - self.write_into_with_options(target, AST_DEFAULT_SERDE_OPTIONS) - } -} - -/// Serialization -impl MaslLibrary { - pub fn write_into_with_options(&self, target: &mut W, options: AstSerdeOptions) { - self.namespace.write_into(target); - self.version.write_into(target); - - let modules = self.modules(); - - // write dependencies - target.write_u16(self.dependencies.len() as u16); - self.dependencies.iter().for_each(|dep| dep.write_into(target)); - - // this assert is OK because maximum number of modules is enforced by Library constructor - debug_assert!(modules.len() <= MAX_MODULES, "too many modules"); - - target.write_u16(modules.len() as u16); - modules.for_each(|module| { - module.write_into_with_options(target, options); - }); - } -} - -impl Deserializable for MaslLibrary { - fn read_from(source: &mut R) -> Result { - let namespace = LibraryNamespace::read_from(source)?; - let version = Version::read_from(source)?; - - // read dependencies - let num_deps = source.read_u16()? as usize; - // TODO: check for duplicate/self-referential dependencies? - let deps_set: BTreeSet = (0..num_deps) - .map(|_| LibraryNamespace::read_from(source)) - .collect::>()?; - - // read modules - let num_modules = source.read_u16()? as usize; - let mut modules = Vec::with_capacity(num_modules); - for _ in 0..num_modules { - let ast = ast::Module::read_from(source)?; - modules.push(ast); - } - - let deps = deps_set.into_iter().collect(); - Self::new(namespace, version, modules, deps) - .map_err(|err| DeserializationError::InvalidValue(format!("{err}"))) - } -} diff --git a/assembly/src/library/mod.rs b/assembly/src/library/mod.rs index e474b346b3..7ca5f3572d 100644 --- a/assembly/src/library/mod.rs +++ b/assembly/src/library/mod.rs @@ -1,67 +1,447 @@ -use crate::ast; +use alloc::{ + collections::{BTreeMap, BTreeSet}, + string::{String, ToString}, + sync::Arc, + vec::Vec, +}; + +use vm_core::{ + crypto::hash::RpoDigest, + mast::{MastForest, MastNodeId}, + utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}, + Kernel, +}; + +use crate::ast::{ProcedureName, QualifiedProcedureName}; mod error; -mod masl; +mod module; mod namespace; mod path; mod version; -pub use self::error::LibraryError; -pub use self::masl::MaslLibrary; -pub use self::namespace::{LibraryNamespace, LibraryNamespaceError}; -pub use self::path::{LibraryPath, LibraryPathComponent, PathError}; -pub use self::version::{Version, VersionError}; +pub use module::{ModuleInfo, ProcedureInfo}; + +pub use self::{ + error::LibraryError, + namespace::{LibraryNamespace, LibraryNamespaceError}, + path::{LibraryPath, LibraryPathComponent, PathError}, + version::{Version, VersionError}, +}; #[cfg(test)] mod tests; -/// Maximum number of modules in a library. -const MAX_MODULES: usize = u16::MAX as usize; +// LIBRARY +// ================================================================================================ + +/// Represents a library where all modules were compiled into a [`MastForest`]. +/// +/// A library exports a set of one or more procedures. Currently, all exported procedures belong +/// to the same top-level namespace. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Library { + /// The content hash of this library, formed by hashing the roots of all exports in + /// lexicographical order (by digest, not procedure name) + digest: RpoDigest, + /// A map between procedure paths and the corresponding procedure roots in the MAST forest. + /// Multiple paths can map to the same root, and also, some roots may not be associated with + /// any paths. + /// + /// Note that we use `MastNodeId` as an identifier for procedures instead of MAST root, since 2 + /// different procedures with the same MAST root can be different due to the decorators they + /// contain. However, note that `MastNodeId` is also not a unique identifier for procedures; if + /// the procedures have the same MAST root and decorators, they will have the same + /// `MastNodeId`. + exports: BTreeMap, + /// The MAST forest underlying this library. + mast_forest: Arc, +} + +impl AsRef for Library { + #[inline(always)] + fn as_ref(&self) -> &Library { + self + } +} + +// ------------------------------------------------------------------------------------------------ +/// Constructors +impl Library { + /// Constructs a new [`Library`] from the provided MAST forest and a set of exports. + /// + /// # Errors + /// Returns an error if any of the specified exports do not have a corresponding procedure root + /// in the provided MAST forest. + pub fn new( + mast_forest: Arc, + exports: BTreeMap, + ) -> Result { + for (fqn, &proc_body_id) in exports.iter() { + if !mast_forest.is_procedure_root(proc_body_id) { + return Err(LibraryError::NoProcedureRootForExport { procedure_path: fqn.clone() }); + } + } + + let digest = compute_content_hash(&exports, &mast_forest); -/// Maximum number of dependencies in a library. -const MAX_DEPENDENCIES: usize = u16::MAX as usize; + Ok(Self { digest, exports, mast_forest }) + } +} -/// A library definition that provides AST modules for the compilation process. -pub trait Library { - /// Returns the root namespace of this library. - fn root_ns(&self) -> &LibraryNamespace; +// ------------------------------------------------------------------------------------------------ +/// Public accessors +impl Library { + /// Returns the [RpoDigest] representing the content hash of this library + pub fn digest(&self) -> &RpoDigest { + &self.digest + } + + /// Returns the fully qualified name of all procedures exported by the library. + pub fn exports(&self) -> impl Iterator { + self.exports.keys() + } - /// Returns the version number of this library. - fn version(&self) -> &Version; + /// Returns the number of exports in this library. + pub fn num_exports(&self) -> usize { + self.exports.len() + } - /// Iterate the modules available in the library. - fn modules(&self) -> impl ExactSizeIterator + '_; + /// Returns a MAST node ID associated with the specified exported procedure. + /// + /// # Panics + /// Panics if the specified procedure is not exported from this library. + pub fn get_export_node_id(&self, proc_name: &QualifiedProcedureName) -> MastNodeId { + *self.exports.get(proc_name).expect("procedure not exported from the library") + } - /// Returns the dependency libraries of this library. - fn dependencies(&self) -> &[LibraryNamespace]; + /// Returns true if the specified exported procedure is re-exported from a dependency. + pub fn is_reexport(&self, proc_name: &QualifiedProcedureName) -> bool { + self.exports + .get(proc_name) + .map(|&node_id| self.mast_forest[node_id].is_external()) + .unwrap_or(false) + } - /// Returns the module stored at the provided path. - fn get_module(&self, path: &LibraryPath) -> Option<&ast::Module> { - self.modules().find(|&module| module.path() == path) + /// Returns a reference to the inner [`MastForest`]. + pub fn mast_forest(&self) -> &Arc { + &self.mast_forest } } -impl Library for &T -where - T: Library, -{ - fn root_ns(&self) -> &LibraryNamespace { - T::root_ns(self) +/// Conversions +impl Library { + /// Returns an iterator over the module infos of the library. + pub fn module_infos(&self) -> impl Iterator { + let mut modules_by_path: BTreeMap = BTreeMap::new(); + + for (proc_name, &proc_root_node_id) in self.exports.iter() { + modules_by_path + .entry(proc_name.module.clone()) + .and_modify(|compiled_module| { + let proc_digest = self.mast_forest[proc_root_node_id].digest(); + compiled_module.add_procedure(proc_name.name.clone(), proc_digest); + }) + .or_insert_with(|| { + let mut module_info = ModuleInfo::new(proc_name.module.clone()); + + let proc_digest = self.mast_forest[proc_root_node_id].digest(); + module_info.add_procedure(proc_name.name.clone(), proc_digest); + + module_info + }); + } + + modules_by_path.into_values() } +} + +impl Serializable for Library { + fn write_into(&self, target: &mut W) { + let Self { digest: _, exports, mast_forest } = self; + + mast_forest.write_into(target); - fn version(&self) -> &Version { - T::version(self) + target.write_usize(exports.len()); + for (proc_name, proc_node_id) in exports { + proc_name.module.write_into(target); + proc_name.name.as_str().write_into(target); + target.write_u32(proc_node_id.as_u32()); + } } +} + +impl Deserializable for Library { + fn read_from(source: &mut R) -> Result { + let mast_forest = Arc::new(MastForest::read_from(source)?); + + let num_exports = source.read_usize()?; + let mut exports = BTreeMap::new(); + for _ in 0..num_exports { + let proc_module = source.read()?; + let proc_name: String = source.read()?; + let proc_name = ProcedureName::new(proc_name) + .map_err(|err| DeserializationError::InvalidValue(err.to_string()))?; + let proc_name = QualifiedProcedureName::new(proc_module, proc_name); + let proc_node_id = MastNodeId::from_u32_safe(source.read_u32()?, &mast_forest)?; + + exports.insert(proc_name, proc_node_id); + } + + let digest = compute_content_hash(&exports, &mast_forest); - fn modules(&self) -> impl ExactSizeIterator + '_ { - T::modules(self) + Ok(Self { digest, exports, mast_forest }) } +} + +fn compute_content_hash( + exports: &BTreeMap, + mast_forest: &MastForest, +) -> RpoDigest { + let digests = BTreeSet::from_iter(exports.values().map(|&id| mast_forest[id].digest())); + digests + .into_iter() + .reduce(|a, b| vm_core::crypto::hash::Rpo256::merge(&[a, b])) + .unwrap() +} + +#[cfg(feature = "std")] +mod use_std_library { + use std::{fs, io, path::Path}; + + use miette::Report; + use vm_core::utils::ReadAdapter; + + use super::*; + use crate::Assembler; + + impl Library { + /// File extension for the Assembly Library. + pub const LIBRARY_EXTENSION: &'static str = "masl"; + + /// Write the library to a target file + /// + /// NOTE: It is up to the caller to use the correct file extension, but there is no + /// specific requirement that the extension be set, or the same as + /// [`Self::LIBRARY_EXTENSION`]. + pub fn write_to_file(&self, path: impl AsRef) -> io::Result<()> { + let path = path.as_ref(); + + if let Some(dir) = path.parent() { + fs::create_dir_all(dir)?; + } + + // NOTE: We catch panics due to i/o errors here due to the fact that the ByteWriter + // trait does not provide fallible APIs, so WriteAdapter will panic if the underlying + // writes fail. This needs to be addressed in winterfell at some point + std::panic::catch_unwind(|| { + let mut file = fs::File::create(path)?; + self.write_into(&mut file); + Ok(()) + }) + .map_err(|p| { + match p.downcast::() { + // SAFETY: It is guaranteed safe to read Box + Ok(err) => unsafe { core::ptr::read(&*err) }, + Err(err) => std::panic::resume_unwind(err), + } + })? + } - fn dependencies(&self) -> &[LibraryNamespace] { - T::dependencies(self) + /// Create a [Library] from a standard Miden Assembly project layout. + /// + /// The standard layout dictates that a given path is the root of a namespace, and the + /// directory hierarchy corresponds to the namespace hierarchy. A `.masm` file found in a + /// given subdirectory of the root, will be parsed with its [LibraryPath] set based on + /// where it resides in the directory structure. + /// + /// This function recursively parses the entire directory structure under `path`, ignoring + /// any files which do not have the `.masm` extension. + /// + /// For example, let's say I call this function like so: + /// + /// ```rust + /// use std::sync::Arc; + /// + /// use miden_assembly::{Assembler, Library, LibraryNamespace}; + /// use vm_core::debuginfo::DefaultSourceManager; + /// + /// Library::from_dir( + /// "~/masm/std", + /// LibraryNamespace::new("std").unwrap(), + /// Assembler::new(Arc::new(DefaultSourceManager::default())), + /// ); + /// ``` + /// + /// Here's how we would handle various files under this path: + /// + /// - ~/masm/std/sys.masm -> Parsed as "std::sys" + /// - ~/masm/std/crypto/hash.masm -> Parsed as "std::crypto::hash" + /// - ~/masm/std/math/u32.masm -> Parsed as "std::math::u32" + /// - ~/masm/std/math/u64.masm -> Parsed as "std::math::u64" + /// - ~/masm/std/math/README.md -> Ignored + pub fn from_dir( + path: impl AsRef, + namespace: LibraryNamespace, + assembler: Assembler, + ) -> Result { + let path = path.as_ref(); + + let modules = + crate::parser::read_modules_from_dir(namespace, path, &assembler.source_manager())?; + assembler.assemble_library(modules) + } + + pub fn deserialize_from_file(path: impl AsRef) -> Result { + let path = path.as_ref(); + let mut file = fs::File::open(path).map_err(|err| { + DeserializationError::InvalidValue(format!( + "failed to open file at {}: {err}", + path.to_string_lossy() + )) + })?; + let mut adapter = ReadAdapter::new(&mut file); + + Self::read_from(&mut adapter) + } + } +} + +// KERNEL LIBRARY +// ================================================================================================ + +/// Represents a library containing a Miden VM kernel. +/// +/// This differs from the regular [Library] as follows: +/// - All exported procedures must be exported directly from the kernel namespace (i.e., `#sys`). +/// - There must be at least one exported procedure. +/// - The number of exported procedures cannot exceed [Kernel::MAX_NUM_PROCEDURES] (i.e., 256). +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct KernelLibrary { + kernel: Kernel, + kernel_info: ModuleInfo, + library: Library, +} + +impl AsRef for KernelLibrary { + #[inline(always)] + fn as_ref(&self) -> &Library { + &self.library + } +} + +impl KernelLibrary { + /// Returns the [Kernel] for this kernel library. + pub fn kernel(&self) -> &Kernel { + &self.kernel + } + + /// Returns a reference to the inner [`MastForest`]. + pub fn mast_forest(&self) -> &Arc { + self.library.mast_forest() + } + + /// Destructures this kernel library into individual parts. + pub fn into_parts(self) -> (Kernel, ModuleInfo, Arc) { + (self.kernel, self.kernel_info, self.library.mast_forest) } +} + +impl TryFrom for KernelLibrary { + type Error = LibraryError; + + fn try_from(library: Library) -> Result { + if library.exports.is_empty() { + return Err(LibraryError::EmptyKernel); + } + + let kernel_path = LibraryPath::from(LibraryNamespace::Kernel); + let mut proc_digests = Vec::with_capacity(library.exports.len()); + + let mut kernel_module = ModuleInfo::new(kernel_path.clone()); + + for (proc_path, &proc_node_id) in library.exports.iter() { + // make sure all procedures are exported only from the kernel root + if proc_path.module != kernel_path { + return Err(LibraryError::InvalidKernelExport { + procedure_path: proc_path.clone(), + }); + } + + let proc_digest = library.mast_forest[proc_node_id].digest(); + proc_digests.push(proc_digest); + kernel_module.add_procedure(proc_path.name.clone(), proc_digest); + } + + let kernel = Kernel::new(&proc_digests)?; + + Ok(Self { + kernel, + kernel_info: kernel_module, + library, + }) + } +} + +impl Serializable for KernelLibrary { + fn write_into(&self, target: &mut W) { + let Self { kernel: _, kernel_info: _, library } = self; + + library.write_into(target); + } +} + +impl Deserializable for KernelLibrary { + fn read_from(source: &mut R) -> Result { + let library = Library::read_from(source)?; + + Self::try_from(library).map_err(|err| { + DeserializationError::InvalidValue(format!( + "Failed to deserialize kernel library: {err}" + )) + }) + } +} + +#[cfg(feature = "std")] +mod use_std_kernel { + use std::{io, path::Path}; + + use super::*; + use crate::{diagnostics::Report, Assembler}; + + impl KernelLibrary { + /// Write the library to a target file + pub fn write_to_file(&self, path: impl AsRef) -> io::Result<()> { + self.library.write_to_file(path) + } + + /// Create a [KernelLibrary] from a standard Miden Assembly kernel project layout. + /// + /// The kernel library will export procedures defined by the module at `sys_module_path`. + /// If the optional `lib_dir` is provided, all modules under this directory will be + /// available from the kernel module under the `kernel` namespace. For example, if + /// `lib_dir` is set to "~/masm/lib", the files will be accessible in the kernel module as + /// follows: + /// + /// - ~/masm/lib/foo.masm -> Can be imported as "kernel::foo" + /// - ~/masm/lib/bar/baz.masm -> Can be imported as "kernel::bar::baz" + /// + /// Note: this is a temporary structure which will likely change once + /// is implemented. + pub fn from_dir( + sys_module_path: impl AsRef, + lib_dir: Option>, + mut assembler: Assembler, + ) -> Result { + // if library directory is provided, add modules from this directory to the assembler + if let Some(lib_dir) = lib_dir { + let lib_dir = lib_dir.as_ref(); + let namespace = LibraryNamespace::new("kernel").expect("invalid namespace"); + assembler.add_modules_from_dir(namespace, lib_dir)?; + } - fn get_module(&self, path: &LibraryPath) -> Option<&ast::Module> { - T::get_module(self, path) + assembler.assemble_kernel(sys_module_path.as_ref()) + } } } diff --git a/assembly/src/library/module.rs b/assembly/src/library/module.rs new file mode 100644 index 0000000000..ed010d0e2b --- /dev/null +++ b/assembly/src/library/module.rs @@ -0,0 +1,75 @@ +use alloc::vec::Vec; + +use super::LibraryPath; +use crate::{ + ast::{ProcedureIndex, ProcedureName}, + RpoDigest, +}; + +// MODULE INFO +// ================================================================================================ + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ModuleInfo { + path: LibraryPath, + procedures: Vec, +} + +impl ModuleInfo { + /// Returns a new [`ModuleInfo`] instantiated library path. + pub fn new(path: LibraryPath) -> Self { + Self { path, procedures: Vec::new() } + } + + /// Adds a procedure to the module. + pub fn add_procedure(&mut self, name: ProcedureName, digest: RpoDigest) { + self.procedures.push(ProcedureInfo { name, digest }); + } + + /// Returns the module's library path. + pub fn path(&self) -> &LibraryPath { + &self.path + } + + /// Returns the number of procedures in the module. + pub fn num_procedures(&self) -> usize { + self.procedures.len() + } + + /// Returns the [`ProcedureInfo`] of the procedure at the provided index, if any. + pub fn get_procedure_by_index(&self, index: ProcedureIndex) -> Option<&ProcedureInfo> { + self.procedures.get(index.as_usize()) + } + + /// Returns the digest of the procedure with the provided name, if any. + pub fn get_procedure_digest_by_name(&self, name: &ProcedureName) -> Option { + self.procedures.iter().find_map(|proc_info| { + if &proc_info.name == name { + Some(proc_info.digest) + } else { + None + } + }) + } + + /// Returns an iterator over the procedure infos in the module with their corresponding + /// procedure index in the module. + pub fn procedures(&self) -> impl Iterator { + self.procedures + .iter() + .enumerate() + .map(|(idx, proc)| (ProcedureIndex::new(idx), proc)) + } + + /// Returns an iterator over the MAST roots of procedures defined in this module. + pub fn procedure_digests(&self) -> impl Iterator + '_ { + self.procedures.iter().map(|p| p.digest) + } +} + +/// Stores the name and digest of a procedure. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ProcedureInfo { + pub name: ProcedureName, + pub digest: RpoDigest, +} diff --git a/assembly/src/library/namespace.rs b/assembly/src/library/namespace.rs index 07c1d0d2e3..4a0510618c 100644 --- a/assembly/src/library/namespace.rs +++ b/assembly/src/library/namespace.rs @@ -9,6 +9,9 @@ use crate::{ DeserializationError, LibraryPath, Serializable, Span, }; +// LIBRARY NAMESPACE +// ================================================================================================ + /// Represents an error when parsing or validating a library namespace #[derive(Debug, thiserror::Error, Diagnostic, PartialEq, Eq)] pub enum LibraryNamespaceError { @@ -43,6 +46,8 @@ pub enum LibraryNamespace { User(Arc), } +// ------------------------------------------------------------------------------------------------ +/// Constants impl LibraryNamespace { /// Namespaces must be 255 bytes or less pub const MAX_LENGTH: usize = u8::MAX as usize; @@ -55,7 +60,11 @@ impl LibraryNamespace { /// Path for a module without library path. pub const ANON_PATH: &'static str = "#anon"; +} +// ------------------------------------------------------------------------------------------------ +/// Constructors +impl LibraryNamespace { /// Construct a new [LibraryNamespace] from `source` pub fn new(source: S) -> Result where @@ -64,6 +73,18 @@ impl LibraryNamespace { source.as_ref().parse() } + /// Construct a new [LibraryNamespace] from a previously-validated [Ident]. + /// + /// NOTE: The caller must ensure that the given identifier is a valid namespace name. + pub fn from_ident_unchecked(name: Ident) -> Self { + match name.as_str() { + Self::KERNEL_PATH => Self::Kernel, + Self::EXEC_PATH => Self::Exec, + Self::ANON_PATH => Self::Anon, + _ => Self::User(name.into_inner()), + } + } + /// Parse a [LibraryNamespace] by taking the prefix of the given path string, and returning /// the namespace and remaining string if successful. pub fn strip_path_prefix(path: &str) -> Result<(Self, &str), LibraryNamespaceError> { @@ -72,36 +93,12 @@ impl LibraryNamespace { None => path.parse().map(|ns| (ns, "")), } } +} - /// Get the string representation of this namespace - pub fn as_str(&self) -> &str { - match self { - Self::Kernel => Self::KERNEL_PATH, - Self::Exec => Self::EXEC_PATH, - Self::Anon => Self::ANON_PATH, - Self::User(ref path) => path, - } - } - - /// Get an [`Arc`] representing this namespace - pub fn as_refcounted_str(&self) -> Arc { - match self { - Self::User(ref path) => path.clone(), - other => Arc::from(other.as_str().to_string().into_boxed_str()), - } - } - - /// Create a [LibraryPath] representing this [LibraryNamespace] - pub fn to_path(&self) -> LibraryPath { - LibraryPath::from(self.clone()) - } - - /// Create an [Ident] representing this namespace - pub fn to_ident(&self) -> Ident { - Ident::new_unchecked(Span::unknown(self.as_refcounted_str())) - } - - /// Returns true if this namespace is a reserved namespace +// ------------------------------------------------------------------------------------------------ +/// Public accessors +impl LibraryNamespace { + /// Returns true if this namespace is a reserved namespace. pub fn is_reserved(&self) -> bool { !matches!(self, Self::User(_)) } @@ -128,6 +125,38 @@ impl LibraryNamespace { } } +// ------------------------------------------------------------------------------------------------ +/// Conversions +impl LibraryNamespace { + /// Get the string representation of this namespace. + pub fn as_str(&self) -> &str { + match self { + Self::Kernel => Self::KERNEL_PATH, + Self::Exec => Self::EXEC_PATH, + Self::Anon => Self::ANON_PATH, + Self::User(ref path) => path, + } + } + + /// Get an [`Arc`] representing this namespace. + pub fn as_refcounted_str(&self) -> Arc { + match self { + Self::User(ref path) => path.clone(), + other => Arc::from(other.as_str().to_string().into_boxed_str()), + } + } + + /// Create a [LibraryPath] representing this [LibraryNamespace]. + pub fn to_path(&self) -> LibraryPath { + LibraryPath::from(self.clone()) + } + + /// Create an [Ident] representing this namespace. + pub fn to_ident(&self) -> Ident { + Ident::new_unchecked(Span::unknown(self.as_refcounted_str())) + } +} + impl core::ops::Deref for LibraryNamespace { type Target = str; @@ -159,7 +188,7 @@ impl FromStr for LibraryNamespace { other => { Self::validate(other)?; Ok(Self::User(Arc::from(other.to_string().into_boxed_str()))) - } + }, } } } @@ -176,6 +205,9 @@ impl TryFrom for LibraryNamespace { } } +// SERIALIZATION / DESERIALIZATION +// ------------------------------------------------------------------------------------------------ + impl Serializable for LibraryNamespace { fn write_into(&self, target: &mut W) { // Catch any situations where a namespace was incorrectly constructed diff --git a/assembly/src/library/path.rs b/assembly/src/library/path.rs index 814d7cf54f..a77ea1b287 100644 --- a/assembly/src/library/path.rs +++ b/assembly/src/library/path.rs @@ -1,8 +1,3 @@ -use crate::{ - ast::{Ident, IdentError}, - ByteReader, ByteWriter, Deserializable, DeserializationError, LibraryNamespace, Serializable, - Span, -}; use alloc::{ borrow::Cow, string::{String, ToString}, @@ -13,8 +8,15 @@ use core::{ fmt, str::{self, FromStr}, }; + use smallvec::smallvec; +use crate::{ + ast::{Ident, IdentError}, + ByteReader, ByteWriter, Deserializable, DeserializationError, LibraryNamespace, Serializable, + Span, +}; + /// Represents errors that can occur when creating, parsing, or manipulating [LibraryPath]s #[derive(Debug, PartialEq, Eq, thiserror::Error)] pub enum PathError { @@ -43,6 +45,26 @@ pub enum LibraryPathComponent<'a> { Normal(&'a Ident), } +impl<'a> LibraryPathComponent<'a> { + /// Get this component as a [prim@str] + #[inline(always)] + pub fn as_str(&self) -> &'a str { + match self { + Self::Namespace(ns) => ns.as_str(), + Self::Normal(id) => id.as_str(), + } + } + + /// Get this component as an [Ident] + #[inline] + pub fn to_ident(&self) -> Ident { + match self { + Self::Namespace(ns) => ns.to_ident(), + Self::Normal(id) => Ident::clone(id), + } + } +} + impl<'a> Eq for LibraryPathComponent<'a> {} impl<'a> PartialEq for LibraryPathComponent<'a> { @@ -76,6 +98,13 @@ impl<'a> fmt::Display for LibraryPathComponent<'a> { } } +impl From> for Ident { + #[inline] + fn from(component: LibraryPathComponent<'_>) -> Self { + component.to_ident() + } +} + /// This is a convenience type alias for a smallvec of [Ident] type Components = smallvec::SmallVec<[Ident; 1]>; @@ -182,13 +211,18 @@ impl LibraryPath { &self.inner.ns } - /// Returns the last component of the path. + /// Returns the last component of the path as a `str` pub fn last(&self) -> &str { + self.last_component().as_str() + } + + /// Returns the last component of the path. + pub fn last_component(&self) -> LibraryPathComponent<'_> { self.inner .components .last() - .map(|component| component.as_str()) - .unwrap_or_else(|| self.inner.ns.as_str()) + .map(LibraryPathComponent::Normal) + .unwrap_or_else(|| LibraryPathComponent::Namespace(&self.inner.ns)) } /// Returns the number of components in the path. @@ -234,7 +268,7 @@ impl LibraryPath { if a != b { break false; } - } + }, } } } @@ -344,7 +378,7 @@ impl LibraryPath { let mut components = self.inner.components.clone(); components.pop(); Some(Self::make(ns, components)) - } + }, } } @@ -403,7 +437,7 @@ impl<'a> TryFrom>> for LibraryPath { LibraryPathComponent::Normal(ident) => components.push(ident.clone()), LibraryPathComponent::Namespace(LibraryNamespace::User(name)) => { components.push(Ident::new_unchecked(Span::unknown(name.clone()))); - } + }, LibraryPathComponent::Namespace(_) => return Err(PathError::UnsupportedJoin), } } @@ -501,9 +535,10 @@ fn validate_component(component: &str) -> Result<(), PathError> { /// Tests #[cfg(test)] mod tests { - use super::{super::LibraryNamespaceError, IdentError, LibraryPath, PathError}; use vm_core::assert_matches; + use super::{super::LibraryNamespaceError, IdentError, LibraryPath, PathError}; + #[test] fn new_path() { let path = LibraryPath::new("foo").unwrap(); diff --git a/assembly/src/library/tests.rs b/assembly/src/library/tests.rs index 476f96f6c6..adadc53029 100644 --- a/assembly/src/library/tests.rs +++ b/assembly/src/library/tests.rs @@ -1,25 +1,179 @@ -use alloc::{string::ToString, sync::Arc, vec::Vec}; +use alloc::string::ToString; +use core::str::FromStr; -use vm_core::utils::{Deserializable, Serializable, SliceReader}; - -use super::{Library, LibraryNamespace, LibraryPath, MaslLibrary, Version}; +use super::*; use crate::{ - ast::{AstSerdeOptions, Module, ModuleKind}, - diagnostics::{IntoDiagnostic, Report, SourceFile}, + ast::{Module, ModuleKind, ProcedureName}, + diagnostics::{IntoDiagnostic, Report}, testing::TestContext, + Assembler, Deserializable, }; macro_rules! parse_module { - ($path:literal, $source:expr) => {{ + ($context:expr, $path:literal, $source:expr) => {{ let path = LibraryPath::new($path).into_diagnostic()?; - let source_file = Arc::from(SourceFile::new(concat!("test", line!()), $source.to_string())); + let source_file = + $context.source_manager().load(concat!("test", line!()), $source.to_string()); Module::parse(path, ModuleKind::Library, source_file)? }}; } +// TESTS +// ================================================================================================ + #[test] -fn masl_locations_serialization() -> Result<(), Report> { - let _context = TestContext::new(); +fn library_exports() -> Result<(), Report> { + let context = TestContext::new(); + + // build the first library + let baz = r#" + export.baz1 + push.7 push.8 sub + end + "#; + let baz = parse_module!(&context, "lib1::baz", baz); + + let lib1 = Assembler::new(context.source_manager()).assemble_library([baz])?; + + // build the second library + let foo = r#" + proc.foo1 + push.1 add + end + + export.foo2 + push.2 add + exec.foo1 + end + + export.foo3 + push.3 mul + exec.foo1 + exec.foo2 + end + "#; + let foo = parse_module!(&context, "lib2::foo", foo); + + // declare bar module + let bar = r#" + use.lib1::baz + use.lib2::foo + + export.baz::baz1->bar1 + + export.foo::foo2->bar2 + + export.bar3 + exec.foo::foo2 + end + + proc.bar4 + push.1 push.2 mul + end + + export.bar5 + push.3 sub + exec.foo::foo2 + exec.bar1 + exec.bar2 + exec.bar4 + end + "#; + let bar = parse_module!(&context, "lib2::bar", bar); + let lib2_modules = [foo, bar]; + + let lib2 = Assembler::new(context.source_manager()) + .with_library(lib1)? + .assemble_library(lib2_modules.iter().cloned())?; + + let foo2 = QualifiedProcedureName::from_str("lib2::foo::foo2").unwrap(); + let foo3 = QualifiedProcedureName::from_str("lib2::foo::foo3").unwrap(); + let bar1 = QualifiedProcedureName::from_str("lib2::bar::bar1").unwrap(); + let bar2 = QualifiedProcedureName::from_str("lib2::bar::bar2").unwrap(); + let bar3 = QualifiedProcedureName::from_str("lib2::bar::bar3").unwrap(); + let bar5 = QualifiedProcedureName::from_str("lib2::bar::bar5").unwrap(); + + // make sure the library exports all exported procedures + let expected_exports: BTreeSet<_> = [&foo2, &foo3, &bar1, &bar2, &bar3, &bar5].into(); + let actual_exports: BTreeSet<_> = lib2.exports().collect(); + assert_eq!(expected_exports, actual_exports); + + // make sure foo2, bar2, and bar3 map to the same MastNode + assert_eq!(lib2.get_export_node_id(&foo2), lib2.get_export_node_id(&bar2)); + assert_eq!(lib2.get_export_node_id(&foo2), lib2.get_export_node_id(&bar3)); + + // make sure there are 6 roots in the MAST (foo1, foo2, foo3, bar1, bar4, and bar5) + assert_eq!(lib2.mast_forest.num_procedures(), 6); + + // bar1 should be the only re-export (i.e. the only procedure re-exported from a dependency) + assert!(!lib2.is_reexport(&foo2)); + assert!(!lib2.is_reexport(&foo3)); + assert!(lib2.is_reexport(&bar1)); + assert!(!lib2.is_reexport(&bar2)); + assert!(!lib2.is_reexport(&bar3)); + assert!(!lib2.is_reexport(&bar5)); + + Ok(()) +} + +#[test] +fn library_procedure_collision() -> Result<(), Report> { + let context = TestContext::new(); + + // build the first library + let foo = r#" + export.foo1 + push.1 + if.true + push.1 push.2 add + else + push.1 push.2 mul + end + end + "#; + let foo = parse_module!(&context, "lib1::foo", foo); + let lib1 = Assembler::new(context.source_manager()).assemble_library([foo])?; + + // build the second library which defines the same procedure as the first one + let bar = r#" + use.lib1::foo + + export.foo::foo1->bar1 + + export.bar2 + push.1 + if.true + push.1 push.2 add + else + push.1 push.2 mul + end + end + "#; + let bar = parse_module!(&context, "lib2::bar", bar); + let lib2 = Assembler::new(context.source_manager()) + .with_library(lib1)? + .assemble_library([bar])?; + + // make sure lib2 has the expected exports (i.e., bar1 and bar2) + assert_eq!(lib2.num_exports(), 2); + + // make sure that bar1 and bar2 are equal nodes in the MAST forest + let lib2_bar_bar1 = QualifiedProcedureName::from_str("lib2::bar::bar1").unwrap(); + let lib2_bar_bar2 = QualifiedProcedureName::from_str("lib2::bar::bar2").unwrap(); + assert_eq!(lib2.get_export_node_id(&lib2_bar_bar1), lib2.get_export_node_id(&lib2_bar_bar2)); + + // make sure only one node was added to the forest + // NOTE: the MAST forest should actually have only 1 node (external node for the re-exported + // procedure), because nodes for the local procedure nodes should be pruned from the forest, + // but this is not implemented yet + assert_eq!(lib2.mast_forest().num_nodes(), 5); + + Ok(()) +} + +#[test] +fn library_serialization() -> Result<(), Report> { + let context = TestContext::new(); // declare foo module let foo = r#" export.foo @@ -29,7 +183,7 @@ fn masl_locations_serialization() -> Result<(), Report> { mul end "#; - let foo = parse_module!("test::foo", foo); + let foo = parse_module!(&context, "test::foo", foo); // declare bar module let bar = r#" @@ -40,27 +194,15 @@ fn masl_locations_serialization() -> Result<(), Report> { mul end "#; - let bar = parse_module!("test::bar", bar); - let modules = vec![foo, bar]; + let bar = parse_module!(&context, "test::bar", bar); + let modules = [foo, bar]; // serialize/deserialize the bundle with locations - let namespace = LibraryNamespace::new("test").unwrap(); - let version = Version::min(); - let bundle = MaslLibrary::new(namespace, version, modules.iter().cloned(), Vec::new())?; - - let mut bytes = Vec::new(); - bundle.write_into(&mut bytes); - let deserialized = MaslLibrary::read_from(&mut SliceReader::new(&bytes)).unwrap(); - assert_eq!(bundle, deserialized); - - // serialize/deserialize the bundle without locations - let namespace = LibraryNamespace::new("test").unwrap(); - let bundle = MaslLibrary::new(namespace, version, modules, Vec::new())?; + let bundle = + Assembler::new(context.source_manager()).assemble_library(modules.iter().cloned())?; - // serialize/deserialize the bundle - let mut bytes = Vec::new(); - bundle.write_into_with_options(&mut bytes, AstSerdeOptions::new(true, false)); - let deserialized = MaslLibrary::read_from(&mut SliceReader::new(&bytes)).unwrap(); + let bytes = bundle.to_bytes(); + let deserialized = Library::read_from_bytes(&bytes).unwrap(); assert_eq!(bundle, deserialized); Ok(()) @@ -68,31 +210,26 @@ fn masl_locations_serialization() -> Result<(), Report> { #[test] fn get_module_by_path() -> Result<(), Report> { - let _context = TestContext::new(); + let context = TestContext::new(); // declare foo module let foo_source = r#" export.foo add end "#; - let foo = parse_module!("test::foo", foo_source); - let modules = vec![foo]; + let foo = parse_module!(&context, "test::foo", foo_source); + let modules = [foo]; // create the bundle with locations - let namespace = LibraryNamespace::new("test")?; - let version = Version::min(); - let bundle = MaslLibrary::new(namespace, version, modules, Vec::new())?; - - // get AST associated with "test::foo" path - let foo_ast = bundle.get_module(&LibraryPath::new("test::foo").unwrap()).unwrap(); - let foo_expected = "export.foo - add -end + let bundle = Assembler::new(context.source_manager()) + .assemble_library(modules.iter().cloned()) + .unwrap(); -"; - assert_eq!(foo_ast.to_string(), foo_expected); + let foo_module_info = bundle.module_infos().next().unwrap(); + assert_eq!(foo_module_info.path(), &LibraryPath::new("test::foo").unwrap()); - assert!(bundle.get_module(&LibraryPath::new("test::bar").unwrap()).is_none()); + let (_, foo_proc) = foo_module_info.procedures().next().unwrap(); + assert_eq!(foo_proc.name, ProcedureName::new("foo").unwrap()); Ok(()) } diff --git a/assembly/src/library/version.rs b/assembly/src/library/version.rs index 6f7cf5744f..6f361b0c49 100644 --- a/assembly/src/library/version.rs +++ b/assembly/src/library/version.rs @@ -24,11 +24,7 @@ impl Version { /// Returns the current minimal version supported by the code in this crate. #[inline(always)] pub const fn min() -> Self { - Self { - major: 0, - minor: 1, - patch: 0, - } + Self { major: 0, minor: 1, patch: 0 } } } @@ -38,11 +34,7 @@ impl Version { /// /// This is useful for comparing two versions at major version granularity pub const fn to_nearest_major(self) -> Self { - Self { - minor: 0, - patch: 0, - ..self - } + Self { minor: 0, patch: 0, ..self } } /// Returns a new [Version] clamped to the minor version @@ -63,19 +55,12 @@ impl Version { /// Return a new [Version] representing the next minor version release pub const fn next_minor(self) -> Self { - Self { - minor: self.minor + 1, - patch: 0, - ..self - } + Self { minor: self.minor + 1, patch: 0, ..self } } /// Return a new [Version] representing the next patch release pub const fn next_patch(self) -> Self { - Self { - patch: self.patch + 1, - ..self - } + Self { patch: self.patch + 1, ..self } } } @@ -101,11 +86,7 @@ impl Deserializable for Version { let major = source.read_u16()?; let minor = source.read_u16()?; let patch = source.read_u16()?; - Ok(Self { - major, - minor, - patch, - }) + Ok(Self { major, minor, patch }) } } @@ -163,11 +144,7 @@ impl FromStr for Version { if components.next().is_some() { Err(VersionError::Unsupported) } else { - Ok(Self { - major, - minor, - patch, - }) + Ok(Self { major, minor, patch }) } } } diff --git a/assembly/src/parser/error.rs b/assembly/src/parser/error.rs index 9c8c3ec9fe..4b0fd1bf2b 100644 --- a/assembly/src/parser/error.rs +++ b/assembly/src/parser/error.rs @@ -5,7 +5,7 @@ use alloc::{ use core::{fmt, ops::Range}; use super::{ParseError, SourceSpan}; -use crate::diagnostics::Diagnostic; +use crate::{diagnostics::Diagnostic, SourceId}; // LITERAL ERROR KIND // ================================================================================================ @@ -33,7 +33,7 @@ impl fmt::Display for LiteralErrorKind { Self::FeltOverflow => f.write_str("value overflowed the field modulus"), Self::InvalidBitSize => { f.write_str("expected value to be a valid bit size, e.g. 0..63") - } + }, } } } @@ -59,7 +59,7 @@ impl fmt::Display for HexErrorKind { match self { Self::MissingDigits => { f.write_str("expected number of hex digits to be a multiple of 2") - } + }, Self::Invalid => f.write_str("expected 2, 4, 8, 16, or 64 hex digits"), Self::Overflow => f.write_str("value overflowed the field modulus"), Self::TooLong => f.write_str( @@ -69,6 +69,25 @@ impl fmt::Display for HexErrorKind { } } +// BINARY ERROR KIND +// ================================================================================================ + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum BinErrorKind { + /// Occurs when the bin-encoded value is > 32 digits + TooLong, +} + +impl fmt::Display for BinErrorKind { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::TooLong => f.write_str( + "value has too many digits, binary string can contain no more than 32 digits", + ), + } + } +} + // PARSING ERROR // ================================================================================================ @@ -162,6 +181,13 @@ pub enum ParsingError { span: SourceSpan, kind: HexErrorKind, }, + #[error("invalid literal: {}", kind)] + #[diagnostic()] + InvalidBinaryLiteral { + #[label] + span: SourceSpan, + kind: BinErrorKind, + }, #[error("invalid MAST root literal")] InvalidMastRoot { #[label] @@ -203,6 +229,31 @@ pub enum ParsingError { #[label] span: SourceSpan, }, + #[error("re-exporting a procedure identified by digest requires giving it a name, e.g. `export.DIGEST->foo`")] + UnnamedReexportOfMastRoot { + #[label] + span: SourceSpan, + }, + #[error("conflicting attributes for procedure definition")] + #[diagnostic()] + AttributeConflict { + #[label( + "conflict occurs because an attribute with the same name has already been defined" + )] + span: SourceSpan, + #[label("previously defined here")] + prev: SourceSpan, + }, + #[error("conflicting key-value attributes for procedure definition")] + #[diagnostic()] + AttributeKeyValueConflict { + #[label( + "conflict occurs because a key with the same name has already been set in a previous declaration" + )] + span: SourceSpan, + #[label("previously defined here")] + prev: SourceSpan, + }, } impl ParsingError { @@ -226,7 +277,7 @@ impl PartialEq for ParsingError { (Self::InvalidLiteral { kind: l, .. }, Self::InvalidLiteral { kind: r, .. }) => l == r, (Self::InvalidHexLiteral { kind: l, .. }, Self::InvalidHexLiteral { kind: r, .. }) => { l == r - } + }, ( Self::InvalidLibraryPath { message: l, .. }, Self::InvalidLibraryPath { message: r, .. }, @@ -237,78 +288,58 @@ impl PartialEq for ParsingError { ) => l == r, (Self::PushOverflow { count: l, .. }, Self::PushOverflow { count: r, .. }) => l == r, ( - Self::UnrecognizedToken { - token: ltok, - expected: lexpect, - .. - }, - Self::UnrecognizedToken { - token: rtok, - expected: rexpect, - .. - }, + Self::UnrecognizedToken { token: ltok, expected: lexpect, .. }, + Self::UnrecognizedToken { token: rtok, expected: rexpect, .. }, ) => ltok == rtok && lexpect == rexpect, (Self::ExtraToken { token: ltok, .. }, Self::ExtraToken { token: rtok, .. }) => { ltok == rtok - } + }, ( - Self::UnrecognizedEof { - expected: lexpect, .. - }, - Self::UnrecognizedEof { - expected: rexpect, .. - }, + Self::UnrecognizedEof { expected: lexpect, .. }, + Self::UnrecognizedEof { expected: rexpect, .. }, ) => lexpect == rexpect, (x, y) => x.tag() == y.tag(), } } } -impl From for ParsingError { - fn from(err: core::str::Utf8Error) -> Self { +impl ParsingError { + pub fn from_utf8_error(source_id: SourceId, err: core::str::Utf8Error) -> Self { let start = u32::try_from(err.valid_up_to()).ok().unwrap_or(u32::MAX); match err.error_len() { - None => Self::IncompleteUtf8 { - span: SourceSpan::at(start), - }, + None => Self::IncompleteUtf8 { span: SourceSpan::at(source_id, start) }, Some(len) => Self::InvalidUtf8 { - span: SourceSpan::new(start..(start + len as u32)), + span: SourceSpan::new(source_id, start..(start + len as u32)), }, } } -} -impl<'a> From> for ParsingError { - fn from(err: ParseError) -> Self { + pub fn from_parse_error(source_id: SourceId, err: ParseError<'_>) -> Self { use super::Token; + match err { - ParseError::InvalidToken { location: at } => Self::InvalidToken { - span: SourceSpan::from(at..at), + ParseError::InvalidToken { location: at } => { + Self::InvalidToken { span: SourceSpan::at(source_id, at) } }, - ParseError::UnrecognizedToken { - token: (l, Token::Eof, r), - expected, - } => Self::UnrecognizedEof { - span: SourceSpan::from(l..r), - expected: simplify_expected_tokens(expected), + ParseError::UnrecognizedToken { token: (l, Token::Eof, r), expected } => { + Self::UnrecognizedEof { + span: SourceSpan::new(source_id, l..r), + expected: simplify_expected_tokens(expected), + } }, - ParseError::UnrecognizedToken { - token: (l, tok, r), - expected, - } => Self::UnrecognizedToken { - span: SourceSpan::from(l..r), - token: tok.to_string(), - expected: simplify_expected_tokens(expected), + ParseError::UnrecognizedToken { token: (l, tok, r), expected } => { + Self::UnrecognizedToken { + span: SourceSpan::new(source_id, l..r), + token: tok.to_string(), + expected: simplify_expected_tokens(expected), + } }, ParseError::ExtraToken { token: (l, tok, r) } => Self::ExtraToken { - span: SourceSpan::from(l..r), + span: SourceSpan::new(source_id, l..r), token: tok.to_string(), }, - ParseError::UnrecognizedEof { - location: at, - expected, - } => Self::UnrecognizedEof { - span: SourceSpan::from(at..at), + ParseError::UnrecognizedEof { location: at, expected } => Self::UnrecognizedEof { + span: SourceSpan::new(source_id, at..at), expected: simplify_expected_tokens(expected), }, ParseError::User { error } => error, @@ -335,6 +366,7 @@ fn simplify_expected_tokens(expected: Vec) -> Vec { "quoted_ident" => return Some("quoted identifier".to_string()), "doc_comment" => return Some("doc comment".to_string()), "hex_value" => return Some("hex-encoded literal".to_string()), + "bin_value" => return Some("bin-encoded literal".to_string()), "uint" => return Some("integer literal".to_string()), "EOF" => return Some("end of file".to_string()), other => other[1..].strip_suffix('"').and_then(|t| Token::parse(t).ok()), @@ -347,7 +379,7 @@ fn simplify_expected_tokens(expected: Vec) -> Vec { } else { None } - } + }, Some(tok) if tok.is_instruction() => { if !has_instruction { has_instruction = true; @@ -355,7 +387,7 @@ fn simplify_expected_tokens(expected: Vec) -> Vec { } else { None } - } + }, _ => Some(t), } }) diff --git a/assembly/src/parser/grammar.lalrpop b/assembly/src/parser/grammar.lalrpop index e1db565c87..65b78ae427 100644 --- a/assembly/src/parser/grammar.lalrpop +++ b/assembly/src/parser/grammar.lalrpop @@ -1,6 +1,6 @@ use alloc::{ boxed::Box, - collections::{VecDeque, BTreeSet}, + collections::{VecDeque, BTreeSet, BTreeMap}, string::ToString, sync::Arc, vec::Vec, @@ -12,7 +12,7 @@ use vm_core::{Felt, FieldElement, StarkField, crypto::hash::RpoDigest}; use crate::{LibraryPath, LibraryNamespace, ast::*, diagnostics::SourceFile, SourceSpan}; use super::{ - HexEncodedValue, Token, ParseError, ParsingError, + BinEncodedValue, HexEncodedValue, Token, ParseError, ParsingError, LiteralErrorKind, HexErrorKind, Span, Spanned, DocumentationType }; @@ -34,7 +34,9 @@ extern { bare_ident => Token::Ident(<&'input str>), const_ident => Token::ConstantIdent(<&'input str>), quoted_ident => Token::QuotedIdent(<&'input str>), + quoted_string => Token::QuotedString(<&'input str>), hex_value => Token::HexValue(), + bin_value => Token::BinValue(), doc_comment => Token::DocComment(), comment => Token::Comment, uint => Token::Int(), @@ -93,6 +95,7 @@ extern { "exp" => Token::Exp, "exp.u" => Token::ExpU, "export" => Token::Export, + "false" => Token::False, "fri_ext2fold4" => Token::FriExt2Fold4, "gt" => Token::Gt, "gte" => Token::Gte, @@ -129,6 +132,7 @@ extern { "neg" => Token::Neg, "neq" => Token::Neq, "not" => Token::Not, + "nop" => Token::Nop, "or" => Token::Or, "padw" => Token::Padw, "pow2" => Token::Pow2, @@ -189,22 +193,36 @@ extern { "u32xor" => Token::U32Xor, "while" => Token::While, "xor" => Token::Xor, + "@" => Token::At, "!" => Token::Bang, "::" => Token::ColonColon, "." => Token::Dot, + "," => Token::Comma, "=" => Token::Equal, "(" => Token::Lparen, + "[" => Token::Lbracket, "-" => Token::Minus, "+" => Token::Plus, "//" => Token::SlashSlash, "/" => Token::Slash, "*" => Token::Star, ")" => Token::Rparen, + "]" => Token::Rbracket, "->" => Token::Rstab, EOF => Token::Eof, } } + +// comma-delimited with at least one element +#[inline] +CommaDelimited: Vec = { + ",")*> => { + v.push(e); + v + } +}; + // dot-delimited with at least one element #[inline] DotDelimited: Vec = { @@ -242,11 +260,11 @@ Form: Form = { Doc: Form = { =>? { if doc.as_bytes().len() > u16::MAX as usize { - Err(ParseError::User { error: ParsingError::DocsTooLarge { span: span!(l, r) } }) + Err(ParseError::User { error: ParsingError::DocsTooLarge { span: span!(source_file.id(), l, r) } }) } else { match doc { - DocumentationType::Module(doc) => Ok(Form::ModuleDoc(Span::new(span!(l, r), doc))), - DocumentationType::Form(doc) => Ok(Form::Doc(Span::new(span!(l, r), doc))), + DocumentationType::Module(doc) => Ok(Form::ModuleDoc(Span::new(span!(source_file.id(), l, r), doc))), + DocumentationType::Form(doc) => Ok(Form::Doc(Span::new(span!(source_file.id(), l, r), doc))), } } } @@ -256,15 +274,15 @@ Import: Form = { "use" "." " )?> =>? { match import_path { (module_name, None) => Err(ParseError::User { - error: ParsingError::UnqualifiedImport { span: span!(l, r) }, + error: ParsingError::UnqualifiedImport { span: span!(source_file.id(), l, r) }, }), (module_name, Some(path)) => { let path = path.append_ident(module_name.clone()) .map_err(|error| { - ParseError::User { error: ParsingError::InvalidLibraryPath { span: span!(l, r), message: error.to_string() }} + ParseError::User { error: ParsingError::InvalidLibraryPath { span: span!(source_file.id(), l, r), message: error.to_string() }} })?; let name = alias.unwrap_or(module_name); - Ok(Form::Import(Import { span: span!(l, r), name, path, uses: 0 })) + Ok(Form::Import(Import { span: span!(source_file.id(), l, r), name, path, uses: 0 })) } } } @@ -273,7 +291,7 @@ Import: Form = { Const: Form = { "const" "." "=" => { Form::Constant(Constant::new( - span!(l, r), + span!(source_file.id(), l, r), name, value, )) @@ -282,35 +300,126 @@ Const: Form = { Begin: Form = { "begin" "end" => { - Form::Begin(Block::new(span!(l, r), body)) + Form::Begin(Block::new(span!(source_file.id(), l, r), body)) } } Proc: Form = { + =>? { + use alloc::collections::btree_map::Entry; + let attributes = proc.attributes_mut(); + for attr in annotations { + match attr { + Attribute::KeyValue(kv) => { + match attributes.entry(kv.id()) { + AttributeSetEntry::Vacant(entry) => { + entry.insert(Attribute::KeyValue(kv)); + } + AttributeSetEntry::Occupied(mut entry) => { + let value = entry.get_mut(); + match value { + Attribute::KeyValue(ref mut existing_kvs) => { + for (k, v) in kv.into_iter() { + let span = k.span(); + match existing_kvs.entry(k) { + Entry::Vacant(entry) => { + entry.insert(v); + } + Entry::Occupied(ref entry) => { + let prev = entry.get(); + return Err(ParseError::User { + error: ParsingError::AttributeKeyValueConflict { span, prev: prev.span() }, + }); + } + } + } + } + other => { + return Err(ParseError::User { + error: ParsingError::AttributeConflict { span: kv.span(), prev: other.span() }, + }); + } + } + } + } + } + attr => { + match attributes.entry(attr.id()) { + AttributeSetEntry::Vacant(entry) => { + entry.insert(attr); + } + AttributeSetEntry::Occupied(ref entry) => { + let prev_attr = entry.get(); + return Err(ParseError::User { + error: ParsingError::AttributeConflict { span: attr.span(), prev: prev_attr.span() }, + }); + } + } + } + } + } + Ok(Form::Procedure(Export::Procedure(proc))) + }, + AliasDef => Form::Procedure(Export::Alias(<>)), +} + +#[inline] +ProcedureDef: Procedure = { "." > "end" =>? { let num_locals = num_locals.unwrap_or(0); let procedure = Procedure::new( - span!(l, r), + span!(source_file.id(), l, r), visibility, name, num_locals, body - ).with_source_file(Some(source_file.clone())); - Ok(Form::Procedure(Export::Procedure(procedure))) + ); + Ok(procedure) }, +} - "export" "." " )?> => { - let (name, path) = qualified_name; - let name = ProcedureName::new_unchecked(name.clone()); - let export_name = alias.map(ProcedureName::new_unchecked).unwrap_or_else(|| name.clone()); - let target = FullyQualifiedProcedureName { - span: span!(l, r), - module: path, - name, +#[inline] +AliasDef: ProcedureAlias = { + "export" "." " )?> =>? { + let span = span!(source_file.id(), l, r); + let alias = match name { + InvocationTarget::ProcedureName(_) => return Err(ParseError::User { + error: ParsingError::UnqualifiedImport { span }, + }), + InvocationTarget::MastRoot(digest) => { + if alias.is_none() { + return Err(ParseError::User { + error: ParsingError::UnnamedReexportOfMastRoot { span }, + }); + } + ProcedureAlias::new(alias.unwrap(), AliasTarget::MastRoot(digest)) + } + InvocationTarget::ProcedurePath { name, module } => { + let export_name = alias.unwrap_or_else(|| name.clone()); + let module = match module.as_str() { + LibraryNamespace::KERNEL_PATH => LibraryPath::new_from_components(LibraryNamespace::Kernel, []), + LibraryNamespace::EXEC_PATH => LibraryPath::new_from_components(LibraryNamespace::Exec, []), + LibraryNamespace::ANON_PATH => LibraryPath::new_from_components(LibraryNamespace::Anon, []), + _ => LibraryPath::new_from_components(LibraryNamespace::User(module.into_inner()), []), + }; + let target = QualifiedProcedureName { + span, + module, + name, + }; + ProcedureAlias::new(export_name, AliasTarget::ProcedurePath(target)) + } + InvocationTarget::AbsoluteProcedurePath { name, path } => { + let export_name = alias.unwrap_or_else(|| name.clone()); + let target = QualifiedProcedureName { + span, + module: path, + name, + }; + ProcedureAlias::new(export_name, AliasTarget::AbsoluteProcedurePath(target)) + } }; - let alias = ProcedureAlias::new(export_name, target) - .with_source_file(Some(source_file.clone())); - Form::Procedure(Export::Alias(alias)) + Ok(alias) } } @@ -320,11 +429,73 @@ Visibility: Visibility = { "export" => Visibility::Public, } +// ANNOTATIONS +// ================================================================================================ + +Annotation: Attribute = { + "@" => attr.with_span(span!(source_file.id(), l, r)), +} + +#[inline] +Attribute: Attribute = { + "(" > ")" => { + Attribute::List(MetaList { span: span!(source_file.id(), l, r), name, items }) + }, + + "(" > ")" =>? { + use alloc::collections::btree_map::Entry; + + let mut map = BTreeMap::::default(); + for meta_kv in items { + let (span, (k, v)) = meta_kv.into_parts(); + match map.entry(k) { + Entry::Occupied(ref entry) => { + let prev = entry.key().span(); + return Err(ParseError::User { + error: ParsingError::AttributeKeyValueConflict { span, prev }, + }); + } + Entry::Vacant(entry) => { + entry.insert(v); + } + } + } + Ok(Attribute::KeyValue(MetaKeyValue { span: span!(source_file.id(), l, r), name, items: map })) + }, + + => Attribute::Marker(<>), +} + +MetaKeyValue: Span<(Ident, MetaExpr)> = { + "=" => { + let span = span!(source_file.id(), l, r); + Span::new(span, (key, value)) + } +} + +MetaExpr: MetaExpr = { + BareIdent => MetaExpr::Ident(<>), + QuotedString => MetaExpr::String(<>), + => MetaExpr::Int(Span::new(span!(source_file.id(), l, r), value)), +} + +#[inline] +QuotedString: Ident = { + => { + let value = interned.get(value).cloned().unwrap_or_else(|| { + let value = Arc::::from(value.to_string().into_boxed_str()); + interned.insert(value.clone()); + value + }); + Ident::new_unchecked(Span::new(span!(source_file.id(), l, r), value)) + } +} + // CODE BLOCKS // ================================================================================================ Block: Block = { - => Block::new(span!(l, r), body), + => Block::new(span!(source_file.id(), l, r), body), } #[inline] @@ -332,7 +503,7 @@ Ops: Vec = { =>? { let ops = ops.into_iter().flat_map(|ops| ops.into_iter()).collect::>(); if ops.len() > u16::MAX as usize { - Err(ParseError::User { error: ParsingError::CodeBlockTooBig { span: span!(l, r) } }) + Err(ParseError::User { error: ParsingError::CodeBlockTooBig { span: span!(source_file.id(), l, r) } }) } else { Ok(ops) } @@ -347,24 +518,56 @@ Op: SmallOpsVec = { } IfElse: Op = { - "if" "." "true" "else" "end" => { - Op::If { span: span!(l, r), then_blk, else_blk } + // Handles the edge case of a code generator emitting an empty "then" block + "if" "." "else" "end" => { + let span = span!(source_file.id(), l, r); + let then_blk = Block::new(span, vec![Op::Inst(Span::new(span, Instruction::Nop))]); + // If false-conditioned, swap the blocks + if cond { + Op::If { span, then_blk, else_blk } + } else { + Op::If { span, then_blk: else_blk, else_blk: then_blk } + } }, - "if" "." "true" "end" => { - Op::If { span: span!(l, r), then_blk, else_blk: Default::default() } + "if" "." "else" "end" => { + let span = span!(source_file.id(), l, r); + let else_blk = else_blk.unwrap_or_else(|| Block::new(span, vec![Op::Inst(Span::new(span, Instruction::Nop))])); + // If false-conditioned, swap the blocks + if cond { + Op::If { span, then_blk, else_blk } + } else { + Op::If { span, then_blk: else_blk, else_blk: then_blk } + } + }, + + "if" "." "end" => { + let span = span!(source_file.id(), l, r); + let else_blk = Block::new(span, vec![Op::Inst(Span::new(span, Instruction::Nop))]); + // If false-conditioned, swap the blocks + if cond { + Op::If { span, then_blk, else_blk } + } else { + Op::If { span, then_blk: else_blk, else_blk: then_blk } + } } } +#[inline] +Condition: bool = { + "true" => true, + "false" => false, +} + While: Op = { "while" "." "true" "end" => { - Op::While { span: span!(l, r), body } - } + Op::While { span: span!(source_file.id(), l, r), body } + }, } Repeat: Op = { "repeat" "." "end" =>? { - let span = span!(l, r); + let span = span!(source_file.id(), l, r); u32::try_from(count) .map_err(|error| ParseError::User { error: ParsingError::ImmediateOutOfRange { span, range: 1..(u32::MAX as usize) } }) .and_then(|count| { @@ -377,7 +580,7 @@ Repeat: Op = { #[inline] Instruction: SmallOpsVec = { - => smallvec![Op::Inst(Span::new(span!(l, r), inst))], + => smallvec![Op::Inst(Span::new(span!(source_file.id(), l, r), inst))], // For instructions which may fold to zero or multiple instructions; // or for instruction macros, which expand to multiple instructions, // this is the rule under which those instructions should be handled @@ -397,14 +600,14 @@ MacroInst: SmallOpsVec = { #[inline] Inst: Instruction = { AdviceInjector, - Assert, Call, Debug, - InstWithFeltImmediate, - InstWithU32Immediate, InstWithBitSizeImmediate, + InstWithErrorCode, + InstWithFeltImmediate, InstWithLocalIndex, InstWithStackIndex, + InstWithU32Immediate, ProcRef, "adv_pipe" => Instruction::AdvPipe, "adv_loadw" => Instruction::AdvLoadW, @@ -427,39 +630,26 @@ Inst: Instruction = { "ext2neg" => Instruction::Ext2Neg, "ext2sub" => Instruction::Ext2Sub, "fri_ext2fold4" => Instruction::FriExt2Fold4, - "gt" => Instruction::Gt, - "gte" => Instruction::Gte, "hash" => Instruction::Hash, "hperm" => Instruction::HPerm, "hmerge" => Instruction::HMerge, "ilog2" => Instruction::ILog2, "inv" => Instruction::Inv, "is_odd" => Instruction::IsOdd, - "lt" => Instruction::Lt, - "lte" => Instruction::Lte, "mem_stream" => Instruction::MemStream, "mtree_get" => Instruction::MTreeGet, "mtree_merge" => Instruction::MTreeMerge, "mtree_set" => Instruction::MTreeSet, - "mtree_verify" => Instruction::MTreeVerify, "neg" => Instruction::Neg, "not" => Instruction::Not, + "nop" => Instruction::Nop, "or" => Instruction::Or, "padw" => Instruction::PadW, "pow2" => Instruction::Pow2, "rcomb_base" => Instruction::RCombBase, "sdepth" => Instruction::Sdepth, "swapdw" => Instruction::SwapDw, - "u32and" => Instruction::U32And, "u32cast" => Instruction::U32Cast, - "u32gt" => Instruction::U32Gt, - "u32gte" => Instruction::U32Gte, - "u32lt" => Instruction::U32Lt, - "u32lte" => Instruction::U32Lte, - "u32max" => Instruction::U32Max, - "u32min" => Instruction::U32Min, - "u32not" => Instruction::U32Not, - "u32or" => Instruction::U32Or, "u32overflowing_add3" => Instruction::U32OverflowingAdd3, "u32overflowing_madd" => Instruction::U32OverflowingMadd, "u32popcnt" => Instruction::U32Popcnt, @@ -472,7 +662,6 @@ Inst: Instruction = { "u32testw" => Instruction::U32TestW, "u32wrapping_add3" => Instruction::U32WrappingAdd3, "u32wrapping_madd" => Instruction::U32WrappingMadd, - "u32xor" => Instruction::U32Xor, "xor" => Instruction::Xor, } @@ -502,7 +691,7 @@ AdviceInjector: Instruction = { } #[inline] -Assert: Instruction = { +InstWithErrorCode: Instruction = { "assert" => error_code.map(Instruction::AssertWithError).unwrap_or(Instruction::Assert), "assertz" => error_code.map(Instruction::AssertzWithError).unwrap_or(Instruction::Assertz), "assert_eq" => error_code.map(Instruction::AssertEqWithError).unwrap_or(Instruction::AssertEq), @@ -510,6 +699,7 @@ Assert: Instruction = { "u32assert" => error_code.map(Instruction::U32AssertWithError).unwrap_or(Instruction::U32Assert), "u32assert2" => error_code.map(Instruction::U32Assert2WithError).unwrap_or(Instruction::U32Assert2), "u32assertw" => error_code.map(Instruction::U32AssertWWithError).unwrap_or(Instruction::U32AssertW), + "mtree_verify" => error_code.map(Instruction::MTreeVerifyWithError).unwrap_or(Instruction::MTreeVerify), } MaybeAssertCode: Option> = { @@ -518,20 +708,9 @@ MaybeAssertCode: Option> = { } Call: Instruction = { - "exec" "." => Instruction::Exec(callee), - "call" "." => Instruction::Call(callee), - "syscall" "." => Instruction::SysCall(callee), -} - -Callee: InvocationTarget = { - => InvocationTarget::MastRoot(<>), - "::")?> => { - match module_name { - Some(module) => InvocationTarget::ProcedurePath { name, module }, - None => InvocationTarget::ProcedureName(name), - } - }, - => InvocationTarget::ProcedureName(<>), + "exec" "." => Instruction::Exec(callee), + "call" "." => Instruction::Call(callee), + "syscall" "." => Instruction::SysCall(callee), } #[inline] @@ -562,44 +741,77 @@ Debug: Instruction = { #[inline] ProcRef: Instruction = { - "procref" "." => { - match target { - (name, None) => { - let name = ProcedureName::new_unchecked(name); - Instruction::ProcRef(InvocationTarget::ProcedureName(name)) - }, - (name, Some(module)) => { - let module = match module.namespace() { - LibraryNamespace::User(ns) => Ident::new_unchecked(Span::new(span!(l, r), ns.clone())), - _ => unreachable!(), - }; - Instruction::ProcRef(InvocationTarget::ProcedurePath { - name: ProcedureName::new_unchecked(name), - module, - }) - } - } + "procref" "." => { + Instruction::ProcRef(target) } } #[inline] FoldableInstWithFeltImmediate: SmallOpsVec = { "eq" > => { - let span = span!(l, r); + let span = span!(source_file.id(), l, r); match imm { Some(imm) => smallvec![Op::Inst(Span::new(span, Instruction::EqImm(imm)))], None => smallvec![Op::Inst(Span::new(span, Instruction::Eq))], } }, "neq" > => { - let span = span!(l, r); + let span = span!(source_file.id(), l, r); match imm { Some(imm) => smallvec![Op::Inst(Span::new(span, Instruction::NeqImm(imm)))], None => smallvec![Op::Inst(Span::new(span, Instruction::Neq))], } }, + "lt" > => { + let span = span!(source_file.id(), l, r); + match imm { + Some(imm) => { + match imm { + Immediate::Constant(name) => smallvec![Op::Inst(Span::new(span, Instruction::Push(Immediate::Constant(name)))), Op::Inst(Span::new(span, Instruction::Lt))], + Immediate::Value(value) => smallvec![Op::Inst(Span::new(span, Instruction::PushFelt(value.into_inner()))), Op::Inst(Span::new(span, Instruction::Lt))], + } + } + None => smallvec![Op::Inst(Span::new(span, Instruction::Lt))], + } + }, + "lte" > => { + let span = span!(source_file.id(), l, r); + match imm { + Some(imm) => { + match imm { + Immediate::Constant(name) => smallvec![Op::Inst(Span::new(span, Instruction::Push(Immediate::Constant(name)))), Op::Inst(Span::new(span, Instruction::Lte))], + Immediate::Value(value) => smallvec![Op::Inst(Span::new(span, Instruction::PushFelt(value.into_inner()))), Op::Inst(Span::new(span, Instruction::Lte))], + } + } + None => smallvec![Op::Inst(Span::new(span, Instruction::Lte))], + } + }, + "gt" > => { + let span = span!(source_file.id(), l, r); + match imm { + Some(imm) => { + match imm { + Immediate::Constant(name) => smallvec![Op::Inst(Span::new(span, Instruction::Push(Immediate::Constant(name)))), Op::Inst(Span::new(span, Instruction::Gt))], + Immediate::Value(value) => smallvec![Op::Inst(Span::new(span, Instruction::PushFelt(value.into_inner()))), Op::Inst(Span::new(span, Instruction::Gt))], + } + } + None => smallvec![Op::Inst(Span::new(span, Instruction::Gt))], + } + }, + "gte" > => { + let span = span!(source_file.id(), l, r); + match imm { + Some(imm) => { + match imm { + Immediate::Constant(name) => smallvec![Op::Inst(Span::new(span, Instruction::Push(Immediate::Constant(name)))), Op::Inst(Span::new(span, Instruction::Gte))], + Immediate::Value(value) => smallvec![Op::Inst(Span::new(span, Instruction::PushFelt(value.into_inner()))), Op::Inst(Span::new(span, Instruction::Gte))], + } + } + None => smallvec![Op::Inst(Span::new(span, Instruction::Gte))], + } + }, "add" > => { - let span = span!(l, r); + let span = span!(source_file.id(), l, r); match imm { Some(imm) if imm == Felt::ZERO => smallvec![], Some(imm) if imm == Felt::ONE => smallvec![Op::Inst(Span::new(span, Instruction::Incr))], @@ -608,7 +820,7 @@ FoldableInstWithFeltImmediate: SmallOpsVec = { } }, "sub" > => { - let span = span!(l, r); + let span = span!(source_file.id(), l, r); match imm { Some(imm) if imm == Felt::ZERO => smallvec![], Some(imm) => smallvec![Op::Inst(Span::new(span, Instruction::SubImm(imm)))], @@ -616,7 +828,7 @@ FoldableInstWithFeltImmediate: SmallOpsVec = { } }, "mul" > => { - let span = span!(l, r); + let span = span!(source_file.id(), l, r); match imm { Some(imm) if imm == Felt::ZERO => smallvec![Op::Inst(Span::new(span, Instruction::Drop)), Op::Inst(Span::new(span, Instruction::PushU8(0)))], Some(imm) if imm == Felt::ONE => smallvec![], @@ -625,9 +837,9 @@ FoldableInstWithFeltImmediate: SmallOpsVec = { } }, "div" > =>? { - let span = span!(l, r); + let span = span!(source_file.id(), l, r); match imm { - Some(imm) if imm == Felt::ZERO => Err(ParseError::User { error: ParsingError::DivisionByZero { span: span!(l, r) } }), + Some(imm) if imm == Felt::ZERO => Err(ParseError::User { error: ParsingError::DivisionByZero { span: span!(source_file.id(), l, r) } }), Some(imm) if imm == Felt::ONE => Ok(smallvec![]), Some(imm) => Ok(smallvec![Op::Inst(Span::new(span, Instruction::DivImm(imm)))]), None => Ok(smallvec![Op::Inst(Span::new(span, Instruction::Div))]), @@ -638,32 +850,83 @@ FoldableInstWithFeltImmediate: SmallOpsVec = { #[inline] FoldableInstWithU32Immediate: SmallOpsVec = { "u32div" > =>? { - let span = span!(l, r); + let span = span!(source_file.id(), l, r); match imm { - Some(imm) if imm == 0 => Err(ParseError::User { error: ParsingError::DivisionByZero { span: span!(l, r) } }), + Some(imm) if imm == 0 => Err(ParseError::User { error: ParsingError::DivisionByZero { span: span!(source_file.id(), l, r) } }), Some(imm) if imm == 1 => Ok(smallvec![]), Some(imm) => Ok(smallvec![Op::Inst(Span::new(span, Instruction::U32DivImm(imm)))]), None => Ok(smallvec![Op::Inst(Span::new(span, Instruction::U32Div))]), } }, "u32divmod" > =>? { - let span = span!(l, r); + let span = span!(source_file.id(), l, r); match imm { - Some(imm) if imm == 0 => Err(ParseError::User { error: ParsingError::DivisionByZero { span: span!(l, r) } }), + Some(imm) if imm == 0 => Err(ParseError::User { error: ParsingError::DivisionByZero { span: span!(source_file.id(), l, r) } }), Some(imm) => Ok(smallvec![Op::Inst(Span::new(span, Instruction::U32DivModImm(imm)))]), None => Ok(smallvec![Op::Inst(Span::new(span, Instruction::U32DivMod))]), } }, "u32mod" > =>? { - let span = span!(l, r); + let span = span!(source_file.id(), l, r); match imm { - Some(imm) if imm == 0 => Err(ParseError::User { error: ParsingError::DivisionByZero { span: span!(l, r) } }), + Some(imm) if imm == 0 => Err(ParseError::User { error: ParsingError::DivisionByZero { span: span!(source_file.id(), l, r) } }), Some(imm) => Ok(smallvec![Op::Inst(Span::new(span, Instruction::U32ModImm(imm)))]), None => Ok(smallvec![Op::Inst(Span::new(span, Instruction::U32Mod))]), } }, + "u32and" > => { + let span = span!(source_file.id(), l, r); + match imm { + Some(imm) if imm == 0 => smallvec![Op::Inst(Span::new(span, Instruction::Drop)), Op::Inst(Span::new(span, Instruction::PushU8(0)))], + Some(imm) => { + match imm { + Immediate::Constant(name) => smallvec![Op::Inst(Span::new(span, Instruction::Push(Immediate::Constant(name)))), Op::Inst(Span::new(span, Instruction::U32And))], + Immediate::Value(value) => smallvec![Op::Inst(Span::new(span, Instruction::PushU32(value.into_inner()))), Op::Inst(Span::new(span, Instruction::U32And))], + } + } + None => smallvec![Op::Inst(Span::new(span, Instruction::U32And))], + } + }, + "u32or" > => { + let span = span!(source_file.id(), l, r); + match imm { + Some(imm) if imm == 0 => smallvec![], + Some(imm) => { + match imm { + Immediate::Constant(name) => smallvec![Op::Inst(Span::new(span, Instruction::Push(Immediate::Constant(name)))), Op::Inst(Span::new(span, Instruction::U32Or))], + Immediate::Value(value) => smallvec![Op::Inst(Span::new(span, Instruction::PushU32(value.into_inner()))), Op::Inst(Span::new(span, Instruction::U32Or))], + } + } + None => smallvec![Op::Inst(Span::new(span, Instruction::U32Or))], + } + }, + "u32xor" > => { + let span = span!(source_file.id(), l, r); + match imm { + Some(imm) if imm == 0 => smallvec![], + Some(imm) => { + match imm { + Immediate::Constant(name) => smallvec![Op::Inst(Span::new(span, Instruction::Push(Immediate::Constant(name)))), Op::Inst(Span::new(span, Instruction::U32Xor))], + Immediate::Value(value) => smallvec![Op::Inst(Span::new(span, Instruction::PushU32(value.into_inner()))), Op::Inst(Span::new(span, Instruction::U32Xor))], + } + } + None => smallvec![Op::Inst(Span::new(span, Instruction::U32Xor))], + } + }, + "u32not" > => { + let span = span!(source_file.id(), l, r); + match imm { + Some(imm) => { + match imm { + Immediate::Constant(name) => smallvec![Op::Inst(Span::new(span, Instruction::Push(Immediate::Constant(name)))), Op::Inst(Span::new(span, Instruction::U32Not))], + Immediate::Value(value) => smallvec![Op::Inst(Span::new(span, Instruction::PushU32(value.into_inner()))), Op::Inst(Span::new(span, Instruction::U32Not))], + } + } + None => smallvec![Op::Inst(Span::new(span, Instruction::U32Not))], + } + }, "u32wrapping_add" > => { - let span = span!(l, r); + let span = span!(source_file.id(), l, r); match imm { Some(imm) if imm == 0 => smallvec![], Some(imm) => smallvec![Op::Inst(Span::new(span, Instruction::U32WrappingAddImm(imm)))], @@ -671,7 +934,7 @@ FoldableInstWithU32Immediate: SmallOpsVec = { } }, "u32wrapping_sub" > => { - let span = span!(l, r); + let span = span!(source_file.id(), l, r); match imm { Some(imm) if imm == 0 => smallvec![], Some(imm) => smallvec![Op::Inst(Span::new(span, Instruction::U32WrappingSubImm(imm)))], @@ -679,7 +942,7 @@ FoldableInstWithU32Immediate: SmallOpsVec = { } }, "u32wrapping_mul" > => { - let span = span!(l, r); + let span = span!(source_file.id(), l, r); match imm { Some(imm) if imm == 0 => smallvec![Op::Inst(Span::new(span, Instruction::Drop)), Op::Inst(Span::new(span, Instruction::PushU8(0)))], Some(imm) if imm == 1 => smallvec![], @@ -688,28 +951,28 @@ FoldableInstWithU32Immediate: SmallOpsVec = { } }, "u32overflowing_add" > => { - let span = span!(l, r); + let span = span!(source_file.id(), l, r); match imm { Some(imm) => smallvec![Op::Inst(Span::new(span, Instruction::U32OverflowingAddImm(imm)))], None => smallvec![Op::Inst(Span::new(span, Instruction::U32OverflowingAdd))], } }, "u32overflowing_sub" > => { - let span = span!(l, r); + let span = span!(source_file.id(), l, r); match imm { Some(imm) => smallvec![Op::Inst(Span::new(span, Instruction::U32OverflowingSubImm(imm)))], None => smallvec![Op::Inst(Span::new(span, Instruction::U32OverflowingSub))], } }, "u32overflowing_mul" > => { - let span = span!(l, r); + let span = span!(source_file.id(), l, r); match imm { Some(imm) => smallvec![Op::Inst(Span::new(span, Instruction::U32OverflowingMulImm(imm)))], None => smallvec![Op::Inst(Span::new(span, Instruction::U32OverflowingMul))], } }, "u32shl" > => { - let span = span!(l, r); + let span = span!(source_file.id(), l, r); match imm { Some(imm) if imm == 0 => smallvec![], Some(imm) => smallvec![Op::Inst(Span::new(span, Instruction::U32ShlImm(imm)))], @@ -717,7 +980,7 @@ FoldableInstWithU32Immediate: SmallOpsVec = { } }, "u32shr" > => { - let span = span!(l, r); + let span = span!(source_file.id(), l, r); match imm { Some(imm) if imm == 0 => smallvec![], Some(imm) => smallvec![Op::Inst(Span::new(span, Instruction::U32ShrImm(imm)))], @@ -725,7 +988,7 @@ FoldableInstWithU32Immediate: SmallOpsVec = { } }, "u32rotl" > => { - let span = span!(l, r); + let span = span!(source_file.id(), l, r); match imm { Some(imm) if imm == 0 => smallvec![], Some(imm) => smallvec![Op::Inst(Span::new(span, Instruction::U32RotlImm(imm)))], @@ -733,13 +996,85 @@ FoldableInstWithU32Immediate: SmallOpsVec = { } }, "u32rotr" > => { - let span = span!(l, r); + let span = span!(source_file.id(), l, r); match imm { Some(imm) if imm == 0 => smallvec![], Some(imm) => smallvec![Op::Inst(Span::new(span, Instruction::U32RotrImm(imm)))], None => smallvec![Op::Inst(Span::new(span, Instruction::U32Rotr))], } }, + "u32lt" > => { + let span = span!(source_file.id(), l, r); + match imm { + Some(imm) => { + match imm { + Immediate::Constant(name) => smallvec![Op::Inst(Span::new(span, Instruction::Push(Immediate::Constant(name)))), Op::Inst(Span::new(span, Instruction::U32Lt))], + Immediate::Value(value) => smallvec![Op::Inst(Span::new(span, Instruction::PushU32(value.into_inner()))), Op::Inst(Span::new(span, Instruction::U32Lt))], + } + } + None => smallvec![Op::Inst(Span::new(span, Instruction::U32Lt))], + } + }, + "u32lte" > => { + let span = span!(source_file.id(), l, r); + match imm { + Some(imm) => { + match imm { + Immediate::Constant(name) => smallvec![Op::Inst(Span::new(span, Instruction::Push(Immediate::Constant(name)))), Op::Inst(Span::new(span, Instruction::U32Lte))], + Immediate::Value(value) => smallvec![Op::Inst(Span::new(span, Instruction::PushU32(value.into_inner()))), Op::Inst(Span::new(span, Instruction::U32Lte))], + } + } + None => smallvec![Op::Inst(Span::new(span, Instruction::U32Lte))], + } + }, + "u32gt" > => { + let span = span!(source_file.id(), l, r); + match imm { + Some(imm) => { + match imm { + Immediate::Constant(name) => smallvec![Op::Inst(Span::new(span, Instruction::Push(Immediate::Constant(name)))), Op::Inst(Span::new(span, Instruction::U32Gt))], + Immediate::Value(value) => smallvec![Op::Inst(Span::new(span, Instruction::PushU32(value.into_inner()))), Op::Inst(Span::new(span, Instruction::U32Gt))], + } + } + None => smallvec![Op::Inst(Span::new(span, Instruction::U32Gt))], + } + }, + "u32gte" > => { + let span = span!(source_file.id(), l, r); + match imm { + Some(imm) => { + match imm { + Immediate::Constant(name) => smallvec![Op::Inst(Span::new(span, Instruction::Push(Immediate::Constant(name)))), Op::Inst(Span::new(span, Instruction::U32Gte))], + Immediate::Value(value) => smallvec![Op::Inst(Span::new(span, Instruction::PushU32(value.into_inner()))), Op::Inst(Span::new(span, Instruction::U32Gte))], + } + } + None => smallvec![Op::Inst(Span::new(span, Instruction::U32Gte))], + } + }, + "u32min" > => { + let span = span!(source_file.id(), l, r); + match imm { + Some(imm) => { + match imm { + Immediate::Constant(name) => smallvec![Op::Inst(Span::new(span, Instruction::Push(Immediate::Constant(name)))), Op::Inst(Span::new(span, Instruction::U32Min))], + Immediate::Value(value) => smallvec![Op::Inst(Span::new(span, Instruction::PushU32(value.into_inner()))), Op::Inst(Span::new(span, Instruction::U32Min))], + } + } + None => smallvec![Op::Inst(Span::new(span, Instruction::U32Min))], + } + }, + "u32max" > => { + let span = span!(source_file.id(), l, r); + match imm { + Some(imm) => { + match imm { + Immediate::Constant(name) => smallvec![Op::Inst(Span::new(span, Instruction::Push(Immediate::Constant(name)))), Op::Inst(Span::new(span, Instruction::U32Max))], + Immediate::Value(value) => smallvec![Op::Inst(Span::new(span, Instruction::PushU32(value.into_inner()))), Op::Inst(Span::new(span, Instruction::U32Max))], + } + } + None => smallvec![Op::Inst(Span::new(span, Instruction::U32Max))], + } + }, } #[inline] @@ -766,7 +1101,7 @@ InstWithLocalIndex: Instruction = { #[inline] InstWithStackIndex: Instruction = { - "adv_push" "." => Instruction::AdvPush(Immediate::Value(Span::new(span!(l, r), i))), + "adv_push" "." => Instruction::AdvPush(Immediate::Value(Span::new(span!(source_file.id(), l, r), i))), "dup" )?> =>? { let (span, idx) = i.map(|s| s.into_parts()).unwrap_or((SourceSpan::default(), 0)); Ok(match idx { @@ -888,8 +1223,14 @@ InstWithBitSizeImmediate: Instruction = { Push: SmallOpsVec = { "push" "." > =>? { - let ops = values.into_iter().map(|imm| { + let ops = values.into_iter().enumerate().map(|(i, imm)| { let span = imm.span(); + // Include the "push" token in the first item's span + let span = if i == 0 { + span!(source_file.id(), l, span.end().to_u32()) + } else { + span + }; Op::Inst(Span::new(span, match imm { Immediate::Constant(name) => Instruction::Push(Immediate::Constant(name)), Immediate::Value(value) => { @@ -904,7 +1245,7 @@ Push: SmallOpsVec = { })) }).collect::(); if ops.len() > 16 { - Err(ParseError::User { error: ParsingError::PushOverflow { span: span!(l, r), count: ops.len() } }) + Err(ParseError::User { error: ParsingError::PushOverflow { span: span!(source_file.id(), l, r), count: ops.len() } }) } else { Ok(ops) } @@ -922,7 +1263,7 @@ Imm: Immediate = { #[inline] ImmValue: Immediate = { - => Immediate::Value(Span::new(span!(l, r), t)), + => Immediate::Value(Span::new(span!(source_file.id(), l, r), t)), => Immediate::Constant(<>), } @@ -952,7 +1293,6 @@ Shift32: u8 = { } } - #[inline] RawU8: u8 = { => n.into_inner(), @@ -960,7 +1300,7 @@ RawU8: u8 = { U8: Span = { =>? { - let span = span!(l, r); + let span = span!(source_file.id(), l, r); u8::try_from(n).map_err(|error| ParseError::User { error: ParsingError::ImmediateOutOfRange { span, range: 0..(u8::MAX as usize + 1) }, }).map(|n| Span::new(span, n)) @@ -970,7 +1310,7 @@ U8: Span = { U16: u16 = { =>? { u16::try_from(n).map_err(|error| ParseError::User { - error: ParsingError::ImmediateOutOfRange { span: span!(l, r), range: 0..(u16::MAX as usize + 1) }, + error: ParsingError::ImmediateOutOfRange { span: span!(source_file.id(), l, r), range: 0..(u16::MAX as usize + 1) }, }) } } @@ -978,7 +1318,7 @@ U16: u16 = { U32: u32 = { =>? { u32::try_from(n).map_err(|error| ParseError::User { - error: ParsingError::InvalidLiteral { span: span!(l, r), kind: LiteralErrorKind::U32Overflow }, + error: ParsingError::InvalidLiteral { span: span!(source_file.id(), l, r), kind: LiteralErrorKind::U32Overflow }, }) }, @@ -987,7 +1327,15 @@ U32: u32 = { HexEncodedValue::U8(v) => Ok(v as u32), HexEncodedValue::U16(v) => Ok(v as u32), HexEncodedValue::U32(v) => Ok(v), - _ => Err(ParseError::User { error: ParsingError::InvalidLiteral { span: span!(l, r), kind: LiteralErrorKind::U32Overflow } }), + _ => Err(ParseError::User { error: ParsingError::InvalidLiteral { span: span!(source_file.id(), l, r), kind: LiteralErrorKind::U32Overflow } }), + } + }, + + =>? { + match value { + BinEncodedValue::U8(v) => Ok(v as u32), + BinEncodedValue::U16(v) => Ok(v as u32), + BinEncodedValue::U32(v) => Ok(v), } } } @@ -995,9 +1343,9 @@ U32: u32 = { MastRoot: Span = { =>? { match value { - HexEncodedValue::Word(word) => Ok(Span::new(span!(l, r), RpoDigest::from(word))), + HexEncodedValue::Word(word) => Ok(Span::new(span!(source_file.id(), l, r), RpoDigest::from(word))), _ => { - Err(ParseError::User { error: ParsingError::InvalidMastRoot { span: span!(l, r) } }) + Err(ParseError::User { error: ParsingError::InvalidMastRoot { span: span!(source_file.id(), l, r) } }) } } } @@ -1054,14 +1402,14 @@ BitSize: u8 = { } IntOrHexImm: Immediate = { - => Immediate::Value(Span::new(span!(l, r), value)), + => Immediate::Value(Span::new(span!(source_file.id(), l, r), value)), => Immediate::Constant(<>), } IntOrHex: HexEncodedValue = { =>? { if n > Felt::MODULUS { - return Err(ParseError::User { error: ParsingError::InvalidLiteral { span: span!(l, r), kind: LiteralErrorKind::FeltOverflow } }); + return Err(ParseError::User { error: ParsingError::InvalidLiteral { span: span!(source_file.id(), l, r), kind: LiteralErrorKind::FeltOverflow } }); } if n <= (u8::MAX as u64) { Ok(HexEncodedValue::U8(n as u8)) @@ -1080,7 +1428,7 @@ IntOrHex: HexEncodedValue = { Felt: Felt = { =>? { if n > Felt::MODULUS { - return Err(ParseError::User { error: ParsingError::InvalidLiteral { span: span!(l, r), kind: LiteralErrorKind::FeltOverflow } }); + return Err(ParseError::User { error: ParsingError::InvalidLiteral { span: span!(source_file.id(), l, r), kind: LiteralErrorKind::FeltOverflow } }); } Ok(Felt::new(n)) }, @@ -1092,7 +1440,7 @@ Felt: Felt = { HexEncodedValue::U32(v) => Felt::new(v as u64), HexEncodedValue::Felt(v) => v, HexEncodedValue::Word(_) => return Err(ParseError::User { - error: ParsingError::InvalidHexLiteral { span: span!(l, r), kind: HexErrorKind::Overflow }, + error: ParsingError::InvalidHexLiteral { span: span!(source_file.id(), l, r), kind: HexErrorKind::Overflow }, }), }) } @@ -1120,11 +1468,59 @@ QuotedProcedureName: ProcedureName = { interned.insert(name.clone()); name }); - let id = Ident::new_unchecked(Span::new(span!(l, r), name)); + let id = Ident::new_unchecked(Span::new(span!(source_file.id(), l, r), name)); ProcedureName::new_unchecked(id) } } +InvocationTarget: InvocationTarget = { + => InvocationTarget::MastRoot(<>), + MaybeQualifiedProcedurePath, +} + +MaybeQualifiedProcedurePath: InvocationTarget = { + "::" "::")*> =>? { + // A fully-qualified path without a module is routed to the anonymous namespace + if components.is_empty() { + let path = LibraryPath::new_from_components(LibraryNamespace::Anon, []); + return Ok(InvocationTarget::AbsoluteProcedurePath { name, path }); + } + + // Otherwise, use the path specified + let mut components = VecDeque::from(components); + let ns = components.pop_front().unwrap(); + let ns = match ns.as_str() { + // Disallow the use of special namespaces with other components + special_ns @ ( + LibraryNamespace::KERNEL_PATH + | LibraryNamespace::EXEC_PATH + | LibraryNamespace::ANON_PATH + ) if !components.is_empty() => { + return Err(ParseError::User { + error: ParsingError::InvalidLibraryPath { + span: ns.span(), + message: format!("the {special_ns} namespace cannot have submodules") + }, + }); + } + LibraryNamespace::KERNEL_PATH => LibraryNamespace::Kernel, + LibraryNamespace::EXEC_PATH => LibraryNamespace::Exec, + LibraryNamespace::ANON_PATH => LibraryNamespace::Anon, + _ => LibraryNamespace::User(ns.into_inner()), + }; + let path = LibraryPath::new_from_components(ns, components); + Ok(InvocationTarget::AbsoluteProcedurePath { name, path }) + }, + + "::")?> => { + if let Some(module) = module { + InvocationTarget::ProcedurePath { name, module } + } else { + InvocationTarget::ProcedureName(name) + } + } +} + #[inline] BareIdent: Ident = { => { @@ -1133,7 +1529,7 @@ BareIdent: Ident = { interned.insert(name.clone()); name }); - Ident::new_unchecked(Span::new(span!(l, r), name)) + Ident::new_unchecked(Span::new(span!(source_file.id(), l, r), name)) }, OpcodeName, @@ -1141,7 +1537,7 @@ BareIdent: Ident = { MaybeQualifiedName: (Ident, Option) = { > =>? { - let span = span!(l, r); + let span = span!(source_file.id(), l, r); let name = components.pop().unwrap(); if components.is_empty() { Ok((name, None)) @@ -1149,6 +1545,19 @@ MaybeQualifiedName: (Ident, Option) = { let mut components = VecDeque::from(components); let ns = components.pop_front().unwrap(); let ns = match ns.as_str() { + // Disallow the use of special namespaces with other components + special_ns @ ( + LibraryNamespace::KERNEL_PATH + | LibraryNamespace::EXEC_PATH + | LibraryNamespace::ANON_PATH + ) if !components.is_empty() => { + return Err(ParseError::User { + error: ParsingError::InvalidLibraryPath { + span: ns.span(), + message: format!("the {special_ns} namespace cannot have submodules") + }, + }); + } LibraryNamespace::KERNEL_PATH => LibraryNamespace::Kernel, LibraryNamespace::EXEC_PATH => LibraryNamespace::Exec, LibraryNamespace::ANON_PATH => LibraryNamespace::Anon, @@ -1161,10 +1570,23 @@ MaybeQualifiedName: (Ident, Option) = { QualifiedName: (Ident, LibraryPath) = { "::")> > =>? { - let span = span!(l, r); + let span = span!(source_file.id(), l, r); let name = components.pop().unwrap(); let ns = match first.as_str() { + // Disallow the use of special namespaces with other components + special_ns @ ( + LibraryNamespace::KERNEL_PATH + | LibraryNamespace::EXEC_PATH + | LibraryNamespace::ANON_PATH + ) if !components.is_empty() => { + return Err(ParseError::User { + error: ParsingError::InvalidLibraryPath { + span: first.span(), + message: format!("the {special_ns} namespace cannot have submodules") + }, + }); + } LibraryNamespace::KERNEL_PATH => LibraryNamespace::Kernel, LibraryNamespace::EXEC_PATH => LibraryNamespace::Exec, LibraryNamespace::ANON_PATH => LibraryNamespace::Anon, @@ -1185,7 +1607,7 @@ OpcodeName: Ident = { interned.insert(name.clone()); name }); - Ident::new_unchecked(Span::new(span!(l, r), name)) + Ident::new_unchecked(Span::new(span!(source_file.id(), l, r), name)) } } @@ -1330,12 +1752,12 @@ Opcode: &'static str = { ConstantExpr: ConstantExpr = { "+" =>? { - let expr = ConstantExpr::BinaryOp { span: span!(l, r), op: ConstantOp::Add, lhs: Box::new(x), rhs: Box::new(y) }; + let expr = ConstantExpr::BinaryOp { span: span!(source_file.id(), l, r), op: ConstantOp::Add, lhs: Box::new(x), rhs: Box::new(y) }; expr.try_fold().map_err(|error| ParseError::User { error }) }, "-" =>? { - let expr = ConstantExpr::BinaryOp { span: span!(l, r), op: ConstantOp::Sub, lhs: Box::new(x), rhs: Box::new(y) }; + let expr = ConstantExpr::BinaryOp { span: span!(source_file.id(), l, r), op: ConstantOp::Sub, lhs: Box::new(x), rhs: Box::new(y) }; expr.try_fold().map_err(|error| ParseError::User { error }) }, @@ -1344,17 +1766,17 @@ ConstantExpr: ConstantExpr = { ConstantExpr100: ConstantExpr = { "*" =>? { - let expr = ConstantExpr::BinaryOp { span: span!(l, r), op: ConstantOp::Mul, lhs: Box::new(x), rhs: Box::new(y) }; + let expr = ConstantExpr::BinaryOp { span: span!(source_file.id(), l, r), op: ConstantOp::Mul, lhs: Box::new(x), rhs: Box::new(y) }; expr.try_fold().map_err(|error| ParseError::User { error }) }, "/" =>? { - let expr = ConstantExpr::BinaryOp { span: span!(l, r), op: ConstantOp::Div, lhs: Box::new(x), rhs: Box::new(y) }; + let expr = ConstantExpr::BinaryOp { span: span!(source_file.id(), l, r), op: ConstantOp::Div, lhs: Box::new(x), rhs: Box::new(y) }; expr.try_fold().map_err(|error| ParseError::User { error }) }, "//" =>? { - let expr = ConstantExpr::BinaryOp { span: span!(l, r), op: ConstantOp::IntDiv, lhs: Box::new(x), rhs: Box::new(y) }; + let expr = ConstantExpr::BinaryOp { span: span!(source_file.id(), l, r), op: ConstantOp::IntDiv, lhs: Box::new(x), rhs: Box::new(y) }; expr.try_fold().map_err(|error| ParseError::User { error }) }, @@ -1369,7 +1791,7 @@ ConstantName: Ident = { interned.insert(name.clone()); name }); - Ident::new_unchecked(Span::new(span!(l, r), name)) + Ident::new_unchecked(Span::new(span!(source_file.id(), l, r), name)) } } diff --git a/assembly/src/parser/lexer.rs b/assembly/src/parser/lexer.rs index be09a785d9..0664ab0e9d 100644 --- a/assembly/src/parser/lexer.rs +++ b/assembly/src/parser/lexer.rs @@ -1,10 +1,13 @@ -use crate::Felt; use alloc::string::String; use core::{num::IntErrorKind, ops::Range}; use super::{ - DocumentationType, HexEncodedValue, HexErrorKind, LiteralErrorKind, ParsingError, Scanner, - SourceSpan, Token, + BinEncodedValue, BinErrorKind, DocumentationType, HexEncodedValue, HexErrorKind, + LiteralErrorKind, ParsingError, Scanner, Token, +}; +use crate::{ + diagnostics::{ByteOffset, SourceId, SourceSpan}, + Felt, }; /// The value produced by the [Lexer] when iterated @@ -55,6 +58,9 @@ macro_rules! pop2 { /// guarantee that parsing them will produce meaningful results, it is primarily to assist in /// gathering as many errors as possible. pub struct Lexer<'input> { + /// The [SourceId] of the file being lexed, for use in producing spans in lexer diagnostics + source_id: SourceId, + /// The scanner produces a sequence of chars + location, and can be controlled /// The location type is usize scanner: Scanner<'input>, @@ -90,10 +96,11 @@ pub struct Lexer<'input> { impl<'input> Lexer<'input> { /// 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(scanner: Scanner<'input>) -> Self { + pub fn new(source_id: SourceId, scanner: Scanner<'input>) -> Self { let start = scanner.start(); let keywords = Token::keyword_searcher(); let mut lexer = Self { + source_id, scanner, token: Token::Eof, token_start: start, @@ -136,10 +143,10 @@ impl<'input> Lexer<'input> { match self.tokenize() { Ok(tok) => { self.token = tok; - } + }, Err(err) => { self.error = Some(err); - } + }, } } @@ -206,7 +213,7 @@ impl<'input> Lexer<'input> { fn span(&self) -> SourceSpan { assert!(self.token_start <= self.token_end, "invalid range"); assert!(self.token_end <= u32::MAX as usize, "file too large"); - SourceSpan::from((self.token_start as u32)..(self.token_end as u32)) + SourceSpan::new(self.source_id, (self.token_start as u32)..(self.token_end as u32)) } #[inline] @@ -248,12 +255,12 @@ impl<'input> Lexer<'input> { self.skip(); self.skip(); return self.lex_docs(); - } + }, _ => { self.skip(); self.skip_comment(); return Ok(Token::Comment); - } + }, } } @@ -267,15 +274,19 @@ impl<'input> Lexer<'input> { } match self.read() { + '@' => pop!(self, Token::At), '!' => pop!(self, Token::Bang), ':' => match self.peek() { ':' => pop2!(self, Token::ColonColon), _ => Err(ParsingError::InvalidToken { span: self.span() }), }, '.' => pop!(self, Token::Dot), + ',' => pop!(self, Token::Comma), '=' => pop!(self, Token::Equal), '(' => pop!(self, Token::Lparen), + '[' => pop!(self, Token::Lbracket), ')' => pop!(self, Token::Rparen), + ']' => pop!(self, Token::Rbracket), '-' => match self.peek() { '>' => pop2!(self, Token::Rstab), _ => pop!(self, Token::Minus), @@ -286,13 +297,18 @@ impl<'input> Lexer<'input> { _ => pop!(self, Token::Slash), }, '*' => pop!(self, Token::Star), - '"' => self.lex_quoted_identifier(), + '"' => self.lex_quoted_identifier_or_string(), '0' => match self.peek() { 'x' => { self.skip(); self.skip(); self.lex_hex() - } + }, + 'b' => { + self.skip(); + self.skip(); + self.lex_bin() + }, '0'..='9' => self.lex_number(), _ => pop!(self, Token::Int(0)), }, @@ -342,13 +358,13 @@ impl<'input> Lexer<'input> { self.skip(); line_start = self.token_end; continue; - } + }, _ if is_module_doc => { break Ok(Token::DocComment(DocumentationType::Module(buf))); - } + }, _ => { break Ok(Token::DocComment(DocumentationType::Form(buf))); - } + }, } } @@ -397,45 +413,56 @@ impl<'input> Lexer<'input> { } else { Ok(Token::Exp) } - } + }, _ => Ok(Token::from_keyword_or_ident_with_searcher(name, &self.keywords)), } } - fn lex_quoted_identifier(&mut self) -> Result, ParsingError> { + fn lex_quoted_identifier_or_string(&mut self) -> Result, ParsingError> { // Skip quotation mark self.skip(); - let quote_size = '"'.len_utf8() as u32; + let mut is_identifier = true; + let quote_size = ByteOffset::from_char_len('"'); loop { match self.read() { '\0' | '\n' => { break Err(ParsingError::UnclosedQuote { - start: SourceSpan::at(self.span().start() as u32), + start: SourceSpan::at(self.source_id, self.span().start()), }); - } + }, + '\\' => { + is_identifier = false; + self.skip(); + match self.read() { + '"' | '\n' => { + self.skip(); + }, + _ => (), + } + }, '"' => { let span = self.span(); - let start = span.start() as u32 + quote_size; - let span = SourceSpan::from(start..(span.end() as u32)); + let start = span.start() + quote_size; + let span = SourceSpan::new(self.source_id, start..span.end()); self.skip(); - break Ok(Token::QuotedIdent(self.slice_span(span))); - } + break Ok(if is_identifier { + Token::QuotedIdent(self.slice_span(span)) + } else { + Token::QuotedString(self.slice_span(span)) + }); + }, c if c.is_ascii_alphanumeric() => { self.skip(); - continue; - } - '_' | '.' | '$' => { + }, + '_' | '$' | '-' | '!' | '?' | '<' | '>' | ':' | '.' => { self.skip(); - continue; - } - c => { - let loc = self.span().end() - c.len_utf8(); - break Err(ParsingError::InvalidIdentCharacter { - span: SourceSpan::at(loc as u32), - }); - } + }, + _ => { + is_identifier = false; + self.skip(); + }, } } } @@ -517,13 +544,35 @@ impl<'input> Lexer<'input> { } let span = self.span(); - let start = span.start() as u32; - let digit_start = start + 2; - let end = span.end() as u32; - let span = SourceSpan::from(start..end); - let value = parse_hex(span, self.slice_span(digit_start..end))?; + let start = span.start(); + let end = span.end(); + let digit_start = start.to_u32() + 2; + let span = SourceSpan::new(span.source_id(), start..end); + let value = parse_hex(span, self.slice_span(digit_start..end.to_u32()))?; Ok(Token::HexValue(value)) } + + fn lex_bin(&mut self) -> Result, ParsingError> { + // Expect the first character to be a valid binary digit + debug_assert!(is_ascii_binary(self.read())); + + loop { + // If we hit a non-binary digit, we're done + let c1 = self.read(); + if !is_ascii_binary(c1) { + break; + } + self.skip(); + } + + let span = self.span(); + let start = span.start(); + let digit_start = start.to_u32() + 2; + let end = span.end(); + let span = SourceSpan::new(span.source_id(), start..end); + let value = parse_bin(span, self.slice_span(digit_start..end.to_u32()))?; + Ok(Token::BinValue(value)) + } } impl<'input> Iterator for Lexer<'input> { @@ -561,8 +610,8 @@ fn parse_hex(span: SourceSpan, hex_digits: &str) -> Result { let mut word = [Felt::ZERO; 4]; @@ -592,25 +641,39 @@ fn parse_hex(span: SourceSpan, hex_digits: &str) -> Result 64 => Err(ParsingError::InvalidHexLiteral { - span, - kind: HexErrorKind::TooLong, - }), - n if n % 2 != 0 && n < 64 => Err(ParsingError::InvalidHexLiteral { - span, - kind: HexErrorKind::MissingDigits, - }), - _ => Err(ParsingError::InvalidHexLiteral { - span, - kind: HexErrorKind::Invalid, - }), + n if n > 64 => Err(ParsingError::InvalidHexLiteral { span, kind: HexErrorKind::TooLong }), + n if n % 2 != 0 && n < 64 => { + Err(ParsingError::InvalidHexLiteral { span, kind: HexErrorKind::MissingDigits }) + }, + _ => Err(ParsingError::InvalidHexLiteral { span, kind: HexErrorKind::Invalid }), } } +fn parse_bin(span: SourceSpan, bin_digits: &str) -> Result { + if bin_digits.len() <= 32 { + let value = + u32::from_str_radix(bin_digits, 2).map_err(|error| ParsingError::InvalidLiteral { + span, + kind: int_error_kind_to_literal_error_kind( + error.kind(), + LiteralErrorKind::U32Overflow, + ), + })?; + Ok(shrink_u32_bin(value)) + } else { + Err(ParsingError::InvalidBinaryLiteral { span, kind: BinErrorKind::TooLong }) + } +} + +#[inline(always)] +fn is_ascii_binary(c: char) -> bool { + matches!(c, '0'..='1') +} + #[inline] -fn shrink_u64(n: u64) -> HexEncodedValue { +fn shrink_u64_hex(n: u64) -> HexEncodedValue { if n <= (u8::MAX as u64) { HexEncodedValue::U8(n as u8) } else if n <= (u16::MAX as u64) { @@ -622,6 +685,17 @@ fn shrink_u64(n: u64) -> HexEncodedValue { } } +#[inline] +fn shrink_u32_bin(n: u32) -> BinEncodedValue { + if n <= (u8::MAX as u32) { + BinEncodedValue::U8(n as u8) + } else if n <= (u16::MAX as u32) { + BinEncodedValue::U16(n as u16) + } else { + BinEncodedValue::U32(n) + } +} + #[inline] fn int_error_kind_to_literal_error_kind( kind: &IntErrorKind, diff --git a/assembly/src/parser/location.rs b/assembly/src/parser/location.rs deleted file mode 100644 index 9ff1c1fd5f..0000000000 --- a/assembly/src/parser/location.rs +++ /dev/null @@ -1,57 +0,0 @@ -use crate::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; -use core::fmt; - -// SOURCE LOCATION -// ================================================================================================ - -/// A struct containing information about the location of a source item. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct SourceLocation { - // TODO add uri - line: u32, - column: u32, -} - -impl SourceLocation { - /// Creates a new instance of [SourceLocation]. - pub const fn new(line: u32, column: u32) -> Self { - Self { line, column } - } - - /// Returns the line of the location. - pub const fn line(&self) -> u32 { - self.line - } - - /// Moves the column by the given offset. - pub fn move_column(&mut self, offset: u32) { - self.column += offset; - } -} - -impl Default for SourceLocation { - fn default() -> Self { - Self { line: 1, column: 1 } - } -} - -impl fmt::Display for SourceLocation { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "[{}:{}]", self.line, self.column) - } -} - -impl Serializable for SourceLocation { - fn write_into(&self, target: &mut W) { - target.write_u32(self.line); - target.write_u32(self.column); - } -} - -impl Deserializable for SourceLocation { - fn read_from(source: &mut R) -> Result { - let line = source.read_u32()?; - let column = source.read_u32()?; - Ok(Self { line, column }) - } -} diff --git a/assembly/src/parser/mod.rs b/assembly/src/parser/mod.rs index e939c2fcab..d91f9e1024 100644 --- a/assembly/src/parser/mod.rs +++ b/assembly/src/parser/mod.rs @@ -1,10 +1,10 @@ /// Simple macro used in the grammar definition for constructing spans macro_rules! span { - ($l:expr, $r:expr) => { - crate::SourceSpan::new($l..$r) + ($id:expr, $l:expr, $r:expr) => { + crate::SourceSpan::new($id, $l..$r) }; - ($i:expr) => { - crate::SourceSpan::new($i..$i) + ($id:expr, $i:expr) => { + crate::SourceSpan::at($id, $i) }; } @@ -16,24 +16,25 @@ lalrpop_util::lalrpop_mod!( mod error; mod lexer; -mod location; mod scanner; -mod span; mod token; -pub use self::error::{HexErrorKind, LiteralErrorKind, ParsingError}; -pub use self::lexer::Lexer; -pub use self::location::SourceLocation; -pub use self::scanner::Scanner; -pub use self::span::{SourceSpan, Span, Spanned}; -pub use self::token::{DocumentationType, HexEncodedValue, Token}; +use alloc::{boxed::Box, collections::BTreeSet, string::ToString, sync::Arc, vec::Vec}; +pub use self::{ + error::{BinErrorKind, HexErrorKind, LiteralErrorKind, ParsingError}, + lexer::Lexer, + scanner::Scanner, + token::{BinEncodedValue, DocumentationType, HexEncodedValue, Token}, +}; use crate::{ ast, - diagnostics::{Report, SourceFile}, - sema, LibraryPath, + diagnostics::{Report, SourceFile, SourceSpan, Span, Spanned}, + sema, LibraryPath, SourceManager, }; -use alloc::{boxed::Box, collections::BTreeSet, string::ToString, sync::Arc, vec::Vec}; + +// TYPE ALIASES +// ================================================================================================ type ParseError<'a> = lalrpop_util::ParseError, ParsingError>; @@ -41,10 +42,7 @@ type ParseError<'a> = lalrpop_util::ParseError, ParsingError>; // ================================================================================================ /// This is a wrapper around the lower-level parser infrastructure which handles orchestrating all -/// of the pieces needed to parse a [Module] from source, and run semantic analysis on it. -/// -/// In the vast majority of cases though, you will want to use the more ergonomic -/// [Module::parse_str] or [Module::parse_file] APIs instead. +/// of the pieces needed to parse a [ast::Module] from source, and run semantic analysis on it. #[derive(Default)] pub struct ModuleParser { /// The kind of module we're parsing. @@ -74,7 +72,7 @@ pub struct ModuleParser { } impl ModuleParser { - /// Construct a new parser for the given `kind` of [Module] + /// Construct a new parser for the given `kind` of [ast::Module]. pub fn new(kind: ast::ModuleKind) -> Self { Self { kind, @@ -88,7 +86,7 @@ impl ModuleParser { self.warnings_as_errors = yes; } - /// Parse a [Module] from `source`, and give it the provided `path`. + /// Parse a [ast::Module] from `source`, and give it the provided `path`. pub fn parse( &mut self, path: LibraryPath, @@ -99,36 +97,47 @@ impl ModuleParser { sema::analyze(source, self.kind, path, forms, self.warnings_as_errors).map_err(Report::new) } - /// Parse a [Module], `name`, from `path`. + /// Parse a [ast::Module], `name`, from `path`. #[cfg(feature = "std")] - pub fn parse_file

(&mut self, name: LibraryPath, path: P) -> Result, Report> + pub fn parse_file

( + &mut self, + name: LibraryPath, + path: P, + source_manager: &dyn SourceManager, + ) -> Result, Report> where P: AsRef, { + use vm_core::debuginfo::SourceManagerExt; + use crate::diagnostics::{IntoDiagnostic, WrapErr}; let path = path.as_ref(); - let filename = path.to_string_lossy(); - let source = std::fs::read_to_string(path) + let source_file = source_manager + .load_file(path) .into_diagnostic() - .wrap_err_with(|| format!("failed to parse module from '{filename}'"))?; - let source_file = Arc::new(SourceFile::new(filename, source)); + .wrap_err_with(|| format!("failed to load source file from '{}'", path.display()))?; self.parse(name, source_file) } - /// Parse a [Module], `name`, from `source`. + /// Parse a [ast::Module], `name`, from `source`. pub fn parse_str( &mut self, name: LibraryPath, source: impl ToString, + source_manager: &dyn SourceManager, ) -> Result, Report> { - let source = source.to_string(); - let source_file = Arc::new(SourceFile::new(name.path(), source)); + use vm_core::debuginfo::SourceContent; + + let path = Arc::from(name.path().into_owned().into_boxed_str()); + let content = SourceContent::new(Arc::clone(&path), source.to_string().into_boxed_str()); + let source_file = source_manager.load_from_raw_parts(path, content); self.parse(name, source_file) } } -/// This is used in tests to parse `source` as a set of raw [ast::Form]s rather than as a [Module]. +/// This is used in tests to parse `source` as a set of raw [ast::Form]s rather than as a +/// [ast::Module]. /// /// NOTE: This does _not_ run semantic analysis. #[cfg(any(test, feature = "testing"))] @@ -145,11 +154,179 @@ fn parse_forms_internal( source: Arc, interned: &mut BTreeSet>, ) -> Result, ParsingError> { - let scanner = Scanner::new(source.inner().as_ref()); - let lexer = Lexer::new(scanner); + let source_id = source.id(); + let scanner = Scanner::new(source.as_str()); + let lexer = Lexer::new(source_id, scanner); grammar::FormsParser::new() .parse(&source, interned, core::marker::PhantomData, lexer) - .map_err(ParsingError::from) + .map_err(|err| ParsingError::from_parse_error(source_id, err)) +} + +// DIRECTORY PARSER +// ================================================================================================ + +/// Read the contents (modules) of this library from `dir`, returning any errors that occur +/// while traversing the file system. +/// +/// Errors may also be returned if traversal discovers issues with the modules, such as +/// invalid names, etc. +/// +/// Returns an iterator over all parsed modules. +#[cfg(feature = "std")] +pub fn read_modules_from_dir( + namespace: crate::LibraryNamespace, + dir: &std::path::Path, + source_manager: &dyn SourceManager, +) -> Result>, Report> { + use std::collections::{btree_map::Entry, BTreeMap}; + + use miette::miette; + use module_walker::{ModuleEntry, WalkModules}; + + use crate::diagnostics::{IntoDiagnostic, WrapErr}; + + if !dir.is_dir() { + return Err(miette!("the provided path '{}' is not a valid directory", dir.display())); + } + + // mod.masm is not allowed in the root directory + if dir.join(ast::Module::ROOT_FILENAME).exists() { + return Err(miette!("{} is not allowed in the root directory", ast::Module::ROOT_FILENAME)); + } + + let mut modules = BTreeMap::default(); + + let walker = WalkModules::new(namespace.clone(), dir) + .into_diagnostic() + .wrap_err_with(|| format!("failed to load modules from '{}'", dir.display()))?; + for entry in walker { + let ModuleEntry { mut name, source_path } = entry?; + if name.last() == ast::Module::ROOT { + name.pop(); + } + + // Parse module at the given path + let mut parser = ModuleParser::new(ast::ModuleKind::Library); + let ast = parser.parse_file(name.clone(), &source_path, source_manager)?; + match modules.entry(name) { + Entry::Occupied(ref entry) => { + return Err(miette!("duplicate module '{0}'", entry.key().clone())); + }, + Entry::Vacant(entry) => { + entry.insert(ast); + }, + } + } + + Ok(modules.into_values()) +} + +#[cfg(feature = "std")] +mod module_walker { + + use std::{ + ffi::OsStr, + fs::{self, DirEntry, FileType}, + io, + path::{Path, PathBuf}, + }; + + use miette::miette; + + use crate::{ + ast::Module, + diagnostics::{IntoDiagnostic, Report}, + LibraryNamespace, LibraryPath, + }; + + pub struct ModuleEntry { + pub name: LibraryPath, + pub source_path: PathBuf, + } + + pub struct WalkModules<'a> { + namespace: LibraryNamespace, + root: &'a Path, + stack: alloc::collections::VecDeque>, + } + + impl<'a> WalkModules<'a> { + pub fn new(namespace: LibraryNamespace, path: &'a Path) -> io::Result { + use alloc::collections::VecDeque; + + let stack = VecDeque::from_iter(fs::read_dir(path)?); + + Ok(Self { namespace, root: path, stack }) + } + + fn next_entry( + &mut self, + entry: &DirEntry, + ty: &FileType, + ) -> Result, Report> { + if ty.is_dir() { + let dir = entry.path(); + self.stack.extend(fs::read_dir(dir).into_diagnostic()?); + return Ok(None); + } + + let mut file_path = entry.path(); + let is_module = file_path + .extension() + .map(|ext| ext == AsRef::::as_ref(Module::FILE_EXTENSION)) + .unwrap_or(false); + if !is_module { + return Ok(None); + } + + // Remove the file extension and the root prefix, leaving a namespace-relative path + file_path.set_extension(""); + if file_path.is_dir() { + return Err(miette!( + "file and directory with same name are not allowed: {}", + file_path.display() + )); + } + let relative_path = file_path + .strip_prefix(self.root) + .expect("expected path to be a child of the root directory"); + + // Construct a [LibraryPath] from the path components, after validating them + let mut libpath = LibraryPath::from(self.namespace.clone()); + for component in relative_path.iter() { + let component = component.to_str().ok_or_else(|| { + let p = entry.path(); + miette!("{} is an invalid directory entry", p.display()) + })?; + libpath.push(component).into_diagnostic()?; + } + Ok(Some(ModuleEntry { name: libpath, source_path: entry.path() })) + } + } + + impl<'a> Iterator for WalkModules<'a> { + type Item = Result; + + fn next(&mut self) -> Option { + loop { + let entry = self + .stack + .pop_front()? + .and_then(|entry| entry.file_type().map(|ft| (entry, ft))) + .into_diagnostic(); + + match entry { + Ok((ref entry, ref file_type)) => { + match self.next_entry(entry, file_type).transpose() { + None => continue, + result => break result, + } + }, + Err(err) => break Some(Err(err)), + } + } + } + } } // TESTS @@ -157,14 +334,17 @@ fn parse_forms_internal( #[cfg(test)] mod tests { - use super::*; use vm_core::assert_matches; + use super::*; + use crate::SourceId; + // This test checks the lexer behavior with regard to tokenizing `exp(.u?[\d]+)?` #[test] fn lex_exp() { + let source_id = SourceId::default(); let scanner = Scanner::new("begin exp.u9 end"); - let mut lexer = Lexer::new(scanner).map(|result| result.map(|(_, t, _)| t)); + let mut lexer = Lexer::new(source_id, scanner).map(|result| result.map(|(_, t, _)| t)); assert_matches!(lexer.next(), Some(Ok(Token::Begin))); assert_matches!(lexer.next(), Some(Ok(Token::ExpU))); assert_matches!(lexer.next(), Some(Ok(Token::Int(n))) if n == 9); @@ -173,6 +353,7 @@ mod tests { #[test] fn lex_block() { + let source_id = SourceId::default(); let scanner = Scanner::new( "\ const.ERR1=1 @@ -184,7 +365,7 @@ begin end ", ); - let mut lexer = Lexer::new(scanner).map(|result| result.map(|(_, t, _)| t)); + let mut lexer = Lexer::new(source_id, scanner).map(|result| result.map(|(_, t, _)| t)); assert_matches!(lexer.next(), Some(Ok(Token::Const))); assert_matches!(lexer.next(), Some(Ok(Token::Dot))); assert_matches!(lexer.next(), Some(Ok(Token::ConstantIdent("ERR1")))); @@ -208,6 +389,7 @@ end #[test] fn lex_emit() { + let source_id = SourceId::default(); let scanner = Scanner::new( "\ begin @@ -216,7 +398,7 @@ begin end ", ); - let mut lexer = Lexer::new(scanner).map(|result| result.map(|(_, t, _)| t)); + let mut lexer = Lexer::new(source_id, scanner).map(|result| result.map(|(_, t, _)| t)); assert_matches!(lexer.next(), Some(Ok(Token::Begin))); assert_matches!(lexer.next(), Some(Ok(Token::Push))); assert_matches!(lexer.next(), Some(Ok(Token::Dot))); diff --git a/assembly/src/parser/token.rs b/assembly/src/parser/token.rs index 4776c6474a..8251278716 100644 --- a/assembly/src/parser/token.rs +++ b/assembly/src/parser/token.rs @@ -50,6 +50,84 @@ pub enum HexEncodedValue { /// A set of 4 field elements, 32 bytes, encoded as a contiguous string of 64 hex digits Word([Felt; 4]), } +impl fmt::Display for HexEncodedValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::U8(value) => write!(f, "{value}"), + Self::U16(value) => write!(f, "{value}"), + Self::U32(value) => write!(f, "{value:#04x}"), + Self::Felt(value) => write!(f, "{:#08x}", &value.as_int().to_be()), + Self::Word(value) => write!( + f, + "{:#08x}{:08x}{:08x}{:08x}", + &value[0].as_int(), + &value[1].as_int(), + &value[2].as_int(), + &value[3].as_int(), + ), + } + } +} +impl PartialOrd for HexEncodedValue { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl Ord for HexEncodedValue { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + use core::cmp::Ordering; + match (self, other) { + (Self::U8(l), Self::U8(r)) => l.cmp(r), + (Self::U8(_), _) => Ordering::Less, + (Self::U16(_), Self::U8(_)) => Ordering::Greater, + (Self::U16(l), Self::U16(r)) => l.cmp(r), + (Self::U16(_), _) => Ordering::Less, + (Self::U32(_), Self::U8(_) | Self::U16(_)) => Ordering::Greater, + (Self::U32(l), Self::U32(r)) => l.cmp(r), + (Self::U32(_), _) => Ordering::Less, + (Self::Felt(_), Self::U8(_) | Self::U16(_) | Self::U32(_)) => Ordering::Greater, + (Self::Felt(l), Self::Felt(r)) => l.as_int().cmp(&r.as_int()), + (Self::Felt(_), _) => Ordering::Less, + (Self::Word([l0, l1, l2, l3]), Self::Word([r0, r1, r2, r3])) => l0 + .as_int() + .cmp(&r0.as_int()) + .then_with(|| l1.as_int().cmp(&r1.as_int())) + .then_with(|| l2.as_int().cmp(&r2.as_int())) + .then_with(|| l3.as_int().cmp(&r3.as_int())), + (Self::Word(_), _) => Ordering::Greater, + } + } +} + +impl core::hash::Hash for HexEncodedValue { + fn hash(&self, state: &mut H) { + core::mem::discriminant(self).hash(state); + match self { + Self::U8(value) => value.hash(state), + Self::U16(value) => value.hash(state), + Self::U32(value) => value.hash(state), + Self::Felt(value) => value.as_int().hash(state), + Self::Word([a, b, c, d]) => { + [a.as_int(), b.as_int(), c.as_int(), d.as_int()].hash(state) + }, + } + } +} + +// BINARY ENCODED VALUE +// ================================================================================================ + +/// Represents one of the various types of values that have a hex-encoded representation in Miden +/// Assembly source files. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum BinEncodedValue { + /// A tiny value + U8(u8), + /// A small value + U16(u16), + /// A u32 constant, typically represents a memory address + U32(u32), +} // TOKEN // ================================================================================================ @@ -112,6 +190,7 @@ pub enum Token<'input> { Export, Exp, ExpU, + False, FriExt2Fold4, Gt, Gte, @@ -148,6 +227,7 @@ pub enum Token<'input> { Neg, Neq, Not, + Nop, Or, Padw, Pow2, @@ -208,24 +288,30 @@ pub enum Token<'input> { U32Xor, While, Xor, + At, Bang, ColonColon, Dot, + Comma, Equal, Lparen, + Lbracket, Minus, Plus, SlashSlash, Slash, Star, Rparen, + Rbracket, Rstab, DocComment(DocumentationType), HexValue(HexEncodedValue), + BinValue(BinEncodedValue), Int(u64), Ident(&'input str), ConstantIdent(&'input str), QuotedIdent(&'input str), + QuotedString(&'input str), Comment, Eof, } @@ -288,6 +374,7 @@ impl<'input> fmt::Display for Token<'input> { Token::Exp => write!(f, "exp"), Token::ExpU => write!(f, "exp.u"), Token::Export => write!(f, "export"), + Token::False => write!(f, "false"), Token::FriExt2Fold4 => write!(f, "fri_ext2fold4"), Token::Gt => write!(f, "gt"), Token::Gte => write!(f, "gte"), @@ -324,6 +411,7 @@ impl<'input> fmt::Display for Token<'input> { Token::Neg => write!(f, "neg"), Token::Neq => write!(f, "neq"), Token::Not => write!(f, "not"), + Token::Nop => write!(f, "nop"), Token::Or => write!(f, "or"), Token::Padw => write!(f, "padw"), Token::Pow2 => write!(f, "pow2"), @@ -384,25 +472,31 @@ impl<'input> fmt::Display for Token<'input> { Token::U32Xor => write!(f, "u32xor"), Token::While => write!(f, "while"), Token::Xor => write!(f, "xor"), + Token::At => write!(f, "@"), Token::Bang => write!(f, "!"), Token::ColonColon => write!(f, "::"), Token::Dot => write!(f, "."), + Token::Comma => write!(f, ","), Token::Equal => write!(f, "="), Token::Lparen => write!(f, "("), + Token::Lbracket => write!(f, "["), Token::Minus => write!(f, "-"), Token::Plus => write!(f, "+"), Token::SlashSlash => write!(f, "//"), Token::Slash => write!(f, "/"), Token::Star => write!(f, "*"), Token::Rparen => write!(f, ")"), + Token::Rbracket => write!(f, "]"), Token::Rstab => write!(f, "->"), Token::DocComment(DocumentationType::Module(_)) => f.write_str("module doc"), Token::DocComment(DocumentationType::Form(_)) => f.write_str("doc comment"), Token::HexValue(_) => f.write_str("hex-encoded value"), + Token::BinValue(_) => f.write_str("bin-encoded value"), Token::Int(_) => f.write_str("integer"), Token::Ident(_) => f.write_str("identifier"), Token::ConstantIdent(_) => f.write_str("constant identifier"), Token::QuotedIdent(_) => f.write_str("quoted identifier"), + Token::QuotedString(_) => f.write_str("quoted string"), Token::Comment => f.write_str("comment"), Token::Eof => write!(f, "end of file"), } @@ -501,6 +595,7 @@ impl<'input> Token<'input> { | Token::Neg | Token::Neq | Token::Not + | Token::Nop | Token::Or | Token::Padw | Token::Pow2 @@ -615,6 +710,7 @@ impl<'input> Token<'input> { ("exp", Token::Exp), ("exp.u", Token::ExpU), ("export", Token::Export), + ("false", Token::False), ("fri_ext2fold4", Token::FriExt2Fold4), ("gt", Token::Gt), ("gte", Token::Gte), @@ -651,6 +747,7 @@ impl<'input> Token<'input> { ("neg", Token::Neg), ("neq", Token::Neq), ("not", Token::Not), + ("nop", Token::Nop), ("or", Token::Or), ("padw", Token::Padw), ("pow2", Token::Pow2), @@ -780,30 +877,36 @@ impl<'input> Token<'input> { Token::Ident(_) => { // Nope, try again match s { + "@" => Ok(Token::At), "!" => Ok(Token::Bang), "::" => Ok(Token::ColonColon), "." => Ok(Token::Dot), + "," => Ok(Token::Comma), "=" => Ok(Token::Equal), "(" => Ok(Token::Lparen), + "[" => Ok(Token::Lbracket), "-" => Ok(Token::Minus), "+" => Ok(Token::Plus), "//" => Ok(Token::SlashSlash), "/" => Ok(Token::Slash), "*" => Ok(Token::Star), ")" => Ok(Token::Rparen), + "]" => Ok(Token::Rbracket), "->" => Ok(Token::Rstab), "end of file" => Ok(Token::Eof), "module doc" => Ok(Token::DocComment(DocumentationType::Module(String::new()))), "doc comment" => Ok(Token::DocComment(DocumentationType::Form(String::new()))), "comment" => Ok(Token::Comment), "hex-encoded value" => Ok(Token::HexValue(HexEncodedValue::U8(0))), + "bin-encoded value" => Ok(Token::BinValue(BinEncodedValue::U8(0))), "integer" => Ok(Token::Int(0)), "identifier" => Ok(Token::Ident("")), "constant identifier" => Ok(Token::ConstantIdent("")), "quoted identifier" => Ok(Token::QuotedIdent("")), + "quoted string" => Ok(Token::QuotedString("")), _ => Err(()), } - } + }, // We matched a keyword token => Ok(token), } diff --git a/assembly/src/sema/context.rs b/assembly/src/sema/context.rs index b3c8e0294d..b98b972351 100644 --- a/assembly/src/sema/context.rs +++ b/assembly/src/sema/context.rs @@ -1,8 +1,3 @@ -use crate::{ - ast::*, - diagnostics::{Diagnostic, Severity, SourceFile}, - Felt, Span, Spanned, -}; use alloc::{ collections::{BTreeMap, BTreeSet}, sync::Arc, @@ -10,24 +5,29 @@ use alloc::{ }; use super::{SemanticAnalysisError, SyntaxError}; +use crate::{ + ast::*, + diagnostics::{Diagnostic, Severity}, + Felt, SourceFile, Span, Spanned, +}; /// This maintains the state for semantic analysis of a single [Module]. pub struct AnalysisContext { - source_code: Arc, /// A map of constants to the value of that constant constants: BTreeMap, procedures: BTreeSet, errors: Vec, + source_file: Arc, warnings_as_errors: bool, } impl AnalysisContext { - pub fn new(source_code: Arc) -> Self { + pub fn new(source_file: Arc) -> Self { Self { - source_code, constants: Default::default(), procedures: Default::default(), errors: Default::default(), + source_file, warnings_as_errors: false, } } @@ -41,10 +41,6 @@ impl AnalysisContext { self.warnings_as_errors } - pub fn source_file(&self) -> Arc { - self.source_code.clone() - } - pub fn register_procedure_name(&mut self, name: ProcedureName) { self.procedures.insert(name); } @@ -68,15 +64,15 @@ impl AnalysisContext { constant.value = ConstantExpr::Literal(Span::new(constant.span(), value)); self.constants.insert(constant.name.clone(), constant); Ok(()) - } + }, Err(err) => { self.errors.push(err); let errors = core::mem::take(&mut self.errors); Err(SyntaxError { - input: self.source_code.clone(), + source_file: self.source_file.clone(), errors, }) - } + }, } } @@ -84,12 +80,7 @@ impl AnalysisContext { match value { ConstantExpr::Literal(value) => Ok(value.into_inner()), ConstantExpr::Var(ref name) => self.get_constant(name), - ConstantExpr::BinaryOp { - op, - ref lhs, - ref rhs, - .. - } => { + ConstantExpr::BinaryOp { op, ref lhs, ref rhs, .. } => { let rhs = self.const_eval(rhs)?; let lhs = self.const_eval(lhs)?; match op { @@ -99,7 +90,7 @@ impl AnalysisContext { ConstantOp::Div => Ok(lhs / rhs), ConstantOp::IntDiv => Ok(Felt::new(lhs.as_int() / rhs.as_int())), } - } + }, } } @@ -131,7 +122,7 @@ impl AnalysisContext { pub fn has_failed(&mut self) -> Result<(), SyntaxError> { if self.has_errors() { Err(SyntaxError { - input: self.source_file(), + source_file: self.source_file.clone(), errors: core::mem::take(&mut self.errors), }) } else { @@ -142,7 +133,7 @@ impl AnalysisContext { pub fn into_result(self) -> Result<(), SyntaxError> { if self.has_errors() { Err(SyntaxError { - input: self.source_code, + source_file: self.source_file.clone(), errors: self.errors, }) } else { @@ -158,7 +149,7 @@ impl AnalysisContext { if !self.errors.is_empty() { // Emit warnings to stderr let warning = Report::from(super::errors::SyntaxWarning { - input: self.source_code, + source_file: self.source_file, errors: self.errors, }); std::eprintln!("{}", warning); diff --git a/assembly/src/sema/errors.rs b/assembly/src/sema/errors.rs index 59b1852f4d..849798f8a1 100644 --- a/assembly/src/sema/errors.rs +++ b/assembly/src/sema/errors.rs @@ -1,10 +1,8 @@ -use crate::{ - diagnostics::{Diagnostic, SourceFile}, - SourceSpan, -}; use alloc::{sync::Arc, vec::Vec}; use core::fmt; +use crate::{diagnostics::Diagnostic, SourceFile, SourceSpan}; + /// The high-level error type for all semantic analysis errors. /// /// This rolls up multiple errors into a single one, and as such, can emit many @@ -20,7 +18,7 @@ use core::fmt; #[diagnostic(help("see emitted diagnostics for details"))] pub struct SyntaxError { #[source_code] - pub input: Arc, + pub source_file: Arc, #[related] pub errors: Vec, } @@ -35,7 +33,7 @@ pub struct SyntaxError { #[diagnostic(help("see below for details"))] pub struct SyntaxWarning { #[source_code] - pub input: Arc, + pub source_file: Arc, #[related] pub errors: Vec, } diff --git a/assembly/src/sema/mod.rs b/assembly/src/sema/mod.rs index cc43754360..e8b02aed59 100644 --- a/assembly/src/sema/mod.rs +++ b/assembly/src/sema/mod.rs @@ -2,14 +2,19 @@ mod context; mod errors; mod passes; -pub use self::context::AnalysisContext; -pub use self::errors::{SemanticAnalysisError, SyntaxError}; +use alloc::{ + boxed::Box, + collections::{BTreeSet, VecDeque}, + sync::Arc, + vec::Vec, +}; use self::passes::{ConstEvalVisitor, VerifyInvokeTargets}; - -use crate::{ast::*, diagnostics::SourceFile, LibraryNamespace, LibraryPath, Span, Spanned}; -use alloc::collections::BTreeSet; -use alloc::{boxed::Box, collections::VecDeque, sync::Arc, vec::Vec}; +pub use self::{ + context::AnalysisContext, + errors::{SemanticAnalysisError, SyntaxError}, +}; +use crate::{ast::*, diagnostics::SourceFile, LibraryPath, Spanned}; /// Constructs and validates a [Module], given the forms constituting the module body. /// @@ -31,7 +36,7 @@ pub fn analyze( let mut analyzer = AnalysisContext::new(source.clone()); analyzer.set_warnings_as_errors(warnings_as_errors); - let mut module = Box::new(Module::new(kind, path).with_source_file(Some(source))); + let mut module = Box::new(Module::new(kind, path).with_span(source.source_span())); let mut forms = VecDeque::from(forms); let mut docs = None; @@ -40,73 +45,62 @@ pub fn analyze( Form::ModuleDoc(docstring) => { assert!(docs.is_none()); module.set_docs(Some(docstring)); - } + }, Form::Doc(docstring) => { if let Some(unused) = docs.replace(docstring) { - analyzer.error(SemanticAnalysisError::UnusedDocstring { - span: unused.span(), - }); + analyzer.error(SemanticAnalysisError::UnusedDocstring { span: unused.span() }); } - } + }, Form::Constant(constant) => { analyzer.define_constant(constant.with_docs(docs.take()))?; - } + }, Form::Import(import) => { if let Some(docs) = docs.take() { analyzer.error(SemanticAnalysisError::ImportDocstring { span: docs.span() }); } define_import(import, &mut module, &mut analyzer)?; - } + }, Form::Procedure(export @ Export::Alias(_)) => match kind { ModuleKind::Kernel => { docs.take(); - analyzer.error(SemanticAnalysisError::ReexportFromKernel { - span: export.span(), - }); - } + analyzer + .error(SemanticAnalysisError::ReexportFromKernel { span: export.span() }); + }, ModuleKind::Executable => { docs.take(); - analyzer.error(SemanticAnalysisError::UnexpectedExport { - span: export.span(), - }); - } + analyzer.error(SemanticAnalysisError::UnexpectedExport { span: export.span() }); + }, ModuleKind::Library => { define_procedure(export.with_docs(docs.take()), &mut module, &mut analyzer)?; - } + }, }, Form::Procedure(export) => match kind { ModuleKind::Executable if export.visibility().is_exported() && !export.is_main() => { docs.take(); - analyzer.error(SemanticAnalysisError::UnexpectedExport { - span: export.span(), - }); - } + analyzer.error(SemanticAnalysisError::UnexpectedExport { span: export.span() }); + }, _ => { define_procedure(export.with_docs(docs.take()), &mut module, &mut analyzer)?; - } + }, }, Form::Begin(body) if matches!(kind, ModuleKind::Executable) => { let docs = docs.take(); - let source_file = analyzer.source_file(); let procedure = Procedure::new(body.span(), Visibility::Public, ProcedureName::main(), 0, body) - .with_docs(docs) - .with_source_file(Some(source_file)); + .with_docs(docs); define_procedure(Export::Procedure(procedure), &mut module, &mut analyzer)?; - } + }, Form::Begin(body) => { docs.take(); analyzer.error(SemanticAnalysisError::UnexpectedEntrypoint { span: body.span() }); - } + }, } } if let Some(unused) = docs.take() { - analyzer.error(SemanticAnalysisError::UnusedDocstring { - span: unused.span(), - }); + analyzer.error(SemanticAnalysisError::UnusedDocstring { span: unused.span() }); } if matches!(kind, ModuleKind::Executable) && !module.has_entrypoint() { @@ -121,9 +115,7 @@ pub fn analyze( // Check unused imports for import in module.imports() { if !import.is_used() { - analyzer.error(SemanticAnalysisError::UnusedImport { - span: import.span(), - }); + analyzer.error(SemanticAnalysisError::UnusedImport { span: import.span() }); } } @@ -173,29 +165,30 @@ fn visit_procedures( visitor.visit_mut_procedure(&mut procedure); } module.procedures.push(Export::Procedure(procedure)); - } + }, Export::Alias(mut alias) => { // Resolve the underlying import, and expand the `target` // to its fully-qualified path. This is needed because after // parsing, the path only contains the last component, // e.g. `u64` of `std::math::u64`. - let target = &mut alias.target; - let imported_module = match target.module.namespace() { - LibraryNamespace::User(ref ns) => { - Ident::new_unchecked(Span::new(target.span(), ns.clone())) + let is_absolute = alias.is_absolute(); + if !is_absolute { + if let AliasTarget::ProcedurePath(ref mut target) = alias.target_mut() { + let imported_module = + target.module.namespace().to_ident().with_span(target.span); + if let Some(import) = module.resolve_import_mut(&imported_module) { + target.module = import.path.clone(); + // Mark the backing import as used + import.uses += 1; + } else { + // Missing import + analyzer + .error(SemanticAnalysisError::MissingImport { span: alias.span() }); + } } - _ => unreachable!(), - }; - if let Some(import) = module.resolve_import_mut(&imported_module) { - target.module = import.path.clone(); - // Mark the backing import as used - import.uses += 1; - } else { - // Missing import - analyzer.error(SemanticAnalysisError::MissingImport { span: alias.span() }); } module.procedures.push(Export::Alias(alias)); - } + }, } } @@ -212,12 +205,12 @@ fn define_import( SemanticAnalysisError::ImportConflict { .. } => { // Proceed anyway, to try and capture more errors context.error(err); - } + }, err => { // We can't proceed without producing a bunch of errors context.error(err); context.has_failed()?; - } + }, } } @@ -235,12 +228,12 @@ fn define_procedure( SemanticAnalysisError::SymbolConflict { .. } => { // Proceed anyway, to try and capture more errors context.error(err); - } + }, err => { // We can't proceed without producing a bunch of errors context.error(err); context.has_failed()?; - } + }, } } diff --git a/assembly/src/sema/passes/const_eval.rs b/assembly/src/sema/passes/const_eval.rs index 07232a4101..f76de03ad2 100644 --- a/assembly/src/sema/passes/const_eval.rs +++ b/assembly/src/sema/passes/const_eval.rs @@ -1,9 +1,10 @@ +use core::ops::ControlFlow; + use crate::{ ast::*, sema::{AnalysisContext, SemanticAnalysisError}, Felt, Span, Spanned, }; -use core::ops::ControlFlow; /// This visitor evaluates all constant expressions and folds them to literals. pub struct ConstEvalVisitor<'analyzer> { @@ -29,17 +30,17 @@ impl<'analyzer> ConstEvalVisitor<'analyzer> { Ok(value) => match T::try_from(value.as_int()) { Ok(value) => { *imm = Immediate::Value(Span::new(span, value)); - } + }, Err(_) => { self.analyzer.error(SemanticAnalysisError::ImmediateOverflow { span }); - } + }, }, Err(error) => { self.analyzer.error(error); - } + }, } ControlFlow::Continue(()) - } + }, } } } @@ -65,13 +66,13 @@ impl<'analyzer> VisitMut for ConstEvalVisitor<'analyzer> { match self.analyzer.get_constant(name) { Ok(value) => { *imm = Immediate::Value(Span::new(span, value)); - } + }, Err(error) => { self.analyzer.error(error); - } + }, } ControlFlow::Continue(()) - } + }, } } } diff --git a/assembly/src/sema/passes/mod.rs b/assembly/src/sema/passes/mod.rs index 008b0aec90..c2a5d82dd8 100644 --- a/assembly/src/sema/passes/mod.rs +++ b/assembly/src/sema/passes/mod.rs @@ -1,5 +1,4 @@ mod const_eval; mod verify_invoke; -pub use self::const_eval::ConstEvalVisitor; -pub use self::verify_invoke::VerifyInvokeTargets; +pub use self::{const_eval::ConstEvalVisitor, verify_invoke::VerifyInvokeTargets}; diff --git a/assembly/src/sema/passes/verify_invoke.rs b/assembly/src/sema/passes/verify_invoke.rs index 08ab8cb5fd..a1c38ba8ca 100644 --- a/assembly/src/sema/passes/verify_invoke.rs +++ b/assembly/src/sema/passes/verify_invoke.rs @@ -63,11 +63,11 @@ impl<'a> VerifyInvokeTargets<'a> { name: name.clone(), path: import.path.clone(), }) - } + }, None => { self.analyzer.error(SemanticAnalysisError::MissingImport { span: name.span() }); None - } + }, } } } @@ -80,7 +80,7 @@ impl<'a> VisitMut for VerifyInvokeTargets<'a> { Instruction::Caller => { self.analyzer.error(SemanticAnalysisError::CallerInKernel { span }); ControlFlow::Continue(()) - } + }, _ => visit::visit_mut_inst(self, inst), } } @@ -97,9 +97,8 @@ impl<'a> VisitMut for VerifyInvokeTargets<'a> { } fn visit_mut_syscall(&mut self, target: &mut InvocationTarget) -> ControlFlow<()> { if self.module.is_kernel() { - self.analyzer.error(SemanticAnalysisError::SyscallInKernel { - span: target.span(), - }); + self.analyzer + .error(SemanticAnalysisError::SyscallInKernel { span: target.span() }); } match target { // Do not analyze syscalls referencing a local name, these @@ -115,9 +114,7 @@ impl<'a> VisitMut for VerifyInvokeTargets<'a> { } fn visit_mut_call(&mut self, target: &mut InvocationTarget) -> ControlFlow<()> { if self.module.is_kernel() { - self.analyzer.error(SemanticAnalysisError::CallInKernel { - span: target.span(), - }); + self.analyzer.error(SemanticAnalysisError::CallInKernel { span: target.span() }); } self.visit_mut_invoke_target(target)?; self.invoked.insert(Invoke::new(InvokeKind::Call, target.clone())); @@ -136,21 +133,23 @@ impl<'a> VisitMut for VerifyInvokeTargets<'a> { fn visit_mut_invoke_target(&mut self, target: &mut InvocationTarget) -> ControlFlow<()> { let span = target.span(); match target { - InvocationTarget::MastRoot(_) | InvocationTarget::AbsoluteProcedurePath { .. } => (), + InvocationTarget::MastRoot(_) => (), + InvocationTarget::AbsoluteProcedurePath { name, path } => { + if self.module.path() == path && &self.current_procedure == name { + self.analyzer.error(SemanticAnalysisError::SelfRecursive { span }); + } + }, InvocationTarget::ProcedureName(ref name) if name == &self.current_procedure => { self.analyzer.error(SemanticAnalysisError::SelfRecursive { span }); - } + }, InvocationTarget::ProcedureName(ref name) => { return self.resolve_local(name); - } - InvocationTarget::ProcedurePath { - ref name, - ref module, - } => { + }, + InvocationTarget::ProcedurePath { ref name, ref module } => { if let Some(new_target) = self.resolve_external(name, module) { *target = new_target; } - } + }, } ControlFlow::Continue(()) } diff --git a/assembly/src/testing.rs b/assembly/src/testing.rs index 990f2c2ec5..2ba14bcfe8 100644 --- a/assembly/src/testing.rs +++ b/assembly/src/testing.rs @@ -1,20 +1,21 @@ +use alloc::{boxed::Box, string::String, sync::Arc, vec::Vec}; +use core::fmt; + +use vm_core::Program; + +#[cfg(feature = "std")] +use crate::diagnostics::reporting::set_panic_hook; use crate::{ - assembler::{Assembler, AssemblyContext, ProcedureCache}, - ast::{Form, FullyQualifiedProcedureName, Module, ModuleKind}, + assembler::Assembler, + ast::{Form, Module, ModuleKind}, diagnostics::{ reporting::{set_hook, ReportHandlerOpts}, - Report, SourceFile, + Report, SourceFile, SourceManager, }, - Compile, CompileOptions, Library, LibraryPath, RpoDigest, + library::Library, + Compile, CompileOptions, LibraryPath, RpoDigest, }; -#[cfg(feature = "std")] -use crate::diagnostics::reporting::set_panic_hook; - -use alloc::{boxed::Box, string::String, sync::Arc, vec::Vec}; -use core::fmt; -use vm_core::{utils::DisplayHex, Program}; - /// Represents a pattern for matching text abstractly /// for use in asserting contents of complex diagnostics #[derive(Debug)] @@ -124,17 +125,11 @@ macro_rules! regex { /// the source file was constructed. #[macro_export] macro_rules! source_file { - ($source:literal) => { - ::alloc::sync::Arc::new($crate::diagnostics::SourceFile::new( - concat!("test", line!()), - $source.to_string(), - )) + ($context:expr, $source:literal) => { + $context.source_manager().load(concat!("test", line!()), $source.to_string()) }; - ($source:expr) => { - ::alloc::sync::Arc::new($crate::diagnostics::SourceFile::new( - concat!("test", line!()), - $source, - )) + ($context:expr, $source:expr) => { + $context.source_manager().load(concat!("test", line!()), $source.to_string()) }; } @@ -153,11 +148,12 @@ macro_rules! assert_diagnostic { }}; } -/// Like [assert_diagnostic], but matches each non-empty line of the rendered output -/// to a corresponding pattern. So if the output has 3 lines, the second of which is -/// empty, and you provide 2 patterns, the assertion passes if the first line matches -/// the first pattern, and the third line matches the second pattern - the second -/// line is ignored because it is empty. +/// Like [assert_diagnostic], but matches each non-empty line of the rendered output to a +/// corresponding pattern. +/// +/// So if the output has 3 lines, the second of which is empty, and you provide 2 patterns, the +/// assertion passes if the first line matches the first pattern, and the third line matches the +/// second pattern - the second line is ignored because it is empty. #[macro_export] macro_rules! assert_diagnostic_lines { ($diagnostic:expr, $($expected:expr),+) => {{ @@ -177,6 +173,7 @@ macro_rules! assert_diagnostic_lines { /// /// Some of the assertion macros defined above require a [TestContext], so be aware of that. pub struct TestContext { + source_manager: Arc, assembler: Assembler, } @@ -201,9 +198,21 @@ impl TestContext { { let _ = set_hook(Box::new(|_| Box::new(ReportHandlerOpts::new().build()))); } - Self { - assembler: Assembler::default().with_debug_mode(true).with_warnings_as_errors(true), - } + let source_manager = Arc::new(crate::DefaultSourceManager::default()); + // Note: we do not set debug mode by default because we do not want AsmOp decorators to be + // inserted in our programs + let assembler = Assembler::new(source_manager.clone()).with_warnings_as_errors(true); + Self { source_manager, assembler } + } + + pub fn with_debug_info(mut self, yes: bool) -> Self { + self.assembler.set_debug_mode(yes); + self + } + + #[inline(always)] + pub fn source_manager(&self) -> Arc { + self.source_manager.clone() } /// Parse the given source file into a vector of top-level [Form]s. @@ -211,7 +220,7 @@ impl TestContext { /// This does not run semantic analysis, or construct a [Module] from the parsed /// forms, and is largely intended for low-level testing of the parser. #[track_caller] - pub fn parse_forms(&mut self, source: Arc) -> Result, Report> { + pub fn parse_forms(&self, source: Arc) -> Result, Report> { crate::parser::parse_forms(source.clone()) .map_err(|err| Report::new(err).with_source_code(source)) } @@ -221,11 +230,14 @@ impl TestContext { /// This runs semantic analysis, and the returned module is guaranteed to be syntactically /// valid. #[track_caller] - pub fn parse_program(&mut self, source: impl Compile) -> Result, Report> { - source.compile_with_options(CompileOptions { - warnings_as_errors: self.assembler.warnings_as_errors(), - ..Default::default() - }) + pub fn parse_program(&self, source: impl Compile) -> Result, Report> { + source.compile_with_options( + self.source_manager.as_ref(), + CompileOptions { + warnings_as_errors: self.assembler.warnings_as_errors(), + ..Default::default() + }, + ) } /// Parse the given source file into a kernel [Module]. @@ -234,11 +246,14 @@ impl TestContext { /// valid. #[allow(unused)] #[track_caller] - pub fn parse_kernel(&mut self, source: impl Compile) -> Result, Report> { - source.compile_with_options(CompileOptions { - warnings_as_errors: self.assembler.warnings_as_errors(), - ..CompileOptions::for_kernel() - }) + pub fn parse_kernel(&self, source: impl Compile) -> Result, Report> { + source.compile_with_options( + self.source_manager.as_ref(), + CompileOptions { + warnings_as_errors: self.assembler.warnings_as_errors(), + ..CompileOptions::for_kernel() + }, + ) } /// Parse the given source file into an anonymous library [Module]. @@ -246,24 +261,30 @@ impl TestContext { /// This runs semantic analysis, and the returned module is guaranteed to be syntactically /// valid. #[track_caller] - pub fn parse_module(&mut self, source: impl Compile) -> Result, Report> { - source.compile_with_options(CompileOptions { - warnings_as_errors: self.assembler.warnings_as_errors(), - ..CompileOptions::for_library() - }) + pub fn parse_module(&self, source: impl Compile) -> Result, Report> { + source.compile_with_options( + self.source_manager.as_ref(), + CompileOptions { + warnings_as_errors: self.assembler.warnings_as_errors(), + ..CompileOptions::for_library() + }, + ) } /// Parse the given source file into a library [Module] with the given fully-qualified path. #[track_caller] pub fn parse_module_with_path( - &mut self, + &self, path: LibraryPath, source: impl Compile, ) -> Result, Report> { - source.compile_with_options(CompileOptions { - warnings_as_errors: self.assembler.warnings_as_errors(), - ..CompileOptions::new(ModuleKind::Library, path).unwrap() - }) + source.compile_with_options( + self.source_manager.as_ref(), + CompileOptions { + warnings_as_errors: self.assembler.warnings_as_errors(), + ..CompileOptions::new(ModuleKind::Library, path).unwrap() + }, + ) } /// Add `module` to the [Assembler] constructed by this context, making it available to @@ -295,10 +316,7 @@ impl TestContext { /// Add the modules of `library` to the [Assembler] constructed by this context. #[track_caller] - pub fn add_library(&mut self, library: &L) -> Result<(), Report> - where - L: ?Sized + Library + 'static, - { + pub fn add_library(&mut self, library: impl AsRef) -> Result<(), Report> { self.assembler.add_library(library) } @@ -307,54 +325,31 @@ impl TestContext { /// NOTE: Any modules added by, e.g. `add_module`, will be available to the executable /// module represented in `source`. #[track_caller] - pub fn assemble(&mut self, source: impl Compile) -> Result { - self.assembler.assemble(source) + pub fn assemble(&self, source: impl Compile) -> Result { + self.assembler.clone().assemble_program(source) + } + + /// Compile a [Library] from `modules` using the [Assembler] constructed by this + /// context. + /// + /// NOTE: Any modules added by, e.g. `add_module`, will be available to the library + #[track_caller] + pub fn assemble_library( + &self, + modules: impl IntoIterator>, + ) -> Result { + self.assembler.clone().assemble_library(modules) } /// Compile a module from `source`, with the fully-qualified name `path`, to MAST, returning /// the MAST roots of all the exported procedures of that module. #[track_caller] pub fn assemble_module( - &mut self, - path: LibraryPath, - module: impl Compile, - ) -> Result, Report> { - let mut context = AssemblyContext::for_library(&path); - context.set_warnings_as_errors(self.assembler.warnings_as_errors()); - - let options = CompileOptions { - path: Some(path), - warnings_as_errors: self.assembler.warnings_as_errors(), - ..CompileOptions::for_library() - }; - self.assembler.assemble_module(module, options, &mut context) - } - - /// Get a reference to the [ProcedureCache] of the [Assembler] constructed by this context. - pub fn procedure_cache(&self) -> &ProcedureCache { - self.assembler.procedure_cache() - } - - /// Display the MAST root associated with `name` in the procedure cache of the [Assembler] - /// constructed by this context. - /// - /// It is expected that the module containing `name` was previously compiled by the assembler, - /// and is thus in the cache. This function will panic if that is not the case. - pub fn display_digest_from_cache( &self, - name: &FullyQualifiedProcedureName, - ) -> impl fmt::Display { - self.procedure_cache() - .get_by_name(name) - .map(|p| p.code().hash()) - .map(DisplayDigest) - .unwrap_or_else(|| panic!("procedure '{}' is not in the procedure cache", name)) - } -} - -struct DisplayDigest(RpoDigest); -impl fmt::Display for DisplayDigest { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:#x}", DisplayHex(self.0.as_bytes().as_slice())) + _path: LibraryPath, + _module: impl Compile, + ) -> Result, Report> { + // This API will change after we implement `Assembler::add_library()` + unimplemented!() } } diff --git a/assembly/src/tests.rs b/assembly/src/tests.rs index c7af80d47b..d361e1c60d 100644 --- a/assembly/src/tests.rs +++ b/assembly/src/tests.rs @@ -1,12 +1,14 @@ -use alloc::{rc::Rc, string::ToString, vec::Vec}; +use alloc::string::ToString; + +use vm_core::mast::{MastNode, MastNodeId}; use crate::{ assert_diagnostic_lines, ast::{Module, ModuleKind}, - diagnostics::Report, + diagnostics::{IntoDiagnostic, Report}, regex, source_file, testing::{Pattern, TestContext}, - Assembler, AssemblyContext, Library, LibraryNamespace, LibraryPath, MaslLibrary, Version, + Assembler, LibraryPath, ModuleParser, }; type TestResult = Result<(), Report>; @@ -29,33 +31,42 @@ macro_rules! assert_assembler_diagnostic { }}; } +macro_rules! parse_module { + ($context:expr, $path:literal, $source:expr) => {{ + let path = LibraryPath::new($path).into_diagnostic()?; + let source_file = + $context.source_manager().load(concat!("test", line!()), $source.to_string()); + Module::parse(path, ModuleKind::Library, source_file)? + }}; +} + // SIMPLE PROGRAMS // ================================================================================================ #[test] fn simple_instructions() -> TestResult { - let mut context = TestContext::default(); - let source = source_file!("begin push.0 assertz end"); + let context = TestContext::default(); + let source = source_file!(&context, "begin push.0 assertz end"); let program = context.assemble(source)?; let expected = "\ begin - span pad eqz assert(0) end + basic_block pad eqz assert(0) end end"; assert_str_eq!(format!("{program}"), expected); - let source = source_file!("begin push.10 push.50 push.2 u32wrapping_madd end"); + let source = source_file!(&context, "begin push.10 push.50 push.2 u32wrapping_madd end"); let program = context.assemble(source)?; let expected = "\ begin - span push(10) push(50) push(2) u32madd drop end + basic_block push(10) push(50) push(2) u32madd drop end end"; assert_str_eq!(format!("{program}"), expected); - let source = source_file!("begin push.10 push.50 push.2 u32wrapping_add3 end"); + let source = source_file!(&context, "begin push.10 push.50 push.2 u32wrapping_add3 end"); let program = context.assemble(source)?; let expected = "\ begin - span push(10) push(50) push(2) u32add3 drop end + basic_block push(10) push(50) push(2) u32add3 drop end end"; assert_str_eq!(format!("{program}"), expected); Ok(()) @@ -65,27 +76,44 @@ end"; #[test] #[ignore] fn empty_program() -> TestResult { - let mut context = TestContext::default(); - let source = source_file!("begin end"); + let context = TestContext::default(); + let source = source_file!(&context, "begin end"); let program = context.assemble(source)?; - let expected = "begin span noop end end"; + let expected = "begin basic_block noop end end"; assert_eq!(expected, format!("{}", program)); Ok(()) } -/// TODO(pauls): Do we want to allow this in Miden Assembly #[test] -#[ignore] fn empty_if() -> TestResult { - let mut context = TestContext::default(); - let source = source_file!("begin if.true end end"); + let context = TestContext::default(); + let source = source_file!(&context, "begin if.true end end"); + assert_assembler_diagnostic!( + context, + source, + "invalid syntax", + regex!(r#",-\[test[\d]+:1:15\]"#), + "1 | begin if.true end end", + " : ^|^", + " : `-- found a end here", + " `----", + " help: expected primitive opcode (e.g. \"add\"), or \"else\", or control flow", + " opcode (e.g. \"if.true\")" + ); + Ok(()) +} + +#[test] +fn empty_if_true_then_branch() -> TestResult { + let context = TestContext::default(); + let source = source_file!(&context, "begin if.true nop end end"); let program = context.assemble(source)?; let expected = "\ begin if.true - span noop end + basic_block noop end else - span noop end + basic_block noop end end end"; assert_str_eq!(format!("{}", program), expected); @@ -96,13 +124,13 @@ end"; #[test] #[ignore] fn empty_while() -> TestResult { - let mut context = TestContext::default(); - let source = source_file!("begin while.true end end"); + let context = TestContext::default(); + let source = source_file!(&context, "begin while.true end end"); let program = context.assemble(source)?; let expected = "\ begin while.true - span noop end + basic_block noop end end end"; assert_str_eq!(format!("{}", program), expected); @@ -113,61 +141,117 @@ end"; #[test] #[ignore] fn empty_repeat() -> TestResult { - let mut context = TestContext::default(); - let source = source_file!("begin repeat.5 end end"); + let context = TestContext::default(); + let source = source_file!(&context, "begin repeat.5 end end"); let program = context.assemble(source)?; let expected = "\ begin - span noop noop noop noop noop end + basic_block noop noop noop noop noop end end"; assert_str_eq!(format!("{}", program), expected); Ok(()) } +/// This test ensures that all iterations of a repeat control block are merged into a single basic +/// block. #[test] -fn single_span() -> TestResult { - let mut context = TestContext::default(); - let source = source_file!("begin push.1 push.2 add end"); +fn repeat_basic_blocks_merged() -> TestResult { + let context = TestContext::default(); + let source = source_file!(&context, "begin mul repeat.5 add end end"); + let program = context.assemble(source)?; + let expected = "\ +begin + basic_block mul add add add add add end +end"; + assert_str_eq!(format!("{}", program), expected); + + // Also ensure that dead code elimination works properly + assert_eq!(program.mast_forest().num_nodes(), 1); + Ok(()) +} + +#[test] +fn single_basic_block() -> TestResult { + let context = TestContext::default(); + let source = source_file!(&context, "begin push.1 push.2 add end"); let program = context.assemble(source)?; let expected = "\ begin - span pad incr push(2) add end + basic_block pad incr push(2) add end end"; assert_str_eq!(format!("{program}"), expected); Ok(()) } #[test] -fn span_and_simple_if() -> TestResult { - let mut context = TestContext::default(); +fn basic_block_and_simple_if_true() -> TestResult { + let context = TestContext::default(); + + // if with else + let source = source_file!(&context, "begin push.2 push.3 if.true add else mul end end"); + let program = context.assemble(source)?; + let expected = "\ +begin + join + basic_block push(2) push(3) end + if.true + basic_block add end + else + basic_block mul end + end + end +end"; + assert_str_eq!(format!("{program}"), expected); + + // if without else + let source = source_file!(&context, "begin push.2 push.3 if.true add end end"); + let program = context.assemble(source)?; + let expected = "\ +begin + join + basic_block push(2) push(3) end + if.true + basic_block add end + else + basic_block noop end + end + end +end"; + assert_str_eq!(format!("{program}"), expected); + Ok(()) +} + +#[test] +fn basic_block_and_simple_if_false() -> TestResult { + let context = TestContext::default(); // if with else - let source = source_file!("begin push.2 push.3 if.true add else mul end end"); + let source = source_file!(&context, "begin push.2 push.3 if.false add else mul end end"); let program = context.assemble(source)?; let expected = "\ begin join - span push(2) push(3) end + basic_block push(2) push(3) end if.true - span add end + basic_block mul end else - span mul end + basic_block add end end end end"; assert_str_eq!(format!("{program}"), expected); // if without else - let source = source_file!("begin push.2 push.3 if.true add end end"); + let source = source_file!(&context, "begin push.2 push.3 if.false add end end"); let program = context.assemble(source)?; let expected = "\ begin join - span push(2) push(3) end + basic_block push(2) push(3) end if.true - span add end + basic_block noop end else - span noop end + basic_block add end end end end"; @@ -187,6 +271,7 @@ fn simple_main_call() -> TestResult { let account_code = context.parse_module_with_path( account_path, source_file!( + &context, "\ export.account_method_1 push.2.1 add @@ -203,6 +288,7 @@ fn simple_main_call() -> TestResult { // compile note 1 program context.assemble(source_file!( + &context, " use.context::account begin @@ -213,6 +299,7 @@ fn simple_main_call() -> TestResult { // compile note 2 program context.assemble(source_file!( + &context, " use.context::account begin @@ -223,14 +310,17 @@ fn simple_main_call() -> TestResult { Ok(()) } +// TODO: Fix test after we implement the new `Assembler::add_library()` +#[ignore] #[test] fn call_without_path() -> TestResult { - let mut context = TestContext::default(); + let context = TestContext::default(); + // compile first module - //context.add_module_from_source( context.assemble_module( "account_code1".parse().unwrap(), source_file!( + &context, "\ export.account_method_1 push.2.1 add @@ -246,10 +336,10 @@ fn call_without_path() -> TestResult { //--------------------------------------------------------------------------------------------- // compile second module - //context.add_module_from_source( context.assemble_module( "account_code2".parse().unwrap(), source_file!( + &context, "\ export.account_method_1 push.2.2 add @@ -266,6 +356,7 @@ fn call_without_path() -> TestResult { // compile program in which functions from different modules but with equal names are called context.assemble(source_file!( + &context, " begin # call the account_method_1 from the first module (account_code1) @@ -295,6 +386,7 @@ fn procref_call() -> TestResult { context.add_module_from_source( "module::path::one".parse().unwrap(), source_file!( + &context, " export.aaa push.7.8 @@ -310,6 +402,7 @@ fn procref_call() -> TestResult { context.add_module_from_source( "module::path::two".parse().unwrap(), source_file!( + &context, " use.module::path::one export.one::foo @@ -322,6 +415,7 @@ fn procref_call() -> TestResult { // compile program with procref calls context.assemble(source_file!( + &context, " use.module::path::two @@ -340,11 +434,12 @@ fn procref_call() -> TestResult { #[test] fn get_proc_name_of_unknown_module() -> TestResult { - let mut context = TestContext::default(); + let context = TestContext::default(); // Module `two` is unknown. This program should return // `AssemblyError::UndefinedProcedure`, referencing the // use of `bar` let module_source1 = source_file!( + &context, " use.module::path::two @@ -355,25 +450,12 @@ fn get_proc_name_of_unknown_module() -> TestResult { let module_path_one = "module::path::one".parse().unwrap(); let module1 = context.parse_module_with_path(module_path_one, module_source1)?; - let masl_lib = - MaslLibrary::new(module1.namespace().clone(), Version::default(), [module1], vec![]) - .unwrap(); - - // instantiate assembler - context.add_library(&masl_lib)?; - - // compile program with procref calls - let source = source_file!( - " - use.module::path::one + let report = Assembler::new(context.source_manager()) + .assemble_library(core::iter::once(module1)) + .expect_err("expected unknown module error"); - begin - procref.one::foo - end" - ); - assert_assembler_diagnostic!( - context, - source, + assert_diagnostic_lines!( + report, "undefined module 'module::path::two'", regex!(r#",-\[test[\d]+:5:22\]"#), "4 | export.foo", @@ -382,6 +464,7 @@ fn get_proc_name_of_unknown_module() -> TestResult { "6 | end", " `----" ); + Ok(()) } @@ -390,8 +473,9 @@ fn get_proc_name_of_unknown_module() -> TestResult { #[test] fn simple_constant() -> TestResult { - let mut context = TestContext::default(); + let context = TestContext::default(); let source = source_file!( + &context, "\ const.TEST_CONSTANT=7 begin @@ -400,7 +484,7 @@ fn simple_constant() -> TestResult { ); let expected = "\ begin - span push(7) end + basic_block push(7) end end"; let program = context.assemble(source)?; assert_str_eq!(format!("{program}"), expected); @@ -409,8 +493,9 @@ end"; #[test] fn multiple_constants_push() -> TestResult { - let mut context = TestContext::default(); + let context = TestContext::default(); let source = source_file!( + &context, "const.CONSTANT_1=21 \ const.CONSTANT_2=44 \ begin \ @@ -419,7 +504,7 @@ fn multiple_constants_push() -> TestResult { ); let expected = "\ begin - span push(21) push(64) push(44) push(72) end + basic_block push(21) push(64) push(44) push(72) end end"; let program = context.assemble(source)?; assert_str_eq!(format!("{program}"), expected); @@ -428,8 +513,9 @@ end"; #[test] fn constant_numeric_expression() -> TestResult { - let mut context = TestContext::default(); + let context = TestContext::default(); let source = source_file!( + &context, "const.TEST_CONSTANT=11-2+4*(12-(10+1))+9+8//4*2 \ begin \ push.TEST_CONSTANT \ @@ -438,7 +524,7 @@ fn constant_numeric_expression() -> TestResult { ); let expected = "\ begin - span push(26) end + basic_block push(26) end end"; let program = context.assemble(source)?; assert_str_eq!(format!("{program}"), expected); @@ -447,8 +533,9 @@ end"; #[test] fn constant_alphanumeric_expression() -> TestResult { - let mut context = TestContext::default(); + let context = TestContext::default(); let source = source_file!( + &context, "const.TEST_CONSTANT_1=(18-1+10)*6-((13+7)*2) \ const.TEST_CONSTANT_2=11-2+4*(12-(10+1))+9 const.TEST_CONSTANT_3=(TEST_CONSTANT_1-(TEST_CONSTANT_2+10))//5+3 @@ -459,7 +546,7 @@ fn constant_alphanumeric_expression() -> TestResult { ); let expected = "\ begin - span push(21) end + basic_block push(21) end end"; let program = context.assemble(source)?; assert_str_eq!(format!("{program}"), expected); @@ -468,8 +555,9 @@ end"; #[test] fn constant_hexadecimal_value() -> TestResult { - let mut context = TestContext::default(); + let context = TestContext::default(); let source = source_file!( + &context, "const.TEST_CONSTANT=0xFF \ begin \ push.TEST_CONSTANT \ @@ -478,7 +566,7 @@ fn constant_hexadecimal_value() -> TestResult { ); let expected = "\ begin - span push(255) end + basic_block push(255) end end"; let program = context.assemble(source)?; assert_str_eq!(format!("{program}"), expected); @@ -487,8 +575,9 @@ end"; #[test] fn constant_field_division() -> TestResult { - let mut context = TestContext::default(); + let context = TestContext::default(); let source = source_file!( + &context, "const.TEST_CONSTANT=(17//4)/4*(1//2)+2 \ begin \ push.TEST_CONSTANT \ @@ -497,7 +586,7 @@ fn constant_field_division() -> TestResult { ); let expected = "\ begin - span push(2) end + basic_block push(2) end end"; let program = context.assemble(source)?; assert_str_eq!(format!("{program}"), expected); @@ -506,8 +595,9 @@ end"; #[test] fn constant_err_const_not_initialized() -> TestResult { - let mut context = TestContext::default(); + let context = TestContext::default(); let source = source_file!( + &context, "const.TEST_CONSTANT=5+A \ begin \ push.TEST_CONSTANT \ @@ -529,8 +619,9 @@ fn constant_err_const_not_initialized() -> TestResult { #[test] fn constant_err_div_by_zero() -> TestResult { - let mut context = TestContext::default(); + let context = TestContext::default(); let source = source_file!( + &context, "const.TEST_CONSTANT=5/0 \ begin \ push.TEST_CONSTANT \ @@ -547,6 +638,7 @@ fn constant_err_div_by_zero() -> TestResult { ); let source = source_file!( + &context, "const.TEST_CONSTANT=5//0 \ begin \ push.TEST_CONSTANT \ @@ -566,8 +658,9 @@ fn constant_err_div_by_zero() -> TestResult { #[test] fn constants_must_be_uppercase() -> TestResult { - let mut context = TestContext::default(); + let context = TestContext::default(); let source = source_file!( + &context, "const.constant_1=12 \ begin \ push.constant_1 \ @@ -590,8 +683,9 @@ fn constants_must_be_uppercase() -> TestResult { #[test] fn duplicate_constant_name() -> TestResult { - let mut context = TestContext::default(); + let context = TestContext::default(); let source = source_file!( + &context, "const.CONSTANT=12 \ const.CONSTANT=14 \ begin \ @@ -617,8 +711,9 @@ fn duplicate_constant_name() -> TestResult { #[test] fn constant_must_be_valid_felt() -> TestResult { - let mut context = TestContext::default(); + let context = TestContext::default(); let source = source_file!( + &context, "const.CONSTANT=1122INVALID \ begin \ push.CONSTANT \ @@ -634,7 +729,7 @@ fn constant_must_be_valid_felt() -> TestResult { " : ^^^|^^^", " : `-- found a constant identifier here", " `----", - " help: expected \"*\", or \"+\", or \"-\", or \"/\", or \"//\", or \"begin\", or \"const\", \ + " help: expected \"*\", or \"+\", or \"-\", or \"/\", or \"//\", or \"@\", or \"begin\", or \"const\", \ or \"export\", or \"proc\", or \"use\", or end of file, or doc comment" ); Ok(()) @@ -642,8 +737,9 @@ or \"export\", or \"proc\", or \"use\", or end of file, or doc comment" #[test] fn constant_must_be_within_valid_felt_range() -> TestResult { - let mut context = TestContext::default(); + let context = TestContext::default(); let source = source_file!( + &context, "const.CONSTANT=18446744073709551615 \ begin \ push.CONSTANT \ @@ -664,8 +760,9 @@ fn constant_must_be_within_valid_felt_range() -> TestResult { #[test] fn constants_defined_in_global_scope() -> TestResult { - let mut context = TestContext::default(); + let context = TestContext::default(); let source = source_file!( + &context, " begin \ const.CONSTANT=12 @@ -691,8 +788,9 @@ fn constants_defined_in_global_scope() -> TestResult { #[test] fn constant_not_found() -> TestResult { - let mut context = TestContext::default(); + let context = TestContext::new(); let source = source_file!( + &context, " begin \ push.CONSTANT \ @@ -716,7 +814,7 @@ fn constant_not_found() -> TestResult { #[test] fn mem_operations_with_constants() -> TestResult { - let mut context = TestContext::default(); + let context = TestContext::default(); // Define constant values const PROC_LOC_STORE_PTR: u64 = 0; @@ -728,8 +826,10 @@ fn mem_operations_with_constants() -> TestResult { const GLOBAL_STOREW_PTR: u64 = 6; const GLOBAL_LOADW_PTR: u64 = 7; - let source = source_file!(format!( - "\ + let source = source_file!( + &context, + format!( + "\ const.PROC_LOC_STORE_PTR={PROC_LOC_STORE_PTR} const.PROC_LOC_LOAD_PTR={PROC_LOC_LOAD_PTR} const.PROC_LOC_STOREW_PTR={PROC_LOC_STOREW_PTR} @@ -773,12 +873,15 @@ fn mem_operations_with_constants() -> TestResult { mem_loadw.GLOBAL_LOADW_PTR end " - )); + ) + ); let program = context.assemble(source)?; // Define expected - let expected = source_file!(format!( - "\ + let expected = source_file!( + &context, + format!( + "\ proc.test_const_loc.4 # constant should resolve using locaddr operation locaddr.{PROC_LOC_STORE_PTR} @@ -813,7 +916,8 @@ fn mem_operations_with_constants() -> TestResult { mem_loadw.{GLOBAL_LOADW_PTR} end " - )); + ) + ); let expected_program = context.assemble(expected)?; assert_str_eq!(expected_program.to_string(), program.to_string()); Ok(()) @@ -824,8 +928,11 @@ fn const_conversion_failed_to_u16() -> TestResult { // Define constant value greater than u16::MAX let constant_value: u64 = u16::MAX as u64 + 1; - let source = source_file!(format!( - "\ + let context = TestContext::default(); + let source = source_file!( + &context, + format!( + "\ const.CONSTANT={constant_value} proc.test_constant_overflow.1 @@ -836,8 +943,8 @@ fn const_conversion_failed_to_u16() -> TestResult { exec.test_constant_overflow end " - )); - let mut context = TestContext::default(); + ) + ); assert_assembler_diagnostic!( context, @@ -857,19 +964,22 @@ fn const_conversion_failed_to_u16() -> TestResult { #[test] fn const_conversion_failed_to_u32() -> TestResult { - let mut context = TestContext::default(); + let context = TestContext::default(); // Define constant value greater than u16::MAX let constant_value: u64 = u32::MAX as u64 + 1; - let source = source_file!(format!( - "\ + let source = source_file!( + &context, + format!( + "\ const.CONSTANT={constant_value} begin mem_load.CONSTANT end " - )); + ) + ); assert_assembler_diagnostic!( context, @@ -887,12 +997,253 @@ fn const_conversion_failed_to_u32() -> TestResult { Ok(()) } +// DECORATORS +// ================================================================================================ + +#[test] +fn decorators_basic_block() -> TestResult { + let context = TestContext::default(); + let source = source_file!( + &context, + "\ + begin + trace.0 + add + trace.1 + mul + trace.2 + end" + ); + let expected = "\ +begin + basic_block trace(0) add trace(1) mul trace(2) end +end"; + let program = context.assemble(source)?; + assert_str_eq!(expected, format!("{program}")); + Ok(()) +} + +#[test] +fn decorators_repeat_one_basic_block() -> TestResult { + let context = TestContext::default(); + let source = source_file!( + &context, + "\ + begin + trace.0 + repeat.2 add end + trace.1 + repeat.2 mul end + trace.2 + end" + ); + let expected = "\ +begin + basic_block trace(0) add add trace(1) mul mul trace(2) end +end"; + let program = context.assemble(source)?; + assert_str_eq!(expected, format!("{program}")); + Ok(()) +} + +#[test] +fn decorators_repeat_split() -> TestResult { + let context = TestContext::default(); + let source = source_file!( + &context, + "\ + begin + trace.0 + repeat.2 + if.true + trace.1 push.42 trace.2 + else + trace.3 push.22 trace.3 + end + trace.4 + end + trace.5 + end" + ); + let expected = "\ +begin + join + trace(0) + if.true + basic_block trace(1) push(42) trace(2) end + else + basic_block trace(3) push(22) trace(3) end + end + trace(4) + if.true + basic_block trace(1) push(42) trace(2) end + else + basic_block trace(3) push(22) trace(3) end + end + trace(4) + end + trace(5) +end"; + let program = context.assemble(source)?; + assert_str_eq!(expected, format!("{program}")); + Ok(()) +} + +#[test] +fn decorators_call() -> TestResult { + let context = TestContext::default(); + let source = source_file!( + &context, + "\ + begin + trace.0 trace.1 + call.0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef + trace.2 + end" + ); + let expected = "\ +begin + trace(0) trace(1) + call.0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef + trace(2) +end"; + let program = context.assemble(source)?; + assert_str_eq!(expected, format!("{program}")); + Ok(()) +} + +#[test] +fn decorators_dyn() -> TestResult { + // single line + let context = TestContext::default(); + let source = source_file!( + &context, + "\ + begin + trace.0 + dynexec + trace.1 + end" + ); + let expected = "\ +begin + trace(0) dyn trace(1) +end"; + let program = context.assemble(source)?; + assert_str_eq!(expected, format!("{program}")); + + // multi line + let context = TestContext::default(); + let source = source_file!( + &context, + "\ + begin + trace.0 trace.1 trace.2 trace.3 trace.4 + dynexec + trace.5 trace.6 trace.7 trace.8 trace.9 + end" + ); + let expected = "\ +begin + trace(0) trace(1) trace(2) trace(3) trace(4) + dyn + trace(5) trace(6) trace(7) trace(8) trace(9) +end"; + let program = context.assemble(source)?; + assert_str_eq!(expected, format!("{program}")); + Ok(()) +} + +#[test] +fn decorators_external() -> TestResult { + let context = TestContext::default(); + let baz = r#" + export.f + push.7 push.8 sub + end + "#; + let baz = parse_module!(&context, "lib::baz", baz); + + let lib = Assembler::new(context.source_manager()).assemble_library([baz])?; + + let program_source = source_file!( + &context, + "\ + use.lib::baz + begin + trace.0 + exec.baz::f + trace.1 + end" + ); + + let expected = "\ +begin + trace(0) + external.0xe776df8dc02329acc43a09fe8e510b44a87dfd876e375ad383891470ece4f6de + trace(1) +end"; + let program = Assembler::new(context.source_manager()) + .with_library(lib)? + .assemble_program(program_source)?; + assert_str_eq!(expected, format!("{program}")); + + Ok(()) +} + +#[test] +fn decorators_join_and_split() -> TestResult { + let context = TestContext::default(); + let source = source_file!( + &context, + "\ + begin + trace.0 trace.1 + if.true + trace.2 add trace.3 + else + trace.4 mul trace.5 + end + trace.6 + if.true + trace.7 push.42 trace.8 + else + trace.9 push.22 trace.10 + end + trace.11 + end" + ); + let expected = "\ +begin + join + trace(0) trace(1) + if.true + basic_block trace(2) add trace(3) end + else + basic_block trace(4) mul trace(5) end + end + trace(6) + if.true + basic_block trace(7) push(42) trace(8) end + else + basic_block trace(9) push(22) trace(10) end + end + end + trace(11) +end"; + let program = context.assemble(source)?; + assert_str_eq!(expected, format!("{program}")); + Ok(()) +} + // ASSERTIONS // ================================================================================================ #[test] fn assert_with_code() -> TestResult { + let context = TestContext::default(); let source = source_file!( + &context, "\ const.ERR1=1 @@ -903,12 +1254,11 @@ fn assert_with_code() -> TestResult { end " ); - let mut context = TestContext::default(); let program = context.assemble(source)?; let expected = "\ begin - span assert(0) assert(1) assert(2) end + basic_block assert(0) assert(1) assert(2) end end"; assert_str_eq!(format!("{program}"), expected); Ok(()) @@ -916,7 +1266,9 @@ end"; #[test] fn assertz_with_code() -> TestResult { + let context = TestContext::default(); let source = source_file!( + &context, "\ const.ERR1=1 @@ -927,12 +1279,11 @@ fn assertz_with_code() -> TestResult { end " ); - let mut context = TestContext::default(); let program = context.assemble(source)?; let expected = "\ begin - span eqz assert(0) eqz assert(1) eqz assert(2) end + basic_block eqz assert(0) eqz assert(1) eqz assert(2) end end"; assert_str_eq!(format!("{program}"), expected); Ok(()) @@ -940,7 +1291,9 @@ end"; #[test] fn assert_eq_with_code() -> TestResult { + let context = TestContext::default(); let source = source_file!( + &context, "\ const.ERR1=1 @@ -951,12 +1304,11 @@ fn assert_eq_with_code() -> TestResult { end " ); - let mut context = TestContext::default(); let program = context.assemble(source)?; let expected = "\ begin - span eq assert(0) eq assert(1) eq assert(2) end + basic_block eq assert(0) eq assert(1) eq assert(2) end end"; assert_str_eq!(format!("{program}"), expected); Ok(()) @@ -964,7 +1316,9 @@ end"; #[test] fn assert_eqw_with_code() -> TestResult { + let context = TestContext::default(); let source = source_file!( + &context, "\ const.ERR1=1 @@ -975,12 +1329,11 @@ fn assert_eqw_with_code() -> TestResult { end " ); - let mut context = TestContext::default(); let program = context.assemble(source)?; let expected = "\ begin - span + basic_block movup4 eq assert(0) @@ -1022,7 +1375,9 @@ end"; #[test] fn u32assert_with_code() -> TestResult { + let context = TestContext::default(); let source = source_file!( + &context, "\ const.ERR1=1 @@ -1033,12 +1388,11 @@ fn u32assert_with_code() -> TestResult { end " ); - let mut context = TestContext::default(); let program = context.assemble(source)?; let expected = "\ begin - span + basic_block pad u32assert2(0) drop @@ -1056,7 +1410,9 @@ end"; #[test] fn u32assert2_with_code() -> TestResult { + let context = TestContext::default(); let source = source_file!( + &context, "\ const.ERR1=1 @@ -1067,12 +1423,11 @@ fn u32assert2_with_code() -> TestResult { end " ); - let mut context = TestContext::default(); let program = context.assemble(source)?; let expected = "\ begin - span u32assert2(0) u32assert2(1) u32assert2(2) end + basic_block u32assert2(0) u32assert2(1) u32assert2(2) end end"; assert_str_eq!(format!("{program}"), expected); Ok(()) @@ -1080,7 +1435,9 @@ end"; #[test] fn u32assertw_with_code() -> TestResult { + let context = TestContext::default(); let source = source_file!( + &context, "\ const.ERR1=1 @@ -1091,12 +1448,11 @@ fn u32assertw_with_code() -> TestResult { end " ); - let mut context = TestContext::default(); let program = context.assemble(source)?; let expected = "\ begin - span + basic_block u32assert2(0) movup3 movup3 @@ -1121,15 +1477,167 @@ end"; Ok(()) } -// NESTED CONTROL BLOCKS -// ================================================================================================ +/// Ensure that there is no collision between `Assert`, `U32assert2`, and `MpVerify` instructions +/// with different inner values (which all don't contribute to the MAST root). +#[test] +fn asserts_and_mpverify_with_code_in_duplicate_procedure() -> TestResult { + let context = TestContext::default(); + let source = source_file!( + &context, + "\ + proc.f1 + u32assert.err=1 + end + proc.f2 + u32assert.err=2 + end + proc.f12 + u32assert.err=1 + u32assert.err=2 + end + proc.f21 + u32assert.err=2 + u32assert.err=1 + end + proc.g1 + assert.err=1 + end + proc.g2 + assert.err=2 + end + proc.g12 + assert.err=1 + assert.err=2 + end + proc.g21 + assert.err=2 + assert.err=1 + end + proc.fg + assert.err=1 + u32assert.err=1 + assert.err=2 + u32assert.err=2 + + u32assert.err=1 + assert.err=1 + u32assert.err=2 + assert.err=2 + end + + proc.mpverify + mtree_verify.err=1 + mtree_verify.err=2 + mtree_verify.err=2 + mtree_verify.err=1 + end + + begin + exec.f1 + exec.f2 + exec.f12 + exec.f21 + exec.g1 + exec.g2 + exec.g12 + exec.g21 + exec.fg + exec.mpverify + end + " + ); + let program = context.assemble(source)?; + + let expected = "\ +begin + basic_block + pad + u32assert2(1) + drop + pad + u32assert2(2) + drop + pad + u32assert2(1) + drop + pad + u32assert2(2) + drop + pad + u32assert2(2) + drop + pad + u32assert2(1) + drop + assert(1) + assert(2) + assert(1) + assert(2) + assert(2) + assert(1) + assert(1) + pad + u32assert2(1) + drop + assert(2) + pad + u32assert2(2) + drop + pad + u32assert2(1) + drop + assert(1) + pad + u32assert2(2) + drop + assert(2) + mpverify(1) + mpverify(2) + mpverify(2) + mpverify(1) + end +end"; + + assert_str_eq!(expected, format!("{program}")); + Ok(()) +} + +#[test] +fn mtree_verify_with_code() -> TestResult { + let context = TestContext::default(); + let source = source_file!( + &context, + "\ + const.ERR1=1 + + begin + mtree_verify + mtree_verify.err=ERR1 + mtree_verify.err=2 + end + " + ); + + let program = context.assemble(source)?; + + let expected = "\ +begin + basic_block mpverify(0) mpverify(1) mpverify(2) end +end"; + assert_str_eq!(format!("{program}"), expected); + Ok(()) +} + +// NESTED CONTROL BLOCKS +// ================================================================================================ #[test] fn nested_control_blocks() -> TestResult { - let mut context = TestContext::default(); + let context = TestContext::default(); // if with else let source = source_file!( + &context, "begin \ push.2 push.3 \ if.true \ @@ -1145,111 +1653,142 @@ fn nested_control_blocks() -> TestResult { begin join join - span push(2) push(3) end + basic_block push(2) push(3) end if.true join - span add end + basic_block add end while.true - span push(7) push(11) add end + basic_block push(7) push(11) add end end end else join - span mul push(8) push(8) end + basic_block mul push(8) push(8) end if.true - span mul end + basic_block mul end else - span noop end + basic_block noop end end end end end - span push(3) add end + basic_block push(3) add end end end"; - assert_str_eq!(format!("{program}"), expected); + assert_str_eq!(expected, format!("{program}")); Ok(()) } // PROGRAMS WITH PROCEDURES // ================================================================================================ +/// If the program has 2 procedures with the same MAST root (but possibly different decorators), the +/// correct procedure is chosen on exec #[test] -fn program_with_one_procedure() -> TestResult { - let mut context = TestContext::default(); - let source = - source_file!("proc.foo push.3 push.7 mul end begin push.2 push.3 add exec.foo end"); - let program = context.assemble(source)?; - let foo = context.display_digest_from_cache(&"#exec::foo".parse().unwrap()); - let expected = format!( - "\ -begin - join - span push(2) push(3) add end - proxy.{foo} - end -end" +fn ensure_correct_procedure_selection_on_collision() -> TestResult { + let context = TestContext::default(); + + // if with else + let source = source_file!( + &context, + " + proc.f + add + end + + proc.g + trace.2 + add + end + + begin + if.true + exec.f + else + exec.g + end + end" ); - assert_str_eq!(format!("{program}"), expected); + let program = context.assemble(source)?; + + // Note: those values were taken from adding prints to the assembler at the time of writing. It + // is possible that this test starts failing if we end up ordering procedures differently. + let expected_f_node_id = + MastNodeId::from_u32_safe(1_u32, program.mast_forest().as_ref()).unwrap(); + let expected_g_node_id = + MastNodeId::from_u32_safe(0_u32, program.mast_forest().as_ref()).unwrap(); + + let (exec_f_node_id, exec_g_node_id) = { + let split_node_id = program.entrypoint(); + let split_node = match &program.mast_forest()[split_node_id] { + MastNode::Split(split_node) => split_node, + _ => panic!("expected split node"), + }; + + (split_node.on_true(), split_node.on_false()) + }; + + assert_eq!(program.mast_forest()[expected_f_node_id], program.mast_forest()[exec_f_node_id]); + assert_eq!(program.mast_forest()[expected_g_node_id], program.mast_forest()[exec_g_node_id]); + Ok(()) } -// TODO(pauls): Do we want to support this in the surface MASM syntax? #[test] -#[ignore] -fn program_with_one_empty_procedure() -> TestResult { - let mut context = TestContext::default(); - let source = source_file!("proc.foo end begin exec.foo end"); +fn program_with_one_procedure() -> TestResult { + let context = TestContext::default(); + let source = source_file!( + &context, + "proc.foo push.3 push.7 mul end begin push.2 push.3 add exec.foo end" + ); let program = context.assemble(source)?; - let foo = context.display_digest_from_cache(&"#exec::foo".parse().unwrap()); - let expected = format!( - "\ + let expected = "\ begin - proxy.{foo} -end" - ); - assert_str_eq!(format!("{}", program), expected); + basic_block push(2) push(3) add push(3) push(7) mul end +end"; + assert_str_eq!(format!("{program}"), expected); Ok(()) } #[test] fn program_with_nested_procedure() -> TestResult { - let mut context = TestContext::default(); + let context = TestContext::default(); let source = source_file!( + &context, "\ proc.foo push.3 push.7 mul end \ proc.bar push.5 exec.foo add end \ begin push.2 push.4 add exec.foo push.11 exec.bar sub end" ); let program = context.assemble(source)?; - let foo = context.display_digest_from_cache(&"#exec::foo".parse().unwrap()); - let bar = context.display_digest_from_cache(&"#exec::bar".parse().unwrap()); - let expected = format!( - "\ + let expected = "\ begin - join - join - join - span push(2) push(4) add end - proxy.{foo} - end - join - span push(11) end - proxy.{bar} - end - end - span neg add end + basic_block + push(2) + push(4) + add + push(3) + push(7) + mul + push(11) + push(5) + push(3) + push(7) + mul + add + neg + add end -end" - ); +end"; assert_str_eq!(format!("{program}"), expected); Ok(()) } #[test] fn program_with_proc_locals() -> TestResult { - let mut context = TestContext::default(); + let context = TestContext::default(); let source = source_file!( + &context, "\ proc.foo.1 \ loc_store.0 \ @@ -1263,25 +1802,64 @@ fn program_with_proc_locals() -> TestResult { end" ); let program = context.assemble(source)?; - let foo = context.display_digest_from_cache(&"#exec::foo".parse().unwrap()); - let expected = format!( - "\ + let expected = "\ begin - join - span push(4) push(3) push(2) end - proxy.{foo} + basic_block + push(4) + push(3) + push(2) + push(1) + fmpupdate + pad + fmpadd + mstore + drop + add + pad + fmpadd + mload + mul + push(18446744069414584320) + fmpupdate end -end" - ); +end"; assert_str_eq!(format!("{program}"), expected); Ok(()) } +#[test] +fn program_with_proc_locals_fail() -> TestResult { + let context = TestContext::default(); + let source = source_file!( + &context, + "\ + proc.foo \ + loc_store.0 \ + add \ + loc_load.0 \ + mul \ + end \ + begin \ + push.4 push.3 push.2 \ + exec.foo \ + end" + ); + assert_assembler_diagnostic!( + context, + source, + "number of procedure locals was not set (or set to 0), but local values were used" + ); + + Ok(()) +} + #[test] fn program_with_exported_procedure() -> TestResult { - let mut context = TestContext::default(); - let source = - source_file!("export.foo push.3 push.7 mul end begin push.2 push.3 add exec.foo end"); + let context = TestContext::default(); + let source = source_file!( + &context, + "export.foo push.3 push.7 mul end begin push.2 push.3 add exec.foo end" + ); assert_assembler_diagnostic!( context, @@ -1302,8 +1880,8 @@ fn program_with_exported_procedure() -> TestResult { #[test] fn program_with_dynamic_code_execution() -> TestResult { - let mut context = TestContext::default(); - let source = source_file!("begin dynexec end"); + let context = TestContext::default(); + let source = source_file!(&context, "begin dynexec end"); let program = context.assemble(source)?; let expected = "\ begin @@ -1315,8 +1893,8 @@ end"; #[test] fn program_with_dynamic_code_execution_in_new_context() -> TestResult { - let mut context = TestContext::default(); - let source = source_file!("begin dyncall end"); + let context = TestContext::default(); + let source = source_file!(&context, "begin dyncall end"); let program = context.assemble(source)?; let expected = "\ begin @@ -1331,8 +1909,8 @@ end"; #[test] fn program_with_incorrect_mast_root_length() -> TestResult { - let mut context = TestContext::default(); - let source = source_file!("begin call.0x1234 end"); + let context = TestContext::default(); + let source = source_file!(&context, "begin call.0x1234 end"); assert_assembler_diagnostic!( context, @@ -1348,8 +1926,9 @@ fn program_with_incorrect_mast_root_length() -> TestResult { #[test] fn program_with_invalid_mast_root_chars() { - let mut context = TestContext::default(); + let context = TestContext::default(); let source = source_file!( + &context, "begin call.0xc2545da99d3a1f3f38d957c7893c44d78998d8ea8b11aba7e22c8c2b2a21xyzb end" ); @@ -1366,8 +1945,9 @@ fn program_with_invalid_mast_root_chars() { #[test] fn program_with_invalid_rpo_digest_call() { - let mut context = TestContext::default(); + let context = TestContext::default(); let source = source_file!( + &context, "begin call.0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff end" ); @@ -1384,33 +1964,15 @@ fn program_with_invalid_rpo_digest_call() { #[test] fn program_with_phantom_mast_call() -> TestResult { - let mut context = TestContext::default(); + let context = TestContext::default(); let source = source_file!( + &context, "begin call.0xc2545da99d3a1f3f38d957c7893c44d78998d8ea8b11aba7e22c8c2b2a213dae end" ); let ast = context.parse_program(source)?; - // phantom calls not allowed - let mut assembler = Assembler::default().with_debug_mode(true); - - let mut context = AssemblyContext::for_program(ast.path()).with_phantom_calls(false); - let err = assembler - .assemble_in_context(ast.clone(), &mut context) - .expect_err("expected compilation to fail with phantom calls"); - assert_diagnostic_lines!( - err, - "cannot call phantom procedure: phantom calls are disabled", - regex!(r#",-\[test[\d]+:1:12\]"#), - "1 | begin call.0xc2545da99d3a1f3f38d957c7893c44d78998d8ea8b11aba7e22c8c2b2a213dae end", - " : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^|^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^", - " : `-- the procedure referenced here is not available", - " `----", - " help: mast root is 0xc2545da99d3a1f3f38d957c7893c44d78998d8ea8b11aba7e22c8c2b2a213dae" - ); - - // phantom calls allowed - let mut context = AssemblyContext::for_program(ast.path()).with_phantom_calls(true); - assembler.assemble_in_context(ast, &mut context)?; + let assembler = Assembler::new(context.source_manager()).with_debug_mode(true); + assembler.assemble_program(ast)?; Ok(()) } @@ -1432,37 +1994,38 @@ fn program_with_one_import_and_hex_call() -> TestResult { let mut context = TestContext::default(); let path = MODULE.parse().unwrap(); - let ast = context.parse_module_with_path(path, source_file!(PROCEDURE.to_string()))?; - let ns = ast.namespace().clone(); - let library = DummyLibrary::new(ns, vec![Rc::from(ast)]); + let ast = + context.parse_module_with_path(path, source_file!(&context, PROCEDURE.to_string()))?; + let library = Assembler::new(context.source_manager()) + .assemble_library(core::iter::once(ast)) + .unwrap(); context.add_library(&library)?; - let source = source_file!(format!( - r#" + let source = source_file!( + &context, + format!( + r#" use.{MODULE} begin push.4 push.3 exec.u256::iszero_unsafe - call.0xc2545da99d3a1f3f38d957c7893c44d78998d8ea8b11aba7e22c8c2b2a213dae + call.0x20234ee941e53a15886e733cc8e041198c6e90d2a16ea18ce1030e8c3596dd38 end"# - )); + ) + ); let program = context.assemble(source)?; - let iszero_unsafe = - context.display_digest_from_cache(&"dummy::math::u256::iszero_unsafe".parse().unwrap()); - let expected = format!( - "\ + let expected = "\ begin join join - span push(4) push(3) end - proxy.{iszero_unsafe} + basic_block push(4) push(3) end + external.0xc2545da99d3a1f3f38d957c7893c44d78998d8ea8b11aba7e22c8c2b2a213dae end - call.0xc2545da99d3a1f3f38d957c7893c44d78998d8ea8b11aba7e22c8c2b2a213dae + call.0x20234ee941e53a15886e733cc8e041198c6e90d2a16ea18ce1030e8c3596dd38 end -end" - ); +end"; assert_str_eq!(format!("{program}"), expected); Ok(()) } @@ -1491,21 +2054,26 @@ fn program_with_two_imported_procs_with_same_mast_root() -> TestResult { let mut context = TestContext::default(); let path = MODULE.parse().unwrap(); - let ast = context.parse_module_with_path(path, source_file!(PROCEDURE.to_string()))?; - let ns = ast.namespace().clone(); - let library = DummyLibrary::new(ns, vec![Rc::from(ast)]); + let ast = + context.parse_module_with_path(path, source_file!(&context, PROCEDURE.to_string()))?; + let library = Assembler::new(context.source_manager()) + .assemble_library(core::iter::once(ast)) + .unwrap(); context.add_library(&library)?; - let source = source_file!(format!( - r#" + let source = source_file!( + &context, + format!( + r#" use.{MODULE} begin push.4 push.3 exec.u256::iszero_unsafe exec.u256::iszero_unsafe_dup end"# - )); + ) + ); context.assemble(source)?; Ok(()) } @@ -1542,7 +2110,10 @@ fn program_with_reexported_proc_in_same_library() -> TestResult { "#; let mut context = TestContext::new(); - let ast = Module::parse_str(MODULE.parse().unwrap(), ModuleKind::Library, MODULE_BODY).unwrap(); + let mut parser = Module::parser(ModuleKind::Library); + let ast = parser + .parse_str(MODULE.parse().unwrap(), MODULE_BODY, &context.source_manager()) + .unwrap(); // check docs let docs_checked_eqz = @@ -1558,41 +2129,40 @@ fn program_with_reexported_proc_in_same_library() -> TestResult { "unchecked_eqz checks if the value is zero and returns 1 if it is, 0 otherwise\n" ); - let ref_ast = - Module::parse_str(REF_MODULE.parse().unwrap(), ModuleKind::Library, REF_MODULE_BODY) - .unwrap(); + let mut parser = Module::parser(ModuleKind::Library); + let ref_ast = parser + .parse_str(REF_MODULE.parse().unwrap(), REF_MODULE_BODY, &context.source_manager()) + .unwrap(); - let ns = ref_ast.namespace().clone(); - let library = DummyLibrary::new(ns, vec![Rc::from(ast), Rc::from(ref_ast)]); + let library = Assembler::new(context.source_manager()) + .assemble_library([ast, ref_ast]) + .unwrap(); context.add_library(&library)?; - let source = source_file!(format!( - r#" + let source = source_file!( + &context, + format!( + r#" use.{MODULE} begin push.4 push.3 exec.u256::checked_eqz exec.u256::notchecked_eqz end"# - )); + ) + ); let program = context.assemble(source)?; - let checked_eqz = - context.display_digest_from_cache(&"dummy1::math::u64::checked_eqz".parse().unwrap()); - let notchecked_eqz = - context.display_digest_from_cache(&"dummy1::math::u64::unchecked_eqz".parse().unwrap()); - let expected = format!( - "\ + let expected = "\ begin join join - span push(4) push(3) end - proxy.{checked_eqz} + basic_block push(4) push(3) end + external.0xb9691da1d9b4b364aca0a0990e9f04c446a2faa622c8dd0d8831527dbec61393 end - proxy.{notchecked_eqz} + external.0xcb08c107c81c582788cbf63c99f6b455e11b33bb98ca05fe1cfa17c087dfa8f1 end -end" - ); +end"; assert_str_eq!(format!("{program}"), expected); Ok(()) } @@ -1625,61 +2195,62 @@ fn program_with_reexported_proc_in_another_library() -> TestResult { "#; let mut context = TestContext::default(); - let ast = Module::parse_str(MODULE.parse().unwrap(), ModuleKind::Library, MODULE_BODY).unwrap(); - let ns = ast.namespace().clone(); - let dummy_library_1 = DummyLibrary::new(ns, vec![Rc::from(ast)]); - + let mut parser = Module::parser(ModuleKind::Library); + let source_manager = context.source_manager(); + // We reference code in this module let ref_ast = - Module::parse_str(REF_MODULE.parse().unwrap(), ModuleKind::Library, REF_MODULE_BODY) - .unwrap(); - let ns = ref_ast.namespace().clone(); - let dummy_library_2 = DummyLibrary::new(ns, vec![Rc::from(ref_ast)]); + parser.parse_str(REF_MODULE.parse().unwrap(), REF_MODULE_BODY, &source_manager)?; + // But only exports from this module are exposed by the library + let ast = parser.parse_str(MODULE.parse().unwrap(), MODULE_BODY, &source_manager)?; + + let dummy_library = + Assembler::new(source_manager).with_module(ref_ast)?.assemble_library([ast])?; - context.add_library(&dummy_library_1)?; - context.add_library(&dummy_library_2)?; + // Now we want to use the the library we've compiled + context.add_library(&dummy_library)?; - let source = source_file!(format!( - r#" + let source = source_file!( + &context, + format!( + r#" use.{MODULE} begin push.4 push.3 exec.u256::checked_eqz exec.u256::notchecked_eqz end"# - )); + ) + ); let program = context.assemble(source)?; - let checked_eqz = - context.display_digest_from_cache(&"dummy2::math::u64::checked_eqz".parse().unwrap()); - let notchecked_eqz = - context.display_digest_from_cache(&"dummy2::math::u64::unchecked_eqz".parse().unwrap()); - let expected = format!( - "\ + let expected = "\ begin join join - span push(4) push(3) end - proxy.{checked_eqz} + basic_block push(4) push(3) end + external.0xb9691da1d9b4b364aca0a0990e9f04c446a2faa622c8dd0d8831527dbec61393 end - proxy.{notchecked_eqz} + external.0xcb08c107c81c582788cbf63c99f6b455e11b33bb98ca05fe1cfa17c087dfa8f1 end -end" - ); +end"; assert_str_eq!(format!("{program}"), expected); - // when the re-exported proc is part of a different library and the library is not passed to - // the assembler it should fail + // We also want to assert that exports from the referenced module do not leak let mut context = TestContext::default(); - context.add_library(&dummy_library_1)?; - let source = source_file!(format!( - r#" - use.{MODULE} + context.add_library(dummy_library)?; + + let source = source_file!( + &context, + format!( + r#" + use.{REF_MODULE} begin push.4 push.3 - exec.u256::checked_eqz - exec.u256::notchecked_eqz + exec.u64::checked_eqz + exec.u64::notchecked_eqz end"# - )); + ) + ); assert_assembler_diagnostic!(context, source, "undefined module 'dummy2::math::u64'"); Ok(()) } @@ -1702,13 +2273,15 @@ fn module_alias() -> TestResult { end"#; let mut context = TestContext::default(); - let ast = Module::parse_str(MODULE.parse().unwrap(), ModuleKind::Library, PROCEDURE).unwrap(); - let ns = ast.namespace().clone(); - let library = DummyLibrary::new(ns, vec![Rc::from(ast)]); + let source_manager = context.source_manager(); + let mut parser = Module::parser(ModuleKind::Library); + let ast = parser.parse_str(MODULE.parse().unwrap(), PROCEDURE, &source_manager).unwrap(); + let library = Assembler::new(source_manager).assemble_library([ast]).unwrap(); context.add_library(&library)?; let source = source_file!( + &context, " use.dummy::math::u64->bigint @@ -1720,21 +2293,18 @@ fn module_alias() -> TestResult { ); let program = context.assemble(source)?; - let checked_add = - context.display_digest_from_cache(&"dummy::math::u64::checked_add".parse().unwrap()); - let expected = format!( - "\ + let expected = "\ begin join - span pad incr pad push(2) pad end - proxy.{checked_add} + basic_block pad incr pad push(2) pad end + external.0x3cff5b58a573dc9d25fd3c57130cc57e5b1b381dc58b5ae3594b390c59835e63 end -end" - ); +end"; assert_str_eq!(format!("{program}"), expected); // --- invalid module alias ----------------------------------------------- let source = source_file!( + &context, " use.dummy::math::u64->bigint->invalidname @@ -1755,11 +2325,12 @@ end" " : `-- found a -> here", "3 |", " `----", - r#" help: expected "begin", or "const", or "export", or "proc", or "use", or end of file, or doc comment"# + r#" help: expected "@", or "begin", or "const", or "export", or "proc", or "use", or end of file, or doc comment"# ); // --- duplicate module import -------------------------------------------- let source = source_file!( + &context, " use.dummy::math::u64 use.dummy::math::u64->bigint @@ -1793,6 +2364,7 @@ end" // fail for the time being /* let source = source_file!( + &context, " use.dummy::math::u64->bigint use.dummy::math::u64->bigint2 @@ -1810,9 +2382,10 @@ end" #[test] fn program_with_import_errors() { - let mut context = TestContext::default(); + let context = TestContext::default(); // --- non-existent import ------------------------------------------------ let source = source_file!( + &context, "\ use.std::math::u512 begin \ @@ -1834,6 +2407,7 @@ fn program_with_import_errors() { // --- non-existent procedure in import ----------------------------------- let source = source_file!( + &context, "\ use.std::math::u256 begin \ @@ -1859,12 +2433,12 @@ fn program_with_import_errors() { #[test] fn comment_simple() -> TestResult { - let mut context = TestContext::default(); - let source = source_file!("begin # simple comment \n push.1 push.2 add end"); + let context = TestContext::default(); + let source = source_file!(&context, "begin # simple comment \n push.1 push.2 add end"); let program = context.assemble(source)?; let expected = "\ begin - span pad incr push(2) add end + basic_block pad incr push(2) add end end"; assert_str_eq!(format!("{program}"), expected); Ok(()) @@ -1872,10 +2446,11 @@ end"; #[test] fn comment_in_nested_control_blocks() -> TestResult { - let mut context = TestContext::default(); + let context = TestContext::default(); // if with else let source = source_file!( + &context, "begin \ push.1 push.2 \ if.true \ @@ -1893,40 +2468,40 @@ fn comment_in_nested_control_blocks() -> TestResult { begin join join - span pad incr push(2) end + basic_block pad incr push(2) end if.true join - span add end + basic_block add end while.true - span push(7) push(11) add end + basic_block push(7) push(11) add end end end else join - span mul push(8) push(8) end + basic_block mul push(8) push(8) end if.true - span mul end + basic_block mul end else - span noop end + basic_block noop end end end end end - span push(3) add end + basic_block push(3) add end end end"; - assert_str_eq!(format!("{program}"), expected); + assert_str_eq!(expected, format!("{program}")); Ok(()) } #[test] fn comment_before_program() -> TestResult { - let mut context = TestContext::default(); - let source = source_file!(" # starting comment \n begin push.1 push.2 add end"); + let context = TestContext::default(); + let source = source_file!(&context, " # starting comment \n begin push.1 push.2 add end"); let program = context.assemble(source)?; let expected = "\ begin - span pad incr push(2) add end + basic_block pad incr push(2) add end end"; assert_str_eq!(format!("{program}"), expected); Ok(()) @@ -1934,12 +2509,12 @@ end"; #[test] fn comment_after_program() -> TestResult { - let mut context = TestContext::default(); - let source = source_file!("begin push.1 push.2 add end # closing comment"); + let context = TestContext::default(); + let source = source_file!(&context, "begin push.1 push.2 add end # closing comment"); let program = context.assemble(source)?; let expected = "\ begin - span pad incr push(2) add end + basic_block pad incr push(2) add end end"; assert_str_eq!(format!("{program}"), expected); Ok(()) @@ -1950,48 +2525,48 @@ end"; #[test] fn invalid_empty_program() { - let mut context = TestContext::default(); + let context = TestContext::default(); assert_assembler_diagnostic!( context, - source_file!(""), + source_file!(&context, ""), "unexpected end of file", regex!(r#",-\[test[\d]+:1:1\]"#), "`----", - r#" help: expected "begin", or "const", or "export", or "proc", or "use", or doc comment"# + r#" help: expected "@", or "begin", or "const", or "export", or "proc", or "use", or doc comment"# ); assert_assembler_diagnostic!( context, - source_file!(""), + source_file!(&context, ""), "unexpected end of file", regex!(r#",-\[test[\d]+:1:1\]"#), " `----", - r#" help: expected "begin", or "const", or "export", or "proc", or "use", or doc comment"# + r#" help: expected "@", or "begin", or "const", or "export", or "proc", or "use", or doc comment"# ); } #[test] fn invalid_program_unrecognized_token() { - let mut context = TestContext::default(); + let context = TestContext::default(); assert_assembler_diagnostic!( context, - source_file!("none"), + source_file!(&context, "none"), "invalid syntax", regex!(r#",-\[test[\d]+:1:1\]"#), "1 | none", " : ^^|^", " : `-- found a identifier here", " `----", - r#" help: expected "begin", or "const", or "export", or "proc", or "use", or doc comment"# + r#" help: expected "@", or "begin", or "const", or "export", or "proc", or "use", or doc comment"# ); } #[test] fn invalid_program_unmatched_begin() { - let mut context = TestContext::default(); + let context = TestContext::default(); assert_assembler_diagnostic!( context, - source_file!("begin add"), + source_file!(&context, "begin add"), "unexpected end of file", regex!(r#",-\[test[\d]+:1:10\]"#), "1 | begin add", @@ -2002,24 +2577,24 @@ fn invalid_program_unmatched_begin() { #[test] fn invalid_program_invalid_top_level_token() { - let mut context = TestContext::default(); + let context = TestContext::default(); assert_assembler_diagnostic!( context, - source_file!("begin add end mul"), + source_file!(&context, "begin add end mul"), "invalid syntax", regex!(r#",-\[test[\d]+:1:15\]"#), "1 | begin add end mul", " : ^|^", " : `-- found a mul here", " `----", - r#" help: expected "begin", or "const", or "export", or "proc", or "use", or end of file, or doc comment"# + r#" help: expected "@", or "begin", or "const", or "export", or "proc", or "use", or end of file, or doc comment"# ); } #[test] fn invalid_proc_missing_end_unexpected_begin() { - let mut context = TestContext::default(); - let source = source_file!("proc.foo add mul begin push.1 end"); + let context = TestContext::default(); + let source = source_file!(&context, "proc.foo add mul begin push.1 end"); assert_assembler_diagnostic!( context, source, @@ -2035,8 +2610,8 @@ fn invalid_proc_missing_end_unexpected_begin() { #[test] fn invalid_proc_missing_end_unexpected_proc() { - let mut context = TestContext::default(); - let source = source_file!("proc.foo add mul proc.bar push.3 end begin push.1 end"); + let context = TestContext::default(); + let source = source_file!(&context, "proc.foo add mul proc.bar push.3 end begin push.1 end"); assert_assembler_diagnostic!( context, source, @@ -2052,8 +2627,8 @@ fn invalid_proc_missing_end_unexpected_proc() { #[test] fn invalid_proc_undefined_local() { - let mut context = TestContext::default(); - let source = source_file!("proc.foo add mul end begin push.1 exec.bar end"); + let context = TestContext::default(); + let source = source_file!(&context, "proc.foo add mul end begin push.1 exec.bar end"); assert_assembler_diagnostic!( context, source, @@ -2070,8 +2645,8 @@ fn invalid_proc_undefined_local() { #[test] fn invalid_proc_invalid_numeric_name() { - let mut context = TestContext::default(); - let source = source_file!("proc.123 add mul end begin push.1 exec.123 end"); + let context = TestContext::default(); + let source = source_file!(&context, "proc.123 add mul end begin push.1 exec.123 end"); assert_assembler_diagnostic!( context, source, @@ -2088,8 +2663,9 @@ fn invalid_proc_invalid_numeric_name() { #[test] fn invalid_proc_duplicate_procedure_name() { - let mut context = TestContext::default(); - let source = source_file!("proc.foo add mul end proc.foo push.3 end begin push.1 end"); + let context = TestContext::default(); + let source = + source_file!(&context, "proc.foo add mul end proc.foo push.3 end begin push.1 end"); assert_assembler_diagnostic!( context, source, @@ -2107,8 +2683,8 @@ fn invalid_proc_duplicate_procedure_name() { #[test] fn invalid_if_missing_end_no_else() { - let mut context = TestContext::default(); - let source = source_file!("begin push.1 add if.true mul"); + let context = TestContext::default(); + let source = source_file!(&context, "begin push.1 add if.true mul"); assert_assembler_diagnostic!( context, source, @@ -2122,8 +2698,8 @@ fn invalid_if_missing_end_no_else() { #[test] fn invalid_else_with_no_if() { - let mut context = TestContext::default(); - let source = source_file!("begin push.1 add else mul end"); + let context = TestContext::default(); + let source = source_file!(&context, "begin push.1 add else mul end"); assert_assembler_diagnostic!( context, source, @@ -2136,7 +2712,7 @@ fn invalid_else_with_no_if() { r#" help: expected primitive opcode (e.g. "add"), or "end", or control flow opcode (e.g. "if.true")"# ); - let source = source_file!("begin push.1 while.true add else mul end end"); + let source = source_file!(&context, "begin push.1 while.true add else mul end end"); assert_assembler_diagnostic!( context, source, @@ -2152,9 +2728,10 @@ fn invalid_else_with_no_if() { #[test] fn invalid_unmatched_else_within_if_else() { - let mut context = TestContext::default(); + let context = TestContext::default(); - let source = source_file!("begin push.1 if.true add else mul else push.1 end end end"); + let source = + source_file!(&context, "begin push.1 if.true add else mul else push.1 end end end"); assert_assembler_diagnostic!( context, source, @@ -2170,9 +2747,9 @@ fn invalid_unmatched_else_within_if_else() { #[test] fn invalid_if_else_no_matching_end() { - let mut context = TestContext::default(); + let context = TestContext::default(); - let source = source_file!("begin push.1 add if.true mul else add"); + let source = source_file!(&context, "begin push.1 add if.true mul else add"); assert_assembler_diagnostic!( context, source, @@ -2186,10 +2763,10 @@ fn invalid_if_else_no_matching_end() { #[test] fn invalid_repeat() -> TestResult { - let mut context = TestContext::default(); + let context = TestContext::default(); // unmatched repeat - let source = source_file!("begin push.1 add repeat.10 mul"); + let source = source_file!(&context, "begin push.1 add repeat.10 mul"); assert_assembler_diagnostic!( context, source, @@ -2201,7 +2778,7 @@ fn invalid_repeat() -> TestResult { ); // invalid iter count - let source = source_file!("begin push.1 add repeat.23x3 mul end end"); + let source = source_file!(&context, "begin push.1 add repeat.23x3 mul end end"); assert_assembler_diagnostic!( context, source, @@ -2219,9 +2796,9 @@ fn invalid_repeat() -> TestResult { #[test] fn invalid_while() -> TestResult { - let mut context = TestContext::default(); + let context = TestContext::default(); - let source = source_file!("begin push.1 add while mul end end"); + let source = source_file!(&context, "begin push.1 add while mul end end"); assert_assembler_diagnostic!( context, source, @@ -2234,7 +2811,7 @@ fn invalid_while() -> TestResult { r#" help: expected ".""# ); - let source = source_file!("begin push.1 add while.abc mul end end"); + let source = source_file!(&context, "begin push.1 add while.abc mul end end"); assert_assembler_diagnostic!( context, source, @@ -2247,7 +2824,7 @@ fn invalid_while() -> TestResult { r#" help: expected "true""# ); - let source = source_file!("begin push.1 add while.true mul"); + let source = source_file!(&context, "begin push.1 add while.true mul"); assert_assembler_diagnostic!( context, source, @@ -2260,40 +2837,136 @@ fn invalid_while() -> TestResult { Ok(()) } -// DUMMY LIBRARY +// COMPILED LIBRARIES // ================================================================================================ +#[test] +fn test_compiled_library() { + let context = TestContext::new(); + let mut mod_parser = ModuleParser::new(ModuleKind::Library); + let mod1 = { + let source = source_file!( + &context, + " + proc.internal + push.5 + end + export.foo + push.1 + drop + end + export.bar + exec.internal + drop + end + " + ); + mod_parser.parse(LibraryPath::new("mylib::mod1").unwrap(), source).unwrap() + }; -struct DummyLibrary { - namespace: LibraryNamespace, - modules: Vec>, - dependencies: Vec, -} + let mod2 = { + let source = source_file!( + &context, + " + export.foo + push.7 + add.5 + end + # Same definition as mod1::foo + export.bar + push.1 + drop + end + " + ); + mod_parser.parse(LibraryPath::new("mylib::mod2").unwrap(), source).unwrap() + }; + + let compiled_library = { + let assembler = Assembler::new(context.source_manager()); + assembler.assemble_library([mod1, mod2]).unwrap() + }; + + assert_eq!(compiled_library.exports().count(), 4); + + // Compile program that uses compiled library + let mut assembler = Assembler::new(context.source_manager()); + + assembler.add_library(&compiled_library).unwrap(); + + let program_source = " + use.mylib::mod1 + use.mylib::mod2 + + proc.foo + push.1 + drop + end + + begin + exec.mod1::foo + exec.mod1::bar + exec.mod2::foo + exec.mod2::bar + exec.foo + end + "; -impl DummyLibrary { - fn new(namespace: LibraryNamespace, modules: Vec>) -> Self { - Self { - namespace, - modules, - dependencies: Vec::new(), - } - } + let _program = assembler.assemble_program(program_source).unwrap(); } -impl Library for DummyLibrary { - fn root_ns(&self) -> &LibraryNamespace { - &self.namespace - } +#[test] +fn test_reexported_proc_with_same_name_as_local_proc_diff_locals() { + let context = TestContext::new(); + let mut mod_parser = ModuleParser::new(ModuleKind::Library); + let mod1 = { + let source = source_file!( + &context, + "export.foo.2 + push.1 + drop + end + " + ); + mod_parser.parse(LibraryPath::new("test::mod1").unwrap(), source).unwrap() + }; + + let mod2 = { + let source = source_file!( + &context, + "use.test::mod1 + export.foo + exec.mod1::foo + end + " + ); + mod_parser.parse(LibraryPath::new("test::mod2").unwrap(), source).unwrap() + }; + + let compiled_library = { + let assembler = Assembler::new(context.source_manager()); + assembler.assemble_library([mod1, mod2]).unwrap() + }; + + assert_eq!(compiled_library.exports().count(), 2); + + // Compile program that uses compiled library + let mut assembler = Assembler::new(context.source_manager()); + + assembler.add_library(&compiled_library).unwrap(); - fn version(&self) -> &Version { - const MIN: Version = Version::min(); - &MIN - } + let program_source = " + use.test::mod1 + use.test::mod2 - fn modules(&self) -> impl ExactSizeIterator + '_ { - self.modules.iter().map(|p| p.as_ref()) - } + proc.foo.1 + exec.mod1::foo + exec.mod2::foo + end + + begin + exec.foo + end + "; - fn dependencies(&self) -> &[LibraryNamespace] { - &self.dependencies - } + let _program = assembler.assemble_program(program_source).unwrap(); } diff --git a/core/Cargo.toml b/core/Cargo.toml index 78460f9772..3dd8eeea4b 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -1,16 +1,17 @@ [package] name = "miden-core" -version = "0.8.0" +version = "0.10.5" description = "Miden VM core components" -authors = ["miden contributors"] +documentation = "https://docs.rs/miden-core/0.10.5" readme = "README.md" -license = "MIT" -repository = "https://github.com/0xPolygonMiden/miden-vm" -documentation = "https://docs.rs/miden-core/0.8.0" categories = ["emulators", "no-std"] keywords = ["instruction-set", "miden", "program"] -edition = "2021" -rust-version = "1.75" +license.workspace = true +authors.workspace = true +homepage.workspace = true +repository.workspace = true +rust-version.workspace = true +edition.workspace = true [lib] bench = false @@ -18,7 +19,10 @@ doctest = false [features] default = ["std"] +diagnostics = ["dep:miette"] std = [ + "dep:parking_lot", + "memchr/std", "miden-crypto/std", "miden-formatting/std", "math/std", @@ -27,13 +31,29 @@ std = [ ] [dependencies] +lock_api = { version = "0.4", features = ["arc_lock"] } math = { package = "winter-math", version = "0.9", default-features = false } -#miden-crypto = { version = "0.9", default-features = false } +memchr = { version = "2.7", default-features = false } +#miden-crypto = { version = "0.10", default-features = false } miden-crypto = { git = "https://github.com/0xPolygonMiden/crypto", branch = "al-rpo-new-padding-rule", default-features = false } miden-formatting = { version = "0.1", default-features = false } -thiserror = { version = "1.0", git = "https://github.com/bitwalker/thiserror", branch = "no-std", default-features = false } +miette = { package = "miden-miette", version = "7.1", default-features = false, features = [ + "fancy-no-syscall", + "derive" +], optional = true } +num-derive = { version = "0.4", default-features = false } +num-traits = { version = "0.2", default-features = false } +parking_lot = { version = "0.12", optional = true } +thiserror = { package = "miden-thiserror", version = "1.0", default-features = false } winter-utils = { package = "winter-utils", version = "0.9", default-features = false } [dev-dependencies] -proptest = "1.3" +loom = "0.7" +proptest = "1.5" rand_utils = { version = "0.9", package = "winter-rand-utils" } + +[target.'cfg(loom)'.dependencies] +loom = "0.7" + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(loom)'] } diff --git a/core/README.md b/core/README.md index 3d2175b6b1..9d8ff7387a 100644 --- a/core/README.md +++ b/core/README.md @@ -11,3 +11,7 @@ This crate contains core components used by Miden VM. These components include: ## License This project is [MIT licensed](../LICENSE). + +## Acknowledgements + +The `racy_lock` module found under `core/src/utils/sync` is based on the [once_cell](https://crates.io/crates/once_cell) crate's implementation of `race::OnceBox`. diff --git a/core/src/chiplets/hasher.rs b/core/src/chiplets/hasher.rs index 3649171d1b..a785f0b5e3 100644 --- a/core/src/chiplets/hasher.rs +++ b/core/src/chiplets/hasher.rs @@ -1,6 +1,5 @@ //! TODO: add docs use super::Felt; - pub use crate::crypto::hash::{Rpo256 as Hasher, RpoDigest as Digest}; /// Number of field element needed to represent the sponge state for the hash function. diff --git a/core/src/debuginfo/location.rs b/core/src/debuginfo/location.rs new file mode 100644 index 0000000000..47f8132f64 --- /dev/null +++ b/core/src/debuginfo/location.rs @@ -0,0 +1,71 @@ +use alloc::sync::Arc; +use core::{fmt, ops::Range}; + +use super::ByteIndex; + +/// A [Location] represents file and span information for portability across source managers +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Location { + /// The path to the source file in which the relevant source code can be found + pub path: Arc, + /// The starting byte index (inclusive) of this location + pub start: ByteIndex, + /// The ending byte index (exclusive) of this location + pub end: ByteIndex, +} + +impl Location { + /// Creates a new [Location]. + pub const fn new(path: Arc, start: ByteIndex, end: ByteIndex) -> Self { + Self { path, start, end } + } + + /// Get the name (or path) of the source file + pub fn path(&self) -> Arc { + self.path.clone() + } + + /// Returns the byte range represented by this location + pub const fn range(&self) -> Range { + self.start..self.end + } +} + +/// A [FileLineCol] represents traditional file/line/column information for use in rendering. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct FileLineCol { + /// The path to the source file in which the relevant source code can be found + pub path: Arc, + /// The one-indexed number of the line to which this location refers + pub line: u32, + /// The one-indexed column of the line on which this location starts + pub column: u32, +} + +impl FileLineCol { + /// Creates a new [Location]. + pub const fn new(path: Arc, line: u32, column: u32) -> Self { + Self { path, line, column } + } + + /// Get the name (or path) of the source file + pub fn path(&self) -> Arc { + self.path.clone() + } + + /// Returns the line of the location. + pub const fn line(&self) -> u32 { + self.line + } + + /// Moves the column by the given offset. + pub fn move_column(&mut self, offset: u32) { + self.column += offset; + } +} + +impl fmt::Display for FileLineCol { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "[{}@{}:{}]", &self.path, self.line, self.column) + } +} diff --git a/core/src/debuginfo/mod.rs b/core/src/debuginfo/mod.rs new file mode 100644 index 0000000000..27772806de --- /dev/null +++ b/core/src/debuginfo/mod.rs @@ -0,0 +1,15 @@ +mod location; +mod source_file; +mod source_manager; +mod span; + +#[cfg(feature = "std")] +pub use self::source_manager::SourceManagerExt; +pub use self::{ + location::{FileLineCol, Location}, + source_file::{ + ByteIndex, ByteOffset, ColumnIndex, LineIndex, SourceContent, SourceFile, SourceFileRef, + }, + source_manager::{DefaultSourceManager, SourceId, SourceManager}, + span::{SourceSpan, Span, Spanned}, +}; diff --git a/core/src/debuginfo/source_file.rs b/core/src/debuginfo/source_file.rs new file mode 100644 index 0000000000..2f7fc69f76 --- /dev/null +++ b/core/src/debuginfo/source_file.rs @@ -0,0 +1,770 @@ +use alloc::{boxed::Box, sync::Arc, vec::Vec}; +use core::{fmt, num::NonZeroU32, ops::Range}; + +use super::{FileLineCol, SourceId, SourceSpan}; + +// SOURCE FILE +// ================================================================================================ + +/// A [SourceFile] represents a single file stored in a [super::SourceManager] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct SourceFile { + /// The unique identifier allocated for this [SourceFile] by its owning [super::SourceManager] + id: SourceId, + /// The file content + content: SourceContent, +} + +#[cfg(feature = "diagnostics")] +impl miette::SourceCode for SourceFile { + fn read_span<'a>( + &'a self, + span: &miette::SourceSpan, + context_lines_before: usize, + context_lines_after: usize, + ) -> Result + 'a>, miette::MietteError> { + let mut start = + u32::try_from(span.offset()).map_err(|_| miette::MietteError::OutOfBounds)?; + let len = u32::try_from(span.len()).map_err(|_| miette::MietteError::OutOfBounds)?; + let mut end = start.checked_add(len).ok_or(miette::MietteError::OutOfBounds)?; + if context_lines_before > 0 { + let line_index = self.content.line_index(start.into()); + let start_line_index = line_index.saturating_sub(context_lines_before as u32); + start = self.content.line_start(start_line_index).map(|idx| idx.to_u32()).unwrap_or(0); + } + if context_lines_after > 0 { + let line_index = self.content.line_index(end.into()); + let end_line_index = line_index + .checked_add(context_lines_after as u32) + .ok_or(miette::MietteError::OutOfBounds)?; + end = self + .content + .line_range(end_line_index) + .map(|range| range.end.to_u32()) + .unwrap_or_else(|| self.content.source_range().end.to_u32()); + } + Ok(Box::new(ScopedSourceFileRef { + file: self, + span: miette::SourceSpan::new((start as usize).into(), end.abs_diff(start) as usize), + })) + } +} + +impl SourceFile { + /// Create a new [SourceFile] from its raw components + pub fn new(id: SourceId, path: impl Into>, content: impl Into>) -> Self { + let path = path.into(); + let content = SourceContent::new(path, content.into()); + Self { id, content } + } + + pub(super) fn from_raw_parts(id: SourceId, content: SourceContent) -> Self { + Self { id, content } + } + + /// Get the [SourceId] associated with this file + pub const fn id(&self) -> SourceId { + self.id + } + + /// Get the name of this source file + pub fn name(&self) -> Arc { + self.content.name() + } + + /// Get the path of this source file as a [std::path::Path] + #[cfg(feature = "std")] + #[inline] + pub fn path(&self) -> &std::path::Path { + self.content.path() + } + + /// Returns a reference to the underlying [SourceContent] + pub fn content(&self) -> &SourceContent { + &self.content + } + + /// Returns the number of lines in this file + pub fn line_count(&self) -> usize { + self.content.last_line_index().to_usize() + 1 + } + + /// Returns the number of bytes in this file + pub fn len(&self) -> usize { + self.content.len() + } + + /// Returns true if this file is empty + pub fn is_empty(&self) -> bool { + self.content.is_empty() + } + + /// Get the underlying content of this file + #[inline(always)] + pub fn as_str(&self) -> &str { + self.content.as_str() + } + + /// Get the underlying content of this file as a byte slice + #[inline(always)] + pub fn as_bytes(&self) -> &[u8] { + self.content.as_bytes() + } + + /// Returns a [SourceSpan] covering the entirety of this file + #[inline] + pub fn source_span(&self) -> SourceSpan { + let range = self.content.source_range(); + SourceSpan::new(self.id, range.start.0..range.end.0) + } + + /// Returns a subset of the underlying content as a string slice. + /// + /// The bounds of the given span are character indices, _not_ byte indices. + /// + /// Returns `None` if the given span is out of bounds + #[inline(always)] + pub fn source_slice(&self, span: impl Into>) -> Option<&str> { + self.content.source_slice(span) + } + + /// Returns a [SourceFileRef] corresponding to the bytes contained in the specified span. + pub fn slice(self: &Arc, span: impl Into>) -> SourceFileRef { + SourceFileRef::new(Arc::clone(self), span) + } + + /// Get a [SourceSpan] which points to the first byte of the character at `column` on `line` + /// + /// Returns `None` if the given line/column is out of bounds for this file. + pub fn line_column_to_span(&self, line: u32, column: u32) -> Option { + let line_index = LineIndex::from(line.saturating_sub(1)); + let column_index = ColumnIndex::from(column.saturating_sub(1)); + let offset = self.content.line_column_to_offset(line_index, column_index)?; + Some(SourceSpan::at(self.id, offset.0)) + } + + /// Get a [FileLineCol] equivalent to the start of the given [SourceSpan] + pub fn location(&self, span: SourceSpan) -> FileLineCol { + assert_eq!(span.source_id(), self.id, "mismatched source ids"); + + self.content + .location(ByteIndex(span.into_range().start)) + .expect("invalid source span: starting byte is out of bounds") + } +} + +impl AsRef for SourceFile { + #[inline(always)] + fn as_ref(&self) -> &str { + self.as_str() + } +} + +impl AsRef<[u8]> for SourceFile { + #[inline(always)] + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } +} + +#[cfg(feature = "std")] +impl AsRef for SourceFile { + #[inline(always)] + fn as_ref(&self) -> &std::path::Path { + self.path() + } +} + +// SOURCE FILE REF +// ================================================================================================ + +/// A reference to a specific spanned region of a [SourceFile], that provides access to the actual +/// [SourceFile], but scoped to the span it was created with. +/// +/// This is useful in error types that implement [miette::Diagnostic], as it contains all of the +/// data necessary to render the source code being referenced, without a [super::SourceManager] on +/// hand. +#[derive(Debug, Clone)] +pub struct SourceFileRef { + file: Arc, + span: SourceSpan, +} + +impl SourceFileRef { + /// Create a [SourceFileRef] from a [SourceFile] and desired span (in bytes) + /// + /// The given span will be constrained to the bytes of `file`, so a span that reaches out of + /// bounds will have its end bound set to the last byte of the file. + pub fn new(file: Arc, span: impl Into>) -> Self { + let span = span.into(); + let end = core::cmp::min(span.end, file.len() as u32); + let span = SourceSpan::new(file.id(), span.start..end); + Self { file, span } + } + + /// Returns a ref-counted handle to the underlying [SourceFile] + pub fn source_file(&self) -> Arc { + self.file.clone() + } + + /// Returns the name of the file this [SourceFileRef] is selecting, as a [std::path::Path] + #[cfg(feature = "std")] + pub fn path(&self) -> &std::path::Path { + self.file.path() + } + + /// Returns the name of the file this [SourceFileRef] is selecting + pub fn name(&self) -> &str { + self.file.content.path.as_ref() + } + + /// Returns the [SourceSpan] selected by this [SourceFileRef] + pub const fn span(&self) -> SourceSpan { + self.span + } + + /// Returns the underlying `str` selected by this [SourceFileRef] + pub fn as_str(&self) -> &str { + self.file.source_slice(self.span).unwrap() + } + + /// Returns the underlying bytes selected by this [SourceFileRef] + #[inline] + pub fn as_bytes(&self) -> &[u8] { + self.as_str().as_bytes() + } + + /// Returns the number of bytes represented by the subset of the underlying file that is covered + /// by this [SourceFileRef] + pub fn len(&self) -> usize { + self.span.len() + } + + /// Returns true if this selection is empty + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +impl Eq for SourceFileRef {} + +impl PartialEq for SourceFileRef { + fn eq(&self, other: &Self) -> bool { + self.as_str() == other.as_str() + } +} + +impl Ord for SourceFileRef { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.as_str().cmp(other.as_str()) + } +} + +impl PartialOrd for SourceFileRef { + #[inline(always)] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl core::hash::Hash for SourceFileRef { + fn hash(&self, state: &mut H) { + self.span.hash(state); + self.as_str().hash(state); + } +} + +impl AsRef for SourceFileRef { + #[inline(always)] + fn as_ref(&self) -> &str { + self.as_str() + } +} + +impl AsRef<[u8]> for SourceFileRef { + #[inline(always)] + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } +} + +#[cfg(feature = "diagnostics")] +impl From<&SourceFileRef> for miette::SourceSpan { + fn from(source: &SourceFileRef) -> Self { + source.span.into() + } +} + +/// Used to implement [miette::SpanContents] for [SourceFile] and [SourceFileRef] +#[cfg(feature = "diagnostics")] +struct ScopedSourceFileRef<'a> { + file: &'a SourceFile, + span: miette::SourceSpan, +} + +#[cfg(feature = "diagnostics")] +impl<'a> miette::SpanContents<'a> for ScopedSourceFileRef<'a> { + #[inline] + fn data(&self) -> &'a [u8] { + let start = self.span.offset(); + let end = start + self.span.len(); + &self.file.as_bytes()[start..end] + } + + #[inline] + fn span(&self) -> &miette::SourceSpan { + &self.span + } + + fn line(&self) -> usize { + let offset = self.span.offset() as u32; + self.file.content.line_index(offset.into()).to_usize() + } + + fn column(&self) -> usize { + let start = self.span.offset() as u32; + let end = start + self.span.len() as u32; + let span = SourceSpan::new(self.file.id(), start..end); + let loc = self.file.location(span); + loc.column.saturating_sub(1) as usize + } + + #[inline] + fn line_count(&self) -> usize { + self.file.line_count() + } + + #[inline] + fn name(&self) -> Option<&str> { + Some(self.file.content.path.as_ref()) + } + + #[inline] + fn language(&self) -> Option<&str> { + None + } +} + +#[cfg(feature = "diagnostics")] +impl miette::SourceCode for SourceFileRef { + #[inline] + fn read_span<'a>( + &'a self, + span: &miette::SourceSpan, + context_lines_before: usize, + context_lines_after: usize, + ) -> Result + 'a>, miette::MietteError> { + self.file.read_span(span, context_lines_before, context_lines_after) + } +} + +// SOURCE CONTENT +// ================================================================================================ + +/// Represents key information about a source file and its content: +/// +/// * The path to the file (or its name, in the case of virtual files) +/// * The content of the file +/// * The byte offsets of every line in the file, for use in looking up line/column information +#[derive(Clone)] +pub struct SourceContent { + /// The path (or name) of this file + path: Arc, + /// The underlying content of this file + content: Box, + /// The byte offsets for each line in this file + line_starts: Box<[ByteIndex]>, +} + +impl fmt::Debug for SourceContent { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("SourceContent") + .field("path", &self.path) + .field("size_in_bytes", &self.content.len()) + .field("line_count", &self.line_starts.len()) + .field("content", &self.content) + .finish() + } +} + +impl Eq for SourceContent {} + +impl PartialEq for SourceContent { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.path == other.path && self.content == other.content + } +} + +impl Ord for SourceContent { + #[inline] + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.path.cmp(&other.path).then_with(|| self.content.cmp(&other.content)) + } +} + +impl PartialOrd for SourceContent { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl core::hash::Hash for SourceContent { + fn hash(&self, state: &mut H) { + self.path.hash(state); + self.content.hash(state); + } +} + +impl SourceContent { + /// Create a new [SourceContent] from the (possibly virtual) file path, and its content as a + /// UTF-8 string. + /// + /// When created, the line starts for this file will be computed, which requires scanning the + /// file content once. + pub fn new(path: Arc, content: Box) -> Self { + let bytes = content.as_bytes(); + + assert!( + bytes.len() < u32::MAX as usize, + "unsupported source file: current maximum supported length in bytes is 2^32" + ); + + let line_starts = core::iter::once(ByteIndex(0)) + .chain(memchr::memchr_iter(b'\n', content.as_bytes()).filter_map(|mut offset| { + // Determine if the newline has any preceding escapes + let mut preceding_escapes = 0; + let line_start = offset + 1; + while let Some(prev_offset) = offset.checked_sub(1) { + if bytes[prev_offset] == b'\\' { + offset = prev_offset; + preceding_escapes += 1; + continue; + } + break; + } + + // If the newline is escaped, do not count it as a new line + let is_escaped = preceding_escapes > 0 && preceding_escapes % 2 != 0; + if is_escaped { + None + } else { + Some(ByteIndex(line_start as u32)) + } + })) + .collect::>() + .into_boxed_slice(); + + Self { path, content, line_starts } + } + + /// Get the name of this source file + pub fn name(&self) -> Arc { + self.path.clone() + } + + /// Get the name of this source file as a [std::path::Path] + #[cfg(feature = "std")] + #[inline] + pub fn path(&self) -> &std::path::Path { + std::path::Path::new(self.path.as_ref()) + } + + /// Returns the underlying content as a string slice + #[inline(always)] + pub fn as_str(&self) -> &str { + self.content.as_ref() + } + + /// Returns the underlying content as a byte slice + #[inline(always)] + pub fn as_bytes(&self) -> &[u8] { + self.content.as_bytes() + } + + /// Returns the size in bytes of the underlying content + #[inline(always)] + pub fn len(&self) -> usize { + self.content.len() + } + + /// Returns true if the underlying content is empty + #[inline(always)] + pub fn is_empty(&self) -> bool { + self.content.is_empty() + } + + /// Returns the range of valid byte indices for this file + #[inline] + pub fn source_range(&self) -> Range { + ByteIndex(0)..ByteIndex(self.content.len() as u32) + } + + /// Returns a subset of the underlying content as a string slice. + /// + /// The bounds of the given span are character indices, _not_ byte indices. + /// + /// Returns `None` if the given span is out of bounds + #[inline(always)] + pub fn source_slice(&self, span: impl Into>) -> Option<&str> { + self.as_str().get(span.into()) + } + + /// Returns the byte index at which the line corresponding to `line_index` starts + /// + /// Returns `None` if the given index is out of bounds + pub fn line_start(&self, line_index: LineIndex) -> Option { + self.line_starts.get(line_index.to_usize()).copied().map(ByteIndex::from) + } + + /// Returns the index of the last line in this file + #[inline] + pub fn last_line_index(&self) -> LineIndex { + LineIndex(self.line_starts.len() as u32) + } + + /// Get the range of byte indices covered by the given line + pub fn line_range(&self, line_index: LineIndex) -> Option> { + let line_start = self.line_start(line_index)?; + match self.line_start(line_index + 1) { + Some(line_end) => Some(line_start..line_end), + None => Some(line_start..ByteIndex(self.content.len() as u32)), + } + } + + /// Get the index of the line to which `byte_index` belongs + pub fn line_index(&self, byte_index: ByteIndex) -> LineIndex { + match self.line_starts.binary_search(&byte_index) { + Ok(line) => LineIndex(line as u32), + Err(next_line) => LineIndex(next_line as u32 - 1), + } + } + + /// Get the [ByteIndex] corresponding to the given line and column indices. + /// + /// Returns `None` if the line or column indices are out of bounds. + pub fn line_column_to_offset( + &self, + line_index: LineIndex, + column_index: ColumnIndex, + ) -> Option { + let column_index = column_index.to_usize(); + let line_span = self.line_range(line_index)?; + let line_src = self + .content + .get(line_span.start.to_usize()..line_span.end.to_usize()) + .expect("invalid line boundaries: invalid utf-8"); + if line_src.len() < column_index { + return None; + } + let (pre, _) = line_src.split_at(column_index); + let start = line_span.start; + Some(start + ByteOffset::from_str_len(pre)) + } + + /// Get a [FileLineCol] corresponding to the line/column in this file at which `byte_index` + /// occurs + pub fn location(&self, byte_index: ByteIndex) -> Option { + let line_index = self.line_index(byte_index); + let line_start_index = self.line_start(line_index)?; + let line_src = self.content.get(line_start_index.to_usize()..byte_index.to_usize())?; + let column_index = ColumnIndex::from(line_src.chars().count() as u32); + Some(FileLineCol { + path: self.path.clone(), + line: line_index.number().get(), + column: column_index.number().get(), + }) + } +} + +// SOURCE CONTENT INDICES +// ================================================================================================ + +/// An index representing the offset in bytes from the start of a source file +#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ByteIndex(u32); +impl ByteIndex { + /// Create a [ByteIndex] from a raw `u32` index + pub const fn new(index: u32) -> Self { + Self(index) + } + + /// Get the raw index as a usize + #[inline(always)] + pub const fn to_usize(self) -> usize { + self.0 as usize + } + + /// Get the raw index as a u32 + #[inline(always)] + pub const fn to_u32(self) -> u32 { + self.0 + } +} +impl core::ops::Add for ByteIndex { + type Output = ByteIndex; + + fn add(self, rhs: ByteOffset) -> Self { + Self((self.0 as i64 + rhs.0) as u32) + } +} +impl core::ops::Add for ByteIndex { + type Output = ByteIndex; + + fn add(self, rhs: u32) -> Self { + Self(self.0 + rhs) + } +} +impl core::ops::AddAssign for ByteIndex { + fn add_assign(&mut self, rhs: ByteOffset) { + *self = *self + rhs; + } +} +impl core::ops::AddAssign for ByteIndex { + fn add_assign(&mut self, rhs: u32) { + self.0 += rhs; + } +} +impl core::ops::Sub for ByteIndex { + type Output = ByteIndex; + + fn sub(self, rhs: ByteOffset) -> Self { + Self((self.0 as i64 - rhs.0) as u32) + } +} +impl core::ops::Sub for ByteIndex { + type Output = ByteIndex; + + fn sub(self, rhs: u32) -> Self { + Self(self.0 - rhs) + } +} +impl core::ops::SubAssign for ByteIndex { + fn sub_assign(&mut self, rhs: ByteOffset) { + *self = *self - rhs; + } +} +impl core::ops::SubAssign for ByteIndex { + fn sub_assign(&mut self, rhs: u32) { + self.0 -= rhs; + } +} +impl From for ByteIndex { + fn from(index: u32) -> Self { + Self(index) + } +} +impl From for u32 { + fn from(index: ByteIndex) -> Self { + index.0 + } +} + +/// An offset in bytes relative to some [ByteIndex] +#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct ByteOffset(i64); +impl ByteOffset { + /// Compute the offset in bytes represented by the given `char` + pub fn from_char_len(c: char) -> ByteOffset { + Self(c.len_utf8() as i64) + } + + /// Compute the offset in bytes represented by the given `str` + pub fn from_str_len(s: &str) -> ByteOffset { + Self(s.len() as i64) + } +} +impl core::ops::Add for ByteOffset { + type Output = ByteOffset; + + fn add(self, rhs: Self) -> Self { + Self(self.0 + rhs.0) + } +} +impl core::ops::AddAssign for ByteOffset { + fn add_assign(&mut self, rhs: Self) { + self.0 += rhs.0; + } +} +impl core::ops::Sub for ByteOffset { + type Output = ByteOffset; + + fn sub(self, rhs: Self) -> Self { + Self(self.0 - rhs.0) + } +} +impl core::ops::SubAssign for ByteOffset { + fn sub_assign(&mut self, rhs: Self) { + self.0 -= rhs.0; + } +} + +/// A zero-indexed line number +#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct LineIndex(u32); +impl LineIndex { + /// Get a one-indexed number for display + pub const fn number(self) -> NonZeroU32 { + unsafe { NonZeroU32::new_unchecked(self.0 + 1) } + } + + /// Get the raw index as a usize + #[inline(always)] + pub const fn to_usize(self) -> usize { + self.0 as usize + } + + /// Add `offset` to this index, returning `None` on overflow + pub fn checked_add(self, offset: u32) -> Option { + self.0.checked_add(offset).map(Self) + } + + /// Subtract `offset` from this index, returning `None` on underflow + pub fn checked_sub(self, offset: u32) -> Option { + self.0.checked_sub(offset).map(Self) + } + + /// Add `offset` to this index, saturating to `u32::MAX` on overflow + pub const fn saturating_add(self, offset: u32) -> Self { + Self(self.0.saturating_add(offset)) + } + + /// Subtract `offset` from this index, saturating to `0` on overflow + pub const fn saturating_sub(self, offset: u32) -> Self { + Self(self.0.saturating_sub(offset)) + } +} +impl From for LineIndex { + fn from(index: u32) -> Self { + Self(index) + } +} +impl core::ops::Add for LineIndex { + type Output = LineIndex; + + fn add(self, rhs: u32) -> Self { + Self(self.0 + rhs) + } +} + +/// A zero-indexed column number +#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct ColumnIndex(u32); +impl ColumnIndex { + /// Get a one-indexed number for display + pub const fn number(self) -> NonZeroU32 { + unsafe { NonZeroU32::new_unchecked(self.0 + 1) } + } + + /// Get the raw index as a usize + #[inline(always)] + pub const fn to_usize(self) -> usize { + self.0 as usize + } +} +impl From for ColumnIndex { + fn from(index: u32) -> Self { + Self(index) + } +} diff --git a/core/src/debuginfo/source_manager.rs b/core/src/debuginfo/source_manager.rs new file mode 100644 index 0000000000..e88e96d3dd --- /dev/null +++ b/core/src/debuginfo/source_manager.rs @@ -0,0 +1,369 @@ +use alloc::{ + collections::BTreeMap, + string::{String, ToString}, + sync::Arc, + vec::Vec, +}; + +use super::*; + +// SOURCE ID +// ================================================================================================ + +/// A [SourceId] represents the index/identifier associated with a unique source file in a +/// [SourceManager] implementation. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct SourceId(u32); + +impl Default for SourceId { + fn default() -> Self { + Self::UNKNOWN + } +} + +impl SourceId { + pub const UNKNOWN: Self = Self(u32::MAX); + + /// Create a new [SourceId] from a `u32` value, but assert if the value is reserved + pub fn new(id: u32) -> Self { + assert_ne!(id, u32::MAX, "u32::MAX is a reserved value for SourceId::default()/UNKNOWN"); + + Self(id) + } + + /// Create a new [SourceId] from a raw `u32` value + #[inline(always)] + pub const fn new_unchecked(id: u32) -> Self { + Self(id) + } + + #[inline(always)] + pub const fn to_usize(self) -> usize { + self.0 as usize + } + + #[inline(always)] + pub const fn to_u32(self) -> u32 { + self.0 + } + + pub const fn is_unknown(&self) -> bool { + self.0 == u32::MAX + } +} + +impl TryFrom for SourceId { + type Error = (); + + #[inline] + fn try_from(id: usize) -> Result { + match u32::try_from(id) { + Ok(n) if n < u32::MAX => Ok(Self(n)), + _ => Err(()), + } + } +} + +// SOURCE MANAGER +// ================================================================================================ + +/// The set of errors which may be raised by a [SourceManager] +#[derive(Debug, thiserror::Error)] +#[non_exhaustive] +pub enum SourceManagerError { + /// A [SourceId] was provided to a [SourceManager] which was allocated by a different + /// [SourceManager] + #[error("attempted to use an invalid source id")] + InvalidSourceId, + /// An attempt was made to read content using invalid byte indices + #[error("attempted to read content out of bounds")] + InvalidBounds, + /// An attempt to load a source file failed due to an I/O error + #[cfg(feature = "std")] + #[error(transparent)] + LoadFailed(#[from] std::io::Error), +} + +pub trait SourceManager { + /// Returns true if `file` is managed by this source manager + fn is_manager_of(&self, file: &SourceFile) -> bool { + match self.get(file.id()) { + Ok(found) => core::ptr::addr_eq(Arc::as_ptr(&found), file), + Err(_) => false, + } + } + /// Copies `file` into this source manager (if not already managed by this manager). + /// + /// The returned source file is guaranteed to be owned by this manager. + fn copy_into(&self, file: &SourceFile) -> Arc { + if let Ok(found) = self.get(file.id()) { + if core::ptr::addr_eq(Arc::as_ptr(&found), file) { + return found; + } + } + self.load_from_raw_parts(file.name(), file.content().clone()) + } + /// Load the given `content` into this [SourceManager] with `name` + fn load(&self, name: &str, content: String) -> Arc { + let name = Arc::from(name.to_string().into_boxed_str()); + let content = SourceContent::new(Arc::clone(&name), content.into_boxed_str()); + self.load_from_raw_parts(name, content) + } + /// Load content into this [SourceManager] from raw [SourceFile] components + fn load_from_raw_parts(&self, name: Arc, content: SourceContent) -> Arc; + /// Get the [SourceFile] corresponding to `id` + fn get(&self, id: SourceId) -> Result, SourceManagerError>; + /// Get the most recent [SourceFile] whose path is `path` + fn get_by_path(&self, path: &str) -> Option> { + self.find(path).and_then(|id| self.get(id).ok()) + } + /// Search for a source file named `name`, and return its [SourceId] if found. + fn find(&self, name: &str) -> Option; + /// Convert a [FileLineCol] to an equivalent [SourceSpan], if the referenced file is available + fn file_line_col_to_span(&self, loc: FileLineCol) -> Option; + /// Convert a [SourceSpan] to an equivalent [FileLineCol], if the span is valid + fn file_line_col(&self, span: SourceSpan) -> Result; + /// Convert a [Location] to an equivalent [SourceSpan], if the referenced file is available + fn location_to_span(&self, loc: Location) -> Option; + /// Convert a [SourceSpan] to an equivalent [Location], if the span is valid + fn location(&self, span: SourceSpan) -> Result; + /// Get the source associated with `id` as a string slice + fn source(&self, id: SourceId) -> Result<&str, SourceManagerError>; + /// Get the source corresponding to `span` as a string slice + fn source_slice(&self, span: SourceSpan) -> Result<&str, SourceManagerError>; +} + +impl SourceManager for Arc { + #[inline(always)] + fn is_manager_of(&self, file: &SourceFile) -> bool { + (**self).is_manager_of(file) + } + #[inline(always)] + fn copy_into(&self, file: &SourceFile) -> Arc { + (**self).copy_into(file) + } + #[inline(always)] + fn load(&self, name: &str, content: String) -> Arc { + (**self).load(name, content) + } + #[inline(always)] + fn load_from_raw_parts(&self, name: Arc, content: SourceContent) -> Arc { + (**self).load_from_raw_parts(name, content) + } + #[inline(always)] + fn get(&self, id: SourceId) -> Result, SourceManagerError> { + (**self).get(id) + } + #[inline(always)] + fn get_by_path(&self, path: &str) -> Option> { + (**self).get_by_path(path) + } + #[inline(always)] + fn find(&self, name: &str) -> Option { + (**self).find(name) + } + #[inline(always)] + fn file_line_col_to_span(&self, loc: FileLineCol) -> Option { + (**self).file_line_col_to_span(loc) + } + #[inline(always)] + fn file_line_col(&self, span: SourceSpan) -> Result { + (**self).file_line_col(span) + } + #[inline(always)] + fn location_to_span(&self, loc: Location) -> Option { + (**self).location_to_span(loc) + } + #[inline(always)] + fn location(&self, span: SourceSpan) -> Result { + (**self).location(span) + } + #[inline(always)] + fn source(&self, id: SourceId) -> Result<&str, SourceManagerError> { + (**self).source(id) + } + #[inline(always)] + fn source_slice(&self, span: SourceSpan) -> Result<&str, SourceManagerError> { + (**self).source_slice(span) + } +} + +#[cfg(feature = "std")] +pub trait SourceManagerExt: SourceManager { + /// Load the content of `path` into this [SourceManager], using the given path as the source + /// name. + fn load_file(&self, path: &std::path::Path) -> Result, SourceManagerError> { + let name = path.to_string_lossy(); + if let Some(existing) = self.get_by_path(name.as_ref()) { + return Ok(existing); + } + + let name = Arc::from(name.into_owned().into_boxed_str()); + let content = std::fs::read_to_string(path) + .map(|s| SourceContent::new(Arc::clone(&name), s.into_boxed_str())) + .map_err(SourceManagerError::LoadFailed)?; + + Ok(self.load_from_raw_parts(name, content)) + } +} + +#[cfg(feature = "std")] +impl SourceManagerExt for T {} + +// DEFAULT SOURCE MANAGER +// ================================================================================================ + +use crate::utils::sync::RwLock; + +#[derive(Default)] +pub struct DefaultSourceManager(RwLock); +impl Clone for DefaultSourceManager { + fn clone(&self) -> Self { + let manager = self.0.read(); + Self(RwLock::new(manager.clone())) + } +} + +#[derive(Default, Clone)] +struct DefaultSourceManagerImpl { + files: Vec>, + names: BTreeMap, SourceId>, +} + +impl DefaultSourceManagerImpl { + fn insert(&mut self, name: Arc, content: SourceContent) -> Arc { + // If we have previously inserted the same content with `name`, return the previously + // inserted source id + if let Some(file) = self.names.get(&name).copied().and_then(|id| { + let file = &self.files[id.to_usize()]; + if file.as_str() == content.as_str() { + Some(Arc::clone(file)) + } else { + None + } + }) { + return file; + } + let id = SourceId::try_from(self.files.len()) + .expect("system limit: source manager has exhausted its supply of source ids"); + let file = Arc::new(SourceFile::from_raw_parts(id, content)); + self.files.push(Arc::clone(&file)); + file + } + + fn get(&self, id: SourceId) -> Result, SourceManagerError> { + self.files + .get(id.to_usize()) + .cloned() + .ok_or(SourceManagerError::InvalidSourceId) + } + + fn get_by_path(&self, path: &str) -> Option> { + self.find(path).and_then(|id| self.get(id).ok()) + } + + fn find(&self, name: &str) -> Option { + self.names.get(name).copied() + } + + fn file_line_col_to_span(&self, loc: FileLineCol) -> Option { + let file = self + .names + .get(&loc.path) + .copied() + .and_then(|id| self.files.get(id.to_usize()))?; + file.line_column_to_span(loc.line, loc.column) + } + + fn file_line_col(&self, span: SourceSpan) -> Result { + self.files + .get(span.source_id().to_usize()) + .ok_or(SourceManagerError::InvalidSourceId) + .map(|file| file.location(span)) + } + + fn location_to_span(&self, loc: Location) -> Option { + let file = self + .names + .get(&loc.path) + .copied() + .and_then(|id| self.files.get(id.to_usize()))?; + + let max_len = ByteIndex::from(file.as_str().len() as u32); + if loc.start >= max_len || loc.end > max_len { + return None; + } + + Some(SourceSpan::new(file.id(), loc.start..loc.end)) + } + + fn location(&self, span: SourceSpan) -> Result { + self.files + .get(span.source_id().to_usize()) + .ok_or(SourceManagerError::InvalidSourceId) + .map(|file| Location::new(file.name(), span.start(), span.end())) + } +} + +impl SourceManager for DefaultSourceManager { + fn load_from_raw_parts(&self, name: Arc, content: SourceContent) -> Arc { + let mut manager = self.0.write(); + manager.insert(name, content) + } + + fn get(&self, id: SourceId) -> Result, SourceManagerError> { + let manager = self.0.read(); + manager.get(id) + } + + fn get_by_path(&self, path: &str) -> Option> { + let manager = self.0.read(); + manager.get_by_path(path) + } + + fn find(&self, name: &str) -> Option { + let manager = self.0.read(); + manager.find(name) + } + + fn file_line_col_to_span(&self, loc: FileLineCol) -> Option { + let manager = self.0.read(); + manager.file_line_col_to_span(loc) + } + + fn file_line_col(&self, span: SourceSpan) -> Result { + let manager = self.0.read(); + manager.file_line_col(span) + } + + fn location_to_span(&self, loc: Location) -> Option { + let manager = self.0.read(); + manager.location_to_span(loc) + } + + fn location(&self, span: SourceSpan) -> Result { + let manager = self.0.read(); + manager.location(span) + } + + fn source(&self, id: SourceId) -> Result<&str, SourceManagerError> { + let manager = self.0.read(); + let ptr = manager + .files + .get(id.to_usize()) + .ok_or(SourceManagerError::InvalidSourceId) + .map(|file| file.as_str() as *const str)?; + drop(manager); + // SAFETY: Because the lifetime of the returned reference is bound to the manager, and + // because we can only ever add files, not modify/remove them, this is safe. Exclusive + // access to the manager does _not_ mean exclusive access to the contents of previously + // added source files + Ok(unsafe { &*ptr }) + } + + fn source_slice(&self, span: SourceSpan) -> Result<&str, SourceManagerError> { + self.source(span.source_id())? + .get(span.into_slice_index()) + .ok_or(SourceManagerError::InvalidBounds) + } +} diff --git a/assembly/src/parser/span.rs b/core/src/debuginfo/span.rs similarity index 67% rename from assembly/src/parser/span.rs rename to core/src/debuginfo/span.rs index e6f663d299..c3d9a50dc6 100644 --- a/assembly/src/parser/span.rs +++ b/core/src/debuginfo/span.rs @@ -5,14 +5,34 @@ use core::{ ops::{Bound, Deref, DerefMut, Index, Range, RangeBounds}, }; -use crate::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; +use super::{ByteIndex, ByteOffset, SourceId}; +use crate::utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; /// This trait should be implemented for any type that has an associated [SourceSpan]. pub trait Spanned { fn span(&self) -> SourceSpan; } -impl Spanned for &T { +impl Spanned for SourceSpan { + #[inline(always)] + fn span(&self) -> SourceSpan { + *self + } +} + +impl Spanned for alloc::boxed::Box { + fn span(&self) -> SourceSpan { + (**self).span() + } +} + +impl Spanned for alloc::rc::Rc { + fn span(&self) -> SourceSpan { + (**self).span() + } +} + +impl Spanned for alloc::sync::Arc { fn span(&self) -> SourceSpan { (**self).span() } @@ -49,27 +69,22 @@ impl Span { /// Creates a span for `spanned` with `span`. #[inline] pub fn new(span: impl Into, spanned: T) -> Self { - Self { - span: span.into(), - spanned, - } + Self { span: span.into(), spanned } } /// Creates a span for `spanned` representing a single location, `offset`. #[inline] - pub fn at(offset: usize, spanned: T) -> Self { + pub fn at(source_id: SourceId, offset: usize, spanned: T) -> Self { + let offset = u32::try_from(offset).expect("invalid source offset: too large"); Self { - span: SourceSpan::at(offset.try_into().expect("invalid source offset: too large")), + span: SourceSpan::at(source_id, offset), spanned, } } /// Creates a [Span] from a value with an unknown/default location. pub fn unknown(spanned: T) -> Self { - Self { - span: Default::default(), - spanned, - } + Self { span: Default::default(), spanned } } /// Gets the associated [SourceSpan] for this spanned item. @@ -78,6 +93,12 @@ impl Span { self.span } + /// Gets a reference to the spanned item. + #[inline(always)] + pub const fn inner(&self) -> &T { + &self.spanned + } + /// Applies a transformation to the spanned value while retaining the same [SourceSpan]. #[inline] pub fn map(self, mut f: F) -> Span @@ -105,24 +126,19 @@ impl Span { /// Gets a new [Span] that borrows the inner value. pub fn as_ref(&self) -> Span<&T> { - Span { - span: self.span, - spanned: &self.spanned, - } + Span { span: self.span, spanned: &self.spanned } } /// Shifts the span right by `count` units #[inline] - pub fn shift(&mut self, count: usize) { - let count: u32 = count.try_into().expect("invalid count: must be smaller than 2^32"); + pub fn shift(&mut self, count: ByteOffset) { self.span.start += count; self.span.end += count; } /// Extends the end of the span by `count` units. #[inline] - pub fn extend(&mut self, count: usize) { - let count: u32 = count.try_into().expect("invalid count: must be smaller than 2^32"); + pub fn extend(&mut self, count: ByteOffset) { self.span.end += count; } @@ -229,23 +245,28 @@ impl Hash for Span { } } -/// Serialization impl Span { - pub fn write_into(&self, target: &mut W, options: crate::ast::AstSerdeOptions) { - if options.debug_info { + pub fn write_into_with_options(&self, target: &mut W, debug: bool) { + if debug { self.span.write_into(target); } self.spanned.write_into(target); } } -/// Deserialization +impl Serializable for Span { + fn write_into(&self, target: &mut W) { + self.span.write_into(target); + self.spanned.write_into(target); + } +} + impl Span { - pub fn read_from( + pub fn read_from_with_options( source: &mut R, - options: crate::ast::AstSerdeOptions, + debug: bool, ) -> Result { - let span = if options.debug_info { + let span = if debug { SourceSpan::read_from(source)? } else { SourceSpan::default() @@ -255,13 +276,6 @@ impl Span { } } -impl Serializable for Span { - fn write_into(&self, target: &mut W) { - self.span.write_into(target); - self.spanned.write_into(target); - } -} - impl Deserializable for Span { fn read_from(source: &mut R) -> Result { let span = SourceSpan::read_from(source)?; @@ -286,43 +300,85 @@ impl Deserializable for Span { /// to produce nice errors with it compared to this representation. #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct SourceSpan { - start: u32, - end: u32, + source_id: SourceId, + start: ByteIndex, + end: ByteIndex, } +#[derive(Debug, thiserror::Error)] +#[error("invalid byte index range: maximum supported byte index is 2^32")] +pub struct InvalidByteIndexRange; + impl SourceSpan { + /// A sentinel [SourceSpan] that indicates the span is unknown/invalid + pub const UNKNOWN: Self = Self { + source_id: SourceId::UNKNOWN, + start: ByteIndex::new(0), + end: ByteIndex::new(0), + }; + /// Creates a new [SourceSpan] from the given range. - pub fn new(range: Range) -> Self { + pub fn new(source_id: SourceId, range: Range) -> Self + where + B: Into, + { Self { - start: range.start, - end: range.end, + source_id, + start: range.start.into(), + end: range.end.into(), } } /// Creates a new [SourceSpan] for a specific offset. - pub fn at(offset: u32) -> Self { - Self { - start: offset, - end: offset, + pub fn at(source_id: SourceId, offset: impl Into) -> Self { + let offset = offset.into(); + Self { source_id, start: offset, end: offset } + } + + /// Try to create a new [SourceSpan] from the given range with `usize` bounds. + pub fn try_from_range( + source_id: SourceId, + range: Range, + ) -> Result { + const MAX: usize = u32::MAX as usize; + if range.start > MAX || range.end > MAX { + return Err(InvalidByteIndexRange); } + + Ok(SourceSpan { + source_id, + start: ByteIndex::from(range.start as u32), + end: ByteIndex::from(range.end as u32), + }) + } + + /// Returns `true` if this [SourceSpan] represents the unknown span + pub const fn is_unknown(&self) -> bool { + self.source_id.is_unknown() + } + + /// Get the [SourceId] associated with this source span + #[inline(always)] + pub fn source_id(&self) -> SourceId { + self.source_id } /// Gets the offset in bytes corresponding to the start of this span (inclusive). #[inline(always)] - pub fn start(&self) -> usize { - self.start as usize + pub fn start(&self) -> ByteIndex { + self.start } /// Gets the offset in bytes corresponding to the end of this span (exclusive). #[inline(always)] - pub fn end(&self) -> usize { - self.end as usize + pub fn end(&self) -> ByteIndex { + self.end } /// Gets the length of this span in bytes. #[inline(always)] pub fn len(&self) -> usize { - (self.end - self.start) as usize + self.end.to_usize() - self.start.to_usize() } /// Returns true if this span is empty. @@ -333,44 +389,37 @@ impl SourceSpan { /// Converts this span into a [`Range`]. #[inline] pub fn into_range(self) -> Range { - self.start..self.end + self.start.to_u32()..self.end.to_u32() } -} -impl Serializable for SourceSpan { - fn write_into(&self, target: &mut W) { - target.write_u32(self.start); - target.write_u32(self.end) + /// Converts this span into a [`Range`]. + #[inline] + pub fn into_slice_index(self) -> Range { + self.start.to_usize()..self.end.to_usize() } } -impl Deserializable for SourceSpan { - fn read_from(source: &mut R) -> Result { - let start = source.read_u32()?; - let end = source.read_u32()?; - Ok(Self { start, end }) +#[cfg(feature = "diagnostics")] +impl From for miette::SourceSpan { + fn from(span: SourceSpan) -> Self { + Self::new(miette::SourceOffset::from(span.start().to_usize()), span.len()) } } -impl TryFrom> for SourceSpan { - type Error = (); - - fn try_from(range: Range) -> Result { - const MAX: usize = u32::MAX as usize; - if range.start > MAX || range.end > MAX { - return Err(()); - } - Ok(SourceSpan { - start: range.start as u32, - end: range.end as u32, - }) +impl Serializable for SourceSpan { + fn write_into(&self, target: &mut W) { + target.write_u32(self.source_id.to_u32()); + target.write_u32(self.start.into()); + target.write_u32(self.end.into()) } } -impl From> for SourceSpan { - #[inline(always)] - fn from(range: Range) -> Self { - Self::new(range) +impl Deserializable for SourceSpan { + fn read_from(source: &mut R) -> Result { + let source_id = SourceId::new_unchecked(source.read_u32()?); + let start = ByteIndex::from(source.read_u32()?); + let end = ByteIndex::from(source.read_u32()?); + Ok(Self { source_id, start, end }) } } @@ -381,10 +430,10 @@ impl From for Range { } } -impl From for miette::SourceSpan { - #[inline] +impl From for Range { + #[inline(always)] fn from(span: SourceSpan) -> Self { - miette::SourceSpan::new(miette::SourceOffset::from(span.start as usize), span.len()) + span.into_slice_index() } } @@ -393,18 +442,18 @@ impl Index for [u8] { #[inline] fn index(&self, index: SourceSpan) -> &Self::Output { - &self[index.start()..index.end()] + &self[index.start().to_usize()..index.end().to_usize()] } } -impl RangeBounds for SourceSpan { +impl RangeBounds for SourceSpan { #[inline(always)] - fn start_bound(&self) -> Bound<&u32> { + fn start_bound(&self) -> Bound<&ByteIndex> { Bound::Included(&self.start) } #[inline(always)] - fn end_bound(&self) -> Bound<&u32> { + fn end_bound(&self) -> Bound<&ByteIndex> { Bound::Excluded(&self.end) } } diff --git a/core/src/kernel.rs b/core/src/kernel.rs new file mode 100644 index 0000000000..c802d93fca --- /dev/null +++ b/core/src/kernel.rs @@ -0,0 +1,73 @@ +use alloc::vec::Vec; + +use miden_crypto::hash::rpo::RpoDigest; + +use crate::{ + errors::KernelError, + utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}, +}; + +// KERNEL +// ================================================================================================ + +/// A list of procedure hashes defining a VM kernel. +/// +/// The internally-stored list always has a consistent order, regardless of the order of procedure +/// list used to instantiate a kernel. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct Kernel(Vec); + +impl Kernel { + /// The maximum number of procedures which can be exported from a Kernel. + pub const MAX_NUM_PROCEDURES: usize = u8::MAX as usize; + + /// Returns a new [Kernel] instantiated with the specified procedure hashes. + pub fn new(proc_hashes: &[RpoDigest]) -> Result { + if proc_hashes.len() > Self::MAX_NUM_PROCEDURES { + Err(KernelError::TooManyProcedures(Self::MAX_NUM_PROCEDURES, proc_hashes.len())) + } else { + let mut hashes = proc_hashes.to_vec(); + hashes.sort_by_key(|v| v.as_bytes()); // ensure consistent order + + let duplicated = hashes.windows(2).any(|data| data[0] == data[1]); + + if duplicated { + Err(KernelError::DuplicatedProcedures) + } else { + Ok(Self(hashes)) + } + } + } + + /// Returns true if this kernel does not contain any procedures. + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// Returns true if a procedure with the specified hash belongs to this kernel. + pub fn contains_proc(&self, proc_hash: RpoDigest) -> bool { + self.0.binary_search(&proc_hash).is_ok() + } + + /// Returns a list of procedure hashes contained in this kernel. + pub fn proc_hashes(&self) -> &[RpoDigest] { + &self.0 + } +} + +// this is required by AIR as public inputs will be serialized with the proof +impl Serializable for Kernel { + fn write_into(&self, target: &mut W) { + // expect is OK here because the number of procedures is enforced by the constructor + target.write_u8(self.0.len().try_into().expect("too many kernel procedures")); + target.write_many(&self.0) + } +} + +impl Deserializable for Kernel { + fn read_from(source: &mut R) -> Result { + let len = source.read_u8()? as usize; + let kernel = source.read_many::(len)?; + Ok(Self(kernel)) + } +} diff --git a/core/src/lib.rs b/core/src/lib.rs index aa905a4b43..53f97b23e0 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -50,8 +50,14 @@ assertion failed: `(left matches right)` } pub mod chiplets; +pub mod debuginfo; pub mod errors; +mod program; +pub use program::{Program, ProgramInfo}; + +mod kernel; +pub use kernel::Kernel; pub use miden_crypto::{Word, EMPTY_WORD, ONE, WORD_SIZE, ZERO}; pub mod crypto { pub mod merkle { @@ -67,7 +73,7 @@ pub mod crypto { blake::{Blake3Digest, Blake3_160, Blake3_192, Blake3_256}, rpo::{Rpo256, RpoDigest}, rpx::{Rpx256, RpxDigest}, - ElementHasher, Hasher, + Digest, ElementHasher, Hasher, }; } @@ -82,6 +88,8 @@ pub mod crypto { } } +pub mod mast; + pub use math::{ fields::{f64::BaseElement as Felt, QuadExtension}, polynom, ExtensionOf, FieldElement, StarkField, ToElements, @@ -89,15 +97,27 @@ pub use math::{ pub mod prettier { pub use miden_formatting::{prettier::*, pretty_via_display, pretty_via_to_string}; -} -mod program; -pub use program::{blocks as code_blocks, CodeBlockTable, Kernel, Program, ProgramInfo}; + /// Pretty-print a list of [PrettyPrint] values as comma-separated items. + pub fn pretty_print_csv<'a, T>(items: impl IntoIterator) -> Document + where + T: PrettyPrint + 'a, + { + let mut doc = Document::Empty; + for (i, item) in items.into_iter().enumerate() { + if i > 0 { + doc += const_text(", "); + } + doc += item.render(); + } + doc + } +} mod operations; pub use operations::{ - AdviceInjector, AssemblyOp, DebugOptions, Decorator, DecoratorIterator, DecoratorList, - Operation, SignatureKind, + opcode_constants::*, AdviceInjector, AssemblyOp, DebugOptions, Decorator, DecoratorIterator, + DecoratorList, Operation, SignatureKind, }; pub mod stack; diff --git a/core/src/mast/mod.rs b/core/src/mast/mod.rs new file mode 100644 index 0000000000..bc9cc8e3b5 --- /dev/null +++ b/core/src/mast/mod.rs @@ -0,0 +1,589 @@ +use alloc::{ + collections::{BTreeMap, BTreeSet}, + vec::Vec, +}; +use core::{ + fmt, mem, + ops::{Index, IndexMut}, +}; + +use miden_crypto::hash::rpo::RpoDigest; + +mod node; +pub use node::{ + BasicBlockNode, CallNode, DynNode, ExternalNode, JoinNode, LoopNode, MastNode, OpBatch, + OperationOrDecorator, SplitNode, OP_BATCH_SIZE, OP_GROUP_SIZE, +}; +use winter_utils::{ByteWriter, DeserializationError, Serializable}; + +use crate::{Decorator, DecoratorList, Operation}; + +mod serialization; + +#[cfg(test)] +mod tests; + +// MAST FOREST +// ================================================================================================ + +/// Represents one or more procedures, represented as a collection of [`MastNode`]s. +/// +/// A [`MastForest`] does not have an entrypoint, and hence is not executable. A [`crate::Program`] +/// can be built from a [`MastForest`] to specify an entrypoint. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct MastForest { + /// All of the nodes local to the trees comprising the MAST forest. + nodes: Vec, + + /// Roots of procedures defined within this MAST forest. + roots: Vec, + + /// All the decorators included in the MAST forest. + decorators: Vec, +} + +// ------------------------------------------------------------------------------------------------ +/// Constructors +impl MastForest { + /// Creates a new empty [`MastForest`]. + pub fn new() -> Self { + Self::default() + } +} + +// ------------------------------------------------------------------------------------------------ +/// State mutators +impl MastForest { + /// The maximum number of nodes that can be stored in a single MAST forest. + const MAX_NODES: usize = (1 << 30) - 1; + /// The maximum number of decorators that can be stored in a single MAST forest. + const MAX_DECORATORS: usize = Self::MAX_NODES; + + /// Adds a decorator to the forest, and returns the associated [`DecoratorId`]. + pub fn add_decorator(&mut self, decorator: Decorator) -> Result { + if self.decorators.len() >= u32::MAX as usize { + return Err(MastForestError::TooManyDecorators); + } + + let new_decorator_id = DecoratorId(self.decorators.len() as u32); + self.decorators.push(decorator); + + Ok(new_decorator_id) + } + + /// Adds a node to the forest, and returns the associated [`MastNodeId`]. + /// + /// Adding two duplicate nodes will result in two distinct returned [`MastNodeId`]s. + pub fn add_node(&mut self, node: MastNode) -> Result { + if self.nodes.len() == Self::MAX_NODES { + return Err(MastForestError::TooManyNodes); + } + + let new_node_id = MastNodeId(self.nodes.len() as u32); + self.nodes.push(node); + + Ok(new_node_id) + } + + /// Adds a basic block node to the forest, and returns the [`MastNodeId`] associated with it. + pub fn add_block( + &mut self, + operations: Vec, + decorators: Option, + ) -> Result { + let block = MastNode::new_basic_block(operations, decorators)?; + self.add_node(block) + } + + /// Adds a join node to the forest, and returns the [`MastNodeId`] associated with it. + pub fn add_join( + &mut self, + left_child: MastNodeId, + right_child: MastNodeId, + ) -> Result { + let join = MastNode::new_join(left_child, right_child, self)?; + self.add_node(join) + } + + /// Adds a split node to the forest, and returns the [`MastNodeId`] associated with it. + pub fn add_split( + &mut self, + if_branch: MastNodeId, + else_branch: MastNodeId, + ) -> Result { + let split = MastNode::new_split(if_branch, else_branch, self)?; + self.add_node(split) + } + + /// Adds a loop node to the forest, and returns the [`MastNodeId`] associated with it. + pub fn add_loop(&mut self, body: MastNodeId) -> Result { + let loop_node = MastNode::new_loop(body, self)?; + self.add_node(loop_node) + } + + /// Adds a call node to the forest, and returns the [`MastNodeId`] associated with it. + pub fn add_call(&mut self, callee: MastNodeId) -> Result { + let call = MastNode::new_call(callee, self)?; + self.add_node(call) + } + + /// Adds a syscall node to the forest, and returns the [`MastNodeId`] associated with it. + pub fn add_syscall(&mut self, callee: MastNodeId) -> Result { + let syscall = MastNode::new_syscall(callee, self)?; + self.add_node(syscall) + } + + /// Adds a dyn node to the forest, and returns the [`MastNodeId`] associated with it. + pub fn add_dyn(&mut self) -> Result { + self.add_node(MastNode::new_dyn()) + } + + /// Adds an external node to the forest, and returns the [`MastNodeId`] associated with it. + pub fn add_external(&mut self, mast_root: RpoDigest) -> Result { + self.add_node(MastNode::new_external(mast_root)) + } + + /// Marks the given [`MastNodeId`] as being the root of a procedure. + /// + /// If the specified node is already marked as a root, this will have no effect. + /// + /// # Panics + /// - if `new_root_id`'s internal index is larger than the number of nodes in this forest (i.e. + /// clearly doesn't belong to this MAST forest). + pub fn make_root(&mut self, new_root_id: MastNodeId) { + assert!((new_root_id.0 as usize) < self.nodes.len()); + + if !self.roots.contains(&new_root_id) { + self.roots.push(new_root_id); + } + } + + /// Removes all nodes in the provided set from the MAST forest. The nodes MUST be orphaned (i.e. + /// have no parent). Otherwise, this parent's reference is considered "dangling" after the + /// removal (i.e. will point to an incorrect node after the removal), and this removal operation + /// would result in an invalid [`MastForest`]. + /// + /// It also returns the map from old node IDs to new node IDs; or `None` if the set of nodes to + /// remove was empty. Any [`MastNodeId`] used in reference to the old [`MastForest`] should be + /// remapped using this map. + pub fn remove_nodes( + &mut self, + nodes_to_remove: &BTreeSet, + ) -> Option> { + if nodes_to_remove.is_empty() { + return None; + } + + let old_nodes = mem::take(&mut self.nodes); + let old_root_ids = mem::take(&mut self.roots); + let (retained_nodes, id_remappings) = remove_nodes(old_nodes, nodes_to_remove); + + self.remap_and_add_nodes(retained_nodes, &id_remappings); + self.remap_and_add_roots(old_root_ids, &id_remappings); + Some(id_remappings) + } + + pub fn set_before_enter(&mut self, node_id: MastNodeId, decorator_ids: Vec) { + self[node_id].set_before_enter(decorator_ids) + } + + pub fn set_after_exit(&mut self, node_id: MastNodeId, decorator_ids: Vec) { + self[node_id].set_after_exit(decorator_ids) + } + + /// Adds a basic block node to the forest, and returns the [`MastNodeId`] associated with it. + /// + /// It is assumed that the decorators have not already been added to the MAST forest. If they + /// were, they will be added again (and result in a different set of [`DecoratorId`]s). + #[cfg(test)] + pub fn add_block_with_raw_decorators( + &mut self, + operations: Vec, + decorators: Vec<(usize, Decorator)>, + ) -> Result { + let block = MastNode::new_basic_block_with_raw_decorators(operations, decorators, self)?; + self.add_node(block) + } +} + +/// Helpers +impl MastForest { + /// Adds all provided nodes to the internal set of nodes, remapping all [`MastNodeId`] + /// references in those nodes. + /// + /// # Panics + /// - Panics if the internal set of nodes is not empty. + fn remap_and_add_nodes( + &mut self, + nodes_to_add: Vec, + id_remappings: &BTreeMap, + ) { + assert!(self.nodes.is_empty()); + + // Add each node to the new MAST forest, making sure to rewrite any outdated internal + // `MastNodeId`s + for live_node in nodes_to_add { + match &live_node { + MastNode::Join(join_node) => { + let first_child = + id_remappings.get(&join_node.first()).copied().unwrap_or(join_node.first()); + let second_child = id_remappings + .get(&join_node.second()) + .copied() + .unwrap_or(join_node.second()); + + self.add_join(first_child, second_child).unwrap(); + }, + MastNode::Split(split_node) => { + let on_true_child = id_remappings + .get(&split_node.on_true()) + .copied() + .unwrap_or(split_node.on_true()); + let on_false_child = id_remappings + .get(&split_node.on_false()) + .copied() + .unwrap_or(split_node.on_false()); + + self.add_split(on_true_child, on_false_child).unwrap(); + }, + MastNode::Loop(loop_node) => { + let body_id = + id_remappings.get(&loop_node.body()).copied().unwrap_or(loop_node.body()); + + self.add_loop(body_id).unwrap(); + }, + MastNode::Call(call_node) => { + let callee_id = id_remappings + .get(&call_node.callee()) + .copied() + .unwrap_or(call_node.callee()); + + if call_node.is_syscall() { + self.add_syscall(callee_id).unwrap(); + } else { + self.add_call(callee_id).unwrap(); + } + }, + MastNode::Block(_) | MastNode::Dyn(_) | MastNode::External(_) => { + self.add_node(live_node).unwrap(); + }, + } + } + } + + /// Remaps and adds all old root ids to the internal set of roots. + /// + /// # Panics + /// - Panics if the internal set of roots is not empty. + fn remap_and_add_roots( + &mut self, + old_root_ids: Vec, + id_remappings: &BTreeMap, + ) { + assert!(self.roots.is_empty()); + + for old_root_id in old_root_ids { + let new_root_id = id_remappings.get(&old_root_id).copied().unwrap_or(old_root_id); + self.make_root(new_root_id); + } + } +} + +/// Returns the set of nodes that are live, as well as the mapping from "old ID" to "new ID" for all +/// live nodes. +fn remove_nodes( + mast_nodes: Vec, + nodes_to_remove: &BTreeSet, +) -> (Vec, BTreeMap) { + // Note: this allows us to safely use `usize as u32`, guaranteeing that it won't wrap around. + assert!(mast_nodes.len() < u32::MAX as usize); + + let mut retained_nodes = Vec::with_capacity(mast_nodes.len()); + let mut id_remappings = BTreeMap::new(); + + for (old_node_index, old_node) in mast_nodes.into_iter().enumerate() { + let old_node_id: MastNodeId = MastNodeId(old_node_index as u32); + + if !nodes_to_remove.contains(&old_node_id) { + let new_node_id: MastNodeId = MastNodeId(retained_nodes.len() as u32); + id_remappings.insert(old_node_id, new_node_id); + + retained_nodes.push(old_node); + } + } + + (retained_nodes, id_remappings) +} + +// ------------------------------------------------------------------------------------------------ + +/// Public accessors +impl MastForest { + /// Returns the [`Decorator`] associated with the provided [`DecoratorId`] if valid, or else + /// `None`. + /// + /// This is the fallible version of indexing (e.g. `mast_forest[decorator_id]`). + #[inline(always)] + pub fn get_decorator_by_id(&self, decorator_id: DecoratorId) -> Option<&Decorator> { + let idx = decorator_id.0 as usize; + + self.decorators.get(idx) + } + + /// Returns the [`MastNode`] associated with the provided [`MastNodeId`] if valid, or else + /// `None`. + /// + /// This is the fallible version of indexing (e.g. `mast_forest[node_id]`). + #[inline(always)] + pub fn get_node_by_id(&self, node_id: MastNodeId) -> Option<&MastNode> { + let idx = node_id.0 as usize; + + self.nodes.get(idx) + } + + /// Returns the [`MastNodeId`] of the procedure associated with a given digest, if any. + #[inline(always)] + pub fn find_procedure_root(&self, digest: RpoDigest) -> Option { + self.roots.iter().find(|&&root_id| self[root_id].digest() == digest).copied() + } + + /// Returns true if a node with the specified ID is a root of a procedure in this MAST forest. + pub fn is_procedure_root(&self, node_id: MastNodeId) -> bool { + self.roots.contains(&node_id) + } + + /// Returns an iterator over the digests of all procedures in this MAST forest. + pub fn procedure_digests(&self) -> impl Iterator + '_ { + self.roots.iter().map(|&root_id| self[root_id].digest()) + } + + /// Returns an iterator over the digests of local procedures in this MAST forest. + /// + /// A local procedure is defined as a procedure which is not a single external node. + pub fn local_procedure_digests(&self) -> impl Iterator + '_ { + self.roots.iter().filter_map(|&root_id| { + let node = &self[root_id]; + if node.is_external() { + None + } else { + Some(node.digest()) + } + }) + } + + /// Returns an iterator over the IDs of the procedures in this MAST forest. + pub fn procedure_roots(&self) -> &[MastNodeId] { + &self.roots + } + + /// Returns the number of procedures in this MAST forest. + pub fn num_procedures(&self) -> u32 { + self.roots + .len() + .try_into() + .expect("MAST forest contains more than 2^32 procedures.") + } + + /// Returns the number of nodes in this MAST forest. + pub fn num_nodes(&self) -> u32 { + self.nodes.len() as u32 + } + + /// Returns the underlying nodes in this MAST forest. + pub fn nodes(&self) -> &[MastNode] { + &self.nodes + } +} + +impl Index for MastForest { + type Output = MastNode; + + #[inline(always)] + fn index(&self, node_id: MastNodeId) -> &Self::Output { + let idx = node_id.0 as usize; + + &self.nodes[idx] + } +} + +impl IndexMut for MastForest { + #[inline(always)] + fn index_mut(&mut self, node_id: MastNodeId) -> &mut Self::Output { + let idx = node_id.0 as usize; + + &mut self.nodes[idx] + } +} + +impl Index for MastForest { + type Output = Decorator; + + #[inline(always)] + fn index(&self, decorator_id: DecoratorId) -> &Self::Output { + let idx = decorator_id.0 as usize; + + &self.decorators[idx] + } +} + +impl IndexMut for MastForest { + #[inline(always)] + fn index_mut(&mut self, decorator_id: DecoratorId) -> &mut Self::Output { + let idx = decorator_id.0 as usize; + &mut self.decorators[idx] + } +} + +// MAST NODE ID +// ================================================================================================ + +/// An opaque handle to a [`MastNode`] in some [`MastForest`]. It is the responsibility of the user +/// to use a given [`MastNodeId`] with the corresponding [`MastForest`]. +/// +/// Note that the [`MastForest`] does *not* ensure that equal [`MastNode`]s have equal +/// [`MastNodeId`] handles. Hence, [`MastNodeId`] equality must not be used to test for equality of +/// the underlying [`MastNode`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct MastNodeId(u32); + +impl MastNodeId { + /// Returns a new `MastNodeId` with the provided inner value, or an error if the provided + /// `value` is greater than the number of nodes in the forest. + /// + /// For use in deserialization. + pub fn from_u32_safe( + value: u32, + mast_forest: &MastForest, + ) -> Result { + if (value as usize) < mast_forest.nodes.len() { + Ok(Self(value)) + } else { + Err(DeserializationError::InvalidValue(format!( + "Invalid deserialized MAST node ID '{}', but only {} nodes in the forest", + value, + mast_forest.nodes.len(), + ))) + } + } + + pub fn as_usize(&self) -> usize { + self.0 as usize + } + + pub fn as_u32(&self) -> u32 { + self.0 + } +} + +impl From for usize { + fn from(value: MastNodeId) -> Self { + value.0 as usize + } +} + +impl From for u32 { + fn from(value: MastNodeId) -> Self { + value.0 + } +} + +impl From<&MastNodeId> for u32 { + fn from(value: &MastNodeId) -> Self { + value.0 + } +} + +impl fmt::Display for MastNodeId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "MastNodeId({})", self.0) + } +} + +// DECORATOR ID +// ================================================================================================ + +/// An opaque handle to a [`Decorator`] in some [`MastForest`]. It is the responsibility of the user +/// to use a given [`DecoratorId`] with the corresponding [`MastForest`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct DecoratorId(u32); + +impl DecoratorId { + /// Returns a new `DecoratorId` with the provided inner value, or an error if the provided + /// `value` is greater than the number of nodes in the forest. + /// + /// For use in deserialization. + pub fn from_u32_safe( + value: u32, + mast_forest: &MastForest, + ) -> Result { + if (value as usize) < mast_forest.decorators.len() { + Ok(Self(value)) + } else { + Err(DeserializationError::InvalidValue(format!( + "Invalid deserialized MAST decorator id '{}', but only {} decorators in the forest", + value, + mast_forest.nodes.len(), + ))) + } + } + + pub fn as_usize(&self) -> usize { + self.0 as usize + } + + pub fn as_u32(&self) -> u32 { + self.0 + } +} + +impl From for usize { + fn from(value: DecoratorId) -> Self { + value.0 as usize + } +} + +impl From for u32 { + fn from(value: DecoratorId) -> Self { + value.0 + } +} + +impl From<&DecoratorId> for u32 { + fn from(value: &DecoratorId) -> Self { + value.0 + } +} + +impl fmt::Display for DecoratorId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "DecoratorId({})", self.0) + } +} + +impl Serializable for DecoratorId { + fn write_into(&self, target: &mut W) { + self.0.write_into(target) + } +} + +// MAST FOREST ERROR +// ================================================================================================ + +/// Represents the types of errors that can occur when dealing with MAST forest. +#[derive(Debug, thiserror::Error, PartialEq)] +pub enum MastForestError { + #[error( + "invalid decorator count: MAST forest exceeds the maximum of {} decorators", + u32::MAX + )] + TooManyDecorators, + #[error( + "invalid node count: MAST forest exceeds the maximum of {} nodes", + MastForest::MAX_NODES + )] + TooManyNodes, + #[error("node id: {0} is greater than or equal to forest length: {1}")] + NodeIdOverflow(MastNodeId, usize), + #[error("basic block cannot be created from an empty list of operations")] + EmptyBasicBlock, +} diff --git a/core/src/mast/node/basic_block_node/mod.rs b/core/src/mast/node/basic_block_node/mod.rs new file mode 100644 index 0000000000..81f0afa0a5 --- /dev/null +++ b/core/src/mast/node/basic_block_node/mod.rs @@ -0,0 +1,436 @@ +use alloc::vec::Vec; +use core::{fmt, mem}; + +use miden_crypto::{hash::rpo::RpoDigest, Felt, ZERO}; +use miden_formatting::prettier::PrettyPrint; + +use crate::{ + chiplets::hasher, + mast::{DecoratorId, MastForest, MastForestError}, + DecoratorIterator, DecoratorList, Operation, +}; + +mod op_batch; +pub use op_batch::OpBatch; +use op_batch::OpBatchAccumulator; + +#[cfg(test)] +mod tests; + +// CONSTANTS +// ================================================================================================ + +/// Maximum number of operations per group. +pub const GROUP_SIZE: usize = 9; + +/// Maximum number of groups per batch. +pub const BATCH_SIZE: usize = 8; + +// BASIC BLOCK NODE +// ================================================================================================ + +/// Block for a linear sequence of operations (i.e., no branching or loops). +/// +/// Executes its operations in order. Fails if any of the operations fails. +/// +/// A basic block is composed of operation batches, operation batches are composed of operation +/// groups, operation groups encode the VM's operations and immediate values. These values are +/// created according to these rules: +/// +/// - A basic block contains one or more batches. +/// - A batch contains exactly 8 groups. +/// - A group contains exactly 9 operations or 1 immediate value. +/// - NOOPs are used to fill a group or batch when necessary. +/// - An immediate value follows the operation that requires it, using the next available group in +/// the batch. If there are no batches available in the group, then both the operation and its +/// immediate are moved to the next batch. +/// +/// Example: 8 pushes result in two operation batches: +/// +/// - First batch: First group with 7 push opcodes and 2 zero-paddings packed together, followed by +/// 7 groups with their respective immediate values. +/// - Second batch: First group with the last push opcode and 8 zero-paddings packed together, +/// followed by one immediate and 6 padding groups. +/// +/// The hash of a basic block is: +/// +/// > hash(batches, domain=BASIC_BLOCK_DOMAIN) +/// +/// Where `batches` is the concatenation of each `batch` in the basic block, and each batch is 8 +/// field elements (512 bits). +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct BasicBlockNode { + /// The primitive operations contained in this basic block. + /// + /// The operations are broken up into batches of 8 groups, with each group containing up to 9 + /// operations, or a single immediates. Thus the maximum size of each batch is 72 operations. + /// Multiple batches are used for blocks consisting of more than 72 operations. + op_batches: Vec, + digest: RpoDigest, + decorators: DecoratorList, +} + +// ------------------------------------------------------------------------------------------------ +/// Constants +impl BasicBlockNode { + /// The domain of the basic block node (used for control block hashing). + pub const DOMAIN: Felt = ZERO; +} + +// ------------------------------------------------------------------------------------------------ +/// Constructors +impl BasicBlockNode { + /// Returns a new [`BasicBlockNode`] instantiated with the specified operations and decorators. + /// + /// Returns an error if: + /// - `operations` vector is empty. + pub fn new( + operations: Vec, + decorators: Option, + ) -> Result { + if operations.is_empty() { + return Err(MastForestError::EmptyBasicBlock); + } + + // None is equivalent to an empty list of decorators moving forward. + let decorators = decorators.unwrap_or_default(); + + // Validate decorators list (only in debug mode). + #[cfg(debug_assertions)] + validate_decorators(&operations, &decorators); + + let (op_batches, digest) = batch_and_hash_ops(operations); + Ok(Self { op_batches, digest, decorators }) + } + + /// Returns a new [`BasicBlockNode`] from values that are assumed to be correct. + /// Should only be used when the source of the inputs is trusted (e.g. deserialization). + pub fn new_unsafe( + operations: Vec, + decorators: DecoratorList, + digest: RpoDigest, + ) -> Self { + assert!(!operations.is_empty()); + let op_batches = batch_ops(operations); + Self { op_batches, digest, decorators } + } + + /// Returns a new [`BasicBlockNode`] instantiated with the specified operations and decorators. + #[cfg(test)] + pub fn new_with_raw_decorators( + operations: Vec, + decorators: Vec<(usize, crate::Decorator)>, + mast_forest: &mut crate::mast::MastForest, + ) -> Result { + let mut decorator_list = Vec::new(); + for (idx, decorator) in decorators { + decorator_list.push((idx, mast_forest.add_decorator(decorator)?)); + } + + Self::new(operations, Some(decorator_list)) + } +} + +// ------------------------------------------------------------------------------------------------ +/// Public accessors +impl BasicBlockNode { + /// Returns a commitment to this basic block. + pub fn digest(&self) -> RpoDigest { + self.digest + } + + /// Returns a reference to the operation batches in this basic block. + pub fn op_batches(&self) -> &[OpBatch] { + &self.op_batches + } + + /// Returns the number of operation batches in this basic block. + pub fn num_op_batches(&self) -> usize { + self.op_batches.len() + } + + /// Returns the total number of operation groups in this basic block. + /// + /// Then number of operation groups is computed as follows: + /// - For all batches but the last one we set the number of groups to 8, regardless of the + /// actual number of groups in the batch. The reason for this is that when operation batches + /// are concatenated together each batch contributes 8 elements to the hash. + /// - For the last batch, we take the number of actual groups and round it up to the next power + /// of two. The reason for rounding is that the VM always executes a number of operation + /// groups which is a power of two. + pub fn num_op_groups(&self) -> usize { + let last_batch_num_groups = self.op_batches.last().expect("no last group").num_groups(); + (self.op_batches.len() - 1) * BATCH_SIZE + last_batch_num_groups.next_power_of_two() + } + + /// Returns the number of operations in this basic block. + pub fn num_operations(&self) -> u32 { + let num_ops: usize = self.op_batches.iter().map(|batch| batch.ops().len()).sum(); + num_ops.try_into().expect("basic block contains more than 2^32 operations") + } + + /// Returns a list of decorators in this basic block node. + /// + /// Each decorator is accompanied by the operation index specifying the operation prior to + /// which the decorator should be executed. + pub fn decorators(&self) -> &DecoratorList { + &self.decorators + } + + /// Returns a [`DecoratorIterator`] which allows us to iterate through the decorator list of + /// this basic block node while executing operation batches of this basic block node. + pub fn decorator_iter(&self) -> DecoratorIterator { + DecoratorIterator::new(&self.decorators) + } + + /// Returns an iterator over the operations in the order in which they appear in the program. + pub fn operations(&self) -> impl Iterator { + self.op_batches.iter().flat_map(|batch| batch.ops()) + } + + /// Returns the total number of operations and decorators in this basic block. + pub fn num_operations_and_decorators(&self) -> u32 { + let num_ops: usize = self.num_operations() as usize; + let num_decorators = self.decorators.len(); + + (num_ops + num_decorators) + .try_into() + .expect("basic block contains more than 2^32 operations and decorators") + } + + /// Returns an iterator over all operations and decorator, in the order in which they appear in + /// the program. + pub fn iter(&self) -> impl Iterator { + OperationOrDecoratorIterator::new(self) + } +} + +/// Mutators +impl BasicBlockNode { + /// Sets the provided list of decorators to be executed before all existing decorators. + pub fn prepend_decorators(&mut self, decorator_ids: Vec) { + let mut new_decorators: DecoratorList = + decorator_ids.into_iter().map(|decorator_id| (0, decorator_id)).collect(); + new_decorators.extend(mem::take(&mut self.decorators)); + + self.decorators = new_decorators; + } + + /// Sets the provided list of decorators to be executed after all existing decorators. + pub fn append_decorators(&mut self, decorator_ids: Vec) { + let after_last_op_idx = self.num_operations() as usize; + + self.decorators.extend( + decorator_ids.into_iter().map(|decorator_id| (after_last_op_idx, decorator_id)), + ); + } +} + +// PRETTY PRINTING +// ================================================================================================ + +impl BasicBlockNode { + pub(super) fn to_display<'a>(&'a self, mast_forest: &'a MastForest) -> impl fmt::Display + 'a { + BasicBlockNodePrettyPrint { block_node: self, mast_forest } + } + + pub(super) fn to_pretty_print<'a>( + &'a self, + mast_forest: &'a MastForest, + ) -> impl PrettyPrint + 'a { + BasicBlockNodePrettyPrint { block_node: self, mast_forest } + } +} + +struct BasicBlockNodePrettyPrint<'a> { + block_node: &'a BasicBlockNode, + mast_forest: &'a MastForest, +} + +impl<'a> PrettyPrint for BasicBlockNodePrettyPrint<'a> { + #[rustfmt::skip] + fn render(&self) -> crate::prettier::Document { + use crate::prettier::*; + + // e.g. `basic_block a b c end` + let single_line = const_text("basic_block") + + const_text(" ") + + self. + block_node + .iter() + .map(|op_or_dec| match op_or_dec { + OperationOrDecorator::Operation(op) => op.render(), + OperationOrDecorator::Decorator(&decorator_id) => self.mast_forest[decorator_id].render(), + }) + .reduce(|acc, doc| acc + const_text(" ") + doc) + .unwrap_or_default() + + const_text(" ") + + const_text("end"); + + // e.g. ` + // basic_block + // a + // b + // c + // end + // ` + + let multi_line = indent( + 4, + const_text("basic_block") + + nl() + + self + .block_node + .iter() + .map(|op_or_dec| match op_or_dec { + OperationOrDecorator::Operation(op) => op.render(), + OperationOrDecorator::Decorator(&decorator_id) => self.mast_forest[decorator_id].render(), + }) + .reduce(|acc, doc| acc + nl() + doc) + .unwrap_or_default(), + ) + nl() + + const_text("end"); + + single_line | multi_line + } +} + +impl<'a> fmt::Display for BasicBlockNodePrettyPrint<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use crate::prettier::PrettyPrint; + self.pretty_print(f) + } +} + +// OPERATION OR DECORATOR +// ================================================================================================ + +/// Encodes either an [`Operation`] or a [`crate::Decorator`]. +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum OperationOrDecorator<'a> { + Operation(&'a Operation), + Decorator(&'a DecoratorId), +} + +struct OperationOrDecoratorIterator<'a> { + node: &'a BasicBlockNode, + + /// The index of the current batch + batch_index: usize, + + /// The index of the operation in the current batch + op_index_in_batch: usize, + + /// The index of the current operation across all batches + op_index: usize, + + /// The index of the next element in `node.decorator_list`. This list is assumed to be sorted. + decorator_list_next_index: usize, +} + +impl<'a> OperationOrDecoratorIterator<'a> { + fn new(node: &'a BasicBlockNode) -> Self { + Self { + node, + batch_index: 0, + op_index_in_batch: 0, + op_index: 0, + decorator_list_next_index: 0, + } + } +} + +impl<'a> Iterator for OperationOrDecoratorIterator<'a> { + type Item = OperationOrDecorator<'a>; + + fn next(&mut self) -> Option { + // check if there's a decorator to execute + if let Some((op_index, decorator)) = + self.node.decorators.get(self.decorator_list_next_index) + { + if *op_index == self.op_index { + self.decorator_list_next_index += 1; + return Some(OperationOrDecorator::Decorator(decorator)); + } + } + + // If no decorator needs to be executed, then execute the operation + if let Some(batch) = self.node.op_batches.get(self.batch_index) { + if let Some(operation) = batch.ops.get(self.op_index_in_batch) { + self.op_index_in_batch += 1; + self.op_index += 1; + + Some(OperationOrDecorator::Operation(operation)) + } else { + self.batch_index += 1; + self.op_index_in_batch = 0; + + self.next() + } + } else { + None + } + } +} + +// HELPER FUNCTIONS +// ================================================================================================ + +/// Groups the provided operations into batches and computes the hash of the block. +fn batch_and_hash_ops(ops: Vec) -> (Vec, RpoDigest) { + // Group the operations into batches. + let batches = batch_ops(ops); + + // Compute the hash of all operation groups. + let op_groups: Vec = batches.iter().flat_map(|batch| batch.groups).collect(); + let hash = hasher::hash_elements(&op_groups); + + (batches, hash) +} + +/// Groups the provided operations into batches as described in the docs for this module (i.e., up +/// to 9 operations per group, and 8 groups per batch). +fn batch_ops(ops: Vec) -> Vec { + let mut batches = Vec::::new(); + let mut batch_acc = OpBatchAccumulator::new(); + + for op in ops { + // If the operation cannot be accepted into the current accumulator, add the contents of + // the accumulator to the list of batches and start a new accumulator. + if !batch_acc.can_accept_op(op) { + let batch = batch_acc.into_batch(); + batch_acc = OpBatchAccumulator::new(); + + batches.push(batch); + } + + // Add the operation to the accumulator. + batch_acc.add_op(op); + } + + // Make sure we finished processing the last batch. + if !batch_acc.is_empty() { + let batch = batch_acc.into_batch(); + batches.push(batch); + } + + batches +} + +/// Checks if a given decorators list is valid (only checked in debug mode) +/// - Assert the decorator list is in ascending order. +/// - Assert the last op index in decorator list is less than or equal to the number of operations. +#[cfg(debug_assertions)] +fn validate_decorators(operations: &[Operation], decorators: &DecoratorList) { + if !decorators.is_empty() { + // check if decorator list is sorted + for i in 0..(decorators.len() - 1) { + debug_assert!(decorators[i + 1].0 >= decorators[i].0, "unsorted decorators list"); + } + // assert the last index in decorator list is less than operations vector length + debug_assert!( + operations.len() >= decorators.last().expect("empty decorators list").0, + "last op index in decorator list should be less than or equal to the number of ops" + ); + } +} diff --git a/core/src/mast/node/basic_block_node/op_batch.rs b/core/src/mast/node/basic_block_node/op_batch.rs new file mode 100644 index 0000000000..e848df45fe --- /dev/null +++ b/core/src/mast/node/basic_block_node/op_batch.rs @@ -0,0 +1,175 @@ +use alloc::vec::Vec; + +use super::{Felt, Operation, BATCH_SIZE, GROUP_SIZE, ZERO}; + +// OPERATION BATCH +// ================================================================================================ + +/// A batch of operations in a span block. +/// +/// An operation batch consists of up to 8 operation groups, with each group containing up to 9 +/// operations or a single immediate value. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct OpBatch { + pub(super) ops: Vec, + pub(super) groups: [Felt; BATCH_SIZE], + pub(super) op_counts: [usize; BATCH_SIZE], + pub(super) num_groups: usize, +} + +impl OpBatch { + /// Returns a list of operations contained in this batch. + pub fn ops(&self) -> &[Operation] { + &self.ops + } + + /// Returns a list of operation groups contained in this batch. + /// + /// Each group is represented by a single field element. + pub fn groups(&self) -> &[Felt; BATCH_SIZE] { + &self.groups + } + + /// Returns the number of non-decorator operations for each operation group. + /// + /// Number of operations for groups containing immediate values is set to 0. + pub fn op_counts(&self) -> &[usize; BATCH_SIZE] { + &self.op_counts + } + + /// Returns the number of groups in this batch. + pub fn num_groups(&self) -> usize { + self.num_groups + } +} + +// OPERATION BATCH ACCUMULATOR +// ================================================================================================ + +/// An accumulator used in construction of operation batches. +pub(super) struct OpBatchAccumulator { + /// A list of operations in this batch, including decorators. + ops: Vec, + /// Values of operation groups, including immediate values. + groups: [Felt; BATCH_SIZE], + /// Number of non-decorator operations in each operation group. Operation count for groups + /// with immediate values is set to 0. + op_counts: [usize; BATCH_SIZE], + /// Value of the currently active op group. + group: u64, + /// Index of the next opcode in the current group. + op_idx: usize, + /// index of the current group in the batch. + group_idx: usize, + // Index of the next free group in the batch. + next_group_idx: usize, +} + +impl OpBatchAccumulator { + /// Returns a blank [OpBatchAccumulator]. + pub fn new() -> Self { + Self { + ops: Vec::new(), + groups: [ZERO; BATCH_SIZE], + op_counts: [0; BATCH_SIZE], + group: 0, + op_idx: 0, + group_idx: 0, + next_group_idx: 1, + } + } + + /// Returns true if this accumulator does not contain any operations. + pub fn is_empty(&self) -> bool { + self.ops.is_empty() + } + + /// Returns true if this accumulator can accept the specified operation. + /// + /// An accumulator may not be able accept an operation for the following reasons: + /// - There is no more space in the underlying batch (e.g., the 8th group of the batch already + /// contains 9 operations). + /// - There is no space for the immediate value carried by the operation (e.g., the 8th group is + /// only partially full, but we are trying to add a PUSH operation). + /// - The alignment rules require that the operation overflows into the next group, and if this + /// happens, there will be no space for the operation or its immediate value. + pub fn can_accept_op(&self, op: Operation) -> bool { + if op.imm_value().is_some() { + // an operation carrying an immediate value cannot be the last one in a group; so, we + // check if we need to move the operation to the next group. in either case, we need + // to make sure there is enough space for the immediate value as well. + if self.op_idx < GROUP_SIZE - 1 { + self.next_group_idx < BATCH_SIZE + } else { + self.next_group_idx + 1 < BATCH_SIZE + } + } else { + // check if there is space for the operation in the current group, or if there isn't, + // whether we can add another group + self.op_idx < GROUP_SIZE || self.next_group_idx < BATCH_SIZE + } + } + + /// Adds the specified operation to this accumulator. It is expected that the specified + /// operation is not a decorator and that (can_accept_op())[OpBatchAccumulator::can_accept_op] + /// is called before this function to make sure that the specified operation can be added to + /// the accumulator. + pub fn add_op(&mut self, op: Operation) { + // if the group is full, finalize it and start a new group + if self.op_idx == GROUP_SIZE { + self.finalize_op_group(); + } + + // for operations with immediate values, we need to do a few more things + if let Some(imm) = op.imm_value() { + // since an operation with an immediate value cannot be the last one in a group, if + // the operation would be the last one in the group, we need to start a new group + if self.op_idx == GROUP_SIZE - 1 { + self.finalize_op_group(); + } + + // save the immediate value at the next group index and advance the next group pointer + self.groups[self.next_group_idx] = imm; + self.next_group_idx += 1; + } + + // add the opcode to the group and increment the op index pointer + let opcode = op.op_code() as u64; + self.group |= opcode << (Operation::OP_BITS * self.op_idx); + self.ops.push(op); + self.op_idx += 1; + } + + /// Convert the accumulator into an [OpBatch]. + pub fn into_batch(mut self) -> OpBatch { + // make sure the last group gets added to the group array; we also check the op_idx to + // handle the case when a group contains a single NOOP operation. + if self.group != 0 || self.op_idx != 0 { + self.groups[self.group_idx] = Felt::new(self.group); + self.op_counts[self.group_idx] = self.op_idx; + } + + OpBatch { + ops: self.ops, + groups: self.groups, + op_counts: self.op_counts, + num_groups: self.next_group_idx, + } + } + + // HELPER METHODS + // -------------------------------------------------------------------------------------------- + + /// Saves the current group into the group array, advances current and next group pointers, + /// and resets group content. + pub(super) fn finalize_op_group(&mut self) { + self.groups[self.group_idx] = Felt::new(self.group); + self.op_counts[self.group_idx] = self.op_idx; + + self.group_idx = self.next_group_idx; + self.next_group_idx = self.group_idx + 1; + + self.op_idx = 0; + self.group = 0; + } +} diff --git a/core/src/mast/node/basic_block_node/tests.rs b/core/src/mast/node/basic_block_node/tests.rs new file mode 100644 index 0000000000..607793642c --- /dev/null +++ b/core/src/mast/node/basic_block_node/tests.rs @@ -0,0 +1,343 @@ +use super::*; +use crate::{mast::MastForest, Decorator, ONE}; + +#[test] +fn batch_ops() { + // --- one operation ---------------------------------------------------------------------- + let ops = vec![Operation::Add]; + let (batches, hash) = super::batch_and_hash_ops(ops.clone()); + assert_eq!(1, batches.len()); + + let batch = &batches[0]; + assert_eq!(ops, batch.ops); + assert_eq!(1, batch.num_groups()); + + let mut batch_groups = [ZERO; BATCH_SIZE]; + batch_groups[0] = build_group(&ops); + + assert_eq!(batch_groups, batch.groups); + assert_eq!([1_usize, 0, 0, 0, 0, 0, 0, 0], batch.op_counts); + assert_eq!(hasher::hash_elements(&batch_groups), hash); + + // --- two operations --------------------------------------------------------------------- + let ops = vec![Operation::Add, Operation::Mul]; + let (batches, hash) = super::batch_and_hash_ops(ops.clone()); + assert_eq!(1, batches.len()); + + let batch = &batches[0]; + assert_eq!(ops, batch.ops); + assert_eq!(1, batch.num_groups()); + + let mut batch_groups = [ZERO; BATCH_SIZE]; + batch_groups[0] = build_group(&ops); + + assert_eq!(batch_groups, batch.groups); + assert_eq!([2_usize, 0, 0, 0, 0, 0, 0, 0], batch.op_counts); + assert_eq!(hasher::hash_elements(&batch_groups), hash); + + // --- one group with one immediate value ------------------------------------------------- + let ops = vec![Operation::Add, Operation::Push(Felt::new(12345678))]; + let (batches, hash) = super::batch_and_hash_ops(ops.clone()); + assert_eq!(1, batches.len()); + + let batch = &batches[0]; + assert_eq!(ops, batch.ops); + assert_eq!(2, batch.num_groups()); + + let mut batch_groups = [ZERO; BATCH_SIZE]; + batch_groups[0] = build_group(&ops); + batch_groups[1] = Felt::new(12345678); + + assert_eq!(batch_groups, batch.groups); + assert_eq!([2_usize, 0, 0, 0, 0, 0, 0, 0], batch.op_counts); + assert_eq!(hasher::hash_elements(&batch_groups), hash); + + // --- one group with 7 immediate values -------------------------------------------------- + let ops = vec![ + Operation::Push(ONE), + Operation::Push(Felt::new(2)), + Operation::Push(Felt::new(3)), + Operation::Push(Felt::new(4)), + Operation::Push(Felt::new(5)), + Operation::Push(Felt::new(6)), + Operation::Push(Felt::new(7)), + Operation::Add, + ]; + let (batches, hash) = super::batch_and_hash_ops(ops.clone()); + assert_eq!(1, batches.len()); + + let batch = &batches[0]; + assert_eq!(ops, batch.ops); + assert_eq!(8, batch.num_groups()); + + let batch_groups = [ + build_group(&ops), + ONE, + Felt::new(2), + Felt::new(3), + Felt::new(4), + Felt::new(5), + Felt::new(6), + Felt::new(7), + ]; + + assert_eq!(batch_groups, batch.groups); + assert_eq!([8_usize, 0, 0, 0, 0, 0, 0, 0], batch.op_counts); + assert_eq!(hasher::hash_elements(&batch_groups), hash); + + // --- two groups with 7 immediate values; the last push overflows to the second batch ---- + let ops = vec![ + Operation::Add, + Operation::Mul, + Operation::Push(ONE), + Operation::Push(Felt::new(2)), + Operation::Push(Felt::new(3)), + Operation::Push(Felt::new(4)), + Operation::Push(Felt::new(5)), + Operation::Push(Felt::new(6)), + Operation::Add, + Operation::Push(Felt::new(7)), + ]; + let (batches, hash) = super::batch_and_hash_ops(ops.clone()); + assert_eq!(2, batches.len()); + + let batch0 = &batches[0]; + assert_eq!(ops[..9], batch0.ops); + assert_eq!(7, batch0.num_groups()); + + let batch0_groups = [ + build_group(&ops[..9]), + ONE, + Felt::new(2), + Felt::new(3), + Felt::new(4), + Felt::new(5), + Felt::new(6), + ZERO, + ]; + + assert_eq!(batch0_groups, batch0.groups); + assert_eq!([9_usize, 0, 0, 0, 0, 0, 0, 0], batch0.op_counts); + + let batch1 = &batches[1]; + assert_eq!(vec![ops[9]], batch1.ops); + assert_eq!(2, batch1.num_groups()); + + let mut batch1_groups = [ZERO; BATCH_SIZE]; + batch1_groups[0] = build_group(&[ops[9]]); + batch1_groups[1] = Felt::new(7); + + assert_eq!([1_usize, 0, 0, 0, 0, 0, 0, 0], batch1.op_counts); + assert_eq!(batch1_groups, batch1.groups); + + let all_groups = [batch0_groups, batch1_groups].concat(); + assert_eq!(hasher::hash_elements(&all_groups), hash); + + // --- immediate values in-between groups ------------------------------------------------- + let ops = vec![ + Operation::Add, + Operation::Mul, + Operation::Add, + Operation::Push(Felt::new(7)), + Operation::Add, + Operation::Add, + Operation::Push(Felt::new(11)), + Operation::Mul, + Operation::Mul, + Operation::Add, + ]; + + let (batches, hash) = super::batch_and_hash_ops(ops.clone()); + assert_eq!(1, batches.len()); + + let batch = &batches[0]; + assert_eq!(ops, batch.ops); + assert_eq!(4, batch.num_groups()); + + let batch_groups = [ + build_group(&ops[..9]), + Felt::new(7), + Felt::new(11), + build_group(&ops[9..]), + ZERO, + ZERO, + ZERO, + ZERO, + ]; + + assert_eq!([9_usize, 0, 0, 1, 0, 0, 0, 0], batch.op_counts); + assert_eq!(batch_groups, batch.groups); + assert_eq!(hasher::hash_elements(&batch_groups), hash); + + // --- push at the end of a group is moved into the next group ---------------------------- + let ops = vec![ + Operation::Add, + Operation::Mul, + Operation::Add, + Operation::Add, + Operation::Add, + Operation::Mul, + Operation::Mul, + Operation::Add, + Operation::Push(Felt::new(11)), + ]; + let (batches, hash) = super::batch_and_hash_ops(ops.clone()); + assert_eq!(1, batches.len()); + + let batch = &batches[0]; + assert_eq!(ops, batch.ops); + assert_eq!(3, batch.num_groups()); + + let batch_groups = [ + build_group(&ops[..8]), + build_group(&[ops[8]]), + Felt::new(11), + ZERO, + ZERO, + ZERO, + ZERO, + ZERO, + ]; + + assert_eq!(batch_groups, batch.groups); + assert_eq!([8_usize, 1, 0, 0, 0, 0, 0, 0], batch.op_counts); + assert_eq!(hasher::hash_elements(&batch_groups), hash); + + // --- push at the end of a group is moved into the next group ---------------------------- + let ops = vec![ + Operation::Add, + Operation::Mul, + Operation::Add, + Operation::Add, + Operation::Add, + Operation::Mul, + Operation::Mul, + Operation::Push(ONE), + Operation::Push(Felt::new(2)), + ]; + let (batches, hash) = super::batch_and_hash_ops(ops.clone()); + assert_eq!(1, batches.len()); + + let batch = &batches[0]; + assert_eq!(ops, batch.ops); + assert_eq!(4, batch.num_groups()); + + let batch_groups = [ + build_group(&ops[..8]), + ONE, + build_group(&[ops[8]]), + Felt::new(2), + ZERO, + ZERO, + ZERO, + ZERO, + ]; + + assert_eq!(batch_groups, batch.groups); + assert_eq!([8_usize, 0, 1, 0, 0, 0, 0, 0], batch.op_counts); + assert_eq!(hasher::hash_elements(&batch_groups), hash); + + // --- push at the end of the 7th group overflows to the next batch ----------------------- + let ops = vec![ + Operation::Add, + Operation::Mul, + Operation::Push(ONE), + Operation::Push(Felt::new(2)), + Operation::Push(Felt::new(3)), + Operation::Push(Felt::new(4)), + Operation::Push(Felt::new(5)), + Operation::Add, + Operation::Mul, + Operation::Add, + Operation::Mul, + Operation::Add, + Operation::Mul, + Operation::Add, + Operation::Mul, + Operation::Add, + Operation::Mul, + Operation::Push(Felt::new(6)), + Operation::Pad, + ]; + + let (batches, hash) = super::batch_and_hash_ops(ops.clone()); + assert_eq!(2, batches.len()); + + let batch0 = &batches[0]; + assert_eq!(ops[..17], batch0.ops); + assert_eq!(7, batch0.num_groups()); + + let batch0_groups = [ + build_group(&ops[..9]), + ONE, + Felt::new(2), + Felt::new(3), + Felt::new(4), + Felt::new(5), + build_group(&ops[9..17]), + ZERO, + ]; + + assert_eq!(batch0_groups, batch0.groups); + assert_eq!([9_usize, 0, 0, 0, 0, 0, 8, 0], batch0.op_counts); + + let batch1 = &batches[1]; + assert_eq!(ops[17..], batch1.ops); + assert_eq!(2, batch1.num_groups()); + + let batch1_groups = [build_group(&ops[17..]), Felt::new(6), ZERO, ZERO, ZERO, ZERO, ZERO, ZERO]; + assert_eq!(batch1_groups, batch1.groups); + assert_eq!([2_usize, 0, 0, 0, 0, 0, 0, 0], batch1.op_counts); + + let all_groups = [batch0_groups, batch1_groups].concat(); + assert_eq!(hasher::hash_elements(&all_groups), hash); +} + +#[test] +fn operation_or_decorator_iterator() { + let mut mast_forest = MastForest::new(); + let operations = vec![Operation::Add, Operation::Mul, Operation::MovDn2, Operation::MovDn3]; + + // Note: there are 2 decorators after the last instruction + let decorators = vec![ + (0, Decorator::Trace(0)), // ID: 0 + (0, Decorator::Trace(1)), // ID: 1 + (3, Decorator::Trace(2)), // ID: 2 + (4, Decorator::Trace(3)), // ID: 3 + (4, Decorator::Trace(4)), // ID: 4 + ]; + + let node = + BasicBlockNode::new_with_raw_decorators(operations, decorators, &mut mast_forest).unwrap(); + + let mut iterator = node.iter(); + + // operation index 0 + assert_eq!(iterator.next(), Some(OperationOrDecorator::Decorator(&DecoratorId(0)))); + assert_eq!(iterator.next(), Some(OperationOrDecorator::Decorator(&DecoratorId(1)))); + assert_eq!(iterator.next(), Some(OperationOrDecorator::Operation(&Operation::Add))); + + // operations indices 1, 2 + assert_eq!(iterator.next(), Some(OperationOrDecorator::Operation(&Operation::Mul))); + assert_eq!(iterator.next(), Some(OperationOrDecorator::Operation(&Operation::MovDn2))); + + // operation index 3 + assert_eq!(iterator.next(), Some(OperationOrDecorator::Decorator(&DecoratorId(2)))); + assert_eq!(iterator.next(), Some(OperationOrDecorator::Operation(&Operation::MovDn3))); + + // after last operation + assert_eq!(iterator.next(), Some(OperationOrDecorator::Decorator(&DecoratorId(3)))); + assert_eq!(iterator.next(), Some(OperationOrDecorator::Decorator(&DecoratorId(4)))); + assert_eq!(iterator.next(), None); +} + +// TEST HELPERS +// -------------------------------------------------------------------------------------------- + +fn build_group(ops: &[Operation]) -> Felt { + let mut group = 0u64; + for (i, op) in ops.iter().enumerate() { + group |= (op.op_code() as u64) << (Operation::OP_BITS * i); + } + Felt::new(group) +} diff --git a/core/src/mast/node/call_node.rs b/core/src/mast/node/call_node.rs new file mode 100644 index 0000000000..c028a32456 --- /dev/null +++ b/core/src/mast/node/call_node.rs @@ -0,0 +1,273 @@ +use alloc::vec::Vec; +use core::fmt; + +use miden_crypto::{hash::rpo::RpoDigest, Felt}; +use miden_formatting::{ + hex::ToHex, + prettier::{const_text, nl, text, Document, PrettyPrint}, +}; + +use crate::{ + chiplets::hasher, + mast::{DecoratorId, MastForest, MastForestError, MastNodeId}, + OPCODE_CALL, OPCODE_SYSCALL, +}; + +// CALL NODE +// ================================================================================================ + +/// A Call node describes a function call such that the callee is executed in a different execution +/// context from the currently executing code. +/// +/// A call node can be of two types: +/// - A simple call: the callee is executed in the new user context. +/// - A syscall: the callee is executed in the root context. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CallNode { + callee: MastNodeId, + is_syscall: bool, + digest: RpoDigest, + before_enter: Vec, + after_exit: Vec, +} + +//------------------------------------------------------------------------------------------------- +/// Constants +impl CallNode { + /// The domain of the call block (used for control block hashing). + pub const CALL_DOMAIN: Felt = Felt::new(OPCODE_CALL as u64); + /// The domain of the syscall block (used for control block hashing). + pub const SYSCALL_DOMAIN: Felt = Felt::new(OPCODE_SYSCALL as u64); +} + +//------------------------------------------------------------------------------------------------- +/// Constructors +impl CallNode { + /// Returns a new [`CallNode`] instantiated with the specified callee. + pub fn new(callee: MastNodeId, mast_forest: &MastForest) -> Result { + if callee.as_usize() >= mast_forest.nodes.len() { + return Err(MastForestError::NodeIdOverflow(callee, mast_forest.nodes.len())); + } + let digest = { + let callee_digest = mast_forest[callee].digest(); + + hasher::merge_in_domain(&[callee_digest, RpoDigest::default()], Self::CALL_DOMAIN) + }; + + Ok(Self { + callee, + is_syscall: false, + digest, + before_enter: Vec::new(), + after_exit: Vec::new(), + }) + } + + /// Returns a new [`CallNode`] from values that are assumed to be correct. + /// Should only be used when the source of the inputs is trusted (e.g. deserialization). + pub fn new_unsafe(callee: MastNodeId, digest: RpoDigest) -> Self { + Self { + callee, + is_syscall: false, + digest, + before_enter: Vec::new(), + after_exit: Vec::new(), + } + } + + /// Returns a new [`CallNode`] instantiated with the specified callee and marked as a kernel + /// call. + pub fn new_syscall( + callee: MastNodeId, + mast_forest: &MastForest, + ) -> Result { + if callee.as_usize() >= mast_forest.nodes.len() { + return Err(MastForestError::NodeIdOverflow(callee, mast_forest.nodes.len())); + } + let digest = { + let callee_digest = mast_forest[callee].digest(); + + hasher::merge_in_domain(&[callee_digest, RpoDigest::default()], Self::SYSCALL_DOMAIN) + }; + + Ok(Self { + callee, + is_syscall: true, + digest, + before_enter: Vec::new(), + after_exit: Vec::new(), + }) + } + + /// Returns a new syscall [`CallNode`] from values that are assumed to be correct. + /// Should only be used when the source of the inputs is trusted (e.g. deserialization). + pub fn new_syscall_unsafe(callee: MastNodeId, digest: RpoDigest) -> Self { + Self { + callee, + is_syscall: true, + digest, + before_enter: Vec::new(), + after_exit: Vec::new(), + } + } +} + +//------------------------------------------------------------------------------------------------- +/// Public accessors +impl CallNode { + /// Returns a commitment to this Call node. + /// + /// The commitment is computed as a hash of the callee and an empty word ([ZERO; 4]) in the + /// domain defined by either [Self::CALL_DOMAIN] or [Self::SYSCALL_DOMAIN], depending on + /// whether the node represents a simple call or a syscall - i.e.,: + /// ``` + /// # use miden_core::mast::CallNode; + /// # use miden_crypto::{hash::rpo::{RpoDigest as Digest, Rpo256 as Hasher}}; + /// # let callee_digest = Digest::default(); + /// Hasher::merge_in_domain(&[callee_digest, Digest::default()], CallNode::CALL_DOMAIN); + /// ``` + /// or + /// ``` + /// # use miden_core::mast::CallNode; + /// # use miden_crypto::{hash::rpo::{RpoDigest as Digest, Rpo256 as Hasher}}; + /// # let callee_digest = Digest::default(); + /// Hasher::merge_in_domain(&[callee_digest, Digest::default()], CallNode::SYSCALL_DOMAIN); + /// ``` + pub fn digest(&self) -> RpoDigest { + self.digest + } + + /// Returns the ID of the node to be invoked by this call node. + pub fn callee(&self) -> MastNodeId { + self.callee + } + + /// Returns true if this call node represents a syscall. + pub fn is_syscall(&self) -> bool { + self.is_syscall + } + + /// Returns the domain of this call node. + pub fn domain(&self) -> Felt { + if self.is_syscall() { + Self::SYSCALL_DOMAIN + } else { + Self::CALL_DOMAIN + } + } + + /// Returns the decorators to be executed before this node is executed. + pub fn before_enter(&self) -> &[DecoratorId] { + &self.before_enter + } + + /// Returns the decorators to be executed after this node is executed. + pub fn after_exit(&self) -> &[DecoratorId] { + &self.after_exit + } +} + +/// Mutators +impl CallNode { + /// Sets the list of decorators to be executed before this node. + pub fn set_before_enter(&mut self, decorator_ids: Vec) { + self.before_enter = decorator_ids; + } + + /// Sets the list of decorators to be executed after this node. + pub fn set_after_exit(&mut self, decorator_ids: Vec) { + self.after_exit = decorator_ids; + } +} + +// PRETTY PRINTING +// ================================================================================================ + +impl CallNode { + pub(super) fn to_pretty_print<'a>( + &'a self, + mast_forest: &'a MastForest, + ) -> impl PrettyPrint + 'a { + CallNodePrettyPrint { node: self, mast_forest } + } + + pub(super) fn to_display<'a>(&'a self, mast_forest: &'a MastForest) -> impl fmt::Display + 'a { + CallNodePrettyPrint { node: self, mast_forest } + } +} + +struct CallNodePrettyPrint<'a> { + node: &'a CallNode, + mast_forest: &'a MastForest, +} + +impl<'a> CallNodePrettyPrint<'a> { + /// Concatenates the provided decorators in a single line. If the list of decorators is not + /// empty, prepends `prepend` and appends `append` to the decorator document. + fn concatenate_decorators( + &self, + decorator_ids: &[DecoratorId], + prepend: Document, + append: Document, + ) -> Document { + let decorators = decorator_ids + .iter() + .map(|&decorator_id| self.mast_forest[decorator_id].render()) + .reduce(|acc, doc| acc + const_text(" ") + doc) + .unwrap_or_default(); + + if decorators.is_empty() { + decorators + } else { + prepend + decorators + append + } + } + + fn single_line_pre_decorators(&self) -> Document { + self.concatenate_decorators(self.node.before_enter(), Document::Empty, const_text(" ")) + } + + fn single_line_post_decorators(&self) -> Document { + self.concatenate_decorators(self.node.after_exit(), const_text(" "), Document::Empty) + } + + fn multi_line_pre_decorators(&self) -> Document { + self.concatenate_decorators(self.node.before_enter(), Document::Empty, nl()) + } + + fn multi_line_post_decorators(&self) -> Document { + self.concatenate_decorators(self.node.after_exit(), nl(), Document::Empty) + } +} + +impl<'a> PrettyPrint for CallNodePrettyPrint<'a> { + fn render(&self) -> Document { + let call_or_syscall = { + let callee_digest = self.mast_forest[self.node.callee].digest(); + if self.node.is_syscall { + const_text("syscall") + + const_text(".") + + text(callee_digest.as_bytes().to_hex_with_prefix()) + } else { + const_text("call") + + const_text(".") + + text(callee_digest.as_bytes().to_hex_with_prefix()) + } + }; + + let single_line = self.single_line_pre_decorators() + + call_or_syscall.clone() + + self.single_line_post_decorators(); + let multi_line = + self.multi_line_pre_decorators() + call_or_syscall + self.multi_line_post_decorators(); + + single_line | multi_line + } +} + +impl<'a> fmt::Display for CallNodePrettyPrint<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use crate::prettier::PrettyPrint; + self.pretty_print(f) + } +} diff --git a/core/src/mast/node/dyn_node.rs b/core/src/mast/node/dyn_node.rs new file mode 100644 index 0000000000..34e87610b8 --- /dev/null +++ b/core/src/mast/node/dyn_node.rs @@ -0,0 +1,171 @@ +use alloc::vec::Vec; +use core::fmt; + +use miden_crypto::{hash::rpo::RpoDigest, Felt}; +use miden_formatting::prettier::{const_text, nl, Document, PrettyPrint}; + +use crate::{ + mast::{DecoratorId, MastForest}, + OPCODE_DYN, +}; + +// DYN NODE +// ================================================================================================ + +/// A Dyn node specifies that the node to be executed next is defined dynamically via the stack. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct DynNode { + before_enter: Vec, + after_exit: Vec, +} + +/// Constants +impl DynNode { + /// The domain of the Dyn block (used for control block hashing). + pub const DOMAIN: Felt = Felt::new(OPCODE_DYN as u64); +} + +/// Public accessors +impl DynNode { + /// Returns a commitment to a Dyn node. + /// + /// The commitment is computed by hashing two empty words ([ZERO; 4]) in the domain defined + /// by [Self::DOMAIN], i.e.: + /// + /// ``` + /// # use miden_core::mast::DynNode; + /// # use miden_crypto::{hash::rpo::{RpoDigest as Digest, Rpo256 as Hasher}}; + /// Hasher::merge_in_domain(&[Digest::default(), Digest::default()], DynNode::DOMAIN); + /// ``` + pub fn digest(&self) -> RpoDigest { + RpoDigest::new([ + Felt::new(8115106948140260551), + Felt::new(13491227816952616836), + Felt::new(15015806788322198710), + Felt::new(16575543461540527115), + ]) + } + + /// Returns the decorators to be executed before this node is executed. + pub fn before_enter(&self) -> &[DecoratorId] { + &self.before_enter + } + + /// Returns the decorators to be executed after this node is executed. + pub fn after_exit(&self) -> &[DecoratorId] { + &self.after_exit + } +} + +/// Mutators +impl DynNode { + /// Sets the list of decorators to be executed before this node. + pub fn set_before_enter(&mut self, decorator_ids: Vec) { + self.before_enter = decorator_ids; + } + + /// Sets the list of decorators to be executed after this node. + pub fn set_after_exit(&mut self, decorator_ids: Vec) { + self.after_exit = decorator_ids; + } +} + +// PRETTY PRINTING +// ================================================================================================ + +impl DynNode { + pub(super) fn to_display<'a>(&'a self, mast_forest: &'a MastForest) -> impl fmt::Display + 'a { + DynNodePrettyPrint { node: self, mast_forest } + } + + pub(super) fn to_pretty_print<'a>( + &'a self, + mast_forest: &'a MastForest, + ) -> impl PrettyPrint + 'a { + DynNodePrettyPrint { node: self, mast_forest } + } +} + +struct DynNodePrettyPrint<'a> { + node: &'a DynNode, + mast_forest: &'a MastForest, +} + +impl<'a> DynNodePrettyPrint<'a> { + /// Concatenates the provided decorators in a single line. If the list of decorators is not + /// empty, prepends `prepend` and appends `append` to the decorator document. + fn concatenate_decorators( + &self, + decorator_ids: &[DecoratorId], + prepend: Document, + append: Document, + ) -> Document { + let decorators = decorator_ids + .iter() + .map(|&decorator_id| self.mast_forest[decorator_id].render()) + .reduce(|acc, doc| acc + const_text(" ") + doc) + .unwrap_or_default(); + + if decorators.is_empty() { + decorators + } else { + prepend + decorators + append + } + } + + fn single_line_pre_decorators(&self) -> Document { + self.concatenate_decorators(self.node.before_enter(), Document::Empty, const_text(" ")) + } + + fn single_line_post_decorators(&self) -> Document { + self.concatenate_decorators(self.node.after_exit(), const_text(" "), Document::Empty) + } + + fn multi_line_pre_decorators(&self) -> Document { + self.concatenate_decorators(self.node.before_enter(), Document::Empty, nl()) + } + + fn multi_line_post_decorators(&self) -> Document { + self.concatenate_decorators(self.node.after_exit(), nl(), Document::Empty) + } +} + +impl<'a> crate::prettier::PrettyPrint for DynNodePrettyPrint<'a> { + fn render(&self) -> crate::prettier::Document { + let dyn_text = const_text("dyn"); + + let single_line = self.single_line_pre_decorators() + + dyn_text.clone() + + self.single_line_post_decorators(); + let multi_line = + self.multi_line_pre_decorators() + dyn_text + self.multi_line_post_decorators(); + + single_line | multi_line + } +} + +impl<'a> fmt::Display for DynNodePrettyPrint<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.pretty_print(f) + } +} + +// TESTS +// ================================================================================================ + +#[cfg(test)] +mod tests { + use miden_crypto::hash::rpo::Rpo256; + + use super::*; + + /// Ensures that the hash of `DynNode` is indeed the hash of 2 empty words, in the `DynNode` + /// domain. + #[test] + pub fn test_dyn_node_digest() { + assert_eq!( + DynNode::default().digest(), + Rpo256::merge_in_domain(&[RpoDigest::default(), RpoDigest::default()], DynNode::DOMAIN) + ); + } +} diff --git a/core/src/mast/node/external.rs b/core/src/mast/node/external.rs new file mode 100644 index 0000000000..3b3f23e403 --- /dev/null +++ b/core/src/mast/node/external.rs @@ -0,0 +1,152 @@ +use alloc::vec::Vec; +use core::fmt; + +use miden_crypto::hash::rpo::RpoDigest; +use miden_formatting::{ + hex::ToHex, + prettier::{const_text, nl, text, Document, PrettyPrint}, +}; + +use crate::mast::{DecoratorId, MastForest}; + +// EXTERNAL NODE +// ================================================================================================ + +/// Node for referencing procedures not present in a given [`MastForest`] (hence "external"). +/// +/// External nodes can be used to verify the integrity of a program's hash while keeping parts of +/// the program secret. They also allow a program to refer to a well-known procedure that was not +/// compiled with the program (e.g. a procedure in the standard library). +/// +/// The hash of an external node is the hash of the procedure it represents, such that an external +/// node can be swapped with the actual subtree that it represents without changing the MAST root. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ExternalNode { + digest: RpoDigest, + before_enter: Vec, + after_exit: Vec, +} + +impl ExternalNode { + /// Returns a new [`ExternalNode`] instantiated with the specified procedure hash. + pub fn new(procedure_hash: RpoDigest) -> Self { + Self { + digest: procedure_hash, + before_enter: Vec::new(), + after_exit: Vec::new(), + } + } +} + +impl ExternalNode { + /// Returns the commitment to the MAST node referenced by this external node. + pub fn digest(&self) -> RpoDigest { + self.digest + } + + /// Returns the decorators to be executed before this node is executed. + pub fn before_enter(&self) -> &[DecoratorId] { + &self.before_enter + } + + /// Returns the decorators to be executed after this node is executed. + pub fn after_exit(&self) -> &[DecoratorId] { + &self.after_exit + } +} + +/// Mutators +impl ExternalNode { + /// Sets the list of decorators to be executed before this node. + pub fn set_before_enter(&mut self, decorator_ids: Vec) { + self.before_enter = decorator_ids; + } + + /// Sets the list of decorators to be executed after this node. + pub fn set_after_exit(&mut self, decorator_ids: Vec) { + self.after_exit = decorator_ids; + } +} + +// PRETTY PRINTING +// ================================================================================================ + +impl ExternalNode { + pub(super) fn to_display<'a>(&'a self, mast_forest: &'a MastForest) -> impl fmt::Display + 'a { + ExternalNodePrettyPrint { node: self, mast_forest } + } + + pub(super) fn to_pretty_print<'a>( + &'a self, + mast_forest: &'a MastForest, + ) -> impl PrettyPrint + 'a { + ExternalNodePrettyPrint { node: self, mast_forest } + } +} + +struct ExternalNodePrettyPrint<'a> { + node: &'a ExternalNode, + mast_forest: &'a MastForest, +} + +impl<'a> ExternalNodePrettyPrint<'a> { + /// Concatenates the provided decorators in a single line. If the list of decorators is not + /// empty, prepends `prepend` and appends `append` to the decorator document. + fn concatenate_decorators( + &self, + decorator_ids: &[DecoratorId], + prepend: Document, + append: Document, + ) -> Document { + let decorators = decorator_ids + .iter() + .map(|&decorator_id| self.mast_forest[decorator_id].render()) + .reduce(|acc, doc| acc + const_text(" ") + doc) + .unwrap_or_default(); + + if decorators.is_empty() { + decorators + } else { + prepend + decorators + append + } + } + + fn single_line_pre_decorators(&self) -> Document { + self.concatenate_decorators(self.node.before_enter(), Document::Empty, const_text(" ")) + } + + fn single_line_post_decorators(&self) -> Document { + self.concatenate_decorators(self.node.after_exit(), const_text(" "), Document::Empty) + } + + fn multi_line_pre_decorators(&self) -> Document { + self.concatenate_decorators(self.node.before_enter(), Document::Empty, nl()) + } + + fn multi_line_post_decorators(&self) -> Document { + self.concatenate_decorators(self.node.after_exit(), nl(), Document::Empty) + } +} + +impl<'a> crate::prettier::PrettyPrint for ExternalNodePrettyPrint<'a> { + fn render(&self) -> crate::prettier::Document { + let external = const_text("external") + + const_text(".") + + text(self.node.digest.as_bytes().to_hex_with_prefix()); + + let single_line = self.single_line_pre_decorators() + + external.clone() + + self.single_line_post_decorators(); + let multi_line = + self.multi_line_pre_decorators() + external + self.multi_line_post_decorators(); + + single_line | multi_line + } +} + +impl<'a> fmt::Display for ExternalNodePrettyPrint<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use crate::prettier::PrettyPrint; + self.pretty_print(f) + } +} diff --git a/core/src/mast/node/join_node.rs b/core/src/mast/node/join_node.rs new file mode 100644 index 0000000000..17aaccd8ec --- /dev/null +++ b/core/src/mast/node/join_node.rs @@ -0,0 +1,203 @@ +use alloc::vec::Vec; +use core::fmt; + +use miden_crypto::{hash::rpo::RpoDigest, Felt}; + +use crate::{ + chiplets::hasher, + mast::{DecoratorId, MastForest, MastForestError, MastNodeId}, + prettier::PrettyPrint, + OPCODE_JOIN, +}; + +// JOIN NODE +// ================================================================================================ + +/// A Join node describe sequential execution. When the VM encounters a Join node, it executes the +/// first child first and the second child second. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct JoinNode { + children: [MastNodeId; 2], + digest: RpoDigest, + before_enter: Vec, + after_exit: Vec, +} + +/// Constants +impl JoinNode { + /// The domain of the join block (used for control block hashing). + pub const DOMAIN: Felt = Felt::new(OPCODE_JOIN as u64); +} + +/// Constructors +impl JoinNode { + /// Returns a new [`JoinNode`] instantiated with the specified children nodes. + pub fn new( + children: [MastNodeId; 2], + mast_forest: &MastForest, + ) -> Result { + let forest_len = mast_forest.nodes.len(); + if children[0].as_usize() >= forest_len { + return Err(MastForestError::NodeIdOverflow(children[0], forest_len)); + } else if children[1].as_usize() >= forest_len { + return Err(MastForestError::NodeIdOverflow(children[1], forest_len)); + } + let digest = { + let left_child_hash = mast_forest[children[0]].digest(); + let right_child_hash = mast_forest[children[1]].digest(); + + hasher::merge_in_domain(&[left_child_hash, right_child_hash], Self::DOMAIN) + }; + + Ok(Self { + children, + digest, + before_enter: Vec::new(), + after_exit: Vec::new(), + }) + } + + /// Returns a new [`JoinNode`] from values that are assumed to be correct. + /// Should only be used when the source of the inputs is trusted (e.g. deserialization). + pub fn new_unsafe(children: [MastNodeId; 2], digest: RpoDigest) -> Self { + Self { + children, + digest, + before_enter: Vec::new(), + after_exit: Vec::new(), + } + } +} + +/// Public accessors +impl JoinNode { + /// Returns a commitment to this Join node. + /// + /// The commitment is computed as a hash of the `first` and `second` child node in the domain + /// defined by [Self::DOMAIN] - i.e.,: + /// ``` + /// # use miden_core::mast::JoinNode; + /// # use miden_crypto::{hash::rpo::{RpoDigest as Digest, Rpo256 as Hasher}}; + /// # let first_child_digest = Digest::default(); + /// # let second_child_digest = Digest::default(); + /// Hasher::merge_in_domain(&[first_child_digest, second_child_digest], JoinNode::DOMAIN); + /// ``` + pub fn digest(&self) -> RpoDigest { + self.digest + } + + /// Returns the ID of the node that is to be executed first. + pub fn first(&self) -> MastNodeId { + self.children[0] + } + + /// Returns the ID of the node that is to be executed after the execution of the program + /// defined by the first node completes. + pub fn second(&self) -> MastNodeId { + self.children[1] + } + + /// Returns the decorators to be executed before this node is executed. + pub fn before_enter(&self) -> &[DecoratorId] { + &self.before_enter + } + + /// Returns the decorators to be executed after this node is executed. + pub fn after_exit(&self) -> &[DecoratorId] { + &self.after_exit + } +} + +/// Mutators +impl JoinNode { + /// Sets the list of decorators to be executed before this node. + pub fn set_before_enter(&mut self, decorator_ids: Vec) { + self.before_enter = decorator_ids; + } + + /// Sets the list of decorators to be executed after this node. + pub fn set_after_exit(&mut self, decorator_ids: Vec) { + self.after_exit = decorator_ids; + } +} + +// PRETTY PRINTING +// ================================================================================================ + +impl JoinNode { + pub(super) fn to_display<'a>(&'a self, mast_forest: &'a MastForest) -> impl fmt::Display + 'a { + JoinNodePrettyPrint { join_node: self, mast_forest } + } + + pub(super) fn to_pretty_print<'a>( + &'a self, + mast_forest: &'a MastForest, + ) -> impl PrettyPrint + 'a { + JoinNodePrettyPrint { join_node: self, mast_forest } + } +} + +struct JoinNodePrettyPrint<'a> { + join_node: &'a JoinNode, + mast_forest: &'a MastForest, +} + +impl<'a> PrettyPrint for JoinNodePrettyPrint<'a> { + #[rustfmt::skip] + fn render(&self) -> crate::prettier::Document { + use crate::prettier::*; + + let pre_decorators = { + let mut pre_decorators = self + .join_node + .before_enter() + .iter() + .map(|&decorator_id| self.mast_forest[decorator_id].render()) + .reduce(|acc, doc| acc + const_text(" ") + doc) + .unwrap_or_default(); + if !pre_decorators.is_empty() { + pre_decorators += nl(); + } + + pre_decorators + }; + + let post_decorators = { + let mut post_decorators = self + .join_node + .after_exit() + .iter() + .map(|&decorator_id| self.mast_forest[decorator_id].render()) + .reduce(|acc, doc| acc + const_text(" ") + doc) + .unwrap_or_default(); + if !post_decorators.is_empty() { + post_decorators = nl() + post_decorators; + } + + post_decorators + }; + + let first_child = + self.mast_forest[self.join_node.first()].to_pretty_print(self.mast_forest); + let second_child = + self.mast_forest[self.join_node.second()].to_pretty_print(self.mast_forest); + + pre_decorators + + indent( + 4, + const_text("join") + + nl() + + first_child.render() + + nl() + + second_child.render(), + ) + nl() + const_text("end") + + post_decorators + } +} + +impl<'a> fmt::Display for JoinNodePrettyPrint<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use crate::prettier::PrettyPrint; + self.pretty_print(f) + } +} diff --git a/core/src/mast/node/loop_node.rs b/core/src/mast/node/loop_node.rs new file mode 100644 index 0000000000..08933ac521 --- /dev/null +++ b/core/src/mast/node/loop_node.rs @@ -0,0 +1,183 @@ +use alloc::vec::Vec; +use core::fmt; + +use miden_crypto::{hash::rpo::RpoDigest, Felt}; +use miden_formatting::prettier::PrettyPrint; + +use crate::{ + chiplets::hasher, + mast::{DecoratorId, MastForest, MastForestError, MastNodeId}, + OPCODE_LOOP, +}; + +// LOOP NODE +// ================================================================================================ + +/// A Loop node defines condition-controlled iterative execution. When the VM encounters a Loop +/// node, it will keep executing the body of the loop as long as the top of the stack is `1``. +/// +/// The loop is exited when at the end of executing the loop body the top of the stack is `0``. +/// If the top of the stack is neither `0` nor `1` when the condition is checked, the execution +/// fails. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct LoopNode { + body: MastNodeId, + digest: RpoDigest, + before_enter: Vec, + after_exit: Vec, +} + +/// Constants +impl LoopNode { + /// The domain of the loop node (used for control block hashing). + pub const DOMAIN: Felt = Felt::new(OPCODE_LOOP as u64); +} + +/// Constructors +impl LoopNode { + /// Returns a new [`LoopNode`] instantiated with the specified body node. + pub fn new(body: MastNodeId, mast_forest: &MastForest) -> Result { + if body.as_usize() >= mast_forest.nodes.len() { + return Err(MastForestError::NodeIdOverflow(body, mast_forest.nodes.len())); + } + let digest = { + let body_hash = mast_forest[body].digest(); + + hasher::merge_in_domain(&[body_hash, RpoDigest::default()], Self::DOMAIN) + }; + + Ok(Self { + body, + digest, + before_enter: Vec::new(), + after_exit: Vec::new(), + }) + } + + /// Returns a new [`LoopNode`] from values that are assumed to be correct. + /// Should only be used when the source of the inputs is trusted (e.g. deserialization). + pub fn new_unsafe(body: MastNodeId, digest: RpoDigest) -> Self { + Self { + body, + digest, + before_enter: Vec::new(), + after_exit: Vec::new(), + } + } +} + +impl LoopNode { + /// Returns a commitment to this Loop node. + /// + /// The commitment is computed as a hash of the loop body and an empty word ([ZERO; 4]) in + /// the domain defined by [Self::DOMAIN] - i..e,: + /// ``` + /// # use miden_core::mast::LoopNode; + /// # use miden_crypto::{hash::rpo::{RpoDigest as Digest, Rpo256 as Hasher}}; + /// # let body_digest = Digest::default(); + /// Hasher::merge_in_domain(&[body_digest, Digest::default()], LoopNode::DOMAIN); + /// ``` + pub fn digest(&self) -> RpoDigest { + self.digest + } + + /// Returns the ID of the node presenting the body of the loop. + pub fn body(&self) -> MastNodeId { + self.body + } + + /// Returns the decorators to be executed before this node is executed. + pub fn before_enter(&self) -> &[DecoratorId] { + &self.before_enter + } + + /// Returns the decorators to be executed after this node is executed. + pub fn after_exit(&self) -> &[DecoratorId] { + &self.after_exit + } +} + +/// Mutators +impl LoopNode { + /// Sets the list of decorators to be executed before this node. + pub fn set_before_enter(&mut self, decorator_ids: Vec) { + self.before_enter = decorator_ids; + } + + /// Sets the list of decorators to be executed after this node. + pub fn set_after_exit(&mut self, decorator_ids: Vec) { + self.after_exit = decorator_ids; + } +} + +// PRETTY PRINTING +// ================================================================================================ + +impl LoopNode { + pub(super) fn to_display<'a>(&'a self, mast_forest: &'a MastForest) -> impl fmt::Display + 'a { + LoopNodePrettyPrint { loop_node: self, mast_forest } + } + + pub(super) fn to_pretty_print<'a>( + &'a self, + mast_forest: &'a MastForest, + ) -> impl PrettyPrint + 'a { + LoopNodePrettyPrint { loop_node: self, mast_forest } + } +} + +struct LoopNodePrettyPrint<'a> { + loop_node: &'a LoopNode, + mast_forest: &'a MastForest, +} + +impl<'a> crate::prettier::PrettyPrint for LoopNodePrettyPrint<'a> { + fn render(&self) -> crate::prettier::Document { + use crate::prettier::*; + + let pre_decorators = { + let mut pre_decorators = self + .loop_node + .before_enter() + .iter() + .map(|&decorator_id| self.mast_forest[decorator_id].render()) + .reduce(|acc, doc| acc + const_text(" ") + doc) + .unwrap_or_default(); + if !pre_decorators.is_empty() { + pre_decorators += nl(); + } + + pre_decorators + }; + + let post_decorators = { + let mut post_decorators = self + .loop_node + .after_exit() + .iter() + .map(|&decorator_id| self.mast_forest[decorator_id].render()) + .reduce(|acc, doc| acc + const_text(" ") + doc) + .unwrap_or_default(); + if !post_decorators.is_empty() { + post_decorators = nl() + post_decorators; + } + + post_decorators + }; + + let loop_body = self.mast_forest[self.loop_node.body].to_pretty_print(self.mast_forest); + + pre_decorators + + indent(4, const_text("while.true") + nl() + loop_body.render()) + + nl() + + const_text("end") + + post_decorators + } +} + +impl<'a> fmt::Display for LoopNodePrettyPrint<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use crate::prettier::PrettyPrint; + self.pretty_print(f) + } +} diff --git a/core/src/mast/node/mod.rs b/core/src/mast/node/mod.rs new file mode 100644 index 0000000000..cbb3192f40 --- /dev/null +++ b/core/src/mast/node/mod.rs @@ -0,0 +1,296 @@ +mod basic_block_node; +use alloc::{boxed::Box, vec::Vec}; +use core::fmt; + +pub use basic_block_node::{ + BasicBlockNode, OpBatch, OperationOrDecorator, BATCH_SIZE as OP_BATCH_SIZE, + GROUP_SIZE as OP_GROUP_SIZE, +}; + +mod call_node; +pub use call_node::CallNode; + +mod dyn_node; +pub use dyn_node::DynNode; + +mod external; +pub use external::ExternalNode; + +mod join_node; +pub use join_node::JoinNode; + +mod split_node; +use miden_crypto::{hash::rpo::RpoDigest, Felt}; +use miden_formatting::prettier::{Document, PrettyPrint}; +pub use split_node::SplitNode; + +mod loop_node; +pub use loop_node::LoopNode; + +use super::{DecoratorId, MastForestError}; +use crate::{ + mast::{MastForest, MastNodeId}, + DecoratorList, Operation, +}; + +// MAST NODE +// ================================================================================================ + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum MastNode { + Block(BasicBlockNode), + Join(JoinNode), + Split(SplitNode), + Loop(LoopNode), + Call(CallNode), + Dyn(DynNode), + External(ExternalNode), +} + +// ------------------------------------------------------------------------------------------------ +/// Constructors +impl MastNode { + pub fn new_basic_block( + operations: Vec, + decorators: Option, + ) -> Result { + let block = BasicBlockNode::new(operations, decorators)?; + Ok(Self::Block(block)) + } + + pub fn new_join( + left_child: MastNodeId, + right_child: MastNodeId, + mast_forest: &MastForest, + ) -> Result { + let join = JoinNode::new([left_child, right_child], mast_forest)?; + Ok(Self::Join(join)) + } + + pub fn new_split( + if_branch: MastNodeId, + else_branch: MastNodeId, + mast_forest: &MastForest, + ) -> Result { + let split = SplitNode::new([if_branch, else_branch], mast_forest)?; + Ok(Self::Split(split)) + } + + pub fn new_loop(body: MastNodeId, mast_forest: &MastForest) -> Result { + let loop_node = LoopNode::new(body, mast_forest)?; + Ok(Self::Loop(loop_node)) + } + + pub fn new_call(callee: MastNodeId, mast_forest: &MastForest) -> Result { + let call = CallNode::new(callee, mast_forest)?; + Ok(Self::Call(call)) + } + + pub fn new_syscall( + callee: MastNodeId, + mast_forest: &MastForest, + ) -> Result { + let syscall = CallNode::new_syscall(callee, mast_forest)?; + Ok(Self::Call(syscall)) + } + + pub fn new_dyn() -> Self { + Self::Dyn(DynNode::default()) + } + + pub fn new_external(mast_root: RpoDigest) -> Self { + Self::External(ExternalNode::new(mast_root)) + } + + #[cfg(test)] + pub fn new_basic_block_with_raw_decorators( + operations: Vec, + decorators: Vec<(usize, crate::Decorator)>, + mast_forest: &mut MastForest, + ) -> Result { + let block = BasicBlockNode::new_with_raw_decorators(operations, decorators, mast_forest)?; + Ok(Self::Block(block)) + } +} + +// ------------------------------------------------------------------------------------------------ +/// Public accessors +impl MastNode { + /// Returns true if this node is an external node. + pub fn is_external(&self) -> bool { + matches!(self, MastNode::External(_)) + } + + /// Returns true if this node is a Dyn node. + pub fn is_dyn(&self) -> bool { + matches!(self, MastNode::Dyn(_)) + } + + /// Returns true if this node is a basic block. + pub fn is_basic_block(&self) -> bool { + matches!(self, Self::Block(_)) + } + + /// Returns the inner basic block node if the [`MastNode`] wraps a [`BasicBlockNode`]; `None` + /// otherwise. + pub fn get_basic_block(&self) -> Option<&BasicBlockNode> { + match self { + MastNode::Block(basic_block_node) => Some(basic_block_node), + _ => None, + } + } + + pub fn to_pretty_print<'a>(&'a self, mast_forest: &'a MastForest) -> impl PrettyPrint + 'a { + match self { + MastNode::Block(basic_block_node) => { + MastNodePrettyPrint::new(Box::new(basic_block_node.to_pretty_print(mast_forest))) + }, + MastNode::Join(join_node) => { + MastNodePrettyPrint::new(Box::new(join_node.to_pretty_print(mast_forest))) + }, + MastNode::Split(split_node) => { + MastNodePrettyPrint::new(Box::new(split_node.to_pretty_print(mast_forest))) + }, + MastNode::Loop(loop_node) => { + MastNodePrettyPrint::new(Box::new(loop_node.to_pretty_print(mast_forest))) + }, + MastNode::Call(call_node) => { + MastNodePrettyPrint::new(Box::new(call_node.to_pretty_print(mast_forest))) + }, + MastNode::Dyn(dyn_node) => { + MastNodePrettyPrint::new(Box::new(dyn_node.to_pretty_print(mast_forest))) + }, + MastNode::External(external_node) => { + MastNodePrettyPrint::new(Box::new(external_node.to_pretty_print(mast_forest))) + }, + } + } + + pub fn domain(&self) -> Felt { + match self { + MastNode::Block(_) => BasicBlockNode::DOMAIN, + MastNode::Join(_) => JoinNode::DOMAIN, + MastNode::Split(_) => SplitNode::DOMAIN, + MastNode::Loop(_) => LoopNode::DOMAIN, + MastNode::Call(call_node) => call_node.domain(), + MastNode::Dyn(_) => DynNode::DOMAIN, + MastNode::External(_) => panic!("Can't fetch domain for an `External` node."), + } + } + + pub fn digest(&self) -> RpoDigest { + match self { + MastNode::Block(node) => node.digest(), + MastNode::Join(node) => node.digest(), + MastNode::Split(node) => node.digest(), + MastNode::Loop(node) => node.digest(), + MastNode::Call(node) => node.digest(), + MastNode::Dyn(node) => node.digest(), + MastNode::External(node) => node.digest(), + } + } + + pub fn to_display<'a>(&'a self, mast_forest: &'a MastForest) -> impl fmt::Display + 'a { + match self { + MastNode::Block(node) => MastNodeDisplay::new(node.to_display(mast_forest)), + MastNode::Join(node) => MastNodeDisplay::new(node.to_display(mast_forest)), + MastNode::Split(node) => MastNodeDisplay::new(node.to_display(mast_forest)), + MastNode::Loop(node) => MastNodeDisplay::new(node.to_display(mast_forest)), + MastNode::Call(node) => MastNodeDisplay::new(node.to_display(mast_forest)), + MastNode::Dyn(node) => MastNodeDisplay::new(node.to_display(mast_forest)), + MastNode::External(node) => MastNodeDisplay::new(node.to_display(mast_forest)), + } + } + + /// Returns the decorators to be executed before this node is executed. + pub fn before_enter(&self) -> &[DecoratorId] { + use MastNode::*; + match self { + Block(_) => &[], + Join(node) => node.before_enter(), + Split(node) => node.before_enter(), + Loop(node) => node.before_enter(), + Call(node) => node.before_enter(), + Dyn(node) => node.before_enter(), + External(node) => node.before_enter(), + } + } + + /// Returns the decorators to be executed after this node is executed. + pub fn after_exit(&self) -> &[DecoratorId] { + use MastNode::*; + match self { + Block(_) => &[], + Join(node) => node.after_exit(), + Split(node) => node.after_exit(), + Loop(node) => node.after_exit(), + Call(node) => node.after_exit(), + Dyn(node) => node.after_exit(), + External(node) => node.after_exit(), + } + } +} + +/// Mutators +impl MastNode { + /// Sets the list of decorators to be executed before this node. + pub fn set_before_enter(&mut self, decorator_ids: Vec) { + match self { + MastNode::Block(node) => node.prepend_decorators(decorator_ids), + MastNode::Join(node) => node.set_before_enter(decorator_ids), + MastNode::Split(node) => node.set_before_enter(decorator_ids), + MastNode::Loop(node) => node.set_before_enter(decorator_ids), + MastNode::Call(node) => node.set_before_enter(decorator_ids), + MastNode::Dyn(node) => node.set_before_enter(decorator_ids), + MastNode::External(node) => node.set_before_enter(decorator_ids), + } + } + + /// Sets the list of decorators to be executed after this node. + pub fn set_after_exit(&mut self, decorator_ids: Vec) { + match self { + MastNode::Block(node) => node.append_decorators(decorator_ids), + MastNode::Join(node) => node.set_after_exit(decorator_ids), + MastNode::Split(node) => node.set_after_exit(decorator_ids), + MastNode::Loop(node) => node.set_after_exit(decorator_ids), + MastNode::Call(node) => node.set_after_exit(decorator_ids), + MastNode::Dyn(node) => node.set_after_exit(decorator_ids), + MastNode::External(node) => node.set_after_exit(decorator_ids), + } + } +} + +// PRETTY PRINTING +// ================================================================================================ + +struct MastNodePrettyPrint<'a> { + node_pretty_print: Box, +} + +impl<'a> MastNodePrettyPrint<'a> { + pub fn new(node_pretty_print: Box) -> Self { + Self { node_pretty_print } + } +} + +impl<'a> PrettyPrint for MastNodePrettyPrint<'a> { + fn render(&self) -> Document { + self.node_pretty_print.render() + } +} + +struct MastNodeDisplay<'a> { + node_display: Box, +} + +impl<'a> MastNodeDisplay<'a> { + pub fn new(node: impl fmt::Display + 'a) -> Self { + Self { node_display: Box::new(node) } + } +} + +impl<'a> fmt::Display for MastNodeDisplay<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.node_display.fmt(f) + } +} diff --git a/core/src/mast/node/split_node.rs b/core/src/mast/node/split_node.rs new file mode 100644 index 0000000000..6b1b96e0cc --- /dev/null +++ b/core/src/mast/node/split_node.rs @@ -0,0 +1,198 @@ +use alloc::vec::Vec; +use core::fmt; + +use miden_crypto::{hash::rpo::RpoDigest, Felt}; +use miden_formatting::prettier::PrettyPrint; + +use crate::{ + chiplets::hasher, + mast::{DecoratorId, MastForest, MastForestError, MastNodeId}, + OPCODE_SPLIT, +}; + +// SPLIT NODE +// ================================================================================================ + +/// A Split node defines conditional execution. When the VM encounters a Split node it executes +/// either the `on_true` child or `on_false` child. +/// +/// Which child is executed is determined based on the top of the stack. If the value is `1`, then +/// the `on_true` child is executed. If the value is `0`, then the `on_false` child is executed. If +/// the value is neither `0` nor `1`, the execution fails. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SplitNode { + branches: [MastNodeId; 2], + digest: RpoDigest, + before_enter: Vec, + after_exit: Vec, +} + +/// Constants +impl SplitNode { + /// The domain of the split node (used for control block hashing). + pub const DOMAIN: Felt = Felt::new(OPCODE_SPLIT as u64); +} + +/// Constructors +impl SplitNode { + pub fn new( + branches: [MastNodeId; 2], + mast_forest: &MastForest, + ) -> Result { + let forest_len = mast_forest.nodes.len(); + if branches[0].as_usize() >= forest_len { + return Err(MastForestError::NodeIdOverflow(branches[0], forest_len)); + } else if branches[1].as_usize() >= forest_len { + return Err(MastForestError::NodeIdOverflow(branches[1], forest_len)); + } + let digest = { + let if_branch_hash = mast_forest[branches[0]].digest(); + let else_branch_hash = mast_forest[branches[1]].digest(); + + hasher::merge_in_domain(&[if_branch_hash, else_branch_hash], Self::DOMAIN) + }; + + Ok(Self { + branches, + digest, + before_enter: Vec::new(), + after_exit: Vec::new(), + }) + } + + /// Returns a new [`SplitNode`] from values that are assumed to be correct. + /// Should only be used when the source of the inputs is trusted (e.g. deserialization). + pub fn new_unsafe(branches: [MastNodeId; 2], digest: RpoDigest) -> Self { + Self { + branches, + digest, + before_enter: Vec::new(), + after_exit: Vec::new(), + } + } +} + +/// Public accessors +impl SplitNode { + /// Returns a commitment to this Split node. + /// + /// The commitment is computed as a hash of the `on_true` and `on_false` child nodes in the + /// domain defined by [Self::DOMAIN] - i..e,: + /// ``` + /// # use miden_core::mast::SplitNode; + /// # use miden_crypto::{hash::rpo::{RpoDigest as Digest, Rpo256 as Hasher}}; + /// # let on_true_digest = Digest::default(); + /// # let on_false_digest = Digest::default(); + /// Hasher::merge_in_domain(&[on_true_digest, on_false_digest], SplitNode::DOMAIN); + /// ``` + pub fn digest(&self) -> RpoDigest { + self.digest + } + + /// Returns the ID of the node which is to be executed if the top of the stack is `1`. + pub fn on_true(&self) -> MastNodeId { + self.branches[0] + } + + /// Returns the ID of the node which is to be executed if the top of the stack is `0`. + pub fn on_false(&self) -> MastNodeId { + self.branches[1] + } + + /// Returns the decorators to be executed before this node is executed. + pub fn before_enter(&self) -> &[DecoratorId] { + &self.before_enter + } + + /// Returns the decorators to be executed after this node is executed. + pub fn after_exit(&self) -> &[DecoratorId] { + &self.after_exit + } +} + +/// Mutators +impl SplitNode { + /// Sets the list of decorators to be executed before this node. + pub fn set_before_enter(&mut self, decorator_ids: Vec) { + self.before_enter = decorator_ids; + } + + /// Sets the list of decorators to be executed after this node. + pub fn set_after_exit(&mut self, decorator_ids: Vec) { + self.after_exit = decorator_ids; + } +} + +// PRETTY PRINTING +// ================================================================================================ + +impl SplitNode { + pub(super) fn to_display<'a>(&'a self, mast_forest: &'a MastForest) -> impl fmt::Display + 'a { + SplitNodePrettyPrint { split_node: self, mast_forest } + } + + pub(super) fn to_pretty_print<'a>( + &'a self, + mast_forest: &'a MastForest, + ) -> impl PrettyPrint + 'a { + SplitNodePrettyPrint { split_node: self, mast_forest } + } +} + +struct SplitNodePrettyPrint<'a> { + split_node: &'a SplitNode, + mast_forest: &'a MastForest, +} + +impl<'a> PrettyPrint for SplitNodePrettyPrint<'a> { + #[rustfmt::skip] + fn render(&self) -> crate::prettier::Document { + use crate::prettier::*; + + let pre_decorators = { + let mut pre_decorators = self + .split_node + .before_enter() + .iter() + .map(|&decorator_id| self.mast_forest[decorator_id].render()) + .reduce(|acc, doc| acc + const_text(" ") + doc) + .unwrap_or_default(); + if !pre_decorators.is_empty() { + pre_decorators += nl(); + } + + pre_decorators + }; + + let post_decorators = { + let mut post_decorators = self + .split_node + .after_exit() + .iter() + .map(|&decorator_id| self.mast_forest[decorator_id].render()) + .reduce(|acc, doc| acc + const_text(" ") + doc) + .unwrap_or_default(); + if !post_decorators.is_empty() { + post_decorators = nl() + post_decorators; + } + + post_decorators + }; + + let true_branch = self.mast_forest[self.split_node.on_true()].to_pretty_print(self.mast_forest); + let false_branch = self.mast_forest[self.split_node.on_false()].to_pretty_print(self.mast_forest); + + let mut doc = pre_decorators; + doc += indent(4, const_text("if.true") + nl() + true_branch.render()) + nl(); + doc += indent(4, const_text("else") + nl() + false_branch.render()); + doc += nl() + const_text("end"); + doc + post_decorators + } +} + +impl<'a> fmt::Display for SplitNodePrettyPrint<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use crate::prettier::PrettyPrint; + self.pretty_print(f) + } +} diff --git a/core/src/mast/serialization/basic_blocks.rs b/core/src/mast/serialization/basic_blocks.rs new file mode 100644 index 0000000000..e32caa6e99 --- /dev/null +++ b/core/src/mast/serialization/basic_blocks.rs @@ -0,0 +1,102 @@ +use alloc::vec::Vec; + +use winter_utils::{ByteReader, DeserializationError, Serializable, SliceReader}; + +use super::{DecoratorDataOffset, NodeDataOffset}; +use crate::{ + mast::{BasicBlockNode, DecoratorId, MastForest}, + DecoratorList, Operation, +}; + +// BASIC BLOCK DATA BUILDER +// ================================================================================================ + +/// Builds the node `data` section of a serialized [`crate::mast::MastForest`]. +#[derive(Debug, Default)] +pub struct BasicBlockDataBuilder { + node_data: Vec, +} + +/// Constructors +impl BasicBlockDataBuilder { + pub fn new() -> Self { + Self::default() + } +} + +/// Mutators +impl BasicBlockDataBuilder { + /// Encodes a [`BasicBlockNode`] into the serialized [`crate::mast::MastForest`] data field. + pub fn encode_basic_block( + &mut self, + basic_block: &BasicBlockNode, + ) -> (NodeDataOffset, Option) { + let ops_offset = self.node_data.len() as NodeDataOffset; + + let operations: Vec = basic_block.operations().copied().collect(); + operations.write_into(&mut self.node_data); + + if basic_block.decorators().is_empty() { + (ops_offset, None) + } else { + let decorator_data_offset = self.node_data.len() as DecoratorDataOffset; + basic_block.decorators().write_into(&mut self.node_data); + + (ops_offset, Some(decorator_data_offset)) + } + } + + /// Returns the serialized [`crate::mast::MastForest`] node data field. + pub fn finalize(self) -> Vec { + self.node_data + } +} + +// BASIC BLOCK DATA DECODER +// ================================================================================================ + +pub struct BasicBlockDataDecoder<'a> { + node_data: &'a [u8], +} + +/// Constructors +impl<'a> BasicBlockDataDecoder<'a> { + pub fn new(node_data: &'a [u8]) -> Self { + Self { node_data } + } +} + +/// Decoding methods +impl<'a> BasicBlockDataDecoder<'a> { + pub fn decode_operations_and_decorators( + &self, + ops_offset: NodeDataOffset, + decorator_list_offset: NodeDataOffset, + mast_forest: &MastForest, + ) -> Result<(Vec, DecoratorList), DeserializationError> { + // Read ops + let mut ops_data_reader = SliceReader::new(&self.node_data[ops_offset as usize..]); + let operations: Vec = ops_data_reader.read()?; + + // read decorators only if there are some + let decorators = if decorator_list_offset == MastForest::MAX_DECORATORS as u32 { + Vec::new() + } else { + let mut decorators_data_reader = + SliceReader::new(&self.node_data[decorator_list_offset as usize..]); + + let num_decorators: usize = decorators_data_reader.read()?; + (0..num_decorators) + .map(|_| { + let decorator_loc: usize = decorators_data_reader.read()?; + let decorator_id = + DecoratorId::from_u32_safe(decorators_data_reader.read()?, mast_forest)?; + + Ok((decorator_loc, decorator_id)) + }) + .collect::>()? + }; + + Ok((operations, decorators)) + } +} diff --git a/core/src/mast/serialization/decorator.rs b/core/src/mast/serialization/decorator.rs new file mode 100644 index 0000000000..a8b2041e3c --- /dev/null +++ b/core/src/mast/serialization/decorator.rs @@ -0,0 +1,440 @@ +use alloc::vec::Vec; + +use miden_crypto::Felt; +use num_derive::{FromPrimitive, ToPrimitive}; +use num_traits::{FromPrimitive, ToPrimitive}; +use winter_utils::{ + ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable, SliceReader, +}; + +use super::{ + string_table::{StringTable, StringTableBuilder}, + DecoratorDataOffset, +}; +use crate::{AdviceInjector, AssemblyOp, DebugOptions, Decorator, SignatureKind}; + +/// Represents a serialized [`Decorator`]. +/// +/// The serialized representation of [`DecoratorInfo`] is guaranteed to be fixed width, so that the +/// decorators stored in the `decorators` table of the serialized [`MastForest`] can be accessed +/// quickly by index. +#[derive(Debug)] +pub struct DecoratorInfo { + variant: EncodedDecoratorVariant, + decorator_data_offset: DecoratorDataOffset, +} + +impl DecoratorInfo { + pub fn from_decorator( + decorator: &Decorator, + data_builder: &mut DecoratorDataBuilder, + string_table_builder: &mut StringTableBuilder, + ) -> Self { + let variant = EncodedDecoratorVariant::from(decorator); + let decorator_data_offset = + data_builder.encode_decorator_data(decorator, string_table_builder).unwrap_or(0); + + Self { variant, decorator_data_offset } + } + + pub fn try_into_decorator( + &self, + string_table: &StringTable, + decorator_data: &[u8], + ) -> Result { + // This is safe because for decorators that don't use the offset, `0` is used (and hence + // will never access an element outside). Note that in this implementation, we trust the + // encoder. + let mut data_reader = + SliceReader::new(&decorator_data[self.decorator_data_offset as usize..]); + match self.variant { + EncodedDecoratorVariant::AdviceInjectorMerkleNodeMerge => { + Ok(Decorator::Advice(AdviceInjector::MerkleNodeMerge)) + }, + EncodedDecoratorVariant::AdviceInjectorMerkleNodeToStack => { + Ok(Decorator::Advice(AdviceInjector::MerkleNodeToStack)) + }, + EncodedDecoratorVariant::AdviceInjectorUpdateMerkleNode => { + Ok(Decorator::Advice(AdviceInjector::UpdateMerkleNode)) + }, + EncodedDecoratorVariant::AdviceInjectorMapValueToStack => { + let include_len = data_reader.read_bool()?; + let key_offset = data_reader.read_usize()?; + + Ok(Decorator::Advice(AdviceInjector::MapValueToStack { include_len, key_offset })) + }, + EncodedDecoratorVariant::AdviceInjectorU64Div => { + Ok(Decorator::Advice(AdviceInjector::U64Div)) + }, + EncodedDecoratorVariant::AdviceInjectorExt2Inv => { + Ok(Decorator::Advice(AdviceInjector::Ext2Inv)) + }, + EncodedDecoratorVariant::AdviceInjectorExt2Intt => { + Ok(Decorator::Advice(AdviceInjector::Ext2Intt)) + }, + EncodedDecoratorVariant::AdviceInjectorSmtGet => { + Ok(Decorator::Advice(AdviceInjector::SmtGet)) + }, + EncodedDecoratorVariant::AdviceInjectorSmtSet => { + Ok(Decorator::Advice(AdviceInjector::SmtSet)) + }, + EncodedDecoratorVariant::AdviceInjectorSmtPeek => { + Ok(Decorator::Advice(AdviceInjector::SmtPeek)) + }, + EncodedDecoratorVariant::AdviceInjectorU32Clz => { + Ok(Decorator::Advice(AdviceInjector::U32Clz)) + }, + EncodedDecoratorVariant::AdviceInjectorU32Ctz => { + Ok(Decorator::Advice(AdviceInjector::U32Ctz)) + }, + EncodedDecoratorVariant::AdviceInjectorU32Clo => { + Ok(Decorator::Advice(AdviceInjector::U32Clo)) + }, + EncodedDecoratorVariant::AdviceInjectorU32Cto => { + Ok(Decorator::Advice(AdviceInjector::U32Cto)) + }, + EncodedDecoratorVariant::AdviceInjectorILog2 => { + Ok(Decorator::Advice(AdviceInjector::ILog2)) + }, + EncodedDecoratorVariant::AdviceInjectorMemToMap => { + Ok(Decorator::Advice(AdviceInjector::MemToMap)) + }, + EncodedDecoratorVariant::AdviceInjectorHdwordToMap => { + let domain = data_reader.read_u64()?; + let domain = Felt::try_from(domain).map_err(|err| { + DeserializationError::InvalidValue(format!( + "Error when deserializing HdwordToMap decorator domain: {err}" + )) + })?; + + Ok(Decorator::Advice(AdviceInjector::HdwordToMap { domain })) + }, + EncodedDecoratorVariant::AdviceInjectorHpermToMap => { + Ok(Decorator::Advice(AdviceInjector::HpermToMap)) + }, + EncodedDecoratorVariant::AdviceInjectorSigToStack => { + Ok(Decorator::Advice(AdviceInjector::SigToStack { + kind: SignatureKind::RpoFalcon512, + })) + }, + EncodedDecoratorVariant::AssemblyOp => { + let num_cycles = data_reader.read_u8()?; + let should_break = data_reader.read_bool()?; + + // source location + let location = if data_reader.read_bool()? { + let str_index_in_table = data_reader.read_usize()?; + let path = string_table.read_arc_str(str_index_in_table)?; + let start = data_reader.read_u32()?; + let end = data_reader.read_u32()?; + Some(crate::debuginfo::Location { + path, + start: start.into(), + end: end.into(), + }) + } else { + None + }; + + let context_name = { + let str_index_in_table = data_reader.read_usize()?; + string_table.read_string(str_index_in_table)? + }; + + let op = { + let str_index_in_table = data_reader.read_usize()?; + string_table.read_string(str_index_in_table)? + }; + + Ok(Decorator::AsmOp(AssemblyOp::new( + location, + context_name, + num_cycles, + op, + should_break, + ))) + }, + EncodedDecoratorVariant::DebugOptionsStackAll => { + Ok(Decorator::Debug(DebugOptions::StackAll)) + }, + EncodedDecoratorVariant::DebugOptionsStackTop => { + let value = data_reader.read_u8()?; + + Ok(Decorator::Debug(DebugOptions::StackTop(value))) + }, + EncodedDecoratorVariant::DebugOptionsMemAll => { + Ok(Decorator::Debug(DebugOptions::MemAll)) + }, + EncodedDecoratorVariant::DebugOptionsMemInterval => { + let start = data_reader.read_u32()?; + let end = data_reader.read_u32()?; + + Ok(Decorator::Debug(DebugOptions::MemInterval(start, end))) + }, + EncodedDecoratorVariant::DebugOptionsLocalInterval => { + let start = data_reader.read_u16()?; + let second = data_reader.read_u16()?; + let end = data_reader.read_u16()?; + + Ok(Decorator::Debug(DebugOptions::LocalInterval(start, second, end))) + }, + EncodedDecoratorVariant::Trace => { + let value = data_reader.read_u32()?; + + Ok(Decorator::Trace(value)) + }, + } + } +} + +impl Serializable for DecoratorInfo { + fn write_into(&self, target: &mut W) { + let Self { variant, decorator_data_offset } = self; + + variant.write_into(target); + decorator_data_offset.write_into(target); + } +} + +impl Deserializable for DecoratorInfo { + fn read_from(source: &mut R) -> Result { + let variant = source.read()?; + let decorator_data_offset = source.read()?; + + Ok(Self { variant, decorator_data_offset }) + } +} + +// ENCODED DATA VARIANT +// =============================================================================================== + +/// Stores all the possible [`Decorator`] variants, without any associated data. +/// +/// This is effectively equivalent to a set of constants, and designed to convert between variant +/// discriminant and enum variant conveniently. +#[derive(Debug, FromPrimitive, ToPrimitive)] +#[repr(u8)] +pub enum EncodedDecoratorVariant { + AdviceInjectorMerkleNodeMerge, + AdviceInjectorMerkleNodeToStack, + AdviceInjectorUpdateMerkleNode, + AdviceInjectorMapValueToStack, + AdviceInjectorU64Div, + AdviceInjectorExt2Inv, + AdviceInjectorExt2Intt, + AdviceInjectorSmtGet, + AdviceInjectorSmtSet, + AdviceInjectorSmtPeek, + AdviceInjectorU32Clz, + AdviceInjectorU32Ctz, + AdviceInjectorU32Clo, + AdviceInjectorU32Cto, + AdviceInjectorILog2, + AdviceInjectorMemToMap, + AdviceInjectorHdwordToMap, + AdviceInjectorHpermToMap, + AdviceInjectorSigToStack, + AssemblyOp, + DebugOptionsStackAll, + DebugOptionsStackTop, + DebugOptionsMemAll, + DebugOptionsMemInterval, + DebugOptionsLocalInterval, + Trace, +} + +impl EncodedDecoratorVariant { + /// Returns the discriminant of the given decorator variant. + /// + /// To distinguish them from [`crate::Operation`] discriminants, the most significant bit of + /// decorator discriminant is always set to 1. + pub fn discriminant(&self) -> u8 { + self.to_u8().expect("guaranteed to fit in a `u8` due to #[repr(u8)]") + } + + /// The inverse operation of [`Self::discriminant`]. + pub fn from_discriminant(discriminant: u8) -> Option { + Self::from_u8(discriminant) + } +} + +impl From<&Decorator> for EncodedDecoratorVariant { + fn from(decorator: &Decorator) -> Self { + match decorator { + Decorator::Advice(advice_injector) => match advice_injector { + AdviceInjector::MerkleNodeMerge => Self::AdviceInjectorMerkleNodeMerge, + AdviceInjector::MerkleNodeToStack => Self::AdviceInjectorMerkleNodeToStack, + AdviceInjector::UpdateMerkleNode => Self::AdviceInjectorUpdateMerkleNode, + AdviceInjector::MapValueToStack { include_len: _, key_offset: _ } => { + Self::AdviceInjectorMapValueToStack + }, + AdviceInjector::U64Div => Self::AdviceInjectorU64Div, + AdviceInjector::Ext2Inv => Self::AdviceInjectorExt2Inv, + AdviceInjector::Ext2Intt => Self::AdviceInjectorExt2Intt, + AdviceInjector::SmtGet => Self::AdviceInjectorSmtGet, + AdviceInjector::SmtSet => Self::AdviceInjectorSmtSet, + AdviceInjector::SmtPeek => Self::AdviceInjectorSmtPeek, + AdviceInjector::U32Clz => Self::AdviceInjectorU32Clz, + AdviceInjector::U32Ctz => Self::AdviceInjectorU32Ctz, + AdviceInjector::U32Clo => Self::AdviceInjectorU32Clo, + AdviceInjector::U32Cto => Self::AdviceInjectorU32Cto, + AdviceInjector::ILog2 => Self::AdviceInjectorILog2, + AdviceInjector::MemToMap => Self::AdviceInjectorMemToMap, + AdviceInjector::HdwordToMap { domain: _ } => Self::AdviceInjectorHdwordToMap, + AdviceInjector::HpermToMap => Self::AdviceInjectorHpermToMap, + AdviceInjector::SigToStack { kind: _ } => Self::AdviceInjectorSigToStack, + }, + Decorator::AsmOp(_) => Self::AssemblyOp, + Decorator::Debug(debug_options) => match debug_options { + DebugOptions::StackAll => Self::DebugOptionsStackAll, + DebugOptions::StackTop(_) => Self::DebugOptionsStackTop, + DebugOptions::MemAll => Self::DebugOptionsMemAll, + DebugOptions::MemInterval(..) => Self::DebugOptionsMemInterval, + DebugOptions::LocalInterval(..) => Self::DebugOptionsLocalInterval, + }, + Decorator::Trace(_) => Self::Trace, + } + } +} + +impl Serializable for EncodedDecoratorVariant { + fn write_into(&self, target: &mut W) { + self.discriminant().write_into(target); + } +} + +impl Deserializable for EncodedDecoratorVariant { + fn read_from(source: &mut R) -> Result { + let discriminant: u8 = source.read_u8()?; + + Self::from_discriminant(discriminant).ok_or_else(|| { + DeserializationError::InvalidValue(format!( + "invalid decorator discriminant: {discriminant}" + )) + }) + } +} + +// DECORATOR DATA BUILDER +// =============================================================================================== + +/// Builds the decorator `data` section of a serialized [`crate::mast::MastForest`]. +#[derive(Debug, Default)] +pub struct DecoratorDataBuilder { + decorator_data: Vec, +} + +/// Constructors +impl DecoratorDataBuilder { + pub fn new() -> Self { + Self::default() + } +} + +/// Mutators +impl DecoratorDataBuilder { + /// If a decorator has extra data to store, encode it in internal data buffer, and return the + /// offset of the newly added data. If not, return `None`. + pub fn encode_decorator_data( + &mut self, + decorator: &Decorator, + string_table_builder: &mut StringTableBuilder, + ) -> Option { + let data_offset = self.decorator_data.len() as DecoratorDataOffset; + + match decorator { + Decorator::Advice(advice_injector) => match advice_injector { + AdviceInjector::MapValueToStack { include_len, key_offset } => { + self.decorator_data.write_bool(*include_len); + self.decorator_data.write_usize(*key_offset); + + Some(data_offset) + }, + AdviceInjector::HdwordToMap { domain } => { + self.decorator_data.extend(domain.as_int().to_le_bytes()); + + Some(data_offset) + }, + + // Note: Since there is only 1 variant, we don't need to write any extra bytes. + AdviceInjector::SigToStack { kind } => match kind { + SignatureKind::RpoFalcon512 => None, + }, + AdviceInjector::MerkleNodeMerge + | AdviceInjector::MerkleNodeToStack + | AdviceInjector::UpdateMerkleNode + | AdviceInjector::U64Div + | AdviceInjector::Ext2Inv + | AdviceInjector::Ext2Intt + | AdviceInjector::SmtGet + | AdviceInjector::SmtSet + | AdviceInjector::SmtPeek + | AdviceInjector::U32Clz + | AdviceInjector::U32Ctz + | AdviceInjector::U32Clo + | AdviceInjector::U32Cto + | AdviceInjector::ILog2 + | AdviceInjector::MemToMap + | AdviceInjector::HpermToMap => None, + }, + Decorator::AsmOp(assembly_op) => { + self.decorator_data.push(assembly_op.num_cycles()); + self.decorator_data.write_bool(assembly_op.should_break()); + + // source location + let loc = assembly_op.location(); + self.decorator_data.write_bool(loc.is_some()); + if let Some(loc) = loc { + let str_offset = string_table_builder.add_string(loc.path.as_ref()); + self.decorator_data.write_usize(str_offset); + self.decorator_data.write_u32(loc.start.to_u32()); + self.decorator_data.write_u32(loc.end.to_u32()); + } + + // context name + { + let str_offset = string_table_builder.add_string(assembly_op.context_name()); + self.decorator_data.write_usize(str_offset); + } + + // op + { + let str_index_in_table = string_table_builder.add_string(assembly_op.op()); + self.decorator_data.write_usize(str_index_in_table); + } + + Some(data_offset) + }, + Decorator::Debug(debug_options) => match debug_options { + DebugOptions::StackTop(value) => { + self.decorator_data.push(*value); + Some(data_offset) + }, + DebugOptions::MemInterval(start, end) => { + self.decorator_data.extend(start.to_le_bytes()); + self.decorator_data.extend(end.to_le_bytes()); + + Some(data_offset) + }, + DebugOptions::LocalInterval(start, second, end) => { + self.decorator_data.extend(start.to_le_bytes()); + self.decorator_data.extend(second.to_le_bytes()); + self.decorator_data.extend(end.to_le_bytes()); + + Some(data_offset) + }, + DebugOptions::StackAll | DebugOptions::MemAll => None, + }, + Decorator::Trace(value) => { + self.decorator_data.extend(value.to_le_bytes()); + + Some(data_offset) + }, + } + } + + /// Returns the serialized [`crate::mast::MastForest`] decorator data field. + pub fn finalize(self) -> Vec { + self.decorator_data + } +} diff --git a/core/src/mast/serialization/info.rs b/core/src/mast/serialization/info.rs new file mode 100644 index 0000000000..4a3fa58652 --- /dev/null +++ b/core/src/mast/serialization/info.rs @@ -0,0 +1,392 @@ +use miden_crypto::hash::rpo::RpoDigest; +use winter_utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; + +use super::{basic_blocks::BasicBlockDataDecoder, NodeDataOffset}; +use crate::mast::{ + BasicBlockNode, CallNode, JoinNode, LoopNode, MastForest, MastNode, MastNodeId, SplitNode, +}; + +// MAST NODE INFO +// ================================================================================================ + +/// Represents a serialized [`MastNode`], with some data inlined in its [`MastNodeType`]. +/// +/// The serialized representation of [`MastNodeInfo`] is guaranteed to be fixed width, so that the +/// nodes stored in the `nodes` table of the serialized [`MastForest`] can be accessed quickly by +/// index. +#[derive(Debug)] +pub struct MastNodeInfo { + ty: MastNodeType, + digest: RpoDigest, +} + +impl MastNodeInfo { + /// Constructs a new [`MastNodeInfo`] from a [`MastNode`], along with an `ops_offset` and + /// `decorator_list_offset` in the case of [`BasicBlockNode`]. + /// + /// If the represented [`MastNode`] is a [`BasicBlockNode`] that has an empty decorator list, + /// use `MastForest::MAX_DECORATORS` for the value of `decorator_list_offset`. For non-basic + /// block nodes, `ops_offset` and `decorator_list_offset` are ignored, and should be set to 0. + pub fn new( + mast_node: &MastNode, + ops_offset: NodeDataOffset, + decorator_list_offset: NodeDataOffset, + ) -> Self { + if !matches!(mast_node, &MastNode::Block(_)) { + debug_assert_eq!(ops_offset, 0); + debug_assert_eq!(decorator_list_offset, 0); + } + + let ty = MastNodeType::new(mast_node, ops_offset, decorator_list_offset); + + Self { ty, digest: mast_node.digest() } + } + + pub fn try_into_mast_node( + self, + mast_forest: &mut MastForest, + basic_block_data_decoder: &BasicBlockDataDecoder, + ) -> Result { + match self.ty { + MastNodeType::Block { ops_offset, decorator_list_offset } => { + let (operations, decorators) = basic_block_data_decoder + .decode_operations_and_decorators( + ops_offset, + decorator_list_offset, + mast_forest, + )?; + let block = BasicBlockNode::new_unsafe(operations, decorators, self.digest); + Ok(MastNode::Block(block)) + }, + MastNodeType::Join { left_child_id, right_child_id } => { + let left_child = MastNodeId::from_u32_safe(left_child_id, mast_forest)?; + let right_child = MastNodeId::from_u32_safe(right_child_id, mast_forest)?; + let join = JoinNode::new_unsafe([left_child, right_child], self.digest); + Ok(MastNode::Join(join)) + }, + MastNodeType::Split { if_branch_id, else_branch_id } => { + let if_branch = MastNodeId::from_u32_safe(if_branch_id, mast_forest)?; + let else_branch = MastNodeId::from_u32_safe(else_branch_id, mast_forest)?; + let split = SplitNode::new_unsafe([if_branch, else_branch], self.digest); + Ok(MastNode::Split(split)) + }, + MastNodeType::Loop { body_id } => { + let body_id = MastNodeId::from_u32_safe(body_id, mast_forest)?; + let loop_node = LoopNode::new_unsafe(body_id, self.digest); + Ok(MastNode::Loop(loop_node)) + }, + MastNodeType::Call { callee_id } => { + let callee_id = MastNodeId::from_u32_safe(callee_id, mast_forest)?; + let call = CallNode::new_unsafe(callee_id, self.digest); + Ok(MastNode::Call(call)) + }, + MastNodeType::SysCall { callee_id } => { + let callee_id = MastNodeId::from_u32_safe(callee_id, mast_forest)?; + let syscall = CallNode::new_syscall_unsafe(callee_id, self.digest); + Ok(MastNode::Call(syscall)) + }, + MastNodeType::Dyn => Ok(MastNode::new_dyn()), + MastNodeType::External => Ok(MastNode::new_external(self.digest)), + } + } +} + +impl Serializable for MastNodeInfo { + fn write_into(&self, target: &mut W) { + let Self { ty, digest } = self; + + ty.write_into(target); + digest.write_into(target); + } +} + +impl Deserializable for MastNodeInfo { + fn read_from(source: &mut R) -> Result { + let ty = Deserializable::read_from(source)?; + let digest = RpoDigest::read_from(source)?; + + Ok(Self { ty, digest }) + } +} + +// MAST NODE TYPE +// ================================================================================================ + +const JOIN: u8 = 0; +const SPLIT: u8 = 1; +const LOOP: u8 = 2; +const BLOCK: u8 = 3; +const CALL: u8 = 4; +const SYSCALL: u8 = 5; +const DYN: u8 = 6; +const EXTERNAL: u8 = 7; + +/// Represents the variant of a [`MastNode`], as well as any additional data. For example, for more +/// efficient decoding, and because of the frequency with which these node types appear, we directly +/// represent the child indices for `Join`, `Split`, and `Loop`, `Call` and `SysCall` inline. +/// +/// The serialized representation of the MAST node type is guaranteed to be 8 bytes, so that +/// [`MastNodeInfo`] (which contains it) can be of fixed width. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum MastNodeType { + Join { + left_child_id: u32, + right_child_id: u32, + } = JOIN, + Split { + if_branch_id: u32, + else_branch_id: u32, + } = SPLIT, + Loop { + body_id: u32, + } = LOOP, + Block { + // offset of operations in node data + ops_offset: u32, + // offset of DecoratorList in node data + decorator_list_offset: u32, + } = BLOCK, + Call { + callee_id: u32, + } = CALL, + SysCall { + callee_id: u32, + } = SYSCALL, + Dyn = DYN, + External = EXTERNAL, +} + +/// Constructors +impl MastNodeType { + /// Constructs a new [`MastNodeType`] from a [`MastNode`]. + /// + /// If the represented [`MastNode`] is a [`BasicBlockNode`] that has an empty decorator list, + /// use `MastForest::MAX_DECORATORS` for the value of `decorator_list_offset`. + pub fn new( + mast_node: &MastNode, + ops_offset: NodeDataOffset, + decorator_list_offset: NodeDataOffset, + ) -> Self { + use MastNode::*; + + match mast_node { + Block(_block_node) => Self::Block { decorator_list_offset, ops_offset }, + Join(join_node) => Self::Join { + left_child_id: join_node.first().0, + right_child_id: join_node.second().0, + }, + Split(split_node) => Self::Split { + if_branch_id: split_node.on_true().0, + else_branch_id: split_node.on_false().0, + }, + Loop(loop_node) => Self::Loop { body_id: loop_node.body().0 }, + Call(call_node) => { + if call_node.is_syscall() { + Self::SysCall { callee_id: call_node.callee().0 } + } else { + Self::Call { callee_id: call_node.callee().0 } + } + }, + Dyn(_) => Self::Dyn, + External(_) => Self::External, + } + } +} + +impl Serializable for MastNodeType { + fn write_into(&self, target: &mut W) { + let discriminant = self.discriminant() as u64; + assert!(discriminant <= 0b1111); + + let payload = match *self { + MastNodeType::Join { + left_child_id: left, + right_child_id: right, + } => Self::encode_u32_pair(left, right), + MastNodeType::Split { + if_branch_id: if_branch, + else_branch_id: else_branch, + } => Self::encode_u32_pair(if_branch, else_branch), + MastNodeType::Loop { body_id: body } => Self::encode_u32_payload(body), + MastNodeType::Block { ops_offset, decorator_list_offset } => { + Self::encode_u32_pair(ops_offset, decorator_list_offset) + }, + MastNodeType::Call { callee_id } => Self::encode_u32_payload(callee_id), + MastNodeType::SysCall { callee_id } => Self::encode_u32_payload(callee_id), + MastNodeType::Dyn => 0, + MastNodeType::External => 0, + }; + + let value = (discriminant << 60) | payload; + target.write_u64(value); + } +} + +/// Serialization helpers +impl MastNodeType { + fn discriminant(&self) -> u8 { + // SAFETY: This is safe because we have given this enum a primitive representation with + // #[repr(u8)], with the first field of the underlying union-of-structs the discriminant. + // + // See the section on "accessing the numeric value of the discriminant" + // here: https://doc.rust-lang.org/std/mem/fn.discriminant.html + unsafe { *<*const _>::from(self).cast::() } + } + + /// Encodes two u32 numbers in the first 60 bits of a `u64`. + /// + /// # Panics + /// - Panics if either `left_value` or `right_value` doesn't fit in 30 bits. + fn encode_u32_pair(left_value: u32, right_value: u32) -> u64 { + assert!( + left_value.leading_zeros() >= 2, + "MastNodeType::encode_u32_pair: left value doesn't fit in 30 bits: {}", + left_value + ); + assert!( + right_value.leading_zeros() >= 2, + "MastNodeType::encode_u32_pair: right value doesn't fit in 30 bits: {}", + right_value + ); + + ((left_value as u64) << 30) | (right_value as u64) + } + + fn encode_u32_payload(payload: u32) -> u64 { + payload as u64 + } +} + +impl Deserializable for MastNodeType { + fn read_from(source: &mut R) -> Result { + let (discriminant, payload) = { + let value = source.read_u64()?; + + // 4 bits + let discriminant = (value >> 60) as u8; + // 60 bits + let payload = value & 0x0f_ff_ff_ff_ff_ff_ff_ff; + + (discriminant, payload) + }; + + match discriminant { + JOIN => { + let (left_child_id, right_child_id) = Self::decode_u32_pair(payload); + Ok(Self::Join { left_child_id, right_child_id }) + }, + SPLIT => { + let (if_branch_id, else_branch_id) = Self::decode_u32_pair(payload); + Ok(Self::Split { if_branch_id, else_branch_id }) + }, + LOOP => { + let body_id = Self::decode_u32_payload(payload)?; + Ok(Self::Loop { body_id }) + }, + BLOCK => { + let (ops_offset, decorator_list_offset) = Self::decode_u32_pair(payload); + Ok(Self::Block { ops_offset, decorator_list_offset }) + }, + CALL => { + let callee_id = Self::decode_u32_payload(payload)?; + Ok(Self::Call { callee_id }) + }, + SYSCALL => { + let callee_id = Self::decode_u32_payload(payload)?; + Ok(Self::SysCall { callee_id }) + }, + DYN => Ok(Self::Dyn), + EXTERNAL => Ok(Self::External), + _ => Err(DeserializationError::InvalidValue(format!( + "Invalid tag for MAST node: {discriminant}" + ))), + } + } +} + +/// Deserialization helpers +impl MastNodeType { + /// Decodes two `u32` numbers from a 60-bit payload. + fn decode_u32_pair(payload: u64) -> (u32, u32) { + let left_value = (payload >> 30) as u32; + let right_value = (payload & 0x3f_ff_ff_ff) as u32; + + (left_value, right_value) + } + + /// Decodes one `u32` number from a 60-bit payload. + /// + /// Returns an error if the payload doesn't fit in a `u32`. + pub fn decode_u32_payload(payload: u64) -> Result { + payload.try_into().map_err(|_| { + DeserializationError::InvalidValue(format!( + "Invalid payload: expected to fit in u32, but was {payload}" + )) + }) + } +} + +#[cfg(test)] +mod tests { + use alloc::vec::Vec; + + use super::*; + + #[test] + fn serialize_deserialize_60_bit_payload() { + // each child needs 30 bits + let mast_node_type = MastNodeType::Join { + left_child_id: 0x3f_ff_ff_ff, + right_child_id: 0x3f_ff_ff_ff, + }; + + let serialized = mast_node_type.to_bytes(); + let deserialized = MastNodeType::read_from_bytes(&serialized).unwrap(); + + assert_eq!(mast_node_type, deserialized); + } + + #[test] + #[should_panic] + fn serialize_large_payloads_fails_1() { + // left child needs 31 bits + let mast_node_type = MastNodeType::Join { + left_child_id: 0x4f_ff_ff_ff, + right_child_id: 0x0, + }; + + // must panic + let _serialized = mast_node_type.to_bytes(); + } + + #[test] + #[should_panic] + fn serialize_large_payloads_fails_2() { + // right child needs 31 bits + let mast_node_type = MastNodeType::Join { + left_child_id: 0x0, + right_child_id: 0x4f_ff_ff_ff, + }; + + // must panic + let _serialized = mast_node_type.to_bytes(); + } + + #[test] + fn deserialize_large_payloads_fails() { + // Serialized `CALL` with a 33-bit payload + let serialized = { + let serialized_value = ((CALL as u64) << 60) | (u32::MAX as u64 + 1_u64); + + let mut serialized_buffer: Vec = Vec::new(); + serialized_value.write_into(&mut serialized_buffer); + + serialized_buffer + }; + + let deserialized_result = MastNodeType::read_from_bytes(&serialized); + + assert_matches!(deserialized_result, Err(DeserializationError::InvalidValue(_))); + } +} diff --git a/core/src/mast/serialization/mod.rs b/core/src/mast/serialization/mod.rs new file mode 100644 index 0000000000..cf67c17a30 --- /dev/null +++ b/core/src/mast/serialization/mod.rs @@ -0,0 +1,287 @@ +//! The serialization format of MastForest is as follows: +//! +//! (Metadata) +//! - MAGIC +//! - VERSION +//! +//! (lengths) +//! - decorators length (`usize`) +//! - nodes length (`usize`) +//! +//! (procedure roots) +//! - procedure roots (`Vec`) +//! +//! (raw data) +//! - Decorator data +//! - Node data +//! - String table +//! +//! (info structs) +//! - decorator infos (`Vec`) +//! - MAST node infos (`Vec`) +//! +//! (before enter and after exit decorators) +//! - before enter decorators (`Vec<(MastNodeId, Vec)>`) +//! - after exit decorators (`Vec<(MastNodeId, Vec)>`) + +use alloc::vec::Vec; + +use decorator::{DecoratorDataBuilder, DecoratorInfo}; +use string_table::{StringTable, StringTableBuilder}; +use winter_utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; + +use super::{DecoratorId, MastForest, MastNode, MastNodeId}; + +mod decorator; + +mod info; +use info::MastNodeInfo; + +mod basic_blocks; +use basic_blocks::{BasicBlockDataBuilder, BasicBlockDataDecoder}; + +mod string_table; + +#[cfg(test)] +mod tests; + +// TYPE ALIASES +// ================================================================================================ + +/// Specifies an offset into the `node_data` section of an encoded [`MastForest`]. +type NodeDataOffset = u32; + +/// Specifies an offset into the `decorator_data` section of an encoded [`MastForest`]. +type DecoratorDataOffset = u32; + +/// Specifies an offset into the `strings_data` section of an encoded [`MastForest`]. +type StringDataOffset = usize; + +/// Specifies an offset into the strings table of an encoded [`MastForest`]. +type StringIndex = usize; + +// CONSTANTS +// ================================================================================================ + +/// Magic string for detecting that a file is binary-encoded MAST. +const MAGIC: &[u8; 5] = b"MAST\0"; + +/// The format version. +/// +/// If future modifications are made to this format, the version should be incremented by 1. A +/// version of `[255, 255, 255]` is reserved for future extensions that require extending the +/// version field itself, but should be considered invalid for now. +const VERSION: [u8; 3] = [0, 0, 0]; + +// MAST FOREST SERIALIZATION/DESERIALIZATION +// ================================================================================================ + +impl Serializable for MastForest { + fn write_into(&self, target: &mut W) { + let mut basic_block_data_builder = BasicBlockDataBuilder::new(); + let mut decorator_data_builder = DecoratorDataBuilder::new(); + let mut string_table_builder = StringTableBuilder::default(); + + // Set up "before enter" and "after exit" decorators by `MastNodeId` + let mut before_enter_decorators: Vec<(usize, Vec)> = Vec::new(); + let mut after_exit_decorators: Vec<(usize, Vec)> = Vec::new(); + + // magic & version + target.write_bytes(MAGIC); + target.write_bytes(&VERSION); + + // decorator & node counts + target.write_usize(self.decorators.len()); + target.write_usize(self.nodes.len()); + + // roots + let roots: Vec = self.roots.iter().map(u32::from).collect(); + roots.write_into(target); + + // decorators + let decorator_infos: Vec = self + .decorators + .iter() + .map(|decorator| { + DecoratorInfo::from_decorator( + decorator, + &mut decorator_data_builder, + &mut string_table_builder, + ) + }) + .collect(); + + // Prepare MAST node infos, but don't store them yet. We store them at the end to make + // deserialization more efficient. + let mast_node_infos: Vec = self + .nodes + .iter() + .enumerate() + .map(|(mast_node_id, mast_node)| { + if !mast_node.before_enter().is_empty() { + before_enter_decorators.push((mast_node_id, mast_node.before_enter().to_vec())); + } + if !mast_node.after_exit().is_empty() { + after_exit_decorators.push((mast_node_id, mast_node.after_exit().to_vec())); + } + + let (ops_offset, decorator_data_offset) = if let MastNode::Block(basic_block) = + mast_node + { + let (ops_offset, decorator_data_offset) = + basic_block_data_builder.encode_basic_block(basic_block); + + (ops_offset, decorator_data_offset.unwrap_or(MastForest::MAX_DECORATORS as u32)) + } else { + (0, 0) + }; + + MastNodeInfo::new(mast_node, ops_offset, decorator_data_offset) + }) + .collect(); + + let decorator_data = decorator_data_builder.finalize(); + let node_data = basic_block_data_builder.finalize(); + let string_table = string_table_builder.into_table(); + + // Write 3 data buffers + decorator_data.write_into(target); + node_data.write_into(target); + string_table.write_into(target); + + // Write decorator and node infos + for decorator_info in decorator_infos { + decorator_info.write_into(target); + } + + for mast_node_info in mast_node_infos { + mast_node_info.write_into(target); + } + + // Write "before enter" and "after exit" decorators + before_enter_decorators.write_into(target); + after_exit_decorators.write_into(target); + } +} + +impl Deserializable for MastForest { + fn read_from(source: &mut R) -> Result { + let magic: [u8; 5] = source.read_array()?; + if magic != *MAGIC { + return Err(DeserializationError::InvalidValue(format!( + "Invalid magic bytes. Expected '{:?}', got '{:?}'", + *MAGIC, magic + ))); + } + + let version: [u8; 3] = source.read_array()?; + if version != VERSION { + return Err(DeserializationError::InvalidValue(format!( + "Unsupported version. Got '{version:?}', but only '{VERSION:?}' is supported", + ))); + } + + let decorator_count = source.read_usize()?; + let node_count = source.read_usize()?; + let roots: Vec = Deserializable::read_from(source)?; + let decorator_data: Vec = Deserializable::read_from(source)?; + let node_data: Vec = Deserializable::read_from(source)?; + let string_table: StringTable = Deserializable::read_from(source)?; + + let mut mast_forest = { + let mut mast_forest = MastForest::new(); + + // decorators + for _ in 0..decorator_count { + let decorator_info = DecoratorInfo::read_from(source)?; + let decorator = + decorator_info.try_into_decorator(&string_table, &decorator_data)?; + + mast_forest.add_decorator(decorator).map_err(|e| { + DeserializationError::InvalidValue(format!( + "failed to add decorator to MAST forest while deserializing: {e}", + )) + })?; + } + + // nodes + let basic_block_data_decoder = BasicBlockDataDecoder::new(&node_data); + for _ in 0..node_count { + let mast_node_info = MastNodeInfo::read_from(source)?; + + let node = mast_node_info + .try_into_mast_node(&mut mast_forest, &basic_block_data_decoder)?; + + mast_forest.add_node(node).map_err(|e| { + DeserializationError::InvalidValue(format!( + "failed to add node to MAST forest while deserializing: {e}", + )) + })?; + } + + // roots + for root in roots { + // make sure the root is valid in the context of the MAST forest + let root = MastNodeId::from_u32_safe(root, &mast_forest)?; + mast_forest.make_root(root); + } + + mast_forest + }; + + // read "before enter" and "after exit" decorators, and update the corresponding nodes + let before_enter_decorators: Vec<(usize, Vec)> = + read_before_after_decorators(source, &mast_forest)?; + for (node_id, decorator_ids) in before_enter_decorators { + let node_id: u32 = node_id.try_into().map_err(|_| { + DeserializationError::InvalidValue(format!( + "Invalid node id '{node_id}' while deserializing" + )) + })?; + let node_id = MastNodeId::from_u32_safe(node_id, &mast_forest)?; + mast_forest.set_before_enter(node_id, decorator_ids); + } + + let after_exit_decorators: Vec<(usize, Vec)> = + read_before_after_decorators(source, &mast_forest)?; + for (node_id, decorator_ids) in after_exit_decorators { + let node_id: u32 = node_id.try_into().map_err(|_| { + DeserializationError::InvalidValue(format!( + "Invalid node id '{node_id}' while deserializing" + )) + })?; + let node_id = MastNodeId::from_u32_safe(node_id, &mast_forest)?; + mast_forest.set_after_exit(node_id, decorator_ids); + } + + Ok(mast_forest) + } +} + +/// Reads the `before_enter_decorators` and `after_exit_decorators` of the serialized `MastForest` +/// format. +/// +/// Note that we need this custom format because we cannot implement `Deserializable` for +/// `DecoratorId` (in favor of using [`DecoratorId::from_u32_safe`]). +fn read_before_after_decorators( + source: &mut R, + mast_forest: &MastForest, +) -> Result)>, DeserializationError> { + let vec_len: usize = source.read()?; + let mut out_vec: Vec<_> = Vec::with_capacity(vec_len); + + for _ in 0..vec_len { + let node_id: usize = source.read()?; + + let inner_vec_len: usize = source.read()?; + let mut inner_vec: Vec = Vec::with_capacity(inner_vec_len); + for _ in 0..inner_vec_len { + let decorator_id = DecoratorId::from_u32_safe(source.read()?, mast_forest)?; + inner_vec.push(decorator_id); + } + + out_vec.push((node_id, inner_vec)); + } + + Ok(out_vec) +} diff --git a/core/src/mast/serialization/string_table.rs b/core/src/mast/serialization/string_table.rs new file mode 100644 index 0000000000..9377aaa856 --- /dev/null +++ b/core/src/mast/serialization/string_table.rs @@ -0,0 +1,114 @@ +use alloc::{collections::BTreeMap, string::String, sync::Arc, vec::Vec}; +use core::cell::RefCell; + +use miden_crypto::hash::blake::{Blake3Digest, Blake3_256}; +use winter_utils::{ + ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable, SliceReader, +}; + +use super::{StringDataOffset, StringIndex}; + +pub struct StringTable { + data: Vec, + + table: Vec, + + /// This field is used to allocate an `Arc` for any string in `strings` where the decoder + /// requests a reference-counted string rather than a fresh allocation as a `String`. + /// + /// Currently, this is only used for debug information (source file names), but most cases + /// where strings are stored in MAST are stored as `Arc` in practice, we just haven't yet + /// updated all of the decoders. + /// + /// We lazily allocate an `Arc` when strings are decoded as an `Arc`, but the underlying + /// string data corresponds to the same index in `strings`. All future requests for a + /// ref-counted string we've allocated an `Arc` for, will clone the `Arc` rather than + /// allocate a fresh string. + refc_strings: Vec>>>, +} + +impl StringTable { + pub fn new(table: Vec, data: Vec) -> Self { + let mut refc_strings = Vec::with_capacity(table.len()); + refc_strings.resize(table.len(), RefCell::new(None)); + + Self { table, data, refc_strings } + } + + pub fn read_arc_str(&self, str_idx: StringIndex) -> Result, DeserializationError> { + if let Some(cached) = self.refc_strings.get(str_idx).and_then(|cell| cell.borrow().clone()) + { + return Ok(cached); + } + + let string = Arc::from(self.read_string(str_idx)?.into_boxed_str()); + *self.refc_strings[str_idx].borrow_mut() = Some(Arc::clone(&string)); + Ok(string) + } + + pub fn read_string(&self, str_idx: StringIndex) -> Result { + let str_offset = self.table.get(str_idx).copied().ok_or_else(|| { + DeserializationError::InvalidValue(format!("invalid index in strings table: {str_idx}")) + })?; + + let mut reader = SliceReader::new(&self.data[str_offset..]); + reader.read() + } +} + +impl Serializable for StringTable { + fn write_into(&self, target: &mut W) { + let Self { table, data, refc_strings: _ } = self; + + table.write_into(target); + data.write_into(target); + } +} + +impl Deserializable for StringTable { + fn read_from(source: &mut R) -> Result { + let table = source.read()?; + let data = source.read()?; + + Ok(Self::new(table, data)) + } +} + +// STRING TABLE BUILDER +// ================================================================================================ + +#[derive(Debug, Default)] +pub struct StringTableBuilder { + table: Vec, + str_to_index: BTreeMap, StringIndex>, + strings_data: Vec, +} + +impl StringTableBuilder { + pub fn add_string(&mut self, string: &str) -> StringIndex { + if let Some(str_idx) = self.str_to_index.get(&Blake3_256::hash(string.as_bytes())) { + // return already interned string + *str_idx + } else { + // add new string to table + let str_offset = self.strings_data.len(); + + assert!( + str_offset + string.len() < u32::MAX as usize, + "strings table larger than 2^32 bytes" + ); + + let str_idx = self.table.len(); + + string.write_into(&mut self.strings_data); + self.table.push(str_offset); + self.str_to_index.insert(Blake3_256::hash(string.as_bytes()), str_idx); + + str_idx + } + } + + pub fn into_table(self) -> StringTable { + StringTable::new(self.table, self.strings_data) + } +} diff --git a/core/src/mast/serialization/tests.rs b/core/src/mast/serialization/tests.rs new file mode 100644 index 0000000000..c76a3ff3ac --- /dev/null +++ b/core/src/mast/serialization/tests.rs @@ -0,0 +1,385 @@ +use alloc::{string::ToString, sync::Arc}; + +use miden_crypto::{hash::rpo::RpoDigest, Felt}; + +use super::*; +use crate::{ + mast::MastForestError, operations::Operation, AdviceInjector, AssemblyOp, DebugOptions, + Decorator, SignatureKind, +}; + +/// If this test fails to compile, it means that `Operation` or `Decorator` was changed. Make sure +/// that all tests in this file are updated accordingly. For example, if a new `Operation` variant +/// was added, make sure that you add it in the vector of operations in +/// [`serialize_deserialize_all_nodes`]. +#[test] +fn confirm_operation_and_decorator_structure() { + match Operation::Noop { + Operation::Noop => (), + Operation::Assert(_) => (), + Operation::FmpAdd => (), + Operation::FmpUpdate => (), + Operation::SDepth => (), + Operation::Caller => (), + Operation::Clk => (), + Operation::Join => (), + Operation::Split => (), + Operation::Loop => (), + Operation::Call => (), + Operation::Dyn => (), + Operation::SysCall => (), + Operation::Span => (), + Operation::End => (), + Operation::Repeat => (), + Operation::Respan => (), + Operation::Halt => (), + Operation::Add => (), + Operation::Neg => (), + Operation::Mul => (), + Operation::Inv => (), + Operation::Incr => (), + Operation::And => (), + Operation::Or => (), + Operation::Not => (), + Operation::Eq => (), + Operation::Eqz => (), + Operation::Expacc => (), + Operation::Ext2Mul => (), + Operation::U32split => (), + Operation::U32add => (), + Operation::U32assert2(_) => (), + Operation::U32add3 => (), + Operation::U32sub => (), + Operation::U32mul => (), + Operation::U32madd => (), + Operation::U32div => (), + Operation::U32and => (), + Operation::U32xor => (), + Operation::Pad => (), + Operation::Drop => (), + Operation::Dup0 => (), + Operation::Dup1 => (), + Operation::Dup2 => (), + Operation::Dup3 => (), + Operation::Dup4 => (), + Operation::Dup5 => (), + Operation::Dup6 => (), + Operation::Dup7 => (), + Operation::Dup9 => (), + Operation::Dup11 => (), + Operation::Dup13 => (), + Operation::Dup15 => (), + Operation::Swap => (), + Operation::SwapW => (), + Operation::SwapW2 => (), + Operation::SwapW3 => (), + Operation::SwapDW => (), + Operation::MovUp2 => (), + Operation::MovUp3 => (), + Operation::MovUp4 => (), + Operation::MovUp5 => (), + Operation::MovUp6 => (), + Operation::MovUp7 => (), + Operation::MovUp8 => (), + Operation::MovDn2 => (), + Operation::MovDn3 => (), + Operation::MovDn4 => (), + Operation::MovDn5 => (), + Operation::MovDn6 => (), + Operation::MovDn7 => (), + Operation::MovDn8 => (), + Operation::CSwap => (), + Operation::CSwapW => (), + Operation::Push(_) => (), + Operation::AdvPop => (), + Operation::AdvPopW => (), + Operation::MLoadW => (), + Operation::MStoreW => (), + Operation::MLoad => (), + Operation::MStore => (), + Operation::MStream => (), + Operation::Pipe => (), + Operation::HPerm => (), + Operation::MpVerify(_) => (), + Operation::MrUpdate => (), + Operation::FriE2F4 => (), + Operation::RCombBase => (), + Operation::Emit(_) => (), + }; + + match Decorator::Trace(0) { + Decorator::Advice(advice) => match advice { + AdviceInjector::MerkleNodeMerge => (), + AdviceInjector::MerkleNodeToStack => (), + AdviceInjector::UpdateMerkleNode => (), + AdviceInjector::MapValueToStack { include_len: _, key_offset: _ } => (), + AdviceInjector::U64Div => (), + AdviceInjector::Ext2Inv => (), + AdviceInjector::Ext2Intt => (), + AdviceInjector::SmtGet => (), + AdviceInjector::SmtSet => (), + AdviceInjector::SmtPeek => (), + AdviceInjector::U32Clz => (), + AdviceInjector::U32Ctz => (), + AdviceInjector::U32Clo => (), + AdviceInjector::U32Cto => (), + AdviceInjector::ILog2 => (), + AdviceInjector::MemToMap => (), + AdviceInjector::HdwordToMap { domain: _ } => (), + AdviceInjector::HpermToMap => (), + AdviceInjector::SigToStack { kind: _ } => (), + }, + Decorator::AsmOp(_) => (), + Decorator::Debug(debug_options) => match debug_options { + DebugOptions::StackAll => (), + DebugOptions::StackTop(_) => (), + DebugOptions::MemAll => (), + DebugOptions::MemInterval(..) => (), + DebugOptions::LocalInterval(..) => (), + }, + Decorator::Trace(_) => (), + }; +} + +#[test] +fn serialize_deserialize_all_nodes() { + let mut mast_forest = MastForest::new(); + + let basic_block_id = { + let operations = vec![ + Operation::Noop, + Operation::Assert(42), + Operation::FmpAdd, + Operation::FmpUpdate, + Operation::SDepth, + Operation::Caller, + Operation::Clk, + Operation::Join, + Operation::Split, + Operation::Loop, + Operation::Call, + Operation::Dyn, + Operation::SysCall, + Operation::Span, + Operation::End, + Operation::Repeat, + Operation::Respan, + Operation::Halt, + Operation::Add, + Operation::Neg, + Operation::Mul, + Operation::Inv, + Operation::Incr, + Operation::And, + Operation::Or, + Operation::Not, + Operation::Eq, + Operation::Eqz, + Operation::Expacc, + Operation::Ext2Mul, + Operation::U32split, + Operation::U32add, + Operation::U32assert2(222), + Operation::U32add3, + Operation::U32sub, + Operation::U32mul, + Operation::U32madd, + Operation::U32div, + Operation::U32and, + Operation::U32xor, + Operation::Pad, + Operation::Drop, + Operation::Dup0, + Operation::Dup1, + Operation::Dup2, + Operation::Dup3, + Operation::Dup4, + Operation::Dup5, + Operation::Dup6, + Operation::Dup7, + Operation::Dup9, + Operation::Dup11, + Operation::Dup13, + Operation::Dup15, + Operation::Swap, + Operation::SwapW, + Operation::SwapW2, + Operation::SwapW3, + Operation::SwapDW, + Operation::MovUp2, + Operation::MovUp3, + Operation::MovUp4, + Operation::MovUp5, + Operation::MovUp6, + Operation::MovUp7, + Operation::MovUp8, + Operation::MovDn2, + Operation::MovDn3, + Operation::MovDn4, + Operation::MovDn5, + Operation::MovDn6, + Operation::MovDn7, + Operation::MovDn8, + Operation::CSwap, + Operation::CSwapW, + Operation::Push(Felt::new(45)), + Operation::AdvPop, + Operation::AdvPopW, + Operation::MLoadW, + Operation::MStoreW, + Operation::MLoad, + Operation::MStore, + Operation::MStream, + Operation::Pipe, + Operation::HPerm, + Operation::MpVerify(1022), + Operation::MrUpdate, + Operation::FriE2F4, + Operation::RCombBase, + Operation::Emit(42), + ]; + + let num_operations = operations.len(); + + let decorators = vec![ + (0, Decorator::Advice(AdviceInjector::MerkleNodeMerge)), + (0, Decorator::Advice(AdviceInjector::MerkleNodeToStack)), + (0, Decorator::Advice(AdviceInjector::UpdateMerkleNode)), + ( + 0, + Decorator::Advice(AdviceInjector::MapValueToStack { + include_len: true, + key_offset: 1023, + }), + ), + (1, Decorator::Advice(AdviceInjector::U64Div)), + (3, Decorator::Advice(AdviceInjector::Ext2Inv)), + (5, Decorator::Advice(AdviceInjector::Ext2Intt)), + (5, Decorator::Advice(AdviceInjector::SmtGet)), + (5, Decorator::Advice(AdviceInjector::SmtSet)), + (5, Decorator::Advice(AdviceInjector::SmtPeek)), + (5, Decorator::Advice(AdviceInjector::U32Clz)), + (10, Decorator::Advice(AdviceInjector::U32Ctz)), + (10, Decorator::Advice(AdviceInjector::U32Clo)), + (10, Decorator::Advice(AdviceInjector::U32Cto)), + (10, Decorator::Advice(AdviceInjector::ILog2)), + (10, Decorator::Advice(AdviceInjector::MemToMap)), + (10, Decorator::Advice(AdviceInjector::HdwordToMap { domain: Felt::new(423) })), + (15, Decorator::Advice(AdviceInjector::HpermToMap)), + ( + 15, + Decorator::Advice(AdviceInjector::SigToStack { kind: SignatureKind::RpoFalcon512 }), + ), + ( + 15, + Decorator::AsmOp(AssemblyOp::new( + Some(crate::debuginfo::Location { + path: Arc::from("test"), + start: 42.into(), + end: 43.into(), + }), + "context".to_string(), + 15, + "op".to_string(), + false, + )), + ), + (15, Decorator::Debug(DebugOptions::StackAll)), + (15, Decorator::Debug(DebugOptions::StackTop(255))), + (15, Decorator::Debug(DebugOptions::MemAll)), + (15, Decorator::Debug(DebugOptions::MemInterval(0, 16))), + (17, Decorator::Debug(DebugOptions::LocalInterval(1, 2, 3))), + (num_operations, Decorator::Trace(55)), + ]; + + mast_forest.add_block_with_raw_decorators(operations, decorators).unwrap() + }; + + // Decorators to add to following nodes + let decorator_id1 = mast_forest.add_decorator(Decorator::Trace(1)).unwrap(); + let decorator_id2 = mast_forest.add_decorator(Decorator::Trace(2)).unwrap(); + + // Call node + let call_node_id = mast_forest.add_call(basic_block_id).unwrap(); + mast_forest[call_node_id].set_before_enter(vec![decorator_id1]); + mast_forest[call_node_id].set_after_exit(vec![decorator_id2]); + + // Syscall node + let syscall_node_id = mast_forest.add_syscall(basic_block_id).unwrap(); + mast_forest[syscall_node_id].set_before_enter(vec![decorator_id1]); + mast_forest[syscall_node_id].set_after_exit(vec![decorator_id2]); + + // Loop node + let loop_node_id = mast_forest.add_loop(basic_block_id).unwrap(); + mast_forest[loop_node_id].set_before_enter(vec![decorator_id1]); + mast_forest[loop_node_id].set_after_exit(vec![decorator_id2]); + + // Join node + let join_node_id = mast_forest.add_join(basic_block_id, call_node_id).unwrap(); + mast_forest[join_node_id].set_before_enter(vec![decorator_id1]); + mast_forest[join_node_id].set_after_exit(vec![decorator_id2]); + + // Split node + let split_node_id = mast_forest.add_split(basic_block_id, call_node_id).unwrap(); + mast_forest[split_node_id].set_before_enter(vec![decorator_id1]); + mast_forest[split_node_id].set_after_exit(vec![decorator_id2]); + + // Dyn node + let dyn_node_id = mast_forest.add_dyn().unwrap(); + mast_forest[dyn_node_id].set_before_enter(vec![decorator_id1]); + mast_forest[dyn_node_id].set_after_exit(vec![decorator_id2]); + + // External node + let external_node_id = mast_forest.add_external(RpoDigest::default()).unwrap(); + mast_forest[external_node_id].set_before_enter(vec![decorator_id1]); + mast_forest[external_node_id].set_after_exit(vec![decorator_id2]); + + mast_forest.make_root(join_node_id); + mast_forest.make_root(syscall_node_id); + mast_forest.make_root(loop_node_id); + mast_forest.make_root(split_node_id); + mast_forest.make_root(dyn_node_id); + mast_forest.make_root(external_node_id); + + let serialized_mast_forest = mast_forest.to_bytes(); + let deserialized_mast_forest = MastForest::read_from_bytes(&serialized_mast_forest).unwrap(); + + assert_eq!(mast_forest, deserialized_mast_forest); +} + +#[test] +fn mast_forest_invalid_node_id() { + // Hydrate a forest smaller than the second + let mut forest = MastForest::new(); + let first = forest.add_block(vec![Operation::U32div], None).unwrap(); + let second = forest.add_block(vec![Operation::U32div], None).unwrap(); + + // Hydrate a forest larger than the first to get an overflow MastNodeId + let mut overflow_forest = MastForest::new(); + let overflow = (0..=3) + .map(|_| overflow_forest.add_block(vec![Operation::U32div], None).unwrap()) + .last() + .unwrap(); + + // Attempt to join with invalid ids + let join = forest.add_join(overflow, second); + assert_eq!(join, Err(MastForestError::NodeIdOverflow(overflow, 2))); + let join = forest.add_join(first, overflow); + assert_eq!(join, Err(MastForestError::NodeIdOverflow(overflow, 2))); + + // Attempt to split with invalid ids + let split = forest.add_split(overflow, second); + assert_eq!(split, Err(MastForestError::NodeIdOverflow(overflow, 2))); + let split = forest.add_split(first, overflow); + assert_eq!(split, Err(MastForestError::NodeIdOverflow(overflow, 2))); + + // Attempt to loop with invalid ids + assert_eq!(forest.add_loop(overflow), Err(MastForestError::NodeIdOverflow(overflow, 2))); + + // Attempt to call with invalid ids + assert_eq!(forest.add_call(overflow), Err(MastForestError::NodeIdOverflow(overflow, 2))); + assert_eq!(forest.add_syscall(overflow), Err(MastForestError::NodeIdOverflow(overflow, 2))); + + // Validate normal operations + forest.add_join(first, second).unwrap(); +} diff --git a/core/src/program/tests.rs b/core/src/mast/tests.rs similarity index 72% rename from core/src/program/tests.rs rename to core/src/mast/tests.rs index 3e5e0a5329..aa224e9296 100644 --- a/core/src/program/tests.rs +++ b/core/src/mast/tests.rs @@ -1,14 +1,17 @@ -use super::{blocks::Dyn, Deserializable, Digest, Felt, Kernel, ProgramInfo, Serializable}; -use crate::{chiplets::hasher, Word}; use alloc::vec::Vec; + +use miden_crypto::{hash::rpo::RpoDigest, Felt}; use proptest::prelude::*; use rand_utils::prng_array; +use winter_utils::{Deserializable, Serializable}; + +use crate::{chiplets::hasher, mast::DynNode, Kernel, ProgramInfo, Word}; #[test] fn dyn_hash_is_correct() { let expected_constant = - hasher::merge_in_domain(&[Digest::default(), Digest::default()], Dyn::DOMAIN); - assert_eq!(expected_constant, Dyn::new().hash()); + hasher::merge_in_domain(&[RpoDigest::default(), RpoDigest::default()], DynNode::DOMAIN); + assert_eq!(expected_constant, DynNode::default().digest()); } proptest! { @@ -18,7 +21,7 @@ proptest! { ref seed in any::<[u8; 32]>() ) { let program_hash = digest_from_seed(*seed); - let kernel: Vec = (0..kernel_count) + let kernel: Vec = (0..kernel_count) .scan(*seed, |seed, _| { *seed = prng_array(*seed); Some(digest_from_seed(*seed)) @@ -35,7 +38,7 @@ proptest! { // HELPER FUNCTIONS // -------------------------------------------------------------------------------------------- -fn digest_from_seed(seed: [u8; 32]) -> Digest { +fn digest_from_seed(seed: [u8; 32]) -> RpoDigest { let mut digest = Word::default(); digest.iter_mut().enumerate().for_each(|(i, d)| { *d = <[u8; 8]>::try_from(&seed[i * 8..(i + 1) * 8]) diff --git a/core/src/operations/decorators/advice.rs b/core/src/operations/decorators/advice.rs index 80235f56ee..bc70906c3f 100644 --- a/core/src/operations/decorators/advice.rs +++ b/core/src/operations/decorators/advice.rs @@ -1,6 +1,7 @@ +use core::fmt; + use super::SignatureKind; use crate::Felt; -use core::fmt; // ADVICE INJECTORS // ================================================================================================ @@ -83,10 +84,7 @@ pub enum AdviceInjector { /// etc. /// /// The valid values of `key_offset` are 0 through 12 (inclusive). - MapValueToStack { - include_len: bool, - key_offset: usize, - }, + MapValueToStack { include_len: bool, key_offset: usize }, /// Pushes the result of [u64] division (both the quotient and the remainder) onto the advice /// stack. @@ -294,17 +292,14 @@ impl fmt::Display for AdviceInjector { Self::MerkleNodeToStack => write!(f, "merkle_node_to_stack"), Self::UpdateMerkleNode => { write!(f, "update_merkle_node") - } - Self::MapValueToStack { - include_len, - key_offset, - } => { + }, + Self::MapValueToStack { include_len, key_offset } => { if *include_len { write!(f, "map_value_to_stack_with_len.{key_offset}") } else { write!(f, "map_value_to_stack.{key_offset}") } - } + }, Self::U64Div => write!(f, "div_u64"), Self::Ext2Inv => write!(f, "ext2_inv"), Self::Ext2Intt => write!(f, "ext2_intt"), diff --git a/core/src/operations/decorators/assembly_op.rs b/core/src/operations/decorators/assembly_op.rs index 56689724cf..4676e76e1b 100644 --- a/core/src/operations/decorators/assembly_op.rs +++ b/core/src/operations/decorators/assembly_op.rs @@ -1,30 +1,45 @@ use alloc::string::String; use core::fmt; +use crate::debuginfo::Location; + // ASSEMBLY OP // ================================================================================================ /// Contains information corresponding to an assembly instruction (only applicable in debug mode). #[derive(Clone, Debug, Eq, PartialEq)] pub struct AssemblyOp { + location: Option, context_name: String, - num_cycles: u8, op: String, + num_cycles: u8, should_break: bool, } impl AssemblyOp { /// Returns [AssemblyOp] instantiated with the specified assembly instruction string and number /// of cycles it takes to execute the assembly instruction. - pub fn new(context_name: String, num_cycles: u8, op: String, should_break: bool) -> Self { + pub fn new( + location: Option, + context_name: String, + num_cycles: u8, + op: String, + should_break: bool, + ) -> Self { Self { + location, context_name, - num_cycles, op, + num_cycles, should_break, } } + /// Returns the [Location] for this operation, if known + pub fn location(&self) -> Option<&Location> { + self.location.as_ref() + } + /// Returns the context name for this operation. pub fn context_name(&self) -> &str { &self.context_name @@ -52,6 +67,11 @@ impl AssemblyOp { pub fn set_num_cycles(&mut self, num_cycles: u8) { self.num_cycles = num_cycles; } + + /// Change the [Location] of this [AssemblyOp] + pub fn set_location(&mut self, location: Location) { + self.location = Some(location); + } } impl fmt::Display for AssemblyOp { diff --git a/core/src/operations/decorators/debug.rs b/core/src/operations/decorators/debug.rs index 8d6b4c90af..55e6f740a0 100644 --- a/core/src/operations/decorators/debug.rs +++ b/core/src/operations/decorators/debug.rs @@ -43,7 +43,7 @@ impl fmt::Display for DebugOptions { Self::MemInterval(n, m) => write!(f, "mem.{n}.{m}"), Self::LocalInterval(start, end, _) => { write!(f, "local.{start}.{end}") - } + }, } } } diff --git a/core/src/operations/decorators/mod.rs b/core/src/operations/decorators/mod.rs index b049cdba86..ecfac643df 100644 --- a/core/src/operations/decorators/mod.rs +++ b/core/src/operations/decorators/mod.rs @@ -1,6 +1,9 @@ -use alloc::vec::Vec; +use alloc::{string::ToString, vec::Vec}; use core::fmt; +use miden_crypto::hash::blake::{Blake3Digest, Blake3_256}; +use num_traits::ToBytes; + mod advice; pub use advice::AdviceInjector; @@ -10,6 +13,8 @@ pub use assembly_op::AssemblyOp; mod debug; pub use debug::DebugOptions; +use crate::mast::DecoratorId; + // DECORATORS // ================================================================================================ @@ -30,12 +35,34 @@ pub enum Decorator { /// Prints out information about the state of the VM based on the specified options. This /// decorator is executed only in debug mode. Debug(DebugOptions), - /// Emits an event to the host. - Event(u32), - /// Emmits a trace to the host. + /// Emits a trace to the host. Trace(u32), } +impl Decorator { + pub fn eq_hash(&self) -> Blake3Digest<32> { + match self { + Self::Advice(advice) => Blake3_256::hash(advice.to_string().as_bytes()), + Self::AsmOp(asm_op) => { + let mut bytes_to_hash = Vec::new(); + if let Some(location) = asm_op.location() { + bytes_to_hash.extend(location.path.as_bytes()); + bytes_to_hash.extend(location.start.to_u32().to_le_bytes()); + bytes_to_hash.extend(location.end.to_u32().to_le_bytes()); + } + bytes_to_hash.extend(asm_op.context_name().as_bytes()); + bytes_to_hash.extend(asm_op.op().as_bytes()); + bytes_to_hash.push(asm_op.num_cycles()); + bytes_to_hash.push(asm_op.should_break() as u8); + + Blake3_256::hash(&bytes_to_hash) + }, + Self::Debug(debug) => Blake3_256::hash(debug.to_string().as_bytes()), + Self::Trace(trace) => Blake3_256::hash(&trace.to_le_bytes()), + } + } +} + impl crate::prettier::PrettyPrint for Decorator { fn render(&self) -> crate::prettier::Document { crate::prettier::display(self) @@ -48,9 +75,8 @@ impl fmt::Display for Decorator { Self::Advice(injector) => write!(f, "advice({injector})"), Self::AsmOp(assembly_op) => { write!(f, "asmOp({}, {})", assembly_op.op(), assembly_op.num_cycles()) - } + }, Self::Debug(options) => write!(f, "debug({options})"), - Self::Event(event_id) => write!(f, "event({})", event_id), Self::Trace(trace_id) => write!(f, "trace({})", trace_id), } } @@ -58,7 +84,7 @@ impl fmt::Display for Decorator { /// Vector consisting of a tuple of operation index (within a span block) and decorator at that /// index -pub type DecoratorList = Vec<(usize, Decorator)>; +pub type DecoratorList = Vec<(usize, DecoratorId)>; /// Iterator used to iterate through the decorator list of a span block /// while executing operation batches of a span block. @@ -76,7 +102,7 @@ impl<'a> DecoratorIterator<'a> { /// Returns the next decorator but only if its position matches the specified position, /// otherwise, None is returned. #[inline(always)] - pub fn next_filtered(&mut self, pos: usize) -> Option<&Decorator> { + pub fn next_filtered(&mut self, pos: usize) -> Option<&DecoratorId> { if self.idx < self.decorators.len() && self.decorators[self.idx].0 == pos { self.idx += 1; Some(&self.decorators[self.idx - 1].1) @@ -87,7 +113,7 @@ impl<'a> DecoratorIterator<'a> { } impl<'a> Iterator for DecoratorIterator<'a> { - type Item = &'a Decorator; + type Item = &'a DecoratorId; fn next(&mut self) -> Option { if self.idx < self.decorators.len() { diff --git a/core/src/operations/mod.rs b/core/src/operations/mod.rs index 4f22c5ab4c..1bb1c7afad 100644 --- a/core/src/operations/mod.rs +++ b/core/src/operations/mod.rs @@ -1,128 +1,253 @@ -use super::Felt; use core::fmt; + +use super::Felt; mod decorators; pub use decorators::{ AdviceInjector, AssemblyOp, DebugOptions, Decorator, DecoratorIterator, DecoratorList, SignatureKind, }; +// OPERATIONS OP CODES +// ================================================================================================ +use opcode_constants::*; +use winter_utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; + +/// Opcode patterns have the following meanings: +/// - 00xxxxx operations do not shift the stack; constraint degree can be up to 2. +/// - 010xxxx operations shift the stack the left; constraint degree can be up to 2. +/// - 011xxxx operations shift the stack to the right; constraint degree can be up to 2. +/// - 100xxx-: operations consume 4 range checks; constraint degree can be up to 3. These are used +/// to encode most u32 operations. +/// - 101xxx-: operations where constraint degree can be up to 3. These include control flow +/// operations and some other operations requiring high degree constraints. +/// - 11xxx--: operations where constraint degree can be up to 5. These include control flow +/// operations and some other operations requiring very high degree constraints. +#[rustfmt::skip] +pub(super) mod opcode_constants { + pub const OPCODE_NOOP: u8 = 0b0000_0000; + pub const OPCODE_EQZ: u8 = 0b0000_0001; + pub const OPCODE_NEG: u8 = 0b0000_0010; + pub const OPCODE_INV: u8 = 0b0000_0011; + pub const OPCODE_INCR: u8 = 0b0000_0100; + pub const OPCODE_NOT: u8 = 0b0000_0101; + pub const OPCODE_FMPADD: u8 = 0b0000_0110; + pub const OPCODE_MLOAD: u8 = 0b0000_0111; + pub const OPCODE_SWAP: u8 = 0b0000_1000; + pub const OPCODE_CALLER: u8 = 0b0000_1001; + pub const OPCODE_MOVUP2: u8 = 0b0000_1010; + pub const OPCODE_MOVDN2: u8 = 0b0000_1011; + pub const OPCODE_MOVUP3: u8 = 0b0000_1100; + pub const OPCODE_MOVDN3: u8 = 0b0000_1101; + pub const OPCODE_ADVPOPW: u8 = 0b0000_1110; + pub const OPCODE_EXPACC: u8 = 0b0000_1111; + + pub const OPCODE_MOVUP4: u8 = 0b0001_0000; + pub const OPCODE_MOVDN4: u8 = 0b0001_0001; + pub const OPCODE_MOVUP5: u8 = 0b0001_0010; + pub const OPCODE_MOVDN5: u8 = 0b0001_0011; + pub const OPCODE_MOVUP6: u8 = 0b0001_0100; + pub const OPCODE_MOVDN6: u8 = 0b0001_0101; + pub const OPCODE_MOVUP7: u8 = 0b0001_0110; + pub const OPCODE_MOVDN7: u8 = 0b0001_0111; + pub const OPCODE_SWAPW: u8 = 0b0001_1000; + pub const OPCODE_EXT2MUL: u8 = 0b0001_1001; + pub const OPCODE_MOVUP8: u8 = 0b0001_1010; + pub const OPCODE_MOVDN8: u8 = 0b0001_1011; + pub const OPCODE_SWAPW2: u8 = 0b0001_1100; + pub const OPCODE_SWAPW3: u8 = 0b0001_1101; + pub const OPCODE_SWAPDW: u8 = 0b0001_1110; + + pub const OPCODE_ASSERT: u8 = 0b0010_0000; + pub const OPCODE_EQ: u8 = 0b0010_0001; + pub const OPCODE_ADD: u8 = 0b0010_0010; + pub const OPCODE_MUL: u8 = 0b0010_0011; + pub const OPCODE_AND: u8 = 0b0010_0100; + pub const OPCODE_OR: u8 = 0b0010_0101; + pub const OPCODE_U32AND: u8 = 0b0010_0110; + pub const OPCODE_U32XOR: u8 = 0b0010_0111; + pub const OPCODE_FRIE2F4: u8 = 0b0010_1000; + pub const OPCODE_DROP: u8 = 0b0010_1001; + pub const OPCODE_CSWAP: u8 = 0b0010_1010; + pub const OPCODE_CSWAPW: u8 = 0b0010_1011; + pub const OPCODE_MLOADW: u8 = 0b0010_1100; + pub const OPCODE_MSTORE: u8 = 0b0010_1101; + pub const OPCODE_MSTOREW: u8 = 0b0010_1110; + pub const OPCODE_FMPUPDATE: u8 = 0b0010_1111; + + pub const OPCODE_PAD: u8 = 0b0011_0000; + pub const OPCODE_DUP0: u8 = 0b0011_0001; + pub const OPCODE_DUP1: u8 = 0b0011_0010; + pub const OPCODE_DUP2: u8 = 0b0011_0011; + pub const OPCODE_DUP3: u8 = 0b0011_0100; + pub const OPCODE_DUP4: u8 = 0b0011_0101; + pub const OPCODE_DUP5: u8 = 0b0011_0110; + pub const OPCODE_DUP6: u8 = 0b0011_0111; + pub const OPCODE_DUP7: u8 = 0b0011_1000; + pub const OPCODE_DUP9: u8 = 0b0011_1001; + pub const OPCODE_DUP11: u8 = 0b0011_1010; + pub const OPCODE_DUP13: u8 = 0b0011_1011; + pub const OPCODE_DUP15: u8 = 0b0011_1100; + pub const OPCODE_ADVPOP: u8 = 0b0011_1101; + pub const OPCODE_SDEPTH: u8 = 0b0011_1110; + pub const OPCODE_CLK: u8 = 0b0011_1111; + + pub const OPCODE_U32ADD: u8 = 0b0100_0000; + pub const OPCODE_U32SUB: u8 = 0b0100_0010; + pub const OPCODE_U32MUL: u8 = 0b0100_0100; + pub const OPCODE_U32DIV: u8 = 0b0100_0110; + pub const OPCODE_U32SPLIT: u8 = 0b0100_1000; + pub const OPCODE_U32ASSERT2: u8 = 0b0100_1010; + pub const OPCODE_U32ADD3: u8 = 0b0100_1100; + pub const OPCODE_U32MADD: u8 = 0b0100_1110; + + pub const OPCODE_HPERM: u8 = 0b0101_0000; + pub const OPCODE_MPVERIFY: u8 = 0b0101_0001; + pub const OPCODE_PIPE: u8 = 0b0101_0010; + pub const OPCODE_MSTREAM: u8 = 0b0101_0011; + pub const OPCODE_SPLIT: u8 = 0b0101_0100; + pub const OPCODE_LOOP: u8 = 0b0101_0101; + pub const OPCODE_SPAN: u8 = 0b0101_0110; + pub const OPCODE_JOIN: u8 = 0b0101_0111; + pub const OPCODE_DYN: u8 = 0b0101_1000; + pub const OPCODE_RCOMBBASE: u8 = 0b0101_1001; + pub const OPCODE_EMIT: u8 = 0b0101_1010; + pub const OPCODE_PUSH: u8 = 0b0101_1011; + + pub const OPCODE_MRUPDATE: u8 = 0b0110_0000; + /* unused: 0b0110_0100 */ + pub const OPCODE_SYSCALL: u8 = 0b0110_1000; + pub const OPCODE_CALL: u8 = 0b0110_1100; + pub const OPCODE_END: u8 = 0b0111_0000; + pub const OPCODE_REPEAT: u8 = 0b0111_0100; + pub const OPCODE_RESPAN: u8 = 0b0111_1000; + pub const OPCODE_HALT: u8 = 0b0111_1100; +} // OPERATIONS // ================================================================================================ -/// A set of native VM operations. -/// -/// These operations take exactly one cycle to execute. +/// A set of native VM operations which take exactly one cycle to execute. #[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[repr(u8)] pub enum Operation { - // ----- system operations -------------------------------------------------------------------- + // ----- system operations ------------------------------------------------------------------- /// Advances cycle counter, but does not change the state of user stack. - Noop, + Noop = OPCODE_NOOP, /// Pops the stack; if the popped value is not 1, execution fails. /// /// The internal value specifies an error code associated with the error in case when the /// execution fails. - Assert(u32), + Assert(u32) = OPCODE_ASSERT, /// Pops an element off the stack, adds the current value of the `fmp` register to it, and /// pushes the result back onto the stack. - FmpAdd, + FmpAdd = OPCODE_FMPADD, /// Pops an element off the stack and adds it to the current value of `fmp` register. - FmpUpdate, + FmpUpdate = OPCODE_FMPUPDATE, /// Pushes the current depth of the stack onto the stack. - SDepth, + SDepth = OPCODE_SDEPTH, /// Overwrites the top four stack items with the hash of a function which initiated the current /// SYSCALL. Thus, this operation can be executed only inside a SYSCALL code block. - Caller, + Caller = OPCODE_CALLER, /// Pushes the current value of the clock cycle onto the stack. This operation can be used to /// measure the number of cycles it has taken to execute the program up to the current /// instruction. - Clk, + Clk = OPCODE_CLK, - // ----- flow control operations -------------------------------------------------------------- + /// Emits an event id (`u32` value) to the host. + /// + /// We interpret the event id as follows: + /// - 16 most significant bits identify the event source, + /// - 16 least significant bits identify the actual event. + /// + /// Similar to Noop, this operation does not change the state of user stack. The immediate + /// value affects the program MAST root computation. + Emit(u32) = OPCODE_EMIT, + + // ----- flow control operations ------------------------------------------------------------- /// Marks the beginning of a join block. - Join, + Join = OPCODE_JOIN, /// Marks the beginning of a split block. - Split, + Split = OPCODE_SPLIT, /// Marks the beginning of a loop block. - Loop, + Loop = OPCODE_LOOP, /// Marks the beginning of a function call. - Call, + Call = OPCODE_CALL, /// Marks the beginning of a dynamic code block, where the target is specified by the stack. - Dyn, + Dyn = OPCODE_DYN, /// Marks the beginning of a kernel call. - SysCall, + SysCall = OPCODE_SYSCALL, /// Marks the beginning of a span code block. - Span, + Span = OPCODE_SPAN, /// Marks the end of a program block. - End, + End = OPCODE_END, /// Indicates that body of an executing loop should be executed again. - Repeat, + Repeat = OPCODE_REPEAT, /// Starts processing a new operation batch. - Respan, + Respan = OPCODE_RESPAN, /// Indicates the end of the program. This is used primarily to pad the execution trace to /// the required length. Once HALT operation is executed, no other operations can be executed /// by the VM (HALT operation itself excepted). - Halt, + Halt = OPCODE_HALT, - // ----- field operations --------------------------------------------------------------------- + // ----- field operations -------------------------------------------------------------------- /// Pops two elements off the stack, adds them, and pushes the result back onto the stack. - Add, + Add = OPCODE_ADD, /// Pops an element off the stack, negates it, and pushes the result back onto the stack. - Neg, + Neg = OPCODE_NEG, /// Pops two elements off the stack, multiplies them, and pushes the result back onto the /// stack. - Mul, + Mul = OPCODE_MUL, /// Pops an element off the stack, computes its multiplicative inverse, and pushes the result /// back onto the stack. - Inv, + Inv = OPCODE_INV, /// Pops an element off the stack, adds 1 to it, and pushes the result back onto the stack. - Incr, + Incr = OPCODE_INCR, /// Pops two elements off the stack, multiplies them, and pushes the result back onto the /// stack. /// /// If either of the elements is greater than 1, execution fails. This operation is equivalent /// to boolean AND. - And, + And = OPCODE_AND, /// Pops two elements off the stack and subtracts their product from their sum. /// /// If either of the elements is greater than 1, execution fails. This operation is equivalent /// to boolean OR. - Or, + Or = OPCODE_OR, /// Pops an element off the stack and subtracts it from 1. /// /// If the element is greater than one, the execution fails. This operation is equivalent to /// boolean NOT. - Not, + Not = OPCODE_NOT, /// Pops two elements off the stack and compares them. If the elements are equal, pushes 1 /// onto the stack, otherwise pushes 0 onto the stack. - Eq, + Eq = OPCODE_EQ, /// Pops an element off the stack and compares it to 0. If the element is 0, pushes 1 onto /// the stack, otherwise pushes 0 onto the stack. - Eqz, + Eqz = OPCODE_EQZ, /// Computes a single turn of exponent accumulation for the given inputs. This operation can be /// be used to compute a single turn of power of a field element. @@ -137,36 +262,36 @@ pub enum Operation { /// At the end of the operation, exponent is replaced with its square, current value of power /// of base number `a` on exponent is incorporated into the accumulator and the number is /// shifted to the right by one bit. - Expacc, + Expacc = OPCODE_EXPACC, - // ----- ext2 operations ---------------------------------------------------------------------- + // ----- ext2 operations --------------------------------------------------------------------- /// Computes the product of two elements in the extension field of degree 2 and pushes the /// result back onto the stack as the third and fourth elements. Pushes 0 onto the stack as /// the first and second elements. - Ext2Mul, + Ext2Mul = OPCODE_EXT2MUL, - // ----- u32 operations ----------------------------------------------------------------------- + // ----- u32 operations ---------------------------------------------------------------------- /// Pops an element off the stack, splits it into upper and lower 32-bit values, and pushes /// these values back onto the stack. - U32split, + U32split = OPCODE_U32SPLIT, /// Pops two elements off the stack, adds them, and splits the result into upper and lower /// 32-bit values. Then pushes these values back onto the stack. /// /// If either of these elements is greater than or equal to 2^32, the result of this /// operation is undefined. - U32add, + U32add = OPCODE_U32ADD, /// Pops two elements off the stack and checks if each of them represents a 32-bit value. /// If both of them are, they are pushed back onto the stack, otherwise an error is returned. /// /// The internal value specifies an error code associated with the error in case when the /// assertion fails. - U32assert2(Felt), + U32assert2(u32) = OPCODE_U32ASSERT2, /// Pops three elements off the stack, adds them together, and splits the result into upper /// and lower 32-bit values. Then pushes the result back onto the stack. - U32add3, + U32add3 = OPCODE_U32ADD3, /// Pops two elements off the stack and subtracts the first element from the second. Then, /// the result, together with a flag indicating whether subtraction underflowed is pushed @@ -174,14 +299,14 @@ pub enum Operation { /// /// If their of the values is greater than or equal to 2^32, the result of this operation is /// undefined. - U32sub, + U32sub = OPCODE_U32SUB, /// Pops two elements off the stack, multiplies them, and splits the result into upper and /// lower 32-bit values. Then pushes these values back onto the stack. /// /// If their of the values is greater than or equal to 2^32, the result of this operation is /// undefined. - U32mul, + U32mul = OPCODE_U32MUL, /// Pops two elements off the stack and multiplies them. Then pops the third element off the /// stack, and adds it to the result. Finally, splits the result into upper and lower 32-bit @@ -189,170 +314,170 @@ pub enum Operation { /// /// If any of the three values is greater than or equal to 2^32, the result of this operation /// is undefined. - U32madd, + U32madd = OPCODE_U32MADD, /// Pops two elements off the stack and divides the second element by the first. Then pushes /// the integer result of the division, together with the remainder, onto the stack. /// /// If their of the values is greater than or equal to 2^32, the result of this operation is /// undefined. - U32div, + U32div = OPCODE_U32DIV, /// Pops two elements off the stack, computes their binary AND, and pushes the result back /// onto the stack. /// /// If either of the elements is greater than or equal to 2^32, execution fails. - U32and, + U32and = OPCODE_U32AND, /// Pops two elements off the stack, computes their binary XOR, and pushes the result back /// onto the stack. /// /// If either of the elements is greater than or equal to 2^32, execution fails. - U32xor, + U32xor = OPCODE_U32XOR, - // ----- stack manipulation ------------------------------------------------------------------- + // ----- stack manipulation ------------------------------------------------------------------ /// Pushes 0 onto the stack. - Pad, + Pad = OPCODE_PAD, /// Removes to element from the stack. - Drop, + Drop = OPCODE_DROP, /// Pushes a copy of stack element 0 onto the stack. - Dup0, + Dup0 = OPCODE_DUP0, /// Pushes a copy of stack element 1 onto the stack. - Dup1, + Dup1 = OPCODE_DUP1, /// Pushes a copy of stack element 2 onto the stack. - Dup2, + Dup2 = OPCODE_DUP2, /// Pushes a copy of stack element 3 onto the stack. - Dup3, + Dup3 = OPCODE_DUP3, /// Pushes a copy of stack element 4 onto the stack. - Dup4, + Dup4 = OPCODE_DUP4, /// Pushes a copy of stack element 5 onto the stack. - Dup5, + Dup5 = OPCODE_DUP5, /// Pushes a copy of stack element 6 onto the stack. - Dup6, + Dup6 = OPCODE_DUP6, /// Pushes a copy of stack element 7 onto the stack. - Dup7, + Dup7 = OPCODE_DUP7, /// Pushes a copy of stack element 9 onto the stack. - Dup9, + Dup9 = OPCODE_DUP9, /// Pushes a copy of stack element 11 onto the stack. - Dup11, + Dup11 = OPCODE_DUP11, /// Pushes a copy of stack element 13 onto the stack. - Dup13, + Dup13 = OPCODE_DUP13, /// Pushes a copy of stack element 15 onto the stack. - Dup15, + Dup15 = OPCODE_DUP15, /// Swaps stack elements 0 and 1. - Swap, + Swap = OPCODE_SWAP, /// Swaps stack elements 0, 1, 2, and 3 with elements 4, 5, 6, and 7. - SwapW, + SwapW = OPCODE_SWAPW, /// Swaps stack elements 0, 1, 2, and 3 with elements 8, 9, 10, and 11. - SwapW2, + SwapW2 = OPCODE_SWAPW2, /// Swaps stack elements 0, 1, 2, and 3, with elements 12, 13, 14, and 15. - SwapW3, + SwapW3 = OPCODE_SWAPW3, /// Swaps the top two words pair wise. /// /// Input: [D, C, B, A, ...] /// Output: [B, A, D, C, ...] - SwapDW, + SwapDW = OPCODE_SWAPDW, /// Moves stack element 2 to the top of the stack. - MovUp2, + MovUp2 = OPCODE_MOVUP2, /// Moves stack element 3 to the top of the stack. - MovUp3, + MovUp3 = OPCODE_MOVUP3, /// Moves stack element 4 to the top of the stack. - MovUp4, + MovUp4 = OPCODE_MOVUP4, /// Moves stack element 5 to the top of the stack. - MovUp5, + MovUp5 = OPCODE_MOVUP5, /// Moves stack element 6 to the top of the stack. - MovUp6, + MovUp6 = OPCODE_MOVUP6, /// Moves stack element 7 to the top of the stack. - MovUp7, + MovUp7 = OPCODE_MOVUP7, /// Moves stack element 8 to the top of the stack. - MovUp8, + MovUp8 = OPCODE_MOVUP8, /// Moves the top stack element to position 2 on the stack. - MovDn2, + MovDn2 = OPCODE_MOVDN2, /// Moves the top stack element to position 3 on the stack. - MovDn3, + MovDn3 = OPCODE_MOVDN3, /// Moves the top stack element to position 4 on the stack. - MovDn4, + MovDn4 = OPCODE_MOVDN4, /// Moves the top stack element to position 5 on the stack. - MovDn5, + MovDn5 = OPCODE_MOVDN5, /// Moves the top stack element to position 6 on the stack. - MovDn6, + MovDn6 = OPCODE_MOVDN6, /// Moves the top stack element to position 7 on the stack. - MovDn7, + MovDn7 = OPCODE_MOVDN7, /// Moves the top stack element to position 8 on the stack. - MovDn8, + MovDn8 = OPCODE_MOVDN8, /// Pops an element off the stack, and if the element is 1, swaps the top two remaining /// elements on the stack. If the popped element is 0, the stack remains unchanged. /// /// If the popped element is neither 0 nor 1, execution fails. - CSwap, + CSwap = OPCODE_CSWAP, /// Pops an element off the stack, and if the element is 1, swaps the remaining elements /// 0, 1, 2, and 3 with elements 4, 5, 6, and 7. If the popped element is 0, the stack /// remains unchanged. /// /// If the popped element is neither 0 nor 1, execution fails. - CSwapW, + CSwapW = OPCODE_CSWAPW, - // ----- input / output ----------------------------------------------------------------------- + // ----- input / output ---------------------------------------------------------------------- /// Pushes the immediate value onto the stack. - Push(Felt), + Push(Felt) = OPCODE_PUSH, /// Removes the next element from the advice stack and pushes it onto the operand stack. - AdvPop, + AdvPop = OPCODE_ADVPOP, /// Removes a word (4 elements) from the advice stack and overwrites the top four operand /// stack elements with it. - AdvPopW, + AdvPopW = OPCODE_ADVPOPW, /// Pops an element off the stack, interprets it as a memory address, and replaces the /// remaining 4 elements at the top of the stack with values located at the specified address. - MLoadW, + MLoadW = OPCODE_MLOADW, /// Pops an element off the stack, interprets it as a memory address, and writes the remaining /// 4 elements at the top of the stack into memory at the specified address. - MStoreW, + MStoreW = OPCODE_MSTOREW, /// Pops an element off the stack, interprets it as a memory address, and pushes the first /// element of the word located at the specified address to the stack. - MLoad, + MLoad = OPCODE_MLOAD, /// Pops an element off the stack, interprets it as a memory address, and writes the remaining /// element at the top of the stack into the first element of the word located at the specified /// memory address. The remaining 3 elements of the word are not affected. - MStore, + MStore = OPCODE_MSTORE, /// Loads two words from memory, and replaces the top 8 elements of the stack with them, /// element-wise, in stack order. @@ -364,7 +489,7 @@ pub enum Operation { /// order). /// - Memory address (in position 12) is incremented by 2. /// - All other stack elements remain the same. - MStream, + MStream = OPCODE_MSTREAM, /// Pops two words from the advice stack, writes them to memory, and replaces the top 8 /// elements of the stack with them, element-wise, in stack order. @@ -378,16 +503,16 @@ pub enum Operation { /// order). /// - Memory address (in position 12) is incremented by 2. /// - All other stack elements remain the same. - Pipe, + Pipe = OPCODE_PIPE, - // ----- cryptographic operations ------------------------------------------------------------- + // ----- cryptographic operations ------------------------------------------------------------ /// Performs a Rescue Prime Optimized permutation on the top 3 words of the operand stack, /// where the top 2 words are the rate (words C and B), the deepest word is the capacity (word /// A), and the digest output is the middle word E. /// /// Stack transition: /// [C, B, A, ...] -> [F, E, D, ...] - HPerm, + HPerm = OPCODE_HPERM, /// Verifies that a Merkle path from the specified node resolves to the specified root. This /// operation can be used to prove that the prover knows a path in the specified Merkle tree @@ -402,7 +527,10 @@ pub enum Operation { /// The Merkle path itself is expected to be provided by the prover non-deterministically (via /// merkle sets). If the prover is not able to provide the required path, the operation fails. /// The state of the stack does not change. - MpVerify, + /// + /// The internal value specifies an error code associated with the error in case when the + /// assertion fails. + MpVerify(u32) = OPCODE_MPVERIFY, /// Computes a new root of a Merkle tree where a node at the specified position is updated to /// the specified value. @@ -421,10 +549,10 @@ pub enum Operation { /// /// The tree will always be copied into a new instance, meaning the advice provider will keep /// track of both the old and new Merkle trees. - MrUpdate, + MrUpdate = OPCODE_MRUPDATE, /// TODO: add docs - FriE2F4, + FriE2F4 = OPCODE_FRIE2F4, /// Performs a single step of a random linear combination defining the DEEP composition /// polynomial i.e., the input to the FRI protocol. More precisely, the sum in question is: @@ -435,136 +563,28 @@ pub enum Operation { /// and $\alpha_i \cdot (T_i(x) - T_i(g \cdot z))$ and stores the values in two accumulators /// $r$ and $p$, respectively. This instruction is specialized to main trace columns i.e. /// the values $T_i(x)$ are base field elements. - RCombBase, + RCombBase = OPCODE_RCOMBBASE, } impl Operation { pub const OP_BITS: usize = 7; /// Returns the opcode of this operation. - /// - /// Opcode patterns have the following meanings: - /// - 00xxxxx operations do not shift the stack; constraint degree can be up to 2. - /// - 010xxxx operations shift the stack the left; constraint degree can be up to 2. - /// - 011xxxx operations shift the stack to the right; constraint degree can be up to 2. - /// - 100xxx-: operations consume 4 range checks; constraint degree can be up to 3. These are - /// used to encode most u32 operations. - /// - 101xxx-: operations where constraint degree can be up to 3. These include control flow - /// operations and some other operations requiring high degree constraints. - /// - 11xxx--: operations where constraint degree can be up to 5. These include control flow - /// operations and some other operations requiring very high degree constraints. #[rustfmt::skip] - pub const fn op_code(&self) -> u8 { - match self { - Self::Noop => 0b0000_0000, - Self::Eqz => 0b0000_0001, - Self::Neg => 0b0000_0010, - Self::Inv => 0b0000_0011, - Self::Incr => 0b0000_0100, - Self::Not => 0b0000_0101, - Self::FmpAdd => 0b0000_0110, - Self::MLoad => 0b0000_0111, - Self::Swap => 0b0000_1000, - Self::Caller => 0b0000_1001, - Self::MovUp2 => 0b0000_1010, - Self::MovDn2 => 0b0000_1011, - Self::MovUp3 => 0b0000_1100, - Self::MovDn3 => 0b0000_1101, - Self::AdvPopW => 0b0000_1110, - Self::Expacc => 0b0000_1111, - - Self::MovUp4 => 0b0001_0000, - Self::MovDn4 => 0b0001_0001, - Self::MovUp5 => 0b0001_0010, - Self::MovDn5 => 0b0001_0011, - Self::MovUp6 => 0b0001_0100, - Self::MovDn6 => 0b0001_0101, - Self::MovUp7 => 0b0001_0110, - Self::MovDn7 => 0b0001_0111, - Self::SwapW => 0b0001_1000, - Self::Ext2Mul => 0b0001_1001, - Self::MovUp8 => 0b0001_1010, - Self::MovDn8 => 0b0001_1011, - Self::SwapW2 => 0b0001_1100, - Self::SwapW3 => 0b0001_1101, - Self::SwapDW => 0b0001_1110, - // => 0b0001_1111, - - Self::Assert(_) => 0b0010_0000, - Self::Eq => 0b0010_0001, - Self::Add => 0b0010_0010, - Self::Mul => 0b0010_0011, - Self::And => 0b0010_0100, - Self::Or => 0b0010_0101, - Self::U32and => 0b0010_0110, - Self::U32xor => 0b0010_0111, - Self::FriE2F4 => 0b0010_1000, - Self::Drop => 0b0010_1001, - Self::CSwap => 0b0010_1010, - Self::CSwapW => 0b0010_1011, - Self::MLoadW => 0b0010_1100, - Self::MStore => 0b0010_1101, - Self::MStoreW => 0b0010_1110, - Self::FmpUpdate => 0b0010_1111, - - Self::Pad => 0b0011_0000, - Self::Dup0 => 0b0011_0001, - Self::Dup1 => 0b0011_0010, - Self::Dup2 => 0b0011_0011, - Self::Dup3 => 0b0011_0100, - Self::Dup4 => 0b0011_0101, - Self::Dup5 => 0b0011_0110, - Self::Dup6 => 0b0011_0111, - Self::Dup7 => 0b0011_1000, - Self::Dup9 => 0b0011_1001, - Self::Dup11 => 0b0011_1010, - Self::Dup13 => 0b0011_1011, - Self::Dup15 => 0b0011_1100, - Self::AdvPop => 0b0011_1101, - Self::SDepth => 0b0011_1110, - Self::Clk => 0b0011_1111, - - Self::U32add => 0b0100_0000, - Self::U32sub => 0b0100_0010, - Self::U32mul => 0b0100_0100, - Self::U32div => 0b0100_0110, - Self::U32split => 0b0100_1000, - Self::U32assert2(_) => 0b0100_1010, - Self::U32add3 => 0b0100_1100, - Self::U32madd => 0b0100_1110, - - Self::HPerm => 0b0101_0000, - Self::MpVerify => 0b0101_0001, - Self::Pipe => 0b0101_0010, - Self::MStream => 0b0101_0011, - Self::Split => 0b0101_0100, - Self::Loop => 0b0101_0101, - Self::Span => 0b0101_0110, - Self::Join => 0b0101_0111, - Self::Dyn => 0b0101_1000, - Self::RCombBase => 0b0101_1001, - // => 0b0101_1010, - // => 0b0101_1011, - // => 0b0101_1100, - // => 0b0101_1101, - // => 0b0101_1110, - // => 0b0101_1111, - - Self::MrUpdate => 0b0110_0000, - Self::Push(_) => 0b0110_0100, - Self::SysCall => 0b0110_1000, - Self::Call => 0b0110_1100, - Self::End => 0b0111_0000, - Self::Repeat => 0b0111_0100, - Self::Respan => 0b0111_1000, - Self::Halt => 0b0111_1100, - } + pub fn op_code(&self) -> u8 { + // SAFETY: This is safe because we have given this enum a primitive representation with + // #[repr(u8)], with the first field of the underlying union-of-structs the discriminant. + // + // See the section on "accessing the numeric value of the discriminant" + // here: https://doc.rust-lang.org/std/mem/fn.discriminant.html + unsafe { *<*const _>::from(self).cast::() } } /// Returns an immediate value carried by this operation. pub fn imm_value(&self) -> Option { - match self { - Self::Push(imm) => Some(*imm), + match *self { + Self::Push(imm) => Some(imm), + Self::Emit(imm) => Some(imm.into()), _ => None, } } @@ -711,12 +731,256 @@ impl fmt::Display for Operation { Self::MStream => write!(f, "mstream"), Self::Pipe => write!(f, "pipe"), + Self::Emit(value) => write!(f, "emit({value})"), + // ----- cryptographic operations ----------------------------------------------------- Self::HPerm => write!(f, "hperm"), - Self::MpVerify => write!(f, "mpverify"), + Self::MpVerify(err_code) => write!(f, "mpverify({err_code})"), Self::MrUpdate => write!(f, "mrupdate"), Self::FriE2F4 => write!(f, "frie2f4"), Self::RCombBase => write!(f, "rcomb1"), } } } + +impl Serializable for Operation { + fn write_into(&self, target: &mut W) { + target.write_u8(self.op_code()); + + // For operations that have extra data, encode it in `data`. + match self { + Operation::Assert(err_code) + | Operation::MpVerify(err_code) + | Operation::U32assert2(err_code) => { + err_code.write_into(target); + }, + Operation::Push(value) => value.as_int().write_into(target), + Operation::Emit(value) => value.write_into(target), + + // Note: we explicitly write out all the operations so that whenever we make a + // modification to the `Operation` enum, we get a compile error here. This + // should help us remember to properly encode/decode each operation variant. + Operation::Noop + | Operation::FmpAdd + | Operation::FmpUpdate + | Operation::SDepth + | Operation::Caller + | Operation::Clk + | Operation::Join + | Operation::Split + | Operation::Loop + | Operation::Call + | Operation::Dyn + | Operation::SysCall + | Operation::Span + | Operation::End + | Operation::Repeat + | Operation::Respan + | Operation::Halt + | Operation::Add + | Operation::Neg + | Operation::Mul + | Operation::Inv + | Operation::Incr + | Operation::And + | Operation::Or + | Operation::Not + | Operation::Eq + | Operation::Eqz + | Operation::Expacc + | Operation::Ext2Mul + | Operation::U32split + | Operation::U32add + | Operation::U32add3 + | Operation::U32sub + | Operation::U32mul + | Operation::U32madd + | Operation::U32div + | Operation::U32and + | Operation::U32xor + | Operation::Pad + | Operation::Drop + | Operation::Dup0 + | Operation::Dup1 + | Operation::Dup2 + | Operation::Dup3 + | Operation::Dup4 + | Operation::Dup5 + | Operation::Dup6 + | Operation::Dup7 + | Operation::Dup9 + | Operation::Dup11 + | Operation::Dup13 + | Operation::Dup15 + | Operation::Swap + | Operation::SwapW + | Operation::SwapW2 + | Operation::SwapW3 + | Operation::SwapDW + | Operation::MovUp2 + | Operation::MovUp3 + | Operation::MovUp4 + | Operation::MovUp5 + | Operation::MovUp6 + | Operation::MovUp7 + | Operation::MovUp8 + | Operation::MovDn2 + | Operation::MovDn3 + | Operation::MovDn4 + | Operation::MovDn5 + | Operation::MovDn6 + | Operation::MovDn7 + | Operation::MovDn8 + | Operation::CSwap + | Operation::CSwapW + | Operation::AdvPop + | Operation::AdvPopW + | Operation::MLoadW + | Operation::MStoreW + | Operation::MLoad + | Operation::MStore + | Operation::MStream + | Operation::Pipe + | Operation::HPerm + | Operation::MrUpdate + | Operation::FriE2F4 + | Operation::RCombBase => (), + } + } +} + +impl Deserializable for Operation { + fn read_from(source: &mut R) -> Result { + let op_code = source.read_u8()?; + + let operation = match op_code { + OPCODE_NOOP => Self::Noop, + OPCODE_EQZ => Self::Eqz, + OPCODE_NEG => Self::Neg, + OPCODE_INV => Self::Inv, + OPCODE_INCR => Self::Incr, + OPCODE_NOT => Self::Not, + OPCODE_FMPADD => Self::FmpAdd, + OPCODE_MLOAD => Self::MLoad, + OPCODE_SWAP => Self::Swap, + OPCODE_CALLER => Self::Caller, + OPCODE_MOVUP2 => Self::MovUp2, + OPCODE_MOVDN2 => Self::MovDn2, + OPCODE_MOVUP3 => Self::MovUp3, + OPCODE_MOVDN3 => Self::MovDn3, + OPCODE_ADVPOPW => Self::AdvPopW, + OPCODE_EXPACC => Self::Expacc, + + OPCODE_MOVUP4 => Self::MovUp4, + OPCODE_MOVDN4 => Self::MovDn4, + OPCODE_MOVUP5 => Self::MovUp5, + OPCODE_MOVDN5 => Self::MovDn5, + OPCODE_MOVUP6 => Self::MovUp6, + OPCODE_MOVDN6 => Self::MovDn6, + OPCODE_MOVUP7 => Self::MovUp7, + OPCODE_MOVDN7 => Self::MovDn7, + OPCODE_SWAPW => Self::SwapW, + OPCODE_EXT2MUL => Self::Ext2Mul, + OPCODE_MOVUP8 => Self::MovUp8, + OPCODE_MOVDN8 => Self::MovDn8, + OPCODE_SWAPW2 => Self::SwapW2, + OPCODE_SWAPW3 => Self::SwapW3, + OPCODE_SWAPDW => Self::SwapDW, + + OPCODE_ASSERT => { + let err_code = source.read_u32()?; + Self::Assert(err_code) + }, + OPCODE_EQ => Self::Eq, + OPCODE_ADD => Self::Add, + OPCODE_MUL => Self::Mul, + OPCODE_AND => Self::And, + OPCODE_OR => Self::Or, + OPCODE_U32AND => Self::U32and, + OPCODE_U32XOR => Self::U32xor, + OPCODE_FRIE2F4 => Self::FriE2F4, + OPCODE_DROP => Self::Drop, + OPCODE_CSWAP => Self::CSwap, + OPCODE_CSWAPW => Self::CSwapW, + OPCODE_MLOADW => Self::MLoadW, + OPCODE_MSTORE => Self::MStore, + OPCODE_MSTOREW => Self::MStoreW, + OPCODE_FMPUPDATE => Self::FmpUpdate, + + OPCODE_PAD => Self::Pad, + OPCODE_DUP0 => Self::Dup0, + OPCODE_DUP1 => Self::Dup1, + OPCODE_DUP2 => Self::Dup2, + OPCODE_DUP3 => Self::Dup3, + OPCODE_DUP4 => Self::Dup4, + OPCODE_DUP5 => Self::Dup5, + OPCODE_DUP6 => Self::Dup6, + OPCODE_DUP7 => Self::Dup7, + OPCODE_DUP9 => Self::Dup9, + OPCODE_DUP11 => Self::Dup11, + OPCODE_DUP13 => Self::Dup13, + OPCODE_DUP15 => Self::Dup15, + OPCODE_ADVPOP => Self::AdvPop, + OPCODE_SDEPTH => Self::SDepth, + OPCODE_CLK => Self::Clk, + + OPCODE_U32ADD => Self::U32add, + OPCODE_U32SUB => Self::U32sub, + OPCODE_U32MUL => Self::U32mul, + OPCODE_U32DIV => Self::U32div, + OPCODE_U32SPLIT => Self::U32split, + OPCODE_U32ASSERT2 => { + let err_code = source.read_u32()?; + + Self::U32assert2(err_code) + }, + OPCODE_U32ADD3 => Self::U32add3, + OPCODE_U32MADD => Self::U32madd, + + OPCODE_HPERM => Self::HPerm, + OPCODE_MPVERIFY => { + let err_code = source.read_u32()?; + + Self::MpVerify(err_code) + }, + OPCODE_PIPE => Self::Pipe, + OPCODE_MSTREAM => Self::MStream, + OPCODE_SPLIT => Self::Split, + OPCODE_LOOP => Self::Loop, + OPCODE_SPAN => Self::Span, + OPCODE_JOIN => Self::Join, + OPCODE_DYN => Self::Dyn, + OPCODE_RCOMBBASE => Self::RCombBase, + + OPCODE_MRUPDATE => Self::MrUpdate, + OPCODE_PUSH => { + let value_u64 = source.read_u64()?; + let value_felt = Felt::try_from(value_u64).map_err(|_| { + DeserializationError::InvalidValue(format!( + "Operation associated data doesn't fit in a field element: {value_u64}" + )) + })?; + + Self::Push(value_felt) + }, + OPCODE_EMIT => { + let value = source.read_u32()?; + + Self::Emit(value) + }, + OPCODE_SYSCALL => Self::SysCall, + OPCODE_CALL => Self::Call, + OPCODE_END => Self::End, + OPCODE_REPEAT => Self::Repeat, + OPCODE_RESPAN => Self::Respan, + OPCODE_HALT => Self::Halt, + _ => { + return Err(DeserializationError::InvalidValue(format!( + "Invalid opcode '{op_code}'" + ))); + }, + }; + + Ok(operation) + } +} diff --git a/core/src/program.rs b/core/src/program.rs new file mode 100644 index 0000000000..385baa9421 --- /dev/null +++ b/core/src/program.rs @@ -0,0 +1,266 @@ +use alloc::{sync::Arc, vec::Vec}; +use core::fmt; + +use miden_crypto::{hash::rpo::RpoDigest, Felt, WORD_SIZE}; +use winter_utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; + +use super::Kernel; +use crate::{ + mast::{MastForest, MastNode, MastNodeId}, + utils::ToElements, +}; + +// PROGRAM +// =============================================================================================== + +/// An executable program for Miden VM. +/// +/// A program consists of a MAST forest, an entrypoint defining the MAST node at which the program +/// execution begins, and a definition of the kernel against which the program must be executed +/// (the kernel can be an empty kernel). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Program { + mast_forest: Arc, + /// The "entrypoint" is the node where execution of the program begins. + entrypoint: MastNodeId, + kernel: Kernel, +} + +/// Constructors +impl Program { + /// Construct a new [`Program`] from the given MAST forest and entrypoint. The kernel is assumed + /// to be empty. + /// + /// # Panics: + /// - if `mast_forest` doesn't contain the specified entrypoint. + /// - if the specified entrypoint is not a procedure root in the `mast_forest`. + pub fn new(mast_forest: Arc, entrypoint: MastNodeId) -> Self { + Self::with_kernel(mast_forest, entrypoint, Kernel::default()) + } + + /// Construct a new [`Program`] from the given MAST forest, entrypoint, and kernel. + /// + /// # Panics: + /// - if `mast_forest` doesn't contain the specified entrypoint. + /// - if the specified entrypoint is not a procedure root in the `mast_forest`. + pub fn with_kernel( + mast_forest: Arc, + entrypoint: MastNodeId, + kernel: Kernel, + ) -> Self { + assert!(mast_forest.get_node_by_id(entrypoint).is_some(), "invalid entrypoint"); + assert!(mast_forest.is_procedure_root(entrypoint), "entrypoint not a procedure"); + + Self { mast_forest, entrypoint, kernel } + } +} + +// ------------------------------------------------------------------------------------------------ +/// Public accessors +impl Program { + /// Returns the hash of the program's entrypoint. + /// + /// Equivalently, returns the hash of the root of the entrypoint procedure. + pub fn hash(&self) -> RpoDigest { + self.mast_forest[self.entrypoint].digest() + } + + /// Returns the entrypoint associated with this program. + pub fn entrypoint(&self) -> MastNodeId { + self.entrypoint + } + + /// Returns a reference to the underlying [`MastForest`]. + pub fn mast_forest(&self) -> &Arc { + &self.mast_forest + } + + /// Returns the kernel associated with this program. + pub fn kernel(&self) -> &Kernel { + &self.kernel + } + + /// Returns the [`MastNode`] associated with the provided [`MastNodeId`] if valid, or else + /// `None`. + /// + /// This is the fallible version of indexing (e.g. `program[node_id]`). + #[inline(always)] + pub fn get_node_by_id(&self, node_id: MastNodeId) -> Option<&MastNode> { + self.mast_forest.get_node_by_id(node_id) + } + + /// Returns the [`MastNodeId`] of the procedure root associated with a given digest, if any. + #[inline(always)] + pub fn find_procedure_root(&self, digest: RpoDigest) -> Option { + self.mast_forest.find_procedure_root(digest) + } + + /// Returns the number of procedures in this program. + pub fn num_procedures(&self) -> u32 { + self.mast_forest.num_procedures() + } +} + +// ------------------------------------------------------------------------------------------------ +/// Serialization +#[cfg(feature = "std")] +impl Program { + /// Writes this [Program] to the provided file path. + pub fn write_to_file

(&self, path: P) -> std::io::Result<()> + where + P: AsRef, + { + let path = path.as_ref(); + if let Some(dir) = path.parent() { + std::fs::create_dir_all(dir)?; + } + + // 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(|| match std::fs::File::create(path) { + Ok(ref mut file) => { + self.write_into(file); + Ok(()) + }, + Err(err) => Err(err), + }) + .map_err(|p| { + 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), + } + })? + } +} + +impl Serializable for Program { + fn write_into(&self, target: &mut W) { + self.mast_forest.write_into(target); + self.kernel.write_into(target); + target.write_u32(self.entrypoint.as_u32()); + } +} + +impl Deserializable for Program { + fn read_from(source: &mut R) -> Result { + let mast_forest = Arc::new(source.read()?); + let kernel = source.read()?; + let entrypoint = MastNodeId::from_u32_safe(source.read_u32()?, &mast_forest)?; + + if mast_forest.is_procedure_root(entrypoint) { + return Err(DeserializationError::InvalidValue(format!( + "entrypoint {entrypoint} is not a procedure" + ))); + } + + Ok(Self::with_kernel(mast_forest, entrypoint, kernel)) + } +} + +// ------------------------------------------------------------------------------------------------ +// Pretty-printing + +impl crate::prettier::PrettyPrint for Program { + fn render(&self) -> crate::prettier::Document { + use crate::prettier::*; + let entrypoint = self.mast_forest[self.entrypoint()].to_pretty_print(&self.mast_forest); + + indent(4, const_text("begin") + nl() + entrypoint.render()) + nl() + const_text("end") + } +} + +impl fmt::Display for Program { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use crate::prettier::PrettyPrint; + self.pretty_print(f) + } +} + +// PROGRAM INFO +// =============================================================================================== + +/// A program information set consisting of its MAST root and set of kernel procedure roots used +/// for its compilation. +/// +/// This will be used as public inputs of the proof so we bind its verification to the kernel and +/// root used to execute the program. This way, we extend the correctness of the proof to the +/// security guarantees provided by the kernel. We also allow the user to easily prove the +/// membership of a given kernel procedure for a given proof, without compromising its +/// zero-knowledge properties. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct ProgramInfo { + program_hash: RpoDigest, + kernel: Kernel, +} + +impl ProgramInfo { + /// Creates a new instance of a program info. + pub const fn new(program_hash: RpoDigest, kernel: Kernel) -> Self { + Self { program_hash, kernel } + } + + /// Returns the program hash computed from its code block root. + pub const fn program_hash(&self) -> &RpoDigest { + &self.program_hash + } + + /// Returns the program kernel used during the compilation. + pub const fn kernel(&self) -> &Kernel { + &self.kernel + } + + /// Returns the list of procedures of the kernel used during the compilation. + pub fn kernel_procedures(&self) -> &[RpoDigest] { + self.kernel.proc_hashes() + } +} + +impl From for ProgramInfo { + fn from(program: Program) -> Self { + let program_hash = program.hash(); + let kernel = program.kernel().clone(); + + Self { program_hash, kernel } + } +} + +// ------------------------------------------------------------------------------------------------ +// Serialization + +impl Serializable for ProgramInfo { + fn write_into(&self, target: &mut W) { + self.program_hash.write_into(target); + self.kernel.write_into(target); + } +} + +impl Deserializable for ProgramInfo { + fn read_from(source: &mut R) -> Result { + let program_hash = source.read()?; + let kernel = source.read()?; + Ok(Self { program_hash, kernel }) + } +} + +// ------------------------------------------------------------------------------------------------ +// ToElements implementation + +impl ToElements for ProgramInfo { + fn to_elements(&self) -> Vec { + let num_kernel_proc_elements = self.kernel.proc_hashes().len() * WORD_SIZE; + let mut result = Vec::with_capacity(WORD_SIZE + num_kernel_proc_elements); + + // append program hash elements + result.extend_from_slice(self.program_hash.as_elements()); + + // append kernel procedure hash elements + for proc_hash in self.kernel.proc_hashes() { + result.extend_from_slice(proc_hash.as_elements()); + } + result + } +} diff --git a/core/src/program/blocks/call_block.rs b/core/src/program/blocks/call_block.rs deleted file mode 100644 index 2cffa54cd9..0000000000 --- a/core/src/program/blocks/call_block.rs +++ /dev/null @@ -1,101 +0,0 @@ -use super::{hasher, Digest, Felt, Operation}; -use core::fmt; - -// CALL BLOCK -// ================================================================================================ -/// Block for a function call. -/// -/// Executes the function referenced by `fn_hash`. Fails if the body is unavailable to the VM, or -/// if the execution of the call fails. -/// -/// The hash of a call block is computed as: -/// -/// > hash(fn_hash || padding, domain=CALL_DOMAIN) -/// > hash(fn_hash || padding, domain=SYSCALL_DOMAIN) # when a syscall is used -/// -/// Where `fn_hash` is 4 field elements (256 bits), and `padding` is 4 ZERO elements (256 bits). -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Call { - hash: Digest, - fn_hash: Digest, - is_syscall: bool, -} - -impl Call { - // CONSTANTS - // -------------------------------------------------------------------------------------------- - /// The domain of the call block (used for control block hashing). - pub const CALL_DOMAIN: Felt = Felt::new(Operation::Call.op_code() as u64); - /// The domain of the syscall block (used for control block hashing). - pub const SYSCALL_DOMAIN: Felt = Felt::new(Operation::SysCall.op_code() as u64); - - // CONSTRUCTOR - // -------------------------------------------------------------------------------------------- - /// Returns a new [Call] block instantiated with the specified function body hash. - pub fn new(fn_hash: Digest) -> Self { - let hash = hasher::merge_in_domain(&[fn_hash, Digest::default()], Self::CALL_DOMAIN); - Self { - hash, - fn_hash, - is_syscall: false, - } - } - - /// Returns a new [Call] block instantiated with the specified function body hash and marked - /// as a kernel call. - pub fn new_syscall(fn_hash: Digest) -> Self { - let hash = hasher::merge_in_domain(&[fn_hash, Digest::default()], Self::SYSCALL_DOMAIN); - Self { - hash, - fn_hash, - is_syscall: true, - } - } - - // PUBLIC ACCESSORS - // -------------------------------------------------------------------------------------------- - - /// Returns a hash of this code block. - pub fn hash(&self) -> Digest { - self.hash - } - - /// Returns a hash of the function to be called by this block. - pub fn fn_hash(&self) -> Digest { - self.fn_hash - } - - /// Returns true if this call block corresponds to a kernel call. - pub fn is_syscall(&self) -> bool { - self.is_syscall - } - - /// Returns the domain of the call block - pub fn domain(&self) -> Felt { - match self.is_syscall() { - true => Self::SYSCALL_DOMAIN, - false => Self::CALL_DOMAIN, - } - } -} - -impl crate::prettier::PrettyPrint for Call { - fn render(&self) -> crate::prettier::Document { - use crate::prettier::*; - use miden_formatting::hex::ToHex; - - let doc = if self.is_syscall { - const_text("syscall") - } else { - const_text("call") - }; - doc + const_text(".") + text(self.fn_hash.as_bytes().to_hex_with_prefix()) - } -} - -impl fmt::Display for Call { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use crate::prettier::PrettyPrint; - self.pretty_print(f) - } -} diff --git a/core/src/program/blocks/dyn_block.rs b/core/src/program/blocks/dyn_block.rs deleted file mode 100644 index 8571abf69d..0000000000 --- a/core/src/program/blocks/dyn_block.rs +++ /dev/null @@ -1,77 +0,0 @@ -use miden_formatting::prettier::PrettyPrint; - -use super::{Digest, Felt, Operation}; -use core::fmt; - -// CONSTANTS -// ================================================================================================ - -/// The Dyn block is represented by a constant, which is set to be the hash of two empty words -/// ([ZERO, ZERO, ZERO, ZERO]) with a domain value of `DYN_DOMAIN`, i.e. -/// hasher::merge_in_domain(&[Digest::default(), Digest::default()], Dyn::DOMAIN) -const DYN_CONSTANT: Digest = Digest::new([ - Felt::new(8115106948140260551), - Felt::new(13491227816952616836), - Felt::new(15015806788322198710), - Felt::new(16575543461540527115), -]); - -// Dyn BLOCK -// ================================================================================================ -/// Block for dynamic code where the target is specified by the stack. -/// -/// Executes the code block referenced by the hash on top of the stack. Fails if the body is -/// unavailable to the VM, or if the execution of the dynamically-specified code block fails. -/// -/// The child of a Dyn block (the target specified by the stack) is always dynamic and does not -/// affect the representation of the Dyn block. Therefore all Dyn blocks are represented by the same -/// constant (rather than by unique hashes), which is computed as an RPO hash of two empty words -/// ([ZERO, ZERO, ZERO, ZERO]) with a domain value of `DYN_DOMAIN`. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Dyn {} - -impl Dyn { - // CONSTANTS - // -------------------------------------------------------------------------------------------- - /// The domain of the Dyn block (used for control block hashing). - pub const DOMAIN: Felt = Felt::new(Operation::Dyn.op_code() as u64); - - // CONSTRUCTOR - // -------------------------------------------------------------------------------------------- - /// Returns a new [Dyn] block instantiated with the specified function body hash. - pub fn new() -> Self { - Self {} - } - - // PUBLIC ACCESSORS - // -------------------------------------------------------------------------------------------- - - /// Returns a hash of this code block. - pub fn hash(&self) -> Digest { - Self::dyn_hash() - } - - /// Returns a hash of this code block. - pub fn dyn_hash() -> Digest { - DYN_CONSTANT - } -} - -impl Default for Dyn { - fn default() -> Self { - Self::new() - } -} - -impl crate::prettier::PrettyPrint for Dyn { - fn render(&self) -> crate::prettier::Document { - use crate::prettier::*; - const_text("dyn") - } -} - -impl fmt::Display for Dyn { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.pretty_print(f) - } -} diff --git a/core/src/program/blocks/join_block.rs b/core/src/program/blocks/join_block.rs deleted file mode 100644 index b68ca75043..0000000000 --- a/core/src/program/blocks/join_block.rs +++ /dev/null @@ -1,83 +0,0 @@ -use alloc::boxed::Box; -use core::fmt; - -use super::{hasher, CodeBlock, Digest, Felt, Operation}; - -// JOIN BLOCKS -// ================================================================================================ -/// Block for sequential execution of two sub-blocks. -/// -/// Executes left sub-block then the right sub-block. Fails if either of the sub-block execution -/// fails. -/// -/// The hash of a join block is computed as: -/// -/// > hash(left_block_hash || right_block_hash, domain=JOIN_DOMAIN) -/// -/// Where `left_block_hash` and `right_block_hash` are 4 field elements (256 bits) each. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Join { - body: Box<[CodeBlock; 2]>, - hash: Digest, -} - -impl Join { - // CONSTANTS - // -------------------------------------------------------------------------------------------- - /// The domain of the join block (used for control block hashing). - pub const DOMAIN: Felt = Felt::new(Operation::Join.op_code() as u64); - - // CONSTRUCTOR - // -------------------------------------------------------------------------------------------- - /// Returns a new [Join] block instantiated with the specified code blocks. - pub fn new(body: [CodeBlock; 2]) -> Self { - let hash = hasher::merge_in_domain(&[body[0].hash(), body[1].hash()], Self::DOMAIN); - Self { - body: Box::new(body), - hash, - } - } - - // PUBLIC ACCESSORS - // -------------------------------------------------------------------------------------------- - - /// Returns a hash of this code block. - pub fn hash(&self) -> Digest { - self.hash - } - - /// Returns a reference to the code block which is to be executed first when this join block - /// is executed. - pub fn first(&self) -> &CodeBlock { - &self.body[0] - } - - /// Returns a reference to the code block which is to be executed second when this join block - /// is executed. - pub fn second(&self) -> &CodeBlock { - &self.body[1] - } -} - -impl crate::prettier::PrettyPrint for Join { - #[rustfmt::skip] - fn render(&self) -> crate::prettier::Document { - use crate::prettier::*; - - indent( - 4, - const_text("join") - + nl() - + self.body[0].render() - + nl() - + self.body[1].render(), - ) + nl() + const_text("end") - } -} - -impl fmt::Display for Join { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use crate::prettier::PrettyPrint; - self.pretty_print(f) - } -} diff --git a/core/src/program/blocks/loop_block.rs b/core/src/program/blocks/loop_block.rs deleted file mode 100644 index 999c8cc89a..0000000000 --- a/core/src/program/blocks/loop_block.rs +++ /dev/null @@ -1,67 +0,0 @@ -use alloc::boxed::Box; -use core::fmt; - -use super::{hasher, CodeBlock, Digest, Felt, Operation}; - -// LOOP BLOCK -// ================================================================================================ -/// Block for a conditional loop. -/// -/// Executes the loop body while the value on the top of the stack is `1`, stops when `0`. Fails if -/// the top of the stack is neither `1` nor `0`, or if the execution of the body fails. -/// -/// The hash of a loop block is: -/// -/// > hash(body_hash || padding, domain=LOOP_DOMAIN) -/// -/// Where `body_hash` is 4 field elements (256 bits), and `padding` is 4 ZERO elements (256 bits). -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Loop { - body: Box, - hash: Digest, -} - -impl Loop { - // CONSTANTS - // -------------------------------------------------------------------------------------------- - /// The domain of the loop block (used for control block hashing). - pub const DOMAIN: Felt = Felt::new(Operation::Loop.op_code() as u64); - - // CONSTRUCTOR - // -------------------------------------------------------------------------------------------- - /// Returns a new [Loop] bock instantiated with the specified body. - pub fn new(body: CodeBlock) -> Self { - let hash = hasher::merge_in_domain(&[body.hash(), Digest::default()], Self::DOMAIN); - Self { - body: Box::new(body), - hash, - } - } - - // PUBLIC ACCESSORS - // -------------------------------------------------------------------------------------------- - - /// Returns a hash of this code block. - pub fn hash(&self) -> Digest { - self.hash - } - - /// Returns a reference to the code block which represents the body of the loop. - pub fn body(&self) -> &CodeBlock { - &self.body - } -} - -impl crate::prettier::PrettyPrint for Loop { - fn render(&self) -> crate::prettier::Document { - use crate::prettier::*; - indent(4, const_text("while.true") + nl() + self.body.render()) + nl() + const_text("end") - } -} - -impl fmt::Display for Loop { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use crate::prettier::PrettyPrint; - self.pretty_print(f) - } -} diff --git a/core/src/program/blocks/mod.rs b/core/src/program/blocks/mod.rs deleted file mode 100644 index 945f5c8eaf..0000000000 --- a/core/src/program/blocks/mod.rs +++ /dev/null @@ -1,148 +0,0 @@ -use super::{hasher, Digest, Felt, Operation}; -use crate::DecoratorList; -use alloc::vec::Vec; -use core::fmt; - -mod call_block; -mod dyn_block; -mod join_block; -mod loop_block; -mod proxy_block; -mod span_block; -mod split_block; - -pub use call_block::Call; -pub use dyn_block::Dyn; -pub use join_block::Join; -pub use loop_block::Loop; -pub use proxy_block::Proxy; -pub use span_block::{ - get_span_op_group_count, OpBatch, Span, BATCH_SIZE as OP_BATCH_SIZE, - GROUP_SIZE as OP_GROUP_SIZE, -}; -pub use split_block::Split; - -// PROGRAM BLOCK -// ================================================================================================ -/// TODO: add comments -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum CodeBlock { - Span(Span), - Join(Join), - Split(Split), - Loop(Loop), - Call(Call), - Dyn(Dyn), - Proxy(Proxy), -} - -impl CodeBlock { - // CONSTRUCTORS - // -------------------------------------------------------------------------------------------- - - /// Returns a new Span block instantiated with the provided operations. - pub fn new_span(operations: Vec) -> Self { - Self::Span(Span::new(operations)) - } - - /// Returns a new Span block instantiated with the provided operations and decorator list. - pub fn new_span_with_decorators(operations: Vec, decorators: DecoratorList) -> Self { - Self::Span(Span::with_decorators(operations, decorators)) - } - - /// TODO: add comments - pub fn new_join(blocks: [CodeBlock; 2]) -> Self { - Self::Join(Join::new(blocks)) - } - - /// TODO: add comments - pub fn new_split(t_branch: CodeBlock, f_branch: CodeBlock) -> Self { - Self::Split(Split::new(t_branch, f_branch)) - } - - /// TODO: add comments - pub fn new_loop(body: CodeBlock) -> Self { - Self::Loop(Loop::new(body)) - } - - /// TODO: add comments - pub fn new_call(fn_hash: Digest) -> Self { - Self::Call(Call::new(fn_hash)) - } - - /// TODO: add comments - pub fn new_syscall(fn_hash: Digest) -> Self { - Self::Call(Call::new_syscall(fn_hash)) - } - - /// TODO: add comments - pub fn new_dyn() -> Self { - Self::Dyn(Dyn::new()) - } - - /// TODO: add comments - pub fn new_dyncall() -> Self { - Self::Call(Call::new(Dyn::dyn_hash())) - } - - /// TODO: add comments - pub fn new_proxy(code_hash: Digest) -> Self { - Self::Proxy(Proxy::new(code_hash)) - } - - // PUBLIC ACCESSORS - // -------------------------------------------------------------------------------------------- - - /// Returns true if this code block is a [Span] block. - pub fn is_span(&self) -> bool { - matches!(self, CodeBlock::Span(_)) - } - - /// Returns a hash of this code block. - pub fn hash(&self) -> Digest { - match self { - CodeBlock::Span(block) => block.hash(), - CodeBlock::Join(block) => block.hash(), - CodeBlock::Split(block) => block.hash(), - CodeBlock::Loop(block) => block.hash(), - CodeBlock::Call(block) => block.hash(), - CodeBlock::Dyn(block) => block.hash(), - CodeBlock::Proxy(block) => block.hash(), - } - } - - /// Returns the domain of the code block - pub fn domain(&self) -> Felt { - match self { - CodeBlock::Call(block) => block.domain(), - CodeBlock::Dyn(_) => Dyn::DOMAIN, - CodeBlock::Join(_) => Join::DOMAIN, - CodeBlock::Loop(_) => Loop::DOMAIN, - CodeBlock::Span(_) => Span::DOMAIN, - CodeBlock::Split(_) => Split::DOMAIN, - CodeBlock::Proxy(_) => panic!("Can't fetch `domain` for a `Proxy` block!"), - } - } -} - -impl crate::prettier::PrettyPrint for CodeBlock { - fn render(&self) -> crate::prettier::Document { - match self { - Self::Span(block) => block.render(), - Self::Join(block) => block.render(), - Self::Split(block) => block.render(), - Self::Loop(block) => block.render(), - Self::Call(block) => block.render(), - Self::Dyn(block) => block.render(), - Self::Proxy(block) => block.render(), - } - } -} - -impl fmt::Display for CodeBlock { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use crate::prettier::PrettyPrint; - - self.pretty_print(f) - } -} diff --git a/core/src/program/blocks/proxy_block.rs b/core/src/program/blocks/proxy_block.rs deleted file mode 100644 index 84a45cf1c3..0000000000 --- a/core/src/program/blocks/proxy_block.rs +++ /dev/null @@ -1,43 +0,0 @@ -use super::Digest; -use core::fmt; - -// PROXY BLOCK -// ================================================================================================ -/// Block for a unknown function call. -/// -/// Proxy blocks are used to verify the integrity of a program's hash while keeping parts -/// of the program secret. Fails if executed. -/// -/// Hash of a proxy block is not computed but is rather defined at instantiation time. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Proxy { - hash: Digest, -} - -impl Proxy { - /// Returns a new [Proxy] block instantiated with the specified code hash. - pub fn new(code_hash: Digest) -> Self { - Self { hash: code_hash } - } - - /// Returns a hash of this code block. - pub fn hash(&self) -> Digest { - self.hash - } -} - -impl crate::prettier::PrettyPrint for Proxy { - fn render(&self) -> crate::prettier::Document { - use crate::prettier::*; - use miden_formatting::hex::ToHex; - - const_text("proxy") + const_text(".") + text(self.hash.as_bytes().to_hex_with_prefix()) - } -} - -impl fmt::Display for Proxy { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use crate::prettier::PrettyPrint; - self.pretty_print(f) - } -} diff --git a/core/src/program/blocks/span_block.rs b/core/src/program/blocks/span_block.rs deleted file mode 100644 index c7ae5c8caa..0000000000 --- a/core/src/program/blocks/span_block.rs +++ /dev/null @@ -1,761 +0,0 @@ -use alloc::vec::Vec; -use core::fmt; - -use winter_utils::flatten_slice_elements; - -use super::{hasher, Digest, Felt, Operation}; -use crate::{DecoratorIterator, DecoratorList, ZERO}; - -// CONSTANTS -// ================================================================================================ - -/// Maximum number of operations per group. -pub const GROUP_SIZE: usize = 9; - -/// Maximum number of groups per batch. -pub const BATCH_SIZE: usize = 8; - -/// Maximum number of operations which can fit into a single operation batch. -const MAX_OPS_PER_BATCH: usize = GROUP_SIZE * BATCH_SIZE; - -// SPAN BLOCK -// ================================================================================================ -/// Block for a linear sequence of operations (i.e., no branching or loops). -/// -/// Executes its operations in order. Fails if any of the operations fails. -/// -/// A span is composed of operation batches, operation batches are composed of operation groups, -/// operation groups encode the VM's operations and immediate values. These values are created -/// according to these rules: -/// -/// - A span contains one or more batches. -/// - A batch contains exactly 8 groups. -/// - A group contains exactly 9 operations or 1 immediate value. -/// - NOOPs are used to fill a group or batch when necessary. -/// - An immediate value follows the operation that requires it, using the next available group in -/// the batch. If there are no batches available in the group, then both the operation and its -/// immediate are moved to the next batch. -/// -/// Example: 8 pushes result in two operation batches: -/// -/// - First batch: First group with 7 push opcodes and 2 zero-paddings packed together, followed by -/// 7 groups with their respective immediate values. -/// - Second batch: First group with the last push opcode and 8 zero-paddings packed together, -/// followed by one immediate and 6 padding groups. -/// -/// The hash of a span block is: -/// -/// > hash(batches, domain=SPAN_DOMAIN) -/// -/// Where `batches` is the concatenation of each `batch` in the span, and each batch is 8 field -/// elements (512 bits). -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Span { - op_batches: Vec, - hash: Digest, - decorators: DecoratorList, -} - -impl Span { - // CONSTANTS - // -------------------------------------------------------------------------------------------- - /// The domain of the span block (used for control block hashing). - pub const DOMAIN: Felt = ZERO; - - // CONSTRUCTOR - // -------------------------------------------------------------------------------------------- - /// Returns a new [Span] block instantiated with the specified operations. - /// - /// # Errors (TODO) - /// Returns an error if: - /// - `operations` vector is empty. - /// - `operations` vector contains any number of system operations. - pub fn new(operations: Vec) -> Self { - assert!(!operations.is_empty()); // TODO: return error - Self::with_decorators(operations, DecoratorList::new()) - } - - /// Returns a new [Span] block instantiated with the specified operations and decorators. - /// - /// # Errors (TODO) - /// Returns an error if: - /// - `operations` vector is empty. - /// - `operations` vector contains any number of system operations. - pub fn with_decorators(operations: Vec, decorators: DecoratorList) -> Self { - assert!(!operations.is_empty()); // TODO: return error - - // validate decorators list (only in debug mode) - #[cfg(debug_assertions)] - validate_decorators(&operations, &decorators); - - let (op_batches, hash) = batch_ops(operations); - Self { - op_batches, - hash, - decorators, - } - } - - // PUBLIC ACCESSORS - // -------------------------------------------------------------------------------------------- - - /// Returns a hash of this code block. - pub fn hash(&self) -> Digest { - self.hash - } - - /// Returns list of operation batches contained in this span block. - pub fn op_batches(&self) -> &[OpBatch] { - &self.op_batches - } - - // SPAN MUTATORS - // -------------------------------------------------------------------------------------------- - - /// Returns a new [Span] block instantiated with operations from this block repeated the - /// specified number of times. - #[must_use] - pub fn replicate(&self, num_copies: usize) -> Self { - let own_ops = self.get_ops(); - let own_decorators = &self.decorators; - let mut ops = Vec::with_capacity(own_ops.len() * num_copies); - let mut decorators = DecoratorList::new(); - - for i in 0..num_copies { - // replicate decorators of a span block - for decorator in own_decorators { - decorators.push((own_ops.len() * i + decorator.0, decorator.1.clone())) - } - ops.extend_from_slice(&own_ops); - } - Self::with_decorators(ops, decorators) - } - - /// Returns a list of decorators in this span block - pub fn decorators(&self) -> &DecoratorList { - &self.decorators - } - - /// Returns a [DecoratorIterator] which allows us to iterate through the decorator list of this - /// span block while executing operation batches of this span block - pub fn decorator_iter(&self) -> DecoratorIterator { - DecoratorIterator::new(&self.decorators) - } - - // HELPER METHODS - // -------------------------------------------------------------------------------------------- - - /// Returns a list of operations contained in this span block. - fn get_ops(&self) -> Vec { - let mut ops = Vec::with_capacity(self.op_batches.len() * MAX_OPS_PER_BATCH); - for batch in self.op_batches.iter() { - ops.extend_from_slice(&batch.ops); - } - ops - } -} - -impl crate::prettier::PrettyPrint for Span { - fn render(&self) -> crate::prettier::Document { - use crate::prettier::*; - - // e.g. `span a b c end` - let single_line = const_text("span") - + const_text(" ") - + self - .op_batches - .iter() - .flat_map(|batch| batch.ops.iter()) - .map(|p| p.render()) - .reduce(|acc, doc| acc + const_text(" ") + doc) - .unwrap_or_default() - + const_text(" ") - + const_text("end"); - - // e.g. ` - // span - // a - // b - // c - // end - // ` - let multi_line = indent( - 4, - const_text("span") - + nl() - + self - .op_batches - .iter() - .flat_map(|batch| batch.ops.iter()) - .map(|p| p.render()) - .reduce(|acc, doc| acc + nl() + doc) - .unwrap_or_default(), - ) + nl() - + const_text("end"); - - single_line | multi_line - } -} - -impl fmt::Display for Span { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use crate::prettier::PrettyPrint; - self.pretty_print(f) - } -} - -// OPERATION BATCH -// ================================================================================================ - -/// A batch of operations in a [Span] block. -/// -/// An operation batch consists of up to 8 operation groups, with each group containing up to 9 -/// operations or a single immediate value. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct OpBatch { - ops: Vec, - groups: [Felt; BATCH_SIZE], - op_counts: [usize; BATCH_SIZE], - num_groups: usize, -} - -impl OpBatch { - /// Returns a list of operations contained in this batch. - pub fn ops(&self) -> &[Operation] { - &self.ops - } - - /// Returns a list of operation groups contained in this batch. - /// - /// Each group is represented by a single field element. - pub fn groups(&self) -> &[Felt; BATCH_SIZE] { - &self.groups - } - - /// Returns the number of non-decorator operations for each operation group. - /// - /// Number of operations for groups containing immediate values is set to 0. - pub fn op_counts(&self) -> &[usize; BATCH_SIZE] { - &self.op_counts - } - - /// Returns the number of groups in this batch. - pub fn num_groups(&self) -> usize { - self.num_groups - } -} - -/// An accumulator used in construction of operation batches. -struct OpBatchAccumulator { - /// A list of operations in this batch, including decorators. - ops: Vec, - /// Values of operation groups, including immediate values. - groups: [Felt; BATCH_SIZE], - /// Number of non-decorator operations in each operation group. Operation count for groups - /// with immediate values is set to 0. - op_counts: [usize; BATCH_SIZE], - /// Value of the currently active op group. - group: u64, - /// Index of the next opcode in the current group. - op_idx: usize, - /// index of the current group in the batch. - group_idx: usize, - // Index of the next free group in the batch. - next_group_idx: usize, -} - -impl OpBatchAccumulator { - /// Returns a blank [OpBatchAccumulator]. - pub fn new() -> Self { - Self { - ops: Vec::new(), - groups: [ZERO; BATCH_SIZE], - op_counts: [0; BATCH_SIZE], - group: 0, - op_idx: 0, - group_idx: 0, - next_group_idx: 1, - } - } - - /// Returns true if this accumulator does not contain any operations. - pub fn is_empty(&self) -> bool { - self.ops.is_empty() - } - - /// Returns true if this accumulator can accept the specified operation. - /// - /// An accumulator may not be able accept an operation for the following reasons: - /// - There is no more space in the underlying batch (e.g., the 8th group of the batch already - /// contains 9 operations). - /// - There is no space for the immediate value carried by the operation (e.g., the 8th group is - /// only partially full, but we are trying to add a PUSH operation). - /// - The alignment rules require that the operation overflows into the next group, and if this - /// happens, there will be no space for the operation or its immediate value. - pub fn can_accept_op(&self, op: Operation) -> bool { - if op.imm_value().is_some() { - // an operation carrying an immediate value cannot be the last one in a group; so, we - // check if we need to move the operation to the next group. in either case, we need - // to make sure there is enough space for the immediate value as well. - if self.op_idx < GROUP_SIZE - 1 { - self.next_group_idx < BATCH_SIZE - } else { - self.next_group_idx + 1 < BATCH_SIZE - } - } else { - // check if there is space for the operation in the current group, or if there isn't, - // whether we can add another group - self.op_idx < GROUP_SIZE || self.next_group_idx < BATCH_SIZE - } - } - - /// Adds the specified operation to this accumulator. It is expected that the specified - /// operation is not a decorator and that (can_accept_op())[OpBatchAccumulator::can_accept_op] - /// is called before this function to make sure that the specified operation can be added to - /// the accumulator. - pub fn add_op(&mut self, op: Operation) { - // if the group is full, finalize it and start a new group - if self.op_idx == GROUP_SIZE { - self.finalize_op_group(); - } - - // for operations with immediate values, we need to do a few more things - if let Some(imm) = op.imm_value() { - // since an operation with an immediate value cannot be the last one in a group, if - // the operation would be the last one in the group, we need to start a new group - if self.op_idx == GROUP_SIZE - 1 { - self.finalize_op_group(); - } - - // save the immediate value at the next group index and advance the next group pointer - self.groups[self.next_group_idx] = imm; - self.next_group_idx += 1; - } - - // add the opcode to the group and increment the op index pointer - let opcode = op.op_code() as u64; - self.group |= opcode << (Operation::OP_BITS * self.op_idx); - self.ops.push(op); - self.op_idx += 1; - } - - /// Convert the accumulator into an [OpBatch]. - pub fn into_batch(mut self) -> OpBatch { - // make sure the last group gets added to the group array; we also check the op_idx to - // handle the case when a group contains a single NOOP operation. - if self.group != 0 || self.op_idx != 0 { - self.groups[self.group_idx] = Felt::new(self.group); - self.op_counts[self.group_idx] = self.op_idx; - } - - OpBatch { - ops: self.ops, - groups: self.groups, - op_counts: self.op_counts, - num_groups: self.next_group_idx, - } - } - - // HELPER METHODS - // -------------------------------------------------------------------------------------------- - - /// Saves the current group into the group array, advances current and next group pointers, - /// and resets group content. - fn finalize_op_group(&mut self) { - self.groups[self.group_idx] = Felt::new(self.group); - self.op_counts[self.group_idx] = self.op_idx; - - self.group_idx = self.next_group_idx; - self.next_group_idx = self.group_idx + 1; - - self.op_idx = 0; - self.group = 0; - } -} - -// HELPER FUNCTIONS -// ================================================================================================ - -/// Groups the provided operations into batches as described in the docs for this module (i.e., -/// up to 9 operations per group, and 8 groups per batch). -/// -/// After the operations have been grouped, computes the hash of the block. -fn batch_ops(ops: Vec) -> (Vec, Digest) { - let mut batch_acc = OpBatchAccumulator::new(); - let mut batches = Vec::::new(); - let mut batch_groups = Vec::<[Felt; BATCH_SIZE]>::new(); - - for op in ops { - // if the operation cannot be accepted into the current accumulator, add the contents of - // the accumulator to the list of batches and start a new accumulator - if !batch_acc.can_accept_op(op) { - let batch = batch_acc.into_batch(); - batch_acc = OpBatchAccumulator::new(); - - batch_groups.push(*batch.groups()); - batches.push(batch); - } - - // add the operation to the accumulator - batch_acc.add_op(op); - } - - // make sure we finished processing the last batch - if !batch_acc.is_empty() { - let batch = batch_acc.into_batch(); - batch_groups.push(*batch.groups()); - batches.push(batch); - } - - // compute the hash of all operation groups - let op_groups = &flatten_slice_elements(&batch_groups); - let hash = hasher::hash_elements(op_groups); - - (batches, hash) -} - -/// Returns the total number of operation groups in a span defined by the provides list of -/// operation batches. -/// -/// Then number of operation groups is computed as follows: -/// - For all batches but the last one we set the number of groups to 8, regardless of the actual -/// number of groups in the batch. The reason for this is that when operation batches are -/// concatenated together each batch contributes 8 elements to the hash. -/// - For the last batch, we take the number of actual batches and round it up to the next power of -/// two. The reason for rounding is that the VM always executes a number of operation groups which -/// is a power of two. -pub fn get_span_op_group_count(op_batches: &[OpBatch]) -> usize { - let last_batch_num_groups = op_batches.last().expect("no last group").num_groups(); - (op_batches.len() - 1) * BATCH_SIZE + last_batch_num_groups.next_power_of_two() -} - -/// Checks if a given decorators list is valid (only checked in debug mode) -/// - Assert the decorator list is in ascending order. -/// - Assert the last op index in decorator list is less than or equal to the number of operations. -#[cfg(debug_assertions)] -fn validate_decorators(operations: &[Operation], decorators: &DecoratorList) { - if !decorators.is_empty() { - // check if decorator list is sorted - for i in 0..(decorators.len() - 1) { - debug_assert!(decorators[i + 1].0 >= decorators[i].0, "unsorted decorators list"); - } - // assert the last index in decorator list is less than operations vector length - debug_assert!( - operations.len() >= decorators.last().expect("empty decorators list").0, - "last op index in decorator list should be less than or equal to the number of ops" - ); - } -} - -// TESTS -// ================================================================================================ - -#[cfg(test)] -mod tests { - use super::{hasher, Felt, Operation, BATCH_SIZE, ZERO}; - use crate::ONE; - - #[test] - fn batch_ops() { - // --- one operation ---------------------------------------------------------------------- - let ops = vec![Operation::Add]; - let (batches, hash) = super::batch_ops(ops.clone()); - assert_eq!(1, batches.len()); - - let batch = &batches[0]; - assert_eq!(ops, batch.ops); - assert_eq!(1, batch.num_groups()); - - let mut batch_groups = [ZERO; BATCH_SIZE]; - batch_groups[0] = build_group(&ops); - - assert_eq!(batch_groups, batch.groups); - assert_eq!([1_usize, 0, 0, 0, 0, 0, 0, 0], batch.op_counts); - assert_eq!(hasher::hash_elements(&batch_groups), hash); - - // --- two operations --------------------------------------------------------------------- - let ops = vec![Operation::Add, Operation::Mul]; - let (batches, hash) = super::batch_ops(ops.clone()); - assert_eq!(1, batches.len()); - - let batch = &batches[0]; - assert_eq!(ops, batch.ops); - assert_eq!(1, batch.num_groups()); - - let mut batch_groups = [ZERO; BATCH_SIZE]; - batch_groups[0] = build_group(&ops); - - assert_eq!(batch_groups, batch.groups); - assert_eq!([2_usize, 0, 0, 0, 0, 0, 0, 0], batch.op_counts); - assert_eq!(hasher::hash_elements(&batch_groups), hash); - - // --- one group with one immediate value ------------------------------------------------- - let ops = vec![Operation::Add, Operation::Push(Felt::new(12345678))]; - let (batches, hash) = super::batch_ops(ops.clone()); - assert_eq!(1, batches.len()); - - let batch = &batches[0]; - assert_eq!(ops, batch.ops); - assert_eq!(2, batch.num_groups()); - - let mut batch_groups = [ZERO; BATCH_SIZE]; - batch_groups[0] = build_group(&ops); - batch_groups[1] = Felt::new(12345678); - - assert_eq!(batch_groups, batch.groups); - assert_eq!([2_usize, 0, 0, 0, 0, 0, 0, 0], batch.op_counts); - assert_eq!(hasher::hash_elements(&batch_groups), hash); - - // --- one group with 7 immediate values -------------------------------------------------- - let ops = vec![ - Operation::Push(ONE), - Operation::Push(Felt::new(2)), - Operation::Push(Felt::new(3)), - Operation::Push(Felt::new(4)), - Operation::Push(Felt::new(5)), - Operation::Push(Felt::new(6)), - Operation::Push(Felt::new(7)), - Operation::Add, - ]; - let (batches, hash) = super::batch_ops(ops.clone()); - assert_eq!(1, batches.len()); - - let batch = &batches[0]; - assert_eq!(ops, batch.ops); - assert_eq!(8, batch.num_groups()); - - let batch_groups = [ - build_group(&ops), - ONE, - Felt::new(2), - Felt::new(3), - Felt::new(4), - Felt::new(5), - Felt::new(6), - Felt::new(7), - ]; - - assert_eq!(batch_groups, batch.groups); - assert_eq!([8_usize, 0, 0, 0, 0, 0, 0, 0], batch.op_counts); - assert_eq!(hasher::hash_elements(&batch_groups), hash); - - // --- two groups with 7 immediate values; the last push overflows to the second batch ---- - let ops = vec![ - Operation::Add, - Operation::Mul, - Operation::Push(ONE), - Operation::Push(Felt::new(2)), - Operation::Push(Felt::new(3)), - Operation::Push(Felt::new(4)), - Operation::Push(Felt::new(5)), - Operation::Push(Felt::new(6)), - Operation::Add, - Operation::Push(Felt::new(7)), - ]; - let (batches, hash) = super::batch_ops(ops.clone()); - assert_eq!(2, batches.len()); - - let batch0 = &batches[0]; - assert_eq!(ops[..9], batch0.ops); - assert_eq!(7, batch0.num_groups()); - - let batch0_groups = [ - build_group(&ops[..9]), - ONE, - Felt::new(2), - Felt::new(3), - Felt::new(4), - Felt::new(5), - Felt::new(6), - ZERO, - ]; - - assert_eq!(batch0_groups, batch0.groups); - assert_eq!([9_usize, 0, 0, 0, 0, 0, 0, 0], batch0.op_counts); - - let batch1 = &batches[1]; - assert_eq!(vec![ops[9]], batch1.ops); - assert_eq!(2, batch1.num_groups()); - - let mut batch1_groups = [ZERO; BATCH_SIZE]; - batch1_groups[0] = build_group(&[ops[9]]); - batch1_groups[1] = Felt::new(7); - - assert_eq!([1_usize, 0, 0, 0, 0, 0, 0, 0], batch1.op_counts); - assert_eq!(batch1_groups, batch1.groups); - - let all_groups = [batch0_groups, batch1_groups].concat(); - assert_eq!(hasher::hash_elements(&all_groups), hash); - - // --- immediate values in-between groups ------------------------------------------------- - let ops = vec![ - Operation::Add, - Operation::Mul, - Operation::Add, - Operation::Push(Felt::new(7)), - Operation::Add, - Operation::Add, - Operation::Push(Felt::new(11)), - Operation::Mul, - Operation::Mul, - Operation::Add, - ]; - - let (batches, hash) = super::batch_ops(ops.clone()); - assert_eq!(1, batches.len()); - - let batch = &batches[0]; - assert_eq!(ops, batch.ops); - assert_eq!(4, batch.num_groups()); - - let batch_groups = [ - build_group(&ops[..9]), - Felt::new(7), - Felt::new(11), - build_group(&ops[9..]), - ZERO, - ZERO, - ZERO, - ZERO, - ]; - - assert_eq!([9_usize, 0, 0, 1, 0, 0, 0, 0], batch.op_counts); - assert_eq!(batch_groups, batch.groups); - assert_eq!(hasher::hash_elements(&batch_groups), hash); - - // --- push at the end of a group is moved into the next group ---------------------------- - let ops = vec![ - Operation::Add, - Operation::Mul, - Operation::Add, - Operation::Add, - Operation::Add, - Operation::Mul, - Operation::Mul, - Operation::Add, - Operation::Push(Felt::new(11)), - ]; - let (batches, hash) = super::batch_ops(ops.clone()); - assert_eq!(1, batches.len()); - - let batch = &batches[0]; - assert_eq!(ops, batch.ops); - assert_eq!(3, batch.num_groups()); - - let batch_groups = [ - build_group(&ops[..8]), - build_group(&[ops[8]]), - Felt::new(11), - ZERO, - ZERO, - ZERO, - ZERO, - ZERO, - ]; - - assert_eq!(batch_groups, batch.groups); - assert_eq!([8_usize, 1, 0, 0, 0, 0, 0, 0], batch.op_counts); - assert_eq!(hasher::hash_elements(&batch_groups), hash); - - // --- push at the end of a group is moved into the next group ---------------------------- - let ops = vec![ - Operation::Add, - Operation::Mul, - Operation::Add, - Operation::Add, - Operation::Add, - Operation::Mul, - Operation::Mul, - Operation::Push(ONE), - Operation::Push(Felt::new(2)), - ]; - let (batches, hash) = super::batch_ops(ops.clone()); - assert_eq!(1, batches.len()); - - let batch = &batches[0]; - assert_eq!(ops, batch.ops); - assert_eq!(4, batch.num_groups()); - - let batch_groups = [ - build_group(&ops[..8]), - ONE, - build_group(&[ops[8]]), - Felt::new(2), - ZERO, - ZERO, - ZERO, - ZERO, - ]; - - assert_eq!(batch_groups, batch.groups); - assert_eq!([8_usize, 0, 1, 0, 0, 0, 0, 0], batch.op_counts); - assert_eq!(hasher::hash_elements(&batch_groups), hash); - - // --- push at the end of the 7th group overflows to the next batch ----------------------- - let ops = vec![ - Operation::Add, - Operation::Mul, - Operation::Push(ONE), - Operation::Push(Felt::new(2)), - Operation::Push(Felt::new(3)), - Operation::Push(Felt::new(4)), - Operation::Push(Felt::new(5)), - Operation::Add, - Operation::Mul, - Operation::Add, - Operation::Mul, - Operation::Add, - Operation::Mul, - Operation::Add, - Operation::Mul, - Operation::Add, - Operation::Mul, - Operation::Push(Felt::new(6)), - Operation::Pad, - ]; - - let (batches, hash) = super::batch_ops(ops.clone()); - assert_eq!(2, batches.len()); - - let batch0 = &batches[0]; - assert_eq!(ops[..17], batch0.ops); - assert_eq!(7, batch0.num_groups()); - - let batch0_groups = [ - build_group(&ops[..9]), - ONE, - Felt::new(2), - Felt::new(3), - Felt::new(4), - Felt::new(5), - build_group(&ops[9..17]), - ZERO, - ]; - - assert_eq!(batch0_groups, batch0.groups); - assert_eq!([9_usize, 0, 0, 0, 0, 0, 8, 0], batch0.op_counts); - - let batch1 = &batches[1]; - assert_eq!(ops[17..], batch1.ops); - assert_eq!(2, batch1.num_groups()); - - let batch1_groups = - [build_group(&ops[17..]), Felt::new(6), ZERO, ZERO, ZERO, ZERO, ZERO, ZERO]; - assert_eq!(batch1_groups, batch1.groups); - assert_eq!([2_usize, 0, 0, 0, 0, 0, 0, 0], batch1.op_counts); - - let all_groups = [batch0_groups, batch1_groups].concat(); - assert_eq!(hasher::hash_elements(&all_groups), hash); - } - - // TEST HELPERS - // -------------------------------------------------------------------------------------------- - - fn build_group(ops: &[Operation]) -> Felt { - let mut group = 0u64; - for (i, op) in ops.iter().enumerate() { - group |= (op.op_code() as u64) << (Operation::OP_BITS * i); - } - Felt::new(group) - } -} diff --git a/core/src/program/blocks/split_block.rs b/core/src/program/blocks/split_block.rs deleted file mode 100644 index 738f3ceffe..0000000000 --- a/core/src/program/blocks/split_block.rs +++ /dev/null @@ -1,77 +0,0 @@ -use alloc::boxed::Box; -use core::fmt; - -use super::{hasher, CodeBlock, Digest, Felt, Operation}; - -// SPLIT BLOCK -// ================================================================================================ -/// Block for conditional execution. -/// -/// Executes the first branch if the top of the stack is `1` or the second branch if `0`. Fails if -/// the top of the stack is neither `1` or `0` or if the branch execution fails. -/// -/// The hash of a split block is: -/// -/// > hash(true_branch_hash || false_branch_hash, domain=SPLIT_DOMAIN) -/// -/// Where `true_branch_hash` and `false_branch_hash` are 4 field elements (256 bits) each. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Split { - branches: Box<[CodeBlock; 2]>, - hash: Digest, -} - -impl Split { - // CONSTANTS - // -------------------------------------------------------------------------------------------- - /// The domain of the split block (used for control block hashing). - pub const DOMAIN: Felt = Felt::new(Operation::Split.op_code() as u64); - - // CONSTRUCTOR - // -------------------------------------------------------------------------------------------- - /// Returns a new [Split] block instantiated with the specified true and false branches. - pub fn new(t_branch: CodeBlock, f_branch: CodeBlock) -> Self { - let hash = hasher::merge_in_domain(&[t_branch.hash(), f_branch.hash()], Self::DOMAIN); - Self { - branches: Box::new([t_branch, f_branch]), - hash, - } - } - - // PUBLIC ACCESSORS - // -------------------------------------------------------------------------------------------- - - /// Returns a hash of this code block. - pub fn hash(&self) -> Digest { - self.hash - } - - /// Returns a reference to the code block which is to be executed when the top of the stack - /// is `1`. - pub fn on_true(&self) -> &CodeBlock { - &self.branches[0] - } - - /// Returns a reference to the code block which is to be executed when the top of the stack - /// is `0`. - pub fn on_false(&self) -> &CodeBlock { - &self.branches[1] - } -} - -impl crate::prettier::PrettyPrint for Split { - fn render(&self) -> crate::prettier::Document { - use crate::prettier::*; - - let mut doc = indent(4, const_text("if.true") + nl() + self.branches[0].render()) + nl(); - doc += indent(4, const_text("else") + nl() + self.branches[1].render()); - doc + nl() + const_text("end") - } -} - -impl fmt::Display for Split { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use crate::prettier::PrettyPrint; - self.pretty_print(f) - } -} diff --git a/core/src/program/info.rs b/core/src/program/info.rs deleted file mode 100644 index 083fcb0c23..0000000000 --- a/core/src/program/info.rs +++ /dev/null @@ -1,106 +0,0 @@ -use super::{ - super::{ToElements, WORD_SIZE}, - ByteReader, ByteWriter, Deserializable, DeserializationError, Digest, Felt, Kernel, Program, - Serializable, -}; -use alloc::vec::Vec; - -// PROGRAM INFO -// ================================================================================================ - -/// A program information set consisting of its MAST root and set of kernel procedure roots used -/// for its compilation. -/// -/// This will be used as public inputs of the proof so we bind its verification to the kernel and -/// root used to execute the program. This way, we extend the correctness of the proof to the -/// security guarantees provided by the kernel. We also allow the user to easily prove the -/// membership of a given kernel procedure for a given proof, without compromising its -/// zero-knowledge properties. -#[derive(Debug, Clone, Default, PartialEq, Eq)] -pub struct ProgramInfo { - program_hash: Digest, - kernel: Kernel, -} - -impl ProgramInfo { - // CONSTRUCTORS - // -------------------------------------------------------------------------------------------- - - /// Creates a new instance of a program info. - pub const fn new(program_hash: Digest, kernel: Kernel) -> Self { - Self { - program_hash, - kernel, - } - } - - // PUBLIC ACCESSORS - // -------------------------------------------------------------------------------------------- - - /// Returns the program hash computed from its code block root. - pub const fn program_hash(&self) -> &Digest { - &self.program_hash - } - - /// Returns the program kernel used during the compilation. - pub const fn kernel(&self) -> &Kernel { - &self.kernel - } - - /// Returns the list of procedures of the kernel used during the compilation. - pub fn kernel_procedures(&self) -> &[Digest] { - self.kernel.proc_hashes() - } -} - -impl From for ProgramInfo { - fn from(program: Program) -> Self { - let Program { root, kernel, .. } = program; - let program_hash = root.hash(); - - Self { - program_hash, - kernel, - } - } -} - -// SERIALIZATION -// ------------------------------------------------------------------------------------------------ - -impl Serializable for ProgramInfo { - fn write_into(&self, target: &mut W) { - self.program_hash.write_into(target); - self.kernel.write_into(target); - } -} - -impl Deserializable for ProgramInfo { - fn read_from(source: &mut R) -> Result { - let program_hash = source.read()?; - let kernel = source.read()?; - Ok(Self { - program_hash, - kernel, - }) - } -} - -// TO ELEMENTS -// ------------------------------------------------------------------------------------------------ - -impl ToElements for ProgramInfo { - fn to_elements(&self) -> Vec { - let num_kernel_proc_elements = self.kernel.proc_hashes().len() * WORD_SIZE; - let mut result = Vec::with_capacity(WORD_SIZE + num_kernel_proc_elements); - - // append program hash elements - result.extend_from_slice(self.program_hash.as_elements()); - - // append kernel procedure hash elements - for proc_hash in self.kernel.proc_hashes() { - result.extend_from_slice(proc_hash.as_elements()); - } - result - } -} diff --git a/core/src/program/mod.rs b/core/src/program/mod.rs deleted file mode 100644 index c343b0ebcb..0000000000 --- a/core/src/program/mod.rs +++ /dev/null @@ -1,195 +0,0 @@ -use super::{ - chiplets::hasher::{self, Digest}, - errors, Felt, Operation, -}; -use crate::utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; -use alloc::{collections::BTreeMap, vec::Vec}; -use core::fmt; - -pub mod blocks; -use blocks::CodeBlock; - -mod info; -pub use info::ProgramInfo; - -#[cfg(test)] -mod tests; - -// PROGRAM -// ================================================================================================ -/// A program which can be executed by the VM. -/// -/// A program is described by a Merkelized Abstract Syntax Tree (MAST), where each node is a -/// [CodeBlock]. Internal nodes describe control flow semantics of the program, while leaf nodes -/// contain linear sequences of instructions which contain no control flow. -#[derive(Clone, Debug)] -pub struct Program { - root: CodeBlock, - kernel: Kernel, - cb_table: CodeBlockTable, -} - -impl Program { - // CONSTRUCTORS - // -------------------------------------------------------------------------------------------- - /// Instantiates a new [Program] from the specified code block. - pub fn new(root: CodeBlock) -> Self { - Self::with_kernel(root, Kernel::default(), CodeBlockTable::default()) - } - - /// Instantiates a new [Program] from the specified code block and associated code block table. - pub fn with_kernel(root: CodeBlock, kernel: Kernel, cb_table: CodeBlockTable) -> Self { - Self { - root, - kernel, - cb_table, - } - } - - // PUBLIC ACCESSORS - // -------------------------------------------------------------------------------------------- - - /// Returns the root code block of this program. - pub fn root(&self) -> &CodeBlock { - &self.root - } - - /// Returns a hash of this program. - pub fn hash(&self) -> Digest { - self.root.hash() - } - - /// Returns a kernel for this program. - pub fn kernel(&self) -> &Kernel { - &self.kernel - } - - /// Returns code block table for this program. - pub fn cb_table(&self) -> &CodeBlockTable { - &self.cb_table - } -} - -impl crate::prettier::PrettyPrint for Program { - fn render(&self) -> crate::prettier::Document { - use crate::prettier::*; - - indent(4, const_text("begin") + nl() + self.root.render()) + nl() + const_text("end") - } -} - -impl fmt::Display for Program { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use crate::prettier::PrettyPrint; - self.pretty_print(f) - } -} - -// CODE BLOCK TABLE -// ================================================================================================ - -/// A map of code block hashes to their underlying code blocks. -/// -/// This table is used to hold code blocks which are referenced from the program MAST but are -/// actually not a part of the MAST itself. Thus, for example, multiple nodes in the MAST can -/// reference the same code block in the table. -#[derive(Clone, Debug, Default)] -pub struct CodeBlockTable(BTreeMap<[u8; 32], CodeBlock>); - -impl CodeBlockTable { - /// Returns a code block for the specified hash, or None if the code block is not present - /// in this table. - pub fn get(&self, hash: Digest) -> Option<&CodeBlock> { - let key: [u8; 32] = hash.into(); - self.0.get(&key) - } - - /// Returns true if a code block with the specified hash is present in this table. - pub fn has(&self, hash: Digest) -> bool { - let key: [u8; 32] = hash.into(); - self.0.contains_key(&key) - } - - /// Inserts the provided code block into this table. - pub fn insert(&mut self, block: CodeBlock) { - let key: [u8; 32] = block.hash().into(); - self.0.insert(key, block); - } - - /// Returns true if this code block table is empty. - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } -} - -// KERNEL -// ================================================================================================ - -/// A list of procedure hashes defining a VM kernel. -/// -/// The internally-stored list always has a consistent order, regardless of the order of procedure -/// list used to instantiate a kernel. -#[derive(Debug, Clone, Default, PartialEq, Eq)] -pub struct Kernel(Vec); - -pub const MAX_KERNEL_PROCEDURES: usize = u8::MAX as usize; - -impl Kernel { - /// Returns a new [Kernel] instantiated with the specified procedure hashes. - pub fn new(proc_hashes: &[Digest]) -> Result { - if proc_hashes.len() > MAX_KERNEL_PROCEDURES { - Err(errors::KernelError::TooManyProcedures(MAX_KERNEL_PROCEDURES, proc_hashes.len())) - } else { - let mut hashes = proc_hashes.to_vec(); - hashes.sort_by_key(|v| v.as_bytes()); // ensure consistent order - - let duplicated = hashes.windows(2).any(|data| data[0] == data[1]); - - if duplicated { - Err(errors::KernelError::DuplicatedProcedures) - } else { - Ok(Self(hashes)) - } - } - } - - /// Returns true if this kernel does not contain any procedures. - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } - - /// Returns true if a procedure with the specified hash belongs to this kernel. - pub fn contains_proc(&self, proc_hash: Digest) -> bool { - // linear search here is OK because we expect the kernels to have a relatively small number - // of procedures (e.g., under 100) - self.0.iter().any(|&h| h == proc_hash) - } - - /// Returns a list of procedure hashes contained in this kernel. - pub fn proc_hashes(&self) -> &[Digest] { - &self.0 - } -} - -// this is required by AIR as public inputs will be serialized with the proof -impl Serializable for Kernel { - fn write_into(&self, target: &mut W) { - debug_assert!(self.0.len() <= MAX_KERNEL_PROCEDURES); - target.write_usize(self.0.len()); - target.write_many(&self.0) - } -} - -impl Deserializable for Kernel { - fn read_from(source: &mut R) -> Result { - let len = source.read_usize()?; - if len > MAX_KERNEL_PROCEDURES { - return Err(DeserializationError::InvalidValue(format!( - "Number of kernel procedures can not be more than {}, but {} was provided", - MAX_KERNEL_PROCEDURES, len - ))); - } - let kernel = source.read_many::(len)?; - Ok(Self(kernel)) - } -} diff --git a/core/src/stack/outputs.rs b/core/src/stack/outputs.rs index 67ad4f6d8b..855afa0e10 100644 --- a/core/src/stack/outputs.rs +++ b/core/src/stack/outputs.rs @@ -69,10 +69,7 @@ impl StackOutputs { stack.resize(STACK_TOP_SIZE, ZERO); } - Ok(Self { - stack, - overflow_addrs, - }) + Ok(Self { stack, overflow_addrs }) } /// Attempts to create [StackOutputs] struct from the provided stack elements and overflow @@ -240,9 +237,6 @@ impl Deserializable for StackOutputs { let count = get_overflow_addrs_len(stack.len()); let overflow_addrs = source.read_many::(count)?; - Ok(Self { - stack, - overflow_addrs, - }) + Ok(Self { stack, overflow_addrs }) } } diff --git a/core/src/utils/mod.rs b/core/src/utils/mod.rs index 823cd48612..f610ed7f84 100644 --- a/core/src/utils/mod.rs +++ b/core/src/utils/mod.rs @@ -1,4 +1,5 @@ -use crate::Felt; +pub mod sync; + use alloc::vec::Vec; use core::{ fmt::Debug, @@ -7,7 +8,6 @@ use core::{ // RE-EXPORTS // ================================================================================================ - pub use miden_crypto::utils::{ collections, uninit_vector, ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable, SliceReader, @@ -16,6 +16,8 @@ pub use winter_utils::group_slice_elements; #[cfg(feature = "std")] pub use winter_utils::ReadAdapter; +use crate::Felt; + pub mod math { pub use math::batch_inversion; } @@ -78,10 +80,7 @@ impl PushMany for Vec { /// Returns a [Range] initialized with the specified `start` and with `end` set to `start` + `len`. pub const fn range(start: usize, len: usize) -> Range { - Range { - start, - end: start + len, - } + Range { start, end: start + len } } /// Converts and parses a [Bound] into an included u64 value. @@ -98,7 +97,7 @@ where } else { u64::MAX } - } + }, } } diff --git a/core/src/utils/sync/mod.rs b/core/src/utils/sync/mod.rs new file mode 100644 index 0000000000..ef68428f86 --- /dev/null +++ b/core/src/utils/sync/mod.rs @@ -0,0 +1,12 @@ +pub mod racy_lock; +pub mod rw_lock; + +#[cfg(feature = "std")] +pub use std::sync::LazyLock; + +#[cfg(feature = "std")] +pub use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; +#[cfg(not(feature = "std"))] +pub use racy_lock::RacyLock as LazyLock; +#[cfg(not(feature = "std"))] +pub use rw_lock::{RwLock, RwLockReadGuard, RwLockWriteGuard}; diff --git a/core/src/utils/sync/racy_lock.rs b/core/src/utils/sync/racy_lock.rs new file mode 100644 index 0000000000..157cd41ed0 --- /dev/null +++ b/core/src/utils/sync/racy_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/core/src/utils/sync/rw_lock.rs b/core/src/utils/sync/rw_lock.rs new file mode 100644 index 0000000000..5a5cc5a722 --- /dev/null +++ b/core/src/utils/sync/rw_lock.rs @@ -0,0 +1,305 @@ +#[cfg(not(loom))] +use core::{ + hint, + sync::atomic::{AtomicUsize, Ordering}, +}; + +use lock_api::RawRwLock; +#[cfg(loom)] +use loom::{ + hint, + sync::atomic::{AtomicUsize, Ordering}, +}; + +/// 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 { + #[cfg(not(loom))] + pub const fn new() -> Self { + Self { + state: AtomicUsize::new(0), + writer_wake_counter: AtomicUsize::new(0), + } + } + + #[cfg(loom)] + pub fn new() -> Self { + Self { + state: AtomicUsize::new(0), + writer_wake_counter: AtomicUsize::new(0), + } + } +} + +unsafe impl RawRwLock for Spinlock { + #[cfg(loom)] + const INIT: Spinlock = unimplemented!(); + + #[cfg(not(loom))] + // 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(); + + type GuardMarker = lock_api::GuardSend; + + /// 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); + } +} + +#[cfg(all(loom, test))] +mod test { + use alloc::vec::Vec; + + use loom::{model::Builder, sync::Arc}; + + use super::rwlock::{RwLock, Spinlock}; + + #[test] + fn test_rwlock_loom() { + let mut builder = Builder::default(); + builder.max_duration = Some(std::time::Duration::from_secs(60)); + builder.log = true; + builder.check(|| { + let raw_rwlock = Spinlock::new(); + let n = Arc::new(RwLock::from_raw(raw_rwlock, 0usize)); + let mut readers = Vec::new(); + let mut writers = Vec::new(); + + let num_readers = 2; + let num_writers = 2; + let num_iterations = 2; + + // Readers should never observe a non-zero value + for _ in 0..num_readers { + let n0 = n.clone(); + let t = loom::thread::spawn(move || { + for _ in 0..num_iterations { + let guard = n0.read(); + assert_eq!(*guard, 0); + } + }); + + readers.push(t); + } + + // Writers should never observe a non-zero value once they've + // acquired the lock, and should never observe a value > 1 + // while holding the lock + for _ in 0..num_writers { + let n0 = n.clone(); + let t = loom::thread::spawn(move || { + for _ in 0..num_iterations { + let mut guard = n0.write(); + assert_eq!(*guard, 0); + *guard += 1; + assert_eq!(*guard, 1); + *guard -= 1; + assert_eq!(*guard, 0); + } + }); + + writers.push(t); + } + + for t in readers { + t.join().unwrap(); + } + + for t in writers { + t.join().unwrap(); + } + }) + } +} diff --git a/docs/book.toml b/docs/book.toml index 0f99f192e1..cbb7fc95db 100644 --- a/docs/book.toml +++ b/docs/book.toml @@ -11,5 +11,7 @@ git-repository-url = "https://github.com/0xPolygonMiden/miden-vm/" [preprocessor.katex] after = ["links"] +[preprocessor.alerts] + [output.linkcheck] warning-policy = "ignore" diff --git a/docs/src/design/chiplets/bitwise.md b/docs/src/design/chiplets/bitwise.md index 969f91c577..792ef5a104 100644 --- a/docs/src/design/chiplets/bitwise.md +++ b/docs/src/design/chiplets/bitwise.md @@ -14,7 +14,7 @@ $$ xor(a, b) = a + b - 2 \cdot a \cdot b $$ -To compute bitwise operations for multi-bit values, we will decompose the values into individual bits, apply the operations to single bits, and then aggregate the bitwsie results into the final result. +To compute bitwise operations for multi-bit values, we will decompose the values into individual bits, apply the operations to single bits, and then aggregate the bitwise results into the final result. To perform this operation we will use a table with 12 columns, and computing a single AND or XOR operation will require 8 table rows. We will also rely on two periodic columns as shown below. diff --git a/docs/src/design/chiplets/hasher.md b/docs/src/design/chiplets/hasher.md index 7e1f75cbc0..cd851d82a2 100644 --- a/docs/src/design/chiplets/hasher.md +++ b/docs/src/design/chiplets/hasher.md @@ -397,7 +397,7 @@ The above constraint reduces to the following under various flag conditions: Note that the degree of the above constraint is $7$. #### Sibling table constraints -*Note: Although this table is described independently, it is implemented as part of the [chiplets virtual table](../chiplets/main.md#chiplets-virtual-table), which combines all virtual tables required by the any of the chiplets into a single master table.* +*Note: Although this table is described independently, it is implemented as part of the [chiplets virtual table](../chiplets/main.md#chiplets-virtual-table), which combines all virtual tables required by any of the chiplets into a single master table.* As mentioned previously, the sibling table (represented by running column $p_1$) is used to keep track of sibling nodes used during Merkle root update computations. For this computation, we need to enforce the following rules: * When computing the old Merkle root, whenever a new sibling node is absorbed into the hasher state (i.e., $f_{mv} + f_{mva} = 1$), an entry for this sibling should be included into $p_1$. diff --git a/docs/src/design/chiplets/kernel_rom.md b/docs/src/design/chiplets/kernel_rom.md index 7c101ddbb9..169157580d 100644 --- a/docs/src/design/chiplets/kernel_rom.md +++ b/docs/src/design/chiplets/kernel_rom.md @@ -70,7 +70,7 @@ $$ Thus, when $s_0 = 0$ this reduces to $b'_{chip} = b_{chip}$, but when $s_0=1$ it becomes $b'_{chip} = b_{chip} \cdot u$. ## Kernel procedure table constraints -*Note: Although this table is described independently, it is implemented as part of the [chiplets virtual table](../chiplets/main.md#chiplets-virtual-table), which combines all virtual tables required by the any of the chiplets into a single master table.* +*Note: Although this table is described independently, it is implemented as part of the [chiplets virtual table](../chiplets/main.md#chiplets-virtual-table), which combines all virtual tables required by any of the chiplets into a single master table.* This kernel procedure table keeps track of all *unique* kernel function roots. The values in this table will be updated only when the value in the address column changes. diff --git a/docs/src/design/chiplets/main.md b/docs/src/design/chiplets/main.md index a974863d85..62d4b1c8cb 100644 --- a/docs/src/design/chiplets/main.md +++ b/docs/src/design/chiplets/main.md @@ -187,7 +187,7 @@ The expected boundary values for each chiplet's portion of the virtual table mus For the sibling table to be properly constrained, the value of the running product column must be $1$ when the sibling table starts and finishes. This can be achieved by: - enforcing a boundary constraint for $vt_{chip}=1$ at the first row -- using the the following transition constraint to enforce that the value is once again $1$ at the last cycle of the hash chiplet. +- using the following transition constraint to enforce that the value is once again $1$ at the last cycle of the hash chiplet. > $$ (s'_0 - s_0) \cdot (1 - vt_{chip}) = 0 \text{ | degree} = 2 diff --git a/docs/src/design/decoder/constraints.md b/docs/src/design/decoder/constraints.md index bf12f217ca..8c38a8652b 100644 --- a/docs/src/design/decoder/constraints.md +++ b/docs/src/design/decoder/constraints.md @@ -23,7 +23,8 @@ AIR constraints for the decoder involve operations listed in the table below. Fo | `SYSCALL` | $f_{syscall}$ | 4 | Stack remains unchanged. | | `END` | $f_{end}$ | 4 | When exiting a loop block, top stack element is dropped; otherwise, the stack remains unchanged. | | `HALT` | $f_{halt}$ | 4 | Stack remains unchanged. | -| `PUSH` | $f_{push}$ | 4 | An immediate value is pushed onto the stack. | +| `PUSH` | $f_{push}$ | 5 | An immediate value is pushed onto the stack. | +| `EMIT` | $f_{emit}$ | 5 | Stack remains unchanged. | We also use the [control flow flag](../stack/op_constraints.md#control-flow-flag) $f_{ctrl}$ exposed by the VM, which is set when any one of the above control flow operations is being executed. It has degree $5$. @@ -286,10 +287,10 @@ Graphically, this looks like so: In a similar manner, we define a value representing the result of hash computation as follows: $$ -bh = \alpha_0 + \alpha_1 \cdot a + \sum_{i=0}^3(\alpha_{i+2} \cdot h_i) + \alpha_7 \cdot h_4 \text{ | degree} = 1 +bh = \alpha_0 + \alpha_1 \cdot a' + \sum_{i=0}^3(\alpha_{i+2} \cdot h_i) + \alpha_7 \cdot f_{is\_loop\_body} \text{ | degree} = 1 $$ -Note that in the above we use $a$ (block address from the current row) rather than $a'$ (block address from the next row) as we did for for values of $ch_1$ and $ch_2$. Also, note that we are not adding a flag indicating whether the block is the first child of a join block (i.e., $\alpha_6$ term is missing). It will be added later on. +Above, $f_{is\_loop\_body}$ refers to the value in the `IS_LOOP_BODY` column (already constrained to be 0 or 1), located in $h_4$. Also, note that we are not adding a flag indicating whether the block is the first child of a join block (i.e., $\alpha_6$ term is missing). It will be added later on. Using the above variables, we define row values to be added to and removed from the block hash table as follows. @@ -325,17 +326,23 @@ $$ v_{dyn} = f_{dyn} \cdot ch_{dyn} \text{ | degree} = 6 $$ -When `END` operation is executed, hash of the completed block is removed from the block hash table. However, we also need to differentiate between removing the first and the second child of a *join* block. We do this by looking at the next operation. Specifically, if the next operation is neither `END` nor `REPEAT` we know that another block is about to be executed, and thus, we have just finished executing the first child of a *join* block. Thus, if the next operation is neither `END` nor `REPEAT` we need to set the term for $\alpha_6$ coefficient to $1$ as shown below: +When the `CALL` or `SYSCALL` operation is executed, the hash of the callee is added to the block hash table. $$ -u_{end} = f_{end} \cdot (bh + \alpha_6 \cdot (1 - (f_{end}' + f_{repeat}'))) \text{ | } \text{degree} = 8 +v_{call\_or\_syscall} = (f_{call} + f_{syscall}) \cdot ch_1 \text{ | degree} = 5 +$$ + +When `END` operation is executed, hash of the completed block is removed from the block hash table. However, we also need to differentiate between removing the first and the second child of a *join* block. We do this by looking at the next operation. Specifically, if the next operation is neither `END` nor `REPEAT` nor `HALT`, we know that another block is about to be executed, and thus, we have just finished executing the first child of a *join* block. Thus, if the next operation is neither `END` nor `REPEAT` nor `HALT` we need to set the term for $\alpha_6$ coefficient to $1$ as shown below: + +$$ +u_{end} = f_{end} \cdot (bh + \alpha_6 \cdot (1 - (f_{end}' + f_{repeat}' + f_{halt}'))) \text{ | } \text{degree} = 8 $$ Using the above definitions, we can describe the constraint for updating the block hash table as follows: > $$ p_2' \cdot (u_{end} + 1 - f_{end}) = \\ -p_2 \cdot (v_{join} + v_{split} + v_{loop} + v_{repeat} + v_{dyn} + 1 - (f_{join} + f_{split} + f_{loop} + f_{repeat} + f_{dyn})) +p_2 \cdot (v_{join} + v_{split} + v_{loop} + v_{repeat} + v_{dyn} + v_{call\_or\_syscall} + 1 - (f_{join} + f_{split} + f_{loop} + f_{repeat} + f_{dyn} + f_{call} + f_{syscall})) $$ We need to add $1$ and subtract the sum of the relevant operation flags from each side to ensure that when none of the flags is set to $1$, the above constraint reduces to $p_2' = p_2$. @@ -404,9 +411,9 @@ In the beginning of a span block (i.e., when `SPAN` operation is executed), the The rules for decrementing values in the $gc$ column are as follows: * The count cannot be decremented by more than $1$ in a single row. * When an operation group is fully executed (which happens when $h_0 = 0$ inside a span block), the count is decremented by $1$. -* When `SPAN`, `RESPAN`, or `PUSH` operations are executed, the count is decremented by $1$. +* When `SPAN`, `RESPAN`, `EMIT` or `PUSH` operations are executed, the count is decremented by $1$. -Note that these rules imply that `PUSH` operation cannot be the last operation in an operation group (otherwise the count would have to be decremented by $2$). +Note that these rules imply that the `EMIT` and `PUSH` operations cannot be the last operation in an operation group (otherwise the count would have to be decremented by $2$). To simplify the description of the constraints, we will define the following variable: @@ -422,18 +429,18 @@ Inside a *span* block, group count can either stay the same or decrease by one: sp \cdot \Delta gc \cdot (\Delta gc - 1) = 0 \text{ | degree} = 3 $$ -When group count is decremented inside a *span* block, either $h_0$ must be $0$ (we consumed all operations in a group) or we must be executing `PUSH` operation: +When group count is decremented inside a *span* block, either $h_0$ must be $0$ (we consumed all operations in a group) or we must be executing an operation with an immediate value: > $$ -sp \cdot \Delta gc \cdot (1 - f_{push})\cdot h_0 = 0 \text{ | degree} = 7 +sp \cdot \Delta gc \cdot (1 - f_{imm})\cdot h_0 = 0 \text{ | degree} = 7 $$ -Notice that the above constraint does not preclude $f_{push} = 1$ and $h_0 = 0$ from being true at the same time. If this happens, op group decoding constraints (described [here](#op-group-decoding-constraints)) will force that the operation following the `PUSH` operation is a `NOOP`. +Notice that the above constraint does not preclude $f_{imm} = 1$ and $h_0 = 0$ from being true at the same time. If this happens, op group decoding constraints (described [here](#op-group-decoding-constraints)) will force that the operation following the operation with an immediate value is a `NOOP`. -When executing a `SPAN`, a `RESPAN`, or a `PUSH` operation, group count must be decremented by $1$: +When executing a `SPAN`, a `RESPAN`, or an operation with an immediate value, group count must be decremented by $1$: > $$ -(f_{span} + f_{respan} + f_{push}) \cdot (\Delta gc - 1) = 0 \text{ | degree} = 6 +(f_{span} + f_{respan} + f_{imm}) \cdot (\Delta gc - 1) = 0 \text{ | degree} = 6 $$ If the next operation is either an `END` or a `RESPAN`, group count must remain the same: @@ -467,17 +474,17 @@ op = \sum_{i=0}^6 (b_i \cdot 2^i) \\ f_{sgc} = sp \cdot sp' \cdot (1 - \Delta gc) $$ -$op$ is just an opcode value implied by the values in `op_bits` registers. $f_{sgc}$ is a flag which is set to $1$ when the group count within a *span* block does not change. We multiply it by $sp'$ to make sure the flag is $0$ when we are about to end decoding of an operation batch. Note that $f_{sgc}$ flag is mutually exclusive with $f_{span}$, $f_{respan}$, and $f_{push}$ flags as these three operations decrement the group count. +$op$ is just an opcode value implied by the values in `op_bits` registers. $f_{sgc}$ is a flag which is set to $1$ when the group count within a *span* block does not change. We multiply it by $sp'$ to make sure the flag is $0$ when we are about to end decoding of an operation batch. Note that $f_{sgc}$ flag is mutually exclusive with $f_{span}$, $f_{respan}$, and $f_{imm}$ flags as these three operations decrement the group count. Using these variables, we can describe operation group decoding constraints as follows: -When a `SPAN`, a `RESPAN`, or a `PUSH` operation is executed or when the group count does not change, the value in $h_0$ should be decremented by the value of the opcode in the next row. +When a `SPAN`, a `RESPAN`, or an operation with an immediate value is executed or when the group count does not change, the value in $h_0$ should be decremented by the value of the opcode in the next row. > $$ -(f_{span} + f_{respan} + f_{push} + f_{sgc}) \cdot (h_0 - h_0' \cdot 2^7 - op') = 0 \text{ | degree} = 6 +(f_{span} + f_{respan} + f_{imm} + f_{sgc}) \cdot (h_0 - h_0' \cdot 2^7 - op') = 0 \text{ | degree} = 6 $$ -Notice that when the group count does change, and we are not executing $f_{span}$, $f_{respan}$, or $f_{push}$ operations, no constraints are placed against $h_0$, and thus, the prover can populate this register non-deterministically. +Notice that when the group count does change, and we are not executing $f_{span}$, $f_{respan}$, or $f_{imm}$ operations, no constraints are placed against $h_0$, and thus, the prover can populate this register non-deterministically. When we are in a *span* block and the next operation is `END` or `RESPAN`, the current value in $h_0$ column must be $0$. @@ -491,11 +498,11 @@ The `op_index` column (denoted as $ox$) tracks index of an operation within its To simplify the description of the constraints, we will define the following variables: $$ -ng = \Delta gc - f_{push} \\ +ng = \Delta gc - f_{imm} \\ \Delta ox = ox' - ox $$ -The value of $ng$ is set to $1$ when we are about to start executing a new operation group (i.e., group count is decremented but we did not execute a `PUSH` operation). Using these variables, we can describe the constraints against the $ox$ column as follows. +The value of $ng$ is set to $1$ when we are about to start executing a new operation group (i.e., group count is decremented but we did not execute an operation with an immediate value). Using these variables, we can describe the constraints against the $ox$ column as follows. When executing `SPAN` or `RESPAN` operations the next value of `op_index` must be set to $0$: @@ -547,6 +554,12 @@ When `SPAN` or `RESPAN` operations is executed, one of the batch flags must be s (f_{span} + f_{respan}) - (f_{g1} + f_{g2} + f_{g4} + f_{g8}) = 0 \text{ | degree} = 5 $$ +When neither `SPAN` nor `RESPAN` is executed, all batch flags must be set to $0$. + +$$ +(1 - (f_{span} + f_{respan})) \cdot (bc_0 + bc_1 + bc_2) = 0 \text{ | degree} = 6 +$$ + When we have at most 4 groups in a batch, registers $h_4, ..., h_7$ should be set to $0$'s. > $$ @@ -584,15 +597,15 @@ Where $i \in [1, 8)$. Thus, $v_1$ defines row value for group in $h_1$, $v_2$ de We compute the value of the row to be removed from the op group table as follows: $$ -u = \alpha_0 + \alpha_1 \cdot a + \alpha_2 \cdot gc + \alpha_3 \cdot ((h_0' \cdot 2^7 + op') \cdot (1 - f_{push}) + s_0' \cdot f_{push}) \text{ | degree} = 5 +u = \alpha_0 + \alpha_1 \cdot a + \alpha_2 \cdot gc + \alpha_3 \cdot ((h_0' \cdot 2^7 + op') \cdot (1 - f_{imm}) + s_0' \cdot f_{push} + h_2 \cdot f_{emit}) \text{ | degree} = 6 $$ -In the above, the value of the group is computed as $(h_0' \cdot 2^7 + op') \cdot (1 - f_{push}) + s_0' \cdot f_{push}$. This basically says that when we execute a `PUSH` operation we need to remove the immediate value from the table. This value is at the top of the stack (column $s_0$) in the next row. However, when we are not executing a `PUSH` operation, the value to be removed is an op group value which is a combination of values in $h_0$ and `op_bits` columns (also in the next row). Note also that value for batch address comes from the current value in the block address column ($a$), and the group position comes from the current value of the group count column ($gc$). +In the above, the value of the group is computed as $(h_0' \cdot 2^7 + op') \cdot (1 - f_{push}) + s_0' \cdot f_{push} + h_2 \cdot f_{emit}$. This basically says that when we execute a `PUSH` or `EMIT` operation we need to remove the immediate value from the table. For `PUSH`, this value is at the top of the stack (column $s_0$) in the next row; for `EMIT`, it is found in $h_2$. However, when we are executing neither a `PUSH` nor `EMIT` operation, the value to be removed is an op group value which is a combination of values in $h_0$ and `op_bits` columns (also in the next row). Note also that value for batch address comes from the current value in the block address column ($a$), and the group position comes from the current value of the group count column ($gc$). We also define a flag which is set to $1$ when a group needs to be removed from the op group table. $$ -f_{dg} = sp \cdot \Delta gc +f_{dg} = sp \cdot \Delta gc \text{ | degree} = 2 $$ The above says that we remove groups from the op group table whenever group count is decremented. We multiply by $sp$ to exclude the cases when the group count is decremented due to `SPAN` or `RESPAN` operations. @@ -600,12 +613,12 @@ The above says that we remove groups from the op group table whenever group coun Using the above variables together with flags $f_{g2}$, $f_{g4}$, $f_{g8}$ defined in the previous section, we describe the constraint for updating op group table as follows (note that we do not use $f_{g1}$ flag as when a batch consists of a single group, nothing is added to the op group table): > $$ -p_3' \cdot (f_{dg} \cdot u + 1 - f_{dg}) = p_3 \cdot (f_{g2} \cdot v_1 + f_{g4} \cdot \prod_{i=1}^3 v_i + f_{g8} \cdot \prod_{i=1}^7 v_i - 1 + (f_{span} + f_{respan})) +p_3' \cdot (f_{dg} \cdot u + 1 - f_{dg}) = p_3 \cdot (f_{g2} \cdot v_1 + f_{g4} \cdot \prod_{i=1}^3 v_i + f_{g8} \cdot (\prod_{i=1}^7 v_i) + 1 - (f_{span} + f_{respan})) $$ The above constraint specifies that: -* When `SPAN` or `RESPAN` operations are executed, we add between $1$ and $7$ groups to the op group table. -* When group count is decremented inside a *span* block, we remove a group from the op group table. +* When `SPAN` or `RESPAN` operations are executed, we add between $1$ and $7$ groups to the op group table; else, leave $p3$ untouched. +* When group count is decremented inside a *span* block, we remove a group from the op group table; else, leave $p3'$ untouched. The degree of this constraint is $9$. diff --git a/docs/src/design/decoder/main.md b/docs/src/design/decoder/main.md index af2020fe8d..d92a776864 100644 --- a/docs/src/design/decoder/main.md +++ b/docs/src/design/decoder/main.md @@ -511,7 +511,7 @@ In the above, the batch contains $3$ operation groups. To bring the count up to Operation batch flags (denoted as $c_0, c_1, c_2$), encode the number of groups and define how many groups are added to the op group table as follows: -* `(1, 0, 0)` - $8$ groups. Groups in $h_1, ... h_7$ are added to the op group table. +* `(1, -, -)` - $8$ groups. Groups in $h_1, ... h_7$ are added to the op group table. * `(0, 1, 0)` - $4$ groups. Groups in $h_1, ... h_3$ are added to the op group table * `(0, 0, 1)` - $2$ groups. Groups in $h_1$ is added to the op group table. * `(0, 1, 1)` - $1$ group. Nothing is added to the op group table diff --git a/docs/src/design/range.md b/docs/src/design/range.md index aa4478267c..6274c83d35 100644 --- a/docs/src/design/range.md +++ b/docs/src/design/range.md @@ -164,7 +164,7 @@ $$ As previously mentioned, constraints cannot include divisions, so the actual constraint which is applied will be the equivalent expression in which all denominators have been multiplied through, which is degree 9. -If $b_{range}$ is initialized to $1$ and the values sent to the bus by other VM components match those that are range-checked in the the trace, then at the end of the trace we should end up with $b_{range} = 1$. +If $b_{range}$ is initialized to $1$ and the values sent to the bus by other VM components match those that are range-checked in the trace, then at the end of the trace we should end up with $b_{range} = 1$. Therefore, in addition to the transition constraint described above, we also need to enforce the following boundary constraints: diff --git a/docs/src/design/stack/io_ops.md b/docs/src/design/stack/io_ops.md index 822d0fa831..08df6dfd93 100644 --- a/docs/src/design/stack/io_ops.md +++ b/docs/src/design/stack/io_ops.md @@ -2,7 +2,7 @@ In this section we describe the AIR constraints for Miden VM input / output operations. These operations move values between the stack and other components of the VM such as program code (i.e., decoder), memory, and advice provider. ### PUSH -The `PUSH` operation pushes the provided immediate value onto the stack (i.e., sets the value of $s_0$ register). Currently, it is the only operation in Miden VM which carries an immediate value. The semantics of this operation are explained in the [decoder section](../decoder/main.html#handling-immediate-values). +The `PUSH` operation pushes the provided immediate value onto the stack non-deterministically (i.e., sets the value of $s_0$ register); it is the responsibility of the [Op Group Table](../decoder/main.md#op-group-table) to ensure that the correct value was pushed on the stack. The semantics of this operation are explained in the [decoder section](../decoder/main.html#handling-immediate-values). The effect of this operation on the rest of the stack is: * **Right shift** starting from position $0$. diff --git a/docs/src/design/stack/op_constraints.md b/docs/src/design/stack/op_constraints.md index f4502b2daa..d60e186dbb 100644 --- a/docs/src/design/stack/op_constraints.md +++ b/docs/src/design/stack/op_constraints.md @@ -189,8 +189,8 @@ This group contains operations which require constraints with degree up to $3$. | `JOIN` | $87$ | `101_0111` | [Flow control ops](../decoder/main.md) | $5$ | | `DYN` | $88$ | `101_1000` | [Flow control ops](../decoder/main.md) | $5$ | | `RCOMBBASE` | $89$ | `101_1001` | [Crypto ops](./crypto_ops.md) | $5$ | -| `` | $90$ | `101_1010` | | $5$ | -| `` | $91$ | `101_1011` | | $5$ | +| `EMIT` | $90$ | `101_1010` | [System ops](./system_ops.md) | $5$ | +| `PUSH` | $91$ | `101_1011` | [I/O ops](./io_ops.md) | $5$ | | `` | $92$ | `101_1100` | | $5$ | | `` | $93$ | `101_1101` | | $5$ | | `` | $94$ | `101_1110` | | $5$ | @@ -211,7 +211,7 @@ This group contains operations which require constraints with degree up to $5$. | Operation | Opcode value | Binary encoding | Operation group | Flag degree | | ------------ | :----------: | :-------------: | :-------------------------------------:| :---------: | | `MRUPDATE` | $96$ | `110_0000` | [Crypto ops](./crypto_ops.md) | $4$ | -| `PUSH` | $100$ | `110_0100` | [I/O ops](./io_ops.md) | $4$ | +| `` | $100$ | `110_0100` | | $4$ | | `SYSCALL` | $104$ | `110_1000` | [Flow control ops](../decoder/main.md) | $4$ | | `CALL` | $108$ | `110_1100` | [Flow control ops](../decoder/main.md) | $4$ | | `END` | $112$ | `111_0000` | [Flow control ops](../decoder/main.md) | $4$ | @@ -294,3 +294,13 @@ $$ $$ f_{ctrl} = f_{span,join,split,loop} + f_{end,repeat,respan,halt} + f_{dyn} + f_{call} + f_{syscall} \text{ | degree} = 5 $$ + +### Immediate value flag + +The immediate value flag $f_{imm}$ is set to 1 when an operation has an immediate value, and 0 otherwise: + +$$ +f_{imm} = f_{push} + f_{emit} \text{ | degree} = 4 +$$ + +Note that the `ASSERT`, `MPVERIFY` and other operations have immediate values too. However, these immediate values are not included in the MAST digest, and hence are not considered for the $f_{imm}$ flag. diff --git a/docs/src/design/stack/system_ops.md b/docs/src/design/stack/system_ops.md index bebd639f23..becbbadae3 100644 --- a/docs/src/design/stack/system_ops.md +++ b/docs/src/design/stack/system_ops.md @@ -10,6 +10,17 @@ The `NOOP` operation does not impose any constraints besides the ones needed to s'_i - s_i = 0 \ \text{ for } i \in [0, 16) \text { | degree} = 1 $$ +## EMIT +Similarly to `NOOP`, the `EMIT` operation advances the cycle counter but does not change the state of the operand stack (i.e., the depth of the stack and the values on the stack remain the same). + +The `EMIT` operation does not impose any constraints besides the ones needed to ensure that the entire state of the stack is copied over. This constraint looks like so: + +>$$ +s'_i - s_i = 0 \ \text{ for } i \in [0, 16) \text { | degree} = 1 +$$ + +Additionally, the prover puts `EMIT`'s immediate value in the first user op helper register non-deterministically. The [Op Group Table](../decoder/main.md#op-group-table) is responsible for ensuring that the prover sets the appropriate value. + ## ASSERT The `ASSERT` operation pops an element off the stack and checks if the popped element is equal to $1$. If the element is not equal to $1$, program execution fails. diff --git a/docs/src/intro/main.md b/docs/src/intro/main.md index a10c23626a..4d39927d79 100644 --- a/docs/src/intro/main.md +++ b/docs/src/intro/main.md @@ -2,7 +2,7 @@ Miden VM is a zero-knowledge virtual machine written in Rust. For any program executed on Miden VM, a STARK-based proof of execution is automatically generated. This proof can then be used by anyone to verify that the program was executed correctly without the need for re-executing the program or even knowing the contents of the program. ## Status and features -Miden VM is currently on release v0.8. In this release, most of the core features of the VM have been stabilized, and most of the STARK proof generation has been implemented. While we expect to keep making changes to the VM internals, the external interfaces should remain relatively stable, and we will do our best to minimize the amount of breaking changes going forward. +Miden VM is currently on release v0.10. In this release, most of the core features of the VM have been stabilized, and most of the STARK proof generation has been implemented. While we expect to keep making changes to the VM internals, the external interfaces should remain relatively stable, and we will do our best to minimize the amount of breaking changes going forward. At this point, Miden VM is good enough for experimentation, and even for real-world applications, but it is not yet ready for production use. The codebase has not been audited and contains known and unknown bugs and security flaws. diff --git a/docs/src/intro/usage.md b/docs/src/intro/usage.md index d241e3f59e..9ca9e1db47 100644 --- a/docs/src/intro/usage.md +++ b/docs/src/intro/usage.md @@ -1,39 +1,45 @@ # Usage -Before you can use Miden VM, you'll need to make sure you have Rust [installed](https://www.rust-lang.org/tools/install). Miden VM v0.8 requires Rust version **1.75** or later. + +Before you can use Miden VM, you'll need to make sure you have Rust [installed](https://www.rust-lang.org/tools/install). Miden VM v0.10 requires Rust version **1.80** or later. Miden VM consists of several crates, each of which exposes a small set of functionality. The most notable of these crates are: -* [miden-processor](https://crates.io/crates/miden-processor), which can be used to execute Miden VM programs. -* [miden-prover](https://crates.io/crates/miden-prover), which can be used to execute Miden VM programs and generate proofs of their execution. -* [miden-verifier](https://crates.io/crates/miden-verifier), which can be used to verify proofs of program execution generated by Miden VM prover. + +- [miden-processor](https://crates.io/crates/miden-processor), which can be used to execute Miden VM programs. +- [miden-prover](https://crates.io/crates/miden-prover), which can be used to execute Miden VM programs and generate proofs of their execution. +- [miden-verifier](https://crates.io/crates/miden-verifier), which can be used to verify proofs of program execution generated by Miden VM prover. The above functionality is also exposed via the single [miden-vm](https://crates.io/crates/miden-vm) crate, which also provides a CLI interface for interacting with Miden VM. ## CLI interface ### Compiling Miden VM + To compile Miden VM into a binary, we have a [Makefile](https://www.gnu.org/software/make/manual/make.html) with the following tasks: + ``` make exec ``` + This will place an optimized, multi-threaded `miden` executable into the `./target/optimized` directory. It is equivalent to executing: + ``` cargo build --profile optimized --features concurrent,executable ``` + If you would like to enable single-threaded mode, you can compile Miden VM using the following command: + ``` -cargo build --profile optimized --features executable -``` -For a faster build, you can compile with less optimizations, replacing `--profile optimized` by `--release`. Example: -``` -cargo build --release --features concurrent,executable +make exec-single ``` -In this case, the `miden` executable will be placed in the `./target/release` directory. ### Controlling parallelism + Internally, Miden VM uses [rayon](https://github.com/rayon-rs/rayon) for parallel computations. To control the number of threads used to generate a STARK proof, you can use `RAYON_NUM_THREADS` environment variable. ### GPU acceleration -Miden VM proof generation can be accelerated via GPUs. Currently, GPU acceleration is enabled only on Apple silicon hardware (via [Metal](https://en.wikipedia.org/wiki/Metal_(API))). To compile Miden VM with Metal acceleration enabled, you can run the following command: + +Miden VM proof generation can be accelerated via GPUs. Currently, GPU acceleration is enabled only on Apple Silicon hardware (via [Metal]()). To compile Miden VM with Metal acceleration enabled, you can run the following command: + ``` make exec-metal ``` @@ -43,14 +49,17 @@ Similar to `make exec` command, this will place the resulting `miden` executable Currently, GPU acceleration is applicable only to recursive proofs which can be generated using the `-r` flag. ### SIMD acceleration -Miden VM execution and proof generation can be accelerated via vectorized instructions. Currently, SIMD acceleration can be enabled on platforms supporting [SVE](https://en.wikipedia.org/wiki/AArch64#Scalable_Vector_Extension_(SVE)) and [AVX2](https://en.wikipedia.org/wiki/Advanced_Vector_Extensions#Advanced_Vector_Extensions_2) instructions. + +Miden VM execution and proof generation can be accelerated via vectorized instructions. Currently, SIMD acceleration can be enabled on platforms supporting [SVE]() and [AVX2](https://en.wikipedia.org/wiki/Advanced_Vector_Extensions#Advanced_Vector_Extensions_2) instructions. To compile Miden VM with AVX2 acceleration enabled, you can run the following command: + ``` make exec-avx2 ``` To compile Miden VM with SVE acceleration enabled, you can run the following command: + ``` make exec-sve ``` @@ -60,25 +69,32 @@ This will place the resulting `miden` executable into the `./target/optimized` d Similar to Metal acceleration, SVE/AVX2 acceleration is currently applicable only to recursive proofs which can be generated using the `-r` flag. ### Running Miden VM + Once the executable has been compiled, you can run Miden VM like so: + ``` ./target/optimized/miden [subcommand] [parameters] ``` + Currently, Miden VM can be executed with the following subcommands: -* `run` - this will execute a Miden assembly program and output the result, but will not generate a proof of execution. -* `prove` - this will execute a Miden assembly program, and will also generate a STARK proof of execution. -* `verify` - this will verify a previously generated proof of execution for a given program. -* `compile` - this will compile a Miden assembly program (i.e., build a program [MAST](../design/programs.md)) and outputs stats about the compilation process. -* `debug` - this will instantiate a [Miden debugger](../tools/debugger.md) against the specified Miden assembly program and inputs. -* `analyze` - this will run a Miden assembly program against specific inputs and will output stats about its execution. -* `repl` - this will initiate the [Miden REPL](../tools/repl.md) tool. -* `example` - this will execute a Miden assembly example program, generate a STARK proof of execution and verify it. Currently it is possible to run `blake3` and `fibonacci` examples. + +- `run` - this will execute a Miden assembly program and output the result, but will not generate a proof of execution. +- `prove` - this will execute a Miden assembly program, and will also generate a STARK proof of execution. +- `verify` - this will verify a previously generated proof of execution for a given program. +- `compile` - this will compile a Miden assembly program (i.e., build a program [MAST](../design/programs.md)) and outputs stats about the compilation process. +- `debug` - this will instantiate a [Miden debugger](../tools/debugger.md) against the specified Miden assembly program and inputs. +- `analyze` - this will run a Miden assembly program against specific inputs and will output stats about its execution. +- `repl` - this will initiate the [Miden REPL](../tools/repl.md) tool. +- `example` - this will execute a Miden assembly example program, generate a STARK proof of execution and verify it. Currently, it is possible to run `blake3` and `fibonacci` examples. All of the above subcommands require various parameters to be provided. To get more detailed help on what is needed for a given subcommand, you can run the following: + ``` ./target/optimized/miden [subcommand] --help ``` + For example: + ``` ./target/optimized/miden prove --help ``` @@ -86,39 +102,67 @@ For example: To execute a program using the Miden VM there needs to be a `.masm` file containing the Miden Assembly code and a `.inputs` file containing the inputs. #### Enabling logging + You can use `MIDEN_LOG` environment variable to control how much logging output the VM produces. For example: + ``` MIDEN_LOG=trace ./target/optimized/miden [subcommand] [parameters] ``` -If the level is not specified, `warn` level is set as default. + +If the level is not specified, `warn` level is set as default. + +#### Enable Debugging features + +You can use the run command with `--debug` parameter to enable debugging with the [debug instruction](../user_docs/assembly/debugging.md) such as `debug.stack`: + +```shell +./target/optimized/miden run -a [path_to.masm] --debug +``` ### Inputs As described [here](https://0xpolygonmiden.github.io/miden-vm/intro/overview.html#inputs-and-outputs) the Miden VM can consume public and secret inputs. -* Public inputs: - * `operand_stack` - can be supplied to the VM to initialize the stack with the desired values before a program starts executing. There is no limit on the number of stack inputs that can be initialized in this way, although increasing the number of public inputs increases the cost to the verifier. -* Secret (or nondeterministic) inputs: - * `advice_stack` - can be supplied to the VM. There is no limit on how much data the advice provider can hold. This is provided as a string array where each string entry represents a field element. - * `advice_map` - is supplied as a map of 64-character hex keys, each mapped to an array of numbers. The hex keys are interpreted as 4 field elements and the arrays of numbers are interpreted as arrays of field elements. - * `merkle_store` - the Merkle store is container that allows the user to define `merkle_tree`, `sparse_merkle_tree` and `partial_merkle_tree` data structures. - * `merkle_tree` - is supplied as an array of 64-character hex values where each value represents a leaf (4 elements) in the tree. - * `sparse_merkle_tree` - is supplied as an array of tuples of the form (number, 64-character hex string). The number represents the leaf index and the hex string represents the leaf value (4 elements). - * `partial_merkle_tree` - is supplied as an array of tuples of the form ((number, number), 64-character hex string). The internal tuple represents the leaf depth and index at this depth, and the hex string represents the leaf value (4 elements). +- Public inputs: + - `operand_stack` - can be supplied to the VM to initialize the stack with the desired values before a program starts executing. There is no limit on the number of stack inputs that can be initialized in this way, although increasing the number of public inputs increases the cost to the verifier. +- Secret (or nondeterministic) inputs: + - `advice_stack` - can be supplied to the VM. There is no limit on how much data the advice provider can hold. This is provided as a string array where each string entry represents a field element. + - `advice_map` - is supplied as a map of 64-character hex keys, each mapped to an array of numbers. The hex keys are interpreted as 4 field elements and the arrays of numbers are interpreted as arrays of field elements. + - `merkle_store` - the Merkle store is container that allows the user to define `merkle_tree`, `sparse_merkle_tree` and `partial_merkle_tree` data structures. + - `merkle_tree` - is supplied as an array of 64-character hex values where each value represents a leaf (4 elements) in the tree. + - `sparse_merkle_tree` - is supplied as an array of tuples of the form (number, 64-character hex string). The number represents the leaf index and the hex string represents the leaf value (4 elements). + - `partial_merkle_tree` - is supplied as an array of tuples of the form ((number, number), 64-character hex string). The internal tuple represents the leaf depth and index at this depth, and the hex string represents the leaf value (4 elements). -*Check out the [comparison example](https://github.com/0xPolygonMiden/examples/blob/main/examples/comparison.masm) to see how secret inputs work.* +_Check out the [comparison example](https://github.com/0xPolygonMiden/examples/blob/main/examples/comparison.masm) to see how secret inputs work._ After a program finishes executing, the elements that remain on the stack become the outputs of the program, along with the overflow addresses (`overflow_addrs`) that are required to reconstruct the [stack overflow table](../design/stack/main.md#overflow-table). ## Fibonacci example + In the `miden/examples/fib` directory, we provide a very simple Fibonacci calculator example. This example computes the 1001st term of the Fibonacci sequence. You can execute this example on Miden VM like so: -``` + +```shell ./target/optimized/miden run -a miden/examples/fib/fib.masm -n 1 ``` + +### Capturing Output + This will run the example code to completion and will output the top element remaining on the stack. If you want the output of the program in a file, you can use the `--output` or `-o` flag and specify the path to the output file. For example: -``` + +```shell ./target/optimized/miden run -a miden/examples/fib/fib.masm -o fib.out ``` + This will dump the output of the program into the `fib.out` file. The output file will contain the state of the stack at the end of the program execution. + +### Running with debug instruction enabled + +Inside `miden/examples/fib/fib.masm`, insert `debug.stack` instruction anywhere between `begin` and `end`. Then run: + +```shell +./target/optimized/miden run -a miden/examples/fib/fib.masm -n 1 --debug +``` + +You should see output similar to "Stack state before step ..." diff --git a/docs/src/user_docs/assembly/code_organization.md b/docs/src/user_docs/assembly/code_organization.md index a22af8b044..5b9d4e428c 100644 --- a/docs/src/user_docs/assembly/code_organization.md +++ b/docs/src/user_docs/assembly/code_organization.md @@ -12,7 +12,7 @@ proc.foo.2 end ``` -A procedure label must start with a letter and can contain any combination of numbers, ASCII letters, and underscores (`_`). The number of characters in the procedure label cannot exceed 100. +A procedure label must start with a letter and can contain any combination of numbers, ASCII letters, and underscores (`_`). Should you need to represent a label with other characters, an extended set is permitted via quoted identifiers, i.e. an identifier surrounded by `".."`. Quoted identifiers additionally allow any alphanumeric letter (ASCII or UTF-8), as well as various common punctuation characters: `!`, `?`, `:`, `.`, `<`, `>`, and `-`. Quoted identifiers are primarily intended for representing symbols/identifiers when compiling higher-level languages to Miden Assembly, but can be used anywhere that normal identifiers are expected. The number of locals specifies the number of memory-based local words a procedure can access (via `loc_load`, `loc_store`, and [other instructions](./io_operations.md#random-access-memory)). If a procedure doesn't need any memory-based locals, this parameter can be omitted or set to `0`. A procedure can have at most $2^{16}$ locals, and the total number of locals available to all procedures at runtime is limited to $2^{30}$. @@ -22,18 +22,19 @@ exec.foo ``` The difference between using each of these instructions is explained in the [next section](./execution_contexts.md#procedure-invocation-semantics). -A procedure may execute any other previously defined procedure, but it cannot execute itself or any of the subsequent procedures. Thus, recursive procedure calls are not possible. For example, the following code block defines a program with two procedures: -``` -proc.foo - -end +A procedure may execute any other procedure, however recursion is not currently permitted, due to limitations imposed by the Merkalized Abstract Syntax Tree. Recursion is caught by static analysis of the call graph during assembly, so in general you don't need to think about this, but it is a limitation to be aware of. For example, the following code block defines a program with two procedures: +``` proc.bar exec.foo end +proc.foo + +end + begin exec.bar @@ -42,25 +43,27 @@ begin end ``` +Finally, a procedure cannot contain *solely* any number of [advice injectors](./io_operations.md#nondeterministic-inputs), `emit`, `debug` and `trace` instructions. In other words, it must contain at least one instruction which is not in the aforementioned list. + #### Dynamic procedure invocation -It is also possible to invoke procedures dynamically - i.e., without specifying target procedure labels at compile time. There are two instructions, `dynexec` and `dyncall`, which can be used to execute dynamically-specified code targets. Both instructions expect [MAST root](../../design/programs.md) of the target to be provided via the stack. The difference between `dynexec` and `dyncall` is that `dyncall` will [change context](./execution_contexts.md) before executing the dynamic code target, while `dynexec` will cause the code target to be executed in the current context. +It is also possible to invoke procedures dynamically - i.e., without specifying target procedure labels at compile time. Unlike static procedure invocation, recursion is technically possible using dynamic invocation, but dynamic invocation is more expensive, and has less available operand stack capacity for procedure arguments, as 4 elements are required for the MAST root of the callee. There are two instructions, `dynexec` and `dyncall`, which can be used to execute dynamically-specified code targets. Both instructions expect the [MAST root](../../design/programs.md) of the target to be provided via the stack. The difference between `dynexec` and `dyncall` corresponds to the difference between `exec` and `call`, see the documentation on [procedure invocation semantics](./execution_contexts.md#procedure-invocation-semantics) for more detail. -Dynamic code execution in the same context is achieved by setting the top $4$ elements of the stack to the hash of the dynamic code block and then executing the following instruction: +Dynamic code execution in the same context is achieved by setting the top $4$ elements of the stack to the hash of the dynamic code block and then executing the `dynexec` or `dyncall` instruction. You can obtain the hash of a procedure in the current program, by name, using the `procref` instruction. See the following example of pairing the two: ``` +procref.foo dynexec ``` -This causes the VM to do the following: +During assembly, the `procref.foo` instruction is compiled to a `push.HASH`, where `HASH` is the hash of the MAST root of the `foo` procedure. -1. Read the top 4 elements of the stack to get the hash of the dynamic target (leaving the stack unchanged). -2. Execute the code block which hashes to the specified target. The VM must know the specified code block and hash: they must be in the CodeBlockTable of the executing Program. Hashes can be put into the CodeBlockTable manually, or by executing `call`, `syscall`, or `procref` instructions. +During execution of the `dynexec` instruction, the VM does the following: -Dynamic code execution in a new context can be achieved similarly by setting the top $4$ elements of the stack to the hash of the dynamic code block and then executing the following instruction: +1. Reads, but does not consume, the top 4 elements of the stack to get the hash of the dynamic target (i.e. the operand stack is left unchanged). +2. Load the code block referenced by the hash, or trap if no such MAST root is known. +3. Execute the loaded code block -``` -dyncall -``` +The `dyncall` instruction is used the same way, with the difference that it involves a context switch to a new context when executing the referenced block, and switching back to the calling context once execution of the callee completes. > **Note**: In both cases, the stack is left unchanged. Therefore, if the dynamic code is intended to manipulate the stack, it should start by either dropping or moving the code block hash from the top of the stack. @@ -99,32 +102,49 @@ A program cannot contain any exported procedures. When a program is executed, the execution starts at the first instruction following the `begin` instruction. The main procedure is expected to be the last procedure in the program and can be followed only by comments. #### Importing modules -To invoke a procedure from an external module, the module first needs to be imported using a `use` instruction. Once a module is imported, procedures from this module can be invoked via the regular `exec` or `call` instructions as `exec|call.::