Compare commits

..

41 Commits

Author SHA1 Message Date
elle-j 6187f76d63 Control concurrency and cancel in-progress runs when re-triggered. 2025-12-02 16:38:06 +01:00
LJ e6ccaa0bab Merge branch 'main' into lj/ci-benchmarks 2025-12-02 08:13:11 +01:00
xermicus e7e40a0ded The revive compiler documentation (#424)
This PR adds comprehensive project documentation in the form of an
mdBook.

---------

Signed-off-by: xermicus <cyrill@parity.io>
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
Co-authored-by: LJ <81748770+elle-j@users.noreply.github.com>
Co-authored-by: PG Herveou <pgherveou@gmail.com>
2025-12-01 14:58:02 +01:00
LJ 94b14b079b ci: Bump macos runner images (#417)
Closes #416.

Workflows using x86_64-apple-darwin target with a `macos-13` runner
image have been migrated to `macos-15-intel` due to deprecation (see
#416), and `macos-14` images have been migrated to `macos-15`.
2025-11-29 11:06:30 +01:00
kvpanch 8cd10a6136 Clone emsdk for release-llvm CI action (#423)
Cloning of emsdk has been accidentally removed, that caused inability to
build LLVM release with current main.
The change adds this cloning back before emsdk script is sourced.
2025-11-27 21:36:28 -05:00
elle-j 7cc1921c01 Update naming. 2025-11-27 21:31:31 +01:00
elle-j 3446aaa238 Update checkouts and report generation. 2025-11-27 17:21:41 +01:00
elle-j 0847b99a7d Implement initial workflow to run benchmarks. 2025-11-26 11:35:30 +01:00
LJ 0742227c5a Implement Yul to LLVM IR compilation benchmarks (#407)
# Description

Closes [#404](https://github.com/paritytech/revive/issues/404)

Adds compilation time benchmarks for:

* Parsing of Yul source code -> AST Object
* Lowering of AST Object -> LLVM IR (unoptimized)

The benchmarks can be run from the root via:

```sh
# Run all benchmarks in the revive-yul crate (parsing + lowering)
make bench-yul
```

HTML reports will be generated under `target/criterion`, and a summary
of the results at
[crates/yul/BENCHMARKS_PARSE_M4PRO.md](https://github.com/paritytech/revive/blob/lj/compilation-benchmarks-yul/crates/yul/BENCHMARKS_PARSE_M4PRO.md)
and
[crates/yul/BENCHMARKS_LOWER_M4PRO.md](https://github.com/paritytech/revive/blob/lj/compilation-benchmarks-yul/crates/yul/BENCHMARKS_LOWER_M4PRO.md)
(currently from running on a Mac M4 Pro).

---------

Co-authored-by: xermicus <cyrill@parity.io>
2025-11-25 12:00:06 +01:00
xermicus 1e0cce0fa8 Fix the Makefile (#421)
Fix the Makefile
2025-11-24 16:17:54 +01:00
kvpanch b6a19c97b1 Fetch LLVM release according to branch in llvm submodule (#418)
This change is essential first step to unblock gracefully enable LLVM 21
in revive compiler as current action blindly fetched latest LLVM binary
from the list of releases on our page.
The change queries release version from the submodule and uses it. Next
step: build LLVM 21 release binary
Next next step: switch revive compile to LLVM 21 after all issues are
fixed
2025-11-21 11:12:00 -05:00
xermicus d97d094a8a Update the Rust dependencies (#420)
The `Cargo.lock` kept changing locally on all my hosts. Not sure why
exactly - maybe it got forgotten in a recent PR. I took it as an
opportunity to quickly update the Rust dependencies.

---------

Signed-off-by: xermicus <bigcyrill@hotmail.com>
2025-11-21 15:34:21 +01:00
xermicus 975b610d9f Add the LLVM dir to the .ignore file (#419)
LLVM is now a submodule and no longer in `.gitignore`. Thus, some dev
tools I use, like `rg` and `fd`, now consider the `llvm/` directory.
This PR fixes that by adding `llvm/` to the `.ignore` file.

Signed-off-by: xermicus <bigcyrill@hotmail.com>
2025-11-21 14:43:44 +01:00
LJ ad61b6e3c9 Implement resolc end-to-end compilation benchmarks (#406)
# Description

Closes [#403](https://github.com/paritytech/revive/issues/403)

Adds compilation time benchmarks for resolc end-to-end.

The benchmarks can be run from the root via:

```sh
make bench-resolc
```

HTML reports will be generated under `target/criterion`, and a summary
of the results at
[crates/resolc/BENCHMARKS_M4PRO.md](https://github.com/paritytech/revive/blob/lj/compilation-benchmarks/crates/resolc/BENCHMARKS_M4PRO.md)
(currently from running on a Mac M4 Pro).
2025-11-19 12:16:07 +01:00
kvpanch e78f3cc419 add LLVM 18.x as a git submodule (#399)
This changeset is based on https://github.com/paritytech/revive/pull/346
2025-11-18 16:10:15 -05:00
xermicus a1090cfa5a revive-yul: fix the code block exit (#414)
This PR fixes the missing `STOP` instruction at the end of `code`
blocks.

---------

Signed-off-by: xermicus <bigcyrill@hotmail.com>
2025-11-17 18:47:58 +01:00
xermicus dd48baadc7 resolc: fix the Yul test utils (#413)
- Fixes multiple bugs in the Yul test util helpers.
- The simple Yul builder utility helper now returns the contract builds.

Signed-off-by: xermicus <bigcyrill@hotmail.com>
2025-11-17 17:45:03 +01:00
dependabot[bot] a3a5c7380d Bump js-yaml from 4.1.0 to 4.1.1 (#412)
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.1.0 to 4.1.1.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md">js-yaml's
changelog</a>.</em></p>
<blockquote>
<h2>[4.1.1] - 2025-11-12</h2>
<h3>Security</h3>
<ul>
<li>Fix prototype pollution issue in yaml merge (&lt;&lt;)
operator.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/nodeca/js-yaml/commit/cc482e775913e6625137572a3712d2826170e53a"><code>cc482e7</code></a>
4.1.1 released</li>
<li><a
href="https://github.com/nodeca/js-yaml/commit/50968b862e75866ef90e626572fe0b2f97b55f9f"><code>50968b8</code></a>
dist rebuild</li>
<li><a
href="https://github.com/nodeca/js-yaml/commit/d092d866031751cb27c12d93f3e2470ad74d678b"><code>d092d86</code></a>
lint fix</li>
<li><a
href="https://github.com/nodeca/js-yaml/commit/383665ff4248ec2192d1274e934462bb30426879"><code>383665f</code></a>
fix prototype pollution in merge (&lt;&lt;)</li>
<li><a
href="https://github.com/nodeca/js-yaml/commit/0d3ca7a27b03a6c974790a30a89e456007d62976"><code>0d3ca7a</code></a>
README.md: HTTP =&gt; HTTPS (<a
href="https://redirect.github.com/nodeca/js-yaml/issues/678">#678</a>)</li>
<li><a
href="https://github.com/nodeca/js-yaml/commit/49baadd52af887d2991e2c39a6639baa56d6c71b"><code>49baadd</code></a>
doc: 'empty' style option for !!null</li>
<li><a
href="https://github.com/nodeca/js-yaml/commit/ba3460eb9d3e4478edcbc29edabe17c2157fc9ce"><code>ba3460e</code></a>
Fix demo link (<a
href="https://redirect.github.com/nodeca/js-yaml/issues/618">#618</a>)</li>
<li>See full diff in <a
href="https://github.com/nodeca/js-yaml/compare/4.1.0...4.1.1">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=js-yaml&package-manager=npm_and_yarn&previous-version=4.1.0&new-version=4.1.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/paritytech/revive/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-17 10:54:36 +01:00
xermicus 79aaca1a85 std json: do not request EVM bytecode (#410)
These compiler outputs are irrelevant for the `resolc` compilation
pipeline. It can also break the compilation pipeline. For example, solc
may reject the code without via-ir or fail during the EVM codegen phase
with stack too deep errors - which is not relevant to us.

---------

Signed-off-by: xermicus <cyrill@parity.io>
2025-11-13 10:32:32 +01:00
PG Herveou 8ee1860f12 js: rm default process.env.RESOLC_BIN (#411)
Ran into this today, my env was set and pointing to an old version
so it's a breaking change, but I think we are better off not hardcoding
the env here, the user can do whatever they want when they invoke
compile
2025-11-13 10:16:34 +01:00
xermicus 878ca91689 RISCV relaxations (#408)
This PR enables RISC-V relaxations and changes to internal linkage for internal function and global symbols.

See also:
https://github.com/llvm/llvm-project/issues/167381#issuecomment-3513830043

Signed-off-by: xermicus <cyrill@parity.io>
2025-11-11 17:27:53 +01:00
xermicus 046455db06 release v0.5.0 (#401)
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-11-03 16:10:20 +01:00
xermicus 70037e1136 initial SELFDESTRUCT support (#400)
Note:
- The unstable interface in `v2509.0.0` of `polkadot-sdk` is required.
- The differential test fails against EVM in `v2509.0.0` of
`polkadot-sdk`.

---------

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-11-03 15:04:04 +01:00
xermicus c0cdde5a5a llvm-context: fix off-by-one in SDIV overflow semantics (#398)
- Fix the wrong predicate (the negative side in 2s complement has one
more bit)
- Increase test coverage

Signed-off-by: xermicus <cyrill@parity.io>
2025-10-31 14:07:01 +01:00
xermicus df1921ba93 runtime-api: sync syscall function signatures with pallet (#397)
This PR synchronizes the syscall function signatures in the runtime-api
with the current revive pallet API surface.

Signed-off-by: xermicus <cyrill@parity.io>
2025-10-30 14:43:55 +01:00
xermicus 84018c18ae switch to the new storage API (#396)
This PR switches to the new `get_storage_or_zero` and
`set_storage_or_clear` syscalls which are more tailored towards
Solidity.

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-10-30 14:13:37 +01:00
xermicus 42cac55be8 update cargo dependencies (#395)
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-10-29 10:01:05 +01:00
kvpanch 6549a4f825 Strip minsize attribute from functions with large div/rem (#390)
That's a workaround to avoid LLVM backend crash while selecting
instruction for 256-bit integer division.
The workaround needs to be removed after we switch to newest inkwell
that has a fix in RISC-V backend

---------

Signed-off-by: kvp <mammal_windier8j@icloud.com>
2025-10-21 10:35:40 +02:00
xermicus f46bea6a96 llvm-context: do not trap zero length OOB heap access (#389)
Fixes https://github.com/paritytech/contract-issues/issues/120

---------

Signed-off-by: xermicus <cyrill@parity.io>
2025-10-15 22:39:42 +02:00
xermicus 2090830858 release 0.4.1 (#388)
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-10-08 14:04:48 +02:00
Pavlo Khrystenko 93c6387dae Fix output selection issues for Foundry (#386)
### Description 

Fixes output selection issues for `Foundry` usage. 

- pruning to look at per file settings
- source to include `ast`


### Note 

`Selection.files` field is reintroduced because that's the way `foundry`
functions. it uses per-file output selection

---------

Co-authored-by: xermicus <cyrill@parity.io>
2025-10-08 12:48:41 +02:00
xermicus 7346d82dfa do not prune the AST output in standard JSON mode (#385)
- do not prune the AST output in standard JSON mode (required for
foundry)
- do not serialize the output format if there was not blob produced

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-10-07 15:27:44 +02:00
Alexander Samusev 39a6db7266 ci: add reusable action for release workflows (#382)
Let's check how good `claude` is.

fix https://github.com/paritytech/revive/issues/359
2025-10-06 17:08:25 +02:00
xermicus 8240163be0 release resolc 0.4.0 (#384)
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-10-06 09:12:34 +02:00
xermicus b560d72139 Prevent frontend function confusion (#383)
Function symbols can clash as we compile multiple YUL `object`
definition into the same `LLVM` module.
- Disambiguate via unique function symbols based its location (runtime
or deploy code).
- Use `LinkOnceODR` linkage for compiler builtin helpers.

---------

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-09-29 23:05:57 +02:00
xermicus 6858cb9a61 mark internal functions linker private (#381)
Prevents unused functions in the emitted ELF object. Drive-by add a
missing test case (which misses a relocation under `-Oz` when all
internal functions are marked as private).

---------

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-09-29 17:55:29 +02:00
xermicus 1cc4f967b4 vet cargo docs on CI (#380)
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-09-29 13:39:09 +02:00
xermicus 713b7e5409 Nightly release workflow fix (#378)
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-09-28 13:12:10 +02:00
dependabot[bot] ffc13697ed Bump brace-expansion (#377)
Bumps and
[brace-expansion](https://github.com/juliangruber/brace-expansion).
These dependencies needed to be updated together.
Updates `brace-expansion` from 1.1.11 to 1.1.12
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/juliangruber/brace-expansion/releases">brace-expansion's
releases</a>.</em></p>
<blockquote>
<h2>v1.1.12</h2>
<ul>
<li>pkg: publish on tag 1.x  c460dbd</li>
<li>fmt  ccb8ac6</li>
<li>Fix potential ReDoS Vulnerability or Inefficient Regular Expression
(<a
href="https://redirect.github.com/juliangruber/brace-expansion/issues/65">#65</a>)
c3c73c8</li>
</ul>
<hr />
<p><a
href="https://github.com/juliangruber/brace-expansion/compare/v1.1.11...v1.1.12">https://github.com/juliangruber/brace-expansion/compare/v1.1.11...v1.1.12</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/juliangruber/brace-expansion/commit/44f33b47c5c6a965d507421af43e86cf5971d711"><code>44f33b4</code></a>
1.1.12</li>
<li><a
href="https://github.com/juliangruber/brace-expansion/commit/c460dbd68e428d147b2080622d8ce126c7a08570"><code>c460dbd</code></a>
pkg: publish on tag 1.x</li>
<li><a
href="https://github.com/juliangruber/brace-expansion/commit/ccb8ac6d4292b7661b677fe048ba6690c877f51f"><code>ccb8ac6</code></a>
fmt</li>
<li><a
href="https://github.com/juliangruber/brace-expansion/commit/c3c73c8b088defc70851843be88ccc3af08e7217"><code>c3c73c8</code></a>
Fix potential ReDoS Vulnerability or Inefficient Regular Expression (<a
href="https://redirect.github.com/juliangruber/brace-expansion/issues/65">#65</a>)</li>
<li>See full diff in <a
href="https://github.com/juliangruber/brace-expansion/compare/1.1.11...v1.1.12">compare
view</a></li>
</ul>
</details>
<br />

Updates `brace-expansion` from 2.0.1 to 2.0.2
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/juliangruber/brace-expansion/releases">brace-expansion's
releases</a>.</em></p>
<blockquote>
<h2>v1.1.12</h2>
<ul>
<li>pkg: publish on tag 1.x  c460dbd</li>
<li>fmt  ccb8ac6</li>
<li>Fix potential ReDoS Vulnerability or Inefficient Regular Expression
(<a
href="https://redirect.github.com/juliangruber/brace-expansion/issues/65">#65</a>)
c3c73c8</li>
</ul>
<hr />
<p><a
href="https://github.com/juliangruber/brace-expansion/compare/v1.1.11...v1.1.12">https://github.com/juliangruber/brace-expansion/compare/v1.1.11...v1.1.12</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/juliangruber/brace-expansion/commit/44f33b47c5c6a965d507421af43e86cf5971d711"><code>44f33b4</code></a>
1.1.12</li>
<li><a
href="https://github.com/juliangruber/brace-expansion/commit/c460dbd68e428d147b2080622d8ce126c7a08570"><code>c460dbd</code></a>
pkg: publish on tag 1.x</li>
<li><a
href="https://github.com/juliangruber/brace-expansion/commit/ccb8ac6d4292b7661b677fe048ba6690c877f51f"><code>ccb8ac6</code></a>
fmt</li>
<li><a
href="https://github.com/juliangruber/brace-expansion/commit/c3c73c8b088defc70851843be88ccc3af08e7217"><code>c3c73c8</code></a>
Fix potential ReDoS Vulnerability or Inefficient Regular Expression (<a
href="https://redirect.github.com/juliangruber/brace-expansion/issues/65">#65</a>)</li>
<li>See full diff in <a
href="https://github.com/juliangruber/brace-expansion/compare/1.1.11...v1.1.12">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/paritytech/revive/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-28 13:06:55 +02:00
xermicus 94ec34c4d5 Separate compilation and linker phases (#376)
Separate between compilation and linker phases to allow deploy time
linking and back-porting era compiler changes to fix #91. Unlinked
contract binaries (caused by missing libraries or missing factory
dependencies in turn) are emitted as raw ELF object.

Few drive by fixes:
- #98
- A compiler panic on missing libraries definitions.
- Fixes some incosistent type forwarding in JSON output (empty string
vs. null object).
- Remove the unused fallback for size optimization setting.
- Remove the broken `--lvm-ir`  mode.
- CI workflow fixes.

---------

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
Signed-off-by: xermicus <bigcyrill@hotmail.com>
Signed-off-by: xermicus <cyrill@parity.io>
2025-09-27 20:52:22 +02:00
xermicus 13faedf08a link to readme in the build utils llvm error message more (#373)
Signed-off-by: xermicus <cyrill@parity.io>
2025-09-02 14:21:54 +02:00
324 changed files with 25108 additions and 8350 deletions
+3
View File
@@ -15,3 +15,6 @@ rustflags = [
"-Clink-arg=-sSTACK_SIZE=128kb",
"-Clink-arg=-sNODEJS_CATCH_EXIT=0"
]
[build]
rustdocflags = ["-D", "warnings"]
+61 -8
View File
@@ -7,23 +7,56 @@ name: "Download LLVM"
inputs:
target:
required: true
version:
description: "LLVM release version (e.g., 'llvm-18.1.8'). If not specified, uses the latest LLVM release."
required: false
default: ""
runs:
using: "composite"
steps:
- name: detect llvm version from submodule
id: detect-version
shell: bash
run: |
if [ -z "${{ inputs.version }}" ]; then
# Extract branch from .gitmodules (e.g., "release/18.x")
BRANCH=$(git config -f .gitmodules submodule.llvm.branch)
if [ -n "$BRANCH" ]; then
# Extract version from branch name (e.g., "18.x" from "release/18.x")
VERSION_PREFIX=$(echo "$BRANCH" | sed 's|release/||' | sed 's|\.x$||')
echo "Detected LLVM version prefix from submodule branch: $VERSION_PREFIX"
# Special case: pin LLVM 18 to specific version 18.1.8
if [ "$VERSION_PREFIX" = "18" ]; then
echo "Using pinned version for LLVM 18: llvm-18.1.8"
echo "version_prefix=llvm-18.1.8" >> $GITHUB_OUTPUT
else
echo "version_prefix=llvm-$VERSION_PREFIX" >> $GITHUB_OUTPUT
fi
else
echo "No branch found in .gitmodules, will use latest release"
echo "version_prefix=" >> $GITHUB_OUTPUT
fi
else
echo "Using explicitly provided version: ${{ inputs.version }}"
echo "version_prefix=${{ inputs.version }}" >> $GITHUB_OUTPUT
fi
- name: find asset
id: find
uses: actions/github-script@v7
env:
target: ${{ inputs.target }}
version_prefix: ${{ steps.detect-version.outputs.version_prefix }}
with:
result-encoding: string
script: |
let page = 1;
let releases = [];
let releasePrefix = "llvm-"
let target = process.env.target
let versionPrefix = process.env.version_prefix
do {
const res = await github.rest.repos.listReleases({
@@ -38,15 +71,31 @@ runs:
return (a.published_at < b.published_at) ? 1 : ((a.published_at > b.published_at) ? -1 : 0);
});
let llvmLatestRelease = releases.find(release => {
return release.tag_name.startsWith(releasePrefix);
});
if (llvmLatestRelease){
let asset = llvmLatestRelease.assets.find(asset =>{
let llvmRelease;
if (versionPrefix) {
// Search for latest release matching the version prefix
llvmRelease = releases.find(release => {
return release.tag_name.startsWith(versionPrefix);
});
if (llvmRelease) {
core.info(`Found LLVM release matching prefix '${versionPrefix}': ${llvmRelease.tag_name}`);
}
} else {
// Find latest LLVM release
llvmRelease = releases.find(release => {
return release.tag_name.startsWith('llvm-');
});
if (llvmRelease) {
core.info(`Found latest LLVM version: ${llvmRelease.tag_name}`);
}
}
if (llvmRelease){
let asset = llvmRelease.assets.find(asset =>{
return asset.name.includes(target);
});
if (!asset){
core.setFailed(`Artifact for '${target}' not found in release ${llvmLatestRelease.tag_name} (${llvmLatestRelease.html_url})`);
core.setFailed(`Artifact for '${target}' not found in release ${llvmRelease.tag_name} (${llvmRelease.html_url})`);
process.exit();
}
return asset.browser_download_url;
@@ -55,7 +104,11 @@ runs:
page++;
} while(releases.length > 0);
core.setFailed(`No LLVM releases with '${releasePrefix}' atifacts found! Please release LLVM before running this workflow.`);
if (versionPrefix) {
core.setFailed(`No LLVM releases matching prefix '${versionPrefix}' found! Please check the version.`);
} else {
core.setFailed(`No LLVM releases found! Please release LLVM before running this workflow.`);
}
process.exit();
- name: download
+123
View File
@@ -0,0 +1,123 @@
# This workflow will run on the default branch when triggered by a `workflow_run` event.
name: Benchmark
on:
workflow_run:
workflows: [Test]
types: [completed]
concurrency:
group: ${{ github.workflow }}-${{ github.event.workflow_run.head_branch }}
cancel-in-progress: true
jobs:
benchmark:
runs-on: ubuntu-24.04
if: ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'pull_request' }}
permissions:
pull-requests: write
steps:
- name: Checkout PR Branch
uses: actions/checkout@v4
with:
ref: ${{ github.event.workflow_run.head_sha }}
- name: Set Up Rust Toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
# Without this it will override our rust flags.
rustflags: ""
- name: Install Solc
uses: ./.github/actions/get-solc
- name: Download LLVM
uses: ./.github/actions/get-llvm
with:
target: x86_64-unknown-linux-gnu
- name: Set LLVM Environment Variables
run: |
echo "LLVM_SYS_181_PREFIX=$(pwd)/llvm-x86_64-unknown-linux-gnu" >> $GITHUB_ENV
- name: Install Benchmarking Tools
run: |
cargo install critcmp
- name: Checkout and Compile Main Branch
run: |
git fetch origin main
git checkout -f main
make install
- name: Run Benchmarks on Main Branch
run: |
cargo bench --package resolc --bench compile -- --save-baseline main_resolc
cargo bench --package revive-yul --bench parse -- --save-baseline main_yul_parse
cargo bench --package revive-yul --bench lower -- --save-baseline main_yul_lower
timeout-minutes: 20
- name: Checkout and Compile PR Branch
run: |
git checkout -f ${{ github.event.workflow_run.head_sha }}
make install
- name: Run Benchmarks on PR Branch
run: |
cargo bench --package resolc --bench compile -- --save-baseline pr_resolc
cargo bench --package revive-yul --bench parse -- --save-baseline pr_yul_parse
cargo bench --package revive-yul --bench lower -- --save-baseline pr_yul_lower
timeout-minutes: 20
- name: Compare Benchmarks
run: |
critcmp main_resolc pr_resolc > benchmarks_resolc
critcmp main_yul_parse pr_yul_parse > benchmarks_yul_parse
critcmp main_yul_lower pr_yul_lower > benchmarks_yul_lower
- name: Create Report
run: |
cat > BENCHMARK_REPORT.md << EOF
# Benchmarks
## Compile E2E
<details>
<summary>Results</summary>
\`\`\`
$(cat benchmarks_resolc)
\`\`\`
</details>
## Parse Yul
<details>
<summary>Results</summary>
\`\`\`
$(cat benchmarks_yul_parse)
\`\`\`
</details>
## Lower Yul
<details>
<summary>Results</summary>
\`\`\`
$(cat benchmarks_yul_lower)
\`\`\`
</details>
EOF
- name: Post PR Comment with Benchmark Report
uses: marocchino/sticky-pull-request-comment@v2
with:
header: benchmark
number: ${{ github.event.workflow_run.pull_requests[0].number }}
path: BENCHMARK_REPORT.md
+39
View File
@@ -0,0 +1,39 @@
name: Check docs up-to-date
on:
push:
branches: ["main"]
paths:
- 'book/**'
- 'docs/**'
- '.gitignore'
- 'Makefile'
- '.github/workflows/book.yml'
pull_request:
branches: ["main"]
types: [opened, synchronize]
paths:
- 'book/**'
- 'docs/**'
- '.gitignore'
- 'Makefile'
- '.github/workflows/book.yml'
jobs:
check-docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- name: Install mdBook
run: make test-book
- name: Build book to tmp
run: mdbook build book -d docs-tmp
- name: Compare with committed docs
run: |
if ! diff -r docs-tmp docs 2>/dev/null; then
echo "docs/ is not up-to-date. Run make test-book and commit the docs/ directory"
exit 1
fi
+5 -6
View File
@@ -54,11 +54,13 @@ jobs:
- target: aarch64-apple-darwin
builder-arg: gnu
host: macos
runner: macos-14
runner: macos-15
- target: x86_64-apple-darwin
builder-arg: gnu
host: macos
runner: macos-13
# `macos-15-intel` will be the last x86_64 `macos` image supported by GHA.
# It will be available until Aug. 2027 (see https://github.com/actions/runner-images/issues/13045).
runner: macos-15-intel
- target: x86_64-pc-windows-msvc
builder-arg: gnu
host: windows
@@ -96,10 +98,6 @@ jobs:
run: |
cargo install --locked --force --path crates/llvm-builder
- name: Clone LLVM
run: |
revive-llvm --target-env ${{ matrix.builder-arg }} clone
- name: Build LLVM
if: ${{ matrix.target != 'wasm32-unknown-emscripten' }}
run: |
@@ -108,6 +106,7 @@ jobs:
- name: Build LLVM
if: ${{ matrix.target == 'wasm32-unknown-emscripten' }}
run: |
revive-llvm emsdk
source emsdk/emsdk_env.sh
revive-llvm --target-env ${{ matrix.builder-arg }} build --llvm-projects lld
+9 -233
View File
@@ -1,4 +1,5 @@
name: Nightly Release
on:
schedule:
# Run every day at 01:00 UTC
@@ -10,10 +11,8 @@ concurrency:
env:
CARGO_TERM_COLOR: always
RUST_MUSL_CROSS_IMAGE: messense/rust-musl-cross@sha256:c0154e992adb791c3b848dd008939d19862549204f8cb26f5ca7a00f629e6067
jobs:
# check if there were commits yesterday
check_commits:
runs-on: ubuntu-latest
outputs:
@@ -22,7 +21,7 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0 # Fetch full history to check previous commits
fetch-depth: 0
ref: "main"
- name: Check for commits from yesterday
@@ -47,229 +46,12 @@ jobs:
fi
build:
# github actions matrix jobs don't support multiple outputs
# ugly workaround from https://github.com/orgs/community/discussions/17245#discussioncomment-11222880
if: ${{ needs.check_commits.outputs.has_commits == 'true' }}
outputs:
resolc-x86_64-unknown-linux-musl_url: ${{ steps.set-output.outputs.resolc-x86_64-unknown-linux-musl_url }}
resolc-x86_64-unknown-linux-musl_sha: ${{ steps.set-output.outputs.resolc-x86_64-unknown-linux-musl_sha }}
resolc-aarch64-apple-darwin_url: ${{ steps.set-output.outputs.resolc-aarch64-apple-darwin_url }}
resolc-aarch64-apple-darwin_sha: ${{ steps.set-output.outputs.resolc-aarch64-apple-darwin_sha }}
resolc-x86_64-apple-darwin_url: ${{ steps.set-output.outputs.resolc-x86_64-apple-darwin_url }}
resolc-x86_64-apple-darwin_sha: ${{ steps.set-output.outputs.resolc-x86_64-apple-darwin_sha }}
resolc-x86_64-pc-windows-msvc_url: ${{ steps.set-output.outputs.resolc-x86_64-pc-windows-msvc_url }}
resolc-x86_64-pc-windows-msvc_sha: ${{ steps.set-output.outputs.resolc-x86_64-pc-windows-msvc_sha }}
strategy:
matrix:
target:
[
x86_64-unknown-linux-musl,
aarch64-apple-darwin,
x86_64-apple-darwin,
x86_64-pc-windows-msvc,
]
include:
- target: x86_64-unknown-linux-musl
type: musl
runner: ubuntu-24.04
- target: aarch64-apple-darwin
type: native
runner: macos-14
- target: x86_64-apple-darwin
type: native
runner: macos-13
- target: x86_64-pc-windows-msvc
type: native
runner: windows-2022
runs-on: ${{ matrix.runner }}
needs: [check_commits]
steps:
- uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
# without this it will override our rust flags
rustflags: ""
cache-key: ${{ matrix.target }}
- name: Download LLVM
uses: ./.github/actions/get-llvm
with:
target: ${{ matrix.target }}
- name: Build
if: ${{ matrix.type == 'native' }}
shell: bash
run: |
export LLVM_SYS_181_PREFIX=$PWD/llvm-${{ matrix.target }}
make install-bin
mv target/release/resolc resolc-${{ matrix.target }} || mv target/release/resolc.exe resolc-${{ matrix.target }}.exe
- name: Build
if: ${{ matrix.type == 'musl' }}
run: |
docker run -v $PWD:/opt/revive $RUST_MUSL_CROSS_IMAGE /bin/bash -c "
cd /opt/revive
chown -R root:root .
apt update && apt upgrade -y && apt install -y pkg-config
export LLVM_SYS_181_PREFIX=/opt/revive/llvm-${{ matrix.target }}
make install-bin
mv target/${{ matrix.target }}/release/resolc resolc-${{ matrix.target }}
"
sudo chown -R $(id -u):$(id -g) .
- name: Install Solc
uses: ./.github/actions/get-solc
- name: Basic Sanity Check
shell: bash
run: |
result=$(./resolc-${{ matrix.target }} --bin crates/integration/contracts/flipper.sol)
echo $result
if [[ $result == *'0x50564d'* ]]; then exit 0; else exit 1; fi
- name: Upload artifacts (nightly)
uses: actions/upload-artifact@v4
id: artifact-upload-step
with:
name: resolc-${{ matrix.target }}
path: resolc-${{ matrix.target }}*
retention-days: 40
- name: Set output variables (nightly)
id: set-output
shell: bash
run: |
echo "Artifact URL is ${{ steps.artifact-upload-step.outputs.artifact-url }}"
echo "Artifact SHA is ${{ steps.artifact-upload-step.outputs.artifact-digest }}"
echo "resolc-${{ matrix.target }}_url=${{ steps.artifact-upload-step.outputs.artifact-url }}"
echo "resolc-${{ matrix.target }}_url=${{ steps.artifact-upload-step.outputs.artifact-url }}" >> "$GITHUB_OUTPUT"
echo "resolc-${{ matrix.target }}_sha=${{ steps.artifact-upload-step.outputs.artifact-digest }}"
echo "resolc-${{ matrix.target }}_sha=${{ steps.artifact-upload-step.outputs.artifact-digest }}" >> "$GITHUB_OUTPUT"
build-wasm:
runs-on: ubuntu-24.04
needs: [check_commits]
if: ${{ needs.check_commits.outputs.has_commits == 'true' }}
outputs:
resolc-web.js_url: ${{ steps.set-output.outputs.resolc_web_js_url }}
resolc-web.js_sha: ${{ steps.set-output.outputs.resolc_web_js_sha }}
env:
RELEASE_RESOLC_WASM_URI: https://github.com/paritytech/revive/releases/download/${{ github.ref_name }}/resolc.wasm
steps:
- uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
target: wasm32-unknown-emscripten
# without this it will override our rust flags
rustflags: ""
- name: Download Host LLVM
uses: ./.github/actions/get-llvm
with:
target: x86_64-unknown-linux-gnu
- name: Download Wasm LLVM
uses: ./.github/actions/get-llvm
with:
target: wasm32-unknown-emscripten
- name: Download EMSDK
uses: ./.github/actions/get-emsdk
- name: Build
run: |
export LLVM_SYS_181_PREFIX=$PWD/llvm-x86_64-unknown-linux-gnu
export REVIVE_LLVM_TARGET_PREFIX=$PWD/llvm-wasm32-unknown-emscripten
source emsdk/emsdk_env.sh
make install-wasm
chmod -x ./target/wasm32-unknown-emscripten/release/resolc.wasm
- name: Set Up Node.js
uses: actions/setup-node@v3
with:
node-version: "20"
- name: Basic Sanity Check
run: |
mkdir -p solc
curl -sSLo solc/soljson.js https://github.com/ethereum/solidity/releases/download/v0.8.30/soljson.js
node -e "
const soljson = require('solc/soljson');
const createRevive = require('./target/wasm32-unknown-emscripten/release/resolc.js');
const compiler = createRevive();
compiler.soljson = soljson;
const standardJsonInput =
{
language: 'Solidity',
sources: {
'MyContract.sol': {
content: 'pragma solidity ^0.8.0; contract MyContract { function greet() public pure returns (string memory) { return \'Hello\'; } }',
},
},
settings: { optimizer: { enabled: false } }
};
compiler.writeToStdin(JSON.stringify(standardJsonInput));
compiler.callMain(['--standard-json']);
// Collect output
const stdout = compiler.readFromStdout();
const stderr = compiler.readFromStderr();
if (stderr) { console.error(stderr); process.exit(1); }
let out = JSON.parse(stdout);
let bytecode = out.contracts['MyContract.sol']['MyContract'].evm.bytecode.object
console.log(bytecode);
if(!bytecode.startsWith('50564d')) { process.exit(1); }
"
- name: Compress Artifact
run: |
mkdir -p resolc-wasm32-unknown-emscripten
mv ./target/wasm32-unknown-emscripten/release/resolc.js ./resolc-wasm32-unknown-emscripten/
mv ./target/wasm32-unknown-emscripten/release/resolc.wasm ./resolc-wasm32-unknown-emscripten/
mv ./target/wasm32-unknown-emscripten/release/resolc_web.js ./resolc-wasm32-unknown-emscripten/
# There is no way to upload several files as several artifacts with a single upload-artifact step
# It's needed to have resolc_web.js separately for night builds for resolc-bin repo
# https://github.com/actions/upload-artifact/issues/331
- name: Upload artifact resolc.js (nightly)
uses: actions/upload-artifact@v4
with:
name: resolc.js
path: resolc-wasm32-unknown-emscripten/resolc.js
retention-days: 40
- name: Upload artifacts resolc.wasm (nightly)
uses: actions/upload-artifact@v4
with:
name: resolc.wasm
path: resolc-wasm32-unknown-emscripten/resolc.wasm
retention-days: 40
- name: Upload artifacts resolc_web.js (nightly)
uses: actions/upload-artifact@v4
id: artifact-upload-step
with:
name: resolc_web.js
path: resolc-wasm32-unknown-emscripten/resolc_web.js
retention-days: 40
- name: Set output variables
id: set-output
env:
TARGET: resolc_web_js
run: |
echo "Artifact URL is ${{ steps.artifact-upload-step.outputs.artifact-url }}"
echo "Artifact SHA is ${{ steps.artifact-upload-step.outputs.artifact-digest }}"
echo "${TARGET}_url=${{ steps.artifact-upload-step.outputs.artifact-url }}"
echo "${TARGET}_url=${{ steps.artifact-upload-step.outputs.artifact-url }}" >> "$GITHUB_OUTPUT"
echo "${TARGET}_sha=${{ steps.artifact-upload-step.outputs.artifact-digest }}""
echo "${TARGET}_sha=${{ steps.artifact-upload-step.outputs.artifact-digest }}"" >> "$GITHUB_OUTPUT"
uses: ./.github/workflows/reusable-build.yml
with:
is_release: false
retention_days: 40
create-macos-fat-binary:
if: ${{ needs.check_commits.outputs.has_commits == 'true' }}
@@ -277,7 +59,7 @@ jobs:
outputs:
resolc-universal-apple-darwin_url: ${{ steps.set-output.outputs.resolc-universal-apple-darwin_url }}
resolc-universal-apple-darwin_sha: ${{ steps.set-output.outputs.resolc-universal-apple-darwin_sha }}
runs-on: macos-14
runs-on: macos-15
steps:
- uses: actions/download-artifact@v4
with:
@@ -303,18 +85,14 @@ jobs:
env:
TARGET: resolc-universal-apple-darwin
run: |
echo "Artifact URL is ${{ steps.artifact-upload-step.outputs.artifact-url }}"
echo "Artifact SHA is ${{ steps.artifact-upload-step.outputs.artifact-digest }}"
echo "${TARGET}_url=${{ steps.artifact-upload-step.outputs.artifact-url }}"
echo "${TARGET}_url=${{ steps.artifact-upload-step.outputs.artifact-url }}" >> "$GITHUB_OUTPUT"
echo "${TARGET}_sha=${{ steps.artifact-upload-step.outputs.artifact-digest }}""
echo "${TARGET}_sha=${{ steps.artifact-upload-step.outputs.artifact-digest }}"" >> "$GITHUB_OUTPUT"
echo "${TARGET}_sha=${{ steps.artifact-upload-step.outputs.artifact-digest }}" >> "$GITHUB_OUTPUT"
generate-nightly-json:
runs-on: ubuntu-24.04
if: ${{ needs.check_commits.outputs.has_commits == 'true' }}
environment: tags
needs: [build-wasm, build, create-macos-fat-binary, check_commits]
needs: [build, create-macos-fat-binary, check_commits]
steps:
- name: Checkout revive
uses: actions/checkout@v4
@@ -352,8 +130,6 @@ jobs:
echo '[' > data.json
echo '${{ toJSON(needs.build.outputs) }}' >> data.json
echo ',' >> data.json
echo '${{ toJSON(needs.build-wasm.outputs) }}' >> data.json
echo ',' >> data.json
echo '${{ toJSON(needs.create-macos-fat-binary.outputs) }}' >> data.json
echo ']' >> data.json
chmod +x bins/resolc-x86_64-unknown-linux-musl
+21 -185
View File
@@ -1,4 +1,5 @@
name: Build & Release
on:
push:
branches: ["main"]
@@ -14,8 +15,6 @@ concurrency:
env:
CARGO_TERM_COLOR: always
# if changed, dont forget to update the env var in release-nightly.yml
RUST_MUSL_CROSS_IMAGE: messense/rust-musl-cross@sha256:c0154e992adb791c3b848dd008939d19862549204f8cb26f5ca7a00f629e6067
jobs:
check-version-changed:
@@ -29,33 +28,28 @@ jobs:
steps:
- uses: actions/checkout@v4
# Check that tag and version in Cargo.toml match
- name: Check versions
id: versions
run: |
if [[ $CURRENT_TAG == 'main' ]];
then
echo "::notice::Tag $CURRENT_TAG is not a release tag, skipping the check in the main branch";
exit 0
fi
if [[ $CURRENT_TAG == 'main' ]]; then
echo "::notice::Tag $CURRENT_TAG is not a release tag, skipping the check in the main branch"
exit 0
fi
if [[ $CURRENT_TAG != "v"* ]];
then
echo "::notice::Tag $CURRENT_TAG is not a release tag, skipping the check in a PR";
exit 0
fi
if [[ $CURRENT_TAG != "v"* ]]; then
echo "::notice::Tag $CURRENT_TAG is not a release tag, skipping the check in a PR"
exit 0
fi
export PKG_VER=v$(cat crates/resolc/Cargo.toml | grep -A 5 package] | grep version | cut -d '=' -f 2 | tr -d '"' | tr -d " ")
echo "Current tag $CURRENT_TAG"
echo "Package version $PKG_VER"
#
if [[ $CURRENT_TAG != $PKG_VER ]];
then
echo "::error::Tag $CURRENT_TAG doesn't match package version $PKG_VER in Cargo.toml, please fix";
exit 1
fi
# Generating release notes early, in order to avoid checkout at the last step
if [[ $CURRENT_TAG != $PKG_VER ]]; then
echo "::error::Tag $CURRENT_TAG doesn't match package version $PKG_VER in Cargo.toml, please fix"
exit 1
fi
export RELEASE_NOTES="$(sed '/^## '${CURRENT_TAG}'/,/^## v/!d' CHANGELOG.md | sed -e '1d' -e '$d')"
echo "Release notes:"
@@ -66,174 +60,16 @@ jobs:
echo 'EOF' >> $GITHUB_OUTPUT
build:
strategy:
matrix:
target:
[
x86_64-unknown-linux-musl,
aarch64-apple-darwin,
x86_64-apple-darwin,
x86_64-pc-windows-msvc,
]
include:
- target: x86_64-unknown-linux-musl
type: musl
runner: ubuntu-24.04
- target: aarch64-apple-darwin
type: native
runner: macos-14
- target: x86_64-apple-darwin
type: native
runner: macos-13
- target: x86_64-pc-windows-msvc
type: native
runner: windows-2022
runs-on: ${{ matrix.runner }}
needs: [check-version-changed]
steps:
- uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
# without this it will override our rust flags
rustflags: ""
cache-key: ${{ matrix.target }}
- name: Download LLVM
uses: ./.github/actions/get-llvm
with:
target: ${{ matrix.target }}
- name: Build
if: ${{ matrix.type == 'native' }}
shell: bash
run: |
export LLVM_SYS_181_PREFIX=$PWD/llvm-${{ matrix.target }}
make install-bin
mv target/release/resolc resolc-${{ matrix.target }} || mv target/release/resolc.exe resolc-${{ matrix.target }}.exe
- name: Build
if: ${{ matrix.type == 'musl' }}
run: |
docker run -v $PWD:/opt/revive $RUST_MUSL_CROSS_IMAGE /bin/bash -c "
cd /opt/revive
chown -R root:root .
apt update && apt upgrade -y && apt install -y pkg-config
export LLVM_SYS_181_PREFIX=/opt/revive/llvm-${{ matrix.target }}
make install-bin
mv target/${{ matrix.target }}/release/resolc resolc-${{ matrix.target }}
"
sudo chown -R $(id -u):$(id -g) .
- name: Install Solc
uses: ./.github/actions/get-solc
- name: Basic Sanity Check
shell: bash
run: |
result=$(./resolc-${{ matrix.target }} --bin crates/integration/contracts/flipper.sol)
echo $result
if [[ $result == *'0x50564d'* ]]; then exit 0; else exit 1; fi
- uses: actions/upload-artifact@v4
with:
name: resolc-${{ matrix.target }}
path: resolc-${{ matrix.target }}*
retention-days: 1
build-wasm:
runs-on: ubuntu-24.04
needs: [check-version-changed]
env:
RELEASE_RESOLC_WASM_URI: https://github.com/paritytech/revive/releases/download/${{ github.ref_name }}/resolc.wasm
steps:
- uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
target: wasm32-unknown-emscripten
# without this it will override our rust flags
rustflags: ""
- name: Download Host LLVM
uses: ./.github/actions/get-llvm
with:
target: x86_64-unknown-linux-gnu
- name: Download Wasm LLVM
uses: ./.github/actions/get-llvm
with:
target: wasm32-unknown-emscripten
- name: Download EMSDK
uses: ./.github/actions/get-emsdk
- name: Build
run: |
export LLVM_SYS_181_PREFIX=$PWD/llvm-x86_64-unknown-linux-gnu
export REVIVE_LLVM_TARGET_PREFIX=$PWD/llvm-wasm32-unknown-emscripten
source emsdk/emsdk_env.sh
make install-wasm
chmod -x ./target/wasm32-unknown-emscripten/release/resolc.wasm
- name: Set Up Node.js
uses: actions/setup-node@v3
with:
node-version: "20"
- name: Basic Sanity Check
run: |
mkdir -p solc
curl -sSLo solc/soljson.js https://github.com/ethereum/solidity/releases/download/v0.8.30/soljson.js
node -e "
const soljson = require('solc/soljson');
const createRevive = require('./target/wasm32-unknown-emscripten/release/resolc.js');
const compiler = createRevive();
compiler.soljson = soljson;
const standardJsonInput =
{
language: 'Solidity',
sources: {
'MyContract.sol': {
content: 'pragma solidity ^0.8.0; contract MyContract { function greet() public pure returns (string memory) { return \'Hello\'; } }',
},
},
settings: { optimizer: { enabled: false } }
};
compiler.writeToStdin(JSON.stringify(standardJsonInput));
compiler.callMain(['--standard-json']);
// Collect output
const stdout = compiler.readFromStdout();
const stderr = compiler.readFromStderr();
if (stderr) { console.error(stderr); process.exit(1); }
let out = JSON.parse(stdout);
let bytecode = out.contracts['MyContract.sol']['MyContract'].evm.bytecode.object
console.log(bytecode);
if(!bytecode.startsWith('50564d')) { process.exit(1); }
"
- name: Compress Artifact
run: |
mkdir -p resolc-wasm32-unknown-emscripten
mv ./target/wasm32-unknown-emscripten/release/resolc.js ./resolc-wasm32-unknown-emscripten/
mv ./target/wasm32-unknown-emscripten/release/resolc.wasm ./resolc-wasm32-unknown-emscripten/
mv ./target/wasm32-unknown-emscripten/release/resolc_web.js ./resolc-wasm32-unknown-emscripten/
- uses: actions/upload-artifact@v4
with:
name: resolc-wasm32-unknown-emscripten
path: resolc-wasm32-unknown-emscripten/*
retention-days: 1
uses: ./.github/workflows/reusable-build.yml
with:
is_release: true
retention_days: 1
create-release:
if: startsWith(github.ref_name, 'v')
needs: [check-version-changed, build-wasm]
runs-on: macos-14
needs: [check-version-changed, build]
runs-on: macos-15
environment: tags
steps:
- name: Download Artifacts
@@ -290,7 +126,7 @@ jobs:
npm-release:
needs: [create-release]
runs-on: macos-14
runs-on: macos-15
environment: tags
steps:
- uses: actions/checkout@v4
+264
View File
@@ -0,0 +1,264 @@
name: Reusable Build
on:
workflow_call:
inputs:
is_release:
description: "Whether this is a release build"
required: true
type: boolean
retention_days:
description: "Artifact retention days"
required: false
type: number
default: 1
outputs:
resolc-x86_64-unknown-linux-musl_url:
value: ${{ jobs.build.outputs.resolc-x86_64-unknown-linux-musl_url }}
resolc-x86_64-unknown-linux-musl_sha:
value: ${{ jobs.build.outputs.resolc-x86_64-unknown-linux-musl_sha }}
resolc-aarch64-apple-darwin_url:
value: ${{ jobs.build.outputs.resolc-aarch64-apple-darwin_url }}
resolc-aarch64-apple-darwin_sha:
value: ${{ jobs.build.outputs.resolc-aarch64-apple-darwin_sha }}
resolc-x86_64-apple-darwin_url:
value: ${{ jobs.build.outputs.resolc-x86_64-apple-darwin_url }}
resolc-x86_64-apple-darwin_sha:
value: ${{ jobs.build.outputs.resolc-x86_64-apple-darwin_sha }}
resolc-x86_64-pc-windows-msvc_url:
value: ${{ jobs.build.outputs.resolc-x86_64-pc-windows-msvc_url }}
resolc-x86_64-pc-windows-msvc_sha:
value: ${{ jobs.build.outputs.resolc-x86_64-pc-windows-msvc_sha }}
resolc-web_js_url:
value: ${{ jobs.build-wasm.outputs.resolc_web_js_url }}
resolc-web_js_sha:
value: ${{ jobs.build-wasm.outputs.resolc_web_js_sha }}
env:
CARGO_TERM_COLOR: always
RUST_MUSL_CROSS_IMAGE: messense/rust-musl-cross@sha256:c0154e992adb791c3b848dd008939d19862549204f8cb26f5ca7a00f629e6067
jobs:
build:
# github actions matrix jobs don't support multiple outputs
# ugly workaround from https://github.com/orgs/community/discussions/17245#discussioncomment-11222880
outputs:
resolc-x86_64-unknown-linux-musl_url: ${{ steps.set-output.outputs.resolc-x86_64-unknown-linux-musl_url }}
resolc-x86_64-unknown-linux-musl_sha: ${{ steps.set-output.outputs.resolc-x86_64-unknown-linux-musl_sha }}
resolc-aarch64-apple-darwin_url: ${{ steps.set-output.outputs.resolc-aarch64-apple-darwin_url }}
resolc-aarch64-apple-darwin_sha: ${{ steps.set-output.outputs.resolc-aarch64-apple-darwin_sha }}
resolc-x86_64-apple-darwin_url: ${{ steps.set-output.outputs.resolc-x86_64-apple-darwin_url }}
resolc-x86_64-apple-darwin_sha: ${{ steps.set-output.outputs.resolc-x86_64-apple-darwin_sha }}
resolc-x86_64-pc-windows-msvc_url: ${{ steps.set-output.outputs.resolc-x86_64-pc-windows-msvc_url }}
resolc-x86_64-pc-windows-msvc_sha: ${{ steps.set-output.outputs.resolc-x86_64-pc-windows-msvc_sha }}
strategy:
matrix:
target:
[
x86_64-unknown-linux-musl,
aarch64-apple-darwin,
x86_64-apple-darwin,
x86_64-pc-windows-msvc,
]
include:
- target: x86_64-unknown-linux-musl
type: musl
runner: ubuntu-24.04
- target: aarch64-apple-darwin
type: native
runner: macos-15
- target: x86_64-apple-darwin
type: native
# `macos-15-intel` will be the last x86_64 `macos` image supported by GHA.
# It will be available until Aug. 2027 (see https://github.com/actions/runner-images/issues/13045).
runner: macos-15-intel
- target: x86_64-pc-windows-msvc
type: native
runner: windows-2022
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
rustflags: ""
cache-key: ${{ matrix.target }}
- name: Download LLVM
uses: ./.github/actions/get-llvm
with:
target: ${{ matrix.target }}
- name: Build (Native)
if: ${{ matrix.type == 'native' }}
shell: bash
run: |
export LLVM_SYS_181_PREFIX=$PWD/llvm-${{ matrix.target }}
make install-bin
mv target/release/resolc resolc-${{ matrix.target }} || mv target/release/resolc.exe resolc-${{ matrix.target }}.exe
- name: Build (MUSL)
if: ${{ matrix.type == 'musl' }}
run: |
docker run -v $PWD:/opt/revive $RUST_MUSL_CROSS_IMAGE /bin/bash -c "
cd /opt/revive
chown -R root:root .
apt update && apt upgrade -y && apt install -y pkg-config
export LLVM_SYS_181_PREFIX=/opt/revive/llvm-${{ matrix.target }}
make install-bin
mv target/${{ matrix.target }}/release/resolc resolc-${{ matrix.target }}
"
sudo chown -R $(id -u):$(id -g) .
- name: Install Solc
uses: ./.github/actions/get-solc
- name: Basic Sanity Check
shell: bash
run: |
result=$(./resolc-${{ matrix.target }} --bin crates/integration/contracts/flipper.sol)
echo $result
if [[ $result == *'50564d'* ]]; then exit 0; else exit 1; fi
- uses: actions/upload-artifact@v4
id: artifact-upload-step
with:
name: resolc-${{ matrix.target }}
path: resolc-${{ matrix.target }}*
retention-days: ${{ inputs.retention_days }}
- name: Set output variables
if: ${{ !inputs.is_release }}
id: set-output
shell: bash
run: |
echo "resolc-${{ matrix.target }}_url=${{ steps.artifact-upload-step.outputs.artifact-url }}" >> "$GITHUB_OUTPUT"
echo "resolc-${{ matrix.target }}_sha=${{ steps.artifact-upload-step.outputs.artifact-digest }}" >> "$GITHUB_OUTPUT"
build-wasm:
runs-on: ubuntu-24.04
outputs:
resolc_web_js_url: ${{ steps.set-output.outputs.resolc_web_js_url }}
resolc_web_js_sha: ${{ steps.set-output.outputs.resolc_web_js_sha }}
env:
RELEASE_RESOLC_WASM_URI: https://github.com/paritytech/revive/releases/download/${{ github.ref_name }}/resolc.wasm
steps:
- uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
target: wasm32-unknown-emscripten
rustflags: ""
- name: Download Host LLVM
uses: ./.github/actions/get-llvm
with:
target: x86_64-unknown-linux-gnu
- name: Download Wasm LLVM
uses: ./.github/actions/get-llvm
with:
target: wasm32-unknown-emscripten
- name: Download EMSDK
uses: ./.github/actions/get-emsdk
- name: Build
run: |
export LLVM_SYS_181_PREFIX=$PWD/llvm-x86_64-unknown-linux-gnu
export REVIVE_LLVM_TARGET_PREFIX=$PWD/llvm-wasm32-unknown-emscripten
source emsdk/emsdk_env.sh
make install-wasm
chmod -x ./target/wasm32-unknown-emscripten/release/resolc.wasm
- name: Set Up Node.js
uses: actions/setup-node@v3
with:
node-version: "20"
- name: Basic Sanity Check
run: |
mkdir -p solc
curl -sSLo solc/soljson.js https://github.com/ethereum/solidity/releases/download/v0.8.30/soljson.js
node -e "
const soljson = require('solc/soljson');
const createRevive = require('./target/wasm32-unknown-emscripten/release/resolc.js');
const compiler = createRevive();
compiler.soljson = soljson;
const standardJsonInput =
{
language: 'Solidity',
sources: {
'MyContract.sol': {
content: 'pragma solidity ^0.8.0; contract MyContract { function greet() public pure returns (string memory) { return \'Hello\'; } }',
},
},
settings: { optimizer: { enabled: false } }
};
compiler.writeToStdin(JSON.stringify(standardJsonInput));
compiler.callMain(['--standard-json']);
const stdout = compiler.readFromStdout();
const stderr = compiler.readFromStderr();
if (stderr) { console.error(stderr); process.exit(1); }
let out = JSON.parse(stdout);
let bytecode = out.contracts['MyContract.sol']['MyContract'].evm.bytecode.object
console.log(bytecode);
if(!bytecode.startsWith('50564d')) { process.exit(1); }
"
- name: Compress Artifact
run: |
mkdir -p resolc-wasm32-unknown-emscripten
mv ./target/wasm32-unknown-emscripten/release/resolc.js ./resolc-wasm32-unknown-emscripten/
mv ./target/wasm32-unknown-emscripten/release/resolc.wasm ./resolc-wasm32-unknown-emscripten/
mv ./target/wasm32-unknown-emscripten/release/resolc_web.js ./resolc-wasm32-unknown-emscripten/
- name: Upload artifacts (Release)
if: ${{ inputs.is_release }}
uses: actions/upload-artifact@v4
with:
name: resolc-wasm32-unknown-emscripten
path: resolc-wasm32-unknown-emscripten/*
retention-days: ${{ inputs.retention_days }}
# There is no way to upload several files as several artifacts with a single upload-artifact step
# It's needed to have resolc_web.js separately for night builds for resolc-bin repo
# https://github.com/actions/upload-artifact/issues/331
- name: Upload artifact resolc.js (Nightly)
if: ${{ !inputs.is_release }}
uses: actions/upload-artifact@v4
with:
name: resolc.js
path: resolc-wasm32-unknown-emscripten/resolc.js
retention-days: ${{ inputs.retention_days }}
- name: Upload artifacts resolc.wasm (Nightly)
if: ${{ !inputs.is_release }}
uses: actions/upload-artifact@v4
with:
name: resolc.wasm
path: resolc-wasm32-unknown-emscripten/resolc.wasm
retention-days: ${{ inputs.retention_days }}
- name: Upload artifacts resolc_web.js (Nightly)
if: ${{ !inputs.is_release }}
uses: actions/upload-artifact@v4
id: artifact-upload-step
with:
name: resolc_web.js
path: resolc-wasm32-unknown-emscripten/resolc_web.js
retention-days: ${{ inputs.retention_days }}
- name: Set output variables
if: ${{ !inputs.is_release }}
id: set-output
env:
TARGET: resolc_web_js
run: |
echo "${TARGET}_url=${{ steps.artifact-upload-step.outputs.artifact-url }}" >> "$GITHUB_OUTPUT"
echo "${TARGET}_sha=${{ steps.artifact-upload-step.outputs.artifact-digest }}" >> "$GITHUB_OUTPUT"
+4 -5
View File
@@ -4,11 +4,8 @@ on:
branches: ["main"]
types: [opened, synchronize]
paths:
- 'LLVM.lock'
- 'crates/llvm-builder/**'
- '.github/workflows/test-llvm-builder.yml'
paths-ignore:
- "**.md"
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
@@ -21,10 +18,12 @@ jobs:
test:
strategy:
matrix:
runner: [parity-large, macos-14, windows-2022]
runner: [parity-large, macos-15, windows-2022]
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4
with:
submodules: true
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
# without this it will override our rust flags
@@ -37,7 +36,7 @@ jobs:
sudo apt update && sudo apt-get install -y cmake ninja-build curl git libssl-dev pkg-config clang lld musl
- name: Install Dependencies
if: matrix.runner == 'macos-14'
if: matrix.runner == 'macos-15'
run: |
brew install ninja
+1 -1
View File
@@ -68,7 +68,7 @@ jobs:
needs: build
strategy:
matrix:
os: ["ubuntu-24.04", "macos-14", "windows-2022"]
os: ["ubuntu-24.04", "macos-15", "windows-2022"]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
+4
View File
@@ -26,6 +26,7 @@ jobs:
with:
# without this it will override our rust flags
rustflags: ""
components: rustfmt, clippy
- name: Install Solc
uses: ./.github/actions/get-solc
@@ -56,3 +57,6 @@ jobs:
- name: Test cargo workspace
run: make test-workspace
- name: Test docs
run: make doc
+5 -1
View File
@@ -3,12 +3,15 @@
target-llvm
*.dot
.vscode/
.zed/
.DS_Store
/*.sol
/*.yul
/*.ll
/*.s
/llvm*
/llvm-*
# Allow llvm submodule directory
!/llvm
node_modules
artifacts
tmp
@@ -19,3 +22,4 @@ test-results
playwright-report
.cache
emsdk
docs-tmp
+4
View File
@@ -0,0 +1,4 @@
[submodule "llvm"]
path = llvm
url = https://github.com/llvm/llvm-project.git
branch = release/18.x
+3
View File
@@ -0,0 +1,3 @@
# This is no longer in .gitignore; we add it here (honored by tools like rg and fd).
llvm/
docs/
+46
View File
@@ -4,23 +4,69 @@
This is a development pre-release.
Supported `polkadot-sdk` rev: `2509.0.0`
### Added
- The comprehensive revive compiler book documentation page: https://paritytech.github.io/revive/
### Changed
- Instruct the LLVM backend and linker to `--relax` (may lead to smaller contract code size).
- Standard JSON mode: Don't forward EVM bytecode related output selections to solc.
### Fixed:
- The missing `STOP` instruction at the end of `code` blocks.
## v0.5.0
This is a development pre-release.
Supported `polkadot-sdk` rev: `2509.0.0`
### Added
- Support for `SELFDESTRUCT`.
### Changed
- Emulated EVM heap memory accesses of zero length are never out of bounds.
- Switched to newer and cheaper storage syscalls (omits reads and writes of `0` values).
### Fixed
- Introduced a workaround avoiding compiler crashes caused by a bug in LLVM affecting `SDIV`.
- An off-by-one bug affecting `SDIV` overflow semantics.
## v0.4.1
This is a development pre-release.
Supported `polkadot-sdk` rev: `2503.0.1`
### Changed
- The `ast` output is no longer pruned in standard JSON mode (required for foundry).
- Support `standard_json.output_selection` to also look at per file settings.
## v0.4.0
This is a development pre-release.
Supported `polkadot-sdk` rev: `2503.0.1`
### Changed
- Remove the broken `--llvm-ir` mode.
- Remove the unused fallback for size optimization setting.
- Unlinked contract binaries are emitted as raw ELF objects.
### Added
- Line debug information per YUL builtin and for `if` statements.
- Column numbers in debug information.
- Support for the YUL optimizer details in the standard json input definition.
- The `revive-explorer` compiler utility.
- `revive-yul`: The AST visitor interface.
- The `--link` deploy time linking mode.
### Fixed
- The debug info source file matches the YUL path in `--debug-output-dir`, allowing tools to display the source line.
- Incosistent type forwarding in JSON output (empty string vs. null object).
- The solc automatic import resolution.
- Compiler panic on missing libraries definition.
## v0.3.0
Generated
+2928 -2098
View File
File diff suppressed because it is too large Load Diff
+34 -35
View File
@@ -14,70 +14,69 @@ repository = "https://github.com/paritytech/revive"
rust-version = "1.85.0"
[workspace.dependencies]
resolc = { version = "0.3.0", path = "crates/resolc" }
revive-benchmarks = { version = "0.1.0", path = "crates/benchmarks" }
revive-builtins = { version = "0.1.0", path = "crates/builtins" }
revive-common = { version = "0.1.0", path = "crates/common" }
revive-differential = { version = "0.1.0", path = "crates/differential" }
revive-explorer = { version = "0.1.0", path = "crates/explore" }
revive-integration = { version = "0.1.1", path = "crates/integration" }
revive-linker = { version = "0.1.0", path = "crates/linker" }
lld-sys = { version = "0.1.0", path = "crates/lld-sys" }
revive-llvm-context = { version = "0.3.0", path = "crates/llvm-context" }
revive-runtime-api = { version = "0.2.0", path = "crates/runtime-api" }
revive-runner = { version = "0.1.0", path = "crates/runner" }
revive-solc-json-interface = { version = "0.2.0", path = "crates/solc-json-interface" }
revive-stdlib = { version = "0.1.1", path = "crates/stdlib" }
revive-build-utils = { version = "0.1.0", path = "crates/build-utils" }
revive-yul = { version = "0.2.1", path = "crates/yul" }
resolc = { version = "0.5.0", path = "crates/resolc", default-features = false }
revive-benchmarks = { version = "0.1.0", path = "crates/benchmarks" }
revive-build-utils = { version = "0.2.0", path = "crates/build-utils" }
revive-builtins = { version = "0.1.0", path = "crates/builtins" }
revive-common = { version = "0.2.1", path = "crates/common" }
revive-differential = { version = "0.2.0", path = "crates/differential" }
revive-explorer = { version = "0.1.0", path = "crates/explore" }
revive-integration = { version = "0.3.0", path = "crates/integration" }
revive-linker = { version = "0.2.0", path = "crates/linker" }
revive-llvm-context = { version = "0.5.0", path = "crates/llvm-context" }
revive-runner = { version = "0.3.0", path = "crates/runner" }
revive-runtime-api = { version = "0.4.0", path = "crates/runtime-api" }
revive-solc-json-interface = { version = "0.4.0", path = "crates/solc-json-interface", default-features = false }
revive-stdlib = { version = "0.2.0", path = "crates/stdlib" }
revive-yul = { version = "0.4.0", path = "crates/yul" }
hex = "0.4.3"
cc = "1.2"
libc = "0.2.172"
tempfile = "3.20"
tempfile = "3.23"
anyhow = "1.0"
semver = { version = "1.0", features = ["serde"] }
itertools = "0.14"
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", features = ["arbitrary_precision"] }
regex = "1.11"
regex = "1.12"
once_cell = "1.21"
num = "0.4.3"
sha1 = "0.10"
sha3 = "0.10"
thiserror = "2.0"
which = "7.0"
which = "8.0"
path-slash = "0.2"
rayon = "1.10"
rayon = "1.11"
clap = { version = "4", default-features = false, features = ["derive"] }
polkavm-common = "0.24.0"
polkavm-linker = "0.24.0"
polkavm-disassembler = "0.24.0"
polkavm = "0.24.0"
alloy-primitives = { version = "1.1", features = ["serde"] }
alloy-sol-types = "1.1"
alloy-genesis = "1.0"
alloy-serde = "1.0"
alloy-core = "1.3"
polkavm-common = "0.30.0"
polkavm-linker = "0.30.0"
polkavm-disassembler = "0.30.0"
polkavm = "0.30.0"
alloy-primitives = { version = "1.4", features = ["serde"] }
alloy-sol-types = "1.4"
alloy-genesis = "1.1.2"
alloy-serde = "1.1"
env_logger = { version = "0.11.8", default-features = false }
serde_stacker = "0.1.12"
criterion = { version = "0.6", features = ["html_reports"] }
log = { version = "0.4.27" }
serde_stacker = "0.1.14"
criterion = { version = "0.7", features = ["html_reports"] }
log = { version = "0.4.28" }
git2 = { version = "0.20.2", default-features = false }
downloader = "0.2.8"
flate2 = "1.1"
fs_extra = "1.3"
num_cpus = "1"
tar = "0.4"
toml = "0.8"
assert_cmd = "2.0"
toml = "0.9"
assert_cmd = "2"
assert_fs = "1.1"
pallet-revive-uapi = "0.7.0"
normpath = "1.5"
# polkadot-sdk and friends
codec = { version = "3.7.5", default-features = false, package = "parity-scale-codec" }
scale-info = { version = "2.11.6", default-features = false }
polkadot-sdk = { version = "2503.0.1" }
polkadot-sdk = { version = "2509.0.0" }
# llvm
[workspace.dependencies.inkwell]
-3
View File
@@ -1,3 +0,0 @@
url = "https://github.com/llvm/llvm-project.git"
branch = "release/18.x"
ref = "3b5b5c1ec4a3095ab096dd780e84d7ab81f3d7ff"
+36 -5
View File
@@ -9,16 +9,22 @@
install-revive-explorer \
format \
clippy \
doc \
book \
machete \
test \
test-integration \
test-resolc \
test-yul \
test-workspace \
test-wasm \
test-llvm-builder
test-llvm-builder \
test-book \
bench \
bench-pvm \
bench-evm \
bench-resolc \
bench-yul \
clean
install: install-bin install-npm
@@ -37,7 +43,7 @@ install-llvm-builder:
cargo install --force --locked --path crates/llvm-builder
install-llvm: install-llvm-builder
revive-llvm clone
git submodule update --init --recursive --depth 1
revive-llvm build --llvm-projects lld --llvm-projects clang
install-revive-runner:
@@ -52,20 +58,29 @@ format:
clippy:
cargo clippy --all-features --workspace --tests --benches -- --deny warnings
doc:
cargo doc --all-features --workspace --document-private-items --no-deps
book: test-book
mdbook serve book --open
machete:
cargo install cargo-machete
cargo machete
test: format clippy machete test-workspace install-revive-runner install-revive-explorer
test: format clippy machete test-workspace install-revive-runner install-revive-explorer doc test-book
test-integration: install-bin
cargo test --package revive-integration
test-resolc: install
cargo test --package resolc
cargo test --package resolc --all-targets
test-yul:
cargo test --package revive-yul --all-targets
test-workspace: install
cargo test --workspace --exclude revive-llvm-builder
cargo test --workspace --all-targets --exclude revive-llvm-builder
test-wasm: install-wasm
npm run test:wasm
@@ -74,6 +89,11 @@ test-llvm-builder:
@echo "warning: the llvm-builder tests will take many hours"
cargo test --package revive-llvm-builder -- --test-threads=1
test-book:
cargo install mdbook --version 0.5.1 --locked
mdbook test book
mdbook build book
bench: install-bin
cargo criterion --all --all-features --message-format=json \
| criterion-table > crates/benchmarks/BENCHMARKS.md
@@ -86,6 +106,16 @@ bench-evm: install-bin
cargo criterion --bench execute --features bench-evm --message-format=json \
| criterion-table > crates/benchmarks/EVM.md
bench-resolc: test-resolc
cargo criterion --package resolc --bench compile --message-format=json \
| criterion-table > crates/resolc/BENCHMARKS_M4PRO.md
bench-yul: test-yul
cargo criterion --package revive-yul --bench parse --message-format=json \
| criterion-table > crates/yul/BENCHMARKS_PARSE_M4PRO.md
cargo criterion --package revive-yul --bench lower --message-format=json \
| criterion-table > crates/yul/BENCHMARKS_LOWER_M4PRO.md
clean:
cargo clean ; \
revive-llvm clean ; \
@@ -93,3 +123,4 @@ clean:
rm -rf crates/resolc/src/tests/cli/artifacts ; \
cargo uninstall resolc ; \
cargo uninstall revive-llvm-builder ;
mdbook clean book
+8 -42
View File
@@ -1,11 +1,11 @@
![CI](https://github.com/paritytech/revive/actions/workflows/test.yml/badge.svg)
[![Docs](https://img.shields.io/badge/Docs-contracts.polkadot.io-brightgreen.svg)](https://contracts.polkadot.io/revive_compiler/)
[![Docs](https://img.shields.io/badge/paritytech.github.io/revive-brightgreen.svg)](https://paritytech.github.io/revive/)
# revive
YUL recompiler to LLVM, targetting RISC-V on [PolkaVM](https://github.com/koute/polkavm).
Yul recompiler to LLVM, targetting RISC-V on [PolkaVM](https://github.com/koute/polkavm).
Visit [contracts.polkadot.io](https://contracts.polkadot.io) to learn more about contracts on Polkadot!
Check the [docs](https://paritytech.github.io/revive/) or visit [contracts.polkadot.io](https://docs.polkadot.com/develop/smart-contracts/) to learn more about `revive` and contracts on Polkadot!
## Status
@@ -14,11 +14,13 @@ This is experimental software in active development and not ready just yet for p
Discussion around the development is hosted on the [Polkadot Forum](https://forum.polkadot.network/t/contracts-update-solidity-on-polkavm/6949#a-new-solidity-compiler-1).
## Installation
Building Solidity contracts for PolkaVM requires installing the following two compilers:
- `resolc`: The revive Solidity compiler YUL frontend and PolkaVM code generator (provided by this repository).
- `resolc`: The revive Solidity compiler Yul frontend and PolkaVM code generator (provided by this repository).
- `solc`: The [Ethereum Solidity reference compiler](https://github.com/ethereum/solidity/) implemenation.`resolc` uses `solc` during the compilation process, please refer to the [Ethereum Solidity documentation](https://docs.soliditylang.org/en/latest/installing-solidity.html) for installation instructions.
### `resolc` binary releases
`resolc` is distributed as a standalone binary (with `solc` as the only external dependency). Please download one of our [binary releases](https://github.com/paritytech/revive/releases) for your target platform and mind the platform specific instructions below.
<details>
@@ -44,6 +46,7 @@ Building Solidity contracts for PolkaVM requires installing the following two co
### `resolc` NPM package
We distribute the revive compiler as [node.js module](https://www.npmjs.com/package/@parity/resolc) and [hardhat plugin](https://www.npmjs.com/package/@parity/hardhat-polkadot-resolc).
Note: The `solc` dependency is bundled via NPM packaging and defaults to the latest supported version.
@@ -96,47 +99,10 @@ make install-bin
resolc --version
```
### Cross-compilation to Wasm
Cross-compile the `resolc.js` frontend executable to Wasm for running it in a Node.js or browser environment. The `REVIVE_LLVM_TARGET_PREFIX` environment variable is used to control the target environment LLVM dependency.
<details>
<summary>Instructions for cross-compilation to wasm32-unknown-emscripten</summary>
```sh
# Build the host LLVM dependency with PolkaVM target support
make install-llvm
export LLVM_SYS_181_PREFIX=${PWD}/target-llvm/gnu/target-final
# Build the target LLVM dependency with PolkaVM target support
revive-llvm --target-env emscripten clone
source emsdk/emsdk_env.sh
revive-llvm --target-env emscripten build --llvm-projects lld
export REVIVE_LLVM_TARGET_PREFIX=${PWD}/target-llvm/emscripten/target-final
# Build the resolc frontend executable
make install-wasm
make test-wasm
```
</details>
## Development
Please consult the [Makefile](Makefile) targets to learn how to run tests and benchmarks.
Ensure that your branch passes `make test` locally when submitting a pull request.
Please consult the [Developer Guide](https://paritytech.github.io/revive/developer_guide/contributing.html) to learn more about how contribute to the project.
### Design overview
See the [relevant section in our documentation](https://contracts.polkadot.io/revive_compiler/architecture) to learn more about how the compiler works.
### Tests
Before running the tests, ensure that Geth (Go Ethereum) is installed on your system. Follow the installation guide here: [Installing Geth](https://geth.ethereum.org/docs/getting-started/installing-geth).
Once Geth is installed, you can run the tests using the following command:
```sh
make test
```
# Acknowledgements
The revive compiler project, after some early experiments with EVM bytecode translations, decided to fork the `era-compiler` framework.
+1
View File
@@ -0,0 +1 @@
book
+7
View File
@@ -0,0 +1,7 @@
[book]
title = "revive compiler book"
authors = ["xermicus"]
language = "en"
[build]
build-dir = "../docs"
+21
View File
@@ -0,0 +1,21 @@
# Summary
- [Welcome](./welcome.md)
- [`resolc` user guide](./user_guide.md)
- [Installation](./user_guide/installation.md)
- [Command Line Interface](./user_guide/cli.md)
- [JS NPM package](./user_guide/js.md)
- [Tooling integration](./user_guide/tooling.md)
- [Standard JSON interface](./user_guide/std_json.md)
- [Differences to EVM](./user_guide/differences.md)
- [Rust contract libraries](./user_guide/rust_libraries.md)
- [`revive-runner` sandbox](./revive_runner.md)
- [Developer Guide](./developer_guide.md)
- [Contributor guide](./developer_guide/contributing.md)
- [Compiler architecture](./developer_guide/architecture.md)
- [PVM and the pallet-revive runtime target](./developer_guide/target.md)
- [Testing strategy](./developer_guide/testing.md)
- [Cross compilation](./developer_guide/cross_compilation.md)
- [FAQ](./faq.md)
- [Roadmap and Vision](./roadmap.md)
+4
View File
@@ -0,0 +1,4 @@
# Developer guide
This chapter covers internal aspects of the compiler and helps contributors getting started with the `revive` codebase.
+55
View File
@@ -0,0 +1,55 @@
# Compiler architecture and internals
`revive` relies on `solc`, the [Ethereum Solidity compiler](https://github.com/argotorg/solidity), as the Solidity frontend to process smart contracts written in Solidity. [LLVM](https://github.com/llvm/llvm-project), a popular and powerful compiler framework, is used as the compiler backend and does the heavy lifting in terms of optimizitations and RISC-V code generation.
`revive` mainly takes care of lowering the Yul intermediate representation (IR) produced by `solc` to LLVM IR. This approach provides a good balance between maintaining a high level of Ethereum compatibility, good contract performance and feasible engineering efforts.
## `resolc`
`resolc` is the overarching compiler driver library and binary.
When compiling a Solidity source file with `resolc`, the following steps happen under the hood:
1. `solc` is used to lower the Solidity source code into [YUL intermediate representation](https://docs.soliditylang.org/en/latest/yul.html).
2. `revive` lowers the YUL IR into LLVM IR.
3. LLVM optimizes the code and emits a RISC-V ELF shared object (through LLD).
4. The [PolkaVM](https://github.com/paritytech/polkavm) linker finally links the ELF shared object into a PolkaVM blob.
This compilation process can be visualized as follows:
![Architecture Overview](images/resolc.svg)
## Reproducible contract builds
Because on-chain contract code is identified via its code blob hash, it is crucial to maintain reproducible contract builds. A given compiler version must reproduce the contract build _exactly_ on every target platform `resolc` supports via the official binary releases.
To ensure this, we employ the following measures:
- The code generation must be fully deterministic. For example iterating over standard `HashMap` invalidates this due to its internal state, making it an invalid operation in `revive`. To circumvent that, a `BTreeMap` can be used instead.
- We release fully statically linked `resolc` binaries. This prevents dynamic linking of potentially differentiating libraries.
- The only non-bundled dependency is the `solc` compiler. This is considered fine because the same properties apply to `solc`.
## The `revive` compiler libraries
The main compiler logic is implemented in the `revive-yul` and `revive-llvm-context` crates.
The Yul library implements a lexer and parser and lowers the resulting tree into LLVM IR. It does so by emitting LL using the LLVM builder and our own `revive-llvm-context` compiler context crate. The revive LLVM context crate encapsulates code generation logic (decoupled from the parser).
The Yul library also implements a simple [visitor](https://en.wikipedia.org/wiki/Visitor_pattern) interface (see [visitor.rs](https://github.com/paritytech/revive/blob/main/crates/yul/src/visitor.rs)). If you want to work with the AST, it is strongly recommended to implement visitors. The LLVM code generation is implemented using a dedicated trait for historical reasons only.
## EVM heap memory
PVM doesn't offer a similar API. Hence the emitted contract code emulates the linear EVM heap memory using a static byte buffer. Data inside this byte buffer is kept big endian for EVM compatibility reasons (unaligned access is allowed and makes optimizing this non-trivial).
Unlike with the EVM, where heap memory usage is gas metered, our heap size is static (the size is user controllable via a setting flag). The compiler emits bound checks to prevent overflows.
## The LLVM dependency
LLVM is a special non Rust dependency. We interface its builder interface via the [inkwell](https://crates.io/crates/inkwell) wrapper crate.
We use upstream LLVM, but release and use our custom builds. We require the compiler builtins specifically built for the PVM rv64e target and always leave assertions on. Furthermore, we need cross builds because `resolc` itself targets emscripten and musl. The [revive-llvm-builer](https://crates.io/crates/revive-llvm-builder) functions as a cross-platform build script and is used to build and release the LLVM dependency.
We also maintain the [lld-sys crate](https://crates.io/crates/lld-sys) for interfacing with `LLD`. The LLVM linker is used during the compilation process, but we don't want to distribute another binary.
## Custom optimizations
At the moment, no significant custom optimizations are implemented. Thus, we are missing some optimization opportunities that neither `solc` nor LLVM can realize (due to their lack of domain specific knowledge about the semantics of our target environment). Furthermore, `solc` optimizes for EVM gas and a target machine orthogonal to our target (BE 256-bit stack machine EVM vs. 64-bit LE RISC architecture PVM). We have started working on an additional IR layer between Yul and LLVM to capture missed optimization opportunities, though.
+65
View File
@@ -0,0 +1,65 @@
# Contributor guide
The `revive` compiler is an open source software project and we gladly accept quality contributions from anyone!
## Getting started
A quick reference on how to build the Solidity compiler is maintained in the project's [README.md](https://github.com/paritytech/revive?tab=readme-ov-file#building-from-source).
### Using the `Makefile`
The [Makefile](https://github.com/paritytech/revive/blob/main/Makefile) comprehensively encapsulates all development aspects of this codebase. It is kept concise and readable. Please read and use it! You'll learn for example:
- How to build and install a `resolc` development version.
- How to run tests and benchmarks.
- How to cross-compile `resolc`.
As a general rule-of-thumb: If `make test` runs fine locally, chances for green CI pipelines are good.
### Codebase organization
For the most parts, `revive` is a rather standard Rust workspace codebase. There are some non-Rust dependencies, which sometimes complicates things a little bit.
#### The `crates/` dir
All Rust crates live under the `crates/` directory. The workspace automatically considers any crate found therein. If you need to add a new create, please implement it there.
Compiler library crates should be named with the `revive-` prefix. The crate location doesn't need the prefix.
#### Dependencies
Dependencies should be added as workspace dependencies. Try to avoid pinning dependencies whenever possible. If possible, add dev dependencies as `dev-dependencies` only.
Please do always include the `Cargo.lock` dependency lock file with your PR. Please don't run `cargo update` together with other changes (it is preferred to update the lock file in a dedicated dependency update PR).
## Contribution rules
1. Changes must be submitted via a pull request (PR) to the github upstream repository.
2. Ensure that your branch passes `make test` locally when submitting a pull request.
3. A PR must not be merged until CI fully passes. Exceptions can be made (for example to fix CI issues itself).
4. No force pushes to the `main` branch and open PR branches.
5. Maintainers can request changes or deny contributions at their own discretion.
## Style guide
We require the official Rust formatter and clippy linter. In addition to that, please also consider the following best-effort aspects:
- Avoid [magic numbers](https://en.wikipedia.org/wiki/Magic_number_(programming)) and strings. Instead, add them as module constants.
- Avoid abbreviated variable and function names. Always provide meaningful and readable symbols.
- Don't write macros and don't use third party macros for things that can easily be expressed in few lines of code or outlined into functions.
- Avoid import aliasing. Please use the parent or fully qualified path for conflicting symbols.
- Any inline comments must provide additional semantic meaning, explain counter-intuitive behavior or highlight non-obvious design decisions. In other words, try to make the code expressive enough to a degree it doesn't need comments expressing the same thing again in the English language. Delete such comments if your AI assistant generated them.
- Public items must have a meaningful doc comment.
- Provide meaningful panic messages to `.expect()` or just use `.unwrap()`.
## AI policy
Contributors may use whatever AI assistance tools they wish to whatever degree they wish in the process of creating their contribution, __given they acknowledge the following__:
_Project maintainers may reject any contribution (or portions of it) if the contribution shows signs of problematic involvement of generative AI_.
Judgement of "problematic involvement" lies at the sole discretion of project maintainers. No proof (whether a contribution was in fact AI generated or not) is required. Rationale:
- No one enjoys reading soulless and uncanny LLM slop. Please review and fix any AI slop yourself prior to submitting a PR.
- A Solidity compiler is security sensitive software. Even miniscule mistakes can ultimately lead to loss of funds. AI models are inherently stochastic. They regurarly fail to capture important nuances or produce straight hallucinations. Code that was "blindly" generated has no home here.
- `revive` is a large codebase. Generative AI assistants may not have enough "context window" to sufficiently capture correctness, consistency and style aspects of the codebase. We'd like to keep this codebase maintainable _by humans_ for the forseeable future.
@@ -0,0 +1,30 @@
# Cross compilation
We cross-compile the `resolc.js` frontend executable to Wasm for running it in a Node.js or browser environment.
The [musl](https://www.musl-libc.org/) target is used to obtain statically linked ELF binaries for Linux.
## Wasm via emscripten
The `REVIVE_LLVM_TARGET_PREFIX` environment variable is used to control the target environment LLVM dependency. This requires a compatible LLVM build, obtainable via the `revive-llvm` build script. Example:
```sh
# Build the host LLVM dependency with PolkaVM target support
make install-llvm
export LLVM_SYS_181_PREFIX=${PWD}/target-llvm/gnu/target-final
# Build the target LLVM dependency with PolkaVM target support
revive-llvm emsdk
source emsdk/emsdk_env.sh
revive-llvm --target-env emscripten build --llvm-projects lld
export REVIVE_LLVM_TARGET_PREFIX=${PWD}/target-llvm/emscripten/target-final
# Build the resolc frontend executable
make install-wasm
make test-wasm
```
## musl libc
[rust-musl-cross](https://github.com/rust-cross/rust-musl-cross) is a straightforward way to cross compile Rust to musl. The [Dockerfile](https://github.com/paritytech/revive/blob/main/Dockerfile) is an executable example of how to do that.
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 290 KiB

+29
View File
@@ -0,0 +1,29 @@
# PVM and the pallet-revive runtime target
The `revive` compiler targets [PolkaVM (PVM)](https://github.com/paritytech/polkavm) via [pallet-revive](https://github.com/paritytech/polkadot-sdk/tree/master/substrate/frame/revive) on Polkadot.
## Target CPU configuration
The exact target CPU configuration can be found [here](https://github.com/paritytech/revive/blob/8cd10a613625428956eb33c39c9022a91bfbf103/crates/llvm-context/src/target_machine/mod.rs#L22-L32).
> [!NOTE]
>
> The PVM linker requires fully relocatable ELF objects.
## Why PVM
PVM is a RISC-V based VM designed to overcome the flaws of [WebAssebmly (Wasm)](https://webassembly.org/). Wasm was believed to be a more efficient successor to the rather slow EVM. However, Wasm is far from an ideal target for smart contracts as some of its design decisions are unfavorable for short-lived workloads. The main problem is on-chain Wasm bytecode compilation or interpretation overhead. Prior benchmarks consistently ignoring this overhead seeded the blockchain industry with flawed assumptions: _Only when ignoring the startup overhead_ Wasm is much faster than the slow computing EVM. In practice however, gains are nullified entirely and Wasm loses completely even against very slow VMs like the EVM. Executing Wasm contracts is in fact so inefficient that typical contract workloads are _orders of magnitude_ more expensive than the equivalent EVM variant.
On the other hand, since RISC-V is similar to CPUs found in validator hardware (x86 and ARM), bytecode translation mostly boils down to a linear mapping from one instruction to another. The _embedded_ ISA specification reduces the number of general purpose registers, in turn removing the need for expensive register allocation. This guarantees single-pass `O(n)` JIT compilation of contract bytecode. The close proximity of PVM bytecode with actual validator CPU bytecode effectively allows to move all expensive compilation workload off-chain. Benchmarks ([1](https://hackmd.io/@XXX9CM1uSSCWVNFRYaSB5g/HJarTUhJA#Results-execute), [2](https://github.com/paritytech/polkavm/blob/master/BENCHMARKS.md)) show that with the PVM JIT, sandboxed PVM code executes at around half the speed of native code, which falls into the same ballpark of the state-of-the-art `wasmtime` Wasm implementation (while EVM sits somewhere around 1/10 to less than 1/100 of native speed). However, the PVM JIT compiler only uses a fraction of the time `wasmtime` requires to compile the code.
> [!NOTE]
>
> The PVM JIT isn't available yet in `pallet-revive`. At the time of writing, the contract code is interpreted, which is orders of magnitude slower than the JIT.
## Host environment: `pallet-revive`
The `revive` compiler targets the [`pallet-revive` runtime environment](https://docs.rs/pallet-revive/).
`pallet-revive` exposes a [syscall like interface](https://docs.rs/pallet-revive/latest/pallet_revive/trait.SyscallDoc.html) for contract interactions with the host environment. This is provided by the [revive-runtime-api](https://crates.io/crates/revive-runtime-api) library.
After the initial launch on the Polkadot Asset Hub blockchain, the runtime API is considered stable and backwards compatible indefinitively.
+67
View File
@@ -0,0 +1,67 @@
# Testing strategy
Contributors are encouraged to implement some appropriate unit and integration tests together with any bug fixes or new feature implementations. However, when it comes to testing the code generation logic, our testing strategy goes way beyond simple unit and integration tests. This chapter explains how the `revive` compiler implementation is tested for correctness and how we define correctness.
> [!TIP]
>
> Running the integration tests require the `evm` tool from `go-ethereum` in your `$PATH`.
>
> Either install it using your package manager or to build it from source:
> ```bash
> git clone https://github.com/ethereum/go-ethereum/
> cd go-ethereum
> make all
> export PATH=/path/to/go-ethereum/build/bin/:$PATH
> ```
## Bug compatibility with Ethereum Solidity
As a Solidity compiler, we aim to preserve contract code semantics as close as possible to Solidity compiled to EVM with the `solc` reference implementation. As highlighted in the user guide, due to the underlying target difference, this isn't always possible. However, wherever it is possible, we follow the philosophy of [**bug compatibility**](https://en.wikipedia.org/wiki/Bug_compatibility) with the Ethereum contracts stack.
## Differential integration tests
A high level of bug compatibility with Ethereum is ensured through [**differential testing**](https://en.wikipedia.org/wiki/Differential_testing) with the Ethereum `solc` and EVM contracts stack. The [revive-integration](https://crates.io/crates/revive-integration) library is the central integration test utility, providing a set of Solidity integration test cases. Further, it implements differential tests against the reference implementation by combining the [revive-runner](https://crates.io/crates/revive-runner) sandbox, the [go-ethereum EVM tool](https://github.com/ethereum/go-ethereum/tree/master/cmd/evm) and the [revive-differential](https://crates.io/crates/revive-differential).
The `revive-runner` library provides a [**declarative**](https://en.wikipedia.org/wiki/Declarative_programming) test [specification format](https://github.com/paritytech/revive/blob/main/crates/runner/src/specs.rs). This vastly simplifies writing differential test cases and removes a lot of room for errors in test logic. Example:
```json
{
"differential": true,
"actions": [
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "Bitwise"
}
}
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "3fa4f245"
}
}
]
}
```
Above example instantiates the `Bitwise` contract and calls it with some defined calldata. The `revive-runner` library implements a helper wrapper to execute test specs on the go-ethereum standalone `evm` tool. This allows the `revive-runner` to execute specs against the EVM and the `pallet-revive` runtime. Key to differential testing is setting `"differential": true`, resulting in the following:
1. The `Bitwise` contract is compiled to EVM and PVM code.
2. The runner executes the defined `actions` on the EVM and collects all state changes (storage, balance) and execution results.
3. The runner executes each action on the PVM. Observed state changes _after each step_ as well as the final execution result is asserted to match the EVM counterparts __exactly__.
__Note how we never defined any expected outcome manually.__ Instead, we simply observe and collect the data defining the "correct" outcome.
Differential testing in combination with declarative test specifications proved to be simple, yet very effective, in ensuring expected Ethereum Solidity semantics on `pallet-revive`.
## The differential testing utility
A lot of nuanced bugs caused by tiny implementation details inside the `revive` compiler _and_ the `pallet-revive` runtime could be identified and eliminated early on thanks to the differential testing strategy. Thus, we decided to take this approach further and created a comprehensive test runner and a large suite of more complex test cases.
The [Revive Differential Tests](https://github.com/paritytech/revive-differential-tests/) follow the exact same strategy but implement a much more powerful test spec format, spec runner and reports. This allows differentially testing much more complex test cases (for example testing Uniswap pair creations and swaps), executed via transactions sent to actual blockchain nodes.
+28
View File
@@ -0,0 +1,28 @@
# FAQ
## What EVM version do you support?
We neither do nor don't support any EVM version. We support Solidity versions, starting from `solc` version 0.8.0 onwards.
## Is inline assembly supported
Yes, almost all inline assembly features are supported ([see the differences in Yul translation chapter](user_guide/differences.md)).
## Do you support opcode `XY`?
See above, the same applies.
## In what Solidity version should I write my dApp?
We generally recommend to always use the latest supported version to profit from latest bugfixes, features and performance improvements.
Find out about the latest supported version by running `resolc --supported-solc-versions` or checking [here](https://github.com/paritytech/resolc-bin).
## Tool `XY` says the contract size is larger than 24kb and will fail to deploy?
The 24kb code size restriction only exist for the EVM. Our limit is currently around 1mb and may increase further in the future.
## Is `resolc` a drop-in replacement for `solc`?
No. `resolc` aims to work similarly to `solc`, but it's not considered a drop-in replacement.
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 40 KiB

+45
View File
@@ -0,0 +1,45 @@
# `revive-runner` sandbox
Running contract code usually requires a blockchain node. While local dev nodes can be used, sometimes it's just not desirable to do so. Instead, it can be much more convenient to run and debug contract code with a stripped down environment.
This is where the `revive-runner` comes in handy. In a nutshell, it is a single-binary no-blockchain `pallet-revive` runtime.
## Installation and usage
Inside the root `revive` repository directory, install it from source (requires Rust installed):
```bash
make install-revive-runner
```
After installing, see `revive-runner --help` for usage help.
## Trace logs
The standard `RUST_LOG` environment variable controls the log output from the contract execution. This includes `revive` runtime logs and PVM execution trace logs. Sometimes it's convenient to have more fine granular insight. Some useful filters:
- `RUST_LOG=runtime=trace`: The `pallet-revive` runtime trace logs.
- `RUST_LOG=polkavm=trace`: Low level PolkaVM instruction tracing.
## Automatic contract instantiation
To avoid running the constract in an unitialized state, `revive-runner` automatically instantiates the contract before calling it (constructor arguments can be provided).
## Example
Suppose we want to trace the syscalls of the execution of a compiled contract file `Flipper.pvm`:
```bash
RUST_LOG=runtime=trace revive-runner -f Flipper.pvm
[DEBUG runtime::revive] Contract memory usage: purgable=6144/3145728 KB baseline=103063/1572864
[TRACE runtime::revive::strace] call_data_size() = Ok(0) gas_consumed: Weight { ref_time: 985209, proof_size: 0 }
[TRACE runtime::revive::strace] value_transferred(out_ptr: 4294836096) = Ok(()) gas_consumed: Weight { ref_time: 2937634, proof_size: 0 }
[TRACE runtime::revive::strace] call_data_copy(out_ptr: 131216, out_len: 0, offset: 0) = Ok(()) gas_consumed: Weight { ref_time: 4084483, proof_size: 0 }
[TRACE runtime::revive::strace] seal_return(flags: 0, data_ptr: 131216, data_len: 0) = Err(TrapReason::Return(ReturnData { flags: 0, data: [] })) gas_consumed: Weight { ref_time: 5510615, proof_size: 0 }
[TRACE runtime::revive] frame finished with: Ok(ExecReturnValue { flags: (empty), data: [] })
[TRACE runtime::revive::strace] call_data_size() = Ok(0) gas_consumed: Weight { ref_time: 985209, proof_size: 0 }
[TRACE runtime::revive::strace] seal_return(flags: 1, data_ptr: 131088, data_len: 0) = Err(TrapReason::Return(ReturnData { flags: 1, data: [] })) gas_consumed: Weight { ref_time: 2456669, proof_size: 0 }
[TRACE runtime::revive] frame finished with: Ok(ExecReturnValue { flags: REVERT, data: [] })
```
+16
View File
@@ -0,0 +1,16 @@
# Roadmap and Vision
The `revive` compiler speeds up Solidity contracts by orders of magnitude. `revive` provides a decisive edge over other contract platforms. Notably, the compiler eliminates the need of rewriting Solidity dApps as single dApp parachains for scaling reasons. Retaining as high compatibility with Ethereum Solidity as possible keeps entry barriers low.
We believe in Dr. Gavin Wood's [ĐApps: What Web 3.0 Looks Like](https://gavwood.com/dappsweb3.html) manifesto and the ecosystem of the Solidity programming language. Our motivation lies in the realization that for a _true_ web3 revolution, significant scaling efforts, like the ones provided by the PVM and this project, are necessary to unfold.
## Roadmap
The first major release, `resolc` v1.0.0, emits functional PVM code from given Solidity sources. It relies on `solc` and LLVM for optimizations. The main priority of this release was delivering a mostly feature complete and safe Solidity v0.8.0 compiler.
Focus for the second major release is on the custom optimization pipeline, which aims to significantly improve emitted code blob sizes.
The below roadmap gives a rough overview of the project's development timeline.
![Roadmap](images/roadmap.svg)
+11
View File
@@ -0,0 +1,11 @@
# `resolc` user guide
`resolc` is a Solidity `v0.8` compiler for [Polkadot `native` smart contracts](https://docs.polkadot.com/develop/smart-contracts/overview/#native-smart-contracts). Solidity compiled with `resolc` executes orders of magnitude faster than the EVM. `resolc` also supports almost all Solidity `v0.8` features including inline assembly, offering a high level of comptability with the Ethereum Solidity reference implementation.
## `revive` vs. `resolc` nomenclature
`revive` is the name of the overarching "Solidity to PolkaVM" compiler project, which contains multiple components (for example the Yul parser, the code generation library, the `resolc` executable itself, and many more things).
`resolc` is the name of the compiler driver executable, combining many `revive` components in a single and easy to use binary application.
In other words, `revive` is the whole compiler infrastructure (more like `LLVM`) and `resolc` is a user-facing single-entrypoint compiler frontend (more like `clang`).
+113
View File
@@ -0,0 +1,113 @@
# CLI usage
We aim to keep the `resolc` CLI usage close to `solc`. There are a few things and options worthwhile to know about in `resolc` which do not exist in the Ethereum world. This chapter explains those in more detail than the CLI help message.
> [!TIP]
>
> For the complete help about CLI options, please see `resolc --help`.
### LLVM optimization levels
```bash
-O, --optimization <OPTIMIZATION>
```
`resolc` exposes the optimization level setting for the LLVM backend. The performance and size of compiled contracts varies wiedly between different optimization levels.
Valid levels are the following:
- `0`: No optimizations are applied.
- `1`: Basic optimizations for execution time.
- `2`: Advanced optimizations for execution time.
- `3`: Aggressive optimizations for execution time.
- `s`: Optimize for code size.
- `z`: Aggressively optimize for code size.
By default, `-O3` is applied.
### Stack size
```bash
--stack-size <STACK_SIZE>
```
PVM is a register machine with a traditional stack memory space for local variables. This controls the total amount of stack space the contract can use.
You are incentivized to keep this value as small as possible:
1. Increasing the stack size will increase startup costs.
2. The stack size contributes to the total memory size a contract can use, which includes the contract's code size.
Default value: 32768
> [!WARNING]
>
> If the contract uses more stack memory than configured, it will compile fine but eventually revert execution at runtime!
### Heap size
```bash
--heap-size <HEAP_SIZE>
```
Unlike the EVM, due to the lack of dynamic memory metering, PVM contracts emulate the EVM heap memory with a static buffer. Consequentially, instead of infinite memory with exponentially growing gas costs, PVM contracts have a finite amount of memory with constant gas costs available.
You are incentivized to keep this value as small as possible:
1.Increasing the heap size will increase startup costs.
2.The heap size contributes to the total memory size a contract can use, which includes the contract's code size
Default value: 65536
> [!WARNING]
>
> If the contract uses more heap memory than configured, it will compile fine but eventually revert execution at runtime!
### solc
```bash
--solc <SOLC>
```
Specify the path to the `solc` executable. By default, the one in `${PATH}` is used.
### Debug artifacts
```bash
--debug-output-dir <DEBUG_OUTPUT_DIRECTORY>
```
Dump all intermediary compiler artifacts to files in the specified directory. This includes the YUL IR, optimized and unoptimized LLVM IR, the ELF object and the PVM assembly. Useful for debugging and development purposes.
### Debug info
```bash
-g
```
Generate source based debug information in the output code file. Useful for debugging and development purposes and disabled by default.
### Deploy time linking
```bash
--link [--libraries <LIBRARIES>] <INPUT_FILES>
```
In Solidity, 3 things can happen with libraries:
1. They are not `extern`ally callable and thus can be inlined.
1. The solc Solidity optimizer inlines those (usually the case). Note: `resolc` always activates the solc Solidity optimizer.
2. If the solc Solidity optimizer is disabled or for some reason fails to inline them (both rare), they are not inlined and require linking.
2. They are `extern`ally callable but still linked at compile time. This is the case if at compile time the library address is known (i.e. `--libraries` supplied in CLI or the corresponding setting in STD JSON input).
3. They are linked at deploy time. This happens when the compiler does not know the library address (i.e. `--libraries` flag is missing or the provided libraries are incomplete, same for STD JSON input). This case is rare because it's discourage and should never be used by production dApps.
In cases `1.2` and `3`:
- Some of the produced code blobs will be in the "unlinked" raw `ELF` object format and not yet deployable.
- To make them deployable, they need to be "linked" (done using the `resolc --link` linker mode explained below).
- The compiler emitted `DELEGATECALL` instructions to call non-inlined (unlinked) libraries. The contract deployer must make sure to deploy any libraries prior to contract deployment.
> [!WARNING]
>
> Using deploy time linking is officially **discouraged**. Mainly due to bytecode hashes changing after the fact. We decided to support it in `resolc` regardless, due to popular request.
Similar to how it works in `solc`, `--libraries` may be used to provide libraries during linking mode.
Unlike with `solc`, where linking implies a simple string substitution mechanism, `resolc` needs to resolve actual missing `ELF` symbols. This is due to how factory dependencies work in PVM. As a consequence, it isn't sufficient to just provide the unlinked blobs to the linker. Instead, they must be provided in the exact same directory structure the Solidity source code was found during compile time.
Example:
- The contract `src/foo/bar.sol:Bar` is involved in deploy time linking. It may be a factory dependency.
- The contract blob needs to be provided inside a relative `src/foo/` directory to `--link`. Otherwise symbol resolution may fail.
> [!NOTE]
>
> Tooling is supposed to take care of this. In the future, we may append explicit linkage data to simplify the deploy time linking feature.
+94
View File
@@ -0,0 +1,94 @@
# Differences to EVM
This section highlights some potentially observable differences in the [YUL EVM dialect](https://docs.soliditylang.org/en/latest/yul.html#evm-dialect) translation compared to Ethereum Solidity.
Solidity developers deploying dApps to [`pallet-revive`](https://github.com/paritytech/polkadot-sdk/tree/master/substrate/frame/revive) ought to read and understand this section well.
## Deploy code vs. runtime code
Our contract runtime does not differentiate between runtime code and deploy (constructor) code.
Instead, both are emitted into a single PVM contract code blob and live on-chain.
Therefore, in EVM terminology, the deploy code equals the runtime code.
> [!TIP]
>
> In constructor code, the `codesize` instruction will return the call data size instead of the actual code blob size.
## Solidity
We are aware of the following differences in the translation of Solidity code.
### `address.creationCode`
This returns the bytecode keccak256 hash instead.
## YUL functions
The below list contains noteworthy differences in the translation of YUL functions.
> [!NOTE]
>
> Many functions receive memory buffer offset pointer or size arguments. Since the PVM pointer size is 32 bit, supplying memory offset or buffer size values above `2^32-1` will trap the contract immediately.
The `solc` compiler ought to always emit valid memory references, so Solidity dApp authors don't need to worry about this unless they deal with low level `assembly` code.
### `mload`, `mstore`, `msize`, `mcopy` (memory related functions)
In general, revive preserves the memory layout, meaning low level memory operations are supported. However, a few caveats apply:
- The EVM linear heap memory is emulated using a fixed byte buffer of 64kb. This implies that the maximum memory a contract can use is limited to 64kbit (on Ethereum, contract memory is capped by gas and therefore varies).
- Thus, accessing memory offsets larger than the fixed buffer size will trap the contract at runtime with an `OutOfBound` error.
- The compiler might detect and optimize unused memory reads and writes, leading to a different `msize` compared to what the EVM would see.
### `calldataload`, `calldatacopy`
In the constructor code, the offset is ignored and this always returns `0`.
### `codecopy`
Only supported in constructor code.
### `invalid`
Traps the contract but does not consume the remaining gas.
### `create`, `create2`
Deployments on revive work different than on EVM. In a nutshell: Instead of supplying the deploy code concatenated with the constructor arguments (the EVM deploy model), the [revive runtime expects two pointers](https://docs.rs/pallet-revive/latest/pallet_revive/trait.SyscallDoc.html#tymethod.instantiate):
1. A buffer containing the code hash to deploy.
2. The constructor arguments buffer.
To make contract instantiation using the `new` keyword in Solidity work seamlessly,
`revive` translates the `dataoffset` and `datasize` instructions so that they assume the contract hash instead of the contract code.
The hash is always of constant size.
Thus, `revive` is able to supply the expected code hash and constructor arguments pointer to the runtime.
> [!WARNING]
>
> This might fall apart in code creating contracts inside `assembly` blocks. **We strongly discourage using the `create` family opcodes to manually craft deployments in `assembly` blocks!** Usually, the reason for using `assembly` blocks is to save gas, which is futile on revive anyways due to lower transaction costs.
### `dataoffset`
Returns the contract hash.
### `datasize`
Returns the contract hash size (constant value of `32`).
### `prevrandao`, `difficulty`
Translates to a constant value of `2500000000000000`.
### `pc`, `extcodecopy`
Only valid to use in EVM (they also have no use case in PVM) and produce a compile time error.
### `blobhash`, `blobbasefee`
Related to the Ethereum rollup model and produce a compile time error. Polkadot offers a superior rollup model, removing the use case for blob data related opcodes.
## Difference regarding the `solc` `via-ir` mode
There are two different compilation pipelines available in `solc` and [there are small differences between them](https://docs.soliditylang.org/en/latest/ir-breaking-changes.html).
Since `resolc` processes the YUL IR, always assume the `solc` IR based codegen behavior for contracts compiled with the `revive` compiler.
+27
View File
@@ -0,0 +1,27 @@
# Installation
Building Solidity contracts for PolkaVM requires installing the following two compilers:
- `solc`: The [Ethereum Solidity reference compiler](https://github.com/argotorg/solidity) implementation.
- `resolc`: The revive Solidity compiler YUL frontend and PolkaVM code generator.
## `resolc` binary releases
`resolc` is supported an all major operating systems and installation is straightforward.
Please find our [binary releases](https://github.com/paritytech/revive/releases) for the following platforms:
- Linux (MUSL)
- MacOS (universal)
- Windows
- Wasm via emscripten
## Installing the `solc` dependency
`resolc` uses `solc` during the compilation process, please refer to the [Ethereum Solidity documentation](https://docs.soliditylang.org/en/latest/installing-solidity.html) for installation instructions.
## `revive` NPM package
We distribute the revive compiler as [node.js module](https://github.com/paritytech/revive/tree/main/js/resolc).
## Buidling `resolc` from source
Please follow the build [instructions in the revive `README.md`](https://github.com/paritytech/revive?tab=readme-ov-file#building-from-source).
+13
View File
@@ -0,0 +1,13 @@
# JS NPM package
The `resolc` compiler driver is published as an NPM package under [@parity/resolc](https://www.npmjs.com/package/@parity/resolc).
It's usable from `Node.js` code or directly from the command line:
```shell
npx @parity/resolc@latest --bin crates/integration/contracts/flipper.sol -o /tmp/out
```
> [!NOTE]
>
> While the npm package makes a nice portable option, it doesn't expose all options.
+15
View File
@@ -0,0 +1,15 @@
# Rust contract libraries
> [!NOTE]
>
> This is not yet implemented but something for consideration on the roadmap.
Solidity - tightly coupled to the EVM - introduces some inherent inefficiencies that are by design and either needs to be followed or can't be easily worked around, even with efforts like better optimized compiler and VM implementations. This represents a technical dead end. So far the EVM sees no adoption beyond the blockchain industry. Chances are that [the EVM end up deprecated](https://ethereum-magicians.org/t/long-term-l1-execution-layer-proposal-replace-the-evm-with-risc-v) for technical reasons (or maybe not and the RISC-V idea gets abandoned, who knows).
PVM, however, is a general purpose VM. It supports LLVM based mainstream programming languages like Rust. It's a common software engineering practice to compose applications from pieces written in multiple languages, using each to their own strength. For example, AI solutions traditionally use the python scripting language for convenient developer experience, while the underlying AI models get implemented in a lower level language such as C++.
The same pattern can of course be applied to dApps, where we'd expect application specific languages like Solidity mixed with libraries implementing computationally complex algorithms in a lower level language. Business logic and user interfaces are naturally implemented as regular Solidity dApps which can include (link against) Rust libraries. Rust is a fast, safe low level language and the Polkadot SDK is written in Rust itself, making it an excellent choice.
For example, [ZK proof verifiers](https://en.wikipedia.org/wiki/Zero-knowledge_proof) or expensive [DeFi](https://en.wikipedia.org/wiki/Decentralized_finance) primitives would benefit greatly from Rust implementations.
`revive` provides tooling support and a small Rust contracts SDK for seamless integration with Rust libraries.
+36
View File
@@ -0,0 +1,36 @@
# Standard JSON interface
The `revive` compiler is mostly compatible with the `solc` standard JSON interface. There are a few additional (PVM related) __input__ configurations:
## The `settings.polkavm` object
Used to configure PVM specific compiler settings.
### `settings.polkavm.debugInformation`
A boolean value allowing to enable debug information. Corresponds to `resolc -g`.
### The `settings.polkavm.memoryConfig` object
Used to apply PVM specific memory configuration settings.
#### `settings.polkavm.heapSize`
A numerical value allowing to configure the contract heap size. Corresponds to `resolc --heap-size`.
#### `settings.polkavm.stackSize`
A numerical value allowing to configure the contract stack size. Corresponds to `resolc --stack-size`.
## The `settings.optimizer` object
The `settings.optimizer` object is augmented with support for PVM specific optimization settings.
### `settings.optimizer.mode`
A single char value to configure the LLVM optimizer settings. Corresponds to `resolc -O`.
## `settings.llvmArguments`
Allows to specify arbitrary command line arguments to LLVM initialization. Used mainly for development and debugging purposes.
+19
View File
@@ -0,0 +1,19 @@
# Tooling integration
`resolc` achieved successful integration with a variety of third party developer tools.
## Solidity toolkits
Support for `resolc` is available in forks of the [hardhat](https://hardhat.org) and [foundry](https://getfoundry.sh) Solidity toolkits:
- [The Parity Hardhat fork](https://github.com/paritytech/hardhat-polkadot)
- [The Parity Foundry fork](https://github.com/paritytech/foundry-polkadot?tab=readme-ov-file#2-resolc-compiler-integration)
## Compiler explorer
`resolc` is available on [godbolt.org](https://godbolt.org/z/6GM6n4Ka3) for the Solidity and Yul input languages. See also the announcement post on the [forum](https://forum.polkadot.network/t/resolc-is-live-on-compiler-explorer).
## Remix IDE
There is remix IDE fork with `resolc` support at [remix.polkadot.io](https://remix.polkadot.io). Unfortunately this is no longer actively maintained (there might be bugs and outdated `resolc` versions).
+16
View File
@@ -0,0 +1,16 @@
# Welcome
Hello and a warm welcome to the `revive` Solidity compiler book!
## Target audience
- **Solidity dApp developers** should read the [user guide](./user_guide.md). Solidity on PolkaVM introduces important differences to EVM which should be well understood.
- **Contributors** will find the [developer guide](./developer_guide.md) helpful for getting up to speed.
## Other Polkadot contracts resources
Head to [contracts.polkadot.io](https://docs.polkadot.com/develop/smart-contracts/) for more general information about contracts on Polkadot.
## About
This [mdBook](https://github.com/rust-lang/mdBook) documents the revive Solidity compiler project. The content is found under `book/`. Run `make book` to observe changes.
+1
View File
@@ -0,0 +1 @@
large-error-threshold = 192
+2 -3
View File
@@ -4,7 +4,7 @@
- [Benchmark Results](#benchmark-results)
- [Baseline](#baseline)
- [OddPorduct](#oddporduct)
- [OddProduct](#oddproduct)
- [TriangleNumber](#trianglenumber)
- [FibonacciRecursive](#fibonaccirecursive)
- [FibonacciIterative](#fibonacciiterative)
@@ -19,7 +19,7 @@
|:--------|:-------------------------|:-------------------------------- |
| **`0`** | `10.08 us` (✅ **1.00x**) | `10.32 us` (✅ **1.02x slower**) |
### OddPorduct
### OddProduct
| | `EVM` | `PVMInterpreter` |
|:-------------|:--------------------------|:-------------------------------- |
@@ -70,4 +70,3 @@
---
Made with [criterion-table](https://github.com/nu11ptr/criterion-table)
+1 -1
View File
@@ -57,7 +57,7 @@ fn bench_baseline(c: &mut Criterion) {
}
fn bench_odd_product(c: &mut Criterion) {
let group = group(c, "OddPorduct");
let group = group(c, "OddProduct");
let parameters = &[10_000, 100_000, 300000];
bench(group, parameters, parameters, Contract::odd_product);
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "revive-build-utils"
version.workspace = true
version = "0.2.0"
license.workspace = true
edition.workspace = true
repository.workspace = true
+5 -3
View File
@@ -6,6 +6,10 @@ pub const REVIVE_LLVM_HOST_PREFIX: &str = "LLVM_SYS_181_PREFIX";
/// The revive LLVM target dependency directory prefix environment variable.
pub const REVIVE_LLVM_TARGET_PREFIX: &str = "REVIVE_LLVM_TARGET_PREFIX";
/// The revive LLVM host tool help link.
pub const REVIVE_LLVM_BUILDER_HELP_LINK: &str =
"https://github.com/paritytech/revive?tab=readme-ov-file#building-from-source";
/// Constructs a path to the LLVM tool `name`.
///
/// Respects the [`REVIVE_LLVM_HOST_PREFIX`] environment variable.
@@ -13,9 +17,7 @@ pub fn llvm_host_tool(name: &str) -> std::path::PathBuf {
std::env::var_os(REVIVE_LLVM_HOST_PREFIX)
.map(Into::<std::path::PathBuf>::into)
.unwrap_or_else(|| {
panic!(
"install LLVM using the revive-llvm builder and export {REVIVE_LLVM_HOST_PREFIX}",
)
panic!("install LLVM using the revive-llvm builder and export '{REVIVE_LLVM_HOST_PREFIX}'; see also: {REVIVE_LLVM_BUILDER_HELP_LINK}")
})
.join("bin")
.join(name)
+3 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "revive-common"
version.workspace = true
version = "0.2.1"
license.workspace = true
edition.workspace = true
repository.workspace = true
@@ -15,6 +15,8 @@ doctest = false
[dependencies]
anyhow = { workspace = true }
hex = { workspace = true }
sha3 = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true, features = [ "arbitrary_precision", "unbounded_depth" ] }
serde_stacker = { workspace = true }
+33
View File
@@ -0,0 +1,33 @@
//! The contract identifier helper library.
use serde::{Deserialize, Serialize};
/// This structure simplifies passing the contract identifiers through the compilation pipeline.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContractIdentifier {
/// The absolute file path.
pub path: String,
/// The contract name.
/// Is set for Solidity contracts only. Otherwise it would be equal to the file name.
pub name: Option<String>,
/// The full contract identifier.
/// For Solidity, The format is `<absolute file path>:<contract name>`.
/// For other languages, `<absolute file path>`.
pub full_path: String,
}
impl ContractIdentifier {
/// A shortcut constructor.
pub fn new(path: String, name: Option<String>) -> Self {
let full_path = match name {
Some(ref name) => format!("{path}:{name}"),
None => path.clone(),
};
Self {
path,
name,
full_path,
}
}
}
+1 -1
View File
@@ -37,4 +37,4 @@ pub static EXTENSION_POLKAVM_ASSEMBLY: &str = "pvmasm";
pub static EXTENSION_POLKAVM_BINARY: &str = "pvm";
/// The ELF shared object file extension.
pub static EXTENSION_SHARED_OBJECT: &str = "so";
pub static EXTENSION_OBJECT: &str = "o";
+68
View File
@@ -0,0 +1,68 @@
//! Keccak-256 hash utilities.
use serde::{Deserialize, Serialize};
use sha3::digest::FixedOutput;
use sha3::Digest;
pub const DIGEST_BYTES: usize = 32;
/// Keccak-256 hash utilities.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Keccak256 {
/// Binary representation.
bytes: [u8; DIGEST_BYTES],
/// Hexadecimal string representation.
string: String,
}
impl Keccak256 {
/// Computes the `keccak256` hash for `preimage`.
pub fn from_slice(preimage: &[u8]) -> Self {
let bytes = sha3::Keccak256::digest(preimage).into();
let string = format!("0x{}", hex::encode(bytes));
Self { bytes, string }
}
/// Computes the `keccak256` hash for an array of `preimages`.
pub fn from_slices<R: AsRef<[u8]>>(preimages: &[R]) -> Self {
let mut hasher = sha3::Keccak256::new();
for preimage in preimages.iter() {
hasher.update(preimage);
}
let bytes: [u8; DIGEST_BYTES] = hasher.finalize_fixed().into();
let string = format!("0x{}", hex::encode(bytes));
Self { bytes, string }
}
/// Returns a reference to the 32-byte SHA-3 hash.
pub fn as_bytes(&self) -> &[u8] {
self.bytes.as_slice()
}
/// Returns a reference to the hexadecimal string representation.
pub fn as_str(&self) -> &str {
self.string.as_str()
}
/// Extracts the binary representation.
pub fn to_vec(&self) -> Vec<u8> {
self.bytes.to_vec()
}
}
impl std::fmt::Display for Keccak256 {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[cfg(test)]
mod tests {
#[test]
fn hash_and_stringify_works() {
assert_eq!(
super::Keccak256::from_slices(&["foo".as_bytes(), "bar".as_bytes(),]).as_str(),
"0x38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e"
);
}
}
+8
View File
@@ -3,9 +3,13 @@
pub(crate) mod base;
pub(crate) mod bit_length;
pub(crate) mod byte_length;
pub(crate) mod contract_identifier;
pub(crate) mod evm_version;
pub(crate) mod exit_code;
pub(crate) mod extension;
pub(crate) mod keccak256;
pub(crate) mod metadata;
pub(crate) mod object;
pub(crate) mod utils;
pub use self::base::*;
@@ -14,4 +18,8 @@ pub use self::byte_length::*;
pub use self::evm_version::EVMVersion;
pub use self::exit_code::*;
pub use self::extension::*;
pub use self::keccak256::*;
pub use self::metadata::*;
pub use self::object::*;
pub use self::utils::*;
pub use contract_identifier::*;
+42
View File
@@ -0,0 +1,42 @@
//! The metadata hash type.
use std::str::FromStr;
use serde::{Deserialize, Serialize};
/// The metadata hash type.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum MetadataHash {
/// Do not include bytecode hash.
#[serde(rename = "none")]
None,
/// Include the `ipfs` hash.
#[serde(rename = "ipfs")]
IPFS,
/// Include the `keccak256`` hash.
#[serde(rename = "keccak256")]
Keccak256,
}
impl FromStr for MetadataHash {
type Err = anyhow::Error;
fn from_str(string: &str) -> Result<Self, Self::Err> {
match string {
"none" => Ok(Self::None),
"ipfs" => Ok(Self::IPFS),
"keccak256" => Ok(Self::Keccak256),
string => anyhow::bail!("unknown bytecode hash mode: `{string}`"),
}
}
}
impl std::fmt::Display for MetadataHash {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::None => write!(f, "none"),
Self::IPFS => write!(f, "ipfs"),
Self::Keccak256 => write!(f, "keccak256"),
}
}
}
+61
View File
@@ -0,0 +1,61 @@
//! The revive binary object helper module.
use std::str::FromStr;
use serde::{Deserialize, Serialize};
/// The binary object format.
///
/// Unlinked contracts are stored in a different object format
/// than final (linked) contract blobs.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ObjectFormat {
/// The unlinked ELF object format.
ELF,
/// The fully linked PVM format.
PVM,
}
impl ObjectFormat {
pub const PVM_MAGIC: [u8; 4] = [b'P', b'V', b'M', b'\0'];
pub const ELF_MAGIC: [u8; 4] = [0x7f, b'E', b'L', b'F'];
}
impl FromStr for ObjectFormat {
type Err = anyhow::Error;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value {
"ELF" => Ok(Self::ELF),
"PVM" => Ok(Self::PVM),
_ => anyhow::bail!(
"Unknown object format: {value}. Supported formats: {}, {}",
Self::ELF,
Self::PVM,
),
}
}
}
impl TryFrom<&[u8]> for ObjectFormat {
type Error = &'static str;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
if value.starts_with(&Self::PVM_MAGIC) {
return Ok(Self::PVM);
}
if value.starts_with(&Self::ELF_MAGIC) {
return Ok(Self::ELF);
}
Err("expected a contract object")
}
}
impl std::fmt::Display for ObjectFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ELF => write!(f, "ELF"),
Self::PVM => write!(f, "PVM"),
}
}
}
+26 -6
View File
@@ -1,25 +1,45 @@
//! The compiler common utils.
/// Deserializes a `serde_json` object from slice with the recursion limit disabled.
///
/// Must be used for all JSON I/O to avoid crashes due to the aforementioned limit.
pub fn deserialize_from_slice<O>(input: &[u8]) -> anyhow::Result<O>
where
O: serde::de::DeserializeOwned,
{
let mut deserializer = serde_json::Deserializer::from_slice(input);
deserializer.disable_recursion_limit();
let deserializer = serde_stacker::Deserializer::new(&mut deserializer);
let result = O::deserialize(deserializer)?;
Ok(result)
let deserializer = serde_json::Deserializer::from_slice(input);
deserialize(deserializer)
}
/// Deserializes a `serde_json` object from string with the recursion limit disabled.
///
/// Must be used for all JSON I/O to avoid crashes due to the aforementioned limit.
pub fn deserialize_from_str<O>(input: &str) -> anyhow::Result<O>
where
O: serde::de::DeserializeOwned,
{
let mut deserializer = serde_json::Deserializer::from_str(input);
let deserializer = serde_json::Deserializer::from_str(input);
deserialize(deserializer)
}
/// Deserializes a `serde_json` object from reader with the recursion limit disabled.
///
/// Must be used for all JSON I/O to avoid crashes due to the aforementioned limit.
pub fn deserialize_from_reader<R, O>(reader: R) -> anyhow::Result<O>
where
R: std::io::Read,
O: serde::de::DeserializeOwned,
{
let deserializer = serde_json::Deserializer::from_reader(reader);
deserialize(deserializer)
}
/// Runs the generic deserializer.
pub fn deserialize<'de, R, O>(mut deserializer: serde_json::Deserializer<R>) -> anyhow::Result<O>
where
R: serde_json::de::Read<'de>,
O: serde::de::DeserializeOwned,
{
deserializer.disable_recursion_limit();
let deserializer = serde_stacker::Deserializer::new(&mut deserializer);
let result = O::deserialize(deserializer)?;
+1 -1
View File
@@ -1,7 +1,7 @@
[package]
name = "revive-differential"
description = "utilities for differential testing the revive compiler against EVM"
version.workspace = true
version = "0.2.0"
license.workspace = true
edition.workspace = true
authors.workspace = true
+2 -2
View File
@@ -3,8 +3,8 @@ use std::time::Duration;
/// Parse a go formatted duration.
///
/// Sources:
/// - https://crates.io/crates/go-parse-duration (fixed an utf8 bug)
/// - https://github.com/golang/go/blob/master/src/time/format.go
/// - <https://crates.io/crates/go-parse-duration> (fixed an utf8 bug)
/// - <https://github.com/golang/go/blob/master/src/time/format.go>
pub fn parse_go_duration(value: &str) -> Result<Duration, String> {
parse_duration(value).map(|ns| Duration::from_nanos(ns.unsigned_abs()))
}
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "revive-explorer"
version.workspace = true
version = "0.1.0"
license.workspace = true
edition.workspace = true
repository.workspace = true
+1 -1
View File
@@ -188,7 +188,7 @@ impl DwarfdumpAnalyzer {
}
}
/// Given a slice of u64 values, returns a Vec<u64> where each element
/// Given a slice of u64 values, returns a `Vec<u64>` where each element
/// is linearly scaled into the closed interval [1, 10].
fn scale_to(data: &[u64], scale_max: u64) -> Vec<u64> {
if data.is_empty() {
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "revive-integration"
version = "0.1.1"
version = "0.3.0"
license.workspace = true
edition.workspace = true
repository.workspace = true
+8 -8
View File
@@ -1,10 +1,10 @@
{
"Baseline": 960,
"Computation": 2367,
"DivisionArithmetics": 9108,
"ERC20": 17655,
"Events": 1680,
"FibonacciIterative": 1536,
"Flipper": 2137,
"SHA1": 8299
"Baseline": 905,
"Computation": 2286,
"DivisionArithmetics": 14347,
"ERC20": 16929,
"Events": 1665,
"FibonacciIterative": 1447,
"Flipper": 2077,
"SHA1": 7721
}
@@ -0,0 +1,81 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8;
/* runner.json
{
"differential": true,
"actions": [
{
"Upload": {
"code": {
"Solidity": {
"contract": "AddModMulMod"
}
}
}
},
{
"Instantiate": {
"value": 123123,
"code": {
"Solidity": {
"contract": "AddModMulModTester"
}
}
}
},
{
"VerifyCall": {
"success": true
}
}
]
}
*/
contract AddModMulMod {
function test() public returns (uint256) {
// Note that this only works because computation on literals is done using
// unbounded integers.
if ((2**255 + 2**255) % 7 != addmod(2**255, 2**255, 7)) return 1;
if ((2**255 + 2**255) % 7 != addmod(2**255, 2**255, 7)) return 2;
return 0;
}
function f(uint256 d) public pure returns (uint256) {
addmod(1, 2, d);
return 2;
}
function g(uint256 d) public pure returns (uint256) {
mulmod(1, 2, d);
return 2;
}
function h() public pure returns (uint256) {
mulmod(0, 1, 2);
mulmod(1, 0, 2);
addmod(0, 1, 2);
addmod(1, 0, 2);
return 2;
}
}
contract AddModMulModTester {
constructor() payable {
AddModMulMod c = new AddModMulMod();
assert(c.test() == 0);
try c.f(0) returns (uint m) { revert(); } catch Panic(uint errorCode) {
assert(errorCode == 0x12);
}
try c.g(0) returns (uint m) { revert(); } catch Panic(uint errorCode) {
assert(errorCode == 0x12);
}
assert(c.h() == 2);
}
}
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/* runner.json
{
"differential": true,
"actions": [
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "MemoryBounds"
}
}
}
},
{
"Call": {
"dest": {
"Instantiated": 0
}
}
}
]
}
*/
contract MemoryBounds {
fallback() external {
assembly {
// Accessing OOB offsets should always work when the length is 0.
return(100000, 0)
}
}
}
@@ -0,0 +1,66 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
// TODO: This currently fails the differential test.
// The pallet doesn't send the correct balance back.
/* runner.json
{
"differential": false,
"actions": [
{
"Upload": {
"code": {
"Solidity": {
"contract": "SelfdestructTester"
}
}
}
},
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "Selfdestruct"
}
},
"value": 123456789
}
},
{
"Call": {
"dest": {
"Instantiated": 0
}
}
}
]
}
*/
contract Selfdestruct {
address tester;
uint value;
constructor() payable {
require(msg.value > 0, "the test should have value");
value = msg.value;
SelfdestructTester s = new SelfdestructTester{value: msg.value}();
tester = address(s);
}
fallback() external {
(bool success, ) = tester.call(hex"");
require(success, "the call to the self destructing contract should succeed");
}
}
contract SelfdestructTester {
constructor() payable {}
fallback() external {
selfdestruct(payable(msg.sender));
}
}
+23 -20
View File
@@ -10,6 +10,29 @@ pub struct Contract {
pub evm_runtime: Vec<u8>,
pub pvm_runtime: Vec<u8>,
pub calldata: Vec<u8>,
pub yul: String,
}
impl Contract {
pub fn build(calldata: Vec<u8>, name: &'static str, code: &str) -> Self {
Self {
name,
evm_runtime: compile_evm_bin_runtime(name, code),
pvm_runtime: compile_blob(name, code),
calldata,
yul: compile_to_yul(name, code, true),
}
}
pub fn build_size_opt(calldata: Vec<u8>, name: &'static str, code: &str) -> Self {
Self {
name,
evm_runtime: compile_evm_bin_runtime(name, code),
pvm_runtime: compile_blob_with_options(name, code, true, OptimizerSettings::size()),
calldata,
yul: compile_to_yul(name, code, true),
}
}
}
macro_rules! case {
@@ -261,26 +284,6 @@ sol!(
case!("AddressPredictor.sol", Predicted, constructorCall, predicted_constructor, salt: U256);
case!("AddressPredictor.sol", AddressPredictor, constructorCall, address_predictor_constructor, salt: U256, bytecode: Bytes);
impl Contract {
pub fn build(calldata: Vec<u8>, name: &'static str, code: &str) -> Self {
Self {
name,
evm_runtime: compile_evm_bin_runtime(name, code),
pvm_runtime: compile_blob(name, code),
calldata,
}
}
pub fn build_size_opt(calldata: Vec<u8>, name: &'static str, code: &str) -> Self {
Self {
name,
evm_runtime: compile_evm_bin_runtime(name, code),
pvm_runtime: compile_blob_with_options(name, code, true, OptimizerSettings::size()),
calldata,
}
}
}
#[cfg(test)]
mod tests {
use rayon::iter::{IntoParallelIterator, ParallelIterator};
+104
View File
@@ -1,6 +1,7 @@
use std::str::FromStr;
use alloy_primitives::*;
use resolc::test_utils::build_yul;
use revive_runner::*;
use SpecsAction::*;
@@ -61,6 +62,9 @@ test_spec!(delegate_no_contract, "DelegateCaller", "DelegateCaller.sol");
test_spec!(function_type, "FunctionType", "FunctionType.sol");
test_spec!(layout_at, "LayoutAt", "LayoutAt.sol");
test_spec!(shift_arithmetic_right, "SAR", "SAR.sol");
test_spec!(add_mod_mul_mod, "AddModMulModTester", "AddModMulMod.sol");
test_spec!(memory_bounds, "MemoryBounds", "MemoryBounds.sol");
test_spec!(selfdestruct, "Selfdestruct", "Selfdestruct.sol");
fn instantiate(path: &str, contract: &str) -> Vec<SpecsAction> {
vec![Instantiate {
@@ -167,6 +171,8 @@ fn signed_division() {
(minus_five, two),
(I256::MINUS_ONE, I256::MIN),
(one, I256::ZERO),
(I256::MIN, I256::MINUS_ONE),
(I256::MIN + I256::ONE, I256::MINUS_ONE),
] {
actions.push(Call {
origin: TestAddress::Alice,
@@ -515,3 +521,101 @@ fn create2_salt() {
}
.run();
}
#[test]
fn code_block_stops() {
let code = &build_yul(&[(
"poc.yul",
r#"object "Test"{
code {
tstore(0x7fd9d641,0x7b1e022)
returndatacopy(0x0,0x0,returndatasize())
}
object "Test_deployed" { code{} }
}"#,
)])
.unwrap()["poc.yul:Test"];
Specs {
actions: vec![
Instantiate {
origin: TestAddress::Alice,
value: 0,
gas_limit: Some(GAS_LIMIT),
storage_deposit_limit: None,
code: Code::Bytes(code.to_vec()),
data: Default::default(),
salt: OptionalHex::default(),
},
Call {
origin: TestAddress::Alice,
dest: TestAddress::Instantiated(0),
value: Default::default(),
gas_limit: None,
storage_deposit_limit: None,
data: Default::default(),
},
VerifyCall(Default::default()),
],
differential: false,
..Default::default()
}
.run();
}
#[test]
fn code_block_with_nested_object_stops() {
let code = &build_yul(&[(
"poc.yul",
r#"object "Test" {
code {
function allocate(size) -> ptr {
ptr := mload(0x40)
if iszero(ptr) { ptr := 0x60 }
mstore(0x40, add(ptr, size))
}
let size := datasize("Test_deployed")
let offset := allocate(size)
datacopy(offset, dataoffset("Test_deployed"), size)
return(offset, size)
}
object "Test_deployed" {
code {
sstore(0, 100)
}
object "Test" {
code {
revert(0,0)
}
}
}
}"#,
)])
.unwrap()["poc.yul:Test"];
Specs {
actions: vec![
Instantiate {
origin: TestAddress::Alice,
value: 0,
gas_limit: Some(GAS_LIMIT),
storage_deposit_limit: None,
code: Code::Bytes(code.to_vec()),
data: Default::default(),
salt: OptionalHex::default(),
},
Call {
origin: TestAddress::Alice,
dest: TestAddress::Instantiated(0),
value: Default::default(),
gas_limit: None,
storage_deposit_limit: None,
data: Default::default(),
},
VerifyCall(Default::default()),
],
differential: false,
..Default::default()
}
.run();
}
+5 -5
View File
@@ -1,6 +1,6 @@
[package]
name = "revive-linker"
version.workspace = true
version = "0.2.0"
license.workspace = true
edition.workspace = true
repository.workspace = true
@@ -8,10 +8,10 @@ authors.workspace = true
description = "revive compiler linker utils"
[dependencies]
tempfile = { workspace = true }
polkavm-linker = { workspace = true }
libc = { workspace = true }
anyhow = { workspace = true }
libc = { workspace = true }
polkavm-linker = { workspace = true }
tempfile = { workspace = true }
revive-builtins = { workspace = true }
lld-sys = { workspace = true }
revive-builtins = { workspace = true }
+114
View File
@@ -0,0 +1,114 @@
//! The revive ELF object linker library.
use std::{ffi::CString, fs, path::PathBuf, sync::Mutex};
use lld_sys::LLDELFLink;
use tempfile::TempDir;
use revive_builtins::COMPILER_RT;
static GUARD: Mutex<()> = Mutex::new(());
/// The revive ELF object linker.
pub struct ElfLinker {
temporary_directory: TempDir,
output_path: PathBuf,
object_path: PathBuf,
symbols_path: PathBuf,
linker_script_path: PathBuf,
}
impl ElfLinker {
const LINKER_SCRIPT: &str = r#"
SECTIONS {
.text : { KEEP(*(.text.polkavm_export)) *(.text .text.*) }
}"#;
const BUILTINS_ARCHIVE_FILE: &str = "libclang_rt.builtins-riscv64.a";
const BUILTINS_LIB_NAME: &str = "clang_rt.builtins-riscv64";
/// The setup routine prepares a temporary working directory.
pub fn setup() -> anyhow::Result<Self> {
let temporary_directory = TempDir::new()?;
let object_path = temporary_directory.path().join("obj.o");
let output_path = temporary_directory.path().join("out.o");
let symbols_path = temporary_directory.path().join("sym.o");
let linker_script_path = temporary_directory.path().join("linker.ld");
fs::write(&linker_script_path, Self::LINKER_SCRIPT)
.map_err(|message| anyhow::anyhow!("{message} {linker_script_path:?}",))?;
let compiler_rt_path = temporary_directory.path().join(Self::BUILTINS_ARCHIVE_FILE);
fs::write(&compiler_rt_path, COMPILER_RT)
.map_err(|message| anyhow::anyhow!("{message} {compiler_rt_path:?}"))?;
Ok(Self {
temporary_directory,
output_path,
object_path,
symbols_path,
linker_script_path,
})
}
/// Link `input` with `symbols` and the `compiler_rt` via `LLD`.
pub fn link<T: AsRef<[u8]>>(self, input: T, symbols: T) -> anyhow::Result<Vec<u8>> {
fs::write(&self.object_path, input)
.map_err(|message| anyhow::anyhow!("{message} {:?}", self.object_path))?;
fs::write(&self.symbols_path, symbols)
.map_err(|message| anyhow::anyhow!("{message} {:?}", self.symbols_path))?;
if lld(self
.create_arguments()
.into_iter()
.map(|v| v.to_string())
.collect())
{
return Err(anyhow::anyhow!("ld.lld failed"));
}
Ok(fs::read(&self.output_path)?)
}
/// The argument creation helper function.
fn create_arguments(&self) -> Vec<String> {
[
"ld.lld",
"--error-limit=0",
"--relocatable",
"--emit-relocs",
"--relax",
"--unique",
"--gc-sections",
self.linker_script_path.to_str().expect("should be utf8"),
"-o",
self.output_path.to_str().expect("should be utf8"),
self.object_path.to_str().expect("should be utf8"),
self.symbols_path.to_str().expect("should be utf8"),
"--library-path",
self.temporary_directory
.path()
.to_str()
.expect("should be utf8"),
"--library",
Self::BUILTINS_LIB_NAME,
]
.iter()
.map(ToString::to_string)
.collect()
}
}
/// The thread-safe LLD helper function.
fn lld(arguments: Vec<String>) -> bool {
let c_strings = arguments
.into_iter()
.map(|arg| CString::new(arg).expect("ld.lld args should not contain null bytes"))
.collect::<Vec<_>>();
let args: Vec<*const libc::c_char> = c_strings.iter().map(|arg| arg.as_ptr()).collect();
let _lock = GUARD.lock().expect("ICE: linker mutex should not poison");
unsafe { LLDELFLink(args.as_ptr(), args.len()) == 0 }
}
+3 -75
View File
@@ -1,76 +1,4 @@
use std::{env, ffi::CString, fs};
//! The revive ELF object to PVM blob linker library.
use lld_sys::LLDELFLink;
use revive_builtins::COMPILER_RT;
const LINKER_SCRIPT: &str = r#"
SECTIONS {
.text : { KEEP(*(.text.polkavm_export)) *(.text .text.*) }
}"#;
const BUILTINS_ARCHIVE_FILE: &str = "libclang_rt.builtins-riscv64.a";
const BUILTINS_LIB_NAME: &str = "clang_rt.builtins-riscv64";
fn invoke_lld(cmd_args: &[&str]) -> bool {
let c_strings = cmd_args
.iter()
.map(|arg| CString::new(*arg).expect("ld.lld args should not contain null bytes"))
.collect::<Vec<_>>();
let args: Vec<*const libc::c_char> = c_strings.iter().map(|arg| arg.as_ptr()).collect();
unsafe { LLDELFLink(args.as_ptr(), args.len()) == 0 }
}
pub fn polkavm_linker<T: AsRef<[u8]>>(code: T, strip_binary: bool) -> anyhow::Result<Vec<u8>> {
let mut config = polkavm_linker::Config::default();
config.set_strip(strip_binary);
config.set_optimize(true);
polkavm_linker::program_from_elf(config, code.as_ref())
.map_err(|reason| anyhow::anyhow!("polkavm linker failed: {}", reason))
}
pub fn link<T: AsRef<[u8]>>(input: T) -> anyhow::Result<Vec<u8>> {
let dir = tempfile::tempdir().expect("failed to create temp directory for linking");
let output_path = dir.path().join("out.so");
let object_path = dir.path().join("out.o");
let linker_script_path = dir.path().join("linker.ld");
let compiler_rt_path = dir.path().join(BUILTINS_ARCHIVE_FILE);
fs::write(&object_path, input).map_err(|msg| anyhow::anyhow!("{msg} {object_path:?}"))?;
if env::var("PVM_LINKER_DUMP_OBJ").is_ok() {
fs::copy(&object_path, "/tmp/out.o")?;
}
fs::write(&linker_script_path, LINKER_SCRIPT)
.map_err(|msg| anyhow::anyhow!("{msg} {linker_script_path:?}"))?;
fs::write(&compiler_rt_path, COMPILER_RT)
.map_err(|msg| anyhow::anyhow!("{msg} {compiler_rt_path:?}"))?;
let ld_args = [
"ld.lld",
"--error-limit=0",
"--relocatable",
"--emit-relocs",
"--no-relax",
"--unique",
"--gc-sections",
"--library-path",
dir.path().to_str().expect("should be utf8"),
"--library",
BUILTINS_LIB_NAME,
linker_script_path.to_str().expect("should be utf8"),
object_path.to_str().expect("should be utf8"),
"-o",
output_path.to_str().expect("should be utf8"),
];
if invoke_lld(&ld_args) {
return Err(anyhow::anyhow!("ld.lld failed"));
}
Ok(fs::read(&output_path)?)
}
pub mod elf;
pub mod pvm;
+14
View File
@@ -0,0 +1,14 @@
//! The revive PVM blob linker library.
pub fn polkavm_linker<T: AsRef<[u8]>>(code: T, strip_binary: bool) -> anyhow::Result<Vec<u8>> {
let mut config = polkavm_linker::Config::default();
config.set_strip(strip_binary);
config.set_optimize(true);
polkavm_linker::program_from_elf(
config,
polkavm_linker::TargetInstructionSet::ReviveV1,
code.as_ref(),
)
.map_err(|reason| anyhow::anyhow!("polkavm linker failed: {}", reason))
}
+1 -3
View File
@@ -6,7 +6,7 @@ authors = [
"Anton Baliasnikov <aba@matterlabs.dev>",
"Cyrill Leutwiler <cyrill@parity.io>",
]
version = "0.2.0"
version = "0.4.0"
license.workspace = true
edition.workspace = true
repository.workspace = true
@@ -21,8 +21,6 @@ doctest = false
[dependencies]
clap = { workspace = true, features = ["help", "std", "derive"] }
anyhow = { workspace = true }
serde = { workspace = true, features = [ "derive" ] }
toml = { workspace = true }
num_cpus = { workspace = true }
fs_extra = { workspace = true }
path-slash = { workspace = true }
-6
View File
@@ -67,12 +67,6 @@ Obtain a compatible build for your host platform from the release section of thi
</details>
<details>
<summary>4. (Optional) Create the `LLVM.lock` file.</summary>
* The `LLVM.lock` dictates the LLVM source tree being used.
A default `./LLVM.lock` pointing to the release used for development is already provided.
</details>
<details>
<summary>5. Build LLVM.</summary>
+2 -1
View File
@@ -4,13 +4,14 @@ use crate::utils::path_windows_to_unix as to_unix;
use std::{env::consts::EXE_EXTENSION, process::Command};
/// Static CFLAGS variable passed to the compiler building the compiler-rt builtins.
const C_FLAGS: [&str; 6] = [
const C_FLAGS: [&str; 7] = [
"--target=riscv64",
"-march=rv64emac",
"-mabi=lp64e",
"-mcpu=generic-rv64",
"-nostdlib",
"-nodefaultlibs",
"-fuse-ld=lld",
];
/// Static CMAKE arguments for building the compiler-rt builtins.
+8 -74
View File
@@ -5,7 +5,6 @@ pub mod builtins;
pub mod ccache_variant;
pub mod llvm_path;
pub mod llvm_project;
pub mod lock;
pub mod platforms;
pub mod sanitizer;
pub mod target_env;
@@ -14,7 +13,6 @@ pub mod utils;
pub use self::build_type::BuildType;
pub use self::llvm_path::LLVMPath;
pub use self::lock::Lock;
pub use self::platforms::Platform;
use std::collections::HashSet;
@@ -23,87 +21,25 @@ use std::process::Command;
pub use target_env::TargetEnv;
pub use target_triple::TargetTriple;
/// Executes the LLVM repository cloning.
pub fn clone(lock: Lock, deep: bool, target_env: TargetEnv) -> anyhow::Result<()> {
/// Initializes the LLVM submodule if not already done.
pub fn init(init_emscripten: bool) -> anyhow::Result<()> {
utils::check_presence("git")?;
if target_env == TargetEnv::Emscripten {
if init_emscripten {
utils::install_emsdk()?;
}
let destination_path = PathBuf::from(LLVMPath::DIRECTORY_LLVM_SOURCE);
if destination_path.exists() {
log::warn!(
"LLVM repository directory {} already exists, falling back to checkout",
destination_path.display()
);
return checkout(lock, false);
}
let mut clone_args = vec!["clone", "--branch", lock.branch.as_str()];
if !deep {
clone_args.push("--depth");
clone_args.push("1");
if destination_path.join(".git").exists() {
log::info!("LLVM submodule already initialized");
return Ok(());
}
utils::command(
Command::new("git")
.args(clone_args)
.arg(lock.url.as_str())
.arg(destination_path.to_string_lossy().as_ref()),
"LLVM repository cloning",
Command::new("git").args(["submodule", "update", "--init", "--recursive"]),
"LLVM submodule initialization",
)?;
if let Some(r#ref) = lock.r#ref {
utils::command(
Command::new("git")
.args(["checkout", r#ref.as_str()])
.current_dir(destination_path.to_string_lossy().as_ref()),
"LLVM repository commit checking out",
)?;
}
Ok(())
}
/// Executes the checkout of the specified branch.
pub fn checkout(lock: Lock, force: bool) -> anyhow::Result<()> {
let destination_path = PathBuf::from(LLVMPath::DIRECTORY_LLVM_SOURCE);
utils::command(
Command::new("git")
.current_dir(destination_path.as_path())
.args(["fetch", "--all", "--tags"]),
"LLVM repository data fetching",
)?;
if force {
utils::command(
Command::new("git")
.current_dir(destination_path.as_path())
.args(["clean", "-d", "-x", "--force"]),
"LLVM repository cleaning",
)?;
}
utils::command(
Command::new("git")
.current_dir(destination_path.as_path())
.args(["checkout", "--force", lock.branch.as_str()]),
"LLVM repository data pulling",
)?;
if let Some(r#ref) = lock.r#ref {
let mut checkout_command = Command::new("git");
checkout_command.current_dir(destination_path.as_path());
checkout_command.arg("checkout");
if force {
checkout_command.arg("--force");
}
checkout_command.arg(r#ref);
utils::command(&mut checkout_command, "LLVM repository checking out")?;
}
Ok(())
}
@@ -332,8 +268,6 @@ pub fn clean() -> anyhow::Result<()> {
.expect("target_env parent directory is target-llvm"),
)?;
remove_if_exists(&PathBuf::from(LLVMPath::DIRECTORY_EMSDK_SOURCE))?;
remove_if_exists(&PathBuf::from(LLVMPath::DIRECTORY_LLVM_SOURCE))?;
remove_if_exists(&PathBuf::from(LLVMPath::DIRECTORY_LLVM_HOST_SOURCE))?;
Ok(())
}
-37
View File
@@ -1,37 +0,0 @@
//! The revive LLVM builder lock file.
use anyhow::Context;
use std::fs::File;
use std::io::Read;
use std::path::PathBuf;
use serde::Deserialize;
use serde::Serialize;
/// The default lock file location.
pub const LLVM_LOCK_DEFAULT_PATH: &str = "LLVM.lock";
/// The lock file data.
///
/// This file describes the exact reference of the LLVM framework.
#[derive(Debug, Deserialize, Serialize)]
pub struct Lock {
/// The LLVM repository URL.
pub url: String,
/// The LLVM repository branch.
pub branch: String,
/// The LLVM repository commit reference.
pub r#ref: Option<String>,
}
impl TryFrom<&PathBuf> for Lock {
type Error = anyhow::Error;
fn try_from(path: &PathBuf) -> Result<Self, Self::Error> {
let mut config_str = String::new();
let mut config_file =
File::open(path).with_context(|| format!("Error opening {path:?} file"))?;
config_file.read_to_string(&mut config_str)?;
Ok(toml::from_str(&config_str)?)
}
}
@@ -18,13 +18,6 @@ pub struct Arguments {
/// The revive LLVM builder arguments.
#[derive(Debug, clap::Subcommand)]
pub enum Subcommand {
/// Clone the branch specified in `LLVM.lock`.
Clone {
/// Clone with full commits history.
#[arg(long)]
deep: bool,
},
/// Build the LLVM framework.
Build {
/// LLVM build type (`Debug`, `Release`, `RelWithDebInfo`, or `MinSizeRel`).
@@ -77,12 +70,8 @@ pub enum Subcommand {
enable_valgrind: bool,
},
/// Checkout the branch specified in `LLVM.lock`.
Checkout {
/// Remove all artifacts preventing the checkout (removes all local changes!).
#[arg(long)]
force: bool,
},
/// Install emsdk
Emsdk,
/// Clean the build artifacts.
Clean,
+4 -13
View File
@@ -3,7 +3,6 @@
pub(crate) mod arguments;
use std::collections::HashSet;
use std::path::PathBuf;
use std::str::FromStr;
use anyhow::Context;
@@ -29,13 +28,6 @@ fn main_inner() -> anyhow::Result<()> {
revive_llvm_builder::utils::directory_target_llvm(arguments.target_env);
match arguments.subcommand {
Subcommand::Clone { deep } => {
let lock = revive_llvm_builder::Lock::try_from(&PathBuf::from(
revive_llvm_builder::lock::LLVM_LOCK_DEFAULT_PATH,
))?;
revive_llvm_builder::clone(lock, deep, arguments.target_env)?;
}
Subcommand::Build {
build_type,
targets,
@@ -50,6 +42,8 @@ fn main_inner() -> anyhow::Result<()> {
sanitizer,
enable_valgrind,
} => {
revive_llvm_builder::init(false)?;
let mut targets = targets
.into_iter()
.map(|target| revive_llvm_builder::Platform::from_str(target.as_str()))
@@ -107,11 +101,8 @@ fn main_inner() -> anyhow::Result<()> {
)?;
}
Subcommand::Checkout { force } => {
let lock = revive_llvm_builder::Lock::try_from(&PathBuf::from(
revive_llvm_builder::lock::LLVM_LOCK_DEFAULT_PATH,
))?;
revive_llvm_builder::checkout(lock, force)?;
Subcommand::Emsdk => {
revive_llvm_builder::init(true)?;
}
Subcommand::Clean => {
+37 -62
View File
@@ -2,20 +2,14 @@ pub mod common;
use std::process::Command;
use assert_cmd::prelude::*;
use assert_cmd::{cargo, prelude::*};
/// This test verifies that the LLVM repository can be successfully cloned, built, and cleaned.
/// This test verifies that the LLVM repository can be successfully built and cleaned.
#[test]
fn clone_build_and_clean() -> anyhow::Result<()> {
let test_dir = common::TestDir::with_lockfile(None)?;
fn build_and_clean() -> anyhow::Result<()> {
let test_dir = common::TestDir::new()?;
Command::cargo_bin(common::REVIVE_LLVM)?
.current_dir(test_dir.path())
.arg("clone")
.assert()
.success();
Command::cargo_bin(common::REVIVE_LLVM)?
Command::new(cargo::cargo_bin!("revive-llvm"))
.current_dir(test_dir.path())
.arg("build")
.arg("--llvm-projects")
@@ -25,13 +19,13 @@ fn clone_build_and_clean() -> anyhow::Result<()> {
.assert()
.success();
Command::cargo_bin(common::REVIVE_LLVM)?
Command::new(cargo::cargo_bin!("revive-llvm"))
.current_dir(test_dir.path())
.arg("builtins")
.assert()
.success();
Command::cargo_bin(common::REVIVE_LLVM)?
Command::new(cargo::cargo_bin!("revive-llvm"))
.current_dir(test_dir.path())
.arg("clean")
.assert()
@@ -40,20 +34,14 @@ fn clone_build_and_clean() -> anyhow::Result<()> {
Ok(())
}
/// This test verifies that the LLVM repository can be successfully cloned, built, and cleaned
/// This test verifies that the LLVM repository can be successfully built and cleaned
/// with 2-staged build using MUSL as sysroot.
#[test]
#[cfg(target_os = "linux")]
fn clone_build_and_clean_musl() -> anyhow::Result<()> {
let test_dir = common::TestDir::with_lockfile(None)?;
fn build_and_clean_musl() -> anyhow::Result<()> {
let test_dir = common::TestDir::new()?;
Command::cargo_bin(common::REVIVE_LLVM)?
.arg("clone")
.current_dir(test_dir.path())
.assert()
.success();
Command::cargo_bin(common::REVIVE_LLVM)?
Command::new(cargo::cargo_bin!("revive-llvm"))
.current_dir(test_dir.path())
.arg("build")
.arg("--llvm-projects")
@@ -63,7 +51,7 @@ fn clone_build_and_clean_musl() -> anyhow::Result<()> {
.assert()
.success();
Command::cargo_bin(common::REVIVE_LLVM)?
Command::new(cargo::cargo_bin!("revive-llvm"))
.arg("--target-env")
.arg("musl")
.arg("build")
@@ -75,7 +63,7 @@ fn clone_build_and_clean_musl() -> anyhow::Result<()> {
.assert()
.success();
Command::cargo_bin(common::REVIVE_LLVM)?
Command::new(cargo::cargo_bin!("revive-llvm"))
.current_dir(test_dir.path())
.arg("clean")
.assert()
@@ -84,20 +72,14 @@ fn clone_build_and_clean_musl() -> anyhow::Result<()> {
Ok(())
}
/// This test verifies that the LLVM repository can be successfully cloned and built in debug mode
/// This test verifies that the LLVM repository can be successfully built in debug mode
/// with tests and coverage enabled.
#[test]
#[cfg(target_os = "linux")]
fn debug_build_with_tests_coverage() -> anyhow::Result<()> {
let test_dir = common::TestDir::with_lockfile(None)?;
let test_dir = common::TestDir::new()?;
Command::cargo_bin(common::REVIVE_LLVM)?
.current_dir(test_dir.path())
.arg("clone")
.assert()
.success();
Command::cargo_bin(common::REVIVE_LLVM)?
Command::new(cargo::cargo_bin!("revive-llvm"))
.current_dir(test_dir.path())
.arg("build")
.arg("--enable-coverage")
@@ -118,15 +100,9 @@ fn debug_build_with_tests_coverage() -> anyhow::Result<()> {
#[test]
#[cfg(target_os = "linux")]
fn build_with_sanitizers() -> anyhow::Result<()> {
let test_dir = common::TestDir::with_lockfile(None)?;
let test_dir = common::TestDir::new()?;
Command::cargo_bin(common::REVIVE_LLVM)?
.current_dir(test_dir.path())
.arg("clone")
.assert()
.success();
Command::cargo_bin(common::REVIVE_LLVM)?
Command::new(cargo::cargo_bin!("revive-llvm"))
.current_dir(test_dir.path())
.arg("build")
.arg("--sanitizer")
@@ -141,27 +117,28 @@ fn build_with_sanitizers() -> anyhow::Result<()> {
Ok(())
}
/// Tests the clone, build, and clean process of the LLVM repository for the emscripten target.
/// Tests the build and clean process of the LLVM repository for the emscripten target.
#[test]
#[cfg(target_os = "linux")]
fn clone_build_and_clean_emscripten() -> anyhow::Result<()> {
let test_dir = common::TestDir::with_lockfile(None)?;
let command = Command::cargo_bin(common::REVIVE_LLVM)?;
fn build_and_clean_emscripten() -> anyhow::Result<()> {
let test_dir = common::TestDir::new()?;
let command = Command::new(cargo::cargo_bin!("revive-llvm"));
let program = command.get_program().to_string_lossy();
let path = test_dir.path();
Command::cargo_bin(common::REVIVE_LLVM)?
.current_dir(test_dir.path())
.arg("clone")
Command::new(cargo::cargo_bin!("revive-llvm"))
.current_dir(path)
.arg("build")
.arg("--llvm-projects")
.arg("clang")
.arg("--llvm-projects")
.arg("lld")
.assert()
.success();
Command::cargo_bin(common::REVIVE_LLVM)?
.current_dir(test_dir.path())
.arg("build")
.arg("--llvm-projects")
.arg("lld")
.arg("--llvm-projects")
.arg("clang")
Command::new(cargo::cargo_bin!("revive-llvm"))
.current_dir(path)
.arg("emsdk")
.assert()
.success();
@@ -170,22 +147,20 @@ fn clone_build_and_clean_emscripten() -> anyhow::Result<()> {
// `cd {} && . ./emsdk_env.sh && cd ..` helps the script to locate `emsdk.py`
// @see https://github.com/emscripten-core/emsdk/blob/9dbdc4b3437750b85d16931c7c801bb71a782122/emsdk_env.sh#L61-L69
let emsdk_wrapped_build_command = format!(
"{program} --target-env emscripten clone && \
cd {} && . ./emsdk_env.sh && cd .. && \
"cd {} && . ./emsdk_env.sh && cd .. && \
{program} --target-env emscripten build --llvm-projects lld",
revive_llvm_builder::LLVMPath::DIRECTORY_EMSDK_SOURCE,
);
Command::new("sh")
.arg("-c")
.arg(emsdk_wrapped_build_command)
.current_dir(test_dir.path())
.current_dir(path)
.assert()
.success();
Command::cargo_bin(common::REVIVE_LLVM)?
Command::new(cargo::cargo_bin!("revive-llvm"))
.arg("clean")
.current_dir(test_dir.path())
.current_dir(path)
.assert()
.success();
-48
View File
@@ -1,48 +0,0 @@
pub mod common;
use std::process::Command;
use assert_cmd::prelude::*;
/// This test verifies that after cloning the LLVM repository, checking out a specific branch
/// or reference works as expected.
#[test]
fn checkout_after_clone() -> anyhow::Result<()> {
let test_dir = common::TestDir::with_lockfile(None)?;
Command::cargo_bin(common::REVIVE_LLVM)?
.current_dir(test_dir.path())
.arg("clone")
.assert()
.success();
Command::cargo_bin(common::REVIVE_LLVM)?
.current_dir(test_dir.path())
.arg("checkout")
.assert()
.success();
Ok(())
}
/// This test verifies that after cloning the LLVM repository, checking out a specific branch
/// or reference with the `--force` option works as expected.
#[test]
fn force_checkout() -> anyhow::Result<()> {
let test_dir = common::TestDir::with_lockfile(None)?;
Command::cargo_bin(common::REVIVE_LLVM)?
.current_dir(test_dir.path())
.arg("clone")
.assert()
.success();
Command::cargo_bin(common::REVIVE_LLVM)?
.current_dir(test_dir.path())
.arg("checkout")
.arg("--force")
.assert()
.success();
Ok(())
}
-36
View File
@@ -1,36 +0,0 @@
pub mod common;
use std::process::Command;
use assert_cmd::prelude::*;
/// This test verifies that the LLVM repository can be successfully cloned using a specific branch
/// and reference.
#[test]
fn clone() -> anyhow::Result<()> {
let test_dir = common::TestDir::with_lockfile(None)?;
Command::cargo_bin(common::REVIVE_LLVM)?
.current_dir(test_dir.path())
.arg("clone")
.assert()
.success();
Ok(())
}
/// This test verifies that the LLVM repository can be successfully cloned using a specific branch
/// and reference with --deep option.
#[test]
fn clone_deep() -> anyhow::Result<()> {
let test_dir = common::TestDir::with_lockfile(None)?;
Command::cargo_bin(common::REVIVE_LLVM)?
.current_dir(test_dir.path())
.arg("clone")
.arg("--deep")
.assert()
.success();
Ok(())
}
+38 -19
View File
@@ -1,32 +1,51 @@
use assert_fs::fixture::FileWriteStr;
use assert_fs::TempDir;
pub const REVIVE_LLVM: &str = "revive-llvm";
pub const REVIVE_LLVM_REPO_URL: &str = "https://github.com/llvm/llvm-project";
pub const REVIVE_LLVM_REPO_TEST_BRANCH: &str = "release/18.x";
pub struct TestDir {
_lockfile: assert_fs::NamedTempFile,
_tempdir: TempDir,
path: std::path::PathBuf,
}
/// Creates a temporary lock file for testing.
/// Creates a temporary directory for testing with submodule setup.
impl TestDir {
pub fn with_lockfile(reference: Option<String>) -> anyhow::Result<Self> {
let file =
assert_fs::NamedTempFile::new(revive_llvm_builder::lock::LLVM_LOCK_DEFAULT_PATH)?;
let lock = revive_llvm_builder::Lock {
url: REVIVE_LLVM_REPO_URL.to_string(),
branch: REVIVE_LLVM_REPO_TEST_BRANCH.to_string(),
r#ref: reference,
};
file.write_str(toml::to_string(&lock)?.as_str())?;
pub fn new() -> anyhow::Result<Self> {
let tempdir = TempDir::new()?;
let tmppath = tempdir.path();
// Initialize a git repo and add the LLVM submodule
std::process::Command::new("git")
.args(["init"])
.current_dir(tmppath)
.output()?;
std::process::Command::new("git")
.args([
"submodule",
"add",
"-b",
"release/18.x",
"https://github.com/llvm/llvm-project.git",
"llvm",
])
.current_dir(tmppath)
.output()?;
std::process::Command::new("git")
.args([
"submodule",
"update",
"--init",
"--recursive",
"--force",
"--depth 1",
])
.current_dir(tmppath)
.output()?;
Ok(Self {
path: file
.parent()
.expect("lockfile parent dir always exists")
.into(),
_lockfile: file,
path: tmppath.to_path_buf(),
_tempdir: tempdir,
})
}
+1 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "revive-llvm-context"
version = "0.3.0"
version = "0.5.0"
license.workspace = true
edition.workspace = true
repository.workspace = true
@@ -20,7 +20,6 @@ itertools = { workspace = true }
serde = { workspace = true, features = ["derive"] }
num = { workspace = true }
hex = { workspace = true }
sha3 = { workspace = true }
inkwell = { workspace = true }
libc = { workspace = true }
polkavm-disassembler = { workspace = true }
@@ -1,5 +1,9 @@
//! The debug IR type.
use revive_common::{
EXTENSION_LLVM_SOURCE, EXTENSION_OBJECT, EXTENSION_POLKAVM_ASSEMBLY, EXTENSION_YUL,
};
/// The debug IR type.
#[allow(clippy::upper_case_acronyms)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -11,22 +15,17 @@ pub enum IRType {
/// Whether to dump the assembly code.
Assembly,
/// Whether to dump the ELF shared object
SO,
/// Whether to jump JSON
#[cfg(debug_assertions)]
JSON,
Object,
}
impl IRType {
/// Returns the file extension for the specified IR.
pub fn file_extension(&self) -> &'static str {
match self {
Self::Yul => revive_common::EXTENSION_YUL,
Self::LLVM => revive_common::EXTENSION_LLVM_SOURCE,
Self::Assembly => revive_common::EXTENSION_POLKAVM_ASSEMBLY,
#[cfg(debug_assertions)]
Self::JSON => revive_common::EXTENSION_JSON,
Self::SO => revive_common::EXTENSION_SHARED_OBJECT,
Self::Yul => EXTENSION_YUL,
Self::LLVM => EXTENSION_LLVM_SOURCE,
Self::Assembly => EXTENSION_POLKAVM_ASSEMBLY,
Self::Object => EXTENSION_OBJECT,
}
}
}
+3 -42
View File
@@ -1,8 +1,5 @@
//! The debug configuration.
pub mod ir_type;
use std::path::Path;
use std::path::PathBuf;
use serde::Deserialize;
@@ -10,6 +7,8 @@ use serde::Serialize;
use self::ir_type::IRType;
pub mod ir_type;
/// The debug configuration.
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
pub struct DebugConfig {
@@ -18,13 +17,7 @@ pub struct DebugConfig {
/// Whether debug info should be emitted.
pub emit_debug_info: bool,
/// The YUL debug output file path.
///
/// Is expected to be configured when running in YUL mode.
pub contract_path: Option<PathBuf>,
/// The YUL input file path.
///
/// Is expected to be configured when not running in YUL mode.
pub yul_path: Option<PathBuf>,
}
impl DebugConfig {
@@ -34,29 +27,15 @@ impl DebugConfig {
output_directory,
emit_debug_info,
contract_path: None,
yul_path: None,
}
}
/// Set the current YUL path.
pub fn set_yul_path(&mut self, yul_path: &Path) {
self.yul_path = yul_path.to_path_buf().into();
}
/// Set the current contract path.
pub fn set_contract_path(&mut self, contract_path: &str) {
self.contract_path = self.yul_source_path(contract_path);
}
/// Returns with the following precedence:
/// 1. The YUL source path if it was configured.
/// 2. The source YUL path from the debug output dir if it was configured.
/// 3. `None` if there is no debug output directory.
pub fn yul_source_path(&self, contract_path: &str) -> Option<PathBuf> {
if let Some(path) = self.yul_path.as_ref() {
return Some(path.clone());
}
self.output_directory.as_ref().map(|output_directory| {
let mut file_path = output_directory.to_owned();
let full_file_name = Self::full_file_name(contract_path, None, IRType::Yul);
@@ -128,7 +107,7 @@ impl DebugConfig {
pub fn dump_object(&self, contract_path: &str, code: &[u8]) -> anyhow::Result<()> {
if let Some(output_directory) = self.output_directory.as_ref() {
let mut file_path = output_directory.to_owned();
let full_file_name = Self::full_file_name(contract_path, None, IRType::SO);
let full_file_name = Self::full_file_name(contract_path, None, IRType::Object);
file_path.push(full_file_name);
std::fs::write(file_path, code)?;
}
@@ -136,24 +115,6 @@ impl DebugConfig {
Ok(())
}
/// Dumps the stage output as a json file suitable for use with --recursive-process
#[cfg(debug_assertions)]
pub fn dump_stage_output(
&self,
contract_path: &str,
contract_suffix: Option<&str>,
stage_json: &Vec<u8>,
) -> anyhow::Result<()> {
if let Some(output_directory) = self.output_directory.as_ref() {
let mut file_path = output_directory.to_owned();
let full_file_name = Self::full_file_name(contract_path, contract_suffix, IRType::JSON);
file_path.push(full_file_name);
std::fs::write(file_path, stage_json)?;
}
Ok(())
}
/// Creates a full file name, given the contract full path, suffix, and extension.
fn full_file_name(contract_path: &str, suffix: Option<&str>, ir_type: IRType) -> String {
let mut full_file_name = contract_path.replace('/', "_").replace(':', ".");
+10 -7
View File
@@ -1,5 +1,7 @@
//! The LLVM context library.
#![allow(clippy::too_many_arguments)]
use std::ffi::CString;
use std::sync::OnceLock;
@@ -8,7 +10,7 @@ pub use self::debug_config::DebugConfig;
pub use self::optimizer::settings::size_level::SizeLevel as OptimizerSettingsSizeLevel;
pub use self::optimizer::settings::Settings as OptimizerSettings;
pub use self::optimizer::Optimizer;
pub use self::polkavm::build_assembly_text as polkavm_build_assembly_text;
pub use self::polkavm::build as polkavm_build;
pub use self::polkavm::context::address_space::AddressSpace as PolkaVMAddressSpace;
pub use self::polkavm::context::argument::Argument as PolkaVMArgument;
pub use self::polkavm::context::attribute::Attribute as PolkaVMAttribute;
@@ -46,6 +48,7 @@ pub use self::polkavm::context::r#loop::Loop as PolkaVMLoop;
pub use self::polkavm::context::solidity_data::SolidityData as PolkaVMContextSolidityData;
pub use self::polkavm::context::yul_data::YulData as PolkaVMContextYulData;
pub use self::polkavm::context::Context as PolkaVMContext;
pub use self::polkavm::disassemble as polkavm_disassemble;
pub use self::polkavm::evm::arithmetic as polkavm_evm_arithmetic;
pub use self::polkavm::evm::bitwise as polkavm_evm_bitwise;
pub use self::polkavm::evm::call as polkavm_evm_call;
@@ -66,13 +69,13 @@ pub use self::polkavm::evm::memory as polkavm_evm_memory;
pub use self::polkavm::evm::r#return as polkavm_evm_return;
pub use self::polkavm::evm::return_data as polkavm_evm_return_data;
pub use self::polkavm::evm::storage as polkavm_evm_storage;
pub use self::polkavm::hash as polkavm_hash;
pub use self::polkavm::link as polkavm_link;
pub use self::polkavm::r#const as polkavm_const;
pub use self::polkavm::Dependency as PolkaVMDependency;
pub use self::polkavm::DummyDependency as PolkaVMDummyDependency;
pub use self::polkavm::DummyLLVMWritable as PolkaVMDummyLLVMWritable;
pub use self::polkavm::WriteLLVM as PolkaVMWriteLLVM;
pub use self::target_machine::target::Target;
pub use self::target_machine::TargetMachine;
pub use self::target_machine::target::Target as PolkaVMTarget;
pub use self::target_machine::TargetMachine as PolkaVMTargetMachine;
pub(crate) mod debug_config;
pub(crate) mod optimizer;
@@ -86,7 +89,7 @@ static DID_INITIALIZE: OnceLock<()> = OnceLock::new();
/// This is a no-op if called subsequentially.
///
/// `llvm_arguments` are passed as-is to the LLVM CL options parser.
pub fn initialize_llvm(target: Target, name: &str, llvm_arguments: &[String]) {
pub fn initialize_llvm(target: PolkaVMTarget, name: &str, llvm_arguments: &[String]) {
let Ok(_) = DID_INITIALIZE.set(()) else {
return; // Tests don't go through a recursive process
};
@@ -109,6 +112,6 @@ pub fn initialize_llvm(target: Target, name: &str, llvm_arguments: &[String]) {
inkwell::support::enable_llvm_pretty_stack_trace();
match target {
Target::PVM => inkwell::targets::Target::initialize_riscv(&Default::default()),
PolkaVMTarget::PVM => inkwell::targets::Target::initialize_riscv(&Default::default()),
}
}
+2 -2
View File
@@ -1,7 +1,5 @@
//! The LLVM optimizing tools.
pub mod settings;
use serde::Deserialize;
use serde::Serialize;
@@ -9,6 +7,8 @@ use crate::target_machine::TargetMachine;
use self::settings::Settings;
pub mod settings;
/// The LLVM optimizing tools.
#[derive(Debug, Serialize, Deserialize)]
pub struct Optimizer {
@@ -1,8 +1,5 @@
//! The LLVM optimizer settings.
pub mod size_level;
use revive_solc_json_interface::SolcStandardJsonInputSettingsOptimizer;
use serde::Deserialize;
use serde::Serialize;
@@ -10,6 +7,8 @@ use itertools::Itertools;
use self::size_level::SizeLevel;
pub mod size_level;
/// The LLVM optimizer and code-gen settings.
#[derive(Debug, Serialize, Deserialize, Clone, Eq)]
pub struct Settings {
@@ -20,9 +19,6 @@ pub struct Settings {
/// The back-end optimization level.
pub level_back_end: inkwell::OptimizationLevel,
/// Fallback to optimizing for size if the bytecode is too large.
pub is_fallback_to_size_enabled: bool,
/// Whether the LLVM `verify each` option is enabled.
pub is_verify_each_enabled: bool,
/// Whether the LLVM `debug logging` option is enabled.
@@ -41,8 +37,6 @@ impl Settings {
level_middle_end_size,
level_back_end,
is_fallback_to_size_enabled: false,
is_verify_each_enabled: false,
is_debug_logging_enabled: false,
}
@@ -62,8 +56,6 @@ impl Settings {
level_middle_end_size,
level_back_end,
is_fallback_to_size_enabled: false,
is_verify_each_enabled,
is_debug_logging_enabled,
}
@@ -197,16 +189,6 @@ impl Settings {
combinations
}
/// Sets the fallback to optimizing for size if the bytecode is too large.
pub fn enable_fallback_to_size(&mut self) {
self.is_fallback_to_size_enabled = true;
}
/// Whether the fallback to optimizing for size is enabled.
pub fn is_fallback_to_size_enabled(&self) -> bool {
self.is_fallback_to_size_enabled
}
}
impl PartialEq for Settings {
@@ -227,18 +209,3 @@ impl std::fmt::Display for Settings {
)
}
}
impl TryFrom<&SolcStandardJsonInputSettingsOptimizer> for Settings {
type Error = anyhow::Error;
fn try_from(value: &SolcStandardJsonInputSettingsOptimizer) -> Result<Self, Self::Error> {
let mut result = match value.mode {
Some(mode) => Self::try_from_cli(mode)?,
None => Self::size(),
};
if value.fallback_to_optimizing_for_size.unwrap_or_default() {
result.enable_fallback_to_size();
}
Ok(result)
}
}
+4 -2
View File
@@ -1,10 +1,12 @@
//! The LLVM context constants.
use revive_common::{BIT_LENGTH_X32, BYTE_LENGTH_WORD};
/// The LLVM framework version.
pub const LLVM_VERSION: semver::Version = semver::Version::new(18, 1, 4);
/// The pointer width sized type.
pub static XLEN: usize = revive_common::BIT_LENGTH_X32;
pub static XLEN: usize = BIT_LENGTH_X32;
/// The calldata size global variable name.
pub static GLOBAL_CALLDATA_SIZE: &str = "calldatasize";
@@ -20,4 +22,4 @@ pub static GLOBAL_ADDRESS_SPILL_BUFFER: &str = "address_spill_buffer";
/// The deployer call header size that consists of:
/// - bytecode hash (32 bytes)
pub const DEPLOYER_CALL_HEADER_SIZE: usize = revive_common::BYTE_LENGTH_WORD;
pub const DEPLOYER_CALL_HEADER_SIZE: usize = BYTE_LENGTH_WORD;
@@ -66,9 +66,9 @@ impl<'ctx> Argument<'ctx> {
/// Access the underlying value.
///
/// Will emit a stack load if `self` is a pointer argument.
pub fn access<D: crate::polkavm::Dependency + Clone>(
pub fn access(
&self,
context: &crate::polkavm::context::Context<'ctx, D>,
context: &crate::polkavm::context::Context<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>> {
match &self.value {
Value::Register(value) => Ok(*value),
@@ -79,9 +79,9 @@ impl<'ctx> Argument<'ctx> {
/// Access the underlying value.
///
/// Will emit a stack load if `self` is a pointer argument.
pub fn as_pointer<D: crate::polkavm::Dependency + Clone>(
pub fn as_pointer(
&self,
context: &crate::polkavm::context::Context<'ctx, D>,
context: &crate::polkavm::context::Context<'ctx>,
) -> anyhow::Result<crate::polkavm::context::Pointer<'ctx>> {
match &self.value {
Value::Register(value) => {
@@ -1,7 +1,6 @@
//! The LLVM module build.
use std::collections::BTreeMap;
use revive_common::BYTE_LENGTH_WORD;
use serde::Deserialize;
use serde::Serialize;
@@ -9,31 +8,23 @@ use serde::Serialize;
#[derive(Debug, Serialize, Deserialize)]
pub struct Build {
/// The PolkaVM text assembly.
pub assembly_text: String,
pub assembly_text: Option<String>,
/// The metadata hash.
pub metadata_hash: Option<[u8; revive_common::BYTE_LENGTH_WORD]>,
pub metadata_hash: Option<[u8; BYTE_LENGTH_WORD]>,
/// The PolkaVM binary bytecode.
pub bytecode: Vec<u8>,
/// The PolkaVM bytecode hash.
pub bytecode_hash: String,
/// The hash-to-full-path mapping of the contract factory dependencies.
pub factory_dependencies: BTreeMap<String, String>,
/// The PolkaVM bytecode hash. Unlinked builds don't have a hash yet.
pub bytecode_hash: Option<[u8; BYTE_LENGTH_WORD]>,
}
impl Build {
/// A shortcut constructor.
pub fn new(
assembly_text: String,
metadata_hash: Option<[u8; revive_common::BYTE_LENGTH_WORD]>,
bytecode: Vec<u8>,
bytecode_hash: String,
) -> Self {
pub fn new(metadata_hash: Option<[u8; BYTE_LENGTH_WORD]>, bytecode: Vec<u8>) -> Self {
Self {
assembly_text,
assembly_text: None,
metadata_hash,
bytecode,
bytecode_hash,
factory_dependencies: BTreeMap::new(),
bytecode_hash: None,
}
}
}
@@ -2,6 +2,8 @@
use std::cell::RefCell;
use revive_common::BIT_LENGTH_WORD;
use inkwell::debug_info::AsDIScope;
use inkwell::debug_info::DIScope;
@@ -164,7 +166,7 @@ impl<'ctx> DebugInfo<'ctx> {
&self,
flags: Option<inkwell::debug_info::DIFlags>,
) -> anyhow::Result<inkwell::debug_info::DIBasicType<'ctx>> {
self.create_primitive_type(revive_common::BIT_LENGTH_WORD, flags)
self.create_primitive_type(BIT_LENGTH_WORD, flags)
}
/// Return the DIBuilder.
@@ -64,24 +64,13 @@ impl<'ctx> LLVMRuntime<'ctx> {
}
}
/// Declares an LLVM runtime function in the `module`,
pub fn declare(
module: &inkwell::module::Module<'ctx>,
name: &str,
r#type: inkwell::types::FunctionType<'ctx>,
linkage: Option<inkwell::module::Linkage>,
) -> FunctionDeclaration<'ctx> {
let value = module.add_function(name, r#type, linkage);
FunctionDeclaration::new(r#type, value)
}
/// Create the function definition from an existing symbol.
pub fn define(
module: &inkwell::module::Module<'ctx>,
name: &str,
) -> Option<FunctionDeclaration<'ctx>> {
let value = module.get_function(name)?;
value.set_linkage(inkwell::module::Linkage::External);
value.set_linkage(inkwell::module::Linkage::Private);
FunctionDeclaration::new(value.get_type(), value).into()
}
}
@@ -1,12 +1,5 @@
//! The LLVM IR generator function.
pub mod declaration;
pub mod intrinsics;
pub mod llvm_runtime;
pub mod r#return;
pub mod runtime;
pub mod yul_data;
use std::collections::HashMap;
use inkwell::debug_info::AsDIScope;
@@ -20,6 +13,13 @@ use self::declaration::Declaration;
use self::r#return::Return;
use self::yul_data::YulData;
pub mod declaration;
pub mod intrinsics;
pub mod llvm_runtime;
pub mod r#return;
pub mod runtime;
pub mod yul_data;
/// The LLVM IR generator function.
#[derive(Debug)]
pub struct Function<'ctx> {
@@ -75,15 +75,6 @@ impl<'ctx> Function<'ctx> {
self.name.as_str()
}
/// Checks whether the function is defined outside of the front-end.
pub fn is_name_external(name: &str) -> bool {
name.starts_with("llvm.")
|| (name.starts_with("__")
&& name != self::runtime::FUNCTION_ENTRY
&& name != self::runtime::FUNCTION_DEPLOY_CODE
&& name != self::runtime::FUNCTION_RUNTIME_CODE)
}
/// Returns the LLVM function declaration.
pub fn declaration(&self) -> Declaration<'ctx> {
self.declaration
@@ -223,30 +214,11 @@ impl<'ctx> Function<'ctx> {
self.stack.get(name).copied()
}
/// Removes the pointer to a stack variable.
pub fn remove_stack_pointer(&mut self, name: &str) {
self.stack.remove(name);
}
/// Returns the return entity representation.
pub fn r#return(&self) -> Return<'ctx> {
self.r#return
}
/// Returns the pointer to the function return value.
/// # Panics
/// If the pointer has not been set yet.
pub fn return_pointer(&self) -> Option<Pointer<'ctx>> {
self.r#return.return_pointer()
}
/// Returns the return data size in bytes, based on the default stack alignment.
/// # Panics
/// If the pointer has not been set yet.
pub fn return_data_size(&self) -> usize {
self.r#return.return_data_size()
}
/// Returns the function entry block.
pub fn entry_block(&self) -> inkwell::basic_block::BasicBlock<'ctx> {
self.entry_block
@@ -1,22 +1,19 @@
//! Translates the arithmetic operations.
use inkwell::values::BasicValue;
use revive_common::BIT_LENGTH_WORD;
use crate::polkavm::context::runtime::RuntimeFunction;
use crate::polkavm::context::Context;
use crate::polkavm::Dependency;
use crate::polkavm::WriteLLVM;
/// Implements the division operator according to the EVM specification.
pub struct Division;
impl<D> RuntimeFunction<D> for Division
where
D: Dependency + Clone,
{
impl RuntimeFunction for Division {
const NAME: &'static str = "__revive_division";
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
fn r#type<'ctx>(context: &Context<'ctx>) -> inkwell::types::FunctionType<'ctx> {
context.word_type().fn_type(
&[context.word_type().into(), context.word_type().into()],
false,
@@ -25,7 +22,7 @@ where
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx, D>,
context: &mut Context<'ctx>,
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>> {
let operand_1 = Self::paramater(context, 0).into_int_value();
let operand_2 = Self::paramater(context, 1).into_int_value();
@@ -39,29 +36,23 @@ where
}
}
impl<D> WriteLLVM<D> for Division
where
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::declare(self, context)
impl WriteLLVM for Division {
fn declare(&mut self, context: &mut Context) -> anyhow::Result<()> {
<Self as RuntimeFunction>::declare(self, context)
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::emit(&self, context)
fn into_llvm(self, context: &mut Context) -> anyhow::Result<()> {
<Self as RuntimeFunction>::emit(&self, context)
}
}
/// Implements the signed division operator according to the EVM specification.
pub struct SignedDivision;
impl<D> RuntimeFunction<D> for SignedDivision
where
D: Dependency + Clone,
{
impl RuntimeFunction for SignedDivision {
const NAME: &'static str = "__revive_signed_division";
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
fn r#type<'ctx>(context: &Context<'ctx>) -> inkwell::types::FunctionType<'ctx> {
context.word_type().fn_type(
&[context.word_type().into(), context.word_type().into()],
false,
@@ -70,7 +61,7 @@ where
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx, D>,
context: &mut Context<'ctx>,
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>> {
let operand_1 = Self::paramater(context, 0).into_int_value();
let operand_2 = Self::paramater(context, 1).into_int_value();
@@ -96,14 +87,12 @@ where
context.set_basic_block(block_overflow);
let max_uint = context.builder().build_int_z_extend(
context
.integer_type(revive_common::BIT_LENGTH_WORD - 1)
.const_all_ones(),
context.integer_type(BIT_LENGTH_WORD - 1).const_all_ones(),
context.word_type(),
"max_uint",
)?;
let is_operand_1_overflow = context.builder().build_int_compare(
inkwell::IntPredicate::EQ,
inkwell::IntPredicate::SLT,
operand_1,
context.builder().build_int_neg(max_uint, "min_uint")?,
"is_operand_1_overflow",
@@ -121,29 +110,23 @@ where
}
}
impl<D> WriteLLVM<D> for SignedDivision
where
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::declare(self, context)
impl WriteLLVM for SignedDivision {
fn declare(&mut self, context: &mut Context) -> anyhow::Result<()> {
<Self as RuntimeFunction>::declare(self, context)
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::emit(&self, context)
fn into_llvm(self, context: &mut Context) -> anyhow::Result<()> {
<Self as RuntimeFunction>::emit(&self, context)
}
}
/// Implements the remainder operator according to the EVM specification.
pub struct Remainder;
impl<D> RuntimeFunction<D> for Remainder
where
D: Dependency + Clone,
{
impl RuntimeFunction for Remainder {
const NAME: &'static str = "__revive_remainder";
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
fn r#type<'ctx>(context: &Context<'ctx>) -> inkwell::types::FunctionType<'ctx> {
context.word_type().fn_type(
&[context.word_type().into(), context.word_type().into()],
false,
@@ -152,7 +135,7 @@ where
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx, D>,
context: &mut Context<'ctx>,
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>> {
let operand_1 = Self::paramater(context, 0).into_int_value();
let operand_2 = Self::paramater(context, 1).into_int_value();
@@ -166,29 +149,23 @@ where
}
}
impl<D> WriteLLVM<D> for Remainder
where
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::declare(self, context)
impl WriteLLVM for Remainder {
fn declare(&mut self, context: &mut Context) -> anyhow::Result<()> {
<Self as RuntimeFunction>::declare(self, context)
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::emit(&self, context)
fn into_llvm(self, context: &mut Context) -> anyhow::Result<()> {
<Self as RuntimeFunction>::emit(&self, context)
}
}
/// Implements the signed remainder operator according to the EVM specification.
pub struct SignedRemainder;
impl<D> RuntimeFunction<D> for SignedRemainder
where
D: Dependency + Clone,
{
impl RuntimeFunction for SignedRemainder {
const NAME: &'static str = "__revive_signed_remainder";
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
fn r#type<'ctx>(context: &Context<'ctx>) -> inkwell::types::FunctionType<'ctx> {
context.word_type().fn_type(
&[context.word_type().into(), context.word_type().into()],
false,
@@ -197,7 +174,7 @@ where
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx, D>,
context: &mut Context<'ctx>,
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>> {
let operand_1 = Self::paramater(context, 0).into_int_value();
let operand_2 = Self::paramater(context, 1).into_int_value();
@@ -211,16 +188,13 @@ where
}
}
impl<D> WriteLLVM<D> for SignedRemainder
where
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::declare(self, context)
impl WriteLLVM for SignedRemainder {
fn declare(&mut self, context: &mut Context) -> anyhow::Result<()> {
<Self as RuntimeFunction>::declare(self, context)
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::emit(&self, context)
fn into_llvm(self, context: &mut Context) -> anyhow::Result<()> {
<Self as RuntimeFunction>::emit(&self, context)
}
}
@@ -231,13 +205,12 @@ where
///
/// The result is either the calculated quotient or zero,
/// selected at runtime.
fn wrapped_division<'ctx, D, F, T>(
context: &Context<'ctx, D>,
fn wrapped_division<'ctx, F, T>(
context: &Context<'ctx>,
denominator: inkwell::values::IntValue<'ctx>,
f: F,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
F: FnOnce() -> anyhow::Result<T>,
T: inkwell::values::IntMathValue<'ctx>,
{
@@ -1,64 +1,52 @@
//! The deploy code function.
use std::marker::PhantomData;
use crate::polkavm::context::code_type::CodeType;
use crate::polkavm::context::function::runtime;
use crate::polkavm::context::Context;
use crate::polkavm::Dependency;
use crate::polkavm::WriteLLVM;
/// The deploy code function.
/// Is a special function that is only used by the front-end generated code.
#[derive(Debug)]
pub struct DeployCode<B, D>
pub struct DeployCode<B>
where
B: WriteLLVM<D>,
D: Dependency + Clone,
B: WriteLLVM,
{
/// The deploy code AST representation.
inner: B,
/// The `D` phantom data.
_pd: PhantomData<D>,
}
impl<B, D> DeployCode<B, D>
impl<B> DeployCode<B>
where
B: WriteLLVM<D>,
D: Dependency + Clone,
B: WriteLLVM,
{
/// A shortcut constructor.
pub fn new(inner: B) -> Self {
Self {
inner,
_pd: PhantomData,
}
Self { inner }
}
}
impl<B, D> WriteLLVM<D> for DeployCode<B, D>
impl<B> WriteLLVM for DeployCode<B>
where
B: WriteLLVM<D>,
D: Dependency + Clone,
B: WriteLLVM,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
fn declare(&mut self, context: &mut Context) -> anyhow::Result<()> {
let function_type = context.function_type::<inkwell::types::BasicTypeEnum>(vec![], 0);
context.add_function(
runtime::FUNCTION_DEPLOY_CODE,
function_type,
0,
Some(inkwell::module::Linkage::External),
Some(inkwell::module::Linkage::Private),
None,
false,
)?;
self.inner.declare(context)
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
context.set_current_function(runtime::FUNCTION_DEPLOY_CODE, None)?;
fn into_llvm(self, context: &mut Context) -> anyhow::Result<()> {
context.set_current_function(runtime::FUNCTION_DEPLOY_CODE, None, false)?;
context.set_basic_block(context.current_function().borrow().entry_block());
context.set_code_type(CodeType::Deploy);
self.inner.into_llvm(context)?;
context.set_debug_location(0, 0, None)?;
@@ -1,12 +1,16 @@
//! The entry function.
use inkwell::types::BasicType;
use revive_common::BIT_LENGTH_ETH_ADDRESS;
use revive_runtime_api::immutable_data::{
GLOBAL_IMMUTABLE_DATA_POINTER, GLOBAL_IMMUTABLE_DATA_SIZE,
};
use revive_runtime_api::polkavm_imports::CALL_DATA_SIZE;
use revive_solc_json_interface::PolkaVMDefaultHeapMemorySize;
use crate::polkavm::context::address_space::AddressSpace;
use crate::polkavm::context::function::runtime;
use crate::polkavm::context::Context;
use crate::polkavm::Dependency;
use crate::polkavm::WriteLLVM;
/// The entry function.
@@ -21,10 +25,7 @@ impl Entry {
/// Initializes the global variables.
/// The pointers are not initialized, because it's not possible to create a null pointer.
pub fn initialize_globals<D>(context: &mut Context<D>) -> anyhow::Result<()>
where
D: Dependency + Clone,
{
pub fn initialize_globals(context: &mut Context) -> anyhow::Result<()> {
context.set_global(
crate::polkavm::GLOBAL_CALLDATA_SIZE,
context.xlen_type(),
@@ -52,7 +53,7 @@ impl Entry {
heap_memory_type.const_zero(),
);
let address_type = context.integer_type(revive_common::BIT_LENGTH_ETH_ADDRESS);
let address_type = context.integer_type(BIT_LENGTH_ETH_ADDRESS);
context.set_global(
crate::polkavm::GLOBAL_ADDRESS_SPILL_BUFFER,
address_type,
@@ -64,16 +65,13 @@ impl Entry {
}
/// Populate the calldata size global value.
pub fn load_calldata_size<D>(context: &mut Context<D>) -> anyhow::Result<()>
where
D: Dependency + Clone,
{
pub fn load_calldata_size(context: &mut Context) -> anyhow::Result<()> {
let call_data_size_pointer = context
.get_global(crate::polkavm::GLOBAL_CALLDATA_SIZE)?
.value
.as_pointer_value();
let call_data_size_value = context
.build_runtime_call(revive_runtime_api::polkavm_imports::CALL_DATA_SIZE, &[])
.build_runtime_call(CALL_DATA_SIZE, &[])
.expect("the call_data_size syscall method should return a value")
.into_int_value();
let call_data_size_value = context.builder().build_int_truncate(
@@ -90,10 +88,7 @@ impl Entry {
/// Calls the deploy code if the first function argument was `1`.
/// Calls the runtime code otherwise.
pub fn leave_entry<D>(context: &mut Context<D>) -> anyhow::Result<()>
where
D: Dependency + Clone,
{
pub fn leave_entry(context: &mut Context) -> anyhow::Result<()> {
context.set_debug_location(0, 0, None)?;
let is_deploy = context
@@ -133,11 +128,8 @@ impl Entry {
}
}
impl<D> WriteLLVM<D> for Entry
where
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
impl WriteLLVM for Entry {
fn declare(&mut self, context: &mut Context) -> anyhow::Result<()> {
let entry_arguments = vec![context.bool_type().as_basic_type_enum()];
let entry_function_type = context.function_type(entry_arguments, 0);
context.add_function(
@@ -146,16 +138,17 @@ where
0,
Some(inkwell::module::Linkage::External),
None,
false,
)?;
context.declare_global(
revive_runtime_api::immutable_data::GLOBAL_IMMUTABLE_DATA_POINTER,
GLOBAL_IMMUTABLE_DATA_POINTER,
context.word_type().array_type(0),
AddressSpace::Stack,
);
context.declare_global(
revive_runtime_api::immutable_data::GLOBAL_IMMUTABLE_DATA_SIZE,
GLOBAL_IMMUTABLE_DATA_SIZE,
context.xlen_type(),
AddressSpace::Stack,
);
@@ -166,9 +159,9 @@ where
/// Instead of a single entrypoint, the runtime expects two exports: `call ` and `deploy`.
/// `call` and `deploy` directly call `entry`, signaling a deploy if the first arg is `1`.
/// The `entry` function loads calldata, sets globals and calls the runtime or deploy code.
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
fn into_llvm(self, context: &mut Context) -> anyhow::Result<()> {
let entry = context
.get_function(runtime::FUNCTION_ENTRY)
.get_function(runtime::FUNCTION_ENTRY, false)
.expect("the entry function should already be declared")
.borrow()
.declaration;
@@ -179,7 +172,7 @@ where
true,
);
context.set_current_function(runtime::FUNCTION_ENTRY, None)?;
context.set_current_function(runtime::FUNCTION_ENTRY, None, false)?;
context.set_basic_block(context.current_function().borrow().entry_block());
Self::initialize_globals(context)?;
@@ -5,7 +5,6 @@ use inkwell::values::BasicValue;
use crate::polkavm::context::function::Attribute;
use crate::polkavm::context::runtime::RuntimeFunction;
use crate::polkavm::context::Context;
use crate::polkavm::Dependency;
use crate::polkavm::WriteLLVM;
/// Pointers are represented as opaque 256 bit integer values in EVM.
@@ -15,10 +14,7 @@ use crate::polkavm::WriteLLVM;
/// (but wrong) pointers when truncated.
pub struct WordToPointer;
impl<D> RuntimeFunction<D> for WordToPointer
where
D: Dependency + Clone,
{
impl RuntimeFunction for WordToPointer {
const NAME: &'static str = "__revive_int_truncate";
const ATTRIBUTES: &'static [Attribute] = &[
@@ -27,7 +23,7 @@ where
Attribute::AlwaysInline,
];
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
fn r#type<'ctx>(context: &Context<'ctx>) -> inkwell::types::FunctionType<'ctx> {
context
.xlen_type()
.fn_type(&[context.word_type().into()], false)
@@ -35,7 +31,7 @@ where
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx, D>,
context: &mut Context<'ctx>,
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>> {
let value = Self::paramater(context, 0).into_int_value();
let truncated =
@@ -67,26 +63,20 @@ where
}
}
impl<D> WriteLLVM<D> for WordToPointer
where
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::declare(self, context)
impl WriteLLVM for WordToPointer {
fn declare(&mut self, context: &mut Context) -> anyhow::Result<()> {
<Self as RuntimeFunction>::declare(self, context)
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::emit(&self, context)
fn into_llvm(self, context: &mut Context) -> anyhow::Result<()> {
<Self as RuntimeFunction>::emit(&self, context)
}
}
/// The revive runtime exit function.
pub struct Exit;
impl<D> RuntimeFunction<D> for Exit
where
D: Dependency + Clone,
{
impl RuntimeFunction for Exit {
const NAME: &'static str = "__revive_exit";
const ATTRIBUTES: &'static [Attribute] = &[
@@ -95,7 +85,7 @@ where
Attribute::AlwaysInline,
];
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
fn r#type<'ctx>(context: &Context<'ctx>) -> inkwell::types::FunctionType<'ctx> {
context.void_type().fn_type(
&[
context.xlen_type().into(),
@@ -108,7 +98,7 @@ where
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx, D>,
context: &mut Context<'ctx>,
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>> {
let flags = Self::paramater(context, 0).into_int_value();
let offset = Self::paramater(context, 1).into_int_value();
@@ -133,15 +123,12 @@ where
}
}
impl<D> WriteLLVM<D> for Exit
where
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::declare(self, context)
impl WriteLLVM for Exit {
fn declare(&mut self, context: &mut Context) -> anyhow::Result<()> {
<Self as RuntimeFunction>::declare(self, context)
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::emit(&self, context)
fn into_llvm(self, context: &mut Context) -> anyhow::Result<()> {
<Self as RuntimeFunction>::emit(&self, context)
}
}
@@ -1,64 +1,52 @@
//! The runtime code function.
use std::marker::PhantomData;
use crate::polkavm::context::code_type::CodeType;
use crate::polkavm::context::function::runtime;
use crate::polkavm::context::Context;
use crate::polkavm::Dependency;
use crate::polkavm::WriteLLVM;
/// The runtime code function.
/// Is a special function that is only used by the front-end generated code.
#[derive(Debug)]
pub struct RuntimeCode<B, D>
pub struct RuntimeCode<B>
where
B: WriteLLVM<D>,
D: Dependency + Clone,
B: WriteLLVM,
{
/// The runtime code AST representation.
inner: B,
/// The `D` phantom data.
_pd: PhantomData<D>,
}
impl<B, D> RuntimeCode<B, D>
impl<B> RuntimeCode<B>
where
B: WriteLLVM<D>,
D: Dependency + Clone,
B: WriteLLVM,
{
/// A shortcut constructor.
pub fn new(inner: B) -> Self {
Self {
inner,
_pd: PhantomData,
}
Self { inner }
}
}
impl<B, D> WriteLLVM<D> for RuntimeCode<B, D>
impl<B> WriteLLVM for RuntimeCode<B>
where
B: WriteLLVM<D>,
D: Dependency + Clone,
B: WriteLLVM,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
fn declare(&mut self, context: &mut Context) -> anyhow::Result<()> {
let function_type = context.function_type::<inkwell::types::BasicTypeEnum>(vec![], 0);
context.add_function(
runtime::FUNCTION_RUNTIME_CODE,
function_type,
0,
Some(inkwell::module::Linkage::External),
Some(inkwell::module::Linkage::Private),
None,
false,
)?;
self.inner.declare(context)
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
context.set_current_function(runtime::FUNCTION_RUNTIME_CODE, None)?;
fn into_llvm(self, context: &mut Context) -> anyhow::Result<()> {
context.set_current_function(runtime::FUNCTION_RUNTIME_CODE, None, false)?;
context.set_basic_block(context.current_function().borrow().entry_block());
context.set_code_type(CodeType::Runtime);
self.inner.into_llvm(context)?;
context.set_debug_location(0, 0, None)?;

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