├── .gitattributes
├── .github
├── CODEOWNERS
├── scripts
│ ├── install_iai_callgrind_runner.sh
│ └── install_llvm_ubuntu.sh
└── workflows
│ ├── bench.yml
│ └── ci.yml
├── .gitignore
├── CHANGELOG.md
├── Cargo.lock
├── Cargo.toml
├── LICENSE-APACHE
├── LICENSE-MIT
├── README.md
├── cliff.toml
├── clippy.toml
├── crates
├── revmc-backend
│ ├── CHANGELOG.md
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ │ ├── lib.rs
│ │ ├── pointer.rs
│ │ └── traits.rs
├── revmc-build
│ ├── CHANGELOG.md
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ │ └── lib.rs
├── revmc-builtins
│ ├── CHANGELOG.md
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ │ ├── gas.rs
│ │ ├── ir.rs
│ │ ├── lib.rs
│ │ ├── macros.rs
│ │ └── utils.rs
├── revmc-cli-tests
│ ├── CHANGELOG.md
│ ├── Cargo.toml
│ └── src
│ │ └── lib.rs
├── revmc-cli
│ ├── CHANGELOG.md
│ ├── Cargo.toml
│ ├── benches
│ │ ├── bench.rs
│ │ ├── iai.rs
│ │ └── iai
│ │ │ └── prev.rs
│ ├── build.rs
│ ├── src
│ │ ├── benches.rs
│ │ ├── lib.rs
│ │ └── main.rs
│ └── tests.rs
├── revmc-context
│ ├── CHANGELOG.md
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ │ └── lib.rs
├── revmc-cranelift
│ ├── CHANGELOG.md
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ │ ├── lib.rs
│ │ └── pretty_clif.rs
├── revmc-llvm
│ ├── CHANGELOG.md
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ │ ├── dh.rs
│ │ ├── lib.rs
│ │ ├── orc.rs
│ │ └── utils.rs
└── revmc
│ ├── CHANGELOG.md
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ ├── bytecode
│ ├── info.rs
│ ├── mod.rs
│ ├── opcode.rs
│ └── sections.rs
│ ├── compiler
│ ├── mod.rs
│ └── translate.rs
│ ├── lib.rs
│ ├── linker.rs
│ └── tests
│ ├── fibonacci.rs
│ ├── macros.rs
│ ├── meta.rs
│ ├── mod.rs
│ ├── resume.rs
│ └── runner.rs
├── data
├── airdrop.rt.hex
├── bswap64.rt.hex
├── bswap64_opt.rt.hex
├── counter-eof.rt.hex
├── counter.rt.hex
├── erc20_transfer.rt.hex
├── fiat_token.rt.hex
├── hash_10k-eof.rt.hex
├── hash_10k.rt.hex
├── push0_proxy.rt.hex
├── seaport.rt.hex
├── snailtracer-eof.rt.hex
├── snailtracer.rt.hex
├── uniswap_v2_pair.rt.hex
├── univ2_router.rt.hex
├── usdc_proxy.rt.hex
└── weth.rt.hex
├── examples
├── compiler
│ ├── Cargo.toml
│ ├── build.rs
│ └── src
│ │ └── main.rs
└── runner
│ ├── Cargo.toml
│ ├── build.rs
│ └── src
│ ├── common.rs
│ ├── lib.rs
│ └── main.rs
├── fuzz
├── .gitignore
├── Cargo.toml
└── fuzz_targets
│ └── vs_interpreter.rs
├── release.toml
├── rustfmt.toml
├── scripts
└── changelog.sh
└── tests
└── codegen
├── address_mask.evm
└── non_zero.evm
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.evm linguist-language=Assembly
2 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @DaniPopes
2 |
--------------------------------------------------------------------------------
/.github/scripts/install_iai_callgrind_runner.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -eo pipefail
3 |
4 | echo "::group::Install"
5 | version=$(cargo metadata --format-version=1 |\
6 | jq '.packages[] | select(.name == "iai-callgrind").version' |\
7 | tr -d '"'
8 | )
9 | cargo binstall iai-callgrind-runner --version "$version" --no-confirm --no-symlinks --force
10 | echo "::endgroup::"
11 | echo "::group::Verification"
12 | which iai-callgrind-runner
13 | echo "::endgroup::"
14 |
--------------------------------------------------------------------------------
/.github/scripts/install_llvm_ubuntu.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -eo pipefail
3 |
4 | v=${1:-$LLVM_VERSION}
5 | bins=(clang llvm-config lld ld.lld FileCheck)
6 | llvm_sh=$(mktemp)
7 |
8 | wget https://apt.llvm.org/llvm.sh -O "$llvm_sh"
9 | chmod +x "$llvm_sh"
10 | "$llvm_sh" "$v" all
11 | for bin in "${bins[@]}"; do
12 | ln -fs "$(which "$bin-$v")" "/usr/bin/$bin"
13 | done
14 |
--------------------------------------------------------------------------------
/.github/workflows/bench.yml:
--------------------------------------------------------------------------------
1 | name: Benchmark
2 |
3 | on:
4 | push:
5 | branches: [main]
6 | pull_request:
7 | workflow_dispatch:
8 |
9 | env:
10 | CARGO_TERM_COLOR: always
11 | LLVM_VERSION: "18" # Must be just the major version
12 |
13 | concurrency:
14 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
15 | cancel-in-progress: true
16 |
17 | jobs:
18 | iai:
19 | runs-on: ubuntu-latest
20 | env:
21 | BASELINE: base
22 | IAI_CALLGRIND_RUNNER: iai-callgrind-runner
23 | steps:
24 | - uses: actions/checkout@v4
25 | - name: Install LLVM
26 | run: sudo .github/scripts/install_llvm_ubuntu.sh ${{ env.LLVM_VERSION }}
27 | - name: llvm-config
28 | run: llvm-config --version --bindir --libdir
29 | - name: Install Valgrind
30 | run: sudo apt update && sudo apt install valgrind
31 | - uses: dtolnay/rust-toolchain@stable
32 | - name: Install cargo-binstall
33 | uses: taiki-e/install-action@cargo-binstall
34 | - name: Checkout base
35 | uses: actions/checkout@v4
36 | with:
37 | ref: ${{ github.base_ref || 'main' }}
38 | fetch-depth: 2
39 | - name: Checkout HEAD^
40 | if: ${{ !github.base_ref }}
41 | run: git checkout HEAD^
42 | - uses: Swatinem/rust-cache@v2
43 | with:
44 | cache-on-failure: true
45 | - name: Install iai-callgrind-runner
46 | run: ./.github/scripts/install_iai_callgrind_runner.sh
47 | - name: Save baseline
48 | run: cargo bench -p revmc-cli --bench iai -- --save-baseline=$BASELINE
49 | - name: Checkout PR
50 | uses: actions/checkout@v4
51 | with:
52 | clean: false
53 | - name: Install iai-callgrind-runner
54 | run: ./.github/scripts/install_iai_callgrind_runner.sh
55 | - name: Compare PR benchmarks
56 | run: cargo bench -p revmc-cli --bench iai -- --baseline=$BASELINE
57 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches: [main]
6 | pull_request:
7 | branches: [main]
8 |
9 | env:
10 | CARGO_TERM_COLOR: always
11 | LLVM_VERSION: "18" # Must be just the major version
12 | ALL_BACKENDS: "llvm,cranelift"
13 |
14 | concurrency:
15 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
16 | cancel-in-progress: true
17 |
18 | jobs:
19 | test:
20 | name: test +${{ matrix.runner }}-${{ matrix.arch }}-${{ matrix.toolchain }} ${{ matrix.profile }}
21 | runs-on: ${{ matrix.runner }}
22 | timeout-minutes: 30
23 | strategy:
24 | fail-fast: false
25 | matrix:
26 | runner: ["ubuntu-latest", "macos-14"]
27 | arch: ["x64", "arm64"]
28 | toolchain: ["stable", "nightly"]
29 | profile: ["dev", "release"]
30 | exclude:
31 | - runner: "ubuntu-latest"
32 | arch: "arm64"
33 | - runner: "macos-14"
34 | arch: "x64"
35 | steps:
36 | - uses: actions/checkout@v4
37 | - name: Install LLVM (apt)
38 | if: ${{ !contains(matrix.runner, 'macos') }}
39 | run: sudo .github/scripts/install_llvm_ubuntu.sh ${{ env.LLVM_VERSION }}
40 | - name: Install LLVM (brew)
41 | if: ${{ contains(matrix.runner, 'macos') }}
42 | run: |
43 | v=${{ env.LLVM_VERSION }}
44 | brew install "llvm@${v}"
45 | echo "LLVM_SYS_${v}0_PREFIX=$(brew --prefix llvm@${v})" >> $GITHUB_ENV
46 | echo "/opt/homebrew/opt/llvm@${v}/bin" >> $GITHUB_PATH
47 | - name: llvm-config
48 | run: llvm-config --version --bindir --libdir
49 | - uses: dtolnay/rust-toolchain@master
50 | with:
51 | toolchain: ${{ matrix.toolchain }}
52 | - uses: Swatinem/rust-cache@v2
53 | with:
54 | cache-on-failure: true
55 | - uses: taiki-e/install-action@nextest
56 | - name: test
57 | run: cargo nextest run --workspace --cargo-profile ${{ matrix.profile }} --features ${{ env.ALL_BACKENDS }}
58 |
59 | feature-checks:
60 | runs-on: ubuntu-latest
61 | timeout-minutes: 30
62 | steps:
63 | - uses: actions/checkout@v4
64 | - name: Install LLVM
65 | run: sudo .github/scripts/install_llvm_ubuntu.sh ${{ env.LLVM_VERSION }}
66 | - uses: dtolnay/rust-toolchain@stable
67 | - uses: taiki-e/install-action@cargo-hack
68 | - uses: Swatinem/rust-cache@v2
69 | with:
70 | cache-on-failure: true
71 | - name: cargo hack
72 | run: |
73 | cargo hack check --feature-powerset --depth 2 --workspace \
74 | --skip llvm-prefer-static --skip prefer-static
75 |
76 | clippy:
77 | runs-on: ubuntu-latest
78 | timeout-minutes: 30
79 | steps:
80 | - uses: actions/checkout@v4
81 | - name: Install LLVM
82 | run: sudo .github/scripts/install_llvm_ubuntu.sh ${{ env.LLVM_VERSION }}
83 | - uses: dtolnay/rust-toolchain@clippy
84 | - uses: Swatinem/rust-cache@v2
85 | with:
86 | cache-on-failure: true
87 | - run: cargo clippy --workspace --all-targets --features ${{ env.ALL_BACKENDS }}
88 | env:
89 | RUSTFLAGS: -Dwarnings
90 |
91 | docs:
92 | runs-on: ubuntu-latest
93 | timeout-minutes: 30
94 | permissions:
95 | contents: write
96 | steps:
97 | - uses: actions/checkout@v4
98 | - name: Install LLVM
99 | run: sudo .github/scripts/install_llvm_ubuntu.sh ${{ env.LLVM_VERSION }}
100 | - uses: dtolnay/rust-toolchain@nightly
101 | - uses: Swatinem/rust-cache@v2
102 | with:
103 | cache-on-failure: true
104 | - run: cargo doc --workspace --features ${{ env.ALL_BACKENDS }} --no-deps --document-private-items
105 | env:
106 | RUSTDOCFLAGS: --cfg docsrs -D warnings --show-type-layout --generate-link-to-definition --enable-index-page -Zunstable-options
107 | - name: Deploy documentation
108 | uses: peaceiris/actions-gh-pages@v4
109 | if: github.event_name == 'push' && github.ref == 'refs/heads/main'
110 | with:
111 | github_token: ${{ secrets.GITHUB_TOKEN }}
112 | publish_dir: target/doc
113 | force_orphan: true
114 |
115 | fmt:
116 | runs-on: ubuntu-latest
117 | timeout-minutes: 30
118 | steps:
119 | - uses: actions/checkout@v4
120 | - uses: dtolnay/rust-toolchain@nightly
121 | with:
122 | components: rustfmt
123 | - run: cargo fmt --all --check
124 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | clif
3 | .vscode
4 | .idea
5 | /tmp
6 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
8 | ## [0.1.0](https://github.com/paradigmxyz/revmc/releases/tag/v0.1.0) - 2024-06-27
9 |
10 | ### Bug Fixes
11 |
12 | - Use `effective_gas_price` in GASPRICE ([#32](https://github.com/paradigmxyz/revmc/issues/32))
13 | - Dereferenceable attribute on builtins ([#25](https://github.com/paradigmxyz/revmc/issues/25))
14 | - Memory leak in LLVM diagnostic handler
15 | - Manually delete LLVM IR to reduce memory consumption
16 | - End a section at SSTORE too (EIP-1706) ([#21](https://github.com/paradigmxyz/revmc/issues/21))
17 | - Build script for macos ([#20](https://github.com/paradigmxyz/revmc/issues/20))
18 | - Linker args
19 | - Stack overflows
20 | - Make comments work on instrs ([#7](https://github.com/paradigmxyz/revmc/issues/7))
21 | - Correct linker flag
22 | - Pass -zundefs to linker
23 | - Only allow setting compilation mode at instantiation
24 | - Features 2
25 | - Features
26 | - Stack io for suspending instructions
27 | - Fix some TODOs
28 | - Ci
29 | - Add rust-src component to rust-toolchain.toml
30 |
31 | ### Dependencies
32 |
33 | - Bump revm
34 | - [deps] Bump all
35 | - Bump revm to 9
36 | - [deps] Cargo update
37 | - Bump to revm main
38 | - Bump revm, fix gas overflows
39 | - Bump
40 | - Bump
41 | - Bump revm patch
42 |
43 | ### Documentation
44 |
45 | - Add changelogs
46 | - Add requirements and examples ([#29](https://github.com/paradigmxyz/revmc/issues/29))
47 | - Wording
48 |
49 | ### Features
50 |
51 | - Add target configuration ([#39](https://github.com/paradigmxyz/revmc/issues/39))
52 | - Improve DX around statically linked bytecodes, add example ([#37](https://github.com/paradigmxyz/revmc/issues/37))
53 | - Implement IR builtins ([#36](https://github.com/paradigmxyz/revmc/issues/36))
54 | - Make builtins no_std ([#27](https://github.com/paradigmxyz/revmc/issues/27))
55 | - Add llvm-prefer-* features ([#24](https://github.com/paradigmxyz/revmc/issues/24))
56 | - More LLVM overrides ([#23](https://github.com/paradigmxyz/revmc/issues/23))
57 | - Add another benchmark ([#22](https://github.com/paradigmxyz/revmc/issues/22))
58 | - Allow running after aot
59 | - [llvm] Use intel syntax for x86 assembly, set diagnostic handler
60 | - Build script support, fix revm update fallout
61 | - Allow disabling stack length overflow checks
62 | - Add linker helper
63 | - Also build builtins as a dynamic library
64 | - Implement AOT compilation
65 | - New backend API ([#3](https://github.com/paradigmxyz/revmc/issues/3))
66 | - Pass most statetest tests
67 | - Implement CALL instructions
68 | - Pre-built callbacks bitcode module
69 | - Consolidate stack IO calculations, fix some instructions
70 | - Add LLVM ORC wrappers
71 | - Unify returns, implement suspend/resume for CALL/CREATE
72 | - Implement dynamic jumps
73 | - Finish core instruction implementations and tests (2/2)
74 | - Finish core instruction implementations and tests (1)
75 | - Implement most instructions (untested)
76 | - Implement some env-fetching instructions
77 | - Don't allocate tmp in SWAP
78 | - Move attributes to the builder trait
79 | - Implement GAS, add some more tests
80 | - Ignore unreachable `JUMPDEST`s in DCE
81 | - Basic DCE, restructure bytecode module
82 | - Implement SIGNEXTEND, refactor test suite
83 | - Implement opcode info ourselves
84 | - Implement callbacks
85 | - Criterion bench
86 | - Debug_assertions/panics, function call hooks, gas in tests, benchmarks
87 | - Implement basic gas accounting
88 | - Test fibonacci
89 | - Codegen jumps
90 | - Internal bytecode representation, add more docs
91 | - Implement cold blocks in llvm
92 | - More implementations, add tests
93 | - Core backend abstraction, add LLVM
94 | - Basic implementation
95 |
96 | ### Miscellaneous Tasks
97 |
98 | - Exclude examples from auto changelogs
99 | - Replace timing macros with tracy ([#40](https://github.com/paradigmxyz/revmc/issues/40))
100 | - Add release configuration
101 | - Update some comments
102 | - Rebrand to `revmc` ([#33](https://github.com/paradigmxyz/revmc/issues/33))
103 | - Patch revm to reth pin ([#31](https://github.com/paradigmxyz/revmc/issues/31))
104 | - Add more benchmarks
105 | - Remove config file
106 | - Add a TODO
107 | - Make DIFFICULTY a builtin ([#26](https://github.com/paradigmxyz/revmc/issues/26))
108 | - Add some todos
109 | - Comment
110 | - Adjustments
111 | - Update benchmarks
112 | - Update revm gas ([#17](https://github.com/paradigmxyz/revmc/issues/17))
113 | - Cleanups
114 | - Add iai-callgrind benchmarks ([#14](https://github.com/paradigmxyz/revmc/issues/14))
115 | - Fix some TODOs, cleanup ([#11](https://github.com/paradigmxyz/revmc/issues/11))
116 | - [jit] Use return_imm builder method ([#9](https://github.com/paradigmxyz/revmc/issues/9))
117 | - Remove one-array
118 | - Rewrite native fibonacci
119 | - Dedup code
120 | - Renames, remove unnecessary dll linking, clippy
121 | - Adjustments
122 | - Update to latest revm
123 | - Minor updates
124 | - Split compiler.rs
125 | - Update to latest revm main
126 | - Make utils private again
127 | - Rename callback to builtin
128 | - Move all callbacks to the crate
129 | - Remove precompiled bitcode
130 | - Improve call_with_interpreter
131 | - Update
132 | - Pass spec_id after stack pointer
133 | - Default stack config to true
134 | - Separate opcode (bytes) from instructions (higher level)
135 | - Core -> backend
136 | - Clippy
137 |
138 | ### Other
139 |
140 | - Update README.md
141 | - Add config file w/ top 250 contracts
142 | - Fix iai on push ([#18](https://github.com/paradigmxyz/revmc/issues/18))
143 | - Run macos in CI ([#6](https://github.com/paradigmxyz/revmc/issues/6))
144 | - Fix checks
145 | - Merge pull request [#2](https://github.com/paradigmxyz/revmc/issues/2) from DaniPopes/remove-precompiled-bitcode
146 | - Callbacks bitcode
147 | - Add callbacks crate
148 | - Merge pull request [#1](https://github.com/paradigmxyz/revmc/issues/1) from DaniPopes/fighting-ci
149 | - Print llvm-config
150 | - Download llvm
151 | - Restructure
152 | - Stuff
153 | - Initial commit
154 |
155 | ### Performance
156 |
157 | - Pay base gas of dynamic opcodes in sections ([#19](https://github.com/paradigmxyz/revmc/issues/19))
158 | - Re-order gas check ([#15](https://github.com/paradigmxyz/revmc/issues/15))
159 | - Fix resume switch branch weights
160 | - Set weight metadata on switches too ([#13](https://github.com/paradigmxyz/revmc/issues/13))
161 | - Set branch weight metadata instead of cold blocks ([#12](https://github.com/paradigmxyz/revmc/issues/12))
162 | - Add attributes to builtin functions' params ([#8](https://github.com/paradigmxyz/revmc/issues/8))
163 | - Unify failure blocks ([#5](https://github.com/paradigmxyz/revmc/issues/5))
164 | - Batch gas and stack length checks ([#4](https://github.com/paradigmxyz/revmc/issues/4))
165 |
166 | ### Styling
167 |
168 | - Clippy, add more lints ([#34](https://github.com/paradigmxyz/revmc/issues/34))
169 | - Fmt
170 |
171 | ### Testing
172 |
173 | - Ensure SELFDESTRUCT stops execution
174 | - Add codegen tests ([#10](https://github.com/paradigmxyz/revmc/issues/10))
175 | - Test suspend-resume
176 | - Cross-reference with revm interpreter
177 |
178 |
179 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | members = ["crates/*", "examples/*", "fuzz"]
3 | default-members = ["crates/revmc-cli"]
4 | resolver = "2"
5 |
6 | [workspace.package]
7 | version = "0.1.0"
8 | authors = ["DaniPopes <57450786+DaniPopes@users.noreply.github.com>"]
9 | edition = "2021"
10 | rust-version = "1.79"
11 | license = "MIT OR Apache-2.0"
12 | categories = ["compilers", "cryptography::cryptocurrencies"]
13 | keywords = ["compile", "compiler", "jit", "evm", "ethereum"]
14 | homepage = "https://github.com/danipopes/cranelift-jit-evm"
15 | repository = "https://github.com/danipopes/cranelift-jit-evm"
16 | exclude = [".github/", "benches/", "fuzz/", "tests/"]
17 |
18 | [workspace.lints.clippy]
19 | dbg-macro = "warn"
20 | manual-string-new = "warn"
21 | uninlined-format-args = "warn"
22 | use-self = "warn"
23 | redundant-clone = "warn"
24 |
25 | [workspace.lints.rust]
26 | missing-debug-implementations = "warn"
27 | missing-docs = "warn"
28 | rust-2018-idioms = "warn"
29 | unreachable-pub = "warn"
30 | unused-must-use = "warn"
31 | redundant-lifetimes = "warn"
32 | unnameable-types = "warn"
33 |
34 | [workspace.lints.rustdoc]
35 | all = "warn"
36 |
37 | [workspace.dependencies]
38 | revmc = { version = "0.1.0", path = "crates/revmc", default-features = false }
39 | revmc-backend = { version = "0.1.0", path = "crates/revmc-backend", default-features = false }
40 | revmc-build = { version = "0.1.0", path = "crates/revmc-build", default-features = false }
41 | revmc-builtins = { version = "0.1.0", path = "crates/revmc-builtins", default-features = false }
42 | revmc-context = { version = "0.1.0", path = "crates/revmc-context", default-features = false }
43 | revmc-cranelift = { version = "0.1.0", path = "crates/revmc-cranelift", default-features = false }
44 | revmc-llvm = { version = "0.1.0", path = "crates/revmc-llvm", default-features = false }
45 |
46 | alloy-primitives = { version = "0.8", default-features = false }
47 | revm = { version = "19.0", default-features = false }
48 | revm-primitives = { version = "15.1", default-features = false }
49 | revm-interpreter = { version = "15.0", default-features = false }
50 | ruint = { version = "1.12", default-features = false }
51 |
52 | color-eyre = "0.6"
53 | eyre = "0.6"
54 | rustc-hash = "2.1"
55 | tracing = "0.1"
56 | tracing-subscriber = "0.3"
57 | tracing-tracy = "0.11"
58 | paste = "1.0"
59 |
60 | [profile.release]
61 | opt-level = 3
62 | lto = true
63 | debug = "line-tables-only"
64 | strip = true
65 | panic = "abort"
66 | codegen-units = 1
67 |
68 | # Use the `--profile profiling` flag to show symbols in release mode.
69 | # e.g. `cargo build --profile profiling`
70 | [profile.profiling]
71 | inherits = "release"
72 | debug = 2
73 | strip = false
74 |
75 | [profile.bench]
76 | inherits = "profiling"
77 |
--------------------------------------------------------------------------------
/LICENSE-APACHE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
--------------------------------------------------------------------------------
/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | Permission is hereby granted, free of charge, to any
2 | person obtaining a copy of this software and associated
3 | documentation files (the "Software"), to deal in the
4 | Software without restriction, including without
5 | limitation the rights to use, copy, modify, merge,
6 | publish, distribute, sublicense, and/or sell copies of
7 | the Software, and to permit persons to whom the Software
8 | is furnished to do so, subject to the following
9 | conditions:
10 |
11 | The above copyright notice and this permission notice
12 | shall be included in all copies or substantial portions
13 | of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23 | DEALINGS IN THE SOFTWARE.
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # revmc
2 |
3 | Experimental [JIT] and [AOT] compiler for the [Ethereum Virtual Machine][EVM].
4 |
5 | The compiler implementation is abstracted over an intermediate representation backend. It performs very well, as demonstrated below from our criterion benchmarks, and exposes an intuitive API via Revm.
6 |
7 | 
8 |
9 | This repository hosts two backend implementations:
10 | - [LLVM] ([`revmc-llvm`]): main backend with full test coverage;
11 | - [Cranelift] ([`revmc-cranelift`]); currently not functional due to missing `i256` support in Cranelift. This will likely require a custom fork of Cranelift.
12 |
13 | [JIT]: https://en.wikipedia.org/wiki/Just-in-time_compilation
14 | [AOT]: https://en.wikipedia.org/wiki/Ahead-of-time_compilation
15 | [EVM]: https://ethereum.org/en/developers/docs/evm/
16 | [LLVM]: https://llvm.org/
17 | [`revmc-llvm`]: /crates/revmc-llvm
18 | [Cranelift]: https://cranelift.dev/
19 | [`revmc-cranelift`]: /crates/revmc-cranelift
20 |
21 | ## Requirements
22 |
23 | - Latest stable Rust version
24 |
25 | ### LLVM backend
26 |
27 | - Linux or macOS, Windows is not supported
28 | - LLVM 18
29 | - On Debian-based Linux distros: see [apt.llvm.org](https://apt.llvm.org/)
30 | - On Arch-based Linux distros: `pacman -S llvm`
31 | - On macOS: `brew install llvm@18`
32 | - The following environment variables may be required:
33 | ```bash
34 | prefix=$(llvm-config --prefix)
35 | # or
36 | #prefix=$(llvm-config-18 --prefix)
37 | # on macOS:
38 | #prefix=$(brew --prefix llvm@18)
39 | export LLVM_SYS_180_PREFIX=$prefix
40 | ```
41 |
42 | ## Usage
43 |
44 | The compiler is implemented as a library and can be used as such through the `revmc` crate.
45 |
46 | A minimal runtime is required to run AOT-compiled bytecodes. A default runtime implementation is
47 | provided through symbols exported in the `revmc-builtins` crate and must be exported in the final
48 | binary. This can be achieved with the following build script:
49 | ```rust,ignore
50 | fn main() {
51 | revmc_build::emit();
52 | }
53 | ```
54 |
55 | You can check out the [examples](/examples) directory for example usage.
56 |
57 | ## Credits
58 |
59 | The initial compiler implementation was inspired by [`paradigmxyz/jitevm`](https://github.com/paradigmxyz/jitevm).
60 |
61 | #### License
62 |
63 |
64 | Licensed under either of Apache License, Version
65 | 2.0 or MIT license at your option.
66 |
67 |
68 |
69 |
70 |
71 | Unless you explicitly state otherwise, any contribution intentionally submitted
72 | for inclusion in these crates by you, as defined in the Apache-2.0 license,
73 | shall be dual licensed as above, without any additional terms or conditions.
74 |
75 |
--------------------------------------------------------------------------------
/cliff.toml:
--------------------------------------------------------------------------------
1 | # Configuration file for [`git-cliff`](https://github.com/orhun/git-cliff)
2 | # See https://git-cliff.org/docs/configuration
3 |
4 | [remote.github]
5 | owner = "paradigmxyz"
6 | repo = "revmc"
7 |
8 | [changelog]
9 | header = """
10 | # Changelog
11 |
12 | All notable changes to this project will be documented in this file.
13 |
14 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
15 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n
16 | """
17 | # https://keats.github.io/tera/docs/#introduction
18 | body = """\
19 | {% set gh_link = "https://github.com/" ~ remote.github.owner ~ "/" ~ remote.github.repo %}\
20 | {% if version %}\
21 | ## [{{ version | trim_start_matches(pat="v") }}]({{ gh_link }}/releases/tag/v{{ version | trim_start_matches(pat="v") }}) - {{ timestamp | date(format="%Y-%m-%d") }}
22 | {% else %}\
23 | ## [Unreleased]({{ gh_link }}/compare/{{ previous.version }}...HEAD)
24 | {% endif %}\
25 | {% for group, commits in commits | group_by(attribute="group") %}
26 | ### {{ group | title }}
27 | {% for commit in commits %}
28 | - {% if commit.scope %}[{{ commit.scope }}] {% endif %}{{ commit.message | upper_first | split(pat="\\n") | first }}\
29 | {% endfor %}
30 | {% endfor %}\n
31 | """
32 | trim = true
33 | footer = """
34 |
35 | """
36 |
37 | [git]
38 | conventional_commits = true
39 | filter_unconventional = false
40 | commit_preprocessors = [
41 | { pattern = '#(\d+)', replace = "[#$1](https://github.com/paradigmxyz/revmc/issues/$1)" },
42 | ]
43 | commit_parsers = [
44 | { message = "^[Ff]eat", group = "Features" },
45 | { message = "^[Ff]ix", group = "Bug Fixes" },
46 | { message = "^[Dd]oc", group = "Documentation" },
47 | { message = ".*\\b([Dd]eps|[Dd]ependencies|[Bb]ump)\\b", group = "Dependencies" },
48 | { message = "^[Pp]erf", group = "Performance" },
49 | { message = "^[Rr]efactor", group = "Refactor" },
50 | { message = ".*\\b([Ss]tyle|[Ff]mt|[Ff]ormat)\\b", group = "Styling" },
51 | { message = "^[Tt]est", group = "Testing" },
52 | { message = "^[Cc]hore", group = "Miscellaneous Tasks" },
53 |
54 | { message = ".*", group = "Other" },
55 | ]
56 | protect_breaking_commits = false
57 | filter_commits = false
58 | tag_pattern = "v[0-9]*"
59 | skip_tags = "beta|alpha"
60 | ignore_tags = "rc"
61 | sort_commits = "newest"
62 |
--------------------------------------------------------------------------------
/clippy.toml:
--------------------------------------------------------------------------------
1 | msrv = "1.79.0"
2 |
--------------------------------------------------------------------------------
/crates/revmc-backend/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
8 | ## [0.1.0](https://github.com/paradigmxyz/revmc/releases/tag/v0.1.0) - 2024-06-27
9 |
10 | ### Documentation
11 |
12 | - Add changelogs
13 |
14 | ### Features
15 |
16 | - Add target configuration ([#39](https://github.com/paradigmxyz/revmc/issues/39))
17 | - Implement IR builtins ([#36](https://github.com/paradigmxyz/revmc/issues/36))
18 |
19 | ### Miscellaneous Tasks
20 |
21 | - Replace timing macros with tracy ([#40](https://github.com/paradigmxyz/revmc/issues/40))
22 | - Rebrand to `revmc` ([#33](https://github.com/paradigmxyz/revmc/issues/33))
23 |
24 |
25 |
--------------------------------------------------------------------------------
/crates/revmc-backend/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "revmc-backend"
3 | description = "EVM bytecode compiler backend abstraction"
4 | homepage = "https://github.com/danipopes/revmc/tree/main/crates/revmc-backend"
5 |
6 | version.workspace = true
7 | authors.workspace = true
8 | edition.workspace = true
9 | rust-version.workspace = true
10 | license.workspace = true
11 | categories.workspace = true
12 | keywords.workspace = true
13 | repository.workspace = true
14 | exclude.workspace = true
15 |
16 | [lints]
17 | workspace = true
18 |
19 | [dependencies]
20 | ruint = { workspace = true, features = ["std"] }
21 | eyre.workspace = true
22 |
--------------------------------------------------------------------------------
/crates/revmc-backend/README.md:
--------------------------------------------------------------------------------
1 | # revmc-backend
2 |
3 | EVM bytecode compiler backend abstraction.
4 |
--------------------------------------------------------------------------------
/crates/revmc-backend/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![doc = include_str!("../README.md")]
2 | #![allow(missing_docs)]
3 | #![cfg_attr(not(test), warn(unused_extern_crates))]
4 | #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
5 |
6 | mod traits;
7 | pub use traits::*;
8 |
9 | #[doc(no_inline)]
10 | pub use eyre;
11 | #[doc(no_inline)]
12 | pub use ruint::{self, aliases::U256, uint};
13 |
14 | mod pointer;
15 | pub use pointer::{Pointer, PointerBase};
16 |
17 | /// Compilation result.
18 | pub type Result = std::result::Result;
19 |
20 | /// Compilation error.
21 | pub type Error = eyre::Error;
22 |
--------------------------------------------------------------------------------
/crates/revmc-backend/src/pointer.rs:
--------------------------------------------------------------------------------
1 | use crate::Builder;
2 |
3 | /// A pointer to a value.
4 | #[derive(Clone, Copy, Debug)]
5 | pub struct Pointer {
6 | /// The type of the pointee.
7 | pub ty: B::Type,
8 | /// The base of the pointer. Either an address or a stack slot.
9 | pub base: PointerBase,
10 | }
11 |
12 | /// The base of a pointer. Either an address or a stack slot.
13 | #[derive(Clone, Copy, Debug)]
14 | pub enum PointerBase {
15 | /// An address.
16 | Address(B::Value),
17 | /// A stack slot.
18 | StackSlot(B::StackSlot),
19 | }
20 |
21 | impl Pointer {
22 | /// Creates a new stack-allocated pointer.
23 | pub fn new_stack_slot(bcx: &mut B, ty: B::Type, name: &str) -> Self {
24 | let slot = bcx.new_stack_slot_raw(ty, name);
25 | Self { ty, base: PointerBase::StackSlot(slot) }
26 | }
27 |
28 | /// Creates a new address pointer.
29 | pub fn new_address(ty: B::Type, value: B::Value) -> Self {
30 | Self { ty, base: PointerBase::Address(value) }
31 | }
32 |
33 | /// Returns `true` if the pointer is an address.
34 | pub fn is_address(&self) -> bool {
35 | matches!(self.base, PointerBase::Address(_))
36 | }
37 |
38 | /// Returns `true` if the pointer is a stack slot.
39 | pub fn is_stack_slot(&self) -> bool {
40 | matches!(self.base, PointerBase::StackSlot(_))
41 | }
42 |
43 | /// Converts the pointer to an address.
44 | pub fn into_address(self) -> Option {
45 | match self.base {
46 | PointerBase::Address(ptr) => Some(ptr),
47 | PointerBase::StackSlot(_) => None,
48 | }
49 | }
50 |
51 | /// Converts the pointer to a stack slot.
52 | pub fn into_stack_slot(self) -> Option {
53 | match self.base {
54 | PointerBase::Address(_) => None,
55 | PointerBase::StackSlot(slot) => Some(slot),
56 | }
57 | }
58 |
59 | /// Loads the value from the pointer.
60 | pub fn load(&self, bcx: &mut B, name: &str) -> B::Value {
61 | match self.base {
62 | PointerBase::Address(ptr) => bcx.load(self.ty, ptr, name),
63 | PointerBase::StackSlot(slot) => bcx.stack_load(self.ty, slot, name),
64 | }
65 | }
66 |
67 | /// Stores the value to the pointer.
68 | pub fn store(&self, bcx: &mut B, value: B::Value) {
69 | match self.base {
70 | PointerBase::Address(ptr) => bcx.store(value, ptr),
71 | PointerBase::StackSlot(slot) => bcx.stack_store(value, slot),
72 | }
73 | }
74 |
75 | /// Stores the value to the pointer.
76 | pub fn store_imm(&self, bcx: &mut B, value: i64) {
77 | let value = bcx.iconst(self.ty, value);
78 | self.store(bcx, value)
79 | }
80 |
81 | /// Gets the address of the pointer.
82 | pub fn addr(&self, bcx: &mut B) -> B::Value {
83 | match self.base {
84 | PointerBase::Address(ptr) => ptr,
85 | PointerBase::StackSlot(slot) => bcx.stack_addr(self.ty, slot),
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/crates/revmc-build/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
8 | ## [0.1.0](https://github.com/paradigmxyz/revmc/releases/tag/v0.1.0) - 2024-06-27
9 |
10 | ### Documentation
11 |
12 | - Add changelogs
13 |
14 | ### Miscellaneous Tasks
15 |
16 | - Update some comments
17 | - Rebrand to `revmc` ([#33](https://github.com/paradigmxyz/revmc/issues/33))
18 |
19 |
20 |
--------------------------------------------------------------------------------
/crates/revmc-build/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "revmc-build"
3 | description = "EVM bytecode compiler build script support"
4 | homepage = "https://github.com/danipopes/revmc/tree/main/crates/revmc-build"
5 |
6 | version.workspace = true
7 | authors.workspace = true
8 | edition.workspace = true
9 | rust-version.workspace = true
10 | license.workspace = true
11 | categories.workspace = true
12 | keywords.workspace = true
13 | repository.workspace = true
14 | exclude = ["build.rs"]
15 |
16 | [lints]
17 | workspace = true
18 |
--------------------------------------------------------------------------------
/crates/revmc-build/README.md:
--------------------------------------------------------------------------------
1 | # revmc-build
2 |
3 | EVM bytecode compiler build script support.
4 |
--------------------------------------------------------------------------------
/crates/revmc-build/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![doc = include_str!("../README.md")]
2 | #![cfg_attr(not(test), warn(unused_extern_crates))]
3 | #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
4 |
5 | // Must be kept in sync with `remvc-builtins`.
6 | const MANGLE_PREFIX: &str = "__revmc_builtin_";
7 |
8 | /// Emits the linker flag to export all the necessary symbols.
9 | pub fn emit() {
10 | let target_vendor = std::env::var("CARGO_CFG_TARGET_VENDOR").unwrap();
11 | let flag =
12 | if target_vendor == "apple" { "-exported_symbol" } else { "--export-dynamic-symbol" };
13 | println!("cargo:rustc-link-arg=-Wl,{flag},{MANGLE_PREFIX}*");
14 | }
15 |
--------------------------------------------------------------------------------
/crates/revmc-builtins/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
8 | ## [0.1.0](https://github.com/paradigmxyz/revmc/releases/tag/v0.1.0) - 2024-06-27
9 |
10 | ### Documentation
11 |
12 | - Add changelogs
13 |
14 | ### Features
15 |
16 | - Implement IR builtins ([#36](https://github.com/paradigmxyz/revmc/issues/36))
17 |
18 | ### Miscellaneous Tasks
19 |
20 | - Update some comments
21 | - Rebrand to `revmc` ([#33](https://github.com/paradigmxyz/revmc/issues/33))
22 |
23 | ### Styling
24 |
25 | - Clippy, add more lints ([#34](https://github.com/paradigmxyz/revmc/issues/34))
26 |
27 |
28 |
--------------------------------------------------------------------------------
/crates/revmc-builtins/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "revmc-builtins"
3 | description = "EVM bytecode compiler builtins"
4 | homepage = "https://github.com/danipopes/revmc/tree/main/crates/revmc-builtins"
5 |
6 | version.workspace = true
7 | authors.workspace = true
8 | edition.workspace = true
9 | rust-version.workspace = true
10 | license.workspace = true
11 | categories.workspace = true
12 | keywords.workspace = true
13 | repository.workspace = true
14 | exclude.workspace = true
15 |
16 | [lints]
17 | workspace = true
18 |
19 | [dependencies]
20 | revmc-context.workspace = true
21 |
22 | revm-primitives.workspace = true
23 | revm-interpreter.workspace = true
24 | paste.workspace = true
25 |
26 | revmc-backend = { workspace = true, optional = true }
27 | tracing = { workspace = true, optional = true }
28 |
29 | [features]
30 | default = ["std", "ir"]
31 | std = ["revmc-context/std", "revm-primitives/std", "revm-interpreter/std"]
32 | ir = ["std", "dep:tracing", "dep:revmc-backend"]
33 |
--------------------------------------------------------------------------------
/crates/revmc-builtins/README.md:
--------------------------------------------------------------------------------
1 | # revmc-builtins
2 |
3 | EVM bytecode compiler builtins.
4 |
--------------------------------------------------------------------------------
/crates/revmc-builtins/src/gas.rs:
--------------------------------------------------------------------------------
1 | //! Gas calculation utilities.
2 |
3 | use revm_primitives::{SpecId, U256};
4 |
5 | pub use revm_interpreter::gas::*;
6 |
7 | /// `const` Option `?`.
8 | #[allow(unused_macros)]
9 | macro_rules! tri {
10 | ($e:expr) => {
11 | match $e {
12 | Some(v) => v,
13 | None => return None,
14 | }
15 | };
16 | }
17 |
18 | // These are overridden to only account for the dynamic cost.
19 |
20 | /// `EXP` opcode cost calculation.
21 | #[inline]
22 | pub fn dyn_exp_cost(spec_id: SpecId, power: U256) -> Option {
23 | #[inline]
24 | const fn log2floor(value: U256) -> u64 {
25 | let mut l: u64 = 256;
26 | let mut i = 3;
27 | loop {
28 | if value.as_limbs()[i] == 0u64 {
29 | l -= 64;
30 | } else {
31 | l -= value.as_limbs()[i].leading_zeros() as u64;
32 | if l == 0 {
33 | return l;
34 | } else {
35 | return l - 1;
36 | }
37 | }
38 | if i == 0 {
39 | break;
40 | }
41 | i -= 1;
42 | }
43 | l
44 | }
45 |
46 | if power == U256::ZERO {
47 | Some(0)
48 | } else {
49 | // EIP-160: EXP cost increase
50 | let gas_byte =
51 | U256::from(if spec_id.is_enabled_in(SpecId::SPURIOUS_DRAGON) { 50 } else { 10 });
52 | let gas = gas_byte.checked_mul(U256::from(log2floor(power) / 8 + 1))?;
53 | u64::try_from(gas).ok()
54 | }
55 | }
56 |
57 | /// `LOG` opcode cost calculation.
58 | #[inline]
59 | pub const fn dyn_log_cost(len: u64) -> Option {
60 | LOGDATA.checked_mul(len)
61 | }
62 |
63 | /// `KECCAK256` opcode cost calculation.
64 | #[inline]
65 | pub const fn dyn_keccak256_cost(len: u64) -> Option {
66 | cost_per_word(len, KECCAK256WORD)
67 | }
68 |
69 | /// `*COPY` opcodes cost calculation.
70 | #[inline]
71 | pub const fn dyn_verylowcopy_cost(len: u64) -> Option {
72 | cost_per_word(len, COPY)
73 | }
74 |
75 | #[cfg(test)]
76 | mod tests {
77 | use super::*;
78 |
79 | #[test]
80 | fn exp_cost() {
81 | for (spec_id, power) in [
82 | (SpecId::CANCUN, U256::from(0)),
83 | (SpecId::CANCUN, U256::from(1)),
84 | (SpecId::CANCUN, U256::from(69)),
85 | ] {
86 | assert_eq!(
87 | super::exp_cost(spec_id, power).unwrap(),
88 | EXP + dyn_exp_cost(spec_id, power).unwrap(),
89 | );
90 | }
91 | }
92 |
93 | #[test]
94 | fn log_cost() {
95 | for n_topics in [0, 1, 2] {
96 | for len in [0, 1, 69] {
97 | assert_eq!(
98 | super::log_cost(n_topics, len).unwrap(),
99 | LOG + (LOGTOPIC * n_topics as u64) + dyn_log_cost(len).unwrap(),
100 | );
101 | }
102 | }
103 | }
104 |
105 | #[test]
106 | fn keccak256_cost() {
107 | for len in [0, 1, 69] {
108 | assert_eq!(
109 | super::keccak256_cost(len).unwrap(),
110 | KECCAK256 + dyn_keccak256_cost(len).unwrap(),
111 | );
112 | }
113 | }
114 |
115 | #[test]
116 | fn verylowcopy_cost() {
117 | for len in [0, 1, 69] {
118 | assert_eq!(
119 | super::verylowcopy_cost(len).unwrap(),
120 | VERYLOW + dyn_verylowcopy_cost(len).unwrap(),
121 | );
122 | }
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/crates/revmc-builtins/src/ir.rs:
--------------------------------------------------------------------------------
1 | use revmc_backend::{Attribute, Backend, Builder, FunctionAttributeLocation, TypeMethods};
2 |
3 | // Must be kept in sync with `remvc-build`.
4 | const MANGLE_PREFIX: &str = "__revmc_builtin_";
5 |
6 | /// Builtin cache.
7 | #[derive(Debug)]
8 | pub struct Builtins([Option; Builtin::COUNT]);
9 |
10 | impl Default for Builtins {
11 | fn default() -> Self {
12 | Self::new()
13 | }
14 | }
15 |
16 | impl Builtins {
17 | /// Create a new cache.
18 | pub fn new() -> Self {
19 | Self([None; Builtin::COUNT])
20 | }
21 |
22 | /// Clear the cache.
23 | pub fn clear(&mut self) {
24 | *self = Self::new();
25 | }
26 |
27 | /// Get the function for the given builtin.
28 | pub fn get(&mut self, builtin: Builtin, bcx: &mut B::Builder<'_>) -> B::Function {
29 | *self.0[builtin as usize].get_or_insert_with(|| Self::init(builtin, bcx))
30 | }
31 |
32 | #[cold]
33 | fn init(builtin: Builtin, bcx: &mut B::Builder<'_>) -> B::Function {
34 | let name = builtin.name();
35 | debug_assert!(name.starts_with(MANGLE_PREFIX), "{name:?}");
36 | bcx.get_function(name).inspect(|r| trace!(name, ?r, "pre-existing")).unwrap_or_else(|| {
37 | let r = Self::build(name, builtin, bcx);
38 | trace!(name, ?r, "built");
39 | r
40 | })
41 | }
42 |
43 | fn build(name: &str, builtin: Builtin, bcx: &mut B::Builder<'_>) -> B::Function {
44 | let ret = builtin.ret(bcx);
45 | let params = builtin.params(bcx);
46 | let address = builtin.addr();
47 | let linkage = revmc_backend::Linkage::Import;
48 | let f = bcx.add_function(name, ¶ms, ret, Some(address), linkage);
49 | let default_attrs: &[Attribute] = if builtin == Builtin::Panic {
50 | &[
51 | Attribute::Cold,
52 | Attribute::NoReturn,
53 | Attribute::NoFree,
54 | Attribute::NoRecurse,
55 | Attribute::NoSync,
56 | ]
57 | } else {
58 | &[
59 | Attribute::WillReturn,
60 | Attribute::NoFree,
61 | Attribute::NoRecurse,
62 | Attribute::NoSync,
63 | Attribute::NoUnwind,
64 | Attribute::Speculatable,
65 | ]
66 | };
67 | for attr in default_attrs.iter().chain(builtin.attrs()).copied() {
68 | bcx.add_function_attribute(Some(f), attr, FunctionAttributeLocation::Function);
69 | }
70 | let param_attrs = builtin.param_attrs();
71 | for (i, param_attrs) in param_attrs.iter().enumerate() {
72 | for attr in param_attrs {
73 | bcx.add_function_attribute(
74 | Some(f),
75 | *attr,
76 | FunctionAttributeLocation::Param(i as u32),
77 | );
78 | }
79 | }
80 | f
81 | }
82 | }
83 |
84 | macro_rules! builtins {
85 | (@count) => { 0 };
86 | (@count $first:tt $(, $rest:tt)*) => { 1 + builtins!(@count $($rest),*) };
87 |
88 | (@param_attr $default:ident) => { $default() };
89 | (@param_attr $default:ident $name:expr) => { $name };
90 |
91 | (@types |$bcx:ident| { $($types_init:tt)* }
92 | @param_attrs |$op:ident| { $($attrs_init:tt)* }
93 | $($ident:ident = $(#[$attr:expr])* $name:ident($($(@[$param_attr:expr])? $params:expr),* $(,)?) $ret:expr),* $(,)?
94 | ) => { paste::paste! {
95 | /// Builtins that can be called by the compiled functions.
96 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
97 | pub enum Builtin {
98 | $($ident,)*
99 | }
100 |
101 | #[allow(unused_variables)]
102 | impl Builtin {
103 | pub const COUNT: usize = builtins!(@count $($ident),*);
104 |
105 | pub const fn name(self) -> &'static str {
106 | match self {
107 | $(Self::$ident => stringify!($name),)*
108 | }
109 | }
110 |
111 | pub fn addr(self) -> usize {
112 | match self {
113 | $(Self::$ident => crate::$name as usize,)*
114 | }
115 | }
116 |
117 | pub fn ret(self, $bcx: &mut B) -> Option {
118 | $($types_init)*
119 | match self {
120 | $(Self::$ident => $ret,)*
121 | }
122 | }
123 |
124 | pub fn params(self, $bcx: &mut B) -> Vec {
125 | $($types_init)*
126 | match self {
127 | $(Self::$ident => vec![$($params),*],)*
128 | }
129 | }
130 |
131 | pub fn attrs(self) -> &'static [Attribute] {
132 | #[allow(unused_imports)]
133 | use Attribute::*;
134 | match self {
135 | $(Self::$ident => &[$($attr)*]),*
136 | }
137 | }
138 |
139 | #[allow(non_upper_case_globals)]
140 | pub fn param_attrs(self) -> Vec> {
141 | #[allow(unused_imports)]
142 | use Attribute::*;
143 | let $op = self;
144 | let default = || vec![Attribute::NoUndef];
145 | $($attrs_init)*
146 | match self {
147 | $(Self::$ident => vec![$(builtins!(@param_attr default $($param_attr)?)),*]),*
148 | }
149 | }
150 |
151 | fn op(self) -> u8 {
152 | use revm_interpreter::opcode::*;
153 | const PANIC: u8 = 0;
154 | const LOG: u8 = LOG0;
155 | const DORETURN: u8 = RETURN;
156 | const RESIZEMEMORY: u8 = 0;
157 | const FUNCSTACKPUSH: u8 = 0;
158 | const FUNCSTACKPOP: u8 = 0;
159 | const FUNCSTACKGROW: u8 = 0;
160 |
161 | match self {
162 | $(Self::$ident => [<$ident:upper>]),*
163 | }
164 | }
165 | }
166 | }};
167 | }
168 |
169 | builtins! {
170 | @types |bcx| {
171 | let ptr = bcx.type_ptr();
172 | let usize = bcx.type_ptr_sized_int();
173 | let bool = bcx.type_int(1);
174 | let u8 = bcx.type_int(8);
175 | }
176 |
177 | @param_attrs |op| {
178 | fn size_and_align() -> Vec {
179 | size_and_align2(Some(core::mem::size_of::()), core::mem::align_of::())
180 | }
181 |
182 | fn size_and_align2(size: Option, align: usize) -> Vec {
183 | let mut vec = Vec::with_capacity(5);
184 | vec.push(Attribute::NoAlias);
185 | vec.push(Attribute::NoCapture);
186 | vec.push(Attribute::NoUndef);
187 | vec.push(Attribute::Align(align as u64));
188 | if let Some(size) = size {
189 | vec.push(Attribute::Dereferenceable(size as u64));
190 | }
191 | vec
192 | }
193 |
194 | let op = op.op();
195 | let (inputs, outputs) = if let Some(info) = revm_interpreter::opcode::OPCODE_INFO_JUMPTABLE[op as usize] {
196 | (info.inputs(), info.outputs())
197 | } else {
198 | (0, 0)
199 | };
200 |
201 | let ecx = size_and_align::>();
202 |
203 | let sp_dyn = size_and_align2(None, core::mem::align_of::());
204 |
205 | let mut sp = sp_dyn.clone();
206 | // `sp` is at `top - inputs`, we have access to `max(inputs, outputs)` words.
207 | let n_stack_words = inputs.max(outputs);
208 | let size_of_word = core::mem::size_of::();
209 | sp.push(Attribute::Dereferenceable(size_of_word as u64 * n_stack_words as u64));
210 | match (inputs, outputs) {
211 | (0, 0) => sp.push(Attribute::ReadNone),
212 | (0, 1..) => sp.push(Attribute::WriteOnly),
213 | (1.., 0) => sp.push(Attribute::ReadOnly),
214 | (1.., 1..) => {}
215 | }
216 | }
217 |
218 | Panic = __revmc_builtin_panic(ptr, usize) None,
219 |
220 | AddMod = __revmc_builtin_addmod(@[sp] ptr) None,
221 | MulMod = __revmc_builtin_mulmod(@[sp] ptr) None,
222 | Exp = __revmc_builtin_exp(@[ecx] ptr, @[sp] ptr, u8) Some(u8),
223 | Keccak256 = __revmc_builtin_keccak256(@[ecx] ptr, @[sp] ptr) Some(u8),
224 | Balance = __revmc_builtin_balance(@[ecx] ptr, @[sp] ptr, u8) Some(u8),
225 | CallDataCopy = __revmc_builtin_calldatacopy(@[ecx] ptr, @[sp] ptr) Some(u8),
226 | CodeSize = __revmc_builtin_codesize(@[ecx] ptr) Some(usize),
227 | CodeCopy = __revmc_builtin_codecopy(@[ecx] ptr, @[sp] ptr) Some(u8),
228 | GasPrice = __revmc_builtin_gas_price(@[ecx] ptr, @[sp] ptr) None,
229 | ExtCodeSize = __revmc_builtin_extcodesize(@[ecx] ptr, @[sp] ptr, u8) Some(u8),
230 | ExtCodeCopy = __revmc_builtin_extcodecopy(@[ecx] ptr, @[sp] ptr, u8) Some(u8),
231 | ReturnDataCopy = __revmc_builtin_returndatacopy(@[ecx] ptr, @[sp] ptr) Some(u8),
232 | ExtCodeHash = __revmc_builtin_extcodehash(@[ecx] ptr, @[sp] ptr, u8) Some(u8),
233 | BlockHash = __revmc_builtin_blockhash(@[ecx] ptr, @[sp] ptr) Some(u8),
234 | Difficulty = __revmc_builtin_difficulty(@[ecx] ptr, @[sp] ptr, u8) None,
235 | SelfBalance = __revmc_builtin_self_balance(@[ecx] ptr, @[sp] ptr) Some(u8),
236 | BlobHash = __revmc_builtin_blob_hash(@[ecx] ptr, @[sp] ptr) None,
237 | BlobBaseFee = __revmc_builtin_blob_base_fee(@[ecx] ptr, @[sp] ptr) None,
238 | Sload = __revmc_builtin_sload(@[ecx] ptr, @[sp] ptr, u8) Some(u8),
239 | Sstore = __revmc_builtin_sstore(@[ecx] ptr, @[sp] ptr, u8) Some(u8),
240 | Msize = __revmc_builtin_msize(@[ecx] ptr) Some(usize),
241 | Tstore = __revmc_builtin_tstore(@[ecx] ptr, @[sp] ptr) Some(u8),
242 | Tload = __revmc_builtin_tload(@[ecx] ptr, @[sp] ptr) None,
243 | Mcopy = __revmc_builtin_mcopy(@[ecx] ptr, @[sp] ptr) Some(u8),
244 | Log = __revmc_builtin_log(@[ecx] ptr, @[sp_dyn] ptr, u8) Some(u8),
245 | DataLoad = __revmc_builtin_data_load(@[ecx] ptr, @[sp] ptr) None,
246 | DataCopy = __revmc_builtin_data_copy(@[ecx] ptr, @[sp] ptr) Some(u8),
247 | ReturnDataLoad = __revmc_builtin_returndataload(@[ecx] ptr, @[sp] ptr) None,
248 |
249 | EofCreate = __revmc_builtin_eof_create(@[ecx] ptr, @[sp] ptr, usize) Some(u8),
250 | ReturnContract = __revmc_builtin_return_contract(@[ecx] ptr, @[sp] ptr, usize) Some(u8),
251 | Create = __revmc_builtin_create(@[ecx] ptr, @[sp_dyn] ptr, u8, u8) Some(u8),
252 | Call = __revmc_builtin_call(@[ecx] ptr, @[sp_dyn] ptr, u8, u8) Some(u8),
253 | ExtCall = __revmc_builtin_ext_call(@[ecx] ptr, @[sp_dyn] ptr, u8, u8) Some(u8),
254 | DoReturn = __revmc_builtin_do_return(@[ecx] ptr, @[sp] ptr, u8) Some(u8),
255 | SelfDestruct = __revmc_builtin_selfdestruct(@[ecx] ptr, @[sp] ptr, u8) Some(u8),
256 |
257 | FuncStackPush = __revmc_builtin_func_stack_push(@[ecx] ptr, ptr, usize) Some(u8),
258 | FuncStackPop = __revmc_builtin_func_stack_pop(@[ecx] ptr) Some(ptr),
259 | FuncStackGrow = __revmc_builtin_func_stack_grow(@[ecx] ptr) None,
260 |
261 | ResizeMemory = __revmc_builtin_resize_memory(@[ecx] ptr, usize) Some(u8),
262 | }
263 |
--------------------------------------------------------------------------------
/crates/revmc-builtins/src/macros.rs:
--------------------------------------------------------------------------------
1 | #[allow(unused_macros)]
2 | macro_rules! tri {
3 | ($e:expr) => {
4 | match $e {
5 | Ok(x) => x,
6 | Err(_) => return InstructionResult::InvalidOperandOOG,
7 | }
8 | };
9 | }
10 |
11 | macro_rules! try_opt {
12 | ($e:expr) => {
13 | match $e {
14 | Some(x) => x,
15 | None => return InstructionResult::InvalidOperandOOG,
16 | }
17 | };
18 | }
19 |
20 | macro_rules! try_host {
21 | ($e:expr) => {
22 | match $e {
23 | Some(x) => x,
24 | None => return InstructionResult::FatalExternalError,
25 | }
26 | };
27 | }
28 |
29 | macro_rules! try_ir {
30 | ($e:expr) => {
31 | match $e {
32 | InstructionResult::Continue => {}
33 | ir => return ir,
34 | }
35 | };
36 | }
37 |
38 | macro_rules! gas {
39 | ($ecx:expr, $gas:expr) => {
40 | if !$ecx.gas.record_cost($gas) {
41 | return InstructionResult::OutOfGas;
42 | }
43 | };
44 | }
45 |
46 | macro_rules! gas_opt {
47 | ($ecx:expr, $gas:expr) => {
48 | match $gas {
49 | Some(gas) => gas!($ecx, gas),
50 | None => return InstructionResult::OutOfGas,
51 | }
52 | };
53 | }
54 |
55 | macro_rules! ensure_non_staticcall {
56 | ($ecx:expr) => {
57 | if $ecx.is_static {
58 | return InstructionResult::StateChangeDuringStaticCall;
59 | }
60 | };
61 | }
62 |
63 | macro_rules! ensure_memory {
64 | ($ecx:expr, $offset:expr, $len:expr) => {
65 | try_ir!(ensure_memory($ecx, $offset, $len))
66 | };
67 | }
68 |
69 | /// Same as `read_words_rev`, but returns the arguments in the order they were passed.
70 | macro_rules! read_words {
71 | ($sp:expr, $($words:ident),+ $(,)?) => {
72 | let rev![$($words),+] = unsafe { read_words_rev($sp) };
73 | };
74 | }
75 |
76 | macro_rules! pop {
77 | ($sp:expr; $($x:ident),* $(,)?) => {
78 | $(
79 | $sp = $sp.sub(1);
80 | let $x = &mut *$sp;
81 | )*
82 | };
83 | }
84 |
85 | macro_rules! try_into_usize {
86 | ($x:expr) => {
87 | match $x.to_u256().as_limbs() {
88 | x => {
89 | if (x[0] > usize::MAX as u64) | (x[1] != 0) | (x[2] != 0) | (x[3] != 0) {
90 | return InstructionResult::InvalidOperandOOG;
91 | }
92 | x[0] as usize
93 | }
94 | }
95 | };
96 | }
97 |
98 | // Credits:
99 | macro_rules! rev {
100 | (@rev [$first:tt$(, $rest:tt)*] [$($rev:tt),*]) => {
101 | rev! {
102 | @rev [$($rest),*][$first $(, $rev)*]
103 | }
104 | };
105 | (@rev [] [$($rev:tt),*]) => {
106 | [$($rev)*] // NOTE: Extra `[]` to make this an array pattern.
107 | };
108 | ($($tt:tt)+) => {
109 | rev! {
110 | @rev [$($tt),+] []
111 | }
112 | };
113 | }
114 |
115 | macro_rules! debug_unreachable {
116 | ($($t:tt)*) => {
117 | if cfg!(debug_assertions) {
118 | unreachable!($($t)*);
119 | } else {
120 | unsafe { core::hint::unreachable_unchecked() };
121 | }
122 | };
123 | }
124 |
125 | macro_rules! assume {
126 | ($e:expr $(,)?) => {
127 | if !$e {
128 | debug_unreachable!(stringify!($e));
129 | }
130 | };
131 |
132 | ($e:expr, $($t:tt)+) => {
133 | if !$e {
134 | debug_unreachable!($($t)+);
135 | }
136 | };
137 | }
138 |
--------------------------------------------------------------------------------
/crates/revmc-builtins/src/utils.rs:
--------------------------------------------------------------------------------
1 | use crate::gas;
2 | use revm_interpreter::{as_usize_saturated, Gas, InstructionResult, SharedMemory};
3 | use revmc_context::{EvmContext, EvmWord};
4 |
5 | /// Splits the stack pointer into `N` elements by casting it to an array.
6 | ///
7 | /// NOTE: this returns the arguments in **reverse order**. Use [`read_words!`] to get them in order.
8 | ///
9 | /// The returned lifetime is valid for the entire duration of the builtin.
10 | ///
11 | /// # Safety
12 | ///
13 | /// Caller must ensure that `N` matches the number of elements popped in JIT code.
14 | #[inline(always)]
15 | pub(crate) unsafe fn read_words_rev<'a, const N: usize>(sp: *mut EvmWord) -> &'a mut [EvmWord; N] {
16 | &mut *sp.cast::<[EvmWord; N]>()
17 | }
18 |
19 | #[inline]
20 | pub(crate) fn ensure_memory(
21 | ecx: &mut EvmContext<'_>,
22 | offset: usize,
23 | len: usize,
24 | ) -> InstructionResult {
25 | ensure_memory_inner(ecx.memory, ecx.gas, offset, len)
26 | }
27 |
28 | #[inline]
29 | pub(crate) fn ensure_memory_inner(
30 | memory: &mut SharedMemory,
31 | gas: &mut Gas,
32 | offset: usize,
33 | len: usize,
34 | ) -> InstructionResult {
35 | let new_size = offset.saturating_add(len);
36 | if new_size > memory.len() {
37 | return resize_memory_inner(memory, gas, new_size);
38 | }
39 | InstructionResult::Continue
40 | }
41 |
42 | #[inline]
43 | pub(crate) fn resize_memory(ecx: &mut EvmContext<'_>, new_size: usize) -> InstructionResult {
44 | resize_memory_inner(ecx.memory, ecx.gas, new_size)
45 | }
46 |
47 | fn resize_memory_inner(
48 | memory: &mut SharedMemory,
49 | gas: &mut Gas,
50 | new_size: usize,
51 | ) -> InstructionResult {
52 | // TODO: Memory limit
53 | if !revm_interpreter::interpreter::resize_memory(memory, gas, new_size) {
54 | return InstructionResult::MemoryOOG;
55 | }
56 | InstructionResult::Continue
57 | }
58 |
59 | pub(crate) unsafe fn copy_operation(
60 | ecx: &mut EvmContext<'_>,
61 | rev![memory_offset, data_offset, len]: &mut [EvmWord; 3],
62 | data: &[u8],
63 | ) -> InstructionResult {
64 | let len = try_into_usize!(len);
65 | if len != 0 {
66 | gas_opt!(ecx, gas::dyn_verylowcopy_cost(len as u64));
67 | let memory_offset = try_into_usize!(memory_offset);
68 | ensure_memory!(ecx, memory_offset, len);
69 | let data_offset = data_offset.to_u256();
70 | let data_offset = as_usize_saturated!(data_offset);
71 | ecx.memory.set_data(memory_offset, data_offset, len, data);
72 | }
73 | InstructionResult::Continue
74 | }
75 |
76 | #[inline(always)]
77 | pub(crate) const unsafe fn decouple_lt<'b, T: ?Sized>(x: &T) -> &'b T {
78 | core::mem::transmute(x)
79 | }
80 |
--------------------------------------------------------------------------------
/crates/revmc-cli-tests/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
8 | ## [0.1.0](https://github.com/paradigmxyz/revmc/releases/tag/v0.1.0) - 2024-06-27
9 |
10 | ### Documentation
11 |
12 | - Add changelogs
13 |
14 | ### Miscellaneous Tasks
15 |
16 | - Rebrand to `revmc` ([#33](https://github.com/paradigmxyz/revmc/issues/33))
17 |
18 |
19 |
--------------------------------------------------------------------------------
/crates/revmc-cli-tests/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "revmc-cli-tests"
3 | description = "EVM bytecode compiler CLI tests"
4 | homepage = "https://github.com/danipopes/revmc/tree/main/crates/revmc-cli-test"
5 | publish = false
6 |
7 | version.workspace = true
8 | authors.workspace = true
9 | edition.workspace = true
10 | rust-version.workspace = true
11 | license.workspace = true
12 | categories.workspace = true
13 | keywords.workspace = true
14 | repository.workspace = true
15 | exclude.workspace = true
16 |
17 | [lints]
18 | workspace = true
19 |
20 | [dependencies]
21 | walkdir = "2.5"
22 | tester = "0.9"
23 |
--------------------------------------------------------------------------------
/crates/revmc-cli-tests/src/lib.rs:
--------------------------------------------------------------------------------
1 | //! CLI tests runner.
2 |
3 | use tester as test;
4 |
5 | use std::{
6 | fs,
7 | path::{Path, PathBuf},
8 | process::Command,
9 | sync::Arc,
10 | };
11 | use test::{ShouldPanic, TestDesc, TestDescAndFn, TestFn, TestName, TestType};
12 | use walkdir::{DirEntry, WalkDir};
13 |
14 | /// Run all tests with the given command.
15 | pub fn run_tests(cmd: &'static Path) -> i32 {
16 | let args = std::env::args().collect::>();
17 | let mut opts = match test::test::parse_opts(&args) {
18 | Some(Ok(o)) => o,
19 | Some(Err(msg)) => {
20 | eprintln!("error: {msg}");
21 | return 101;
22 | }
23 | None => return 0,
24 | };
25 |
26 | /*
27 | // Condense output if not explicitly requested.
28 | let requested_pretty = || args.iter().any(|x| x.contains("--format"));
29 | if opts.format == test::OutputFormat::Pretty && !requested_pretty() {
30 | opts.format = test::OutputFormat::Terse;
31 | }
32 | */
33 | // [`tester`] currently (0.9.1) uses `num_cpus::get_physical`;
34 | // use all available threads instead.
35 | if opts.test_threads.is_none() {
36 | opts.test_threads = std::thread::available_parallelism().map(|x| x.get()).ok();
37 | }
38 |
39 | let mut tests = Vec::new();
40 | make_tests(cmd, &mut tests);
41 | tests.sort_by(|a, b| a.desc.name.as_slice().cmp(b.desc.name.as_slice()));
42 |
43 | if opts.list {
44 | // The only way to call `list_tests_console` is through `test_main`.
45 | test::test_main(&args, tests, Some(opts.options));
46 | return 0;
47 | }
48 |
49 | match test::run_tests_console(&opts, tests) {
50 | Ok(true) => 0,
51 | Ok(false) => {
52 | eprintln!("Some tests failed");
53 | 1
54 | }
55 | Err(e) => {
56 | eprintln!("I/O failure during tests: {e}");
57 | 101
58 | }
59 | }
60 | }
61 |
62 | fn make_tests(cmd: &'static Path, tests: &mut Vec) {
63 | let config = Arc::new(Config::new(cmd));
64 |
65 | let codegen = config.root.join("tests/codegen");
66 | for entry in collect_tests(&codegen) {
67 | let config = config.clone();
68 | let path = entry.path().to_path_buf();
69 | let stripped = path.strip_prefix(config.root).unwrap();
70 | let name = stripped.display().to_string();
71 | tests.push(TestDescAndFn {
72 | desc: TestDesc {
73 | name: TestName::DynTestName(name),
74 | allow_fail: false,
75 | ignore: false,
76 | should_panic: ShouldPanic::No,
77 | test_type: TestType::Unknown,
78 | },
79 | testfn: TestFn::DynTestFn(Box::new(move || run_test(&config, &path))),
80 | });
81 | }
82 | }
83 |
84 | fn collect_tests(root: &Path) -> impl Iterator- {
85 | WalkDir::new(root)
86 | .sort_by_file_name()
87 | .into_iter()
88 | .map(Result::unwrap)
89 | .filter(|e| e.file_type().is_file())
90 | }
91 |
92 | fn run_test(config: &Config, path: &Path) {
93 | let test_name = path.file_stem().unwrap().to_str().unwrap();
94 |
95 | // let s = fs::read_to_string(path).unwrap();
96 | // let comment = if path.extension() == Some("evm".as_ref()) { "#"} else { "//"};
97 |
98 | // let lines = s.lines().map(str::trim).filter(|s| !s.is_empty());
99 | // let comments = lines.filter_map(|s| s.strip_prefix(comment)).map(str::trim_start);
100 | // let mut commands = comments.filter_map(|s| s.strip_prefix("RUN:")).map(str::trim_start);
101 | // let command = commands.next().expect("no `RUN:` directive provided");
102 | // assert!(commands.next().is_none(), "multiple `RUN:` directives provided");
103 |
104 | let build_dir = &config.build_base;
105 |
106 | let mut compiler = Command::new(config.cmd);
107 | fs::create_dir_all(build_dir).unwrap();
108 | compiler.arg(path).arg("-o").arg(build_dir);
109 | // eprintln!("running compiler: {compiler:?}");
110 | let output = compiler.output().expect("failed to run test");
111 | assert!(
112 | output.status.success(),
113 | "compiler failed with {}:\n{}",
114 | output.status,
115 | String::from_utf8_lossy(&output.stderr)
116 | );
117 | let out_dir = build_dir.join(test_name);
118 | assert!(out_dir.exists(), "no output produced");
119 |
120 | let input_path = out_dir.join("opt.ll");
121 |
122 | let mut filecheck = Command::new(config.filecheck.as_deref().unwrap_or("FileCheck".as_ref()));
123 | filecheck.arg(path).arg("--input-file").arg(&input_path);
124 | // eprintln!("running filecheck: {filecheck:?}");
125 | let output = filecheck.output().expect("failed to run FileCheck");
126 | assert!(
127 | output.status.success(),
128 | "FileCheck failed with {}:\n{}",
129 | output.status,
130 | String::from_utf8_lossy(&output.stderr)
131 | );
132 | }
133 |
134 | struct Config {
135 | cmd: &'static Path,
136 | root: &'static Path,
137 | build_base: PathBuf,
138 | filecheck: Option,
139 | }
140 |
141 | impl Config {
142 | fn new(cmd: &'static Path) -> Self {
143 | let root = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent().unwrap();
144 | let build_base = root.join("target/tester");
145 | fs::create_dir_all(&build_base).unwrap();
146 | Self { root, cmd, build_base, filecheck: None }
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/crates/revmc-cli/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
8 | ## [0.1.0](https://github.com/paradigmxyz/revmc/releases/tag/v0.1.0) - 2024-06-27
9 |
10 | ### Documentation
11 |
12 | - Add changelogs
13 |
14 | ### Features
15 |
16 | - Add target configuration ([#39](https://github.com/paradigmxyz/revmc/issues/39))
17 |
18 | ### Miscellaneous Tasks
19 |
20 | - Replace timing macros with tracy ([#40](https://github.com/paradigmxyz/revmc/issues/40))
21 | - Rebrand to `revmc` ([#33](https://github.com/paradigmxyz/revmc/issues/33))
22 |
23 | ### Styling
24 |
25 | - Clippy, add more lints ([#34](https://github.com/paradigmxyz/revmc/issues/34))
26 |
27 |
28 |
--------------------------------------------------------------------------------
/crates/revmc-cli/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "revmc-cli"
3 | description = "EVM bytecode compiler CLI"
4 | homepage = "https://github.com/danipopes/revmc/tree/main/crates/revmc-cli"
5 | publish = false
6 |
7 | version.workspace = true
8 | authors.workspace = true
9 | edition.workspace = true
10 | rust-version.workspace = true
11 | license.workspace = true
12 | categories.workspace = true
13 | keywords.workspace = true
14 | repository.workspace = true
15 | exclude.workspace = true
16 |
17 | [lib]
18 | doc = false
19 |
20 | [lints]
21 | workspace = true
22 |
23 | [dependencies]
24 | revmc.workspace = true
25 |
26 | revm-interpreter = { workspace = true, features = ["parse"] }
27 | revm-primitives.workspace = true
28 |
29 | clap = { version = "4", features = ["derive"] }
30 | color-eyre.workspace = true
31 | libloading = "0.8"
32 | tracing-subscriber = { workspace = true, features = ["fmt", "env-filter"] }
33 | tracing-tracy = { workspace = true, optional = true }
34 |
35 | [dev-dependencies]
36 | criterion = { version = "0.5", default-features = false }
37 | iai-callgrind = "0.14"
38 | revmc-cli-tests = { path = "../revmc-cli-tests/" }
39 |
40 | [build-dependencies]
41 | revmc-build.workspace = true
42 |
43 | [features]
44 | default = ["llvm-prefer-dynamic", "revmc/asm-keccak"]
45 | llvm = ["revmc/llvm"]
46 | llvm-prefer-static = ["llvm", "revmc/llvm-prefer-static"]
47 | llvm-prefer-dynamic = ["llvm", "revmc/llvm-prefer-dynamic"]
48 | cranelift = ["revmc/cranelift"]
49 |
50 | tracy = ["dep:tracing-tracy"]
51 |
52 | [[bin]]
53 | name = "revmc-cli"
54 | path = "src/main.rs"
55 | required-features = ["llvm"]
56 | doc = false
57 |
58 | [[bench]]
59 | name = "bench"
60 | path = "benches/bench.rs"
61 | required-features = ["llvm"]
62 | harness = false
63 | doc = false
64 |
65 | [[bench]]
66 | name = "iai"
67 | path = "benches/iai.rs"
68 | required-features = ["llvm"]
69 | harness = false
70 | doc = false
71 |
72 | [[test]]
73 | name = "tests"
74 | path = "tests.rs"
75 | required-features = ["llvm"]
76 | harness = false
77 | doc = false
78 |
--------------------------------------------------------------------------------
/crates/revmc-cli/benches/bench.rs:
--------------------------------------------------------------------------------
1 | #![allow(missing_docs)]
2 |
3 | use criterion::{
4 | criterion_group, criterion_main, measurement::WallTime, BenchmarkGroup, Criterion,
5 | };
6 | use revm_interpreter::SharedMemory;
7 | use revm_primitives::{Env, SpecId};
8 | use revmc::{llvm, EvmCompiler, EvmCompilerFn, EvmContext, EvmLlvmBackend, EvmStack};
9 | use revmc_cli::Bench;
10 | use std::time::Duration;
11 |
12 | const SPEC_ID: SpecId = SpecId::OSAKA;
13 |
14 | fn bench(c: &mut Criterion) {
15 | for bench in &revmc_cli::get_benches() {
16 | run_bench(c, bench);
17 | if matches!(bench.name, "hash_10k-eof") {
18 | break;
19 | }
20 | }
21 | }
22 |
23 | fn run_bench(c: &mut Criterion, bench: &Bench) {
24 | let Bench { name, bytecode, calldata, stack_input, native } = bench;
25 |
26 | let mut g = mk_group(c, name);
27 |
28 | let gas_limit = 1_000_000_000;
29 |
30 | let mut env = Env::default();
31 | env.tx.data = calldata.clone().into();
32 | env.tx.gas_limit = gas_limit;
33 |
34 | let bytecode = revm_interpreter::analysis::to_analysed(revm_primitives::Bytecode::new_raw(
35 | revm_primitives::Bytes::copy_from_slice(bytecode),
36 | ));
37 | let contract = revm_interpreter::Contract::new_env(&env, bytecode, None);
38 | let mut host = revm_interpreter::DummyHost::new(env);
39 |
40 | let bytecode = contract.bytecode.original_byte_slice();
41 |
42 | let table = &revm_interpreter::opcode::make_instruction_table::<
43 | revm_interpreter::DummyHost,
44 | revm_primitives::CancunSpec,
45 | >();
46 |
47 | // Set up the compiler.
48 | let context = llvm::inkwell::context::Context::create();
49 | let opt_level = revmc::OptimizationLevel::Aggressive;
50 | let backend = EvmLlvmBackend::new(&context, false, opt_level).unwrap();
51 | let mut compiler = EvmCompiler::new(backend);
52 | compiler.inspect_stack_length(!stack_input.is_empty());
53 | compiler.gas_metering(true);
54 |
55 | if let Some(native) = *native {
56 | g.bench_function("native", |b| b.iter(native));
57 | }
58 |
59 | let mut stack = EvmStack::new();
60 | let mut call_jit = |f: EvmCompilerFn| {
61 | for (i, input) in stack_input.iter().enumerate() {
62 | stack.as_mut_slice()[i] = input.into();
63 | }
64 | let mut stack_len = stack_input.len();
65 |
66 | let mut interpreter =
67 | revm_interpreter::Interpreter::new(contract.clone(), gas_limit, false);
68 | host.clear();
69 | let mut ecx = EvmContext::from_interpreter(&mut interpreter, &mut host);
70 |
71 | unsafe { f.call(Some(&mut stack), Some(&mut stack_len), &mut ecx) }
72 | };
73 |
74 | let jit_matrix = [
75 | ("default", (true, true)),
76 | ("no_gas", (false, true)),
77 | // ("no_stack", (true, false)),
78 | // ("no_gas_no_stack", (false, false)),
79 | ];
80 | let jit_ids = jit_matrix.map(|(name, (gas, stack))| {
81 | compiler.gas_metering(gas);
82 | unsafe { compiler.stack_bound_checks(stack) };
83 | (name, compiler.translate(name, bytecode, SPEC_ID).expect(name))
84 | });
85 | for &(name, fn_id) in &jit_ids {
86 | let jit = unsafe { compiler.jit_function(fn_id) }.expect(name);
87 | g.bench_function(format!("revmc/{name}"), |b| b.iter(|| call_jit(jit)));
88 | }
89 |
90 | g.bench_function("revm-interpreter", |b| {
91 | b.iter(|| {
92 | let mut int = revm_interpreter::Interpreter::new(contract.clone(), gas_limit, false);
93 | let mut host = host.clone();
94 |
95 | int.stack.data_mut().extend_from_slice(stack_input);
96 |
97 | let action = int.run(SharedMemory::new(), table, &mut host);
98 | assert!(
99 | int.instruction_result.is_ok(),
100 | "Interpreter failed with {:?}",
101 | int.instruction_result
102 | );
103 | assert!(action.is_return(), "Interpreter bad action: {action:?}");
104 | action
105 | })
106 | });
107 |
108 | g.finish();
109 | }
110 |
111 | fn mk_group<'a>(c: &'a mut Criterion, name: &str) -> BenchmarkGroup<'a, WallTime> {
112 | let mut g = c.benchmark_group(name);
113 | g.sample_size(20);
114 | g.warm_up_time(Duration::from_secs(2));
115 | g.measurement_time(Duration::from_secs(5));
116 | g
117 | }
118 |
119 | criterion_group!(benches, bench);
120 | criterion_main!(benches);
121 |
--------------------------------------------------------------------------------
/crates/revmc-cli/benches/iai.rs:
--------------------------------------------------------------------------------
1 | #![allow(missing_docs)]
2 |
3 | use iai_callgrind::{
4 | binary_benchmark_group, main, Bench, BinaryBenchmark, BinaryBenchmarkConfig,
5 | BinaryBenchmarkGroup, Command, EventKind, FlamegraphConfig, RegressionConfig,
6 | };
7 |
8 | const CMD: &str = env!("CARGO_BIN_EXE_revmc-cli");
9 |
10 | binary_benchmark_group!(
11 | name = revmc;
12 | benchmarks = |group: &mut BinaryBenchmarkGroup| setup_group(group)
13 | );
14 |
15 | fn setup_group(group: &mut BinaryBenchmarkGroup) {
16 | let make_bench = |name: &str, small: bool, is_ct: bool| {
17 | let mut args = Vec::with_capacity(3);
18 | args.push(name);
19 | // let out_dir = std::env::temp_dir().join("revmc-cli-iai");
20 | // let so = out_dir.join(name).join("a.so");
21 | if is_ct {
22 | args.extend(["--aot", "--no-link"]);
23 | // args.extend(["--aot", "--no-link", "-o", out_dir.to_str().unwrap()]);
24 | } else {
25 | args.push("1");
26 | // args.extend(["1", "--shared-library", so.to_str().unwrap()]);
27 | }
28 | let mut bench = Bench::new(name.replace('-', "_"));
29 | bench.command(Command::new(CMD).args(&args)).config(BinaryBenchmarkConfig::default());
30 |
31 | if !is_ct {
32 | bench.config.as_mut().unwrap().entry_point = Some("*EvmCompilerFn::call*".into());
33 | }
34 |
35 | let mut regression = RegressionConfig::default();
36 | if is_ct {
37 | let cycles = if small { 50.0 } else { 20.0 };
38 | regression.limits([(EventKind::EstimatedCycles, cycles)]);
39 | } else {
40 | regression.limits([(EventKind::EstimatedCycles, 5.0)]);
41 | }
42 | bench.config.as_mut().unwrap().regression_config = Some(regression.into());
43 |
44 | // Uses an insane amount of memory (???)
45 | if cfg!(any()) && small && !is_ci() {
46 | let flamegraph = FlamegraphConfig::default();
47 | bench.config.as_mut().unwrap().flamegraph_config = Some(flamegraph.into());
48 | }
49 |
50 | bench
51 | };
52 | let benches = [
53 | ("fibonacci", true),
54 | ("counter", true),
55 | ("hash_10k", true),
56 | // ("hash_10k-eof", true),
57 | ("bswap64", true),
58 | ("usdc_proxy", false),
59 | ("weth", false),
60 | // ("snailtracer", false),
61 | // ("snailtracer-eof", false),
62 | ];
63 | for is_ct in [false, true] {
64 | let mut bench = BinaryBenchmark::new(if is_ct { "compile_time" } else { "run_time" });
65 | for (bench_name, small) in benches {
66 | if !is_ct && !small {
67 | continue;
68 | }
69 | bench.bench(make_bench(bench_name, small, is_ct));
70 | }
71 | group.binary_benchmark(bench);
72 | }
73 | }
74 |
75 | fn is_ci() -> bool {
76 | std::env::var_os("CI").is_some()
77 | }
78 |
79 | main!(binary_benchmark_groups = revmc);
80 |
--------------------------------------------------------------------------------
/crates/revmc-cli/benches/iai/prev.rs:
--------------------------------------------------------------------------------
1 | #![allow(missing_docs)]
2 |
3 | use iai_callgrind::{library_benchmark, library_benchmark_group, main};
4 | use revm_primitives::{Env, SpecId};
5 | use revmc::{
6 | llvm::with_llvm_context, Backend, EvmCompiler, EvmContext, EvmLlvmBackend, OptimizationLevel,
7 | };
8 | use revmc_cli::{get_bench, Bench};
9 | use std::{hint::black_box, path::PathBuf};
10 |
11 | const SPEC_ID: SpecId = SpecId::CANCUN;
12 | const GAS_LIMIT: u64 = 100_000_000;
13 |
14 | fn out_dir() -> PathBuf {
15 | std::env::temp_dir().join("revmc-cli")
16 | }
17 |
18 | fn compile_time_impl(name: &str) {
19 | with_llvm_context(|cx| {
20 | let backend = EvmLlvmBackend::new(cx, true, OptimizationLevel::Aggressive).unwrap();
21 | compile_time_impl_inner(&mut EvmCompiler::new(backend), &get_bench(name).expect(name));
22 | });
23 | }
24 |
25 | fn compile_time_impl_inner(compiler: &mut EvmCompiler, bench: &Bench) {
26 | if !bench.stack_input.is_empty() {
27 | compiler.inspect_stack_length(true);
28 | }
29 | let _ = compiler.translate(Some(bench.name), &bench.bytecode, SPEC_ID).unwrap();
30 |
31 | let out_dir = out_dir();
32 | let obj = out_dir.join(bench.name).with_extension(".o");
33 | compiler.write_object_to_file(&obj).unwrap();
34 |
35 | // TODO: Clear envs.
36 | /*
37 | let so = out_dir.join(bench.name).with_extension(".so");
38 | Linker::new().link(&so, &[&obj]).unwrap();
39 | assert!(so.exists());
40 | */
41 | }
42 |
43 | fn run_time_setup(name: &str) -> Box {
44 | with_llvm_context(|cx| {
45 | let backend = EvmLlvmBackend::new(cx, false, OptimizationLevel::Aggressive).unwrap();
46 | run_time_setup_inner(&mut EvmCompiler::new(backend), &get_bench(name).expect(name))
47 | })
48 | }
49 |
50 | fn run_time_setup_inner(
51 | compiler: &mut EvmCompiler,
52 | bench: &Bench,
53 | ) -> Box {
54 | let Bench { name, bytecode, calldata, stack_input, .. } = bench;
55 |
56 | let stack_input = stack_input.clone();
57 | if !stack_input.is_empty() {
58 | compiler.inspect_stack_length(true);
59 | }
60 | let f = compiler.jit(Some(name), bytecode, SPEC_ID).unwrap();
61 |
62 | let mut env = Env::default();
63 | env.tx.data = calldata.clone().into();
64 |
65 | let bytecode = revm_interpreter::analysis::to_analysed(revm_primitives::Bytecode::new_raw(
66 | revm_primitives::Bytes::copy_from_slice(&bytecode),
67 | ));
68 | let contract = revm_interpreter::Contract::new_env(&env, bytecode, None);
69 | let mut host = revm_interpreter::DummyHost::new(env);
70 |
71 | let mut interpreter = revm_interpreter::Interpreter::new(contract, GAS_LIMIT, false);
72 |
73 | Box::new(move || {
74 | let (mut ecx, stack, stack_len) =
75 | EvmContext::from_interpreter_with_stack(&mut interpreter, &mut host);
76 |
77 | for (i, input) in stack_input.iter().enumerate() {
78 | stack.as_mut_slice()[i] = input.into();
79 | }
80 | *stack_len = stack_input.len();
81 |
82 | let r = unsafe { f.call_noinline(Some(stack), Some(stack_len), &mut ecx) };
83 | black_box(r);
84 | })
85 | }
86 |
87 | macro_rules! make_benchmarks {
88 | ($($name:ident),*) => {
89 | #[library_benchmark]
90 | $(
91 | #[bench::$name(stringify!($name))]
92 | )*
93 | fn compile_time(name: &str) {
94 | crate::compile_time_impl(name);
95 | }
96 |
97 | #[library_benchmark]
98 | $(
99 | #[bench::$name(run_time_setup(stringify!($name)))]
100 | )*
101 | fn run_time(f: Box) {
102 | f();
103 | }
104 |
105 | library_benchmark_group!(
106 | name = all;
107 | benchmarks =
108 | // compile_time,
109 | run_time
110 | );
111 |
112 | main!(library_benchmark_groups = all);
113 | };
114 | }
115 |
116 | make_benchmarks!(fibonacci, counter, snailtracer, push0_proxy, weth, hash_10k);
117 |
--------------------------------------------------------------------------------
/crates/revmc-cli/build.rs:
--------------------------------------------------------------------------------
1 | #![allow(missing_docs)]
2 |
3 | fn main() {
4 | revmc_build::emit();
5 | }
6 |
--------------------------------------------------------------------------------
/crates/revmc-cli/src/benches.rs:
--------------------------------------------------------------------------------
1 | use revmc::{interpreter::opcode as op, primitives::hex, U256};
2 | use std::hint::black_box;
3 |
4 | macro_rules! include_code_str {
5 | ($path:literal) => {
6 | crate::read_code_string(
7 | include_bytes!($path),
8 | std::path::Path::new($path).extension().and_then(|s| s.to_str()),
9 | )
10 | };
11 | }
12 |
13 | #[derive(Clone, Debug, Default)]
14 | pub struct Bench {
15 | pub name: &'static str,
16 | pub bytecode: Vec,
17 | pub calldata: Vec,
18 | pub stack_input: Vec,
19 | pub native: Option,
20 | }
21 |
22 | pub fn get_bench(name: &str) -> Option {
23 | get_benches().into_iter().find(|b| b.name == name)
24 | }
25 |
26 | pub fn get_benches() -> Vec {
27 | vec![
28 | Bench {
29 | name: "fibonacci",
30 | bytecode: FIBONACCI.to_vec(),
31 | stack_input: vec![U256::from(69)],
32 | native: Some(|| {
33 | black_box(fibonacci_rust(black_box(U256::from(70))));
34 | }),
35 | ..Default::default()
36 | },
37 | // https://github.com/lambdaclass/evm_mlir/blob/b766d0bbc2093bbfa4feb3aa25baf82b512aee74/bench/revm_comparison/src/lib.rs#L12-L15
38 | // https://blog.lambdaclass.com/evm-performance-boosts-with-mlir/
39 | // > We chose 1000 as N
40 | Bench {
41 | name: "fibonacci-calldata",
42 | bytecode: hex!(
43 | "5f355f60015b8215601a578181019150909160019003916005565b9150505f5260205ff3"
44 | )
45 | .to_vec(),
46 | calldata: U256::from(1000).to_be_bytes_vec(),
47 | ..Default::default()
48 | },
49 | Bench {
50 | name: "factorial",
51 | bytecode: hex!(
52 | "5f355f60015b8215601b57906001018091029160019003916005565b9150505f5260205ff3"
53 | )
54 | .to_vec(),
55 | calldata: U256::from(1000).to_be_bytes_vec(),
56 | ..Default::default()
57 | },
58 | Bench {
59 | name: "counter",
60 | bytecode: include_code_str!("../../../data/counter.rt.hex").unwrap(),
61 | // `increment()`
62 | calldata: hex!("d09de08a").to_vec(),
63 | ..Default::default()
64 | },
65 | Bench {
66 | name: "counter-eof",
67 | bytecode: include_code_str!("../../../data/counter-eof.rt.hex").unwrap(),
68 | // `increment()`
69 | calldata: hex!("d09de08a").to_vec(),
70 | ..Default::default()
71 | },
72 | Bench {
73 | name: "snailtracer",
74 | bytecode: include_code_str!("../../../data/snailtracer.rt.hex").unwrap(),
75 | // `Benchmark()`
76 | calldata: hex!("30627b7c").to_vec(),
77 | ..Default::default()
78 | },
79 | Bench {
80 | name: "snailtracer-eof",
81 | bytecode: include_code_str!("../../../data/snailtracer-eof.rt.hex").unwrap(),
82 | // `Benchmark()`
83 | calldata: hex!("30627b7c").to_vec(),
84 | ..Default::default()
85 | },
86 | Bench {
87 | name: "weth",
88 | bytecode: include_code_str!("../../../data/weth.rt.hex").unwrap(),
89 | ..Default::default()
90 | },
91 | Bench {
92 | name: "hash_10k",
93 | bytecode: include_code_str!("../../../data/hash_10k.rt.hex").unwrap(),
94 | // `Benchmark()`
95 | calldata: hex!("30627b7c").to_vec(),
96 | ..Default::default()
97 | },
98 | Bench {
99 | name: "hash_10k-eof",
100 | bytecode: include_code_str!("../../../data/hash_10k-eof.rt.hex").unwrap(),
101 | // `Benchmark()`
102 | calldata: hex!("30627b7c").to_vec(),
103 | ..Default::default()
104 | },
105 | Bench {
106 | name: "erc20_transfer",
107 | bytecode: include_code_str!("../../../data/erc20_transfer.rt.hex").unwrap(),
108 | // `Benchmark()`
109 | calldata: hex!("30627b7c").to_vec(),
110 | ..Default::default()
111 | },
112 | Bench {
113 | name: "push0_proxy",
114 | bytecode: include_code_str!("../../../data/push0_proxy.rt.hex").unwrap(),
115 | ..Default::default()
116 | },
117 | Bench {
118 | name: "usdc_proxy",
119 | bytecode: include_code_str!("../../../data/usdc_proxy.rt.hex").unwrap(),
120 | ..Default::default()
121 | },
122 | Bench {
123 | name: "fiat_token",
124 | bytecode: include_code_str!("../../../data/fiat_token.rt.hex").unwrap(),
125 | ..Default::default()
126 | },
127 | Bench {
128 | name: "uniswap_v2_pair",
129 | bytecode: include_code_str!("../../../data/uniswap_v2_pair.rt.hex").unwrap(),
130 | ..Default::default()
131 | },
132 | Bench {
133 | name: "seaport",
134 | bytecode: include_code_str!("../../../data/seaport.rt.hex").unwrap(),
135 | ..Default::default()
136 | },
137 | Bench {
138 | name: "airdrop",
139 | bytecode: include_code_str!("../../../data/airdrop.rt.hex").unwrap(),
140 | // `paused()`
141 | calldata: hex!("5c975abb").to_vec(),
142 | ..Default::default()
143 | },
144 | Bench {
145 | name: "bswap64",
146 | bytecode: include_code_str!("../../../data/bswap64.rt.hex").unwrap(),
147 | // `to_little_endian_64(uint64 = 0x0102)` returns (bytes)
148 | calldata: hex!(
149 | "ff2f79f10000000000000000000000000000000000000000000000000000000000000102"
150 | )
151 | .to_vec(),
152 | ..Default::default()
153 | },
154 | Bench {
155 | name: "bswap64_opt",
156 | bytecode: include_code_str!("../../../data/bswap64_opt.rt.hex").unwrap(),
157 | // `to_little_endian_64(uint64 = 0x0102)` returns (bytes)
158 | calldata: hex!(
159 | "ff2f79f10000000000000000000000000000000000000000000000000000000000000102"
160 | )
161 | .to_vec(),
162 | ..Default::default()
163 | },
164 | ]
165 | }
166 |
167 | #[rustfmt::skip]
168 | const FIBONACCI: &[u8] = &[
169 | // input to the program (which fib number we want)
170 | // op::PUSH2, input[0], input[1],
171 | op::JUMPDEST, op::JUMPDEST, op::JUMPDEST,
172 |
173 | // 1st/2nd fib number
174 | op::PUSH1, 0,
175 | op::PUSH1, 1,
176 | // 7
177 |
178 | // MAINLOOP:
179 | op::JUMPDEST,
180 | op::DUP3,
181 | op::ISZERO,
182 | op::PUSH1, 28, // cleanup
183 | op::JUMPI,
184 |
185 | // fib step
186 | op::DUP2,
187 | op::DUP2,
188 | op::ADD,
189 | op::SWAP2,
190 | op::POP,
191 | op::SWAP1,
192 | // 19
193 |
194 | // decrement fib step counter
195 | op::SWAP2,
196 | op::PUSH1, 1,
197 | op::SWAP1,
198 | op::SUB,
199 | op::SWAP2,
200 | op::PUSH1, 7, // goto MAINLOOP
201 | op::JUMP,
202 | // 28
203 |
204 | // CLEANUP:
205 | op::JUMPDEST,
206 | op::SWAP2,
207 | op::POP,
208 | op::POP,
209 | // done: requested fib number is the only element on the stack!
210 | op::STOP,
211 | ];
212 |
213 | /// Literal translation of the `FIBONACCI` EVM bytecode to Rust.
214 | pub fn fibonacci_rust(mut i: U256) -> U256 {
215 | let mut a = U256::from(0);
216 | let mut b = U256::from(1);
217 | while i != U256::ZERO {
218 | let tmp = a;
219 | a = b;
220 | b = b.wrapping_add(tmp);
221 | i -= U256::from(1);
222 | }
223 | a
224 | }
225 |
226 | #[test]
227 | fn test_fibonacci_rust() {
228 | revm_primitives::uint! {
229 | assert_eq!(fibonacci_rust(0_U256), 0_U256);
230 | assert_eq!(fibonacci_rust(1_U256), 1_U256);
231 | assert_eq!(fibonacci_rust(2_U256), 1_U256);
232 | assert_eq!(fibonacci_rust(3_U256), 2_U256);
233 | assert_eq!(fibonacci_rust(4_U256), 3_U256);
234 | assert_eq!(fibonacci_rust(5_U256), 5_U256);
235 | assert_eq!(fibonacci_rust(6_U256), 8_U256);
236 | assert_eq!(fibonacci_rust(7_U256), 13_U256);
237 | assert_eq!(fibonacci_rust(8_U256), 21_U256);
238 | assert_eq!(fibonacci_rust(9_U256), 34_U256);
239 | assert_eq!(fibonacci_rust(10_U256), 55_U256);
240 | assert_eq!(fibonacci_rust(100_U256), 354224848179261915075_U256);
241 | assert_eq!(fibonacci_rust(1000_U256), 0x2e3510283c1d60b00930b7e8803c312b4c8e6d5286805fc70b594dc75cc0604b_U256);
242 | }
243 | }
244 |
--------------------------------------------------------------------------------
/crates/revmc-cli/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![allow(missing_docs)]
2 |
3 | use revm_interpreter::OpCode;
4 | use revm_primitives::hex;
5 | use revmc::{
6 | eyre::{bail, eyre, Result, WrapErr},
7 | U256,
8 | };
9 | use std::{cmp::Ordering, path::Path, str::FromStr};
10 |
11 | mod benches;
12 | pub use benches::*;
13 |
14 | pub fn read_code(code: Option<&str>, code_path: Option<&Path>) -> Result> {
15 | if let Some(code) = code {
16 | return read_code_string(code.trim().as_bytes(), None);
17 | }
18 |
19 | if let Some(code_path) = code_path {
20 | let contents = std::fs::read(code_path)?;
21 | let ext = code_path.extension().and_then(|s| s.to_str());
22 | return read_code_string(&contents, ext);
23 | }
24 |
25 | Err(eyre!("one of --code, --code-path is required when argument is 'custom'"))
26 | }
27 |
28 | pub fn read_code_string(contents: &[u8], ext: Option<&str>) -> Result> {
29 | let has_prefix = contents.starts_with(b"0x") || contents.starts_with(b"0X");
30 | let is_hex = ext != Some("bin") && (ext == Some("hex") || has_prefix);
31 | let utf8 =
32 | || std::str::from_utf8(contents).wrap_err("given code is not valid UTF-8").map(str::trim);
33 | if is_hex {
34 | let input = utf8()?;
35 | let mut lines = input.lines().map(str::trim);
36 | let first_line = lines.next().unwrap_or_default();
37 | hex::decode(first_line).wrap_err("given code is not valid hex")
38 | } else if ext == Some("bin") || !contents.is_ascii() {
39 | Ok(contents.to_vec())
40 | } else if ext == Some("evm") {
41 | parse_evm_dsl(utf8()?)
42 | } else if contents.is_ascii() {
43 | let s = utf8()?;
44 | parse_evm_dsl(s).or_else(|_| hex::decode(s).wrap_err("given code is not valid hex"))
45 | } else {
46 | Err(eyre!("could not determine bytecode type"))
47 | }
48 | }
49 |
50 | /// Parse EVM code from a string.
51 | fn parse_evm_dsl(s: &str) -> Result> {
52 | const COM: char = ';';
53 |
54 | let mut code = Vec::with_capacity(32);
55 |
56 | let lines = s.lines().map(str::trim).filter(|s| !s.is_empty());
57 | let words = lines.flat_map(|s| s.split_whitespace().take_while(|s| !s.starts_with(COM)));
58 | let mut words = words.peekable();
59 | while let Some(word) = words.next() {
60 | if word == "PUSH" {
61 | let next = words.next().ok_or_else(|| eyre!("missing immediate for opcode PUSH"))?;
62 | let imm_bytes = parse_imm(next, None)?;
63 | code.push(OpCode::PUSH0.get() + imm_bytes.len() as u8);
64 | code.extend_from_slice(&imm_bytes);
65 | } else {
66 | let op = OpCode::parse(word).ok_or_else(|| eyre!("invalid opcode: {word:?}"))?;
67 | code.push(op.get());
68 | let imm_len = op.info().immediate_size();
69 | if imm_len > 0 {
70 | let imm = words.next().ok_or_else(|| eyre!("missing immediate for opcode {op}"))?;
71 | let imm_bytes = parse_imm(imm, Some(imm_len))?;
72 | code.extend_from_slice(&imm_bytes);
73 | } else if let Some(next) = words.peek() {
74 | if U256::from_str(next).is_ok() {
75 | bail!("unexpected immediate for opcode {op}");
76 | }
77 | }
78 | }
79 | }
80 |
81 | Ok(code)
82 | }
83 |
84 | fn parse_imm(s: &str, size: Option) -> Result> {
85 | let num: U256 = s.parse().wrap_err("failed to parse immediate")?;
86 | let mut imm_bytes = num.to_be_bytes_trimmed_vec();
87 | if let Some(size) = size {
88 | debug_assert!(size <= 32);
89 | match imm_bytes.len().cmp(&(size as usize)) {
90 | Ordering::Less => {
91 | let extend = size as usize - imm_bytes.len();
92 | imm_bytes.splice(0..0, std::iter::repeat(0).take(extend));
93 | }
94 | Ordering::Equal => {}
95 | Ordering::Greater => {
96 | bail!("expected at most {size} immediate bytes, got {}", imm_bytes.len())
97 | }
98 | }
99 | }
100 | debug_assert!(imm_bytes.len() <= 32);
101 | Ok(imm_bytes)
102 | }
103 |
104 | #[cfg(test)]
105 | mod tests {
106 | use super::*;
107 | use revm_interpreter::opcode as op;
108 |
109 | #[test]
110 | fn test_evm_dsl() {
111 | let cases: &[(&str, Vec)] = &[
112 | ("ADD ; ADD\n ADD", vec![op::ADD, op::ADD]),
113 | ("PUSH1 0", vec![op::PUSH1, 0]),
114 | ("PUSH3 0x000069", vec![op::PUSH3, 0, 0, 0x69]),
115 | ("PUSH3 0x69 ; padded", vec![op::PUSH3, 0, 0, 0x69]),
116 | ("PUSH 0", vec![op::PUSH0]),
117 | ("PUSH 1", vec![op::PUSH1, 1]),
118 | ("PUSH 2", vec![op::PUSH1, 2]),
119 | ("PUSH 69", vec![op::PUSH1, 69]),
120 | ("PUSH 0x2222", vec![op::PUSH2, 0x22, 0x22]),
121 | ];
122 | for (s, expected) in cases.iter() {
123 | let code = match parse_evm_dsl(s) {
124 | Ok(code) => code,
125 | Err(e) => panic!("code: {s:?}\n\n err: {e}"),
126 | };
127 | assert_eq!(code, *expected, "{s:?}");
128 | }
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/crates/revmc-cli/src/main.rs:
--------------------------------------------------------------------------------
1 | #![allow(missing_docs)]
2 |
3 | use clap::{Parser, ValueEnum};
4 | use color_eyre::{eyre::eyre, Result};
5 | use revm_interpreter::{opcode::make_instruction_table, SharedMemory};
6 | use revm_primitives::{address, spec_to_generic, Env, SpecId, TransactTo};
7 | use revmc::{eyre::ensure, EvmCompiler, EvmContext, EvmLlvmBackend, OptimizationLevel};
8 | use revmc_cli::{get_benches, read_code, Bench};
9 | use std::{
10 | hint::black_box,
11 | path::{Path, PathBuf},
12 | };
13 |
14 | #[derive(Parser)]
15 | struct Cli {
16 | /// Benchmark name, "custom", path to a file, or a symbol to load from a shared object.
17 | bench_name: String,
18 | #[arg(default_value = "1")]
19 | n_iters: u64,
20 |
21 | #[arg(long)]
22 | code: Option,
23 | #[arg(long, conflicts_with = "code")]
24 | code_path: Option,
25 | #[arg(long)]
26 | calldata: Option,
27 |
28 | /// Load a shared object file instead of JIT compiling.
29 | ///
30 | /// Use with `--aot` to also run the compiled library.
31 | #[arg(long)]
32 | load: Option