Compare commits

...

44 Commits

Author SHA1 Message Date
pgherveou 20a3b30296 wip 2025-04-28 16:39:59 +02:00
pgherveou f44b2485bd lint 2025-04-24 16:34:06 +02:00
xermicus f6a412eef4 release resolc v0.1.0-dev.14 (#289)
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-04-24 11:57:46 +02:00
xermicus 20e77cb0b5 configurable stack and heap memory size (#288)
- Allow configuration of the maximum heap and stack size via CLI flags
and JSON input settings.
- Increase the default value for the stack size to 32kb.

---------

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-04-24 10:47:38 +02:00
xermicus 357bf58868 allow dynamic configuration of the heap memory (#287)
This PR changes the implementation of the emulated EVM heap memory:
Instead of linking in a C implementation the code is emitted directly
into the contract module. Which allows making it configurable via a
compiler parameter (a follow up PR to this).

---------

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-04-23 20:25:55 +02:00
xermicus f937188991 revive-runner: install with locked dependencies (#286)
Make the installation of  `revive-runner` easier:
- Use locked dependencies to avoid issues with downstream crates
- Make the llvm-context crate an optional dependency to the runner
- Add it to the default `test` target ensuring that this actually works

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-04-22 19:46:18 +02:00
xermicus 6e44488b4f revive-runner: add a utility binary for local contract execution (#284)
I had this in mind for a while but never implemented a standalone binary
so far because I always end up writing an integration test anyways.

However, using a standalone version of the pallet based on the
revive-runner crate is something people filing in bug reports do
anyways, for example:
https://github.com/paritytech/revive/issues/266
https://github.com/paritytech/contract-issues/issues/54
---------

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-04-22 15:34:51 +02:00
xermicus 5003f3e9ac llvm-context: alloca at the function entry if possible (#283)
Closes  #48

Change the code size test to no longer emit debug info as to get a more
accurate picture.

---------

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-04-15 15:22:24 +02:00
xermicus 431b5a2ce5 llvm-context: lazy handling of function arguments and immutable data (#282)
- Lazily load function arguments so that they can be passed as pointers.
- Lazily call the immutable store function to avoid storing zero sized
immutable data.

---------

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-04-14 15:54:59 +02:00
xermicus ad3315346c release resolc-0.1.0-dev.13 (#279) 2025-04-08 09:10:13 +02:00
dependabot[bot] 516f79ee0f Bump tokio from 1.43.0 to 1.44.2 (#281)
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.43.0 to 1.44.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/tokio-rs/tokio/releases">tokio's
releases</a>.</em></p>
<blockquote>
<h2>Tokio v1.44.2</h2>
<p>This release fixes a soundness issue in the broadcast channel. The
channel
accepts values that are <code>Send</code> but <code>!Sync</code>.
Previously, the channel called
<code>clone()</code> on these values without synchronizing. This release
fixes the channel
by synchronizing calls to <code>.clone()</code> (Thanks Austin Bonander
for finding and
reporting the issue).</p>
<h3>Fixed</h3>
<ul>
<li>sync: synchronize <code>clone()</code> call in broadcast channel (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7232">#7232</a>)</li>
</ul>
<p><a
href="https://redirect.github.com/tokio-rs/tokio/issues/7232">#7232</a>:
<a
href="https://redirect.github.com/tokio-rs/tokio/pull/7232">tokio-rs/tokio#7232</a></p>
<h2>Tokio v1.44.1</h2>
<h1>1.44.1 (March 13th, 2025)</h1>
<h3>Fixed</h3>
<ul>
<li>rt: skip defer queue in <code>block_in_place</code> context (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7216">#7216</a>)</li>
</ul>
<p><a
href="https://redirect.github.com/tokio-rs/tokio/issues/7216">#7216</a>:
<a
href="https://redirect.github.com/tokio-rs/tokio/pull/7216">tokio-rs/tokio#7216</a></p>
<h2>Tokio v1.44.0</h2>
<h1>1.44.0 (March 7th, 2025)</h1>
<p>This release changes the <code>from_std</code> method on sockets to
panic if a blocking socket is provided. We determined this change is not
a breaking change as Tokio is not intended to operate using blocking
sockets. Doing so results in runtime hangs and should be considered a
bug. Accidentally passing a blocking socket to Tokio is one of the most
common user mistakes. If this change causes an issue for you, please
comment on <a
href="https://redirect.github.com/tokio-rs/tokio/issues/7172">#7172</a>.</p>
<h3>Added</h3>
<ul>
<li>coop: add <code>task::coop</code> module (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7116">#7116</a>)</li>
<li>process: add <code>Command::get_kill_on_drop()</code> (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7086">#7086</a>)</li>
<li>sync: add <code>broadcast::Sender::closed</code> (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/6685">#6685</a>,
<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7090">#7090</a>)</li>
<li>sync: add <code>broadcast::WeakSender</code> (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7100">#7100</a>)</li>
<li>sync: add <code>oneshot::Receiver::is_empty()</code> (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7153">#7153</a>)</li>
<li>sync: add <code>oneshot::Receiver::is_terminated()</code> (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7152">#7152</a>)</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>fs: empty reads on <code>File</code> should not start a background
read (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7139">#7139</a>)</li>
<li>process: calling <code>start_kill</code> on exited child should not
fail (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7160">#7160</a>)</li>
<li>signal: fix <code>CTRL_CLOSE</code>, <code>CTRL_LOGOFF</code>,
<code>CTRL_SHUTDOWN</code> on windows (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7122">#7122</a>)</li>
<li>sync: properly handle panic during mpsc drop (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7094">#7094</a>)</li>
</ul>
<h3>Changes</h3>
<ul>
<li>runtime: clean up magic number in registration set (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7112">#7112</a>)</li>
<li>coop: make coop yield using waker defer strategy (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7185">#7185</a>)</li>
<li>macros: make <code>select!</code> budget-aware (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7164">#7164</a>)</li>
<li>net: panic when passing a blocking socket to <code>from_std</code>
(<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7166">#7166</a>)</li>
<li>io: clean up buffer casts (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7142">#7142</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/tokio-rs/tokio/commit/ec4b1d7215a3e1e91797ad3fb6ba0f7c7f3d2566"><code>ec4b1d7</code></a>
chore: forward port 1.43.x</li>
<li><a
href="https://github.com/tokio-rs/tokio/commit/e3c3a56718d201fb7bb430567f05fbb64b2ef082"><code>e3c3a56</code></a>
Merge branch 'tokio-1.43.x' into forward-port-1.43.x</li>
<li><a
href="https://github.com/tokio-rs/tokio/commit/a7b658c35bd40f6811e557aeb97cbb361b612c56"><code>a7b658c</code></a>
chore: prepare Tokio v1.43.1 release</li>
<li><a
href="https://github.com/tokio-rs/tokio/commit/c1c8d1033d637d7027fdc137ec8008c5801cbc0d"><code>c1c8d10</code></a>
Merge remote-tracking branch 'origin/tokio-1.38.x' into
forward-port-1.38.x</li>
<li><a
href="https://github.com/tokio-rs/tokio/commit/aa303bc2051f7c21b48bb7bfcafe8fd4f39afd21"><code>aa303bc</code></a>
chore: prepare Tokio v1.38.2 release</li>
<li><a
href="https://github.com/tokio-rs/tokio/commit/7b6ccb515ff067151ed62db835f735e5653f8784"><code>7b6ccb5</code></a>
chore: backport CI fixes</li>
<li><a
href="https://github.com/tokio-rs/tokio/commit/4b174ce2c95fe1d1a217917db93fcc935e17e0da"><code>4b174ce</code></a>
sync: fix cloning value when receiving from broadcast channel</li>
<li><a
href="https://github.com/tokio-rs/tokio/commit/d413c9c02af8f2b4fea14b769b86484b12f46595"><code>d413c9c</code></a>
chore: prepare Tokio v1.44.1 (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7217">#7217</a>)</li>
<li><a
href="https://github.com/tokio-rs/tokio/commit/addbfb9204be25a8621feb3f20b44a7c1f00edbd"><code>addbfb9</code></a>
rt: skip defer queue in <code>block_in_place</code> context (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7216">#7216</a>)</li>
<li><a
href="https://github.com/tokio-rs/tokio/commit/8182ecf2628d5e80dac52b8ed1ea466dbb0925b9"><code>8182ecf</code></a>
chore: prepare Tokio v1.44.0 (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7202">#7202</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/tokio-rs/tokio/compare/tokio-1.43.0...tokio-1.44.2">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=tokio&package-manager=cargo&previous-version=1.43.0&new-version=1.44.2)](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>
Co-authored-by: xermicus <cyrill@parity.io>
2025-04-08 08:26:06 +02:00
Alexander Samusev 9f5443b6d6 Remove path from release json and add sha256 (#280)
PR addresses
https://github.com/paritytech/revive/issues/162#issuecomment-2783173447
2025-04-08 08:18:35 +02:00
Alexander Samusev 0dafc779ce ci: update release flow and publish list.json (#276)
This pull request adds changes described in **this comment**:

- `.github/scripts/json_generator.py` - a small script that generates
several json files for different platforms.
-
[generate_versions.yml](https://github.com/paritytech/revive/compare/as-release-json?expand=1#diff-2aee05b96020ac60943e6dfcb30597e53898f31542aeb570468b970d9a13a5a6)
- the workflow that runs when release is published, creates info.json
files and pushes them into resolc-bin repo
- `js/build.js` is adjusted in order to set `RESOLC_WASM_URI` from env
variable
- ⚠️ Release workflow is changed: 
  - In PRs and main branch it'll only build artifacts
- Release will happen automatically only on the `v*` tag push. This is
needed for revive_web.js to have the necessary `RESOLC_WASM_URI`
- workflow will check that version in Cargo.toml is the same as the tag
when the new tag is pushed

cc https://github.com/paritytech/revive/issues/162
cc https://github.com/paritytech/devops/issues/3890
2025-04-07 13:39:56 +02:00
xermicus 7d8fa75a0f storage keys and values should be big endian (#277)
Storage keys and values are big endian. Keeping them LE was a pre-mature
optimization because for the contract itself it this is a no-op and thus
not observable. However we should consider the storage layout as part of
the contract ABI. The endianness of transient storage values are still
kept as-is.

---------

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-04-07 10:34:44 +02:00
dependabot[bot] 80f94b5c76 Bump openssl from 0.10.71 to 0.10.72 (#278)
Bumps [openssl](https://github.com/sfackler/rust-openssl) from 0.10.71
to 0.10.72.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/sfackler/rust-openssl/releases">openssl's
releases</a>.</em></p>
<blockquote>
<h2>openssl-v0.10.72</h2>
<h2>What's Changed</h2>
<ul>
<li>make set_rsa_oaep_md visible to boringssl config by <a
href="https://github.com/frncs-rss"><code>@​frncs-rss</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2372">sfackler/rust-openssl#2372</a></li>
<li>Fix typo in openssl-sys build script by <a
href="https://github.com/rushilmehra"><code>@​rushilmehra</code></a> in
<a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2375">sfackler/rust-openssl#2375</a></li>
<li>Unify the two BoringSSL codepaths a bit and simplify init by <a
href="https://github.com/davidben"><code>@​davidben</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2377">sfackler/rust-openssl#2377</a></li>
<li>pkey_ctx: Fix link to the corresponding OpenSSL function by <a
href="https://github.com/Jakuje"><code>@​Jakuje</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2378">sfackler/rust-openssl#2378</a></li>
<li>fix test on MSRV by <a
href="https://github.com/alex"><code>@​alex</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2383">sfackler/rust-openssl#2383</a></li>
<li>Add support for AWS-LC to openssl and openssl-sys crates by <a
href="https://github.com/skmcgrail"><code>@​skmcgrail</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/1805">sfackler/rust-openssl#1805</a></li>
<li>Enable additional capabilities for AWS-LC by <a
href="https://github.com/skmcgrail"><code>@​skmcgrail</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2386">sfackler/rust-openssl#2386</a></li>
<li>Use --experimental with bindgen-cli with aws-lc build by <a
href="https://github.com/skmcgrail"><code>@​skmcgrail</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2389">sfackler/rust-openssl#2389</a></li>
<li>Fixed two UAFs and bumped versions for release by <a
href="https://github.com/alex"><code>@​alex</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2390">sfackler/rust-openssl#2390</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/Jakuje"><code>@​Jakuje</code></a> made
their first contribution in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2378">sfackler/rust-openssl#2378</a></li>
<li><a href="https://github.com/skmcgrail"><code>@​skmcgrail</code></a>
made their first contribution in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/1805">sfackler/rust-openssl#1805</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.71...openssl-v0.10.72">https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.71...openssl-v0.10.72</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/sfackler/rust-openssl/commit/87085bd67896b7f92e6de35d081f607a334beae4"><code>87085bd</code></a>
Merge pull request <a
href="https://redirect.github.com/sfackler/rust-openssl/issues/2390">#2390</a>
from alex/uaf-fix</li>
<li><a
href="https://github.com/sfackler/rust-openssl/commit/d1a12e21573e95727b2e38b8b65273cb389be7e4"><code>d1a12e2</code></a>
Fixed two UAFs and bumped versions for release</li>
<li><a
href="https://github.com/sfackler/rust-openssl/commit/7c7b2e6c9f95e77e56ab37af70b16de75beff387"><code>7c7b2e6</code></a>
Merge pull request <a
href="https://redirect.github.com/sfackler/rust-openssl/issues/2389">#2389</a>
from skmcgrail/aws-lc-follow-up</li>
<li><a
href="https://github.com/sfackler/rust-openssl/commit/34a477bff20cbe43492915338d3c12597430c345"><code>34a477b</code></a>
Use --experimental with bindgen-cli with aws-lc build</li>
<li><a
href="https://github.com/sfackler/rust-openssl/commit/d4bf0710640e4725b8b237968040aef3e5f4ab9a"><code>d4bf071</code></a>
Merge pull request <a
href="https://redirect.github.com/sfackler/rust-openssl/issues/2386">#2386</a>
from skmcgrail/aws-lc-follow-up</li>
<li><a
href="https://github.com/sfackler/rust-openssl/commit/a86bf670c4cba3ee5531838b52419356791d966e"><code>a86bf67</code></a>
Remove comment</li>
<li><a
href="https://github.com/sfackler/rust-openssl/commit/705dbfb2ee3f2d7151ff313d840bf558435d4379"><code>705dbfb</code></a>
Fix test</li>
<li><a
href="https://github.com/sfackler/rust-openssl/commit/e0df413d46a89303c42e15bf7d4566193b242466"><code>e0df413</code></a>
Skip final call for LibreSSL 4.1.0 for CCM mode</li>
<li><a
href="https://github.com/sfackler/rust-openssl/commit/2f1164b5e838d3665dd10a9fac19e22174289ea3"><code>2f1164b</code></a>
Enable additional capabilities for AWS-LC</li>
<li><a
href="https://github.com/sfackler/rust-openssl/commit/dde9ffb36071249ff98474eec853fd830aea44f5"><code>dde9ffb</code></a>
Merge pull request <a
href="https://redirect.github.com/sfackler/rust-openssl/issues/1805">#1805</a>
from skmcgrail/aws-lc-support-final</li>
<li>Additional commits viewable in <a
href="https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.71...openssl-v0.10.72">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=openssl&package-manager=cargo&previous-version=0.10.71&new-version=0.10.72)](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-04-05 12:25:52 +02:00
xermicus dab29bc89b bugfix missing byte swap for the create2 salt value (#272)
Found in https://github.com/paritytech/contract-issues/issues/45, thanks
@albertov19 and @sekisamu

---------

Signed-off-by: xermicus <cyrill@parity.io>
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-04-03 15:20:22 +02:00
xermicus 313c033261 fix Rust 1.86.0 clippy (#273)
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-04-03 15:03:49 +02:00
xermicus 87c1d7a8be Suport passing arbitrary llvm arguments (#271)
- Support for passing LLVM command line options via the prcoess input or
providing one or more `--llvm-arg='..'` resolc CLI flag. This allows
more fine-grained control over the LLVM backend configuration.
- Make LLVM initialization idempotent.

---------

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-04-03 13:21:00 +02:00
xermicus 8a98346a9c Fix CI badge link (#270) 2025-04-01 07:52:39 +02:00
Alexander Samusev 1b4d2c6bb8 ci: modify release according to #162 (#269)
PR modifies release workflow according to changes requested in
https://github.com/paritytech/revive/issues/162#issuecomment-2751287953
2025-03-26 12:06:51 +01:00
xermicus 004b8ac16c Update release.yml (#265)
The Release workflow used to work with a fetch-depth: 0 - not sure why this is required but yolo-ing it in for a test
2025-03-20 20:07:04 +01:00
xermicus 497dae2494 factor out solc JSON interface crate (#264)
The differential testing framework will make a second consumer. There
seems to be no re-usable Rust crate for this. But we already have
everything here, just needs a small refactor to make it fully re-usable.

- Mostly decouple the solc JSON-input-output interface types from the
`solidity` frontend crate
- Expose the JSON-input-output interface types in a dedicated crate

---------

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-03-20 17:11:40 +01:00
Pavlo Khrystenko 36ea69b50f Add --supported-solc-versions CLI command (#260)
### Description 

Adds `--supported-solc-versions` cli command to return a semver range of
supported `solc` versions.
Is a hack until #162 is implemented.

---------

Co-authored-by: xermicus <cyrill@parity.io>
2025-03-19 16:07:48 +01:00
xermicus 2bbc5d713d remove support for webkit (#262)
Signed-off-by: xermicus <cyrill@parity.io>
2025-03-18 15:00:55 +01:00
xermicus 4a9b651235 Support solc v0.8.29 (#261)
No observable changes w.r.t. YUL compilation.
- Mark solc v0.8.29 as the latest supported version
- Add integration test for solc v0.8.29 specific feature
- Use the latest version on CI
- Drive-by fix linter complaints and a two types in the CI yaml files

---------

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
Signed-off-by: xermicus <cyrill@parity.io>
2025-03-18 13:01:15 +01:00
Alexander Theißen 66f9a4d64f Release Windows Version (#251)
With LLVM working only minor changes were necessary to get resolc
running on Windows.

Release in my branch here:
https://github.com/paritytech/revive-alex-workflowtest/releases/tag/v0.1.0-dev.12
2025-03-01 13:49:04 +01:00
xermicus 76f4cf71d6 ci: re-enable the machete (#249)
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-02-28 16:05:53 +01:00
xermicus 77e0344d80 llvm-context: remove dead code (#247)
- remove the __sha3 function symbol: this is provided by the pallet
- remove the storage address spaces: they are not mapped into memory

---------

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
Co-authored-by: Alexander Theißen <alex.theissen@me.com>
2025-02-28 15:38:42 +01:00
Alexander Theißen 2fb8beee62 Build LLVM for Windows (#248)
This PR changes the CI build scripts to also build LLVM for windows.
**It doesn't build `revive` itself for windows**. This will come in a
follow up. But once we have a LLVM binary release the turn around time
will be much quicker for experimenting with the revive windows build.

I manually uploaded the release those changes produce
[here](https://github.com/paritytech/revive-alex-workflowtest/releases/tag/llvm-18.1.8-revive.22f3ceb).
This enables this PR's CI to find the proper release. This is necessary
because I am also making changes to the folder structure and artifact
naming that the other CI jobs are depending on.

Releases generated from this branch can be inspected here:
https://github.com/paritytech/revive-alex-workflowtest/releases/tag/v0.1.0-dev.12

Summary of changes:
- Change `llvm-builder` to use MSVC toolchain on windows
- Fix `llvm-builder` to work with `.exe` files
- Unify the llvm release jobs into a single one. This removed a lot of
copy pasted code and also speeds up the build by giving each their own
runner.
- Use the LLVM target triple to name the binary releases instead of an
ad-hoc naming convention
- Remove the nested folder hierarchy inside the llvm release. Its just
now a single folder `llvm-<target>` that contains the toolchain.
- Give jobs and workflows consistent names
- Replace all runners bei their `*-latest` counterpart
- Only use `parity-large` to build llvm now. All other jobs use github
runners
2025-02-28 15:06:03 +01:00
xermicus 93788e72e9 integration: additional mcopy test (#245)
Closes #237

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-02-27 11:39:31 +01:00
xermicus 296a226d0b integration: add delegate call corner case test (#243)
Closes #235

---------

Signed-off-by: xermicus <cyrill@parity.io>
2025-02-27 11:10:00 +01:00
xermicus 84deb3a29d integration: add function return type test (#244)
Closes #236
2025-02-27 10:37:37 +01:00
Evgeny Snitko ee064671e0 Release archive path fix (#241) 2025-02-26 16:40:49 +01:00
xermicus 63dfd046e5 Update RELEASE.md (#239) 2025-02-26 09:38:00 +01:00
xermicus 08f341ccc1 release resolc v0.1.0-dev.12 (#238)
Signed-off-by: xermicus <cyrill@parity.io>
2025-02-26 09:36:28 +01:00
xermicus a07968205b llvm-context: modularize compiler builtin functions (#234)
- Add the revive runtime function interface to minimize boiler plate
code.
- Outline heavily repeated code into dedicated functions to bring down
code size.
- The code size tests builds optimized for size.
- Function attributes are passed as slices.

This significantly brings down the code size for all OpenZeppelin wizard
contracts (using all possible features) compiled against OpenZeppelin
`v5.0.0` with size optimizations.

|contract|| `-Oz` main | `-Oz` PR || `-O3` main | `-O3` PR |
|-|-|-|-|-|-|-|
|erc1155.sol||100K|67K||114K|147K|
|erc20.sol||120K|90K||160K|191K|
|erc721.sol||128K|101K||178K|214K|
|governor.sol||226K|165K||293K|349K|
|rwa.sol||116K|85K||154K|185K|
|stable.sol||116K|86K||155K|192K|

On the flip side this introduces a heavy penalty for cycle optimized
builds. Setting the no-inline attributes for cycle optimized builds
helps a lot but heavily penalizes runtime speed (LLVM does not yet
inline everything properly - to be investigated later on).

Next steps:
- Modularize more functions
- Refactor the YUL function arguments to use pointers instead of values
- Afterwards check if LLVM still has trouble inline-ing properly on O3
or set the no-inline attribute if it does not penalize runtime
performance too bad.
2025-02-25 16:47:01 +01:00
Evgeny Snitko 7ffe64ed7c CI improvements (#230)
- llvm artifacts search, download and extract now in `get-llvm` action 
- `get-emsdk` action - clone, install, activate emsdk, so we don't need
to build `revive-builder` for this
- switch workflows to new actions
- `concurrency` for remaining workflows (cancel run if new run is
triggered)
- `target_commitish` for main release, fixes release commit 
- Run release workflow in PR without creating a release if PR is labeled
with `release-test`

---------

Co-authored-by: cornholio <0@mcornholio.ru>
Co-authored-by: Alexander Theißen <alex.theissen@me.com>
Co-authored-by: xermicus <cyrill@parity.io>
Co-authored-by: xermicus <bigcyrill@hotmail.com>
2025-02-25 14:33:59 +01:00
Yuri Volkov f1bce4fe3f llvm-builder testing workflow (#164)
Co-authored-by: xermicus <cyrill@parity.io>
2025-02-25 12:39:03 +01:00
xermicus 9efbb4c0b4 ci: temporarily disable the machete (#233) 2025-02-25 09:40:40 +01:00
xermicus bcc8fa892c update dependencies (#229) 2025-02-24 12:45:05 +01:00
Pavlo Khrystenko 89ec25da65 Add per file output selection for --standard-json mode and some other small foundry support bits. (#228) 2025-02-24 12:44:22 +01:00
xermicus 75a83af4da ci: clippy (#227)
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-02-21 16:43:09 +01:00
xermicus 9c330ef8fc llvm-builder: only run expensive tests on linux (#226)
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-02-21 16:38:44 +01:00
Yuri Volkov 687cec31ef Using released LLVM in revive build (#220) 2025-02-21 16:30:26 +01:00
190 changed files with 10233 additions and 3358 deletions
+18
View File
@@ -0,0 +1,18 @@
name: "Get Emscripten SDK"
inputs:
version:
description: ""
required: false
default: "3.1.64"
runs:
using: "composite"
steps:
- name: install emsdk
shell: bash
run: |
git clone https://github.com/emscripten-core/emsdk.git ./emsdk/
cd emsdk
git checkout tags/${{ inputs.version }}
./emsdk install ${{ inputs.version }}
./emsdk activate ${{ inputs.version }}
+70
View File
@@ -0,0 +1,70 @@
# example:
# - uses: ./.github/actions/get-llvm
# with:
# target: x86_64-unknown-linux-gnu
name: "Download LLVM"
inputs:
target:
required: true
runs:
using: "composite"
steps:
- name: find asset
id: find
uses: actions/github-script@v7
env:
target: ${{ inputs.target }}
with:
result-encoding: string
script: |
let page = 1;
let releases = [];
let releasePrefix = "llvm-"
let target = process.env.target
do {
const res = await github.rest.repos.listReleases({
owner: context.repo.owner,
repo: context.repo.repo,
per_page: 50,
page,
});
releases = res.data
releases.sort((a, b) => {
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 =>{
return asset.name.includes(target);
});
if (!asset){
core.setFailed(`Artifact for '${target}' not found in release ${llvmLatestRelease.tag_name} (${llvmLatestRelease.html_url})`);
process.exit();
}
return asset.browser_download_url;
}
page++;
} while(releases.length > 0);
core.setFailed(`No LLVM releases with '${releasePrefix}' atifacts found! Please release LLVM before running this workflow.`);
process.exit();
- name: download
shell: bash
run: |
curl -sSLo llvm.tar.gz ${{ steps.find.outputs.result }}
- name: unpack
shell: bash
run: |
tar -xf llvm.tar.gz
rm llvm.tar.gz
+36
View File
@@ -0,0 +1,36 @@
name: "Install Solidity Compiler"
description: "Installs the Ethereum solc Solidity compiler frontend executable"
runs:
using: "composite"
steps:
- name: Figure out Solc Download URL
shell: bash
run: |
if [[ "${{ runner.os }}" == "Linux" ]]; then
echo "SOLC_NAME=solc-static-linux" >> $GITHUB_ENV
elif [[ "${{ runner.os }}" == "Windows" ]]; then
echo "SOLC_NAME=solc-windows.exe" >> $GITHUB_ENV
else
echo "SOLC_NAME=solc-macos" >> $GITHUB_ENV
fi
- name: Download Solc
shell: bash
run: |
mkdir -p solc
curl -sSL --output solc/solc https://github.com/ethereum/solidity/releases/download/v0.8.29/${SOLC_NAME}
- name: Make Solc Executable
if: ${{ runner.os == 'Windows' }}
shell: bash
run: |
echo "$(pwd -W)\\solc" >> $GITHUB_PATH
mv solc/solc solc/solc.exe
- name: Make Solc Executable
if: ${{ runner.os != 'Windows' }}
shell: bash
run: |
echo "$(pwd)/solc" >> $GITHUB_PATH
chmod +x solc/solc
+165
View File
@@ -0,0 +1,165 @@
import os
import sys
import json
import requests
def validate_github_token():
"""Validate that GITHUB_TOKEN environment variable is set."""
if 'GITHUB_TOKEN' not in os.environ:
print("Error: GITHUB_TOKEN environment variable is not set.")
sys.exit(1)
def fetch_release_data(repo, tag):
"""Fetch release data from GitHub API."""
url = f"https://api.github.com/repos/{repo}/releases/tags/{tag}"
headers = {
'Authorization': f"Bearer {os.environ['GITHUB_TOKEN']}",
'Accept': 'application/vnd.github+json',
'X-GitHub-Api-Version': '2022-11-28'
}
try:
response = requests.get(url, headers=headers)
response.raise_for_status()
return response.json()
except requests.RequestException as e:
print(f"Error fetching release data: {e}")
sys.exit(1)
def fetch_checksum_file(release_data):
"""
Fetch the checksum.txt file from the release assets
and parse it into a dictionary mapping file names to their SHA256 checksums.
"""
checksums = {}
# Find the checksum.txt asset URL
checksum_asset = None
for asset in release_data['assets']:
if asset['name'] == 'checksums.txt':
checksum_asset = asset
break
if not checksum_asset:
print("Warning: checksum.txt file not found in release assets.")
return checksums
# Download the checksum file
headers = {
'Authorization': f"Bearer {os.environ['GITHUB_TOKEN']}",
'Accept': 'application/octet-stream'
}
try:
response = requests.get(checksum_asset['browser_download_url'], headers=headers)
response.raise_for_status()
# Parse checksum file
for line in response.text.splitlines():
if line.strip():
checksum, filename = line.strip().split(None, 1)
checksums[filename] = checksum
return checksums
except requests.RequestException as e:
print(f"Error fetching checksum file: {e}")
return checksums
except Exception as e:
print(f"Error parsing checksum file: {e}")
return checksums
def extract_build_hash(target_commitish):
"""Extract the first 8 characters of the commit hash."""
return f"commit.{target_commitish[:8]}"
def generate_asset_json(release_data, asset, checksums):
"""Generate JSON for a specific asset."""
version = release_data['tag_name'].lstrip('v')
build = extract_build_hash(release_data['target_commitish'])
long_version = f"{version}+{build}"
# Get SHA256 checksum if available
sha256 = checksums.get(asset['name'], "")
return {
"name": asset['name'],
"version": version,
"build": build,
"longVersion": long_version,
"url": asset['browser_download_url'],
"sha256": sha256,
"firstSolcVersion": os.environ.get("FIRST_SOLC_VERSION", ""),
"lastSolcVersion": os.environ.get("LAST_SOLC_VERSION", "")
}
def save_platform_json(platform_folder, asset_json, tag):
"""Save asset JSON and update list.json for a specific platform."""
# Create platform folder if it doesn't exist
os.makedirs(platform_folder, exist_ok=True)
# Update or create list.json
list_file_path = os.path.join(platform_folder, "list.json")
if os.path.exists(list_file_path):
with open(list_file_path, 'r') as f:
try:
list_data = json.load(f)
except json.JSONDecodeError:
list_data = {"builds": [], "releases": {}, "latestRelease": ""}
else:
list_data = {"builds": [], "releases": {}, "latestRelease": ""}
# Remove any existing entry with the same path
list_data['builds'] = [
build for build in list_data['builds']
if build['version'] != asset_json['version']
]
# Add the new build
list_data['builds'].append(asset_json)
# Update releases
version = asset_json['version']
list_data['releases'][version] = f"{asset_json['name']}+{asset_json['longVersion']}"
# Update latest release
list_data['latestRelease'] = version
with open(list_file_path, 'w') as f:
json.dump(list_data, f, indent=4)
def main():
# Validate arguments
if len(sys.argv) != 3:
print("Usage: python script.py <repo> <tag>")
sys.exit(1)
repo, tag = sys.argv[1], sys.argv[2]
# Validate GitHub token
validate_github_token()
# Fetch release data
release_data = fetch_release_data(repo, tag)
# Fetch checksums
checksums = fetch_checksum_file(release_data)
# Mapping of asset names to platform folders
platform_mapping = {
'resolc-x86_64-unknown-linux-musl': 'linux',
'resolc-universal-apple-darwin': 'macos',
'resolc-x86_64-pc-windows-msvc.exe': 'windows',
'resolc_web.js': 'wasm'
}
# Process each asset
for asset in release_data['assets']:
platform_name = platform_mapping.get(asset['name'])
if platform_name:
platform_folder = os.path.join(platform_name)
asset_json = generate_asset_json(release_data, asset, checksums)
save_platform_json(platform_folder, asset_json, tag)
print(f"Processed {asset['name']} for {platform_name}")
if __name__ == "__main__":
main()
-127
View File
@@ -1,127 +0,0 @@
name: Build revive-wasm
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
workflow_dispatch:
env:
CARGO_TERM_COLOR: always
REVIVE_WASM_INSTALL_DIR: ${{ github.workspace }}/target/wasm32-unknown-emscripten/release
jobs:
build-revive-wasm:
runs-on: parity-large
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v4
- name: Cache LLVM build
id: cache-llvm
uses: actions/cache@v3
with:
path: |
target-llvm/emscripten/target-final
target-llvm/gnu/target-final
# Use a unique key based on LLVM version or configuration files to avoid cache invalidation
key: llvm-build-${{ runner.os }}-${{ hashFiles('LLVM.lock', 'Cargo.toml', 'Cargo.lock', 'crates/llvm-builder/**', '.github/workflows/build-revive-wasm.yml') }}
- name: Install system dependencies
run: |
sudo apt-get update && sudo apt-get install -y cmake ninja-build curl git libssl-dev pkg-config clang lld
- name: Install Rust stable toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
components: rust-src
target: wasm32-unknown-emscripten
- name: Install LLVM build dependencies
run: |
make install-llvm-builder
revive-llvm --target-env emscripten clone
- name: Setup revive environment variables
run: |
echo "LLVM_SYS_181_PREFIX=$(pwd)/target-llvm/gnu/target-final" >> $GITHUB_ENV
echo "REVIVE_LLVM_TARGET_PREFIX=$(pwd)/target-llvm/emscripten/target-final" >> $GITHUB_ENV
- name: Build host LLVM
if: steps.cache-llvm.outputs.cache-hit != 'true'
run: |
revive-llvm build --llvm-projects lld --llvm-projects clang
- name: Build target LLVM
if: steps.cache-llvm.outputs.cache-hit != 'true'
run: |
source emsdk/emsdk_env.sh
revive-llvm --target-env emscripten build --llvm-projects lld
- run: |
rustup show
cargo --version
rustup +nightly show
cargo +nightly --version
cmake --version
bash --version
- name: Use Cached LLVM
if: steps.cache-llvm.outputs.cache-hit == 'true'
run: |
echo "Using cached LLVM"
- name: Build revive
run: |
source emsdk/emsdk_env.sh
make install-wasm
- uses: actions/upload-artifact@v4
with:
name: revive-wasm
path: |
${{ env.REVIVE_WASM_INSTALL_DIR }}/resolc.js
${{ env.REVIVE_WASM_INSTALL_DIR }}/resolc.wasm
${{ env.REVIVE_WASM_INSTALL_DIR }}/resolc_web.js
retention-days: 1
test-revive-wasm:
needs: build-revive-wasm
strategy:
matrix:
os: ["ubuntu-24.04", "macos-14", "windows-2022"]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Create Target Directory
run: mkdir -p ${{ env.REVIVE_WASM_INSTALL_DIR }}
- name: Download Artifact
uses: actions/download-artifact@v4
with:
name: revive-wasm
path: ${{ env.REVIVE_WASM_INSTALL_DIR }}
- name: Set Up Node.js
uses: actions/setup-node@v3
with:
node-version: "20"
- name: Install packages
run: npm install
- name: Run Playwright tests
run: |
cd js
npx playwright install --with-deps
npx playwright test
- name: Test revive
run: |
echo "Running tests for ${{ matrix.os }}"
npm run test:wasm
+64
View File
@@ -0,0 +1,64 @@
name: Generate JSON for resolc-bin
on:
release:
types: [published]
jobs:
generateJson:
runs-on: ubuntu-latest
if: contains(github.event.release.tag_name, 'llvm') == false
environment: tags
env:
# the token is needed for json_generator.py
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
path: tmp
- name: Checkout
uses: actions/checkout@v4
with:
repository: paritytech/resolc-bin
path: resolc-bin
- uses: actions/create-github-app-token@v1
id: app-token
with:
app-id: ${{ secrets.REVIVE_JSON_APP_ID }}
private-key: ${{ secrets.REVIVE_JSON_APP_KEY }}
owner: paritytech
repositories: resolc-bin
- name: Generate json and push
env:
TOKEN: ${{ steps.app-token.outputs.token }}
APP_NAME: "paritytech-revive-json"
Green: "\e[32m"
NC: "\e[0m"
run: |
sudo apt-get update && sudo apt-get install -y wget
wget https://github.com/${GITHUB_REPOSITORY}/releases/download/${GITHUB_REF_NAME}/resolc-x86_64-unknown-linux-musl
chmod +x resolc-x86_64-unknown-linux-musl
export FIRST_SOLC_VERSION=$(./resolc-x86_64-unknown-linux-musl --supported-solc-versions | cut -f 1 -d "," | tr -d ">=")
export LAST_SOLC_VERSION=$(./resolc-x86_64-unknown-linux-musl --supported-solc-versions | cut -f 2 -d "," | tr -d "<=")
cd resolc-bin
python ../tmp/.github/scripts/json_generator.py ${GITHUB_REPOSITORY} ${{ github.event.release.tag_name }}
echo "${Green}Add new remote with gh app token${NC}"
git remote set-url origin $(git config remote.origin.url | sed "s/github.com/${APP_NAME}:${TOKEN}@github.com/g")
echo "${Green}Remove http section that causes issues with gh app auth token${NC}"
sed -i.bak '/\[http/d' ./.git/config
sed -i.bak '/extraheader/d' ./.git/config
git config user.email "ci@parity.io"
git config user.name "${APP_NAME}"
git add .
git commit -m "Update json"
git push origin main
echo "::notice::info.list files were successfully published to https://github.com/paritytech/resolc-bin"
+49
View File
@@ -0,0 +1,49 @@
name: NPM Release
on:
release:
types: [released]
env:
CI: true
jobs:
publish:
name: Build & Publish to NPM
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22.x
- name: Install
run: yarn install --immutable
- name: update-resolc
run: yarn update-resolc
- name: Build
run: yarn build
- name: Set version
run: npm version --no-git-tag-version ${{github.event.release.tag_name}}
- name: npm pack
run: npm pack
- uses: actions/upload-artifact@v4
with:
name: package
path: 'parity-revive-*.tgz'
- uses: octokit/request-action@bbedc70b1981e610d89f1f8de88311a1fc02fb83
with:
route: POST /repos/paritytech/npm_publish_automation/actions/workflows/publish.yml/dispatches
ref: main
inputs: '${{ format(''{{ "repo": "{0}", "run_id": "{1}" }}'', github.repository, github.run_id) }}'
env:
GITHUB_TOKEN: ${{ secrets.NPM_PUBLISH_AUTOMATION_TOKEN }}
+82 -109
View File
@@ -1,5 +1,4 @@
name: Release LLVM name: Release LLVM
on: on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
@@ -8,12 +7,16 @@ on:
required: true required: true
description: llvm version in "x.x.x" format, e.g. "18.1.8" description: llvm version in "x.x.x" format, e.g. "18.1.8"
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
env: env:
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
jobs: jobs:
create-release: create-release-draft:
runs-on: ubuntu-latest runs-on: ubuntu-24.04
permissions: permissions:
contents: write contents: write
outputs: outputs:
@@ -23,145 +26,115 @@ jobs:
run: | run: |
echo "version=llvm-${{ inputs.llvm_version }}-revive.${GITHUB_SHA:0:7}" >> $GITHUB_OUTPUT echo "version=llvm-${{ inputs.llvm_version }}-revive.${GITHUB_SHA:0:7}" >> $GITHUB_OUTPUT
- name: create release - name: Create Release
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
with: with:
name: "LLVM binaries release: ${{ steps.resolve-version.outputs.version }}" name: ${{ steps.resolve-version.outputs.version }}
body: "This release includes binaries of LLVM, used to compile revive itself" body: "LLVM is a dependency of revive. The LLVM releases are used by our CI to build revive."
make_latest: "false" draft: true
tag_name: ${{ steps.resolve-version.outputs.version }} tag_name: ${{ steps.resolve-version.outputs.version }}
build-macos: build:
strategy: strategy:
matrix: matrix:
os: [macos-14, macos-13] target:
[
x86_64-unknown-linux-gnu,
x86_64-unknown-linux-musl,
wasm32-unknown-emscripten,
aarch64-apple-darwin,
x86_64-apple-darwin,
x86_64-pc-windows-msvc,
]
include: include:
- os: macos-13 - target: x86_64-unknown-linux-gnu
arch: x64 builder-arg: gnu
- os: macos-14 host: linux
arch: arm64 runner: parity-large
needs: create-release - target: x86_64-unknown-linux-musl
runs-on: ${{ matrix.os }} builder-arg: musl
name: "build-macos-${{ matrix.arch }}" host: linux
runner: parity-large
- target: wasm32-unknown-emscripten
builder-arg: emscripten
host: linux
runner: parity-large
- target: aarch64-apple-darwin
builder-arg: gnu
host: macos
runner: macos-14
- target: x86_64-apple-darwin
builder-arg: gnu
host: macos
runner: macos-13
- target: x86_64-pc-windows-msvc
builder-arg: gnu
host: windows
runner: windows-2022
needs: create-release-draft
runs-on: ${{ matrix.runner }}
env: env:
RUST_LOG: trace RUST_LOG: trace
permissions: permissions:
contents: write # for uploading assets to release contents: write # for uploading assets to release
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1
- name: install macos deps
run: |
brew install ninja
- name: versions
run: |
rustup show
cargo --version
cmake --version
echo "bash:" && bash --version
echo "ninja:" && ninja --version
echo "clang:" && clang --version
- name: Build LLVM
run: |
make install-llvm
- name: clean
# check removed files
run: |
cd target-llvm/gnu/target-final/bin/
rm diagtool llvm-libtool-darwin llvm-lipo llvm-pdbutil llvm-dwarfdump llvm-nm llvm-readobj llvm-cfi-verify \
sancov llvm-debuginfo-analyzer llvm-objdump llvm-profgen llvm-extract llvm-jitlink llvm-c-test llvm-gsymutil llvm-dwp \
dsymutil llvm-dwarfutil llvm-exegesis lli clang-rename bugpoint clang-extdef-mapping clang-refactor c-index-test \
llvm-reduce llvm-lto clang-linker-wrapper llc llvm-lto2
- name: package artifacts
run: |
tar -czf "${{ needs.create-release.outputs.version }}-macos-${{ matrix.arch }}.tar.gz" target-llvm/gnu/target-final
- name: upload archive to release
uses: softprops/action-gh-release@v2
with: with:
make_latest: "false" # without this it will override our rust flags
tag_name: ${{ needs.create-release.outputs.version }} rustflags: ""
files: | cache-key: ${{ matrix.target }}
${{ needs.create-release.outputs.version }}-macos-${{ matrix.arch }}.tar.gz
- name: Install Dependencies
build-linux-all: if: ${{ matrix.host == 'linux' }}
needs: create-release
runs-on: parity-large
env:
RUST_LOG: trace
permissions:
contents: write # for uploading assets to release
steps:
- uses: actions/checkout@v4
- name: install linux deps
run: | run: |
sudo apt-get update && sudo apt-get install -y cmake ninja-build curl git libssl-dev pkg-config clang lld musl sudo apt-get update && sudo apt-get install -y cmake ninja-build curl git libssl-dev pkg-config clang lld musl
- uses: actions-rust-lang/setup-rust-toolchain@v1 - name: Install Dependencies
with: if: ${{ matrix.host == 'macos' }}
toolchain: stable
components: rust-src
target: wasm32-unknown-emscripten
rustflags: ""
- name: versions
run: | run: |
rustup show brew install ninja
cargo --version
cmake --version
echo "bash:" && bash --version
echo "ninja:" && ninja --version
echo "clang:" && clang --version
- name: Build host LLVM - name: Install LLVM Builder
run: | run: |
make install-llvm cargo install --path crates/llvm-builder
- name: Build gnu LLVM - name: Clone LLVM
run: | run: |
revive-llvm clone revive-llvm --target-env ${{ matrix.builder-arg }} clone
revive-llvm build --llvm-projects lld --llvm-projects clang
- name: Build musl LLVM - name: Build LLVM
if: ${{ matrix.target != 'wasm32-unknown-emscripten' }}
run: | run: |
revive-llvm --target-env musl build --llvm-projects lld --llvm-projects clang revive-llvm --target-env ${{ matrix.builder-arg }} build --llvm-projects lld --llvm-projects clang
- name: Build emscripten LLVM - name: Build LLVM
if: ${{ matrix.target == 'wasm32-unknown-emscripten' }}
run: | run: |
revive-llvm --target-env emscripten clone
source emsdk/emsdk_env.sh source emsdk/emsdk_env.sh
revive-llvm --target-env emscripten build --llvm-projects lld revive-llvm --target-env ${{ matrix.builder-arg }} build --llvm-projects lld
- name: clean - name: Remove Unnecessary Binaries
# check removed files shell: bash
run: | run: |
for target in gnu emscripten musl; do cd target-llvm/${{ matrix.builder-arg }}/target-final/bin/
cd target-llvm/${target}/target-final/bin/ rm -f diagtool* llvm-libtool-darwin* llvm-lipo* llvm-pdbutil* llvm-dwarfdump* llvm-nm* llvm-readobj* llvm-cfi-verify* \
rm -rf diagtool llvm-libtool-darwin llvm-lipo llvm-pdbutil llvm-dwarfdump llvm-nm llvm-readobj llvm-cfi-verify \ sancov* llvm-debuginfo-analyzer* llvm-objdump* llvm-profgen* llvm-extract* llvm-jitlink* llvm-c-test* llvm-gsymutil* llvm-dwp* \
sancov llvm-debuginfo-analyzer llvm-objdump llvm-profgen llvm-extract llvm-jitlink llvm-c-test llvm-gsymutil llvm-dwp \ dsymutil* llvm-dwarfutil* llvm-exegesis* lli clang-rename* bugpoint* clang-extdef-mapping* clang-refactor* c-index-test* \
dsymutil llvm-dwarfutil llvm-exegesis lli clang-rename bugpoint clang-extdef-mapping clang-refactor c-index-test \ llvm-reduce* llvm-lto* clang-linker-wrapper* llc* llvm-lto2* llvm-otool* llvm-readelf* \
llvm-reduce llvm-lto clang-linker-wrapper llc llvm-lto2 llvm-otool llvm-readelf clang-repl* clang-check* clang-scan-deps*
cd - cd -
done
- name: package artifacts - name: Package Artifact
shell: bash
run: | run: |
tar -czf "${{ needs.create-release.outputs.version }}-x86_64-linux-gnu-linux.tar.gz" target-llvm/gnu/target-final mv target-llvm/${{ matrix.builder-arg }}/target-final/ llvm-${{ matrix.target }}
tar -czf "${{ needs.create-release.outputs.version }}-x86_64-linux-musl.tar.gz" target-llvm/musl/target-final tar -czf "${{ needs.create-release-draft.outputs.version }}-${{ matrix.target }}.tar.gz" llvm-${{ matrix.target }}
tar -czf "${{ needs.create-release.outputs.version }}-wasm32-unknown-emscripten.tar.gz" target-llvm/emscripten/target-final
- name: upload archive to release - name: Add Artifact to Release
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
with: with:
make_latest: "false" tag_name: ${{ needs.create-release-draft.outputs.version }}
tag_name: ${{ needs.create-release.outputs.version }} draft: true
files: | files: |
${{ needs.create-release.outputs.version }}-x86_64-linux-gnu-linux.tar.gz ${{ needs.create-release-draft.outputs.version }}-${{ matrix.target }}.tar.gz
${{ needs.create-release.outputs.version }}-x86_64-linux-musl.tar.gz
${{ needs.create-release.outputs.version }}-wasm32-unknown-emscripten.tar.gz
+288 -347
View File
@@ -1,347 +1,288 @@
name: Release name: Build & Release
run-name: Release ${{ github.ref_name }} on:
on: push:
push: branches: ["main"]
branches: tags:
- 'main' - "v*"
pull_request:
concurrency: branches: ["main"]
group: ${{ github.ref }}-${{ github.workflow }} types: [opened, synchronize, labeled, unlabeled]
cancel-in-progress: true
concurrency:
env: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
#rust-musl-cross:x86_64-musl cancel-in-progress: true
RUST_MUSL_CROSS_IMAGE: messense/rust-musl-cross@sha256:68b86bc7cb2867259e6b233415a665ff4469c28b57763e78c3bfea1c68091561
env:
jobs: CARGO_TERM_COLOR: always
RUST_MUSL_CROSS_IMAGE: messense/rust-musl-cross@sha256:68b86bc7cb2867259e6b233415a665ff4469c28b57763e78c3bfea1c68091561
#
# jobs:
# check-version-changed:
tag: runs-on: ubuntu-24.04
runs-on: ubuntu-24.04 permissions:
permissions: contents: write
contents: write env:
outputs: CURRENT_TAG: ${{ github.ref_name }}
TAG: ${{ steps.versions.outputs.TAG }} outputs:
PKG_VER: ${{ steps.versions.outputs.PKG_VER }} RELEASE_NOTES: ${{ steps.versions.outputs.RELEASE_NOTES }}
steps: steps:
- name: Checkout - uses: actions/checkout@v4
uses: actions/checkout@v4
with: # Check that tag and version in Cargo.toml match
fetch-tags: 'true' - name: Check versions
fetch-depth: 0 id: versions
run: |
- name: Versions if [[ $CURRENT_TAG == 'main' ]];
id: versions then
run: | echo "::notice::Tag $CURRENT_TAG is not a release tag, skipping the check in the main branch";
export CURRENT_TAG=$(git describe --tags --abbrev=0 --exclude "llvm-*") exit 0
export PKG_VER=v$(cat Cargo.toml | grep -A 5 package] | grep version | cut -d '=' -f 2 | tr -d '"' | tr -d " ") fi
echo "Current tag $CURRENT_TAG"
echo "Package version $PKG_VER" if [[ $CURRENT_TAG != "v"* ]];
# then
echo "PKG_VER=$PKG_VER" >> $GITHUB_OUTPUT echo "::notice::Tag $CURRENT_TAG is not a release tag, skipping the check in a PR";
if [ $CURRENT_TAG == $PKG_VER ]; exit 0
then fi
echo "Tag is up to date. Nothing to do.";
export TAG=old; export PKG_VER=v$(cat Cargo.toml | grep -A 5 package] | grep version | cut -d '=' -f 2 | tr -d '"' | tr -d " ")
else echo "Current tag $CURRENT_TAG"
echo "Tag was updated."; echo "Package version $PKG_VER"
export TAG=new; #
fi if [[ $CURRENT_TAG != $PKG_VER ]];
echo "TAG=$TAG" >> $GITHUB_OUTPUT then
echo "::error::Tag $CURRENT_TAG doesn't match package version $PKG_VER in Cargo.toml, please fix";
# exit 1
# fi
#
build-linux-all: # Generating release notes early, in order to avoid checkout at the last step
if: ${{ needs.tag.outputs.TAG == 'new' }} export RELEASE_NOTES="$(sed '/^## '${CURRENT_TAG}'/,/^## v/!d' CHANGELOG.md | sed -e '1d' -e '$d')"
runs-on: parity-large
needs: [tag] echo "Release notes:"
steps: echo "$RELEASE_NOTES"
- uses: actions/checkout@v4
echo 'RELEASE_NOTES<<EOF' >> $GITHUB_OUTPUT
- name: install linux deps echo "$RELEASE_NOTES" >> $GITHUB_OUTPUT
run: | echo 'EOF' >> $GITHUB_OUTPUT
sudo apt-get update && sudo apt-get install -y cmake ninja-build \
curl git libssl-dev pkg-config clang lld musl build:
strategy:
- name: Install Rust stable toolchain matrix:
uses: actions-rs/toolchain@v1 target:
with: [
profile: minimal x86_64-unknown-linux-musl,
toolchain: stable aarch64-apple-darwin,
components: rust-src x86_64-apple-darwin,
target: wasm32-unknown-emscripten x86_64-pc-windows-msvc,
]
- name: versions include:
run: | - target: x86_64-unknown-linux-musl
rustup show type: musl
cargo --version runner: ubuntu-24.04
cmake --version - target: aarch64-apple-darwin
echo "bash:" && bash --version type: native
echo "ninja:" && ninja --version runner: macos-14
echo "clang:" && clang --version - target: x86_64-apple-darwin
type: native
- name: build revive-llvm runner: macos-13
run: make install-llvm-builder - target: x86_64-pc-windows-msvc
type: native
# musl LLVM runner: windows-2022
runs-on: ${{ matrix.runner }}
- name: llvm-musl-cache restore needs: [check-version-changed]
id: llvm-musl-cache steps:
uses: actions/cache/restore@v4 - uses: actions/checkout@v4
with: - uses: actions-rust-lang/setup-rust-toolchain@v1
path: target-llvm/musl/target-final with:
key: llvm-linux-musl-${{ hashFiles('crates/solidity/**') }} # without this it will override our rust flags
rustflags: ""
- name: Build musl LLVM cache-key: ${{ matrix.target }}
if: steps.llvm-musl-cache.outputs.cache-hit != 'true'
run: | - name: Download LLVM
revive-llvm --target-env musl clone uses: ./.github/actions/get-llvm
revive-llvm --target-env musl build --llvm-projects lld --llvm-projects clang with:
target: ${{ matrix.target }}
- name: llvm-musl-cache save
if: steps.llvm-musl-cache.outputs.cache-hit != 'true' - name: Build
uses: actions/cache/save@v4 if: ${{ matrix.type == 'native' }}
with: shell: bash
path: target-llvm/musl/target-final run: |
key: llvm-linux-musl-${{ hashFiles('crates/solidity/**') }} export LLVM_SYS_181_PREFIX=$PWD/llvm-${{ matrix.target }}
make install-bin
# emscripten LLVM mv target/release/resolc resolc-${{ matrix.target }} || mv target/release/resolc.exe resolc-${{ matrix.target }}.exe
- name: llvm-emscripten-cache restore - name: Build
id: llvm-emscripten-cache if: ${{ matrix.type == 'musl' }}
uses: actions/cache/restore@v4 run: |
with: docker run -v $PWD:/opt/revive $RUST_MUSL_CROSS_IMAGE /bin/bash -c "
path: | cd /opt/revive
target-llvm/emscripten/target-final chown -R root:root .
emsdk apt update && apt upgrade -y && apt install -y pkg-config
key: llvm-linux-emscripten-${{ hashFiles('crates/solidity/**') }} export LLVM_SYS_181_PREFIX=/opt/revive/llvm-${{ matrix.target }}
make install-bin
- name: Build emscripten LLVM mv target/${{ matrix.target }}/release/resolc resolc-${{ matrix.target }}
if: steps.llvm-emscripten-cache.outputs.cache-hit != 'true' "
run: | sudo chown -R $(id -u):$(id -g) .
revive-llvm --target-env emscripten clone
source emsdk/emsdk_env.sh - name: Install Solc
revive-llvm --target-env emscripten build --llvm-projects lld uses: ./.github/actions/get-solc
- name: llvm-emscripten-cache save - name: Basic Sanity Check
if: steps.llvm-emscripten-cache.outputs.cache-hit != 'true' shell: bash
uses: actions/cache/save@v4 run: |
with: result=$(./resolc-${{ matrix.target }} --bin crates/integration/contracts/flipper.sol)
path: | echo $result
target-llvm/emscripten/target-final if [[ $result == *'0x50564d'* ]]; then exit 0; else exit 1; fi
emsdk
key: llvm-linux-emscripten-${{ hashFiles('crates/solidity/**') }} - uses: actions/upload-artifact@v4
with:
# Build revive name: resolc-${{ matrix.target }}
path: resolc-${{ matrix.target }}*
- name: build musl retention-days: 1
run: |
mkdir resolc-out build-wasm:
docker run -v $PWD:/opt/revive $RUST_MUSL_CROSS_IMAGE /bin/bash -c " runs-on: ubuntu-24.04
cd /opt/revive needs: [check-version-changed]
apt update && apt upgrade -y && apt install -y pkg-config env:
export LLVM_SYS_181_PREFIX=/opt/revive/target-llvm/musl/target-final RELEASE_RESOLC_WASM_URI: https://github.com/paritytech/revive-workflow-test/releases/download/${{ github.ref_name }}/resolc.wasm
make install-bin steps:
cp /root/.cargo/bin/resolc /opt/revive/resolc-out/resolc - uses: actions/checkout@v4
" - uses: actions-rust-lang/setup-rust-toolchain@v1
with:
- name: check musl target: wasm32-unknown-emscripten
run: | # without this it will override our rust flags
mkdir solc rustflags: ""
curl -sSLo solc/solc https://github.com/ethereum/solidity/releases/download/v0.8.28/solc-static-linux
chmod +x solc/solc - name: Download Host LLVM
PATH=$PWD/solc:$PATH uses: ./.github/actions/get-llvm
result=$(./resolc-out/resolc --bin crates/integration/contracts/flipper.sol) with:
echo $result target: x86_64-unknown-linux-gnu
if [[ $result == *'0x50564d'* ]]; then exit 0; else exit 1; fi
- name: Download Wasm LLVM
- name: Set Up Node.js uses: ./.github/actions/get-llvm
uses: actions/setup-node@v3 with:
with: target: wasm32-unknown-emscripten
node-version: "20"
- name: Download EMSDK
- name: build wasm uses: ./.github/actions/get-emsdk
run: |
export LLVM_SYS_181_PREFIX=$PWD/target-llvm/musl/target-final - name: Build
export REVIVE_LLVM_TARGET_PREFIX=$PWD/target-llvm/emscripten/target-final run: |
source emsdk/emsdk_env.sh export LLVM_SYS_181_PREFIX=$PWD/llvm-x86_64-unknown-linux-gnu
rustup target add wasm32-unknown-emscripten export REVIVE_LLVM_TARGET_PREFIX=$PWD/llvm-wasm32-unknown-emscripten
make install-wasm source emsdk/emsdk_env.sh
make install-wasm
- name: check wasm chmod -x ./target/wasm32-unknown-emscripten/release/resolc.wasm
run: |
curl -sSLo solc/soljson.js https://github.com/ethereum/solidity/releases/download/v0.8.28/soljson.js - name: Set Up Node.js
node -e " uses: actions/setup-node@v3
const soljson = require('solc/soljson'); with:
const createRevive = require('./target/wasm32-unknown-emscripten/release/resolc.js'); node-version: "20"
const compiler = createRevive(); - name: Basic Sanity Check
compiler.soljson = soljson; run: |
mkdir -p solc
const standardJsonInput = curl -sSLo solc/soljson.js https://github.com/ethereum/solidity/releases/download/v0.8.29/soljson.js
{ node -e "
language: 'Solidity', const soljson = require('solc/soljson');
sources: { const createRevive = require('./target/wasm32-unknown-emscripten/release/resolc.js');
'MyContract.sol': {
content: 'pragma solidity ^0.8.0; contract MyContract { function greet() public pure returns (string memory) { return \'Hello\'; } }', const compiler = createRevive();
}, compiler.soljson = soljson;
},
settings: { optimizer: { enabled: false } } const standardJsonInput =
}; {
language: 'Solidity',
compiler.writeToStdin(JSON.stringify(standardJsonInput)); sources: {
compiler.callMain(['--standard-json']); 'MyContract.sol': {
content: 'pragma solidity ^0.8.0; contract MyContract { function greet() public pure returns (string memory) { return \'Hello\'; } }',
// Collect output },
const stdout = compiler.readFromStdout(); },
const stderr = compiler.readFromStderr(); settings: { optimizer: { enabled: false } }
};
if (stderr) { console.error(stderr); process.exit(1); }
compiler.writeToStdin(JSON.stringify(standardJsonInput));
let out = JSON.parse(stdout); compiler.callMain(['--standard-json']);
let bytecode = out.contracts['MyContract.sol']['MyContract'].evm.bytecode.object
console.log(bytecode); // Collect output
const stdout = compiler.readFromStdout();
if(!bytecode.startsWith('50564d')) { process.exit(1); } const stderr = compiler.readFromStderr();
"
if (stderr) { console.error(stderr); process.exit(1); }
- uses: actions/upload-artifact@v4
with: let out = JSON.parse(stdout);
name: revive-wasm let bytecode = out.contracts['MyContract.sol']['MyContract'].evm.bytecode.object
path: | console.log(bytecode);
./target/wasm32-unknown-emscripten/release/resolc.js
./target/wasm32-unknown-emscripten/release/resolc.wasm if(!bytecode.startsWith('50564d')) { process.exit(1); }
./target/wasm32-unknown-emscripten/release/resolc_web.js "
retention-days: 1
- name: Compress Artifact
- uses: actions/upload-artifact@v4 run: |
with: mkdir -p resolc-wasm32-unknown-emscripten
name: revive-linux mv ./target/wasm32-unknown-emscripten/release/resolc.js ./resolc-wasm32-unknown-emscripten/
path: | mv ./target/wasm32-unknown-emscripten/release/resolc.wasm ./resolc-wasm32-unknown-emscripten/
./resolc-out/resolc mv ./target/wasm32-unknown-emscripten/release/resolc_web.js ./resolc-wasm32-unknown-emscripten/
retention-days: 1
- uses: actions/upload-artifact@v4
# with:
# name: resolc-wasm32-unknown-emscripten
# path: resolc-wasm32-unknown-emscripten/*
create-release: retention-days: 1
needs: [tag, build-linux-all]
runs-on: ubuntu-24.04 create-release:
permissions: if: startsWith(github.ref_name, 'v')
contents: write needs: [check-version-changed, build-wasm]
outputs: runs-on: macos-14
upload_url: ${{ steps.create_release.outputs.result }} environment: tags
steps: steps:
- uses: actions/checkout@v4 - name: Download Artifacts
uses: actions/download-artifact@v4
- name: Create/update tag with:
id: tag merge-multiple: true
uses: actions/github-script@v7
with: - name: Create macOS Fat Binary
result-encoding: string run: |
script: | lipo resolc-aarch64-apple-darwin resolc-x86_64-apple-darwin -create -output resolc-universal-apple-darwin
await github.rest.git.createRef({
owner: context.repo.owner, - name: Make Executable
repo: context.repo.repo, run: |
ref: 'refs/tags/${{ needs.tag.outputs.PKG_VER }}', chmod +x resolc-x86_64-unknown-linux-musl
sha: context.sha chmod +x resolc-universal-apple-darwin
})
- name: Create sha-256 checksum
- name: get relese notes run: |
id: get-notes shasum -a 256 resolc-x86_64-unknown-linux-musl > checksums.txt
run: | shasum -a 256 resolc-universal-apple-darwin >> checksums.txt
{ shasum -a 256 resolc-x86_64-pc-windows-msvc.exe >> checksums.txt
echo 'releaseNotes<<EOF' shasum -a 256 resolc.js >> checksums.txt
sed '/^## ${{ needs.tag.outputs.PKG_VER }}/,/^## v/!d' CHANGELOG.md | sed -e '1d' -e '$d' shasum -a 256 resolc.wasm >> checksums.txt
echo EOF shasum -a 256 resolc_web.js >> checksums.txt
} >> "$GITHUB_OUTPUT"
- uses: actions/create-github-app-token@v1
id: app-token
- name: Create release with:
id: create_release app-id: ${{ secrets.REVIVE_RELEASE_APP_ID }}
env: private-key: ${{ secrets.REVIVE_RELEASE_APP_KEY }}
releaseNotes: ${{ steps.get-notes.outputs.releaseNotes }}
version: ${{ needs.tag.outputs.PKG_VER }} - name: create-release
uses: actions/github-script@v7 uses: softprops/action-gh-release@v2
with: with:
result-encoding: string body: |
script: | ## Changelog
let response = await github.rest.repos.createRelease({ ${{ needs.check-version-changed.outputs.RELEASE_NOTES }}
owner: context.repo.owner,
repo: context.repo.repo, ## Note for macOS Users
tag_name: process.env.version, The macOS binary is unsigned and it needs to be made runnable using `xattr -c resolc-universal-apple-darwin`.
name: process.env.version, tag_name: ${{ github.ref_name }}
body: process.env.releaseNotes, name: ${{ github.ref_name }}
draft: true, prerelease: true
prerelease: true token: ${{ steps.app-token.outputs.token }}
}); target_commitish: ${{ github.sha }}
console.log(response); files: |
return response.data.upload_url; resolc-x86_64-unknown-linux-musl
resolc-universal-apple-darwin
- name: Log resolc-x86_64-pc-windows-msvc.exe
run: | resolc.js
echo "tag result: ${{ needs.tag.outputs.TAG }}" resolc.wasm
echo "pkg version: ${{ needs.tag.outputs.PKG_VER }}" resolc_web.js
checksums.txt
#
#
#
upload-assets:
runs-on: ubuntu-24.04
needs: [create-release]
steps:
- name: Download Artifact
uses: actions/download-artifact@v4
with:
name: revive-wasm
path: resolc/
- name: Download Artifact
uses: actions/download-artifact@v4
with:
name: revive-linux
path: resolc/
- name: upload resolc
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.create-release.outputs.upload_url }}
asset_path: ./resolc/resolc
asset_name: resolc-static-linux
asset_content_type: application/octet-stream
- name: upload resolc.js
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.create-release.outputs.upload_url }}
asset_path: ./resolc/resolc.js
asset_name: resolc.js
asset_content_type: application/octet-stream
- name: upload resolc.wasm
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.create-release.outputs.upload_url }}
asset_path: ./resolc/resolc.wasm
asset_name: resolc.wasm
asset_content_type: application/octet-stream
- name: upload resolc_web.js
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.create-release.outputs.upload_url }}
asset_path: ./resolc/resolc_web.js
asset_name: resolc_web.js
asset_content_type: application/octet-stream
-58
View File
@@ -1,58 +0,0 @@
name: Build
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
env:
CARGO_TERM_COLOR: always
jobs:
build-ubuntu-x86:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Install solc
run: |
mkdir -p solc
curl -sSL --output solc/solc https://github.com/ethereum/solidity/releases/download/v0.8.28/solc-static-linux
chmod +x solc/solc
echo "$(pwd)/solc/" >> $GITHUB_PATH
- name: Install LLVM
run: |
curl -sSL --output llvm.tar.xz https://github.com/paritytech/revive/releases/download/v0.1.0-dev.7/clang+llvm-18.1.8-x86_64-linux-gnu-ubuntu-24.04.tar.xz
mkdir llvm18
tar Jxf llvm.tar.xz -C llvm18/
echo "LLVM_SYS_181_PREFIX=$(pwd)/llvm18" >> $GITHUB_ENV
- name: Install geth
run: |
sudo add-apt-repository -y ppa:ethereum/ethereum
sudo apt update
sudo apt install -y ethereum
- name: Machete
uses: bnjbvr/cargo-machete@main
- name: Format
run: make format
- name: Clippy
run: make clippy
- name: Test cargo workspace
run: make test-workspace
- name: Test CLI
run: make test-cli
- uses: actions/upload-artifact@v4
with:
name: ${{ github.job }}-resolc
path: ./target/release/resolc
retention-days: 1
+45
View File
@@ -0,0 +1,45 @@
name: Test LLVM Builder
on:
pull_request:
branches: ["main"]
types: [opened, synchronize]
paths:
- "LLVM.lock"
- "crates/llvm-builder/**"
- ".github/workflows/test-llvm-builder.yml"
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
jobs:
test:
strategy:
matrix:
runner: [parity-large, macos-14, windows-2022]
runs-on: ${{ matrix.runner }}
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.runner }}
- name: Install Dependencies
if: matrix.runner == 'parity-large'
run: |
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'
run: |
brew install ninja
- name: Test
run: make test-llvm-builder
env:
RUST_LOG: trace
+98
View File
@@ -0,0 +1,98 @@
name: Test Wasm Version
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
types: [opened, synchronize]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
REVIVE_WASM_INSTALL_DIR: ${{ github.workspace }}/target/wasm32-unknown-emscripten/release
jobs:
build:
runs-on: ubuntu-24.04
defaults:
run:
shell: bash
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: Install emsdk
uses: ./.github/actions/get-emsdk
- name: Set LLVM Environment Variables
run: |
echo "LLVM_SYS_181_PREFIX=$(pwd)/llvm-x86_64-unknown-linux-gnu" >> $GITHUB_ENV
echo "REVIVE_LLVM_TARGET_PREFIX=$(pwd)/llvm-wasm32-unknown-emscripten" >> $GITHUB_ENV
- name: Build Revive
run: |
source emsdk/emsdk_env.sh
make install-wasm
- uses: actions/upload-artifact@v4
with:
name: revive-wasm
path: |
${{ env.REVIVE_WASM_INSTALL_DIR }}/resolc.js
${{ env.REVIVE_WASM_INSTALL_DIR }}/resolc.wasm
${{ env.REVIVE_WASM_INSTALL_DIR }}/resolc_web.js
retention-days: 1
test:
needs: build
strategy:
matrix:
os: ["ubuntu-24.04", "macos-14", "windows-2022"]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Create Target Directory
run: mkdir -p ${{ env.REVIVE_WASM_INSTALL_DIR }}
- name: Download Artifact
uses: actions/download-artifact@v4
with:
name: revive-wasm
path: ${{ env.REVIVE_WASM_INSTALL_DIR }}
- name: Set Up Node.js
uses: actions/setup-node@v3
with:
node-version: "20"
- name: Install Node Packages
run: npm install
- name: Run Playwright tests
run: |
cd js
npx playwright install --with-deps
npx playwright test
- name: Test revive
run: |
echo "Running tests for ${{ matrix.os }}"
npm run test:wasm
+57
View File
@@ -0,0 +1,57 @@
name: Test
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
types: [opened, synchronize]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
jobs:
test:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- 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 Geth
run: |
sudo add-apt-repository -y ppa:ethereum/ethereum
sudo apt update
sudo apt install -y ethereum
- name: Machete
uses: bnjbvr/cargo-machete@v0.7.1
- name: Format
run: make format
- name: Clippy
run: make clippy
- name: Test cargo workspace
run: make test-workspace
- name: Test CLI
run: make test-cli
+1
View File
@@ -1,4 +1,5 @@
/target /target
/js/resolc/dist
target-llvm target-llvm
*.dot *.dot
.vscode/ .vscode/
+90 -8
View File
@@ -4,7 +4,71 @@
This is a development pre-release. This is a development pre-release.
Supported `polkadot-sdk` rev: `274a781e8ca1a9432c7ec87593bd93214abbff50` Supported `polkadot-sdk` rev:`c29e72a8628835e34deb6aa7db9a78a2e4eabcee`
### Added
### Changed
### Fixed
## v0.1.0-dev.14
This is a development pre-release.
Supported `polkadot-sdk` rev:`c29e72a8628835e34deb6aa7db9a78a2e4eabcee`
### Added
- The `revive-runner` helper utility binary which helps to run contracts locally without a blockchain node.
- Allow configuration of the EVM heap memory size and stack size via CLI flags and JSON input settings.
### Changed
- The default PVM stack memory size was increased from 16kb to 32kb.
### Fixed
- Constructors avoid storing zero sized immutable data on exit.
## v0.1.0-dev.13
This is a development pre-release.
Supported `polkadot-sdk` rev:`c29e72a8628835e34deb6aa7db9a78a2e4eabcee`
### Added
- Support for solc v0.8.29
- Decouples the solc JSON-input-output type definitions from the Solidity fronted and expose them via a dedicated crate.
- `--supported-solc-versions` for `resolc` binary to return a `semver` range of supported `solc` versions.
- Support for passing LLVM command line options via the prcoess input or providing one or more `--llvm-arg='..'` resolc CLI flag. This allows more fine-grained control over the LLVM backend configuration.
### Changed
- Storage keys and values are big endian. This was a pre-mature optimization because for the contract itself it this is a no-op and thus not observable. However we should consider the storage layout as part of the contract ABI. The endianness of transient storage values are still kept as-is.
- Running `resolc` using webkit is no longer supported.
### Fixed
- A missing byte swap for the create2 salt value.
## v0.1.0-dev.12
This is a development pre-release.
Supported `polkadot-sdk` rev: `21f6f0705e53c15aa2b8a5706b208200447774a9`
### Added
- Per file output selection for `--standard-json` mode.
- The `ir` output selection option for `--standard-json` mode.
### Changed
- Improved code size: Large contracts compile to smaller code blobs when enabling aggressive size optimizations (`-Oz`).
### Fixed
## v0.1.0-dev.11 ## v0.1.0-dev.11
@@ -14,9 +78,10 @@ Supported `polkadot-sdk` rev: `274a781e8ca1a9432c7ec87593bd93214abbff50`
### Added ### Added
### Changed ### Changed
### Fixed ### Fixed
- A bug causing incorrect loads from the emulated EVM linear memory. - A bug causing incorrect loads from the emulated EVM linear memory.
- A missing integer truncate after switching to 64bit. - A missing integer truncate after switching to 64bit.
@@ -27,10 +92,12 @@ This is a development pre-release.
Supported `polkadot-sdk` rev: `274a781e8ca1a9432c7ec87593bd93214abbff50` Supported `polkadot-sdk` rev: `274a781e8ca1a9432c7ec87593bd93214abbff50`
### Added ### Added
- Support for the `coinbase` opcode. - Support for the `coinbase` opcode.
- The resolc web JS version. - The resolc web JS version.
### Changed ### Changed
- Missing the `--overwrite` flag emits an error instead of a warning. - Missing the `--overwrite` flag emits an error instead of a warning.
- The `resolc` executable prints the help by default. - The `resolc` executable prints the help by default.
- Removed support for legacy EVM assembly (EVMLA) translation. - Removed support for legacy EVM assembly (EVMLA) translation.
@@ -40,6 +107,7 @@ Supported `polkadot-sdk` rev: `274a781e8ca1a9432c7ec87593bd93214abbff50`
If detected, the re-entrant call flag is not set and 0 deposit limit is endowed. If detected, the re-entrant call flag is not set and 0 deposit limit is endowed.
### Fixed ### Fixed
- Solidity: Add the solc `--libraries` files to sources. - Solidity: Add the solc `--libraries` files to sources.
- A data race in tests. - A data race in tests.
- Fix `broken pipe` errors. - Fix `broken pipe` errors.
@@ -52,10 +120,12 @@ This is a development pre-release.
### Added ### Added
### Changed ### Changed
- Syscalls with more than 6 arguments now pack them into registers. - Syscalls with more than 6 arguments now pack them into registers.
### Fixed ### Fixed
- Remove reloading of the resolc.js file (fix issue with relative path in web worker) - Remove reloading of the resolc.js file (fix issue with relative path in web worker)
## v0.1.0-dev.8 ## v0.1.0-dev.8
@@ -63,15 +133,18 @@ This is a development pre-release.
This is a development pre-release. This is a development pre-release.
### Added ### Added
- The `revive-llvm-builder` crate with the `revive-llvm` helper utility for streamlined management of the LLVM framework dependency. - The `revive-llvm-builder` crate with the `revive-llvm` helper utility for streamlined management of the LLVM framework dependency.
- Initial support for running `resolc` in the browser. - Initial support for running `resolc` in the browser.
### Changed ### Changed
- Suported contracts runtime is polkadot-sdk git version `d62a90c8c729acd98c7e9a5cab9803b8b211ffc5`. - Suported contracts runtime is polkadot-sdk git version `d62a90c8c729acd98c7e9a5cab9803b8b211ffc5`.
- The minimum supported Rust version is `1.81.0`. - The minimum supported Rust version is `1.81.0`.
- Error out early instead of invoking `solc` with invalid base or include path flags. - Error out early instead of invoking `solc` with invalid base or include path flags.
### Fixed ### Fixed
- Decouple the LLVM target dependency from the LLVM host dependency. - Decouple the LLVM target dependency from the LLVM host dependency.
- Do not error out if no files and no errors were produced. This aligns resolc closer to solc. - Do not error out if no files and no errors were produced. This aligns resolc closer to solc.
- Fixes input normalization in the Wasm version. - Fixes input normalization in the Wasm version.
@@ -81,26 +154,30 @@ This is a development pre-release.
This is a development pre-release. This is a development pre-release.
### Added ### Added
- Implement the `GASPRICE` opcode. - Implement the `GASPRICE` opcode.
- Implement the `BASEFEE` opcode. - Implement the `BASEFEE` opcode.
- Implement the `GASLIMIT` opcode. - Implement the `GASLIMIT` opcode.
### Changed ### Changed
- The `GAS` opcode now returns the remaining `ref_time`. - The `GAS` opcode now returns the remaining `ref_time`.
- Contracts can now be supplied call data input of arbitrary size. - Contracts can now be supplied call data input of arbitrary size.
- Some syscalls now return the value in a register, slightly improving emitted contract code. - Some syscalls now return the value in a register, slightly improving emitted contract code.
- Calls forward maximum weight limits instead of 0, anticipating a change in polkadot-sdk where weight limits of 0 no longer interprets as uncapped limit. - Calls forward maximum weight limits instead of 0, anticipating a change in polkadot-sdk where weight limits of 0 no longer interprets as uncapped limit.
### Fixed ### Fixed
- A linker bug which was preventing certain contracts from linking with the PVM linker.
- A linker bug which was preventing certain contracts from linking with the PVM linker.
- JS: Fix encoding conversion from JS string (UTF-16) to UTF-8. - JS: Fix encoding conversion from JS string (UTF-16) to UTF-8.
- The git commit hash slug is always displayed in the version string. - The git commit hash slug is always displayed in the version string.
## v0.1.0-dev.6 ## v0.1.0-dev.6
This is a development pre-release. This is a development pre-release.
# Added # Added
- Implement the `BLOCKHASH` opcode. - Implement the `BLOCKHASH` opcode.
- Implement delegate calls. - Implement delegate calls.
- Implement the `GASPRICE` opcode. Currently hard-coded to return `1`. - Implement the `GASPRICE` opcode. Currently hard-coded to return `1`.
@@ -108,21 +185,24 @@ This is a development pre-release.
- Initial support for emitting debug info (opt in via the `-g` flag) - Initial support for emitting debug info (opt in via the `-g` flag)
# Changed # Changed
- resolc now emits 64bit PolkaVM blobs, reducing contract code size and execution time. - resolc now emits 64bit PolkaVM blobs, reducing contract code size and execution time.
- The RISC-V bit-manipulation target feature (`zbb`) is enabled. - The RISC-V bit-manipulation target feature (`zbb`) is enabled.
# Fixed # Fixed
- Compilation to Wasm (for usage in node and web browsers)
- Compilation to Wasm (for usage in node and web browsers)
## v0.1.0-dev.5 ## v0.1.0-dev.5
This is development pre-release. This is development pre-release.
# Added # Added
- Implement the `CODESIZE` and `EXTCODESIZE` opcodes. - Implement the `CODESIZE` and `EXTCODESIZE` opcodes.
# Changed # Changed
- Include the full revive version in the contract metadata. - Include the full revive version in the contract metadata.
# Fixed # Fixed
@@ -132,9 +212,11 @@ This is development pre-release.
This is development pre-release. This is development pre-release.
# Added # Added
- Support the `ORIGIN` opcode. - Support the `ORIGIN` opcode.
# Changed # Changed
- Update polkavm to `v0.14.0`. - Update polkavm to `v0.14.0`.
- Enable the `a`, `fast-unaligned-access` and `xtheadcondmov` LLVM target features, decreasing the code size for some contracts. - Enable the `a`, `fast-unaligned-access` and `xtheadcondmov` LLVM target features, decreasing the code size for some contracts.
Generated
+647 -722
View File
File diff suppressed because it is too large Load Diff
+16 -15
View File
@@ -3,7 +3,7 @@ resolver = "2"
members = ["crates/*"] members = ["crates/*"]
[workspace.package] [workspace.package]
version = "0.1.0-dev.11" version = "0.1.0-dev.14"
authors = [ authors = [
"Cyrill Leutwiler <cyrill@parity.io>", "Cyrill Leutwiler <cyrill@parity.io>",
"Parity Technologies <admin@parity.io>", "Parity Technologies <admin@parity.io>",
@@ -14,19 +14,20 @@ repository = "https://github.com/paritytech/revive"
rust-version = "1.81.0" rust-version = "1.81.0"
[workspace.dependencies] [workspace.dependencies]
revive-benchmarks = { version = "0.1.0-dev.11", path = "crates/benchmarks" } revive-benchmarks = { version = "0.1.0-dev.14", path = "crates/benchmarks" }
revive-builtins = { version = "0.1.0-dev.11", path = "crates/builtins" } revive-builtins = { version = "0.1.0-dev.14", path = "crates/builtins" }
revive-common = { version = "0.1.0-dev.11", path = "crates/common" } revive-common = { version = "0.1.0-dev.14", path = "crates/common" }
revive-differential = { version = "0.1.0-dev.11", path = "crates/differential" } revive-differential = { version = "0.1.0-dev.14", path = "crates/differential" }
revive-integration = { version = "0.1.0-dev.11", path = "crates/integration" } revive-integration = { version = "0.1.0-dev.14", path = "crates/integration" }
revive-linker = { version = "0.1.0-dev.11", path = "crates/linker" } revive-linker = { version = "0.1.0-dev.14", path = "crates/linker" }
lld-sys = { version = "0.1.0-dev.11", path = "crates/lld-sys" } lld-sys = { version = "0.1.0-dev.14", path = "crates/lld-sys" }
revive-llvm-context = { version = "0.1.0-dev.11", path = "crates/llvm-context" } revive-llvm-context = { version = "0.1.0-dev.14", path = "crates/llvm-context" }
revive-runtime-api = { version = "0.1.0-dev.11", path = "crates/runtime-api" } revive-runtime-api = { version = "0.1.0-dev.14", path = "crates/runtime-api" }
revive-runner = { version = "0.1.0-dev.11", path = "crates/runner" } revive-runner = { version = "0.1.0-dev.14", path = "crates/runner" }
revive-solidity = { version = "0.1.0-dev.11", path = "crates/solidity" } revive-solc-json-interface = { version = "0.1.0-dev.14", path = "crates/solc-json-interface" }
revive-stdlib = { version = "0.1.0-dev.11", path = "crates/stdlib" } revive-solidity = { version = "0.1.0-dev.14", path = "crates/solidity" }
revive-build-utils = { version = "0.1.0-dev.11", path = "crates/build-utils" } revive-stdlib = { version = "0.1.0-dev.14", path = "crates/stdlib" }
revive-build-utils = { version = "0.1.0-dev.14", path = "crates/build-utils" }
hex = "0.4.3" hex = "0.4.3"
cc = "1.2" cc = "1.2"
@@ -72,7 +73,7 @@ assert_fs = "1.1"
# polkadot-sdk and friends # polkadot-sdk and friends
codec = { version = "3.6.12", default-features = false, package = "parity-scale-codec" } codec = { version = "3.6.12", default-features = false, package = "parity-scale-codec" }
scale-info = { version = "2.11.6", default-features = false } scale-info = { version = "2.11.6", default-features = false }
polkadot-sdk = { git = "https://github.com/paritytech/polkadot-sdk", rev = "274a781e8ca1a9432c7ec87593bd93214abbff50" } polkadot-sdk = { git = "https://github.com/paritytech/polkadot-sdk", rev = "c29e72a8628835e34deb6aa7db9a78a2e4eabcee" }
# llvm # llvm
[workspace.dependencies.inkwell] [workspace.dependencies.inkwell]
+1 -1
View File
@@ -11,7 +11,7 @@ RUN make install-llvm-builder
RUN revive-llvm --target-env musl clone RUN revive-llvm --target-env musl clone
RUN revive-llvm --target-env musl build --llvm-projects lld --llvm-projects clang RUN revive-llvm --target-env musl build --llvm-projects lld --llvm-projects clang
FROM messense/rust-musl-cross:x86_64-musl AS resolc-builder FROM messense/rust-musl-cross@sha256:68b86bc7cb2867259e6b233415a665ff4469c28b57763e78c3bfea1c68091561 AS resolc-builder
WORKDIR /opt/revive WORKDIR /opt/revive
RUN apt update && \ RUN apt update && \
+6 -2
View File
@@ -5,6 +5,7 @@
install-wasm \ install-wasm \
install-llvm-builder \ install-llvm-builder \
install-llvm \ install-llvm \
install-revive-runner \
format \ format \
clippy \ clippy \
machete \ machete \
@@ -23,7 +24,7 @@
install: install-bin install-npm install: install-bin install-npm
install-bin: install-bin:
cargo install --path crates/solidity cargo install --locked --path crates/solidity
install-npm: install-npm:
npm install && npm fund npm install && npm fund
@@ -39,6 +40,9 @@ install-llvm: install-llvm-builder
revive-llvm clone revive-llvm clone
revive-llvm build --llvm-projects lld --llvm-projects clang revive-llvm build --llvm-projects lld --llvm-projects clang
install-revive-runner:
cargo install --path crates/runner --no-default-features --locked
format: format:
cargo fmt --all --check cargo fmt --all --check
@@ -49,7 +53,7 @@ machete:
cargo install cargo-machete cargo install cargo-machete
cargo machete cargo machete
test: format clippy machete test-cli test-workspace test: format clippy machete test-cli test-workspace install-revive-runner
test-integration: install-bin test-integration: install-bin
cargo test --package revive-integration cargo test --package revive-integration
+8 -4
View File
@@ -1,4 +1,4 @@
![CI](https://github.com/paritytech/revive/actions/workflows/rust.yml/badge.svg) ![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/Docs-contracts.polkadot.io-brightgreen.svg)](https://contracts.polkadot.io/revive_compiler/)
# revive # revive
@@ -14,6 +14,7 @@ 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). 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 ## Installation
Please consult [the documentation](https://contracts.polkadot.io/revive_compiler/installation) for installation instructions. Please consult [the documentation](https://contracts.polkadot.io/revive_compiler/installation) for installation instructions.
## Building from source ## Building from source
@@ -30,11 +31,13 @@ Building revive requires a [stable Rust installation](https://rustup.rs/) and a
Download the [latest LLVM build](https://github.com/paritytech/revive/releases?q=LLVM+binaries+release&expanded=true) from our releases. Download the [latest LLVM build](https://github.com/paritytech/revive/releases?q=LLVM+binaries+release&expanded=true) from our releases.
> **MacOS** users need to clear the `downloaded` attribute from all binaries after extracting the archive: > **MacOS** users need to clear the `downloaded` attribute from all binaries after extracting the archive:
>
> ```sh > ```sh
> xattr -rc </path/to/the/extracted/archive>/target-llvm/gnu/target-final/bin/* > xattr -rc </path/to/the/extracted/archive>/target-llvm/gnu/target-final/bin/*
> ``` > ```
After extracting the archive, point `$LLVM_SYS_181_PREFIX` to it: After extracting the archive, point `$LLVM_SYS_181_PREFIX` to it:
```sh ```sh
export LLVM_SYS_181_PREFIX=</path/to/the/extracted/archive>/target-llvm/gnu/target-final export LLVM_SYS_181_PREFIX=</path/to/the/extracted/archive>/target-llvm/gnu/target-final
``` ```
@@ -44,7 +47,7 @@ export LLVM_SYS_181_PREFIX=</path/to/the/extracted/archive>/target-llvm/gnu/targ
<details> <details>
<summary>Building from source</summary> <summary>Building from source</summary>
Use the provided [revive-llvm](crates/llvm-builder/README.md) utility to compile a compatible LLVM build locally and point `$LLVM_SYS_181_PREFIX` to the installation afterwards. Use the provided [revive-llvm](crates/llvm-builder/README.md) utility to compile a compatible LLVM build locally and point `$LLVM_SYS_181_PREFIX` to the installation afterwards.
The `Makefile` provides a shortcut target to obtain a compatible LLVM build: The `Makefile` provides a shortcut target to obtain a compatible LLVM build:
@@ -93,11 +96,12 @@ make test-wasm
## Development ## Development
Please consult the [Makefile](Makefile) targets to learn how to run tests and benchmarks. 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. Ensure that your branch passes `make test` locally when submitting a pull request.
### Design overview ### Design overview
See the [relevant section in our documentation](https://contracts.polkadot.io/revive_compiler/architecture) to learn more about how the compiler works.
See the [relevant section in our documentation](https://contracts.polkadot.io/revive_compiler/architecture) to learn more about how the compiler works.
[Frontend](https://github.com/matter-labs/era-compiler-solidity) and [code generator](https://github.com/matter-labs/era-compiler-llvm-context) are based of ZKSync `zksolc` (the project started as a fork of the era compiler). [Frontend](https://github.com/matter-labs/era-compiler-solidity) and [code generator](https://github.com/matter-labs/era-compiler-llvm-context) are based of ZKSync `zksolc` (the project started as a fork of the era compiler).
+9 -5
View File
@@ -4,14 +4,18 @@ Prior to the first stable release we neither have formal release processes nor d
To create a new pre-release: To create a new pre-release:
1. Merge a release PR which updates the `-dev.X` versions in the workspace `Cargo.toml` and updates the `CHANGELOG.md` accordingly. The release workflow will attempt to build and publish a new release whenever the latest git tag does not match the cargo package version. 1. Create a release PR which updates the `-dev.X` versions in the workspace `Cargo.toml` and updates the `CHANGELOG.md` accordingly.
2. Wait for the `Release` workflow to finish. If the workflow fails after the `build-linux-all` step, check if a tag has been created and delete it before restarting or pushing updates. Note: It's more convenient to debug the release workflow in a fork (the fork has to be under the `paritytech` org to access `parity-large` runners). 2. If the CI passes, merge the release PR.
3. Check draft release on [Releases page](https://github.com/paritytech/revive/releases) and publish (should contain `resolc.js`, `resolc.wasm`, `resolc-web.js`, and `resolc-static-linux` release assets) 3. Push a tag that has the same `-dev.X` version as in `Cargo.toml`
4. Update the [contract-docs](https://github.com/paritytech/contract-docs/) accordingly 4. The release workflow will attempt to build and publish a new pre-release if the latest tag does match the cargo package version.
5. Wait for the `Release` workflow to finish. It should create the pre-release with the same `-dev.X` name.
6. Check that pre-release was created on the [Releases page](https://github.com/paritytech/revive/releases) with all artifacts.
7. After the release is published, another workflow should start automatically and update json files in https://github.com/paritytech/resolc-bin. Check the changes.
8. Update the [contract-docs](https://github.com/paritytech/contract-docs/) accordingly
# LLVM release # LLVM release
To create a new LLVM release, run "Release LLVM" workflow. Use current LLVM version as parameter, e.g. `18.1.8`. To create a new LLVM release, run "Release LLVM" workflow. Use current LLVM version as parameter, e.g. `18.1.8`.
Version suffix will be resolved automatically. Version suffix will be resolved automatically.
The workflows will create new GitHub release, and upload LLVM binaries. The workflows will create new GitHub release, and upload LLVM binaries.
Next release of resolc will use newly created binaries. Next release of resolc will use newly created binaries.
+1 -1
View File
@@ -1 +1 @@
https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/SECURITY.md https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/SECURITY.md
+42 -42
View File
@@ -3,71 +3,71 @@
## Table of Contents ## Table of Contents
- [Benchmark Results](#benchmark-results) - [Benchmark Results](#benchmark-results)
- [Baseline](#baseline) - [Baseline](#baseline)
- [OddPorduct](#oddporduct) - [OddPorduct](#oddporduct)
- [TriangleNumber](#trianglenumber) - [TriangleNumber](#trianglenumber)
- [FibonacciRecursive](#fibonaccirecursive) - [FibonacciRecursive](#fibonaccirecursive)
- [FibonacciIterative](#fibonacciiterative) - [FibonacciIterative](#fibonacciiterative)
- [FibonacciBinet](#fibonaccibinet) - [FibonacciBinet](#fibonaccibinet)
- [SHA1](#sha1) - [SHA1](#sha1)
## Benchmark Results ## Benchmark Results
### Baseline ### Baseline
| | `EVM` | `PVMInterpreter` | | | `EVM` | `PVMInterpreter` |
|:--------|:------------------------|:-------------------------------- | | :------ | :------------------------ | :------------------------------- |
| **`0`** | `3.36 us` (✅ **1.00x**) | `11.84 us` (*3.52x slower*) | | **`0`** | `10.08 us` (✅ **1.00x**) | `10.32 us` (**1.02x slower**) |
### OddPorduct ### OddPorduct
| | `EVM` | `PVMInterpreter` | | | `EVM` | `PVMInterpreter` |
|:-------------|:-------------------------|:-------------------------------- | | :----------- | :------------------------- | :------------------------------- |
| **`10000`** | `3.11 ms` (✅ **1.00x**) | `1.53 ms` (🚀 **2.03x faster**) | | **`10000`** | `3.60 ms` (✅ **1.00x**) | `1.57 ms` (🚀 **2.28x faster**) |
| **`100000`** | `30.70 ms` (✅ **1.00x**) | `15.54 ms` (🚀 **1.98x faster**) | | **`100000`** | `34.72 ms` (✅ **1.00x**) | `14.82 ms` (🚀 **2.34x faster**) |
| **`300000`** | `92.68 ms` (✅ **1.00x**) | `45.47 ms` (🚀 **2.04x faster**) | | **`300000`** | `105.01 ms` (✅ **1.00x**) | `44.11 ms` (🚀 **2.38x faster**) |
### TriangleNumber ### TriangleNumber
| | `EVM` | `PVMInterpreter` | | | `EVM` | `PVMInterpreter` |
|:-------------|:-------------------------|:-------------------------------- | | :----------- | :------------------------ | :------------------------------- |
| **`10000`** | `2.29 ms` (✅ **1.00x**) | `1.09 ms` (🚀 **2.11x faster**) | | **`10000`** | `2.43 ms` (✅ **1.00x**) | `1.12 ms` (🚀 **2.17x faster**) |
| **`100000`** | `22.84 ms` (✅ **1.00x**) | `10.66 ms` (🚀 **2.14x faster**) | | **`100000`** | `24.20 ms` (✅ **1.00x**) | `10.86 ms` (🚀 **2.23x faster**) |
| **`360000`** | `82.29 ms` (✅ **1.00x**) | `37.01 ms` (🚀 **2.22x faster**) | | **`360000`** | `88.69 ms` (✅ **1.00x**) | `38.46 ms` (🚀 **2.31x faster**) |
### FibonacciRecursive ### FibonacciRecursive
| | `EVM` | `PVMInterpreter` | | | `EVM` | `PVMInterpreter` |
|:---------|:--------------------------|:--------------------------------- | | :------- | :------------------------- | :-------------------------------- |
| **`12`** | `135.67 us` (✅ **1.00x**) | `125.02 us` (✅ **1.09x faster**) | | **`12`** | `144.17 us` (✅ **1.00x**) | `150.85 us` (✅ **1.05x slower**) |
| **`16`** | `903.75 us` (✅ **1.00x**) | `762.79 us` (✅ **1.18x faster**) | | **`16`** | `938.71 us` (✅ **1.00x**) | `922.11 us` (✅ **1.02x faster**) |
| **`20`** | `6.12 ms` (✅ **1.00x**) | `4.96 ms` (✅ **1.23x faster**) | | **`20`** | `6.54 ms` (✅ **1.00x**) | `6.20 ms` (✅ **1.05x faster**) |
| **`24`** | `42.05 ms` (✅ **1.00x**) | `33.86 ms` (✅ **1.24x faster**) | | **`24`** | `45.73 ms` (✅ **1.00x**) | `41.98 ms` (✅ **1.09x faster**) |
### FibonacciIterative ### FibonacciIterative
| | `EVM` | `PVMInterpreter` | | | `EVM` | `PVMInterpreter` |
|:----------|:-------------------------|:-------------------------------- | | :-------- | :------------------------ | :------------------------------- |
| **`64`** | `15.04 us` (✅ **1.00x**) | `29.45 us` (❌ *1.96x slower*) | | **`64`** | `23.00 us` (✅ **1.00x**) | `31.88 us` (❌ _1.39x slower_) |
| **`128`** | `26.36 us` (✅ **1.00x**) | `42.19 us` (❌ *1.60x slower*) | | **`128`** | `35.28 us` (✅ **1.00x**) | `42.43 us` (❌ _1.20x slower_) |
| **`256`** | `48.61 us` (✅ **1.00x**) | `65.71 us` (*1.35x slower*) | | **`256`** | `60.12 us` (✅ **1.00x**) | `61.20 us` (**1.02x slower**) |
### FibonacciBinet ### FibonacciBinet
| | `EVM` | `PVMInterpreter` | | | `EVM` | `PVMInterpreter` |
|:----------|:-------------------------|:-------------------------------- | | :-------- | :------------------------ | :----------------------------- |
| **`64`** | `15.22 us` (✅ **1.00x**) | `41.46 us` (❌ *2.72x slower*) | | **`64`** | `23.01 us` (✅ **1.00x**) | `47.74 us` (❌ _2.07x slower_) |
| **`128`** | `17.05 us` (✅ **1.00x**) | `42.84 us` (❌ *2.51x slower*) | | **`128`** | `25.44 us` (✅ **1.00x**) | `49.67 us` (❌ _1.95x slower_) |
| **`256`** | `19.00 us` (✅ **1.00x**) | `44.36 us` (❌ *2.34x slower*) | | **`256`** | `28.66 us` (✅ **1.00x**) | `53.01 us` (❌ _1.85x slower_) |
### SHA1 ### SHA1
| | `EVM` | `PVMInterpreter` | | | `EVM` | `PVMInterpreter` |
|:----------|:--------------------------|:--------------------------------- | | :-------- | :------------------------- | :------------------------------ |
| **`1`** | `110.04 us` (✅ **1.00x**) | `216.11 us` (❌ *1.96x slower*) | | **`1`** | `135.87 us` (✅ **1.00x**) | `243.75 us` (❌ _1.79x slower_) |
| **`64`** | `209.04 us` (✅ **1.00x**) | `309.48 us` (❌ *1.48x slower*) | | **`64`** | `258.45 us` (✅ **1.00x**) | `355.70 us` (❌ _1.38x slower_) |
| **`512`** | `903.65 us` (✅ **1.00x**) | `980.49 us` (✅ **1.09x slower**) | | **`512`** | `1.10 ms` (✅ **1.00x**) | `1.09 ms` (✅ **1.01x faster**) |
--- ---
Made with [criterion-table](https://github.com/nu11ptr/criterion-table)
Made with [criterion-table](https://github.com/nu11ptr/criterion-table)
+45 -45
View File
@@ -1,47 +1,47 @@
{ {
"config": { "config": {
"chainId": 420420420, "chainId": 420420420,
"homesteadBlock": 0, "homesteadBlock": 0,
"eip150Block": 0, "eip150Block": 0,
"eip155Block": 0, "eip155Block": 0,
"eip158Block": 0, "eip158Block": 0,
"byzantiumBlock": 0, "byzantiumBlock": 0,
"constantinopleBlock": 0, "constantinopleBlock": 0,
"petersburgBlock": 0, "petersburgBlock": 0,
"istanbulBlock": 0, "istanbulBlock": 0,
"berlinBlock": 0, "berlinBlock": 0,
"londonBlock": 0, "londonBlock": 0,
"arrowGlacierBlock": 0, "arrowGlacierBlock": 0,
"grayGlacierBlock": 0, "grayGlacierBlock": 0,
"shanghaiTime": 0, "shanghaiTime": 0,
"cancunTime": 0, "cancunTime": 0,
"terminalTotalDifficulty": 0, "terminalTotalDifficulty": 0,
"terminalTotalDifficultyPassed": true, "terminalTotalDifficultyPassed": true,
"blobSchedule": { "blobSchedule": {
"cancun": { "cancun": {
"target": 3, "target": 3,
"max": 6, "max": 6,
"baseFeeUpdateFraction": 3338477 "baseFeeUpdateFraction": 3338477
} }
}
},
"coinbase": "0xffffffffffffffffffffffffffffffffffffffff",
"difficulty": "0x20000",
"extraData": "",
"gasLimit": "0xffffffff",
"nonce": "0x0000000000000042",
"mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"timestamp": "0x00",
"alloc": {
"0101010101010101010101010101010101010101": {
"balance": "1000000000"
},
"0202020202020202020202020202020202020202": {
"balance": "1000000000"
},
"0303030303030303030303030303030303030303": {
"balance": "1000000000"
}
} }
} },
"coinbase": "0xffffffffffffffffffffffffffffffffffffffff",
"difficulty": "0x20000",
"extraData": "",
"gasLimit": "0xffffffff",
"nonce": "0x0000000000000042",
"mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"timestamp": "0x00",
"alloc": {
"0101010101010101010101010101010101010101": {
"balance": "1000000000"
},
"0202020202020202020202020202020202020202": {
"balance": "1000000000"
},
"0303030303030303030303030303030303030303": {
"balance": "1000000000"
}
}
}
+1
View File
@@ -15,6 +15,7 @@ serde_json = { workspace = true }
revive-solidity = { workspace = true } revive-solidity = { workspace = true }
revive-runner = { workspace = true } revive-runner = { workspace = true }
revive-llvm-context = { workspace = true }
[dev-dependencies] [dev-dependencies]
sha1 = { workspace = true } sha1 = { workspace = true }
+9 -9
View File
@@ -1,10 +1,10 @@
{ {
"Baseline": 1110, "Baseline": 939,
"Computation": 2389, "Computation": 2282,
"DivisionArithmetics": 14822, "DivisionArithmetics": 8849,
"ERC20": 23973, "ERC20": 18308,
"Events": 1605, "Events": 1640,
"FibonacciIterative": 2023, "FibonacciIterative": 1497,
"Flipper": 1989, "Flipper": 2099,
"SHA1": 17026 "SHA1": 8243
} }
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
contract Predicted {
uint public salt;
constructor(uint _salt) {
salt = _salt;
}
}
contract AddressPredictor {
constructor(uint _salt, bytes memory _bytecode) payable {
address deployed = address(new Predicted{salt: bytes32(_salt)}(_salt));
address predicted = predictAddress(_salt, _bytecode);
assert(deployed == predicted);
}
function predictAddress(
uint _foo,
bytes memory _bytecode
) public view returns (address predicted) {
bytes32 addr = keccak256(
abi.encodePacked(
bytes1(0xff),
address(this),
bytes32(_foo),
keccak256(abi.encodePacked(_bytecode, abi.encode(_foo)))
)
);
predicted = address(uint160(uint(addr)));
}
}
@@ -0,0 +1,38 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
/* runner.json
{
"differential": true,
"actions": [
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "DelegateCaller"
}
}
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "e466c6c9"
}
}
]
}
*/
contract DelegateCaller {
function delegateNoContract() external returns (bool) {
address testAddress = 0x0000000000000000000000000000000000000000;
(bool success, ) = testAddress.delegatecall(
abi.encodeWithSignature("test()")
);
return success;
}
}
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
/* runner.json
{
"differential": true,
"actions": [
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "FunctionType"
}
}
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "b8c9d365"
}
}
]
}
*/
contract FunctionType {
uint public immutable x = 42;
function h() public view returns (function() external view returns (uint)) {
return this.x;
}
}
+52
View File
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.29;
/* runner.json
{
"differential": true,
"actions": [
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "LayoutAt"
}
}
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "a7a0d537"
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "15393349"
}
}
]
}
*/
contract LayoutAt layout at 0xDEADBEEF + 0xCAFEBABE {
uint[3] public something;
constructor() payable {
something[0] = 1337;
something[1] = 42;
something[2] = 69;
}
function slotOfSomething() public pure returns (uint ret) {
assembly {
ret := something.slot
}
}
}
@@ -0,0 +1,51 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"differential": true,
"actions": [
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "MCopyOverlap"
}
}
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "afdce848"
}
}
]
}
*/
function copy(
uint dstOffset,
uint srcOffset,
uint length
) pure returns (bytes memory out) {
out = hex"2222222222222222333333333333333344444444444444445555555555555555"
hex"6666666666666666777777777777777788888888888888889999999999999999"
hex"aaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbccccccccccccccccdddddddddddddddd";
assembly {
mcopy(
add(add(out, 0x20), dstOffset),
add(add(out, 0x20), srcOffset),
length
)
}
}
contract MCopyOverlap {
function mcopy_to_right_overlap() public pure returns (bytes memory) {
return copy(0x20, 0x10, 0x30);
}
}
+1 -1
View File
@@ -15,7 +15,7 @@ pragma solidity ^0.8;
"Instantiated": 0 "Instantiated": 0
}, },
"key": "0000000000000000000000000000000000000000000000000000000000000000", "key": "0000000000000000000000000000000000000000000000000000000000000000",
"expected": "0100000000000000000000000000000000000000000000000000000000000000" "expected": "0000000000000000000000000000000000000000000000000000000000000001"
} }
}, },
{ {
+63 -10
View File
@@ -1,6 +1,7 @@
use alloy_primitives::{Address, Bytes, I256, U256}; use alloy_primitives::{Address, Bytes, I256, U256};
use alloy_sol_types::{sol, SolCall, SolConstructor}; use alloy_sol_types::{sol, SolCall, SolConstructor};
use revive_llvm_context::OptimizerSettings;
use revive_solidity::test_utils::*; use revive_solidity::test_utils::*;
#[derive(Clone)] #[derive(Clone)]
@@ -249,8 +250,19 @@ sol!(
); );
case!("Storage.sol", Storage, transientCall, storage_transient, value: U256); case!("Storage.sol", Storage, transientCall, storage_transient, value: U256);
sol!(
contract Predicted {
constructor(uint _foo);
}
contract AddressPredictor {
constructor(uint _foo, bytes memory _bytecode) payable;
}
);
case!("AddressPredictor.sol", Predicted, constructorCall, predicted_constructor, salt: U256);
case!("AddressPredictor.sol", AddressPredictor, constructorCall, address_predictor_constructor, salt: U256, bytecode: Bytes);
impl Contract { impl Contract {
fn build(calldata: Vec<u8>, name: &'static str, code: &str) -> Self { pub fn build(calldata: Vec<u8>, name: &'static str, code: &str) -> Self {
Self { Self {
name, name,
evm_runtime: compile_evm_bin_runtime(name, code), evm_runtime: compile_evm_bin_runtime(name, code),
@@ -258,11 +270,19 @@ impl Contract {
calldata, 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)] #[cfg(test)]
mod tests { mod tests {
use alloy_primitives::{Bytes, U256};
use rayon::iter::{IntoParallelIterator, ParallelIterator}; use rayon::iter::{IntoParallelIterator, ParallelIterator};
use serde::{de::Deserialize, Serialize}; use serde::{de::Deserialize, Serialize};
use std::{collections::BTreeMap, fs::File}; use std::{collections::BTreeMap, fs::File};
@@ -302,14 +322,47 @@ mod tests {
}; };
[ [
Contract::baseline as fn() -> Contract, (|| {
Contract::flipper as fn() -> Contract, Contract::build_size_opt(
(|| Contract::odd_product(0)) as fn() -> Contract, vec![],
(|| Contract::fib_iterative(U256::ZERO)) as fn() -> Contract, "Baseline",
Contract::erc20 as fn() -> Contract, include_str!("../contracts/Baseline.sol"),
(|| Contract::sha1(Bytes::new())) as fn() -> Contract, )
(|| Contract::division_arithmetics_div(U256::ZERO, U256::ZERO)) as fn() -> Contract, }) as _,
(|| Contract::event(U256::ZERO)) as fn() -> Contract, (|| {
Contract::build_size_opt(
vec![],
"Flipper",
include_str!("../contracts/flipper.sol"),
)
}) as _,
(|| {
Contract::build_size_opt(
vec![],
"Computation",
include_str!("../contracts/Computation.sol"),
)
}) as _,
(|| {
Contract::build_size_opt(
vec![],
"FibonacciIterative",
include_str!("../contracts/Fibonacci.sol"),
)
}) as _,
(|| Contract::build_size_opt(vec![], "ERC20", include_str!("../contracts/ERC20.sol")))
as _,
(|| Contract::build_size_opt(vec![], "SHA1", include_str!("../contracts/SHA1.sol")))
as _,
(|| {
Contract::build_size_opt(
vec![],
"DivisionArithmetics",
include_str!("../contracts/DivisionArithmetics.sol"),
)
}) as _,
(|| Contract::build_size_opt(vec![], "Events", include_str!("../contracts/Events.sol")))
as _,
] ]
.into_par_iter() .into_par_iter()
.map(extract_code_size) .map(extract_code_size)
+32
View File
@@ -33,6 +33,7 @@ test_spec!(msize, "MSize", "MSize.sol");
test_spec!(sha1, "SHA1", "SHA1.sol"); test_spec!(sha1, "SHA1", "SHA1.sol");
test_spec!(block, "Block", "Block.sol"); test_spec!(block, "Block", "Block.sol");
test_spec!(mcopy, "MCopy", "MCopy.sol"); test_spec!(mcopy, "MCopy", "MCopy.sol");
test_spec!(mcopy_overlap, "MCopyOverlap", "MCopyOverlap.sol");
test_spec!(events, "Events", "Events.sol"); test_spec!(events, "Events", "Events.sol");
test_spec!(storage, "Storage", "Storage.sol"); test_spec!(storage, "Storage", "Storage.sol");
test_spec!(mstore8, "MStore8", "MStore8.sol"); test_spec!(mstore8, "MStore8", "MStore8.sol");
@@ -56,6 +57,9 @@ test_spec!(transfer, "Transfer", "Transfer.sol");
test_spec!(send, "Send", "Send.sol"); test_spec!(send, "Send", "Send.sol");
test_spec!(function_pointer, "FunctionPointer", "FunctionPointer.sol"); test_spec!(function_pointer, "FunctionPointer", "FunctionPointer.sol");
test_spec!(mload, "MLoad", "MLoad.sol"); test_spec!(mload, "MLoad", "MLoad.sol");
test_spec!(delegate_no_contract, "DelegateCaller", "DelegateCaller.sol");
test_spec!(function_type, "FunctionType", "FunctionType.sol");
test_spec!(layout_at, "LayoutAt", "LayoutAt.sol");
fn instantiate(path: &str, contract: &str) -> Vec<SpecsAction> { fn instantiate(path: &str, contract: &str) -> Vec<SpecsAction> {
vec![Instantiate { vec![Instantiate {
@@ -482,3 +486,31 @@ fn transfer_denies_reentrancy() {
} }
.run(); .run();
} }
#[test]
fn create2_salt() {
let salt = U256::from(777);
let predicted = Contract::predicted_constructor(salt).pvm_runtime;
let predictor = Contract::address_predictor_constructor(salt, predicted.clone().into());
Specs {
actions: vec![
Upload {
origin: TestAddress::Alice,
code: Code::Bytes(predicted),
storage_deposit_limit: None,
},
Instantiate {
origin: TestAddress::Alice,
value: 0,
gas_limit: Some(GAS_LIMIT),
storage_deposit_limit: None,
code: Code::Bytes(predictor.pvm_runtime),
data: predictor.calldata,
salt: OptionalHex::default(),
},
],
differential: false,
..Default::default()
}
.run();
}
-1
View File
@@ -52,7 +52,6 @@ pub fn link<T: AsRef<[u8]>>(input: T) -> anyhow::Result<Vec<u8>> {
let ld_args = [ let ld_args = [
"ld.lld", "ld.lld",
"--lto=full",
"--error-limit=0", "--error-limit=0",
"--relocatable", "--relocatable",
"--emit-relocs", "--emit-relocs",
+1 -1
View File
@@ -96,7 +96,7 @@ fn main() {
revive_build_utils::llvm_cxx_flags() revive_build_utils::llvm_cxx_flags()
.split_whitespace() .split_whitespace()
.fold(&mut cc::Build::new(), |builder, flag| builder.flag(flag)) .fold(&mut cc::Build::new(), |builder, flag| builder.flag(flag))
.flag("-Wno-unused-parameter") .warnings(false)
.cpp(true) .cpp(true)
.file("src/linker.cpp") .file("src/linker.cpp")
.compile("liblinker.a"); .compile("liblinker.a");
+1
View File
@@ -32,6 +32,7 @@ tar = { workspace = true }
flate2 = { workspace = true } flate2 = { workspace = true }
env_logger = { workspace = true } env_logger = { workspace = true }
log = { workspace = true } log = { workspace = true }
which = { workspace = true }
[dev-dependencies] [dev-dependencies]
assert_cmd = { workspace = true } assert_cmd = { workspace = true }
+64 -55
View File
@@ -6,6 +6,7 @@ Parity fork of the [Matter Labs zksync LLVM builder](https://github.com/matter-l
The LLVM compiler framework for revive must be built with our tool called `revive-llvm`. The LLVM compiler framework for revive must be built with our tool called `revive-llvm`.
This is because the revive compiler has requirements not fullfilled in upstream builds: This is because the revive compiler has requirements not fullfilled in upstream builds:
- Special builds for compiling the frontend into statically linked ELF binaries and also Wasm executables - Special builds for compiling the frontend into statically linked ELF binaries and also Wasm executables
- The RISC-V target (the PolkaVM target) - The RISC-V target (the PolkaVM target)
- The compiler-rt builtins for the PolkaVM target - The compiler-rt builtins for the PolkaVM target
@@ -17,85 +18,92 @@ Obtain a compatible build for your host platform from the release section of thi
<details> <details>
<summary>1. Install the system prerequisites.</summary> <summary>1. Install the system prerequisites.</summary>
* Linux (Debian): - Linux (Debian):
Install the following packages: Install the following packages:
```shell
apt install cmake ninja-build curl git libssl-dev pkg-config clang lld
```
* Linux (Arch):
Install the following packages: ```shell
```shell apt install cmake ninja-build curl git libssl-dev pkg-config clang lld
pacman -Syu which cmake ninja curl git pkg-config clang lld ```
```
* MacOS: - Linux (Arch):
* Install the [HomeBrew](https://brew.sh) package manager. Install the following packages:
* Install the following packages:
```shell ```shell
brew install cmake ninja coreutils pacman -Syu which cmake ninja curl git pkg-config clang lld
``` ```
* Install your choice of a recent LLVM/[Clang](https://clang.llvm.org) compiler, e.g. via [Xcode](https://developer.apple.com/xcode/), [Apples Command Line Tools](https://developer.apple.com/library/archive/technotes/tn2339/_index.html), or your preferred package manager. - MacOS:
</details>
* Install the [HomeBrew](https://brew.sh) package manager.
* Install the following packages:
```shell
brew install cmake ninja coreutils
```
* Install your choice of a recent LLVM/[Clang](https://clang.llvm.org) compiler, e.g. via [Xcode](https://developer.apple.com/xcode/), [Apples Command Line Tools](https://developer.apple.com/library/archive/technotes/tn2339/_index.html), or your preferred package manager.
</details>
<details> <details>
<summary>2. Install Rust.</summary> <summary>2. Install Rust.</summary>
* Follow the latest [official instructions](https://www.rust-lang.org/tools/install: - Follow the latest [official instructions](https://www.rust-lang.org/tools/install:
```shell `shell
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
. ${HOME}/.cargo/env . ${HOME}/.cargo/env
``` `
> Currently we are not pinned to any specific version of Rust, so just install the latest stable build for your platform. > Currently we are not pinned to any specific version of Rust, so just install the latest stable build for your platform.
</details>
</details>
<details> <details>
<summary>3. Install the revive LLVM framework builder.</summary> <summary>3. Install the revive LLVM framework builder.</summary>
* Install the builder using `cargo`: - Install the builder using `cargo`:
```shell
cargo install --git https://github.com/paritytech/revive-llvm-builder --force --locked
```
> The builder is not the LLVM framework itself, but a tool that clones its repository and runs a sequence of build commands. By default it is installed in `~/.cargo/bin/`, which is recommended to be added to your `$PATH`. ```shell
cargo install --git https://github.com/paritytech/revive-llvm-builder --force --locked
```
> The builder is not the LLVM framework itself, but a tool that clones its repository and runs a sequence of build commands. By default it is installed in `~/.cargo/bin/`, which is recommended to be added to your `$PATH`.
</details> </details>
<details> <details>
<summary>4. (Optional) Create the `LLVM.lock` file.</summary> <summary>4. (Optional) Create the `LLVM.lock` file.</summary>
* The `LLVM.lock` dictates the LLVM source tree being used. - The `LLVM.lock` dictates the LLVM source tree being used.
A default `./LLVM.lock` pointing to the release used for development is already provided. A default `./LLVM.lock` pointing to the release used for development is already provided.
</details> </details>
<details> <details>
<summary>5. Build LLVM.</summary> <summary>5. Build LLVM.</summary>
* Clone and build the LLVM framework using the `revive-llvm` tool. - Clone and build the LLVM framework using the `revive-llvm` tool.
The clang and lld projects are required for the `resolc` Solidity frontend executable; they are enabled by default. LLVM assertions are also enabled by default. The clang and lld projects are required for the `resolc` Solidity frontend executable; they are enabled by default. LLVM assertions are also enabled by default.
```shell ```shell
revive-llvm clone revive-llvm clone
revive-llvm build --llvm-projects lld --llvm-projects clang revive-llvm build --llvm-projects lld --llvm-projects clang
``` ```
Build artifacts end up in the `./target-llvm/gnu/target-final/` directory by default. Build artifacts end up in the `./target-llvm/gnu/target-final/` directory by default.
The `gnu` directory depends on the supported archticture and will either be `gnu`, `musl` or `emscripten`. The `gnu` directory depends on the supported archticture and will either be `gnu`, `musl` or `emscripten`.
You now need to export the final target directory `$LLVM_SYS_181_PREFIX`: `export LLVM_SYS_181_PREFIX=${PWD}/target-llvm/gnu/target-final` You now need to export the final target directory `$LLVM_SYS_181_PREFIX`: `export LLVM_SYS_181_PREFIX=${PWD}/target-llvm/gnu/target-final`
If built with the `--enable-tests` option, test tools will be in the `./target-llvm/gnu/build-final/` directory, along with copies of the build artifacts. For all supported build options, run `revive-llvm build --help`. If built with the `--enable-tests` option, test tools will be in the `./target-llvm/gnu/build-final/` directory, along with copies of the build artifacts. For all supported build options, run `revive-llvm build --help`.
</details> </details>
## Supported target architectures ## Supported target architectures
The following target platforms are supported: The following target platforms are supported:
- Linux GNU (x86) - Linux GNU (x86)
- Linux MUSL (x86) - Linux MUSL (x86)
- MacOS (aarch64) - MacOS (aarch64)
@@ -105,28 +113,29 @@ The following target platforms are supported:
<details> <details>
<summary>Building for MUSL</summary> <summary>Building for MUSL</summary>
* Via a musl build we can build revive into fully static ELF binaries. - Via a musl build we can build revive into fully static ELF binaries.
Which is desirable for reproducible Solidity contracts builds. Which is desirable for reproducible Solidity contracts builds.
The resulting binary is also very portable, akin to the`solc` frontend binary distribution. The resulting binary is also very portable, akin to the`solc` frontend binary distribution.
Clone and build the LLVM framework using the `revive-llvm` tool: Clone and build the LLVM framework using the `revive-llvm` tool:
```shell
revive-llvm --target-env musl clone ```shell
revive-llvm --target-env musl build --enable-assertions --llvm-projects clang --llvm-projects lld revive-llvm --target-env musl clone
``` revive-llvm --target-env musl build --enable-assertions --llvm-projects clang --llvm-projects lld
```
</details> </details>
<details> <details>
<summary>Building for Emscripten</summary> <summary>Building for Emscripten</summary>
* Via an emsdk build we can run revive in the browser and on node.js. - Via an emsdk build we can run revive in the browser and on node.js.
Clone and build the LLVM framework using the `revive-llvm` tool: Clone and build the LLVM framework using the `revive-llvm` tool:
```shell
revive-llvm --target-env emscripten clone ```shell
revive-llvm --target-env emscripten build --enable-assertions --llvm-projects clang --llvm-projects lld revive-llvm --target-env emscripten clone
``` revive-llvm --target-env emscripten build --enable-assertions --llvm-projects clang --llvm-projects lld
```
</details> </details>
+41 -9
View File
@@ -1,5 +1,8 @@
//! Utilities for compiling the LLVM compiler-rt builtins. //! Utilities for compiling the LLVM compiler-rt builtins.
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. /// Static CFLAGS variable passed to the compiler building the compiler-rt builtins.
const C_FLAGS: [&str; 6] = [ const C_FLAGS: [&str; 6] = [
"--target=riscv64", "--target=riscv64",
@@ -44,24 +47,31 @@ fn cmake_dynamic_args(
let mut clang_path = llvm_target_host.to_path_buf(); let mut clang_path = llvm_target_host.to_path_buf();
clang_path.push("bin/clang"); clang_path.push("bin/clang");
clang_path.set_extension(EXE_EXTENSION);
let mut clangxx_path = llvm_target_host.to_path_buf(); let mut clangxx_path = llvm_target_host.to_path_buf();
clangxx_path.push("bin/clang++"); clangxx_path.push("bin/clang++");
clangxx_path.set_extension(EXE_EXTENSION);
let mut llvm_config_path = llvm_target_host.to_path_buf(); let mut llvm_config_path = llvm_target_host.to_path_buf();
llvm_config_path.push("bin/llvm-config"); llvm_config_path.push("bin/llvm-config");
llvm_config_path.set_extension(EXE_EXTENSION);
let mut ar_path = llvm_target_host.to_path_buf(); let mut ar_path = llvm_target_host.to_path_buf();
ar_path.push("bin/llvm-ar"); ar_path.push("bin/llvm-ar");
ar_path.set_extension(EXE_EXTENSION);
let mut nm_path = llvm_target_host.to_path_buf(); let mut nm_path = llvm_target_host.to_path_buf();
nm_path.push("bin/llvm-nm"); nm_path.push("bin/llvm-nm");
nm_path.set_extension(EXE_EXTENSION);
let mut ranlib_path = llvm_target_host.to_path_buf(); let mut ranlib_path = llvm_target_host.to_path_buf();
ranlib_path.push("bin/llvm-ranlib"); ranlib_path.push("bin/llvm-ranlib");
ranlib_path.set_extension(EXE_EXTENSION);
let mut linker_path = llvm_target_host.to_path_buf(); let mut linker_path = llvm_target_host.to_path_buf();
linker_path.push("bin/ld.lld"); linker_path.push("bin/ld.lld");
linker_path.set_extension(EXE_EXTENSION);
Ok([ Ok([
format!( format!(
@@ -76,12 +86,18 @@ fn cmake_dynamic_args(
format!("-DCMAKE_C_FLAGS='{}'", C_FLAGS.join(" ")), format!("-DCMAKE_C_FLAGS='{}'", C_FLAGS.join(" ")),
format!("-DCMAKE_ASM_FLAGS='{}'", C_FLAGS.join(" ")), format!("-DCMAKE_ASM_FLAGS='{}'", C_FLAGS.join(" ")),
format!("-DCMAKE_CXX_FLAGS='{}'", C_FLAGS.join(" ")), format!("-DCMAKE_CXX_FLAGS='{}'", C_FLAGS.join(" ")),
format!("-DCMAKE_C_COMPILER='{}'", clang_path.to_string_lossy()), format!(
format!("-DCMAKE_ASM_COMPILER='{}'", clang_path.to_string_lossy()), "-DCMAKE_C_COMPILER='{}'",
format!("-DCMAKE_CXX_COMPILER='{}'", clangxx_path.to_string_lossy()), to_unix(clang_path.clone())?.display()
format!("-DCMAKE_AR='{}'", ar_path.to_string_lossy()), ),
format!("-DCMAKE_NM='{}'", nm_path.to_string_lossy()), format!("-DCMAKE_ASM_COMPILER='{}'", to_unix(clang_path)?.display()),
format!("-DCMAKE_RANLIB='{}'", ranlib_path.to_string_lossy()), format!(
"-DCMAKE_CXX_COMPILER='{}'",
to_unix(clangxx_path)?.display()
),
format!("-DCMAKE_AR='{}'", to_unix(ar_path)?.display()),
format!("-DCMAKE_NM='{}'", to_unix(nm_path)?.display()),
format!("-DCMAKE_RANLIB='{}'", to_unix(ranlib_path)?.display()),
format!( format!(
"-DLLVM_CONFIG_PATH='{}'", "-DLLVM_CONFIG_PATH='{}'",
llvm_config_path.to_string_lossy() llvm_config_path.to_string_lossy()
@@ -101,7 +117,13 @@ pub fn build(
log::info!("building compiler-rt for rv64emac"); log::info!("building compiler-rt for rv64emac");
crate::utils::check_presence("cmake")?; crate::utils::check_presence("cmake")?;
crate::utils::check_presence("ninja")?;
let generator = if cfg!(target_os = "windows") {
"Visual Studio 17 2022"
} else {
crate::utils::check_presence("ninja")?;
"Ninja"
};
let llvm_module_compiler_rt = crate::LLVMPath::llvm_module_compiler_rt()?; let llvm_module_compiler_rt = crate::LLVMPath::llvm_module_compiler_rt()?;
let llvm_compiler_rt_build = crate::LLVMPath::llvm_build_compiler_rt()?; let llvm_compiler_rt_build = crate::LLVMPath::llvm_build_compiler_rt()?;
@@ -114,7 +136,7 @@ pub fn build(
"-B", "-B",
llvm_compiler_rt_build.to_string_lossy().as_ref(), llvm_compiler_rt_build.to_string_lossy().as_ref(),
"-G", "-G",
"Ninja", generator,
]) ])
.args(CMAKE_STATIC_ARGS) .args(CMAKE_STATIC_ARGS)
.args(cmake_dynamic_args(build_type, target_env)?) .args(cmake_dynamic_args(build_type, target_env)?)
@@ -131,7 +153,17 @@ pub fn build(
"LLVM compiler-rt building cmake", "LLVM compiler-rt building cmake",
)?; )?;
crate::utils::ninja(&llvm_compiler_rt_build)?; crate::utils::command(
Command::new("cmake").args([
"--build",
llvm_compiler_rt_build.to_string_lossy().as_ref(),
"--target",
"install",
"--config",
build_type.to_string().as_str(),
]),
"Building",
)?;
Ok(()) Ok(())
} }
+1 -1
View File
@@ -214,7 +214,7 @@ pub fn build(
sanitizer, sanitizer,
)?; )?;
} else if cfg!(target_os = "windows") { } else if cfg!(target_os = "windows") {
platforms::x86_64_windows_gnu::build( platforms::x86_64_windows_msvc::build(
build_type, build_type,
targets, targets,
llvm_projects, llvm_projects,
+1 -1
View File
@@ -8,7 +8,7 @@ pub mod wasm32_emscripten;
pub mod x86_64_linux_gnu; pub mod x86_64_linux_gnu;
pub mod x86_64_linux_musl; pub mod x86_64_linux_musl;
pub mod x86_64_macos; pub mod x86_64_macos;
pub mod x86_64_windows_gnu; pub mod x86_64_windows_msvc;
use std::str::FromStr; use std::str::FromStr;
+3 -1
View File
@@ -8,7 +8,7 @@ use std::path::Path;
use std::process::Command; use std::process::Command;
/// The build options shared by all platforms. /// The build options shared by all platforms.
pub const SHARED_BUILD_OPTS: [&str; 19] = [ pub const SHARED_BUILD_OPTS: [&str; 21] = [
"-DPACKAGE_VENDOR='Parity Technologies'", "-DPACKAGE_VENDOR='Parity Technologies'",
"-DCMAKE_BUILD_WITH_INSTALL_RPATH=1", "-DCMAKE_BUILD_WITH_INSTALL_RPATH=1",
"-DLLVM_BUILD_DOCS='Off'", "-DLLVM_BUILD_DOCS='Off'",
@@ -28,6 +28,8 @@ pub const SHARED_BUILD_OPTS: [&str; 19] = [
"-DCMAKE_EXPORT_COMPILE_COMMANDS='On'", "-DCMAKE_EXPORT_COMPILE_COMMANDS='On'",
"-DPython3_FIND_REGISTRY='LAST'", // Use Python version from $PATH, not from registry "-DPython3_FIND_REGISTRY='LAST'", // Use Python version from $PATH, not from registry
"-DBUG_REPORT_URL='https://github.com/paritytech/contract-issues/issues/'", "-DBUG_REPORT_URL='https://github.com/paritytech/contract-issues/issues/'",
"-DCLANG_ENABLE_ARCMT='Off'",
"-DCLANG_ENABLE_STATIC_ANALYZER='Off'",
]; ];
/// The build options shared by all platforms except MUSL. /// The build options shared by all platforms except MUSL.
@@ -1,7 +1,6 @@
//! The revive LLVM amd64 `windows-gnu` builder. //! The revive LLVM amd64 `windows-gnu` builder.
use std::collections::HashSet; use std::collections::HashSet;
use std::path::PathBuf;
use std::process::Command; use std::process::Command;
use crate::build_type::BuildType; use crate::build_type::BuildType;
@@ -28,10 +27,6 @@ pub fn build(
sanitizer: Option<Sanitizer>, sanitizer: Option<Sanitizer>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
crate::utils::check_presence("cmake")?; crate::utils::check_presence("cmake")?;
crate::utils::check_presence("clang")?;
crate::utils::check_presence("clang++")?;
crate::utils::check_presence("lld")?;
crate::utils::check_presence("ninja")?;
let llvm_module_llvm = let llvm_module_llvm =
LLVMPath::llvm_module_llvm().and_then(crate::utils::path_windows_to_unix)?; LLVMPath::llvm_module_llvm().and_then(crate::utils::path_windows_to_unix)?;
@@ -48,15 +43,12 @@ pub fn build(
"-B", "-B",
llvm_build_final.to_string_lossy().as_ref(), llvm_build_final.to_string_lossy().as_ref(),
"-G", "-G",
"Ninja", "Visual Studio 17 2022",
format!( format!(
"-DCMAKE_INSTALL_PREFIX='{}'", "-DCMAKE_INSTALL_PREFIX='{}'",
llvm_target_final.to_string_lossy().as_ref(), llvm_target_final.to_string_lossy().as_ref(),
) )
.as_str(), .as_str(),
format!("-DCMAKE_BUILD_TYPE='{build_type}'").as_str(),
"-DCMAKE_C_COMPILER='clang'",
"-DCMAKE_CXX_COMPILER='clang++'",
format!( format!(
"-DLLVM_TARGETS_TO_BUILD='{}'", "-DLLVM_TARGETS_TO_BUILD='{}'",
targets targets
@@ -75,7 +67,7 @@ pub fn build(
.join(";") .join(";")
) )
.as_str(), .as_str(),
"-DLLVM_USE_LINKER='lld'", "-DLLVM_BUILD_LLVM_C_DYLIB=Off",
]) ])
.args(crate::platforms::shared::shared_build_opts_default_target( .args(crate::platforms::shared::shared_build_opts_default_target(
default_target, default_target,
@@ -107,20 +99,16 @@ pub fn build(
"LLVM building cmake", "LLVM building cmake",
)?; )?;
crate::utils::ninja(llvm_build_final.as_ref())?; crate::utils::command(
Command::new("cmake").args([
let libstdcpp_source_path = match std::env::var("LIBSTDCPP_SOURCE_PATH") { "--build",
Ok(libstdcpp_source_path) => PathBuf::from(libstdcpp_source_path), llvm_build_final.to_string_lossy().as_ref(),
Err(error) => anyhow::bail!( "--target",
"The `LIBSTDCPP_SOURCE_PATH` must be set to the path to the libstdc++.a static library: {}", error "install",
), "--config",
}; build_type.to_string().as_str(),
let mut libstdcpp_destination_path = llvm_target_final; ]),
libstdcpp_destination_path.push("./lib/libstdc++.a"); "Building with msbuild",
fs_extra::file::copy(
crate::utils::path_windows_to_unix(libstdcpp_source_path)?,
crate::utils::path_windows_to_unix(libstdcpp_destination_path)?,
&fs_extra::file::CopyOptions::default(),
)?; )?;
Ok(()) Ok(())
+3 -5
View File
@@ -7,6 +7,7 @@ use std::process::Command;
use std::process::Stdio; use std::process::Stdio;
use std::time::Duration; use std::time::Duration;
use anyhow::Context;
use path_slash::PathBufExt; use path_slash::PathBufExt;
/// The LLVM host repository URL. /// The LLVM host repository URL.
@@ -131,11 +132,8 @@ pub fn path_windows_to_unix<P: AsRef<Path> + PathBufExt>(path: P) -> anyhow::Res
/// Checks if the tool exists in the system. /// Checks if the tool exists in the system.
pub fn check_presence(name: &str) -> anyhow::Result<()> { pub fn check_presence(name: &str) -> anyhow::Result<()> {
let description = &format!("checking the `{name}` executable"); which::which(name).with_context(|| format!("Tool `{name}` is missing. Please install"))?;
log::info!("{description}"); Ok(())
command(Command::new("which").arg(name), description)
.map_err(|_| anyhow::anyhow!("Tool `{}` is missing. Please install", name))
} }
/// Identify XCode version using `pkgutil`. /// Identify XCode version using `pkgutil`.
+44 -9
View File
@@ -53,6 +53,16 @@ fn clone_build_and_clean_musl() -> anyhow::Result<()> {
.assert() .assert()
.success(); .success();
Command::cargo_bin(common::REVIVE_LLVM)?
.current_dir(test_dir.path())
.arg("build")
.arg("--llvm-projects")
.arg("clang")
.arg("--llvm-projects")
.arg("lld")
.assert()
.success();
Command::cargo_bin(common::REVIVE_LLVM)? Command::cargo_bin(common::REVIVE_LLVM)?
.arg("--target-env") .arg("--target-env")
.arg("musl") .arg("musl")
@@ -65,12 +75,6 @@ fn clone_build_and_clean_musl() -> anyhow::Result<()> {
.assert() .assert()
.success(); .success();
Command::cargo_bin(common::REVIVE_LLVM)?
.current_dir(test_dir.path())
.arg("builtins")
.assert()
.success();
Command::cargo_bin(common::REVIVE_LLVM)? Command::cargo_bin(common::REVIVE_LLVM)?
.current_dir(test_dir.path()) .current_dir(test_dir.path())
.arg("clean") .arg("clean")
@@ -83,6 +87,7 @@ fn clone_build_and_clean_musl() -> anyhow::Result<()> {
/// 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 cloned and built in debug mode
/// with tests and coverage enabled. /// with tests and coverage enabled.
#[test] #[test]
#[cfg(target_os = "linux")]
fn debug_build_with_tests_coverage() -> anyhow::Result<()> { fn debug_build_with_tests_coverage() -> anyhow::Result<()> {
let test_dir = common::TestDir::with_lockfile(None)?; let test_dir = common::TestDir::with_lockfile(None)?;
@@ -99,6 +104,10 @@ fn debug_build_with_tests_coverage() -> anyhow::Result<()> {
.arg("--enable-tests") .arg("--enable-tests")
.arg("--build-type") .arg("--build-type")
.arg("Debug") .arg("Debug")
.arg("--llvm-projects")
.arg("clang")
.arg("--llvm-projects")
.arg("lld")
.assert() .assert()
.success(); .success();
@@ -107,6 +116,7 @@ fn debug_build_with_tests_coverage() -> anyhow::Result<()> {
/// This test verifies that the LLVM repository can be successfully built with address sanitizer. /// This test verifies that the LLVM repository can be successfully built with address sanitizer.
#[test] #[test]
#[cfg(target_os = "linux")]
fn build_with_sanitizers() -> anyhow::Result<()> { fn build_with_sanitizers() -> anyhow::Result<()> {
let test_dir = common::TestDir::with_lockfile(None)?; let test_dir = common::TestDir::with_lockfile(None)?;
@@ -121,6 +131,10 @@ fn build_with_sanitizers() -> anyhow::Result<()> {
.arg("build") .arg("build")
.arg("--sanitizer") .arg("--sanitizer")
.arg("Address") .arg("Address")
.arg("--llvm-projects")
.arg("lld")
.arg("--llvm-projects")
.arg("clang")
.assert() .assert()
.success(); .success();
@@ -129,15 +143,36 @@ fn build_with_sanitizers() -> anyhow::Result<()> {
/// Tests the clone, build, and clean process of the LLVM repository for the emscripten target. /// Tests the clone, build, and clean process of the LLVM repository for the emscripten target.
#[test] #[test]
#[cfg(any(target_os = "linux", target_os = "macos"))] #[cfg(target_os = "linux")]
fn clone_build_and_clean_emscripten() -> anyhow::Result<()> { fn clone_build_and_clean_emscripten() -> anyhow::Result<()> {
let test_dir = common::TestDir::with_lockfile(None)?; let test_dir = common::TestDir::with_lockfile(None)?;
let command = Command::cargo_bin(common::REVIVE_LLVM)?; let command = Command::cargo_bin(common::REVIVE_LLVM)?;
let program = command.get_program().to_string_lossy(); let program = command.get_program().to_string_lossy();
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("build")
.arg("--llvm-projects")
.arg("lld")
.arg("--llvm-projects")
.arg("clang")
.assert()
.success();
// Two little shell-dependent things here:
// Doing `. ./emsdk_env.sh` instead of `source`, as `source` might be missing in some shells
// `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!( let emsdk_wrapped_build_command = format!(
"{program} --target-env emscripten clone && \ "{program} --target-env emscripten clone && \
source {}emsdk_env.sh && \ cd {} && . ./emsdk_env.sh && cd .. && \
{program} --target-env emscripten build --llvm-projects clang --llvm-projects lld", {program} --target-env emscripten build --llvm-projects lld",
revive_llvm_builder::LLVMPath::DIRECTORY_EMSDK_SOURCE, revive_llvm_builder::LLVMPath::DIRECTORY_EMSDK_SOURCE,
); );
+2
View File
@@ -22,6 +22,7 @@ num = { workspace = true }
hex = { workspace = true } hex = { workspace = true }
sha3 = { workspace = true } sha3 = { workspace = true }
inkwell = { workspace = true } inkwell = { workspace = true }
libc = { workspace = true }
polkavm-disassembler = { workspace = true } polkavm-disassembler = { workspace = true }
polkavm-common = { workspace = true } polkavm-common = { workspace = true }
@@ -29,3 +30,4 @@ revive-common = { workspace = true }
revive-runtime-api = { workspace = true } revive-runtime-api = { workspace = true }
revive-linker = { workspace = true } revive-linker = { workspace = true }
revive-stdlib = { workspace = true } revive-stdlib = { workspace = true }
revive-solc-json-interface = { workspace = true }
+55 -10
View File
@@ -1,12 +1,11 @@
//! The LLVM context library. //! The LLVM context library.
pub(crate) mod debug_config; use std::ffi::CString;
pub(crate) mod optimizer; use std::sync::OnceLock;
pub(crate) mod polkavm;
pub(crate) mod target_machine;
pub use self::debug_config::ir_type::IRType as DebugConfigIR; pub use self::debug_config::ir_type::IRType as DebugConfigIR;
pub use self::debug_config::DebugConfig; pub use self::debug_config::DebugConfig;
pub use self::memory::MemoryConfig;
pub use self::optimizer::settings::size_level::SizeLevel as OptimizerSettingsSizeLevel; pub use self::optimizer::settings::size_level::SizeLevel as OptimizerSettingsSizeLevel;
pub use self::optimizer::settings::Settings as OptimizerSettings; pub use self::optimizer::settings::Settings as OptimizerSettings;
pub use self::optimizer::Optimizer; pub use self::optimizer::Optimizer;
@@ -21,17 +20,28 @@ pub use self::polkavm::context::function::declaration::Declaration as PolkaVMFun
pub use self::polkavm::context::function::intrinsics::Intrinsics as PolkaVMIntrinsicFunction; pub use self::polkavm::context::function::intrinsics::Intrinsics as PolkaVMIntrinsicFunction;
pub use self::polkavm::context::function::llvm_runtime::LLVMRuntime as PolkaVMLLVMRuntime; pub use self::polkavm::context::function::llvm_runtime::LLVMRuntime as PolkaVMLLVMRuntime;
pub use self::polkavm::context::function::r#return::Return as PolkaVMFunctionReturn; pub use self::polkavm::context::function::r#return::Return as PolkaVMFunctionReturn;
pub use self::polkavm::context::function::runtime::arithmetics::Division as PolkaVMDivisionFunction;
pub use self::polkavm::context::function::runtime::arithmetics::Remainder as PolkaVMRemainderFunction;
pub use self::polkavm::context::function::runtime::arithmetics::SignedDivision as PolkaVMSignedDivisionFunction;
pub use self::polkavm::context::function::runtime::arithmetics::SignedRemainder as PolkaVMSignedRemainderFunction;
pub use self::polkavm::context::function::runtime::deploy_code::DeployCode as PolkaVMDeployCodeFunction; pub use self::polkavm::context::function::runtime::deploy_code::DeployCode as PolkaVMDeployCodeFunction;
pub use self::polkavm::context::function::runtime::entry::Entry as PolkaVMEntryFunction; pub use self::polkavm::context::function::runtime::entry::Entry as PolkaVMEntryFunction;
pub use self::polkavm::context::function::runtime::immutable_data_load::ImmutableDataLoad as PolkaVMImmutableDataLoadFunction; pub use self::polkavm::context::function::runtime::revive::Exit as PolkaVMExitFunction;
pub use self::polkavm::context::function::runtime::revive::WordToPointer as PolkaVMWordToPointerFunction;
pub use self::polkavm::context::function::runtime::runtime_code::RuntimeCode as PolkaVMRuntimeCodeFunction; pub use self::polkavm::context::function::runtime::runtime_code::RuntimeCode as PolkaVMRuntimeCodeFunction;
pub use self::polkavm::context::function::runtime::sbrk::Sbrk as PolkaVMSbrkFunction;
pub use self::polkavm::context::function::runtime::FUNCTION_DEPLOY_CODE as PolkaVMFunctionDeployCode; pub use self::polkavm::context::function::runtime::FUNCTION_DEPLOY_CODE as PolkaVMFunctionDeployCode;
pub use self::polkavm::context::function::runtime::FUNCTION_ENTRY as PolkaVMFunctionEntry; pub use self::polkavm::context::function::runtime::FUNCTION_ENTRY as PolkaVMFunctionEntry;
pub use self::polkavm::context::function::runtime::FUNCTION_LOAD_IMMUTABLE_DATA as PolkaVMFunctionImmutableDataLoad;
pub use self::polkavm::context::function::runtime::FUNCTION_RUNTIME_CODE as PolkaVMFunctionRuntimeCode; pub use self::polkavm::context::function::runtime::FUNCTION_RUNTIME_CODE as PolkaVMFunctionRuntimeCode;
pub use self::polkavm::context::function::yul_data::YulData as PolkaVMFunctionYulData; pub use self::polkavm::context::function::yul_data::YulData as PolkaVMFunctionYulData;
pub use self::polkavm::context::function::Function as PolkaVMFunction; pub use self::polkavm::context::function::Function as PolkaVMFunction;
pub use self::polkavm::context::global::Global as PolkaVMGlobal; pub use self::polkavm::context::global::Global as PolkaVMGlobal;
pub use self::polkavm::context::pointer::heap::LoadWord as PolkaVMLoadHeapWordFunction;
pub use self::polkavm::context::pointer::heap::StoreWord as PolkaVMStoreHeapWordFunction;
pub use self::polkavm::context::pointer::storage::LoadTransientWord as PolkaVMLoadTransientStorageWordFunction;
pub use self::polkavm::context::pointer::storage::LoadWord as PolkaVMLoadStorageWordFunction;
pub use self::polkavm::context::pointer::storage::StoreTransientWord as PolkaVMStoreTransientStorageWordFunction;
pub use self::polkavm::context::pointer::storage::StoreWord as PolkaVMStoreStorageWordFunction;
pub use self::polkavm::context::pointer::Pointer as PolkaVMPointer; pub use self::polkavm::context::pointer::Pointer as PolkaVMPointer;
pub use self::polkavm::context::r#loop::Loop as PolkaVMLoop; 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::solidity_data::SolidityData as PolkaVMContextSolidityData;
@@ -47,14 +57,16 @@ pub use self::polkavm::evm::create as polkavm_evm_create;
pub use self::polkavm::evm::crypto as polkavm_evm_crypto; pub use self::polkavm::evm::crypto as polkavm_evm_crypto;
pub use self::polkavm::evm::ether_gas as polkavm_evm_ether_gas; pub use self::polkavm::evm::ether_gas as polkavm_evm_ether_gas;
pub use self::polkavm::evm::event as polkavm_evm_event; pub use self::polkavm::evm::event as polkavm_evm_event;
pub use self::polkavm::evm::event::EventLog as PolkaVMEventLogFunction;
pub use self::polkavm::evm::ext_code as polkavm_evm_ext_code; pub use self::polkavm::evm::ext_code as polkavm_evm_ext_code;
pub use self::polkavm::evm::immutable as polkavm_evm_immutable; pub use self::polkavm::evm::immutable as polkavm_evm_immutable;
pub use self::polkavm::evm::immutable::Load as PolkaVMLoadImmutableDataFunction;
pub use self::polkavm::evm::immutable::Store as PolkaVMStoreImmutableDataFunction;
pub use self::polkavm::evm::math as polkavm_evm_math; pub use self::polkavm::evm::math as polkavm_evm_math;
pub use self::polkavm::evm::memory as polkavm_evm_memory; 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::r#return as polkavm_evm_return;
pub use self::polkavm::evm::return_data as polkavm_evm_return_data; 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::evm::storage as polkavm_evm_storage;
pub use self::polkavm::metadata_hash::MetadataHash as PolkaVMMetadataHash;
pub use self::polkavm::r#const as polkavm_const; pub use self::polkavm::r#const as polkavm_const;
pub use self::polkavm::Dependency as PolkaVMDependency; pub use self::polkavm::Dependency as PolkaVMDependency;
pub use self::polkavm::DummyDependency as PolkaVMDummyDependency; pub use self::polkavm::DummyDependency as PolkaVMDummyDependency;
@@ -63,9 +75,42 @@ pub use self::polkavm::WriteLLVM as PolkaVMWriteLLVM;
pub use self::target_machine::target::Target; pub use self::target_machine::target::Target;
pub use self::target_machine::TargetMachine; pub use self::target_machine::TargetMachine;
/// Initializes the target machine. pub(crate) mod debug_config;
pub fn initialize_target(target: Target) { pub(crate) mod memory;
pub(crate) mod optimizer;
pub(crate) mod polkavm;
pub(crate) mod target_machine;
static DID_INITIALIZE: OnceLock<()> = OnceLock::new();
/// Initializes the LLVM compiler backend.
///
/// 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]) {
let Ok(_) = DID_INITIALIZE.set(()) else {
return; // Tests don't go through a recursive process
};
let argv = [name.to_string()]
.iter()
.chain(llvm_arguments)
.map(|arg| CString::new(arg.as_bytes()).unwrap())
.collect::<Vec<_>>();
let argv: Vec<*const libc::c_char> = argv.iter().map(|arg| arg.as_ptr()).collect();
let overview = CString::new("").unwrap();
unsafe {
inkwell::llvm_sys::support::LLVMParseCommandLineOptions(
argv.len() as i32,
argv.as_ptr(),
overview.as_ptr(),
);
}
inkwell::support::enable_llvm_pretty_stack_trace();
match target { match target {
Target::PVM => self::polkavm::initialize_target(), Target::PVM => inkwell::targets::Target::initialize_riscv(&Default::default()),
} }
} }
+21
View File
@@ -0,0 +1,21 @@
//! The compile time PolkaVM memory configuration settings.
use serde::{Deserialize, Serialize};
/// The PolkaVM memory configuration.
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
pub struct MemoryConfig {
/// The emulated EVM linear heap memory size in bytes.
pub heap_size: u32,
/// The PVM stack size in bytes.
pub stack_size: u32,
}
impl Default for MemoryConfig {
fn default() -> Self {
Self {
heap_size: 64 * 1024,
stack_size: 32 * 1024,
}
}
}
@@ -2,6 +2,7 @@
pub mod size_level; pub mod size_level;
use revive_solc_json_interface::SolcStandardJsonInputSettingsOptimizer;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
@@ -226,3 +227,18 @@ 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::cycles(),
};
if value.fallback_to_optimizing_for_size.unwrap_or_default() {
result.enable_fallback_to_size();
}
Ok(result)
}
}
@@ -9,6 +9,15 @@ pub static XLEN: usize = revive_common::BIT_LENGTH_X32;
/// The calldata size global variable name. /// The calldata size global variable name.
pub static GLOBAL_CALLDATA_SIZE: &str = "calldatasize"; pub static GLOBAL_CALLDATA_SIZE: &str = "calldatasize";
/// The heap size global variable name.
pub static GLOBAL_HEAP_SIZE: &str = "__heap_size";
/// The heap memory global variable name.
pub static GLOBAL_HEAP_MEMORY: &str = "__heap_memory";
/// The spill buffer global variable name.
pub static GLOBAL_ADDRESS_SPILL_BUFFER: &str = "address_spill_buffer";
/// The deployer call header size that consists of: /// The deployer call header size that consists of:
/// - bytecode hash (32 bytes) /// - bytecode hash (32 bytes)
pub const DEPLOYER_CALL_HEADER_SIZE: usize = revive_common::BYTE_LENGTH_WORD; pub const DEPLOYER_CALL_HEADER_SIZE: usize = revive_common::BYTE_LENGTH_WORD;
@@ -8,10 +8,6 @@ pub enum AddressSpace {
Stack, Stack,
/// The heap memory. /// The heap memory.
Heap, Heap,
/// The generic memory page.
Storage,
/// The transient storage.
TransientStorage,
} }
impl From<AddressSpace> for inkwell::AddressSpace { impl From<AddressSpace> for inkwell::AddressSpace {
@@ -19,8 +15,6 @@ impl From<AddressSpace> for inkwell::AddressSpace {
match value { match value {
AddressSpace::Stack => Self::from(0), AddressSpace::Stack => Self::from(0),
AddressSpace::Heap => Self::from(1), AddressSpace::Heap => Self::from(1),
AddressSpace::Storage => Self::from(5),
AddressSpace::TransientStorage => Self::from(6),
} }
} }
} }
@@ -4,61 +4,98 @@
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Argument<'ctx> { pub struct Argument<'ctx> {
/// The actual LLVM operand. /// The actual LLVM operand.
pub value: inkwell::values::BasicValueEnum<'ctx>, pub value: Value<'ctx>,
/// The original AST value. Used mostly for string literals. /// The original AST value. Used mostly for string literals.
pub original: Option<String>, pub original: Option<String>,
/// The preserved constant value, if available. /// The preserved constant value, if available.
pub constant: Option<num::BigUint>, pub constant: Option<num::BigUint>,
} }
/// The function argument can be either a pointer or a integer value.
/// This disambiguation allows for lazy loading of variables.
#[derive(Clone, Debug)]
pub enum Value<'ctx> {
Register(inkwell::values::BasicValueEnum<'ctx>),
Pointer {
pointer: crate::polkavm::context::Pointer<'ctx>,
id: String,
},
}
impl<'ctx> Argument<'ctx> { impl<'ctx> Argument<'ctx> {
/// The calldata offset argument index. /// A shortcut constructor for register arguments.
pub const ARGUMENT_INDEX_CALLDATA_OFFSET: usize = 0; pub fn value(value: inkwell::values::BasicValueEnum<'ctx>) -> Self {
/// The calldata length argument index.
pub const ARGUMENT_INDEX_CALLDATA_LENGTH: usize = 1;
/// A shortcut constructor.
pub fn new(value: inkwell::values::BasicValueEnum<'ctx>) -> Self {
Self { Self {
value, value: Value::Register(value),
original: None, original: None,
constant: None, constant: None,
} }
} }
/// A shortcut constructor. /// A shortcut constructor for stack arguments.
pub fn new_with_original( pub fn pointer(pointer: crate::polkavm::context::Pointer<'ctx>, id: String) -> Self {
value: inkwell::values::BasicValueEnum<'ctx>,
original: String,
) -> Self {
Self { Self {
value, value: Value::Pointer { pointer, id },
original: Some(original), original: None,
constant: None, constant: None,
} }
} }
/// A shortcut constructor. /// Set the original decleratation value.
pub fn new_with_constant( pub fn with_original(mut self, original: String) -> Self {
value: inkwell::values::BasicValueEnum<'ctx>, self.original = Some(original);
constant: num::BigUint, self
) -> Self { }
Self {
value, /// Set the constant value.
original: None, pub fn with_constant(mut self, constant: num::BigUint) -> Self {
constant: Some(constant), self.constant = Some(constant);
} self
} }
/// Returns the inner LLVM value. /// Returns the inner LLVM value.
pub fn to_llvm(&self) -> inkwell::values::BasicValueEnum<'ctx> { ///
self.value /// Panics if `self` is a pointer argument.
pub fn _to_llvm_value(&self) -> inkwell::values::BasicValueEnum<'ctx> {
match &self.value {
Value::Register(value) => *value,
Value::Pointer { .. } => unreachable!("invalid register value access"),
}
}
/// Access the underlying value.
///
/// Will emit a stack load if `self` is a pointer argument.
pub fn access<D: crate::polkavm::Dependency + Clone>(
&self,
context: &crate::polkavm::context::Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>> {
match &self.value {
Value::Register(value) => Ok(*value),
Value::Pointer { pointer, id } => context.build_load(*pointer, id),
}
}
/// Access the underlying value.
///
/// Will emit a stack load if `self` is a pointer argument.
pub fn as_pointer<D: crate::polkavm::Dependency + Clone>(
&self,
context: &crate::polkavm::context::Context<'ctx, D>,
) -> anyhow::Result<crate::polkavm::context::Pointer<'ctx>> {
match &self.value {
Value::Register(value) => {
let pointer = context.build_alloca_at_entry(context.word_type(), "pvm_arg");
context.build_store(pointer, *value)?;
Ok(pointer)
}
Value::Pointer { pointer, .. } => Ok(*pointer),
}
} }
} }
impl<'ctx> From<inkwell::values::BasicValueEnum<'ctx>> for Argument<'ctx> { impl<'ctx> From<inkwell::values::BasicValueEnum<'ctx>> for Argument<'ctx> {
fn from(value: inkwell::values::BasicValueEnum<'ctx>) -> Self { fn from(value: inkwell::values::BasicValueEnum<'ctx>) -> Self {
Self::new(value) Self::value(value)
} }
} }
@@ -1,9 +1,6 @@
//! The LLVM runtime functions. //! The LLVM runtime functions.
use inkwell::types::BasicType;
use crate::optimizer::Optimizer; use crate::optimizer::Optimizer;
use crate::polkavm::context::address_space::AddressSpace;
use crate::polkavm::context::function::declaration::Declaration as FunctionDeclaration; use crate::polkavm::context::function::declaration::Declaration as FunctionDeclaration;
use crate::polkavm::context::function::Function; use crate::polkavm::context::function::Function;
@@ -19,9 +16,6 @@ pub struct LLVMRuntime<'ctx> {
pub exp: FunctionDeclaration<'ctx>, pub exp: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function. /// The corresponding LLVM runtime function.
pub sign_extend: FunctionDeclaration<'ctx>, pub sign_extend: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub sha3: FunctionDeclaration<'ctx>,
} }
impl<'ctx> LLVMRuntime<'ctx> { impl<'ctx> LLVMRuntime<'ctx> {
@@ -37,9 +31,6 @@ impl<'ctx> LLVMRuntime<'ctx> {
/// The corresponding runtime function name. /// The corresponding runtime function name.
pub const FUNCTION_SIGNEXTEND: &'static str = "__signextend"; pub const FUNCTION_SIGNEXTEND: &'static str = "__signextend";
/// The corresponding runtime function name.
pub const FUNCTION_SHA3: &'static str = "__sha3";
/// A shortcut constructor. /// A shortcut constructor.
pub fn new( pub fn new(
llvm: &'ctx inkwell::context::Context, llvm: &'ctx inkwell::context::Context,
@@ -65,43 +56,11 @@ impl<'ctx> LLVMRuntime<'ctx> {
Function::set_default_attributes(llvm, sign_extend, optimizer); Function::set_default_attributes(llvm, sign_extend, optimizer);
Function::set_pure_function_attributes(llvm, sign_extend); Function::set_pure_function_attributes(llvm, sign_extend);
let sha3 = Self::declare(
module,
Self::FUNCTION_SHA3,
llvm.custom_width_int_type(revive_common::BIT_LENGTH_WORD as u32)
.fn_type(
vec![
llvm.ptr_type(AddressSpace::Heap.into())
.as_basic_type_enum()
.into(),
llvm.custom_width_int_type(revive_common::BIT_LENGTH_WORD as u32)
.as_basic_type_enum()
.into(),
llvm.custom_width_int_type(revive_common::BIT_LENGTH_BOOLEAN as u32)
.as_basic_type_enum()
.into(),
]
.as_slice(),
false,
),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, sha3, optimizer);
Function::set_attributes(
llvm,
sha3,
//vec![Attribute::ArgMemOnly, Attribute::ReadOnly],
vec![],
false,
);
Self { Self {
add_mod, add_mod,
mul_mod, mul_mod,
exp, exp,
sign_extend, sign_extend,
sha3,
} }
} }
@@ -81,8 +81,7 @@ impl<'ctx> Function<'ctx> {
|| (name.starts_with("__") || (name.starts_with("__")
&& name != self::runtime::FUNCTION_ENTRY && name != self::runtime::FUNCTION_ENTRY
&& name != self::runtime::FUNCTION_DEPLOY_CODE && name != self::runtime::FUNCTION_DEPLOY_CODE
&& name != self::runtime::FUNCTION_RUNTIME_CODE && name != self::runtime::FUNCTION_RUNTIME_CODE)
&& name != self::runtime::FUNCTION_LOAD_IMMUTABLE_DATA)
} }
/// Returns the LLVM function declaration. /// Returns the LLVM function declaration.
@@ -110,30 +109,21 @@ impl<'ctx> Function<'ctx> {
pub fn set_attributes( pub fn set_attributes(
llvm: &'ctx inkwell::context::Context, llvm: &'ctx inkwell::context::Context,
declaration: Declaration<'ctx>, declaration: Declaration<'ctx>,
attributes: Vec<Attribute>, attributes: &[Attribute],
force: bool, force: bool,
) { ) {
for attribute_kind in attributes.into_iter() { for attribute_kind in attributes {
match attribute_kind { match attribute_kind {
Attribute::Memory => unimplemented!("`memory` attributes are not implemented"), Attribute::Memory => unimplemented!("`memory` attributes are not implemented"),
attribute_kind @ Attribute::AlwaysInline if force => { attribute_kind @ Attribute::AlwaysInline if force => {
let is_optimize_none_set = declaration declaration.value.remove_enum_attribute(
.value inkwell::attributes::AttributeLoc::Function,
.get_enum_attribute( Attribute::NoInline as u32,
inkwell::attributes::AttributeLoc::Function, );
Attribute::OptimizeNone as u32, declaration.value.add_attribute(
) inkwell::attributes::AttributeLoc::Function,
.is_some(); llvm.create_enum_attribute(*attribute_kind as u32, 0),
if !is_optimize_none_set { );
declaration.value.remove_enum_attribute(
inkwell::attributes::AttributeLoc::Function,
Attribute::NoInline as u32,
);
declaration.value.add_attribute(
inkwell::attributes::AttributeLoc::Function,
llvm.create_enum_attribute(attribute_kind as u32, 0),
);
}
} }
attribute_kind @ Attribute::NoInline if force => { attribute_kind @ Attribute::NoInline if force => {
declaration.value.remove_enum_attribute( declaration.value.remove_enum_attribute(
@@ -142,12 +132,12 @@ impl<'ctx> Function<'ctx> {
); );
declaration.value.add_attribute( declaration.value.add_attribute(
inkwell::attributes::AttributeLoc::Function, inkwell::attributes::AttributeLoc::Function,
llvm.create_enum_attribute(attribute_kind as u32, 0), llvm.create_enum_attribute(*attribute_kind as u32, 0),
); );
} }
attribute_kind => declaration.value.add_attribute( attribute_kind => declaration.value.add_attribute(
inkwell::attributes::AttributeLoc::Function, inkwell::attributes::AttributeLoc::Function,
llvm.create_enum_attribute(attribute_kind as u32, 0), llvm.create_enum_attribute(*attribute_kind as u32, 0),
), ),
} }
} }
@@ -178,27 +168,16 @@ impl<'ctx> Function<'ctx> {
declaration: Declaration<'ctx>, declaration: Declaration<'ctx>,
optimizer: &Optimizer, optimizer: &Optimizer,
) { ) {
if optimizer.settings().level_middle_end == inkwell::OptimizationLevel::None { if optimizer.settings().level_middle_end_size == SizeLevel::Z {
Self::remove_attributes(
declaration,
&[Attribute::OptimizeForSize, Attribute::AlwaysInline],
);
Self::set_attributes( Self::set_attributes(
llvm, llvm,
declaration, declaration,
vec![Attribute::OptimizeNone, Attribute::NoInline], &[Attribute::OptimizeForSize, Attribute::MinSize],
false,
);
} else if optimizer.settings().level_middle_end_size == SizeLevel::Z {
Self::set_attributes(
llvm,
declaration,
vec![Attribute::OptimizeForSize, Attribute::MinSize],
false, false,
); );
} }
Self::set_attributes(llvm, declaration, vec![Attribute::NoFree], false); Self::set_attributes(llvm, declaration, &[Attribute::NoFree], false);
} }
/// Sets the front-end runtime attributes. /// Sets the front-end runtime attributes.
@@ -208,7 +187,7 @@ impl<'ctx> Function<'ctx> {
optimizer: &Optimizer, optimizer: &Optimizer,
) { ) {
if optimizer.settings().level_middle_end_size == SizeLevel::Z { if optimizer.settings().level_middle_end_size == SizeLevel::Z {
Self::set_attributes(llvm, declaration, vec![Attribute::NoInline], false); Self::set_attributes(llvm, declaration, &[Attribute::NoInline], false);
} }
} }
@@ -220,7 +199,7 @@ impl<'ctx> Function<'ctx> {
Self::set_attributes( Self::set_attributes(
llvm, llvm,
declaration, declaration,
vec![ &[
Attribute::MustProgress, Attribute::MustProgress,
Attribute::NoUnwind, Attribute::NoUnwind,
Attribute::WillReturn, Attribute::WillReturn,
@@ -0,0 +1,269 @@
//! Translates the arithmetic operations.
use inkwell::values::BasicValue;
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,
{
const NAME: &'static str = "__revive_division";
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
context.word_type().fn_type(
&[context.word_type().into(), context.word_type().into()],
false,
)
}
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx, D>,
) -> 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();
wrapped_division(context, operand_2, || {
Ok(context
.builder()
.build_int_unsigned_div(operand_1, operand_2, "DIV")?)
})
.map(Into::into)
}
}
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)
}
fn into_llvm(self, context: &mut Context<D>) -> 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,
{
const NAME: &'static str = "__revive_signed_division";
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
context.word_type().fn_type(
&[context.word_type().into(), context.word_type().into()],
false,
)
}
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx, D>,
) -> 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();
let block_calculate = context.append_basic_block("calculate");
let block_overflow = context.append_basic_block("overflow");
let block_select = context.append_basic_block("select_result");
let block_origin = context.basic_block();
context.builder().build_switch(
operand_2,
block_calculate,
&[
(context.word_type().const_zero(), block_select),
(context.word_type().const_all_ones(), block_overflow),
],
)?;
context.set_basic_block(block_calculate);
let quotient = context
.builder()
.build_int_signed_div(operand_1, operand_2, "SDIV")?;
context.build_unconditional_branch(block_select);
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.word_type(),
"max_uint",
)?;
let is_operand_1_overflow = context.builder().build_int_compare(
inkwell::IntPredicate::EQ,
operand_1,
context.builder().build_int_neg(max_uint, "min_uint")?,
"is_operand_1_overflow",
)?;
context.build_conditional_branch(is_operand_1_overflow, block_select, block_calculate)?;
context.set_basic_block(block_select);
let result = context.builder().build_phi(context.word_type(), "result")?;
result.add_incoming(&[
(&operand_1, block_overflow),
(&context.word_const(0), block_origin),
(&quotient.as_basic_value_enum(), block_calculate),
]);
Ok(Some(result.as_basic_value()))
}
}
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)
}
fn into_llvm(self, context: &mut Context<D>) -> 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,
{
const NAME: &'static str = "__revive_remainder";
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
context.word_type().fn_type(
&[context.word_type().into(), context.word_type().into()],
false,
)
}
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx, D>,
) -> 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();
wrapped_division(context, operand_2, || {
Ok(context
.builder()
.build_int_unsigned_rem(operand_1, operand_2, "MOD")?)
})
.map(Into::into)
}
}
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)
}
fn into_llvm(self, context: &mut Context<D>) -> 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,
{
const NAME: &'static str = "__revive_signed_remainder";
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
context.word_type().fn_type(
&[context.word_type().into(), context.word_type().into()],
false,
)
}
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx, D>,
) -> 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();
wrapped_division(context, operand_2, || {
Ok(context
.builder()
.build_int_signed_rem(operand_1, operand_2, "SMOD")?)
})
.map(Into::into)
}
}
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)
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::emit(&self, context)
}
}
/// Wrap division operations so that zero will be returned if the
/// denominator is zero (see also Ethereum YP Appendix H.2).
///
/// The closure is expected to calculate and return the quotient.
///
/// The result is either the calculated quotient or zero,
/// selected at runtime.
fn wrapped_division<'ctx, D, F, T>(
context: &Context<'ctx, D>,
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>,
{
assert_eq!(
denominator.get_type().get_bit_width(),
revive_common::BIT_LENGTH_WORD as u32
);
let block_calculate = context.append_basic_block("calculate");
let block_select = context.append_basic_block("select");
let block_origin = context.basic_block();
context.builder().build_switch(
denominator,
block_calculate,
&[(context.word_const(0), block_select)],
)?;
context.set_basic_block(block_calculate);
let calculated_value = f()?.as_basic_value_enum();
context.build_unconditional_branch(block_select);
context.set_basic_block(block_select);
let result = context.builder().build_phi(context.word_type(), "result")?;
result.add_incoming(&[
(&context.word_const(0), block_origin),
(&calculated_value, block_calculate),
]);
Ok(result.as_basic_value())
}
@@ -31,6 +31,31 @@ impl Entry {
context.xlen_type().get_undef(), context.xlen_type().get_undef(),
); );
context.set_global(
crate::polkavm::GLOBAL_HEAP_SIZE,
context.xlen_type(),
AddressSpace::Stack,
context.xlen_type().const_zero(),
);
let heap_memory_type = context
.byte_type()
.array_type(context.memory_config.heap_size);
context.set_global(
crate::polkavm::GLOBAL_HEAP_MEMORY,
heap_memory_type,
AddressSpace::Stack,
heap_memory_type.const_zero(),
);
let address_type = context.integer_type(revive_common::BIT_LENGTH_ETH_ADDRESS);
context.set_global(
crate::polkavm::GLOBAL_ADDRESS_SPILL_BUFFER,
address_type,
AddressSpace::Stack,
address_type.const_zero(),
);
Ok(()) Ok(())
} }
@@ -145,7 +170,7 @@ where
crate::PolkaVMFunction::set_attributes( crate::PolkaVMFunction::set_attributes(
context.llvm(), context.llvm(),
entry, entry,
vec![crate::PolkaVMAttribute::NoReturn], &[crate::PolkaVMAttribute::NoReturn],
true, true,
); );
@@ -1,118 +0,0 @@
//! The immutable data runtime function.
use crate::polkavm::context::address_space::AddressSpace;
use crate::polkavm::context::function::runtime;
use crate::polkavm::context::pointer::Pointer;
use crate::polkavm::context::Context;
use crate::polkavm::Dependency;
use crate::polkavm::WriteLLVM;
/// A function for requesting the immutable data from the runtime.
/// This is a special function that is only used by the front-end generated code.
///
/// The runtime API is called lazily and subsequent calls are no-ops.
///
/// The bytes written is asserted to match the expected length.
/// This should never fail; the length is known.
/// However, this is a one time assertion, hence worth it.
#[derive(Debug)]
pub struct ImmutableDataLoad;
impl<D> WriteLLVM<D> for ImmutableDataLoad
where
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
context.add_function(
runtime::FUNCTION_LOAD_IMMUTABLE_DATA,
context.void_type().fn_type(Default::default(), false),
0,
Some(inkwell::module::Linkage::External),
)?;
Ok(())
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
context.set_current_function(runtime::FUNCTION_LOAD_IMMUTABLE_DATA, None)?;
context.set_basic_block(context.current_function().borrow().entry_block());
let immutable_data_size_pointer = context
.get_global(revive_runtime_api::immutable_data::GLOBAL_IMMUTABLE_DATA_SIZE)?
.value
.as_pointer_value();
let immutable_data_size = context.build_load(
Pointer::new(
context.xlen_type(),
AddressSpace::Stack,
immutable_data_size_pointer,
),
"immutable_data_size_load",
)?;
let load_immutable_data_block = context.append_basic_block("load_immutables_block");
let return_block = context.current_function().borrow().return_block();
let immutable_data_size_is_zero = context.builder().build_int_compare(
inkwell::IntPredicate::EQ,
context.xlen_type().const_zero(),
immutable_data_size.into_int_value(),
"immutable_data_size_is_zero",
)?;
context.build_conditional_branch(
immutable_data_size_is_zero,
return_block,
load_immutable_data_block,
)?;
context.set_basic_block(load_immutable_data_block);
let output_pointer = context
.get_global(revive_runtime_api::immutable_data::GLOBAL_IMMUTABLE_DATA_POINTER)?
.value
.as_pointer_value();
context.build_runtime_call(
revive_runtime_api::polkavm_imports::GET_IMMUTABLE_DATA,
&[
context
.builder()
.build_ptr_to_int(output_pointer, context.xlen_type(), "ptr_to_xlen")?
.into(),
context
.builder()
.build_ptr_to_int(
immutable_data_size_pointer,
context.xlen_type(),
"ptr_to_xlen",
)?
.into(),
],
);
let bytes_written = context.builder().build_load(
context.xlen_type(),
immutable_data_size_pointer,
"bytes_written",
)?;
context.builder().build_store(
immutable_data_size_pointer,
context.xlen_type().const_zero(),
)?;
let overflow_block = context.append_basic_block("immutable_data_overflow");
let is_overflow = context.builder().build_int_compare(
inkwell::IntPredicate::UGT,
immutable_data_size.into_int_value(),
bytes_written.into_int_value(),
"is_overflow",
)?;
context.build_conditional_branch(is_overflow, overflow_block, return_block)?;
context.set_basic_block(overflow_block);
context.build_call(context.intrinsics().trap, &[], "invalid_trap");
context.build_unreachable();
context.set_basic_block(return_block);
context.build_return(None);
context.pop_debug_scope();
Ok(())
}
}
@@ -1,9 +1,11 @@
//! The front-end runtime functions. //! The front-end runtime functions.
pub mod arithmetics;
pub mod deploy_code; pub mod deploy_code;
pub mod entry; pub mod entry;
pub mod immutable_data_load; pub mod revive;
pub mod runtime_code; pub mod runtime_code;
pub mod sbrk;
/// The main entry function name. /// The main entry function name.
pub const FUNCTION_ENTRY: &str = "__entry"; pub const FUNCTION_ENTRY: &str = "__entry";
@@ -13,6 +15,3 @@ pub const FUNCTION_DEPLOY_CODE: &str = "__deploy";
/// The runtime code function name. /// The runtime code function name.
pub const FUNCTION_RUNTIME_CODE: &str = "__runtime"; pub const FUNCTION_RUNTIME_CODE: &str = "__runtime";
/// The immutable data load function name.
pub const FUNCTION_LOAD_IMMUTABLE_DATA: &str = "__immutable_data_load";
@@ -0,0 +1,147 @@
//! The revive compiler runtime functions.
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.
/// In practice, they should never exceed a register sized bit value.
/// However, we still protect against this possibility here: Heap index
/// offsets are generally untrusted and potentially represent valid
/// (but wrong) pointers when truncated.
pub struct WordToPointer;
impl<D> RuntimeFunction<D> for WordToPointer
where
D: Dependency + Clone,
{
const NAME: &'static str = "__revive_int_truncate";
const ATTRIBUTES: &'static [Attribute] = &[
Attribute::WillReturn,
Attribute::NoFree,
Attribute::AlwaysInline,
];
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
context
.xlen_type()
.fn_type(&[context.word_type().into()], false)
}
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx, D>,
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>> {
let value = Self::paramater(context, 0).into_int_value();
let truncated =
context
.builder()
.build_int_truncate(value, context.xlen_type(), "offset_truncated")?;
let extended = context.builder().build_int_z_extend(
truncated,
context.word_type(),
"offset_extended",
)?;
let is_overflow = context.builder().build_int_compare(
inkwell::IntPredicate::NE,
value,
extended,
"compare_truncated_extended",
)?;
let block_continue = context.append_basic_block("offset_pointer_ok");
let block_trap = context.append_basic_block("offset_pointer_overflow");
context.build_conditional_branch(is_overflow, block_trap, block_continue)?;
context.set_basic_block(block_trap);
context.build_call(context.intrinsics().trap, &[], "invalid_trap");
context.build_unreachable();
context.set_basic_block(block_continue);
Ok(Some(truncated.as_basic_value_enum()))
}
}
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)
}
fn into_llvm(self, context: &mut Context<D>) -> 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,
{
const NAME: &'static str = "__revive_exit";
const ATTRIBUTES: &'static [Attribute] = &[
Attribute::NoReturn,
Attribute::NoFree,
Attribute::AlwaysInline,
];
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
context.void_type().fn_type(
&[
context.xlen_type().into(),
context.word_type().into(),
context.word_type().into(),
],
false,
)
}
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx, D>,
) -> 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();
let length = Self::paramater(context, 2).into_int_value();
let offset_truncated = context.safe_truncate_int_to_xlen(offset)?;
let length_truncated = context.safe_truncate_int_to_xlen(length)?;
let heap_pointer = context.build_heap_gep(offset_truncated, length_truncated)?;
let offset_pointer = context.builder().build_ptr_to_int(
heap_pointer.value,
context.xlen_type(),
"return_data_ptr_to_int",
)?;
context.build_runtime_call(
revive_runtime_api::polkavm_imports::RETURN,
&[flags.into(), offset_pointer.into(), length_truncated.into()],
);
context.build_unreachable();
Ok(None)
}
}
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)
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::emit(&self, context)
}
}
@@ -0,0 +1,144 @@
//! Emulates the linear EVM heap memory via a simulated `sbrk` system call.
use inkwell::values::BasicValue;
use crate::polkavm::context::attribute::Attribute;
use crate::polkavm::context::runtime::RuntimeFunction;
use crate::polkavm::context::Context;
use crate::polkavm::Dependency;
use crate::polkavm::WriteLLVM;
/// Simulates the `sbrk` system call, reproducing the semantics of the EVM heap memory.
///
/// Parameters:
/// - The `offset` into the emulated EVM heap memory.
/// - The `size` of the allocation emulated EVM heap memory.
///
/// Returns:
/// - A pointer to the EVM heap memory at given `offset`.
///
/// Semantics:
/// - Traps if the offset is out of bounds.
/// - Aligns the total heap memory size to the EVM word size.
/// - Traps if the memory size would be greater than the configured EVM heap memory size.
/// - Maintains the total memory size (`msize`) in global heap size value.
pub struct Sbrk;
impl<D> RuntimeFunction<D> for Sbrk
where
D: Dependency + Clone,
{
const NAME: &'static str = "__sbrk_internal";
const ATTRIBUTES: &'static [Attribute] = &[
Attribute::NoFree,
Attribute::NoRecurse,
Attribute::WillReturn,
];
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
context.llvm().ptr_type(Default::default()).fn_type(
&[context.xlen_type().into(), context.xlen_type().into()],
false,
)
}
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx, D>,
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>> {
let offset = Self::paramater(context, 0).into_int_value();
let size = Self::paramater(context, 1).into_int_value();
let trap_block = context.append_basic_block("trap");
let offset_in_bounds_block = context.append_basic_block("offset_in_bounds");
let is_offset_out_of_bounds = context.builder().build_int_compare(
inkwell::IntPredicate::UGE,
offset,
context.heap_size(),
"offset_out_of_bounds",
)?;
context.build_conditional_branch(
is_offset_out_of_bounds,
trap_block,
offset_in_bounds_block,
)?;
context.set_basic_block(trap_block);
context.build_call(context.intrinsics().trap, &[], "invalid_trap");
context.build_unreachable();
context.set_basic_block(offset_in_bounds_block);
let mask = context
.xlen_type()
.const_int(revive_common::BYTE_LENGTH_WORD as u64 - 1, false);
let total_size = context
.builder()
.build_int_add(offset, size, "total_size")?;
let memory_size = context.builder().build_and(
context.builder().build_int_add(total_size, mask, "mask")?,
context.builder().build_not(mask, "mask_not")?,
"memory_size",
)?;
let size_in_bounds_block = context.append_basic_block("size_in_bounds");
let is_size_out_of_bounds = context.builder().build_int_compare(
inkwell::IntPredicate::UGT,
memory_size,
context.heap_size(),
"size_out_of_bounds",
)?;
context.build_conditional_branch(
is_size_out_of_bounds,
trap_block,
size_in_bounds_block,
)?;
context.set_basic_block(size_in_bounds_block);
let return_block = context.append_basic_block("return_pointer");
let new_size_block = context.append_basic_block("new_size");
let is_new_size = context.builder().build_int_compare(
inkwell::IntPredicate::UGT,
memory_size,
context
.get_global_value(crate::polkavm::GLOBAL_HEAP_SIZE)?
.into_int_value(),
"is_new_size",
)?;
context.build_conditional_branch(is_new_size, new_size_block, return_block)?;
context.set_basic_block(new_size_block);
context.build_store(
context.get_global(crate::polkavm::GLOBAL_HEAP_SIZE)?.into(),
memory_size,
)?;
context.build_unconditional_branch(return_block);
context.set_basic_block(return_block);
Ok(Some(
context
.build_gep(
context
.get_global(crate::polkavm::GLOBAL_HEAP_MEMORY)?
.into(),
&[context.xlen_type().const_zero(), offset],
context.byte_type(),
"allocation_start_pointer",
)
.value
.as_basic_value_enum(),
))
}
}
impl<D> WriteLLVM<D> for Sbrk
where
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::declare(self, context)
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::emit(&self, context)
}
}
+97 -231
View File
@@ -10,6 +10,7 @@ pub mod function;
pub mod global; pub mod global;
pub mod r#loop; pub mod r#loop;
pub mod pointer; pub mod pointer;
pub mod runtime;
pub mod solidity_data; pub mod solidity_data;
pub mod yul_data; pub mod yul_data;
@@ -25,12 +26,16 @@ use inkwell::debug_info::DIScope;
use inkwell::types::BasicType; use inkwell::types::BasicType;
use inkwell::values::BasicValue; use inkwell::values::BasicValue;
use crate::memory::MemoryConfig;
use crate::optimizer::settings::Settings as OptimizerSettings; use crate::optimizer::settings::Settings as OptimizerSettings;
use crate::optimizer::Optimizer; use crate::optimizer::Optimizer;
use crate::polkavm::DebugConfig; use crate::polkavm::DebugConfig;
use crate::polkavm::Dependency; use crate::polkavm::Dependency;
use crate::target_machine::target::Target; use crate::target_machine::target::Target;
use crate::target_machine::TargetMachine; use crate::target_machine::TargetMachine;
use crate::PolkaVMLoadHeapWordFunction;
use crate::PolkaVMSbrkFunction;
use crate::PolkaVMStoreHeapWordFunction;
use self::address_space::AddressSpace; use self::address_space::AddressSpace;
use self::attribute::Attribute; use self::attribute::Attribute;
@@ -41,10 +46,13 @@ use self::function::declaration::Declaration as FunctionDeclaration;
use self::function::intrinsics::Intrinsics; use self::function::intrinsics::Intrinsics;
use self::function::llvm_runtime::LLVMRuntime; use self::function::llvm_runtime::LLVMRuntime;
use self::function::r#return::Return as FunctionReturn; use self::function::r#return::Return as FunctionReturn;
use self::function::runtime::revive::Exit;
use self::function::runtime::revive::WordToPointer;
use self::function::Function; use self::function::Function;
use self::global::Global; use self::global::Global;
use self::pointer::Pointer; use self::pointer::Pointer;
use self::r#loop::Loop; use self::r#loop::Loop;
use self::runtime::RuntimeFunction;
use self::solidity_data::SolidityData; use self::solidity_data::SolidityData;
use self::yul_data::YulData; use self::yul_data::YulData;
@@ -77,6 +85,10 @@ where
current_function: Option<Rc<RefCell<Function<'ctx>>>>, current_function: Option<Rc<RefCell<Function<'ctx>>>>,
/// The loop context stack. /// The loop context stack.
loop_stack: Vec<Loop<'ctx>>, loop_stack: Vec<Loop<'ctx>>,
/// The extra LLVM arguments that were used during target initialization.
llvm_arguments: &'ctx [String],
/// The PVM memory configuration.
memory_config: MemoryConfig,
/// The project dependency manager. It can be any entity implementing the trait. /// The project dependency manager. It can be any entity implementing the trait.
/// The manager is used to get information about contracts and their dependencies during /// The manager is used to get information about contracts and their dependencies during
@@ -108,9 +120,6 @@ where
/// The loop stack default capacity. /// The loop stack default capacity.
const LOOP_STACK_INITIAL_CAPACITY: usize = 16; const LOOP_STACK_INITIAL_CAPACITY: usize = 16;
/// The PolkaVM minimum stack size.
const POLKAVM_STACK_SIZE: u32 = 0x4000;
/// Link in the stdlib module. /// Link in the stdlib module.
fn link_stdlib_module( fn link_stdlib_module(
llvm: &'ctx inkwell::context::Context, llvm: &'ctx inkwell::context::Context,
@@ -210,6 +219,7 @@ where
} }
/// Initializes a new LLVM context. /// Initializes a new LLVM context.
#[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
llvm: &'ctx inkwell::context::Context, llvm: &'ctx inkwell::context::Context,
module: inkwell::module::Module<'ctx>, module: inkwell::module::Module<'ctx>,
@@ -217,11 +227,13 @@ where
dependency_manager: Option<D>, dependency_manager: Option<D>,
include_metadata_hash: bool, include_metadata_hash: bool,
debug_config: DebugConfig, debug_config: DebugConfig,
llvm_arguments: &'ctx [String],
memory_config: MemoryConfig,
) -> Self { ) -> Self {
Self::set_data_layout(llvm, &module); Self::set_data_layout(llvm, &module);
Self::link_stdlib_module(llvm, &module); Self::link_stdlib_module(llvm, &module);
Self::link_polkavm_imports(llvm, &module); Self::link_polkavm_imports(llvm, &module);
Self::set_polkavm_stack_size(llvm, &module, Self::POLKAVM_STACK_SIZE); Self::set_polkavm_stack_size(llvm, &module, memory_config.stack_size);
Self::set_module_flags(llvm, &module); Self::set_module_flags(llvm, &module);
let intrinsics = Intrinsics::new(llvm, &module); let intrinsics = Intrinsics::new(llvm, &module);
@@ -244,6 +256,8 @@ where
functions: HashMap::with_capacity(Self::FUNCTIONS_HASHMAP_INITIAL_CAPACITY), functions: HashMap::with_capacity(Self::FUNCTIONS_HASHMAP_INITIAL_CAPACITY),
current_function: None, current_function: None,
loop_stack: Vec::with_capacity(Self::LOOP_STACK_INITIAL_CAPACITY), loop_stack: Vec::with_capacity(Self::LOOP_STACK_INITIAL_CAPACITY),
llvm_arguments,
memory_config,
dependency_manager, dependency_manager,
include_metadata_hash, include_metadata_hash,
@@ -633,6 +647,8 @@ where
self.optimizer.settings().to_owned(), self.optimizer.settings().to_owned(),
self.include_metadata_hash, self.include_metadata_hash,
self.debug_config.clone(), self.debug_config.clone(),
self.llvm_arguments,
self.memory_config,
) )
}) })
} }
@@ -721,6 +737,7 @@ where
name: &str, name: &str,
) -> Pointer<'ctx> { ) -> Pointer<'ctx> {
let pointer = self.builder.build_alloca(r#type, name).unwrap(); let pointer = self.builder.build_alloca(r#type, name).unwrap();
pointer pointer
.as_instruction() .as_instruction()
.unwrap() .unwrap()
@@ -738,7 +755,9 @@ where
address: inkwell::values::IntValue<'ctx>, address: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<Pointer<'ctx>> { ) -> anyhow::Result<Pointer<'ctx>> {
let address_type = self.integer_type(revive_common::BIT_LENGTH_ETH_ADDRESS); let address_type = self.integer_type(revive_common::BIT_LENGTH_ETH_ADDRESS);
let address_pointer = self.build_alloca_at_entry(address_type, "address_pointer"); let address_pointer = self
.get_global(crate::polkavm::GLOBAL_ADDRESS_SPILL_BUFFER)?
.into();
let address_truncated = let address_truncated =
self.builder() self.builder()
.build_int_truncate(address, address_type, "address_truncated")?; .build_int_truncate(address, address_type, "address_truncated")?;
@@ -768,60 +787,18 @@ where
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>> { ) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>> {
match pointer.address_space { match pointer.address_space {
AddressSpace::Heap => { AddressSpace::Heap => {
let heap_pointer = self.build_heap_gep( let name = <PolkaVMLoadHeapWordFunction as RuntimeFunction<D>>::NAME;
self.builder().build_ptr_to_int( let declaration =
pointer.value, <PolkaVMLoadHeapWordFunction as RuntimeFunction<D>>::declaration(self);
self.xlen_type(), let arguments = [self
"offset_ptrtoint",
)?,
pointer
.r#type
.size_of()
.expect("should be IntValue")
.const_truncate(self.xlen_type()),
)?;
let value = self
.builder() .builder()
.build_load(pointer.r#type, heap_pointer.value, name)?; .build_ptr_to_int(pointer.value, self.xlen_type(), "offset_ptrtoint")?
self.basic_block() .as_basic_value_enum()];
.get_last_instruction() Ok(self
.expect("Always exists") .build_call(declaration, &arguments, "heap_load")
.set_alignment(revive_common::BYTE_LENGTH_BYTE as u32) .unwrap_or_else(|| {
.expect("Alignment is valid"); panic!("revive runtime function {name} should return a value")
}))
self.build_byte_swap(value)
}
AddressSpace::Storage | AddressSpace::TransientStorage => {
let storage_value_pointer =
self.build_alloca(self.word_type(), "storage_value_pointer");
self.build_store(storage_value_pointer, self.word_const(0))?;
let storage_value_length_pointer =
self.build_alloca(self.xlen_type(), "storage_value_length_pointer");
self.build_store(
storage_value_length_pointer,
self.word_const(revive_common::BIT_LENGTH_WORD as u64),
)?;
let transient = pointer.address_space == AddressSpace::TransientStorage;
self.build_runtime_call(
revive_runtime_api::polkavm_imports::GET_STORAGE,
&[
self.xlen_type().const_int(transient as u64, false).into(),
pointer.to_int(self).into(),
self.xlen_type().const_all_ones().into(),
storage_value_pointer.to_int(self).into(),
storage_value_length_pointer.to_int(self).into(),
],
);
// We do not to check the return value.
// Solidity assumes infallible SLOAD.
// If a key doesn't exist the "zero" value is returned.
self.build_load(storage_value_pointer, "storage_value_load")
} }
AddressSpace::Stack => { AddressSpace::Stack => {
let value = self let value = self
@@ -847,60 +824,13 @@ where
{ {
match pointer.address_space { match pointer.address_space {
AddressSpace::Heap => { AddressSpace::Heap => {
let heap_pointer = self.build_heap_gep( let declaration =
self.builder().build_ptr_to_int( <PolkaVMStoreHeapWordFunction as RuntimeFunction<D>>::declaration(self);
pointer.value, let arguments = [
self.xlen_type(), pointer.to_int(self).as_basic_value_enum(),
"offset_ptrtoint", value.as_basic_value_enum(),
)?, ];
value self.build_call(declaration, &arguments, "heap_store");
.as_basic_value_enum()
.get_type()
.size_of()
.expect("should be IntValue")
.const_truncate(self.xlen_type()),
)?;
let value = value.as_basic_value_enum();
let value = match value.get_type().into_int_type().get_bit_width() as usize {
revive_common::BIT_LENGTH_WORD => self.build_byte_swap(value)?,
revive_common::BIT_LENGTH_BYTE => value,
_ => unreachable!("Only word and byte sized values can be stored on EVM heap"),
};
self.builder
.build_store(heap_pointer.value, value)?
.set_alignment(revive_common::BYTE_LENGTH_BYTE as u32)
.expect("Alignment is valid");
}
AddressSpace::Storage | AddressSpace::TransientStorage => {
assert_eq!(
value.as_basic_value_enum().get_type(),
self.word_type().as_basic_type_enum()
);
let storage_value_pointer = self.build_alloca(self.word_type(), "storage_value");
let storage_value_pointer_casted = self.builder().build_ptr_to_int(
storage_value_pointer.value,
self.xlen_type(),
"storage_value_pointer_casted",
)?;
self.builder()
.build_store(storage_value_pointer.value, value)?;
let transient = pointer.address_space == AddressSpace::TransientStorage;
self.build_runtime_call(
revive_runtime_api::polkavm_imports::SET_STORAGE,
&[
self.xlen_type().const_int(transient as u64, false).into(),
pointer.to_int(self).into(),
self.xlen_type().const_all_ones().into(),
storage_value_pointer_casted.into(),
self.integer_const(crate::polkavm::XLEN, 32).into(),
],
);
} }
AddressSpace::Stack => { AddressSpace::Stack => {
let instruction = self.builder.build_store(pointer.value, value).unwrap(); let instruction = self.builder.build_store(pointer.value, value).unwrap();
@@ -946,9 +876,6 @@ where
where where
T: BasicType<'ctx>, T: BasicType<'ctx>,
{ {
assert_ne!(pointer.address_space, AddressSpace::Storage);
assert_ne!(pointer.address_space, AddressSpace::TransientStorage);
let value = unsafe { let value = unsafe {
self.builder self.builder
.build_gep(pointer.r#type, pointer.value, indexes, name) .build_gep(pointer.r#type, pointer.value, indexes, name)
@@ -1115,38 +1042,16 @@ where
offset: inkwell::values::IntValue<'ctx>, offset: inkwell::values::IntValue<'ctx>,
length: inkwell::values::IntValue<'ctx>, length: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let offset_truncated = self.safe_truncate_int_to_xlen(offset)?; self.build_call(
let length_truncated = self.safe_truncate_int_to_xlen(length)?; <Exit as RuntimeFunction<D>>::declaration(self),
let offset_into_heap = self.build_heap_gep(offset_truncated, length_truncated)?; &[flags.into(), offset.into(), length.into()],
"exit",
let length_pointer = self.safe_truncate_int_to_xlen(length)?;
let offset_pointer = self.builder().build_ptr_to_int(
offset_into_heap.value,
self.xlen_type(),
"return_data_ptr_to_int",
)?;
self.build_runtime_call(
revive_runtime_api::polkavm_imports::RETURN,
&[flags.into(), offset_pointer.into(), length_pointer.into()],
); );
self.build_unreachable();
Ok(()) Ok(())
} }
/// Truncate a memory offset to register size, trapping if it doesn't fit. /// Truncate a memory offset to register size, trapping if it doesn't fit.
/// Pointers are represented as opaque 256 bit integer values in EVM.
/// In practice, they should never exceed a register sized bit value.
/// However, we still protect against this possibility here. Heap index
/// offsets are generally untrusted and potentially represent valid
/// (but wrong) pointers when truncated.
///
/// TODO: Splitting up into a dedicated function
/// could potentially decrease code sizes (LLVM can still decide to inline).
/// However, passing i256 parameters is counter productive and
/// I've found that splitting it up actualy increases code size.
/// Should be reviewed after 64bit support.
pub fn safe_truncate_int_to_xlen( pub fn safe_truncate_int_to_xlen(
&self, &self,
value: inkwell::values::IntValue<'ctx>, value: inkwell::values::IntValue<'ctx>,
@@ -1160,29 +1065,19 @@ where
"expected XLEN or WORD sized int type for memory offset", "expected XLEN or WORD sized int type for memory offset",
); );
let truncated = Ok(self
self.builder() .build_call(
.build_int_truncate(value, self.xlen_type(), "offset_truncated")?; <WordToPointer as RuntimeFunction<D>>::declaration(self),
let extended = &[value.into()],
self.builder() "word_to_pointer",
.build_int_z_extend(truncated, self.word_type(), "offset_extended")?; )
let is_overflow = self.builder().build_int_compare( .unwrap_or_else(|| {
inkwell::IntPredicate::NE, panic!(
value, "revive runtime function {} should return a value",
extended, <WordToPointer as RuntimeFunction<D>>::NAME,
"compare_truncated_extended", )
)?; })
.into_int_value())
let block_continue = self.append_basic_block("offset_pointer_ok");
let block_trap = self.append_basic_block("offset_pointer_overflow");
self.build_conditional_branch(is_overflow, block_trap, block_continue)?;
self.set_basic_block(block_trap);
self.build_call(self.intrinsics().trap, &[], "invalid_trap");
self.build_unreachable();
self.set_basic_block(block_continue);
Ok(truncated)
} }
/// Build a call to PolkaVM `sbrk` for extending the heap from offset by `size`. /// Build a call to PolkaVM `sbrk` for extending the heap from offset by `size`.
@@ -1194,60 +1089,40 @@ where
offset: inkwell::values::IntValue<'ctx>, offset: inkwell::values::IntValue<'ctx>,
size: inkwell::values::IntValue<'ctx>, size: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::PointerValue<'ctx>> { ) -> anyhow::Result<inkwell::values::PointerValue<'ctx>> {
Ok(self let call_site_value = self.builder().build_call(
.builder() <PolkaVMSbrkFunction as RuntimeFunction<D>>::declaration(self).function_value(),
.build_call( &[offset.into(), size.into()],
self.runtime_api_method(revive_runtime_api::polkavm_imports::SBRK), "alloc_start",
&[offset.into(), size.into()], )?;
"call_sbrk",
)? call_site_value.add_attribute(
inkwell::attributes::AttributeLoc::Return,
self.llvm
.create_enum_attribute(Attribute::NonNull as u32, 0),
);
call_site_value.add_attribute(
inkwell::attributes::AttributeLoc::Return,
self.llvm
.create_enum_attribute(Attribute::NoUndef as u32, 0),
);
Ok(call_site_value
.try_as_basic_value() .try_as_basic_value()
.left() .left()
.expect("sbrk returns a pointer") .unwrap_or_else(|| {
panic!(
"revive runtime function {} should return a value",
<PolkaVMSbrkFunction as RuntimeFunction<D>>::NAME,
)
})
.into_pointer_value()) .into_pointer_value())
} }
/// Build a call to PolkaVM `msize` for querying the linear memory size. /// Build a call to PolkaVM `msize` for querying the linear memory size.
pub fn build_msize(&self) -> anyhow::Result<inkwell::values::IntValue<'ctx>> { pub fn build_msize(&self) -> anyhow::Result<inkwell::values::IntValue<'ctx>> {
let memory_size_pointer = self Ok(self
.module() .get_global_value(crate::polkavm::GLOBAL_HEAP_SIZE)?
.get_global(revive_runtime_api::polkavm_imports::MEMORY_SIZE) .into_int_value())
.expect("the memory size symbol should have been declared")
.as_pointer_value();
let memory_size_value = self.builder().build_load(
self.xlen_type(),
memory_size_pointer,
"memory_size_value",
)?;
Ok(memory_size_value.into_int_value())
}
/// Call PolkaVM `sbrk` for extending the heap by `offset` + `size`,
/// trapping the contract if the call failed.
pub fn build_heap_alloc(
&self,
offset: inkwell::values::IntValue<'ctx>,
size: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<()> {
let end_of_memory = self.build_sbrk(offset, size)?;
let return_is_nil = self.builder().build_int_compare(
inkwell::IntPredicate::EQ,
end_of_memory,
self.llvm().ptr_type(Default::default()).const_null(),
"compare_end_of_memory_nil",
)?;
let continue_block = self.append_basic_block("sbrk_not_nil");
let trap_block = self.append_basic_block("sbrk_nil");
self.build_conditional_branch(return_is_nil, trap_block, continue_block)?;
self.set_basic_block(trap_block);
self.build_call(self.intrinsics().trap, &[], "invalid_trap");
self.build_unreachable();
self.set_basic_block(continue_block);
Ok(())
} }
/// Returns a pointer to `offset` into the heap, allocating /// Returns a pointer to `offset` into the heap, allocating
@@ -1262,19 +1137,8 @@ where
assert_eq!(offset.get_type(), self.xlen_type()); assert_eq!(offset.get_type(), self.xlen_type());
assert_eq!(length.get_type(), self.xlen_type()); assert_eq!(length.get_type(), self.xlen_type());
self.build_heap_alloc(offset, length)?; let pointer = self.build_sbrk(offset, length)?;
Ok(Pointer::new(self.byte_type(), AddressSpace::Stack, pointer))
let heap_start = self
.module()
.get_global(revive_runtime_api::polkavm_imports::MEMORY)
.expect("the memory symbol should have been declared")
.as_pointer_value();
Ok(self.build_gep(
Pointer::new(self.byte_type(), AddressSpace::Stack, heap_start),
&[offset],
self.byte_type(),
"heap_offset_via_gep",
))
} }
/// Returns a boolean type constant. /// Returns a boolean type constant.
@@ -1445,13 +1309,6 @@ where
inkwell::attributes::AttributeLoc::Param(index as u32), inkwell::attributes::AttributeLoc::Param(index as u32),
self.llvm.create_enum_attribute(Attribute::NoFree as u32, 0), self.llvm.create_enum_attribute(Attribute::NoFree as u32, 0),
); );
if function == self.llvm_runtime().sha3 {
call_site_value.add_attribute(
inkwell::attributes::AttributeLoc::Param(index as u32),
self.llvm
.create_enum_attribute(Attribute::ReadOnly as u32, 0),
);
}
if Some(argument.get_type()) == function.r#type.get_return_type() { if Some(argument.get_type()) == function.r#type.get_return_type() {
if function if function
.r#type .r#type
@@ -1580,4 +1437,13 @@ where
anyhow::bail!("The immutable size data is not available"); anyhow::bail!("The immutable size data is not available");
} }
} }
pub fn optimizer_settings(&self) -> &OptimizerSettings {
self.optimizer.settings()
}
pub fn heap_size(&self) -> inkwell::values::IntValue<'ctx> {
self.xlen_type()
.const_int(self.memory_config.heap_size as u64, false)
}
} }
@@ -0,0 +1,110 @@
//! The revive simulated EVM linear memory pointer functions.
use inkwell::values::BasicValueEnum;
use crate::polkavm::context::runtime::RuntimeFunction;
use crate::polkavm::context::Context;
use crate::polkavm::Dependency;
use crate::polkavm::WriteLLVM;
/// Load a word size value from a heap pointer.
pub struct LoadWord;
impl<D> RuntimeFunction<D> for LoadWord
where
D: Dependency + Clone,
{
const NAME: &'static str = "__revive_load_heap_word";
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
context
.word_type()
.fn_type(&[context.xlen_type().into()], false)
}
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx, D>,
) -> anyhow::Result<Option<BasicValueEnum<'ctx>>> {
let offset = Self::paramater(context, 0).into_int_value();
let length = context
.xlen_type()
.const_int(revive_common::BYTE_LENGTH_WORD as u64, false);
let pointer = context.build_heap_gep(offset, length)?;
let value = context
.builder()
.build_load(context.word_type(), pointer.value, "value")?;
context
.basic_block()
.get_last_instruction()
.expect("Always exists")
.set_alignment(revive_common::BYTE_LENGTH_BYTE as u32)
.expect("Alignment is valid");
let swapped_value = context.build_byte_swap(value)?;
Ok(Some(swapped_value))
}
}
impl<D> WriteLLVM<D> for LoadWord
where
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::declare(self, context)
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::emit(&self, context)
}
}
/// Store a word size value through a heap pointer.
pub struct StoreWord;
impl<D> RuntimeFunction<D> for StoreWord
where
D: Dependency + Clone,
{
const NAME: &'static str = "__revive_store_heap_word";
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
context.void_type().fn_type(
&[context.xlen_type().into(), context.word_type().into()],
false,
)
}
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx, D>,
) -> anyhow::Result<Option<BasicValueEnum<'ctx>>> {
let offset = Self::paramater(context, 0).into_int_value();
let length = context
.xlen_type()
.const_int(revive_common::BYTE_LENGTH_WORD as u64, false);
let pointer = context.build_heap_gep(offset, length)?;
let value = context.build_byte_swap(Self::paramater(context, 1))?;
context
.builder()
.build_store(pointer.value, value)?
.set_alignment(revive_common::BYTE_LENGTH_BYTE as u32)
.expect("Alignment is valid");
Ok(None)
}
}
impl<D> WriteLLVM<D> for StoreWord
where
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::declare(self, context)
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::emit(&self, context)
}
}
@@ -7,6 +7,9 @@ use crate::polkavm::context::global::Global;
use crate::polkavm::context::Context; use crate::polkavm::context::Context;
use crate::polkavm::Dependency; use crate::polkavm::Dependency;
pub mod heap;
pub mod storage;
/// The LLVM pointer. /// The LLVM pointer.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct Pointer<'ctx> { pub struct Pointer<'ctx> {
@@ -0,0 +1,277 @@
//! The revive storage pointer functions.
use inkwell::values::BasicValueEnum;
use crate::polkavm::context::runtime::RuntimeFunction;
use crate::polkavm::context::Context;
use crate::polkavm::Dependency;
use crate::polkavm::WriteLLVM;
/// Load a word size value from a storage pointer.
pub struct LoadWord;
impl<D> RuntimeFunction<D> for LoadWord
where
D: Dependency + Clone,
{
const NAME: &'static str = "__revive_load_storage_word";
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
context
.word_type()
.fn_type(&[context.llvm().ptr_type(Default::default()).into()], false)
}
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx, D>,
) -> anyhow::Result<Option<BasicValueEnum<'ctx>>> {
Ok(Some(emit_load(
context,
Self::paramater(context, 0),
false,
)?))
}
}
impl<D> WriteLLVM<D> for LoadWord
where
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::declare(self, context)
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::emit(&self, context)
}
}
/// Load a word size value from a transient storage pointer.
pub struct LoadTransientWord;
impl<D> RuntimeFunction<D> for LoadTransientWord
where
D: Dependency + Clone,
{
const NAME: &'static str = "__revive_load_transient_storage_word";
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
context
.word_type()
.fn_type(&[context.llvm().ptr_type(Default::default()).into()], false)
}
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx, D>,
) -> anyhow::Result<Option<BasicValueEnum<'ctx>>> {
Ok(Some(emit_load(context, Self::paramater(context, 0), true)?))
}
}
impl<D> WriteLLVM<D> for LoadTransientWord
where
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::declare(self, context)
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::emit(&self, context)
}
}
/// Store a word size value through a storage pointer.
pub struct StoreWord;
impl<D> RuntimeFunction<D> for StoreWord
where
D: Dependency + Clone,
{
const NAME: &'static str = "__revive_store_storage_word";
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
context.void_type().fn_type(
&[
context.llvm().ptr_type(Default::default()).into(),
context.llvm().ptr_type(Default::default()).into(),
],
false,
)
}
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx, D>,
) -> anyhow::Result<Option<BasicValueEnum<'ctx>>> {
emit_store(
context,
Self::paramater(context, 0),
Self::paramater(context, 1),
false,
)?;
Ok(None)
}
}
impl<D> WriteLLVM<D> for StoreWord
where
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::declare(self, context)
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::emit(&self, context)
}
}
/// Store a word size value through a transient storage pointer.
pub struct StoreTransientWord;
impl<D> RuntimeFunction<D> for StoreTransientWord
where
D: Dependency + Clone,
{
const NAME: &'static str = "__revive_store_transient_storage_word";
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
context.void_type().fn_type(
&[
context.llvm().ptr_type(Default::default()).into(),
context.llvm().ptr_type(Default::default()).into(),
],
false,
)
}
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx, D>,
) -> anyhow::Result<Option<BasicValueEnum<'ctx>>> {
emit_store(
context,
Self::paramater(context, 0),
Self::paramater(context, 1),
true,
)?;
Ok(None)
}
}
impl<D> WriteLLVM<D> for StoreTransientWord
where
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> 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 emit_load<'ctx, D: Dependency + Clone>(
context: &mut Context<'ctx, D>,
key: BasicValueEnum<'ctx>,
transient: bool,
) -> anyhow::Result<BasicValueEnum<'ctx>> {
let mut key = context.build_load(
super::Pointer::new(
context.word_type(),
Default::default(),
key.into_pointer_value(),
),
"key",
)?;
if !transient {
key = context.build_byte_swap(key)?;
}
let key_pointer = context.build_alloca_at_entry(context.word_type(), "key_pointer");
let value_pointer = context.build_alloca_at_entry(context.word_type(), "value_pointer");
let length_pointer = context.build_alloca_at_entry(context.xlen_type(), "length_pointer");
context.builder().build_store(key_pointer.value, key)?;
context.build_store(value_pointer, context.word_const(0))?;
context.build_store(
length_pointer,
context
.xlen_type()
.const_int(revive_common::BYTE_LENGTH_WORD as u64, false),
)?;
let is_transient = context.xlen_type().const_int(transient as u64, false);
let arguments = [
is_transient.into(),
key_pointer.to_int(context).into(),
context.xlen_type().const_all_ones().into(),
value_pointer.to_int(context).into(),
length_pointer.to_int(context).into(),
];
context.build_runtime_call(revive_runtime_api::polkavm_imports::GET_STORAGE, &arguments);
// We do not to check the return value: Solidity assumes infallible loads.
// If a key doesn't exist the "zero" value is returned (ensured by above write).
let value = context.build_load(value_pointer, "storage_value")?;
Ok(if transient {
value
} else {
context.build_byte_swap(value)?
})
}
fn emit_store<'ctx, D: Dependency + Clone>(
context: &mut Context<'ctx, D>,
key: BasicValueEnum<'ctx>,
value: BasicValueEnum<'ctx>,
transient: bool,
) -> anyhow::Result<()> {
let mut key = context.build_load(
super::Pointer::new(
context.word_type(),
Default::default(),
key.into_pointer_value(),
),
"key",
)?;
let mut value = context.build_load(
super::Pointer::new(
context.word_type(),
Default::default(),
value.into_pointer_value(),
),
"key",
)?;
if !transient {
key = context.build_byte_swap(key)?;
value = context.build_byte_swap(value)?;
}
let key_pointer = context.build_alloca_at_entry(context.word_type(), "key_pointer");
let value_pointer = context.build_alloca_at_entry(context.word_type(), "value_pointer");
context.build_store(key_pointer, key)?;
context.build_store(value_pointer, value)?;
let is_transient = context.xlen_type().const_int(transient as u64, false);
let arguments = [
is_transient.into(),
key_pointer.to_int(context).into(),
context.xlen_type().const_all_ones().into(),
value_pointer.to_int(context).into(),
context.integer_const(crate::polkavm::XLEN, 32).into(),
];
context.build_runtime_call(revive_runtime_api::polkavm_imports::SET_STORAGE, &arguments);
Ok(())
}
@@ -0,0 +1,113 @@
//! The revive compiler runtime function interface definition.
//!
//! Common routines should not be inlined but extracted into smaller functions.
//! This benefits contract code size.
use crate::optimizer::settings::size_level::SizeLevel;
use crate::polkavm::context::function::declaration::Declaration;
use crate::polkavm::context::function::Function;
use crate::polkavm::context::Attribute;
use crate::polkavm::context::Context;
use crate::polkavm::Dependency;
/// The revive runtime function interface simplifies declaring runtime functions
/// and code emitting by providing helpful default implementations.
pub trait RuntimeFunction<D>
where
D: Dependency + Clone,
{
/// The function name.
const NAME: &'static str;
const ATTRIBUTES: &'static [Attribute] = &[
Attribute::NoFree,
Attribute::NoRecurse,
Attribute::WillReturn,
];
/// The function type.
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx>;
/// Declare the function.
fn declare(&self, context: &mut Context<D>) -> anyhow::Result<()> {
let function = context.add_function(
Self::NAME,
Self::r#type(context),
0,
Some(inkwell::module::Linkage::External),
)?;
let mut attributes = Self::ATTRIBUTES.to_vec();
attributes.extend_from_slice(match context.optimizer_settings().level_middle_end_size {
SizeLevel::Zero => &[],
_ => &[Attribute::OptimizeForSize, Attribute::MinSize],
});
Function::set_attributes(
context.llvm(),
function.borrow().declaration(),
&attributes,
true,
);
Ok(())
}
/// Get the function declaration.
fn declaration<'ctx>(context: &Context<'ctx, D>) -> Declaration<'ctx> {
context
.get_function(Self::NAME)
.unwrap_or_else(|| panic!("runtime function {} should be declared", Self::NAME))
.borrow()
.declaration()
}
/// Emit the function.
fn emit(&self, context: &mut Context<D>) -> anyhow::Result<()> {
context.set_current_function(Self::NAME, None)?;
context.set_basic_block(context.current_function().borrow().entry_block());
let return_value = self.emit_body(context)?;
self.emit_epilogue(context, return_value);
context.pop_debug_scope();
Ok(())
}
/// Emit the function body.
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx, D>,
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>>;
/// Emit the function return instructions.
fn emit_epilogue<'ctx>(
&self,
context: &mut Context<'ctx, D>,
return_value: Option<inkwell::values::BasicValueEnum<'ctx>>,
) {
let return_block = context.current_function().borrow().return_block();
context.build_unconditional_branch(return_block);
context.set_basic_block(return_block);
match return_value {
Some(value) => context.build_return(Some(&value)),
None => context.build_return(None),
}
}
/// Get the nth function paramater.
fn paramater<'ctx>(
context: &Context<'ctx, D>,
index: usize,
) -> inkwell::values::BasicValueEnum<'ctx> {
let name = Self::NAME;
context
.get_function(name)
.unwrap_or_else(|| panic!("runtime function {name} should be declared"))
.borrow()
.declaration()
.function_value()
.get_nth_param(index as u32)
.unwrap_or_else(|| panic!("runtime function {name} should have parameter #{index}"))
}
}
@@ -10,12 +10,21 @@ pub fn create_context(
llvm: &inkwell::context::Context, llvm: &inkwell::context::Context,
optimizer_settings: OptimizerSettings, optimizer_settings: OptimizerSettings,
) -> Context<DummyDependency> { ) -> Context<DummyDependency> {
crate::polkavm::initialize_target(); crate::initialize_llvm(crate::Target::PVM, "resolc", Default::default());
let module = llvm.create_module("test"); let module = llvm.create_module("test");
let optimizer = Optimizer::new(optimizer_settings); let optimizer = Optimizer::new(optimizer_settings);
Context::<DummyDependency>::new(llvm, module, optimizer, None, true, Default::default()) Context::<DummyDependency>::new(
llvm,
module,
optimizer,
None,
true,
Default::default(),
Default::default(),
Default::default(),
)
} }
#[test] #[test]
+25 -107
View File
@@ -2,8 +2,13 @@
use inkwell::values::BasicValue; use inkwell::values::BasicValue;
use crate::polkavm::context::runtime::RuntimeFunction;
use crate::polkavm::context::Context; use crate::polkavm::context::Context;
use crate::polkavm::Dependency; use crate::polkavm::Dependency;
use crate::PolkaVMDivisionFunction;
use crate::PolkaVMRemainderFunction;
use crate::PolkaVMSignedDivisionFunction;
use crate::PolkaVMSignedRemainderFunction;
/// Translates the arithmetic addition. /// Translates the arithmetic addition.
pub fn addition<'ctx, D>( pub fn addition<'ctx, D>(
@@ -59,11 +64,11 @@ pub fn division<'ctx, D>(
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
wrapped_division(context, operand_2, || { let name = <PolkaVMDivisionFunction as RuntimeFunction<D>>::NAME;
Ok(context let declaration = <PolkaVMDivisionFunction as RuntimeFunction<D>>::declaration(context);
.builder() Ok(context
.build_int_unsigned_div(operand_1, operand_2, "DIV")?) .build_call(declaration, &[operand_1.into(), operand_2.into()], "div")
}) .unwrap_or_else(|| panic!("revive runtime function {name} should return a value",)))
} }
/// Translates the arithmetic remainder. /// Translates the arithmetic remainder.
@@ -75,11 +80,11 @@ pub fn remainder<'ctx, D>(
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
wrapped_division(context, operand_2, || { let name = <PolkaVMRemainderFunction as RuntimeFunction<D>>::NAME;
Ok(context let declaration = <PolkaVMRemainderFunction as RuntimeFunction<D>>::declaration(context);
.builder() Ok(context
.build_int_unsigned_rem(operand_1, operand_2, "MOD")?) .build_call(declaration, &[operand_1.into(), operand_2.into()], "rem")
}) .unwrap_or_else(|| panic!("revive runtime function {name} should return a value",)))
} }
/// Translates the signed arithmetic division. /// Translates the signed arithmetic division.
@@ -94,54 +99,11 @@ pub fn division_signed<'ctx, D>(
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
assert_eq!( let name = <PolkaVMSignedDivisionFunction as RuntimeFunction<D>>::NAME;
operand_2.get_type().get_bit_width(), let declaration = <PolkaVMSignedDivisionFunction as RuntimeFunction<D>>::declaration(context);
revive_common::BIT_LENGTH_WORD as u32 Ok(context
); .build_call(declaration, &[operand_1.into(), operand_2.into()], "sdiv")
.unwrap_or_else(|| panic!("revive runtime function {name} should return a value",)))
let block_calculate = context.append_basic_block("calculate");
let block_overflow = context.append_basic_block("overflow");
let block_select = context.append_basic_block("select_result");
let block_origin = context.basic_block();
context.builder().build_switch(
operand_2,
block_calculate,
&[
(context.word_type().const_zero(), block_select),
(context.word_type().const_all_ones(), block_overflow),
],
)?;
context.set_basic_block(block_calculate);
let quotient = context
.builder()
.build_int_signed_div(operand_1, operand_2, "SDIV")?;
context.build_unconditional_branch(block_select);
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.word_type(),
"max_uint",
)?;
let is_operand_1_overflow = context.builder().build_int_compare(
inkwell::IntPredicate::EQ,
operand_1,
context.builder().build_int_neg(max_uint, "min_uint")?,
"is_operand_1_overflow",
)?;
context.build_conditional_branch(is_operand_1_overflow, block_select, block_calculate)?;
context.set_basic_block(block_select);
let result = context.builder().build_phi(context.word_type(), "result")?;
result.add_incoming(&[
(&operand_1, block_overflow),
(&context.word_const(0), block_origin),
(&quotient.as_basic_value_enum(), block_calculate),
]);
Ok(result.as_basic_value())
} }
/// Translates the signed arithmetic remainder. /// Translates the signed arithmetic remainder.
@@ -153,53 +115,9 @@ pub fn remainder_signed<'ctx, D>(
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
wrapped_division(context, operand_2, || { let name = <PolkaVMSignedRemainderFunction as RuntimeFunction<D>>::NAME;
Ok(context let declaration = <PolkaVMSignedRemainderFunction as RuntimeFunction<D>>::declaration(context);
.builder() Ok(context
.build_int_signed_rem(operand_1, operand_2, "SMOD")?) .build_call(declaration, &[operand_1.into(), operand_2.into()], "srem")
}) .unwrap_or_else(|| panic!("revive runtime function {name} should return a value",)))
}
/// Wrap division operations so that zero will be returned if the
/// denominator is zero (see also Ethereum YP Appendix H.2).
///
/// The closure is expected to calculate and return the quotient.
///
/// The result is either the calculated quotient or zero,
/// selected at runtime.
fn wrapped_division<'ctx, D, F, T>(
context: &Context<'ctx, D>,
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>,
{
assert_eq!(
denominator.get_type().get_bit_width(),
revive_common::BIT_LENGTH_WORD as u32
);
let block_calculate = context.append_basic_block("calculate");
let block_select = context.append_basic_block("select");
let block_origin = context.basic_block();
context.builder().build_switch(
denominator,
block_calculate,
&[(context.word_const(0), block_select)],
)?;
context.set_basic_block(block_calculate);
let calculated_value = f()?.as_basic_value_enum();
context.build_unconditional_branch(block_select);
context.set_basic_block(block_select);
let result = context.builder().build_phi(context.word_type(), "result")?;
result.add_incoming(&[
(&context.word_const(0), block_origin),
(&calculated_value, block_calculate),
]);
Ok(result.as_basic_value())
} }
+28 -14
View File
@@ -63,7 +63,6 @@ where
let non_overflow_block = context.append_basic_block("shift_left_non_overflow"); let non_overflow_block = context.append_basic_block("shift_left_non_overflow");
let join_block = context.append_basic_block("shift_left_join"); let join_block = context.append_basic_block("shift_left_join");
let result_pointer = context.build_alloca(context.word_type(), "shift_left_result_pointer");
let condition_is_overflow = context.builder().build_int_compare( let condition_is_overflow = context.builder().build_int_compare(
inkwell::IntPredicate::UGT, inkwell::IntPredicate::UGT,
shift, shift,
@@ -73,7 +72,6 @@ where
context.build_conditional_branch(condition_is_overflow, overflow_block, non_overflow_block)?; context.build_conditional_branch(condition_is_overflow, overflow_block, non_overflow_block)?;
context.set_basic_block(overflow_block); context.set_basic_block(overflow_block);
context.build_store(result_pointer, context.word_const(0))?;
context.build_unconditional_branch(join_block); context.build_unconditional_branch(join_block);
context.set_basic_block(non_overflow_block); context.set_basic_block(non_overflow_block);
@@ -81,11 +79,17 @@ where
context context
.builder() .builder()
.build_left_shift(value, shift, "shift_left_non_overflow_result")?; .build_left_shift(value, shift, "shift_left_non_overflow_result")?;
context.build_store(result_pointer, value)?;
context.build_unconditional_branch(join_block); context.build_unconditional_branch(join_block);
context.set_basic_block(join_block); context.set_basic_block(join_block);
context.build_load(result_pointer, "shift_left_result") let result = context
.builder()
.build_phi(context.word_type(), "shift_left_value")?;
result.add_incoming(&[
(&value, non_overflow_block),
(&context.word_const(0), overflow_block),
]);
Ok(result.as_basic_value())
} }
/// Translates the bitwise shift right. /// Translates the bitwise shift right.
@@ -101,7 +105,6 @@ where
let non_overflow_block = context.append_basic_block("shift_right_non_overflow"); let non_overflow_block = context.append_basic_block("shift_right_non_overflow");
let join_block = context.append_basic_block("shift_right_join"); let join_block = context.append_basic_block("shift_right_join");
let result_pointer = context.build_alloca(context.word_type(), "shift_right_result_pointer");
let condition_is_overflow = context.builder().build_int_compare( let condition_is_overflow = context.builder().build_int_compare(
inkwell::IntPredicate::UGT, inkwell::IntPredicate::UGT,
shift, shift,
@@ -111,7 +114,6 @@ where
context.build_conditional_branch(condition_is_overflow, overflow_block, non_overflow_block)?; context.build_conditional_branch(condition_is_overflow, overflow_block, non_overflow_block)?;
context.set_basic_block(overflow_block); context.set_basic_block(overflow_block);
context.build_store(result_pointer, context.word_const(0))?;
context.build_unconditional_branch(join_block); context.build_unconditional_branch(join_block);
context.set_basic_block(non_overflow_block); context.set_basic_block(non_overflow_block);
@@ -121,11 +123,17 @@ where
false, false,
"shift_right_non_overflow_result", "shift_right_non_overflow_result",
)?; )?;
context.build_store(result_pointer, value)?;
context.build_unconditional_branch(join_block); context.build_unconditional_branch(join_block);
context.set_basic_block(join_block); context.set_basic_block(join_block);
context.build_load(result_pointer, "shift_right_result") let result = context
.builder()
.build_phi(context.word_type(), "shift_right_value")?;
result.add_incoming(&[
(&value, non_overflow_block),
(&context.word_const(0), overflow_block),
]);
Ok(result.as_basic_value())
} }
/// Translates the arithmetic bitwise shift right. /// Translates the arithmetic bitwise shift right.
@@ -145,8 +153,6 @@ where
let non_overflow_block = context.append_basic_block("shift_right_arithmetic_non_overflow"); let non_overflow_block = context.append_basic_block("shift_right_arithmetic_non_overflow");
let join_block = context.append_basic_block("shift_right_arithmetic_join"); let join_block = context.append_basic_block("shift_right_arithmetic_join");
let result_pointer =
context.build_alloca(context.word_type(), "shift_right_arithmetic_result_pointer");
let condition_is_overflow = context.builder().build_int_compare( let condition_is_overflow = context.builder().build_int_compare(
inkwell::IntPredicate::UGT, inkwell::IntPredicate::UGT,
shift, shift,
@@ -174,11 +180,9 @@ where
)?; )?;
context.set_basic_block(overflow_positive_block); context.set_basic_block(overflow_positive_block);
context.build_store(result_pointer, context.word_const(0))?;
context.build_unconditional_branch(join_block); context.build_unconditional_branch(join_block);
context.set_basic_block(overflow_negative_block); context.set_basic_block(overflow_negative_block);
context.build_store(result_pointer, context.word_type().const_all_ones())?;
context.build_unconditional_branch(join_block); context.build_unconditional_branch(join_block);
context.set_basic_block(non_overflow_block); context.set_basic_block(non_overflow_block);
@@ -188,11 +192,21 @@ where
true, true,
"shift_right_arithmetic_non_overflow_result", "shift_right_arithmetic_non_overflow_result",
)?; )?;
context.build_store(result_pointer, value)?;
context.build_unconditional_branch(join_block); context.build_unconditional_branch(join_block);
context.set_basic_block(join_block); context.set_basic_block(join_block);
context.build_load(result_pointer, "shift_right_arithmetic_result") let result = context
.builder()
.build_phi(context.word_type(), "shift_arithmetic_right_value")?;
result.add_incoming(&[
(&value, non_overflow_block),
(
&context.word_type().const_all_ones(),
overflow_negative_block,
),
(&context.word_const(0), overflow_block),
]);
Ok(result.as_basic_value())
} }
/// Translates the `byte` instruction, extracting the byte of `operand_2` /// Translates the `byte` instruction, extracting the byte of `operand_2`
+16 -16
View File
@@ -2,6 +2,7 @@
use inkwell::values::BasicValue; use inkwell::values::BasicValue;
use crate::polkavm::context::pointer::Pointer;
use crate::polkavm::context::Context; use crate::polkavm::context::Context;
use crate::polkavm::Dependency; use crate::polkavm::Dependency;
@@ -49,7 +50,9 @@ where
D: Dependency + Clone, D: Dependency + Clone,
{ {
let address_type = context.integer_type(revive_common::BIT_LENGTH_ETH_ADDRESS); let address_type = context.integer_type(revive_common::BIT_LENGTH_ETH_ADDRESS);
let address_pointer = context.build_alloca_at_entry(address_type, "origin_address"); let address_pointer: Pointer<'_> = context
.get_global(crate::polkavm::GLOBAL_ADDRESS_SPILL_BUFFER)?
.into();
context.build_store(address_pointer, address_type.const_zero())?; context.build_store(address_pointer, address_type.const_zero())?;
context.build_runtime_call( context.build_runtime_call(
revive_runtime_api::polkavm_imports::ORIGIN, revive_runtime_api::polkavm_imports::ORIGIN,
@@ -97,13 +100,13 @@ where
D: Dependency + Clone, D: Dependency + Clone,
{ {
let output_pointer = context.build_alloca_at_entry(context.word_type(), "blockhash_out_ptr"); let output_pointer = context.build_alloca_at_entry(context.word_type(), "blockhash_out_ptr");
let index_ptr = context.build_alloca_at_entry(context.word_type(), "blockhash_index_ptr"); let index_pointer = context.build_alloca_at_entry(context.word_type(), "blockhash_index_ptr");
context.build_store(index_ptr, index)?; context.build_store(index_pointer, index)?;
context.build_runtime_call( context.build_runtime_call(
revive_runtime_api::polkavm_imports::BLOCK_HASH, revive_runtime_api::polkavm_imports::BLOCK_HASH,
&[ &[
index_ptr.to_int(context).into(), index_pointer.to_int(context).into(),
output_pointer.to_int(context).into(), output_pointer.to_int(context).into(),
], ],
); );
@@ -127,10 +130,9 @@ pub fn coinbase<'ctx, D>(
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
let pointer = context.build_alloca_at_entry( let pointer: Pointer<'_> = context
context.integer_type(revive_common::BIT_LENGTH_ETH_ADDRESS), .get_global(crate::polkavm::GLOBAL_ADDRESS_SPILL_BUFFER)?
"coinbase_output", .into();
);
context.build_runtime_call( context.build_runtime_call(
revive_runtime_api::polkavm_imports::BLOCK_AUTHOR, revive_runtime_api::polkavm_imports::BLOCK_AUTHOR,
&[pointer.to_int(context).into()], &[pointer.to_int(context).into()],
@@ -155,10 +157,9 @@ pub fn address<'ctx, D>(
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
let pointer = context.build_alloca_at_entry( let pointer: Pointer<'_> = context
context.integer_type(revive_common::BIT_LENGTH_ETH_ADDRESS), .get_global(crate::polkavm::GLOBAL_ADDRESS_SPILL_BUFFER)?
"address_output", .into();
);
context.build_runtime_call( context.build_runtime_call(
revive_runtime_api::polkavm_imports::ADDRESS, revive_runtime_api::polkavm_imports::ADDRESS,
&[pointer.to_int(context).into()], &[pointer.to_int(context).into()],
@@ -173,10 +174,9 @@ pub fn caller<'ctx, D>(
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
let pointer = context.build_alloca_at_entry( let pointer: Pointer<'_> = context
context.integer_type(revive_common::BIT_LENGTH_ETH_ADDRESS), .get_global(crate::polkavm::GLOBAL_ADDRESS_SPILL_BUFFER)?
"address_output", .into();
);
context.build_runtime_call( context.build_runtime_call(
revive_runtime_api::polkavm_imports::CALLER, revive_runtime_api::polkavm_imports::CALLER,
&[pointer.to_int(context).into()], &[pointer.to_int(context).into()],
+7 -10
View File
@@ -32,6 +32,7 @@ where
let salt_pointer = match salt { let salt_pointer = match salt {
Some(salt) => { Some(salt) => {
let salt_pointer = context.build_alloca_at_entry(context.word_type(), "salt_pointer"); let salt_pointer = context.build_alloca_at_entry(context.word_type(), "salt_pointer");
let salt = context.build_byte_swap(salt.into())?;
context.build_store(salt_pointer, salt)?; context.build_store(salt_pointer, salt)?;
salt_pointer salt_pointer
} }
@@ -118,10 +119,8 @@ where
_ => error, _ => error,
})?; })?;
if contract_path.as_str() == parent { if contract_path.as_str() == parent {
return Ok(Argument::new_with_constant( return Ok(Argument::value(context.word_const(0).as_basic_value_enum())
context.word_const(0).as_basic_value_enum(), .with_constant(num::BigUint::zero()));
num::BigUint::zero(),
));
} else if identifier.ends_with("_deployed") && code_type == CodeType::Runtime { } else if identifier.ends_with("_deployed") && code_type == CodeType::Runtime {
anyhow::bail!("type({}).runtimeCode is not supported", identifier); anyhow::bail!("type({}).runtimeCode is not supported", identifier);
} }
@@ -130,7 +129,7 @@ where
let hash_value = context let hash_value = context
.word_const_str_hex(hash_string.as_str()) .word_const_str_hex(hash_string.as_str())
.as_basic_value_enum(); .as_basic_value_enum();
Ok(Argument::new_with_original(hash_value, hash_string)) Ok(Argument::value(hash_value).with_original(hash_string))
} }
/// Translates the deploy call header size instruction. the header consists of /// Translates the deploy call header size instruction. the header consists of
@@ -159,10 +158,8 @@ where
_ => error, _ => error,
})?; })?;
if contract_path.as_str() == parent { if contract_path.as_str() == parent {
return Ok(Argument::new_with_constant( return Ok(Argument::value(context.word_const(0).as_basic_value_enum())
context.word_const(0).as_basic_value_enum(), .with_constant(num::BigUint::zero()));
num::BigUint::zero(),
));
} else if identifier.ends_with("_deployed") && code_type == CodeType::Runtime { } else if identifier.ends_with("_deployed") && code_type == CodeType::Runtime {
anyhow::bail!("type({}).runtimeCode is not supported", identifier); anyhow::bail!("type({}).runtimeCode is not supported", identifier);
} }
@@ -171,5 +168,5 @@ where
let size_value = context let size_value = context
.word_const(crate::polkavm::DEPLOYER_CALL_HEADER_SIZE as u64) .word_const(crate::polkavm::DEPLOYER_CALL_HEADER_SIZE as u64)
.as_basic_value_enum(); .as_basic_value_enum();
Ok(Argument::new_with_constant(size_value, size_bigint)) Ok(Argument::value(size_value).with_constant(size_bigint))
} }
@@ -28,7 +28,7 @@ pub fn value<'ctx, D>(
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
let output_pointer = context.build_alloca(context.value_type(), "value_transferred"); let output_pointer = context.build_alloca_at_entry(context.value_type(), "value_transferred");
context.build_store(output_pointer, context.word_const(0))?; context.build_store(output_pointer, context.word_const(0))?;
context.build_runtime_call( context.build_runtime_call(
revive_runtime_api::polkavm_imports::VALUE_TRANSFERRED, revive_runtime_api::polkavm_imports::VALUE_TRANSFERRED,
@@ -46,8 +46,7 @@ where
D: Dependency + Clone, D: Dependency + Clone,
{ {
let address_pointer = context.build_address_argument_store(address)?; let address_pointer = context.build_address_argument_store(address)?;
let balance_pointer = context.build_alloca_at_entry(context.word_type(), "balance_pointer");
let balance_pointer = context.build_alloca(context.word_type(), "balance_pointer");
let balance = context.builder().build_ptr_to_int( let balance = context.builder().build_ptr_to_int(
balance_pointer.value, balance_pointer.value,
context.xlen_type(), context.xlen_type(),
@@ -69,7 +68,7 @@ pub fn self_balance<'ctx, D>(
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
let balance_pointer = context.build_alloca(context.word_type(), "balance_pointer"); let balance_pointer = context.build_alloca_at_entry(context.word_type(), "balance_pointer");
let balance = context.builder().build_ptr_to_int( let balance = context.builder().build_ptr_to_int(
balance_pointer.value, balance_pointer.value,
context.xlen_type(), context.xlen_type(),
+167 -68
View File
@@ -2,84 +2,183 @@
use inkwell::values::BasicValue; use inkwell::values::BasicValue;
use crate::polkavm::context::runtime::RuntimeFunction;
use crate::polkavm::context::Context; use crate::polkavm::context::Context;
use crate::polkavm::Dependency; use crate::polkavm::Dependency;
use crate::polkavm::WriteLLVM;
/// A function for emitting EVM event logs from contract code.
pub struct EventLog<const N: usize>;
impl<D, const N: usize> RuntimeFunction<D> for EventLog<N>
where
D: Dependency + Clone,
{
const NAME: &'static str = match N {
0 => "__revive_log_0",
1 => "__revive_log_1",
2 => "__revive_log_2",
3 => "__revive_log_3",
4 => "__revive_log_4",
_ => unreachable!(),
};
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
let mut parameter_types = vec![context.xlen_type().into(), context.xlen_type().into()];
parameter_types.extend_from_slice(&[context.word_type().into(); N]);
context.void_type().fn_type(&parameter_types, false)
}
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx, D>,
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>> {
let input_offset = Self::paramater(context, 0).into_int_value();
let input_length = Self::paramater(context, 1).into_int_value();
let input_pointer = context.builder().build_ptr_to_int(
context.build_heap_gep(input_offset, input_length)?.value,
context.xlen_type(),
"event_input_offset",
)?;
let arguments = if N == 0 {
[
context.xlen_type().const_zero().as_basic_value_enum(),
context.xlen_type().const_zero().as_basic_value_enum(),
input_pointer.as_basic_value_enum(),
input_length.as_basic_value_enum(),
]
} else {
let topics_buffer_size = N * revive_common::BYTE_LENGTH_WORD;
let topics_buffer_pointer = context.build_alloca_at_entry(
context.byte_type().array_type(topics_buffer_size as u32),
"topics_buffer",
);
for n in 0..N {
let topic = Self::paramater(context, n + 2);
let topic_buffer_offset = context
.xlen_type()
.const_int((n * revive_common::BYTE_LENGTH_WORD) as u64, false);
context.build_store(
context.build_gep(
topics_buffer_pointer,
&[context.xlen_type().const_zero(), topic_buffer_offset],
context.byte_type(),
&format!("topic_buffer_{N}_gep"),
),
context.build_byte_swap(topic.as_basic_value_enum())?,
)?;
}
[
context
.builder()
.build_ptr_to_int(
topics_buffer_pointer.value,
context.xlen_type(),
"event_topics_offset",
)?
.as_basic_value_enum(),
context
.xlen_type()
.const_int(N as u64, false)
.as_basic_value_enum(),
input_pointer.as_basic_value_enum(),
input_length.as_basic_value_enum(),
]
};
context.build_runtime_call(
revive_runtime_api::polkavm_imports::DEPOSIT_EVENT,
&arguments,
);
Ok(None)
}
}
impl<D> WriteLLVM<D> for EventLog<0>
where
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::declare(self, context)
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::emit(&self, context)
}
}
impl<D> WriteLLVM<D> for EventLog<1>
where
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::declare(self, context)
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::emit(&self, context)
}
}
impl<D> WriteLLVM<D> for EventLog<2>
where
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::declare(self, context)
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::emit(&self, context)
}
}
impl<D> WriteLLVM<D> for EventLog<3>
where
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::declare(self, context)
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::emit(&self, context)
}
}
impl<D> WriteLLVM<D> for EventLog<4>
where
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::declare(self, context)
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::emit(&self, context)
}
}
/// Translates a log or event call. /// Translates a log or event call.
/// pub fn log<'ctx, D, const N: usize>(
/// TODO: Splitting up into dedicated functions (log0..log4)
/// could potentially decrease code sizes (LLVM can still decide to inline).
/// However, passing i256 parameters is counter productive and
/// I've found that splitting it up actualy increases code size.
/// Should be reviewed after 64bit support.
pub fn log<'ctx, D>(
context: &mut Context<'ctx, D>, context: &mut Context<'ctx, D>,
input_offset: inkwell::values::IntValue<'ctx>, input_offset: inkwell::values::IntValue<'ctx>,
input_length: inkwell::values::IntValue<'ctx>, input_length: inkwell::values::IntValue<'ctx>,
topics: Vec<inkwell::values::IntValue<'ctx>>, topics: [inkwell::values::BasicValueEnum<'ctx>; N],
) -> anyhow::Result<()> ) -> anyhow::Result<()>
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
let input_offset = context.safe_truncate_int_to_xlen(input_offset)?; let declaration = <EventLog<N> as RuntimeFunction<D>>::declaration(context);
let input_length = context.safe_truncate_int_to_xlen(input_length)?; let mut arguments = vec![
let input_pointer = context.builder().build_ptr_to_int( context.safe_truncate_int_to_xlen(input_offset)?.into(),
context.build_heap_gep(input_offset, input_length)?.value, context.safe_truncate_int_to_xlen(input_length)?.into(),
context.xlen_type(), ];
"event_input_offset", arguments.extend_from_slice(&topics);
)?; context.build_call(declaration, &arguments, "log");
let arguments = if topics.is_empty() {
[
context.xlen_type().const_zero().as_basic_value_enum(),
context.xlen_type().const_zero().as_basic_value_enum(),
input_pointer.as_basic_value_enum(),
input_length.as_basic_value_enum(),
]
} else {
let topics_buffer_size = topics.len() * revive_common::BYTE_LENGTH_WORD;
let topics_buffer_pointer = context.build_alloca(
context.byte_type().array_type(topics_buffer_size as u32),
"topics_buffer",
);
for (n, topic) in topics.iter().enumerate() {
let topic_buffer_offset = context
.xlen_type()
.const_int((n * revive_common::BYTE_LENGTH_WORD) as u64, false);
context.build_store(
context.build_gep(
topics_buffer_pointer,
&[context.xlen_type().const_zero(), topic_buffer_offset],
context.byte_type(),
"topic_buffer_gep",
),
context.build_byte_swap(topic.as_basic_value_enum())?,
)?;
}
[
context
.builder()
.build_ptr_to_int(
topics_buffer_pointer.value,
context.xlen_type(),
"event_topics_offset",
)?
.as_basic_value_enum(),
context
.xlen_type()
.const_int(topics.len() as u64, false)
.as_basic_value_enum(),
input_pointer.as_basic_value_enum(),
input_length.as_basic_value_enum(),
]
};
let _ = context.build_runtime_call(
revive_runtime_api::polkavm_imports::DEPOSIT_EVENT,
&arguments,
);
Ok(()) Ok(())
} }
@@ -4,10 +4,206 @@ use inkwell::types::BasicType;
use crate::polkavm::context::address_space::AddressSpace; use crate::polkavm::context::address_space::AddressSpace;
use crate::polkavm::context::code_type::CodeType; use crate::polkavm::context::code_type::CodeType;
use crate::polkavm::context::function::runtime;
use crate::polkavm::context::pointer::Pointer; use crate::polkavm::context::pointer::Pointer;
use crate::polkavm::context::runtime::RuntimeFunction;
use crate::polkavm::context::Context; use crate::polkavm::context::Context;
use crate::polkavm::Dependency; use crate::polkavm::Dependency;
use crate::polkavm::WriteLLVM;
/// A function for requesting the immutable data from the runtime.
/// This is a special function that is only used by the front-end generated code.
///
/// The runtime API is called lazily and subsequent calls are no-ops.
///
/// The bytes written is asserted to match the expected length.
/// This should never fail; the length is known.
/// However, this is a one time assertion, hence worth it.
pub struct Load;
impl<D> RuntimeFunction<D> for Load
where
D: Dependency + Clone,
{
const NAME: &'static str = "__revive_load_immutable_data";
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
context.void_type().fn_type(Default::default(), false)
}
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx, D>,
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>> {
let immutable_data_size_pointer = context
.get_global(revive_runtime_api::immutable_data::GLOBAL_IMMUTABLE_DATA_SIZE)?
.value
.as_pointer_value();
let immutable_data_size = context.build_load(
Pointer::new(
context.xlen_type(),
AddressSpace::Stack,
immutable_data_size_pointer,
),
"immutable_data_size_load",
)?;
let load_immutable_data_block = context.append_basic_block("load_immutables_block");
let return_block = context.current_function().borrow().return_block();
let immutable_data_size_is_zero = context.builder().build_int_compare(
inkwell::IntPredicate::EQ,
context.xlen_type().const_zero(),
immutable_data_size.into_int_value(),
"immutable_data_size_is_zero",
)?;
context.build_conditional_branch(
immutable_data_size_is_zero,
return_block,
load_immutable_data_block,
)?;
context.set_basic_block(load_immutable_data_block);
let output_pointer = context
.get_global(revive_runtime_api::immutable_data::GLOBAL_IMMUTABLE_DATA_POINTER)?
.value
.as_pointer_value();
context.build_runtime_call(
revive_runtime_api::polkavm_imports::GET_IMMUTABLE_DATA,
&[
context
.builder()
.build_ptr_to_int(output_pointer, context.xlen_type(), "ptr_to_xlen")?
.into(),
context
.builder()
.build_ptr_to_int(
immutable_data_size_pointer,
context.xlen_type(),
"ptr_to_xlen",
)?
.into(),
],
);
let bytes_written = context.builder().build_load(
context.xlen_type(),
immutable_data_size_pointer,
"bytes_written",
)?;
context.builder().build_store(
immutable_data_size_pointer,
context.xlen_type().const_zero(),
)?;
let overflow_block = context.append_basic_block("immutable_data_overflow");
let is_overflow = context.builder().build_int_compare(
inkwell::IntPredicate::UGT,
immutable_data_size.into_int_value(),
bytes_written.into_int_value(),
"is_overflow",
)?;
context.build_conditional_branch(is_overflow, overflow_block, return_block)?;
context.set_basic_block(overflow_block);
context.build_call(context.intrinsics().trap, &[], "invalid_trap");
context.build_unreachable();
Ok(None)
}
}
impl<D> WriteLLVM<D> for Load
where
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::declare(self, context)
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::emit(&self, context)
}
}
/// Store the immutable data from the constructor code.
pub struct Store;
impl<D> RuntimeFunction<D> for Store
where
D: Dependency + Clone,
{
const NAME: &'static str = "__revive_store_immutable_data";
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
context.void_type().fn_type(Default::default(), false)
}
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx, D>,
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>> {
let immutable_data_size_pointer = context
.get_global(revive_runtime_api::immutable_data::GLOBAL_IMMUTABLE_DATA_SIZE)?
.value
.as_pointer_value();
let immutable_data_size = context.build_load(
Pointer::new(
context.xlen_type(),
AddressSpace::Stack,
immutable_data_size_pointer,
),
"immutable_data_size_load",
)?;
let write_immutable_data_block = context.append_basic_block("write_immutables_block");
let join_return_block = context.append_basic_block("join_return_block");
let immutable_data_size_is_zero = context.builder().build_int_compare(
inkwell::IntPredicate::EQ,
context.xlen_type().const_zero(),
immutable_data_size.into_int_value(),
"immutable_data_size_is_zero",
)?;
context.build_conditional_branch(
immutable_data_size_is_zero,
join_return_block,
write_immutable_data_block,
)?;
context.set_basic_block(write_immutable_data_block);
let immutable_data_pointer = context
.get_global(revive_runtime_api::immutable_data::GLOBAL_IMMUTABLE_DATA_POINTER)?
.value
.as_pointer_value();
context.build_runtime_call(
revive_runtime_api::polkavm_imports::SET_IMMUTABLE_DATA,
&[
context
.builder()
.build_ptr_to_int(
immutable_data_pointer,
context.xlen_type(),
"immutable_data_pointer_to_xlen",
)?
.into(),
immutable_data_size,
],
);
context.build_unconditional_branch(join_return_block);
context.set_basic_block(join_return_block);
Ok(None)
}
}
impl<D> WriteLLVM<D> for Store
where
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::declare(self, context)
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::emit(&self, context)
}
}
/// Translates the contract immutable load. /// Translates the contract immutable load.
/// ///
@@ -27,14 +223,15 @@ where
} }
Some(CodeType::Deploy) => load_from_memory(context, index), Some(CodeType::Deploy) => load_from_memory(context, index),
Some(CodeType::Runtime) => { Some(CodeType::Runtime) => {
let name = <Load as RuntimeFunction<D>>::NAME;
context.build_call( context.build_call(
context context
.get_function(runtime::FUNCTION_LOAD_IMMUTABLE_DATA) .get_function(name)
.expect("is always declared for runtime code") .expect("is always declared for runtime code")
.borrow() .borrow()
.declaration(), .declaration(),
&[], &[],
runtime::FUNCTION_LOAD_IMMUTABLE_DATA, name,
); );
load_from_memory(context, index) load_from_memory(context, index)
} }
+10 -1
View File
@@ -85,5 +85,14 @@ where
offset, offset,
"mstore8_destination", "mstore8_destination",
); );
context.build_store(pointer, value) let pointer = context.build_sbrk(
pointer.to_int(context),
context.xlen_type().const_int(1, false),
)?;
context
.builder()
.build_store(pointer, value)?
.set_alignment(revive_common::BYTE_LENGTH_BYTE as u32)
.expect("Alignment is valid");
Ok(())
} }
+7 -55
View File
@@ -1,9 +1,9 @@
//! Translates the transaction return operations. //! Translates the transaction return operations.
use crate::polkavm::context::address_space::AddressSpace;
use crate::polkavm::context::code_type::CodeType; use crate::polkavm::context::code_type::CodeType;
use crate::polkavm::context::pointer::Pointer; use crate::polkavm::context::runtime::RuntimeFunction;
use crate::polkavm::context::Context; use crate::polkavm::context::Context;
use crate::polkavm::evm::immutable::Store;
use crate::polkavm::Dependency; use crate::polkavm::Dependency;
/// Translates the `return` instruction. /// Translates the `return` instruction.
@@ -18,55 +18,11 @@ where
match context.code_type() { match context.code_type() {
None => anyhow::bail!("Return is not available if the contract part is undefined"), None => anyhow::bail!("Return is not available if the contract part is undefined"),
Some(CodeType::Deploy) => { Some(CodeType::Deploy) => {
let immutable_data_size_pointer = context context.build_call(
.get_global(revive_runtime_api::immutable_data::GLOBAL_IMMUTABLE_DATA_SIZE)? <Store as RuntimeFunction<D>>::declaration(context),
.value Default::default(),
.as_pointer_value(); "store_immutable_data",
let immutable_data_size = context.build_load(
Pointer::new(
context.xlen_type(),
AddressSpace::Stack,
immutable_data_size_pointer,
),
"immutable_data_size_load",
)?;
let write_immutable_data_block = context.append_basic_block("write_immutables_block");
let join_return_block = context.append_basic_block("join_return_block");
let immutable_data_size_is_zero = context.builder().build_int_compare(
inkwell::IntPredicate::EQ,
context.xlen_type().const_zero(),
immutable_data_size.into_int_value(),
"immutable_data_size_is_zero",
)?;
context.build_conditional_branch(
immutable_data_size_is_zero,
join_return_block,
write_immutable_data_block,
)?;
context.set_basic_block(write_immutable_data_block);
let immutable_data_pointer = context
.get_global(revive_runtime_api::immutable_data::GLOBAL_IMMUTABLE_DATA_POINTER)?
.value
.as_pointer_value();
context.build_runtime_call(
revive_runtime_api::polkavm_imports::SET_IMMUTABLE_DATA,
&[
context
.builder()
.build_ptr_to_int(
immutable_data_pointer,
context.xlen_type(),
"immutable_data_pointer_to_xlen",
)?
.into(),
immutable_data_size,
],
); );
context.build_unconditional_branch(join_return_block);
context.set_basic_block(join_return_block);
} }
Some(CodeType::Runtime) => {} Some(CodeType::Runtime) => {}
} }
@@ -100,11 +56,7 @@ pub fn stop<D>(context: &mut Context<D>) -> anyhow::Result<()>
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
r#return( r#return(context, context.word_const(0), context.word_const(0))
context,
context.integer_const(crate::polkavm::XLEN, 0),
context.integer_const(crate::polkavm::XLEN, 0),
)
} }
/// Translates the `invalid` instruction. /// Translates the `invalid` instruction.
+38 -23
View File
@@ -1,65 +1,80 @@
//! Translates the storage operations. //! Translates the storage operations.
use crate::polkavm::context::address_space::AddressSpace; use crate::polkavm::context::runtime::RuntimeFunction;
use crate::polkavm::context::Context; use crate::polkavm::context::Context;
use crate::polkavm::Dependency; use crate::polkavm::Dependency;
use crate::PolkaVMArgument;
use crate::PolkaVMLoadStorageWordFunction;
use crate::PolkaVMLoadTransientStorageWordFunction;
use crate::PolkaVMStoreStorageWordFunction;
use crate::PolkaVMStoreTransientStorageWordFunction;
/// Translates the storage load. /// Translates the storage load.
pub fn load<'ctx, D>( pub fn load<'ctx, D>(
context: &mut Context<'ctx, D>, context: &mut Context<'ctx, D>,
position: inkwell::values::IntValue<'ctx>, position: &PolkaVMArgument<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>> ) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
let mut slot_ptr = context.build_alloca_at_entry(context.word_type(), "slot_pointer"); let name = <PolkaVMLoadStorageWordFunction as RuntimeFunction<D>>::NAME;
slot_ptr.address_space = AddressSpace::Storage; let declaration = <PolkaVMLoadStorageWordFunction as RuntimeFunction<D>>::declaration(context);
context.builder().build_store(slot_ptr.value, position)?; let arguments = [position.as_pointer(context)?.value.into()];
context.build_load(slot_ptr, "storage_load_value") Ok(context
.build_call(declaration, &arguments, "storage_load")
.unwrap_or_else(|| panic!("runtime function {name} should return a value")))
} }
/// Translates the storage store. /// Translates the storage store.
pub fn store<'ctx, D>( pub fn store<'ctx, D>(
context: &mut Context<'ctx, D>, context: &mut Context<'ctx, D>,
position: inkwell::values::IntValue<'ctx>, position: &PolkaVMArgument<'ctx>,
value: inkwell::values::IntValue<'ctx>, value: &PolkaVMArgument<'ctx>,
) -> anyhow::Result<()> ) -> anyhow::Result<()>
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
let mut slot_ptr = context.build_alloca_at_entry(context.word_type(), "slot_pointer"); let declaration = <PolkaVMStoreStorageWordFunction as RuntimeFunction<D>>::declaration(context);
slot_ptr.address_space = AddressSpace::Storage; let arguments = [
context.builder().build_store(slot_ptr.value, position)?; position.as_pointer(context)?.value.into(),
context.build_store(slot_ptr, value)?; value.as_pointer(context)?.value.into(),
];
context.build_call(declaration, &arguments, "storage_store");
Ok(()) Ok(())
} }
/// Translates the transient storage load. /// Translates the transient storage load.
pub fn transient_load<'ctx, D>( pub fn transient_load<'ctx, D>(
context: &mut Context<'ctx, D>, context: &mut Context<'ctx, D>,
position: inkwell::values::IntValue<'ctx>, position: &PolkaVMArgument<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>> ) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
let mut slot_ptr = context.build_alloca_at_entry(context.word_type(), "slot_pointer"); let name = <PolkaVMLoadTransientStorageWordFunction as RuntimeFunction<D>>::NAME;
slot_ptr.address_space = AddressSpace::TransientStorage; let arguments = [position.as_pointer(context)?.value.into()];
context.builder().build_store(slot_ptr.value, position)?; let declaration =
context.build_load(slot_ptr, "transient_storage_load_value") <PolkaVMLoadTransientStorageWordFunction as RuntimeFunction<D>>::declaration(context);
Ok(context
.build_call(declaration, &arguments, "transient_storage_load")
.unwrap_or_else(|| panic!("runtime function {name} should return a value")))
} }
/// Translates the transient storage store. /// Translates the transient storage store.
pub fn transient_store<'ctx, D>( pub fn transient_store<'ctx, D>(
context: &mut Context<'ctx, D>, context: &mut Context<'ctx, D>,
position: inkwell::values::IntValue<'ctx>, position: &PolkaVMArgument<'ctx>,
value: inkwell::values::IntValue<'ctx>, value: &PolkaVMArgument<'ctx>,
) -> anyhow::Result<()> ) -> anyhow::Result<()>
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
let mut slot_ptr = context.build_alloca_at_entry(context.word_type(), "slot_pointer"); let declaration =
slot_ptr.address_space = AddressSpace::TransientStorage; <PolkaVMStoreTransientStorageWordFunction as RuntimeFunction<D>>::declaration(context);
context.builder().build_store(slot_ptr.value, position)?; let arguments = [
context.build_store(slot_ptr, value)?; position.as_pointer(context)?.value.into(),
value.as_pointer(context)?.value.into(),
];
context.build_call(declaration, &arguments, "transient_storage_store");
Ok(()) Ok(())
} }
+5 -6
View File
@@ -3,11 +3,11 @@
pub mod r#const; pub mod r#const;
pub mod context; pub mod context;
pub mod evm; pub mod evm;
pub mod metadata_hash;
pub use self::r#const::*; pub use self::r#const::*;
use crate::debug_config::DebugConfig; use crate::debug_config::DebugConfig;
use crate::memory::MemoryConfig;
use crate::optimizer::settings::Settings as OptimizerSettings; use crate::optimizer::settings::Settings as OptimizerSettings;
use anyhow::Context as AnyhowContext; use anyhow::Context as AnyhowContext;
@@ -18,11 +18,6 @@ use sha3::Digest;
use self::context::build::Build; use self::context::build::Build;
use self::context::Context; use self::context::Context;
/// Initializes the PolkaVM target machine.
pub fn initialize_target() {
inkwell::targets::Target::initialize_riscv(&Default::default());
}
/// Builds PolkaVM assembly text. /// Builds PolkaVM assembly text.
pub fn build_assembly_text( pub fn build_assembly_text(
contract_path: &str, contract_path: &str,
@@ -95,6 +90,8 @@ pub trait Dependency {
optimizer_settings: OptimizerSettings, optimizer_settings: OptimizerSettings,
include_metadata_hash: bool, include_metadata_hash: bool,
debug_config: DebugConfig, debug_config: DebugConfig,
llvm_arguments: &[String],
memory_config: MemoryConfig,
) -> anyhow::Result<String>; ) -> anyhow::Result<String>;
/// Resolves a full contract path. /// Resolves a full contract path.
@@ -115,6 +112,8 @@ impl Dependency for DummyDependency {
_optimizer_settings: OptimizerSettings, _optimizer_settings: OptimizerSettings,
_include_metadata_hash: bool, _include_metadata_hash: bool,
_debug_config: DebugConfig, _debug_config: DebugConfig,
_llvm_arguments: &[String],
_memory_config: MemoryConfig,
) -> anyhow::Result<String> { ) -> anyhow::Result<String> {
Ok(String::new()) Ok(String::new())
} }
+9 -1
View File
@@ -10,12 +10,19 @@ description = "Execute revive contracts in a simulated blockchain runtime"
[package.metadata.cargo-machete] [package.metadata.cargo-machete]
ignored = ["codec", "scale-info"] ignored = ["codec", "scale-info"]
[[bin]]
name = "revive-runner"
path = "src/main.rs"
[features] [features]
std = ["polkadot-sdk/std"] std = ["polkadot-sdk/std"]
default = ["solidity"] default = ["solidity"]
solidity = ["revive-solidity", "revive-differential"] solidity = ["revive-solidity", "revive-differential", "revive-llvm-context"]
[dependencies] [dependencies]
env_logger = { workspace = true }
clap = { workspace = true, features = ["help", "std", "derive"] }
anyhow = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
hex = { workspace = true, features = ["serde"] } hex = { workspace = true, features = ["serde"] }
@@ -34,3 +41,4 @@ polkadot-sdk.features = [
revive-solidity = { workspace = true, optional = true } revive-solidity = { workspace = true, optional = true }
revive-differential = { workspace = true, optional = true } revive-differential = { workspace = true, optional = true }
revive-llvm-context = { workspace = true, optional = true }
+27
View File
@@ -0,0 +1,27 @@
# revive-runner
The revive runner is a helper utility aiding in contract debugging.
Given a PVM contract blob, it will upload, deploy and call that contract using a local, stand-alone un-blockchained pallet revive (which is our execution layer).
This is somewhat similar to the geth `evm` utility binary.
## Installation
The `revive-runner` does not depend on the compiler itself, hence installing this utility does not depend on LLVM, so no LLVM build is required.
Inside the root `revive` repository directory, execute:
```bash
make install-revive-runner
```
Which will install the `revive-runner` using `cargo`.
## Usage
Set the `RUST_LOG` environment varibale to the `trace` level to see the full PolkaVM execution trace. For example:
```bash
RUST_LOG=trace revive-runner -f mycontract.pvm -c a9059cbb000000000000000000000000f24ff3a9cf04c71dbc94d0b566f7a27b94566cac0000000000000000000000000000000000000000000000000000000000000000
```
+1
View File
@@ -274,6 +274,7 @@ impl From<Code> for pallet_revive::Code {
&contract, &contract,
&source_code, &source_code,
solc_optimizer.unwrap_or(true), solc_optimizer.unwrap_or(true),
revive_llvm_context::OptimizerSettings::cycles(),
)) ))
} }
Code::Path(path) => pallet_revive::Code::Upload(std::fs::read(path).unwrap()), Code::Path(path) => pallet_revive::Code::Upload(std::fs::read(path).unwrap()),
+93
View File
@@ -0,0 +1,93 @@
use std::path::PathBuf;
use clap::Parser;
use revive_runner::{Code, OptionalHex, Specs, SpecsAction::*, TestAddress};
/// Execute revive PolkaVM contracts locally.
#[derive(Parser)]
#[command(version, about, long_about = None)]
struct Arguments {
/// The hex encoded calldata for the contract call.
#[arg(short, long)]
calldata: Option<String>,
/// The hex encoded calldata for the contract deployment.
#[arg(short, long)]
deploy_calldata: Option<String>,
/// The hex encoded contract code blob to instantiate and execute.
#[arg(short, long)]
blob: Option<String>,
/// The contract code to instantiate and execute.
#[arg(short, long)]
file: Option<PathBuf>,
/// The origin account used to initiate the deploy and call transactions.
#[arg(short, long)]
origin: Option<TestAddress>,
/// The value the call transaction is endowed with.
#[arg(short, long)]
value: Option<u128>,
/// The value the deploy transaction is endowed with.
#[arg(long)]
deploy_value: Option<u128>,
}
fn main() -> anyhow::Result<()> {
env_logger::init();
let arguments = Arguments::parse();
let code = match (arguments.blob, arguments.file) {
(Some(blob), None) => hex::decode(blob)
.map_err(|error| anyhow::anyhow!("expected hex encoded PVM blob: {error}"))?,
(None, Some(file)) => std::fs::read(&file).map_err(|error| {
anyhow::anyhow!("unable to read PVM file {}: {error}", file.display())
})?,
_ => anyhow::bail!("should either provide a PVM blob or a PVM file"),
};
let calldata = match arguments.calldata {
Some(calldata) => hex::decode(calldata)
.map_err(|error| anyhow::anyhow!("expected hex encoded calldata: {error}"))?,
None => vec![],
};
let deploy_calldata = match arguments.deploy_calldata {
Some(calldata) => hex::decode(calldata)
.map_err(|error| anyhow::anyhow!("expected hex encoded calldata: {error}"))?,
None => vec![],
};
let origin = arguments.origin.unwrap_or(TestAddress::Alice);
let actions = vec![
Instantiate {
origin: origin.clone(),
value: arguments.deploy_value.unwrap_or(0),
gas_limit: None,
storage_deposit_limit: None,
code: Code::Bytes(code),
data: deploy_calldata,
salt: OptionalHex::default(),
},
Call {
origin,
dest: TestAddress::Instantiated(0),
value: arguments.value.unwrap_or(0),
gas_limit: None,
storage_deposit_limit: None,
data: calldata,
},
];
Specs {
actions,
differential: false,
..Default::default()
}
.run();
Ok(())
}
+40 -8
View File
@@ -1,9 +1,11 @@
use std::time::Instant; use std::{str::FromStr, time::Instant};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::*; use crate::*;
use alloy_primitives::{keccak256, Address}; use alloy_primitives::keccak256;
#[cfg(feature = "revive-solidity")]
use alloy_primitives::Address;
#[cfg(feature = "revive-solidity")] #[cfg(feature = "revive-solidity")]
use revive_differential::{Evm, EvmLog}; use revive_differential::{Evm, EvmLog};
#[cfg(feature = "revive-solidity")] #[cfg(feature = "revive-solidity")]
@@ -106,14 +108,10 @@ impl SpecsAction {
}; };
for (key, expected) in storage { for (key, expected) in storage {
let mut key = **key;
let mut expected = **expected;
key.reverse();
expected.reverse();
actions.push(Self::VerifyStorage { actions.push(Self::VerifyStorage {
contract: account_pvm.clone(), contract: account_pvm.clone(),
key, key: **key,
expected, expected: **expected,
}); });
} }
@@ -160,6 +158,39 @@ impl TestAddress {
} }
} }
impl FromStr for TestAddress {
type Err = &'static str;
fn from_str(value: &str) -> Result<Self, Self::Err> {
value.try_into()
}
}
impl TryFrom<&str> for TestAddress {
type Error = &'static str;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value {
"alice" => Ok(Self::Alice),
"bob" => Ok(Self::Bob),
"charlie" => Ok(Self::Charlie),
value => {
if let Ok(value) = value.parse() {
return Ok(Self::Instantiated(value));
}
if let Ok(value) = hex::decode(value) {
if value.len() == 20 {
return Ok(Self::AccountId(H160(value.try_into().unwrap())));
}
}
Err("can not parse into test address")
}
}
}
}
/// Specs for a contract test /// Specs for a contract test
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(default)] #[serde(default)]
@@ -216,6 +247,7 @@ impl Specs {
/// Helper to allow not specifying the code bytes or path directly in the runner.json /// Helper to allow not specifying the code bytes or path directly in the runner.json
/// - Replace `Code::Bytes(bytes)` if `bytes` are empty: read `contract_file` /// - Replace `Code::Bytes(bytes)` if `bytes` are empty: read `contract_file`
/// - Replace `Code::Solidity{ path, ..}` if `path` is not provided: replace `path` with `contract_file` /// - Replace `Code::Solidity{ path, ..}` if `path` is not provided: replace `path` with `contract_file`
#[allow(unused_variables)]
pub fn replace_empty_code(&mut self, contract_name: &str, contract_path: &str) { pub fn replace_empty_code(&mut self, contract_name: &str, contract_path: &str) {
for action in self.actions.iter_mut() { for action in self.actions.iter_mut() {
let code = match action { let code = match action {
-28
View File
@@ -5,34 +5,6 @@
// Missing builtins // Missing builtins
#define EVM_WORD_SIZE 32
#define ALIGN(size) ((size + EVM_WORD_SIZE - 1) & ~(EVM_WORD_SIZE - 1))
#define MAX_MEMORY_SIZE (64 * 1024)
char __memory[MAX_MEMORY_SIZE];
uint32_t __memory_size = 0;
void * __sbrk_internal(uint32_t offset, uint32_t size) {
if (offset >= MAX_MEMORY_SIZE || size > MAX_MEMORY_SIZE) {
return NULL;
}
uint32_t new_size = ALIGN(offset + size);
if (new_size > MAX_MEMORY_SIZE) {
return NULL;
}
if (new_size > __memory_size) {
__memory_size = new_size;
}
return (void *)&__memory[__memory_size];
}
void * memset(void *b, int c, size_t len) {
uint8_t *dest = b;
while (len-- > 0) *dest++ = c;
return b;
}
void * memcpy(void *dst, const void *_src, size_t len) { void * memcpy(void *dst, const void *_src, size_t len) {
uint8_t *dest = dst; uint8_t *dest = dst;
const uint8_t *src = _src; const uint8_t *src = _src;
+1 -10
View File
@@ -2,14 +2,6 @@ use inkwell::{context::Context, memory_buffer::MemoryBuffer, module::Module, sup
include!(concat!(env!("OUT_DIR"), "/polkavm_imports.rs")); include!(concat!(env!("OUT_DIR"), "/polkavm_imports.rs"));
/// The emulated EVM heap memory global symbol.
pub static MEMORY: &str = "__memory";
/// The emulated EVM heap memory size global symbol.
pub static MEMORY_SIZE: &str = "__memory_size";
pub static SBRK: &str = "__sbrk_internal";
pub static ADDRESS: &str = "address"; pub static ADDRESS: &str = "address";
pub static BALANCE: &str = "balance"; pub static BALANCE: &str = "balance";
@@ -78,8 +70,7 @@ pub static WEIGHT_TO_FEE: &str = "weight_to_fee";
/// All imported runtime API symbols. /// All imported runtime API symbols.
/// Useful for configuring common attributes and linkage. /// Useful for configuring common attributes and linkage.
pub static IMPORTS: [&str; 34] = [ pub static IMPORTS: [&str; 33] = [
SBRK,
ADDRESS, ADDRESS,
BALANCE, BALANCE,
BALANCE_OF, BALANCE_OF,
+23
View File
@@ -0,0 +1,23 @@
[package]
name = "revive-solc-json-interface"
version.workspace = true
authors.workspace = true
license.workspace = true
edition.workspace = true
repository.workspace = true
rust-version.workspace = true
description = "Rust bindings for the solc standard JSON and combined JSON interface"
[features]
default = ["parallel"]
parallel = ["rayon"]
resolc = [] # The resolc binary adds a bunch of custom fields to the format
[dependencies]
revive-common = { workspace = true }
anyhow = { workspace = true }
rayon = { workspace = true, optional = true }
semver = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
+27
View File
@@ -0,0 +1,27 @@
//! This crates provides (de)serializable Rust types for interacting
//! `solc` via the [JSON-input-output][0] interface.
//!
//! [0]: https://docs.soliditylang.org/en/latest/using-the-compiler.html#compiler-input-and-output-json-description
pub use self::combined_json::contract::Contract as CombinedJsonContract;
pub use self::standard_json::input::language::Language as SolcStandardJsonInputLanguage;
pub use self::standard_json::input::settings::metadata::Metadata as SolcStandardJsonInputSettingsMetadata;
pub use self::standard_json::input::settings::metadata_hash::MetadataHash as SolcStandardJsonInputSettingsMetadataHash;
pub use self::standard_json::input::settings::optimizer::Optimizer as SolcStandardJsonInputSettingsOptimizer;
pub use self::standard_json::input::settings::selection::file::flag::Flag as SolcStandardJsonInputSettingsSelectionFileFlag;
pub use self::standard_json::input::settings::selection::file::File as SolcStandardJsonInputSettingsSelectionFile;
pub use self::standard_json::input::settings::selection::Selection as SolcStandardJsonInputSettingsSelection;
pub use self::standard_json::input::settings::Settings as SolcStandardJsonInputSettings;
pub use self::standard_json::input::source::Source as SolcStandardJsonInputSource;
pub use self::standard_json::input::Input as SolcStandardJsonInput;
pub use self::standard_json::output::contract::evm::bytecode::Bytecode as SolcStandardJsonOutputContractEVMBytecode;
pub use self::standard_json::output::contract::evm::EVM as SolcStandardJsonOutputContractEVM;
pub use self::standard_json::output::contract::Contract as SolcStandardJsonOutputContract;
pub use self::standard_json::output::Output as SolcStandardJsonOutput;
#[cfg(feature = "resolc")]
pub use self::warning::Warning as ResolcWarning;
pub mod combined_json;
pub mod standard_json;
#[cfg(feature = "resolc")]
pub mod warning;
@@ -8,14 +8,15 @@ use std::collections::BTreeMap;
use std::collections::BTreeSet; use std::collections::BTreeSet;
use std::path::PathBuf; use std::path::PathBuf;
#[cfg(feature = "parallel")] #[cfg(all(feature = "parallel", feature = "resolc"))]
use rayon::iter::{IntoParallelIterator, ParallelIterator}; use rayon::iter::{IntoParallelIterator, ParallelIterator};
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
use crate::solc::standard_json::input::settings::metadata::Metadata as SolcStandardJsonInputSettingsMetadata; use crate::standard_json::input::settings::metadata::Metadata as SolcStandardJsonInputSettingsMetadata;
use crate::solc::standard_json::input::settings::optimizer::Optimizer as SolcStandardJsonInputSettingsOptimizer; use crate::standard_json::input::settings::optimizer::Optimizer as SolcStandardJsonInputSettingsOptimizer;
use crate::solc::standard_json::input::settings::selection::Selection as SolcStandardJsonInputSettingsSelection; use crate::standard_json::input::settings::selection::Selection as SolcStandardJsonInputSettingsSelection;
#[cfg(feature = "resolc")]
use crate::warning::Warning; use crate::warning::Warning;
use self::language::Language; use self::language::Language;
@@ -33,6 +34,7 @@ pub struct Input {
/// The compiler settings. /// The compiler settings.
pub settings: Settings, pub settings: Settings,
/// The suppressed warnings. /// The suppressed warnings.
#[cfg(feature = "resolc")]
#[serde(skip_serializing)] #[serde(skip_serializing)]
pub suppressed_warnings: Option<Vec<Warning>>, pub suppressed_warnings: Option<Vec<Warning>>,
} }
@@ -60,7 +62,7 @@ impl Input {
output_selection: SolcStandardJsonInputSettingsSelection, output_selection: SolcStandardJsonInputSettingsSelection,
optimizer: SolcStandardJsonInputSettingsOptimizer, optimizer: SolcStandardJsonInputSettingsOptimizer,
metadata: Option<SolcStandardJsonInputSettingsMetadata>, metadata: Option<SolcStandardJsonInputSettingsMetadata>,
suppressed_warnings: Option<Vec<Warning>>, #[cfg(feature = "resolc")] suppressed_warnings: Option<Vec<Warning>>,
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self> {
let mut paths: BTreeSet<PathBuf> = paths.iter().cloned().collect(); let mut paths: BTreeSet<PathBuf> = paths.iter().cloned().collect();
let libraries = Settings::parse_libraries(library_map)?; let libraries = Settings::parse_libraries(library_map)?;
@@ -89,12 +91,14 @@ impl Input {
optimizer, optimizer,
metadata, metadata,
), ),
#[cfg(feature = "resolc")]
suppressed_warnings, suppressed_warnings,
}) })
} }
/// A shortcut constructor from source code. /// A shortcut constructor from source code.
/// Only for the integration test purposes. /// Only for the integration test purposes.
#[cfg(feature = "resolc")]
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn try_from_sources( pub fn try_from_sources(
evm_version: Option<revive_common::EVMVersion>, evm_version: Option<revive_common::EVMVersion>,
@@ -3,18 +3,20 @@
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
use crate::standard_json::input::settings::metadata_hash::MetadataHash;
/// The `solc --standard-json` input settings metadata. /// The `solc --standard-json` input settings metadata.
#[derive(Debug, Default, Serialize, Deserialize)] #[derive(Debug, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Metadata { pub struct Metadata {
/// The bytecode hash mode. /// The bytecode hash mode.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub bytecode_hash: Option<revive_llvm_context::PolkaVMMetadataHash>, pub bytecode_hash: Option<MetadataHash>,
} }
impl Metadata { impl Metadata {
/// A shortcut constructor. /// A shortcut constructor.
pub fn new(bytecode_hash: revive_llvm_context::PolkaVMMetadataHash) -> Self { pub fn new(bytecode_hash: MetadataHash) -> Self {
Self { Self {
bytecode_hash: Some(bytecode_hash), bytecode_hash: Some(bytecode_hash),
} }
@@ -1,6 +1,7 @@
//! The `solc --standard-json` input settings. //! The `solc --standard-json` input settings.
pub mod metadata; pub mod metadata;
pub mod metadata_hash;
pub mod optimizer; pub mod optimizer;
pub mod selection; pub mod selection;
@@ -49,18 +49,3 @@ impl Optimizer {
}; };
} }
} }
impl TryFrom<&Optimizer> for revive_llvm_context::OptimizerSettings {
type Error = anyhow::Error;
fn try_from(value: &Optimizer) -> Result<Self, Self::Error> {
let mut result = match value.mode {
Some(mode) => Self::try_from_cli(mode)?,
None => Self::cycles(),
};
if value.fallback_to_optimizing_for_size.unwrap_or_default() {
result.enable_fallback_to_size();
}
Ok(result)
}
}
@@ -40,6 +40,9 @@ pub enum Flag {
/// The assembly code /// The assembly code
#[serde(rename = "evm.assembly")] #[serde(rename = "evm.assembly")]
Assembly, Assembly,
/// The Ir
#[serde(rename = "ir")]
Ir,
} }
impl std::fmt::Display for Flag { impl std::fmt::Display for Flag {
@@ -57,6 +60,7 @@ impl std::fmt::Display for Flag {
Self::EVMBC => write!(f, "evm.bytecode"), Self::EVMBC => write!(f, "evm.bytecode"),
Self::EVMDBC => write!(f, "evm.deployedBytecode"), Self::EVMDBC => write!(f, "evm.deployedBytecode"),
Self::Assembly => write!(f, "evm.assembly"), Self::Assembly => write!(f, "evm.assembly"),
Self::Ir => write!(f, "ir"),
} }
} }
} }
@@ -10,7 +10,7 @@ use serde::Serialize;
use self::flag::Flag as SelectionFlag; use self::flag::Flag as SelectionFlag;
/// The `solc --standard-json` output file selection. /// The `solc --standard-json` output file selection.
#[derive(Debug, Default, Serialize, Deserialize)] #[derive(Debug, Default, Serialize, Deserialize, PartialEq)]
pub struct File { pub struct File {
/// The per-file output selections. /// The per-file output selections.
#[serde(rename = "", skip_serializing_if = "Option::is_none")] #[serde(rename = "", skip_serializing_if = "Option::is_none")]
@@ -45,6 +45,7 @@ impl File {
self.per_contract self.per_contract
.get_or_insert_with(HashSet::default) .get_or_insert_with(HashSet::default)
.extend(required.per_contract.unwrap_or_default()); .extend(required.per_contract.unwrap_or_default());
self self
} }
} }
@@ -0,0 +1,99 @@
//! The `solc --standard-json` output selection.
pub mod file;
use std::collections::BTreeMap;
use serde::Deserialize;
use serde::Serialize;
use self::file::File as FileSelection;
/// The `solc --standard-json` output selection.
#[derive(Debug, Serialize, Deserialize, Default, PartialEq)]
pub struct Selection {
/// Only the 'all' wildcard is available for robustness reasons.
#[serde(rename = "*", skip_serializing_if = "Option::is_none")]
all: Option<FileSelection>,
#[serde(skip_serializing_if = "BTreeMap::is_empty", flatten)]
pub files: BTreeMap<String, FileSelection>,
}
impl Selection {
/// Creates the selection required by our compilation process.
pub fn new_required() -> Self {
Self {
all: Some(FileSelection::new_required()),
files: BTreeMap::new(),
}
}
/// Extends the user's output selection with flag required by our compilation process.
pub fn extend_with_required(&mut self) -> &mut Self {
self.all
.get_or_insert_with(FileSelection::new_required)
.extend_with_required();
for (_, v) in self.files.iter_mut() {
v.extend_with_required();
}
self
}
}
#[cfg(test)]
mod test {
use std::collections::BTreeMap;
use crate::SolcStandardJsonInputSettingsSelectionFile;
use super::Selection;
#[test]
fn per_file() {
let init = Selection {
all: None,
files: BTreeMap::from([(
"Test".to_owned(),
SolcStandardJsonInputSettingsSelectionFile::new_required(),
)]),
};
let deser = serde_json::to_string(&init)
.and_then(|string| serde_json::from_str(&string))
.unwrap();
assert_eq!(init, deser)
}
#[test]
fn all() {
let init = Selection {
all: Some(SolcStandardJsonInputSettingsSelectionFile::new_required()),
files: BTreeMap::new(),
};
let deser = serde_json::to_string(&init)
.and_then(|string| serde_json::from_str(&string))
.unwrap();
assert_eq!(init, deser)
}
#[test]
fn all_and_override() {
let init = Selection {
all: Some(SolcStandardJsonInputSettingsSelectionFile::new_required()),
files: BTreeMap::from([(
"Test".to_owned(),
SolcStandardJsonInputSettingsSelectionFile::new_required(),
)]),
};
let deser = serde_json::to_string(&init)
.and_then(|string| serde_json::from_str(&string))
.unwrap();
assert_eq!(init, deser)
}
}
@@ -0,0 +1,4 @@
//! The `solc <input>.sol --standard-json` interface input and output.
pub mod input;
pub mod output;

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