Compare commits

...

62 Commits

Author SHA1 Message Date
Sebastian Miasojed 1eb1083d40 Merge pull request #111 from smiasojed/resolc.js
Add compilation to NodeJS module
2024-11-26 14:44:44 +01:00
Sebastian Miasojed 4c0c74f7f4 Fix npm build 2024-11-25 15:36:36 +01:00
Sebastian Miasojed 229c0d452d Merge remote-tracking branch 'origin/main' into resolc.js 2024-11-25 10:54:44 +01:00
Sebastian Miasojed 4e024655a1 Add support for esm and cjs modules 2024-11-25 10:26:22 +01:00
Ermal Kaleci 01b5ed5ba3 Implement delegate_call (#80) 2024-11-23 18:05:21 +01:00
Sebastian Miasojed 892c9b5fbe Update clone-llvm.sh
Co-authored-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2024-11-22 19:52:06 +01:00
xermicus 84d47fa738 fix missing semicolon
Signed-off-by: xermicus <cyrill@parity.io>
2024-11-22 16:14:07 +01:00
Cyrill Leutwiler dbb47fd13e experimental: support for debug info (#118)
Signed-off-by: wpt967 <matt.aw@parity.io>
Signed-off-by: xermicus <cyrill@parity.io>
2024-11-22 08:56:09 +01:00
Cyrill Leutwiler 87f2bcefb3 dump the elf shared object into the debug output directory (#119)
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2024-11-21 21:48:42 +01:00
Sebastian Miasojed 05925f25f1 Update comment 2024-11-21 13:57:00 +01:00
Sebastian Miasojed 8990b2a486 Update emscripten-build-llvm.sh
Co-authored-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2024-11-21 12:49:23 +01:00
Cyrill Leutwiler 89ddfb28c8 CI: ignore the broken test (#116)
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2024-11-21 12:22:15 +01:00
Sebastian Miasojed 32d4b2309c Fix build script 2024-11-21 10:36:12 +01:00
Sebastian Miasojed a772ff1354 Fix build path 2024-11-21 10:29:21 +01:00
Sebastian Miasojed 0a82addf02 Add directory arg support to clone-llvm 2024-11-20 17:15:55 +01:00
Sebastian Miasojed 491850760f Update readme file 2024-11-20 12:57:41 +01:00
Sebastian Miasojed fcbe00f4a9 Merge branch 'main' into resolc.js 2024-11-20 12:51:10 +01:00
Sebastian Miasojed 77406264ea Improve description 2024-11-20 12:42:29 +01:00
Sebastian Miasojed cdebf69fc0 Fix stdin support 2024-11-20 10:54:15 +01:00
Sebastian Miasojed f36d62ca73 Add stdin support 2024-11-20 10:27:57 +01:00
Sebastian Miasojed 39e504703e Add test with standard json args 2024-11-18 16:52:56 +01:00
Sebastian Miasojed 63da7212a1 Add worker code to resolc.js 2024-11-18 14:49:35 +01:00
Sebastian Miasojed 3072c03600 Fix parallel feature 2024-11-18 13:59:48 +01:00
Sebastian Miasojed d88ddb25bd Refactor soljson compiler 2024-11-18 12:19:36 +01:00
Sebastian Miasojed 87dd77b784 Remove libsolc crate 2024-11-18 11:07:45 +01:00
Sebastian Miasojed cece20deb1 Cleanup 2024-11-18 10:18:21 +01:00
Sebastian Miasojed f57ab96eed Revert changes in llvm build 2024-11-15 16:08:36 +01:00
Cyrill Leutwiler 3232382d96 update dependencies (#115)
Signed-off-by: xermicus <cyrill@parity.io>
2024-11-15 13:50:55 +01:00
Cyrill Leutwiler 6a120463c2 implement the blockhash opcode (#114) 2024-11-15 13:11:04 +01:00
Sebastian Miasojed 881b88354c Rename compiler to solc 2024-11-14 17:09:03 +01:00
Cyrill Leutwiler c9dd347755 Add documentation portal (#99) 2024-11-14 14:21:48 +01:00
Sebastian Miasojed 140545ea15 Fix CI 2024-11-14 11:49:20 +01:00
Sebastian Miasojed 010a2ed223 Fmt 2024-11-14 11:44:48 +01:00
Sebastian Miasojed 563864dd25 Fix CI 2024-11-14 11:26:20 +01:00
Sebastian Miasojed ce8bf3d9ef Apply suggestions from previous review 2024-11-14 11:20:33 +01:00
Cyrill Leutwiler f947984671 update 64bit target flags (#113) 2024-11-14 10:29:21 +01:00
Sebastian Miasojed 14991f40ac Fix CI for wasm path 2024-11-13 15:34:41 +01:00
Sebastian Miasojed 6d16790f83 Update GHA 2024-11-12 17:22:15 +01:00
Sebastian Miasojed 64fefe76b5 Removed not needed libs from linking process 2024-11-12 16:07:21 +01:00
Sebastian Miasojed f59b47df7b Add ltinfo dep to GHA 2024-11-12 12:52:11 +01:00
Sebastian Miasojed 6e6fe20c71 Add Missing dep to GHA 2024-11-12 09:52:06 +01:00
Sebastian Miasojed 93d2f3b9d9 Log LLVM version in GHA 2024-11-12 09:47:43 +01:00
Sebastian Miasojed 8a225871ee Fix deps in GHA 2024-11-08 15:53:27 +01:00
Sebastian Miasojed 677aedc6f3 Switch GHA to ubuntu 2024-11-08 15:45:52 +01:00
Sebastian Miasojed 90423ffb6b Remove llvm-15 env from GHA 2024-11-08 15:36:00 +01:00
Sebastian Miasojed 255176978a Remove old llvm-15 from GHA 2024-11-08 15:27:11 +01:00
Sebastian Miasojed 9a8003afbf Install llvm in GHA 2024-11-08 15:10:42 +01:00
Sebastian Miasojed 7f3d0cecb2 Update cmake versions in GHA 2024-11-08 15:00:14 +01:00
Sebastian Miasojed 18376432f1 Fix GHA emsdk path 2024-11-08 14:01:18 +01:00
Sebastian Miasojed 007b79ee62 Use bash in GHA 2024-11-08 13:51:51 +01:00
Sebastian Miasojed 8c7d18aec7 Update GHA for wasm build 2024-11-08 13:46:00 +01:00
Sebastian Miasojed c0a82ce6d2 Update wasm GHA 2024-11-08 13:38:26 +01:00
Sebastian Miasojed c51d50bc88 Add GHA for wasm target 2024-11-08 13:28:45 +01:00
Sebastian Miasojed 94445bab93 Fix compilation for wasm target 2024-11-08 12:06:11 +01:00
Sebastian Miasojed a934ec204e Add temoprary wasm compilation output 2024-11-08 10:55:26 +01:00
Sebastian Miasojed b6baf6cfd9 Make wasm version to compile 2024-11-08 09:54:10 +01:00
Sebastian Miasojed b7b28efded Update solc compiler version check 2024-11-07 15:30:00 +01:00
Sebastian Miasojed d260472330 Fmt 2024-11-07 15:15:52 +01:00
Sebastian Miasojed 4f6debcbe3 Make native version to compaile again 2024-11-07 15:04:35 +01:00
Sebastian Miasojed 9b23e19479 Fix Cargo.toml 2024-11-07 09:43:44 +01:00
Sebastian Miasojed 88a888d138 Merge remote-tracking branch 'origin/main' into resolc.js 2024-11-06 15:04:34 +01:00
Sebastian Miasojed 5ac67bdc0d Initial wasm support 2024-08-29 17:28:31 +02:00
100 changed files with 3301 additions and 1392 deletions
+80
View File
@@ -0,0 +1,80 @@
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 }}/js/dist/revive-cjs
EMSCRIPTEN_VERSION: 3.1.64
jobs:
build-revive-wasm:
runs-on: ubuntu-latest
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v4
- name: Cache LLVM build
id: cache-llvm
uses: actions/cache@v3
with:
path: |
llvm18.0-emscripten
# Use a unique key based on LLVM version or configuration files to avoid cache invalidation
key: llvm-build-${{ runner.os }}-${{ hashFiles('clone-llvm.sh', 'emscripten-build-llvm.sh') }}
- name: Install Dependencies
run: |
sudo apt-get update && sudo apt-get install -y cmake ninja-build libncurses5
rustup target add wasm32-unknown-emscripten
# Install LLVM required for the compiler runtime, runtime-api and stdlib
curl -sSL --output llvm.tar.xz https://github.com/llvm/llvm-project/releases/download/llvmorg-18.1.4/clang+llvm-18.1.4-x86_64-linux-gnu-ubuntu-18.04.tar.xz
tar Jxf llvm.tar.xz
mv clang+llvm-18.1.4-x86_64-linux-gnu-ubuntu-18.04 llvm18/
echo "$(pwd)/llvm18/bin" >> $GITHUB_PATH
# Install Emscripten
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install ${{ env.EMSCRIPTEN_VERSION }}
./emsdk activate ${{ env.EMSCRIPTEN_VERSION }}
- run: |
rustup show
cargo --version
rustup +nightly show
cargo +nightly --version
cmake --version
bash --version
llvm-config --version
- name: Build LLVM
if: steps.cache-llvm.outputs.cache-hit != 'true'
run: |
export EMSDK_ROOT=${PWD}/emsdk
./emscripten-build-llvm.sh
- name: Use Cached LLVM
if: steps.cache-llvm.outputs.cache-hit == 'true'
run: |
echo "Using cached LLVM"
- name: Build revive
run: |
export LLVM_LINK_PREFIX=${PWD}/llvm18.0-emscripten
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 }}/worker.js
${{ env.REVIVE_WASM_INSTALL_DIR }}/resolc.wasm
retention-days: 1
+3
View File
@@ -8,9 +8,12 @@
/*.s /*.s
/llvm-project /llvm-project
/llvm18.0 /llvm18.0
/llvm18.0-emscripten
node_modules node_modules
artifacts artifacts
tmp tmp
package-lock.json package-lock.json
/*.html /*.html
/js/src/resolc.*
/js/dist/
/build /build
Generated
+1041 -726
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -67,7 +67,7 @@ log = { version = "0.4" }
# 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.1", default-features = false } scale-info = { version = "2.11.1", default-features = false }
polkadot-sdk = { git = "https://github.com/paritytech/polkadot-sdk", rev = "2b6b69641ccff4d7aa9c32051bbb2f1e775ef8cc" } polkadot-sdk = { git = "https://github.com/paritytech/polkadot-sdk", rev = "0449b214accd0f0fbf7ea3e8f3a8d8b7f99445e4" }
# llvm # llvm
[workspace.dependencies.inkwell] [workspace.dependencies.inkwell]
+22 -1
View File
@@ -1,5 +1,19 @@
.PHONY: install format test test-solidity test-cli test-integration test-workspace clean docs docs-build .PHONY: install format test test-solidity test-cli test-integration test-workspace clean docs docs-build
RUSTFLAGS_EMSCRIPTEN := \
-Clink-arg=-sEXPORTED_FUNCTIONS=_main,_free,_malloc \
-Clink-arg=-sNO_INVOKE_RUN \
-Clink-arg=-sEXIT_RUNTIME \
-Clink-arg=-sINITIAL_MEMORY=64MB \
-Clink-arg=-sTOTAL_MEMORY=3GB \
-Clink-arg=-sALLOW_MEMORY_GROWTH \
-Clink-arg=-sEXPORTED_RUNTIME_METHODS=FS,callMain,stringToNewUTF8,cwrap \
-Clink-arg=-sMODULARIZE \
-Clink-arg=-sEXPORT_ES6 \
-Clink-arg=-sEXPORT_NAME=createRevive \
-Clink-arg=--js-library=js/embed/soljson_interface.js \
-Clink-arg=--pre-js=js/embed/pre.js
install: install-bin install-npm install: install-bin install-npm
install-bin: install-bin:
@@ -8,6 +22,11 @@ install-bin:
install-npm: install-npm:
npm install && npm fund npm install && npm fund
install-wasm:
RUSTFLAGS='$(RUSTFLAGS_EMSCRIPTEN)' cargo build --target wasm32-unknown-emscripten -p revive-solidity --release --no-default-features
npm install
npm run build:revive
# install-revive: Build and install to the directory specified in REVIVE_INSTALL_DIR # install-revive: Build and install to the directory specified in REVIVE_INSTALL_DIR
ifeq ($(origin REVIVE_INSTALL_DIR), undefined) ifeq ($(origin REVIVE_INSTALL_DIR), undefined)
REVIVE_INSTALL_DIR=`pwd`/release/revive-debian REVIVE_INSTALL_DIR=`pwd`/release/revive-debian
@@ -58,4 +77,6 @@ clean:
rm -rf node_modules ; \ rm -rf node_modules ; \
rm -rf crates/solidity/src/tests/cli-tests/artifacts ; \ rm -rf crates/solidity/src/tests/cli-tests/artifacts ; \
cargo uninstall revive-solidity ; \ cargo uninstall revive-solidity ; \
rm -f package-lock.json rm -f package-lock.json ; \
rm -rf js/dist ; \
rm -f js/src/resolc.{wasm,js}
+33 -3
View File
@@ -1,14 +1,15 @@
![CI](https://github.com/paritytech/revive/actions/workflows/rust.yml/badge.svg) ![CI](https://github.com/paritytech/revive/actions/workflows/rust.yml/badge.svg)
[![Docs](https://img.shields.io/badge/Docs-contracts.polkadot.io-brightgreen.svg)](https://contracts.polakdot.io)
# revive # revive
YUL and EVM assembly recompiler to LLVM, targetting RISC-V on [PolkaVM](https://github.com/koute/polkavm). YUL and EVM assembly recompiler to LLVM, targetting RISC-V on [PolkaVM](https://github.com/koute/polkavm).
[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`. Visit [contracts.polkadot.io](contracts.polkadot.io) to learn more about contracts on Polkadot!
## Status ## Status
This is experimental software in active development and not ready just yet for production usage. This is experimental software in active development and not ready just yet for production usage. Please do report any compiler related issues or missing features that are [not yet known to us](https://contracts.polkadot.io/known_issues/) here.
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).
@@ -27,7 +28,25 @@ resolc --version
### LLVM ### LLVM
`revive` requires a build of LLVM 18.1.4 or later including `compiler-rt`. Use the provided [build-llvm.sh](build-llvm.sh) build script to compile a compatible LLVM build locally in `$PWD/llvm18.0` (don't forget to add that to `$PATH` afterwards). `revive` requires a build of LLVM 18.1.4 or later including `compiler-rt`. Use the provided [build-llvm.sh](build-llvm.sh) build script to compile a compatible LLVM build locally in `$PWD/llvm18.0` (don't forget to add that to `$PATH` afterwards).
### Cross-compilation to WASM
Cross-compiles the Revive compiler to WASM for running it in a Node.js or browser environment.
Install [emscripten](https://emscripten.org/docs/getting_started/downloads.html). Tested on version 3.1.64.
To build resolc.js execute:
```bash
bash build-llvm.sh
export PATH=${PWD}/llvm18.0/bin:$PATH
export EMSDK_ROOT=<PATH_TO_EMSCRIPTEN_SDK>
bash emscripten-build-llvm.sh
source $EMSDK_ROOT/emsdk_env.sh
export LLVM_LINK_PREFIX=${PWD}/llvm18.0-emscripten
export PATH=$PATH:$PWD/llvm18.0-emscripten/bin/
make install-wasm
```
### Development ### Development
@@ -35,4 +54,15 @@ Please consult the [Makefile](Makefile) targets to learn how to run tests and be
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
`revive` uses [solc](https://github.com/ethereum/solidity/), the Ethereum Solidity compiler, as the [Solidity frontend](crates/solidity/src/lib.rs) to process smart contracts written in Solidity. The YUL IR code (or legacy EVM assembly as a fallback for older `solc` versions) emitted by `solc` is then translated to LLVM IR, targetting [Polkadots `revive` pallet](https://docs.rs/pallet-revive/latest/pallet_revive/trait.SyscallDoc.html). `revive` uses [solc](https://github.com/ethereum/solidity/), the Ethereum Solidity compiler, as the [Solidity frontend](crates/solidity/src/lib.rs) to process smart contracts written in Solidity. The YUL IR code (or legacy EVM assembly as a fallback for older `solc` versions) emitted by `solc` is then translated to LLVM IR, targetting [Polkadots `revive` pallet](https://docs.rs/pallet-revive/latest/pallet_revive/trait.SyscallDoc.html).
[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`.
## Tests
Before running the tests, ensure that Geth (Go Ethereum) is installed on your system. Follow the installation guide here: [Installing Geth](https://geth.ethereum.org/docs/getting-started/installing-geth).
Once Geth is installed, you can run the tests using the following command:
```bash
make test
```
+30
View File
@@ -0,0 +1,30 @@
# Known issues
The following is known and we are either working on it or it is a hard limitation. Please do not open a new issue.
## Release
`0.1.0-dev-2`
## Missing features
- [Libraries with public functions are not supported](https://github.com/paritytech/revive/issues/91)
- [Automatic import resolution is not supported](https://github.com/paritytech/revive/issues/98)
- The emulated EVM linear contract memory is limited to 64kb in size. Will be fixed with support for metered dynamic memory.
- [The contract calldata is currently limited to 1kb in size](https://github.com/paritytech/revive/issues/57)
- [EIP-4844 opcodes are not supported](https://github.com/paritytech/revive/issues/64)
- [Delegate calls are not supported](https://github.com/paritytech/revive/issues/67)
- [The `blockhash` opcode is not supported](https://github.com/paritytech/revive/issues/61)
- [The `extcodesize` opcode is not supported](https://github.com/paritytech/revive/issues/58)
- [The `origin` opcode is not supported](https://github.com/paritytech/revive/issues/59)
- [Gas limits for contract calls are ignored](https://github.com/paritytech/revive/issues/60)
- [Gas related opcodes are not supported](https://github.com/paritytech/revive/issues/60)
- IPFS metadata hashes are not supported
- [Compiled contract artifacts can exceed the pallet static memory limit and fail to deploy](https://github.com/paritytech/revive/issues/96).
- [Transfers to inexistant accounts will fail if the transferred value lies below the ED.](https://github.com/paritytech/revive/issues/83) Will be fixed in the pallet to make the ED completely transparent for contracts.
## Wontfix
Please consult our documentation to learn more about Solidity and EVM features likely to remain unsupported (and why they will not be supported).
TODO: Insert link to the relevant documentation section.
+3 -7
View File
@@ -5,17 +5,13 @@ set -euo pipefail
INSTALL_DIR="${PWD}/llvm18.0" INSTALL_DIR="${PWD}/llvm18.0"
mkdir -p ${INSTALL_DIR} mkdir -p ${INSTALL_DIR}
# Clone LLVM 18 (any revision after commit bd32aaa is supposed to work)
if [ ! -d "llvm-project" ]; then
git clone --depth 1 --branch release/18.x https://github.com/llvm/llvm-project.git
fi
# Build LLVM, clang # Build LLVM, clang
LLVM_SRC_PREFIX=${PWD}/llvm-project LLVM_SRC_PREFIX=${PWD}/llvm-project
LLVM_SRC_DIR=${LLVM_SRC_PREFIX}/llvm LLVM_SRC_DIR=${LLVM_SRC_PREFIX}/llvm
LLVM_BUILD_DIR=${PWD}/build/llvm LLVM_BUILD_DIR=${PWD}/build/llvm
./clone-llvm.sh "${LLVM_SRC_PREFIX}"
if [ ! -d ${LLVM_BUILD_DIR} ] ; then if [ ! -d ${LLVM_BUILD_DIR} ] ; then
mkdir -p ${LLVM_BUILD_DIR} mkdir -p ${LLVM_BUILD_DIR}
fi fi
Executable
+18
View File
@@ -0,0 +1,18 @@
#!/bin/bash
set -euo pipefail
# Default directory for cloning the llvm-project repository
DEFAULT_DIR="llvm-project"
# Check if a directory argument is provided
if [ $# -eq 1 ]; then
DIR=$1
else
DIR=$DEFAULT_DIR
fi
# Clone LLVM 18 (any revision after commit bd32aaa is supposed to work)
if [ ! -d "${DIR}" ]; then
git clone --depth 1 --branch release/18.x https://github.com/llvm/llvm-project.git "${DIR}"
fi
+3
View File
@@ -41,3 +41,6 @@ pub static EXTENSION_POLKAVM_ASSEMBLY: &str = "pvmasm";
/// The PolkaVM bytecode file extension. /// The PolkaVM bytecode file extension.
pub static EXTENSION_POLKAVM_BINARY: &str = "pvm"; pub static EXTENSION_POLKAVM_BINARY: &str = "pvm";
/// The ELF shared object file extension.
pub static EXTENSION_SHARED_OBJECT: &str = "so";
+1
View File
@@ -557,6 +557,7 @@ allocated bytes: 3711"#;
} }
#[test] #[test]
#[ignore] // https://github.com/ethereum/go-ethereum/issues/30778
fn bench_flipper() { fn bench_flipper() {
let log_runtime = Evm::default() let log_runtime = Evm::default()
.code_blob(EVM_BIN_RUNTIME_FIXTURE.as_bytes().to_vec()) .code_blob(EVM_BIN_RUNTIME_FIXTURE.as_bytes().to_vec())
+8 -8
View File
@@ -1,10 +1,10 @@
{ {
"Baseline": 967, "Baseline": 961,
"Computation": 4022, "Computation": 4024,
"DivisionArithmetics": 31787, "DivisionArithmetics": 31789,
"ERC20": 44233, "ERC20": 44214,
"Events": 1743, "Events": 1737,
"FibonacciIterative": 2927, "FibonacciIterative": 2929,
"Flipper": 3408, "Flipper": 3402,
"SHA1": 26009 "SHA1": 26003
} }
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"differential": false,
"actions": [
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "BlockHash"
}
},
"data": "4545454545454545454545454545454545454545454545454545454545454545"
}
}
]
}
*/
contract BlockHash {
constructor(bytes32 expected) payable {
assert(blockhash(0) == expected);
assert(blockhash(1) == 0);
assert(
blockhash(
0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
) == 0
);
}
}
+84
View File
@@ -0,0 +1,84 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"differential": true,
"actions": [
{
"Upload": {
"code": {
"Solidity": {
"contract": "Logic"
}
}
}
},
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "Tester"
}
}
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"value": 123,
"data": "6466414b0000000000000000000000000000000000000000000000000000000000000020"
}
}
]
}
*/
contract Logic {
// NOTE: storage layout must be the same as contract Tester
uint256 public num;
address public sender;
uint256 public value;
uint public immutable multiplier = 4;
event DidSetVars();
function setVars(uint256 _num) public payable returns (uint256) {
num = _num * multiplier;
sender = msg.sender;
value = msg.value;
emit DidSetVars();
return _num;
}
}
contract Tester {
uint256 public num;
address public sender;
uint256 public value;
uint public immutable multiplier = 2;
function setVars(uint256 _num) public payable returns (bool, bytes memory) {
Logic impl = new Logic();
// Tester's storage is set, Logic is not modified.
(bool success, bytes memory data) = address(impl).delegatecall(
abi.encodeWithSignature("setVars(uint256)", _num)
);
assert(success);
assert(impl.num() == 0);
assert(impl.sender() == address(0));
assert(impl.value() == 0);
assert(num == _num * 4);
assert(sender == msg.sender);
assert(value == msg.value);
return (success, data);
}
}
+2
View File
@@ -44,6 +44,8 @@ test_spec!(transfer, "Transfer", "Transfer.sol");
test_spec!(return_data_oob, "ReturnDataOob", "ReturnDataOob.sol"); test_spec!(return_data_oob, "ReturnDataOob", "ReturnDataOob.sol");
test_spec!(immutables, "Immutables", "Immutables.sol"); test_spec!(immutables, "Immutables", "Immutables.sol");
test_spec!(transaction, "Transaction", "Transaction.sol"); test_spec!(transaction, "Transaction", "Transaction.sol");
test_spec!(block_hash, "BlockHash", "BlockHash.sol");
test_spec!(delegate, "Delegate", "Delegate.sol");
fn instantiate(path: &str, contract: &str) -> Vec<SpecsAction> { fn instantiate(path: &str, contract: &str) -> Vec<SpecsAction> {
vec![Instantiate { vec![Instantiate {
+3 -8
View File
@@ -29,9 +29,9 @@ fn invoke_lld(cmd_args: &[&str]) -> bool {
unsafe { LLDELFLink(args.as_ptr(), args.len()) == 0 } unsafe { LLDELFLink(args.as_ptr(), args.len()) == 0 }
} }
fn polkavm_linker<T: AsRef<[u8]>>(code: T) -> anyhow::Result<Vec<u8>> { pub fn polkavm_linker<T: AsRef<[u8]>>(code: T, strip_binary: bool) -> anyhow::Result<Vec<u8>> {
let mut config = polkavm_linker::Config::default(); let mut config = polkavm_linker::Config::default();
config.set_strip(true); config.set_strip(strip_binary);
config.set_optimize(true); config.set_optimize(true);
polkavm_linker::program_from_elf(config, code.as_ref()) polkavm_linker::program_from_elf(config, code.as_ref())
@@ -79,10 +79,5 @@ pub fn link<T: AsRef<[u8]>>(input: T) -> anyhow::Result<Vec<u8>> {
return Err(anyhow::anyhow!("ld.lld failed")); return Err(anyhow::anyhow!("ld.lld failed"));
} }
if env::var("PVM_LINKER_DUMP_SO").is_ok() { Ok(fs::read(&output_path)?)
fs::copy(&output_path, "/tmp/out.so")?;
};
let blob = fs::read(&output_path)?;
polkavm_linker(blob)
} }
+76 -8
View File
@@ -1,5 +1,19 @@
fn llvm_config(arg: &str) -> String { use std::{
let output = std::process::Command::new("llvm-config") env,
path::{Path, PathBuf},
};
const LLVM_LINK_PREFIX: &str = "LLVM_LINK_PREFIX";
fn locate_llvm_config() -> PathBuf {
let prefix = env::var_os(LLVM_LINK_PREFIX)
.map(|p| PathBuf::from(p).join("bin"))
.unwrap_or_default();
prefix.join("llvm-config")
}
fn llvm_config(llvm_config_path: &Path, arg: &str) -> String {
let output = std::process::Command::new(llvm_config_path)
.args([arg]) .args([arg])
.output() .output()
.unwrap_or_else(|_| panic!("`llvm-config {arg}` failed")); .unwrap_or_else(|_| panic!("`llvm-config {arg}` failed"));
@@ -8,8 +22,11 @@ fn llvm_config(arg: &str) -> String {
.unwrap_or_else(|_| panic!("output of `llvm-config {arg}` should be utf8")) .unwrap_or_else(|_| panic!("output of `llvm-config {arg}` should be utf8"))
} }
fn set_rustc_link_flags() { fn set_rustc_link_flags(llvm_config_path: &Path) {
println!("cargo:rustc-link-search=native={}", llvm_config("--libdir")); println!(
"cargo:rustc-link-search=native={}",
llvm_config(llvm_config_path, "--libdir")
);
for lib in [ for lib in [
"lldELF", "lldELF",
@@ -22,19 +39,70 @@ fn set_rustc_link_flags() {
"LLVMTargetParser", "LLVMTargetParser",
"LLVMBinaryFormat", "LLVMBinaryFormat",
"LLVMDemangle", "LLVMDemangle",
// The `llvm-sys` crate relies on `llvm-config` to obtain a list of required LLVM libraries
// during the build process. This works well in typical native environments, where `llvm-config`
// can accurately list the necessary libraries.
// However, when cross-compiling to WebAssembly using Emscripten, `llvm-config` fails to recognize
// JavaScript-based libraries, making it necessary to manually inject the required dependencies.
"LLVMRISCVDisassembler",
"LLVMRISCVAsmParser",
"LLVMRISCVCodeGen",
"LLVMRISCVDesc",
"LLVMRISCVInfo",
"LLVMExecutionEngine",
"LLVMOption",
"LLVMMCDisassembler",
"LLVMPasses",
"LLVMHipStdPar",
"LLVMCFGuard",
"LLVMCoroutines",
"LLVMipo",
"LLVMVectorize",
"LLVMInstrumentation",
"LLVMFrontendOpenMP",
"LLVMFrontendOffloading",
"LLVMGlobalISel",
"LLVMAsmPrinter",
"LLVMSelectionDAG",
"LLVMCodeGen",
"LLVMTarget",
"LLVMObjCARCOpts",
"LLVMCodeGenTypes",
"LLVMIRPrinter",
"LLVMScalarOpts",
"LLVMInstCombine",
"LLVMAggressiveInstCombine",
"LLVMTransformUtils",
"LLVMBitWriter",
"LLVMAnalysis",
"LLVMProfileData",
"LLVMDebugInfoDWARF",
"LLVMObject",
"LLVMMCParser",
"LLVMIRReader",
"LLVMAsmParser",
"LLVMMC",
"LLVMDebugInfoCodeView",
"LLVMBitReader",
"LLVMRemarks",
"LLVMBitstreamReader",
] { ] {
println!("cargo:rustc-link-lib=static={lib}"); println!("cargo:rustc-link-lib=static={lib}");
} }
#[cfg(target_os = "linux")] let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
{ if target_os == "linux" {
println!("cargo:rustc-link-lib=dylib=stdc++"); println!("cargo:rustc-link-lib=dylib=stdc++");
println!("cargo:rustc-link-lib=tinfo"); println!("cargo:rustc-link-lib=tinfo");
} }
} }
fn main() { fn main() {
llvm_config("--cxxflags") println!("cargo:rerun-if-env-changed={}", LLVM_LINK_PREFIX);
let llvm_config_path = locate_llvm_config();
llvm_config(&llvm_config_path, "--cxxflags")
.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") .flag("-Wno-unused-parameter")
@@ -42,7 +110,7 @@ fn main() {
.file("src/linker.cpp") .file("src/linker.cpp")
.compile("liblinker.a"); .compile("liblinker.a");
set_rustc_link_flags(); set_rustc_link_flags(&llvm_config_path);
println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-changed=build.rs");
} }
+6 -1
View File
@@ -15,7 +15,12 @@ doctest = false
[features] [features]
riscv-zbb = [] riscv-zbb = []
riscv-64 = [] riscv-64 = [
"revive-linker/riscv-64",
"revive-builtins/riscv-64",
"revive-runtime-api/riscv-64",
"revive-common/riscv-64",
]
[dependencies] [dependencies]
anyhow = { workspace = true } anyhow = { workspace = true }
@@ -15,6 +15,8 @@ pub enum IRType {
LLVM, LLVM,
/// Whether to dump the assembly code. /// Whether to dump the assembly code.
Assembly, Assembly,
/// Whether to dump the ELF shared object
SO,
/// Whether to jump JSON /// Whether to jump JSON
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
JSON, JSON,
@@ -31,6 +33,7 @@ impl IRType {
Self::Assembly => revive_common::EXTENSION_POLKAVM_ASSEMBLY, Self::Assembly => revive_common::EXTENSION_POLKAVM_ASSEMBLY,
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
Self::JSON => revive_common::EXTENSION_JSON, Self::JSON => revive_common::EXTENSION_JSON,
Self::SO => revive_common::EXTENSION_SHARED_OBJECT,
} }
} }
} }
+66 -33
View File
@@ -13,41 +13,52 @@ use self::ir_type::IRType;
#[derive(Debug, Default, Serialize, Deserialize, Clone)] #[derive(Debug, Default, Serialize, Deserialize, Clone)]
pub struct DebugConfig { pub struct DebugConfig {
/// The directory to dump the IRs to. /// The directory to dump the IRs to.
pub output_directory: PathBuf, pub output_directory: Option<PathBuf>,
/// Whether debug info should be emitted.
pub emit_debug_info: bool,
} }
impl DebugConfig { impl DebugConfig {
/// A shortcut constructor. /// A shortcut constructor.
pub fn new(output_directory: PathBuf) -> Self { pub const fn new(output_directory: Option<PathBuf>, emit_debug_info: bool) -> Self {
Self { output_directory } Self {
output_directory,
emit_debug_info,
}
} }
/// Dumps the Yul IR. /// Dumps the Yul IR.
pub fn dump_yul(&self, contract_path: &str, code: &str) -> anyhow::Result<()> { pub fn dump_yul(&self, contract_path: &str, code: &str) -> anyhow::Result<()> {
let mut file_path = self.output_directory.to_owned(); if let Some(output_directory) = self.output_directory.as_ref() {
let full_file_name = Self::full_file_name(contract_path, None, IRType::Yul); let mut file_path = output_directory.to_owned();
file_path.push(full_file_name); let full_file_name = Self::full_file_name(contract_path, None, IRType::Yul);
std::fs::write(file_path, code)?; file_path.push(full_file_name);
std::fs::write(file_path, code)?;
}
Ok(()) Ok(())
} }
/// Dumps the EVM legacy assembly IR. /// Dumps the EVM legacy assembly IR.
pub fn dump_evmla(&self, contract_path: &str, code: &str) -> anyhow::Result<()> { pub fn dump_evmla(&self, contract_path: &str, code: &str) -> anyhow::Result<()> {
let mut file_path = self.output_directory.to_owned(); if let Some(output_directory) = self.output_directory.as_ref() {
let full_file_name = Self::full_file_name(contract_path, None, IRType::EVMLA); let mut file_path = output_directory.to_owned();
file_path.push(full_file_name); let full_file_name = Self::full_file_name(contract_path, None, IRType::EVMLA);
std::fs::write(file_path, code)?; file_path.push(full_file_name);
std::fs::write(file_path, code)?;
}
Ok(()) Ok(())
} }
/// Dumps the Ethereal IR. /// Dumps the Ethereal IR.
pub fn dump_ethir(&self, contract_path: &str, code: &str) -> anyhow::Result<()> { pub fn dump_ethir(&self, contract_path: &str, code: &str) -> anyhow::Result<()> {
let mut file_path = self.output_directory.to_owned(); if let Some(output_directory) = self.output_directory.as_ref() {
let full_file_name = Self::full_file_name(contract_path, None, IRType::EthIR); let mut file_path = output_directory.to_owned();
file_path.push(full_file_name); let full_file_name = Self::full_file_name(contract_path, None, IRType::EthIR);
std::fs::write(file_path, code)?; file_path.push(full_file_name);
std::fs::write(file_path, code)?;
}
Ok(()) Ok(())
} }
@@ -58,12 +69,15 @@ impl DebugConfig {
contract_path: &str, contract_path: &str,
module: &inkwell::module::Module, module: &inkwell::module::Module,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let llvm_code = module.print_to_string().to_string(); if let Some(output_directory) = self.output_directory.as_ref() {
let llvm_code = module.print_to_string().to_string();
let mut file_path = self.output_directory.to_owned(); let mut file_path = output_directory.to_owned();
let full_file_name = Self::full_file_name(contract_path, Some("unoptimized"), IRType::LLVM); let full_file_name =
file_path.push(full_file_name); Self::full_file_name(contract_path, Some("unoptimized"), IRType::LLVM);
std::fs::write(file_path, llvm_code)?; file_path.push(full_file_name);
std::fs::write(file_path, llvm_code)?;
}
Ok(()) Ok(())
} }
@@ -74,22 +88,39 @@ impl DebugConfig {
contract_path: &str, contract_path: &str,
module: &inkwell::module::Module, module: &inkwell::module::Module,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let llvm_code = module.print_to_string().to_string(); if let Some(output_directory) = self.output_directory.as_ref() {
let llvm_code = module.print_to_string().to_string();
let mut file_path = self.output_directory.to_owned(); let mut file_path = output_directory.to_owned();
let full_file_name = Self::full_file_name(contract_path, Some("optimized"), IRType::LLVM); let full_file_name =
file_path.push(full_file_name); Self::full_file_name(contract_path, Some("optimized"), IRType::LLVM);
std::fs::write(file_path, llvm_code)?; file_path.push(full_file_name);
std::fs::write(file_path, llvm_code)?;
}
Ok(()) Ok(())
} }
/// Dumps the assembly. /// Dumps the assembly.
pub fn dump_assembly(&self, contract_path: &str, code: &str) -> anyhow::Result<()> { pub fn dump_assembly(&self, contract_path: &str, code: &str) -> anyhow::Result<()> {
let mut file_path = self.output_directory.to_owned(); if let Some(output_directory) = self.output_directory.as_ref() {
let full_file_name = Self::full_file_name(contract_path, None, IRType::Assembly); let mut file_path = output_directory.to_owned();
file_path.push(full_file_name); let full_file_name = Self::full_file_name(contract_path, None, IRType::Assembly);
std::fs::write(file_path, code)?; file_path.push(full_file_name);
std::fs::write(file_path, code)?;
}
Ok(())
}
/// Dumps the code object.
pub fn dump_object(&self, contract_path: &str, code: &[u8]) -> anyhow::Result<()> {
if let Some(output_directory) = self.output_directory.as_ref() {
let mut file_path = output_directory.to_owned();
let full_file_name = Self::full_file_name(contract_path, None, IRType::SO);
file_path.push(full_file_name);
std::fs::write(file_path, code)?;
}
Ok(()) Ok(())
} }
@@ -102,10 +133,12 @@ impl DebugConfig {
contract_suffix: Option<&str>, contract_suffix: Option<&str>,
stage_json: &Vec<u8>, stage_json: &Vec<u8>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let mut file_path = self.output_directory.to_owned(); if let Some(output_directory) = self.output_directory.as_ref() {
let full_file_name = Self::full_file_name(contract_path, contract_suffix, IRType::JSON); let mut file_path = output_directory.to_owned();
file_path.push(full_file_name); let full_file_name = Self::full_file_name(contract_path, contract_suffix, IRType::JSON);
std::fs::write(file_path, stage_json)?; file_path.push(full_file_name);
std::fs::write(file_path, stage_json)?;
}
Ok(()) Ok(())
} }
+1
View File
@@ -16,6 +16,7 @@ pub use self::polkavm::context::argument::Argument as PolkaVMArgument;
pub use self::polkavm::context::attribute::Attribute as PolkaVMAttribute; pub use self::polkavm::context::attribute::Attribute as PolkaVMAttribute;
pub use self::polkavm::context::build::Build as PolkaVMBuild; pub use self::polkavm::context::build::Build as PolkaVMBuild;
pub use self::polkavm::context::code_type::CodeType as PolkaVMCodeType; pub use self::polkavm::context::code_type::CodeType as PolkaVMCodeType;
pub use self::polkavm::context::debug_info::DebugInfo;
pub use self::polkavm::context::evmla_data::EVMLAData as PolkaVMContextEVMLAData; pub use self::polkavm::context::evmla_data::EVMLAData as PolkaVMContextEVMLAData;
pub use self::polkavm::context::function::block::evmla_data::key::Key as PolkaVMFunctionBlockKey; pub use self::polkavm::context::function::block::evmla_data::key::Key as PolkaVMFunctionBlockKey;
pub use self::polkavm::context::function::block::evmla_data::EVMLAData as PolkaVMFunctionBlockEVMLAData; pub use self::polkavm::context::function::block::evmla_data::EVMLAData as PolkaVMFunctionBlockEVMLAData;
@@ -9,7 +9,7 @@ use itertools::Itertools;
use self::size_level::SizeLevel; use self::size_level::SizeLevel;
/// The LLVM optimizer settings. /// The LLVM optimizer and code-gen settings.
#[derive(Debug, Serialize, Deserialize, Clone, Eq)] #[derive(Debug, Serialize, Deserialize, Clone, Eq)]
pub struct Settings { pub struct Settings {
/// The middle-end optimization level. /// The middle-end optimization level.
@@ -1,7 +1,43 @@
//! The LLVM debug information. //! The LLVM debug information.
use std::cell::RefCell;
use inkwell::debug_info::AsDIScope; use inkwell::debug_info::AsDIScope;
use num::Zero; use inkwell::debug_info::DIScope;
/// Debug info scope stack
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ScopeStack<'ctx> {
stack: Vec<DIScope<'ctx>>,
}
// Abstract the type of the DIScope stack.
impl<'ctx> ScopeStack<'ctx> {
pub fn from(item: DIScope<'ctx>) -> Self {
Self { stack: vec![item] }
}
/// Return the top of the scope stack, or None if the stack is empty.
pub fn top(&self) -> Option<DIScope<'ctx>> {
self.stack.last().copied()
}
/// Push a scope onto the stack.
pub fn push(&mut self, scope: DIScope<'ctx>) {
self.stack.push(scope)
}
/// Pop the scope at the top of the stack and return it.
/// Return None if the stack is empty.
pub fn pop(&mut self) -> Option<DIScope<'ctx>> {
self.stack.pop()
}
/// Return the number of scopes on the stack.
pub fn len(&self) -> usize {
self.stack.len()
}
}
/// The LLVM debug information. /// The LLVM debug information.
pub struct DebugInfo<'ctx> { pub struct DebugInfo<'ctx> {
@@ -9,6 +45,8 @@ pub struct DebugInfo<'ctx> {
compile_unit: inkwell::debug_info::DICompileUnit<'ctx>, compile_unit: inkwell::debug_info::DICompileUnit<'ctx>,
/// The debug info builder. /// The debug info builder.
builder: inkwell::debug_info::DebugInfoBuilder<'ctx>, builder: inkwell::debug_info::DebugInfoBuilder<'ctx>,
/// Enclosing debug info scopes.
scope_stack: RefCell<ScopeStack<'ctx>>,
} }
impl<'ctx> DebugInfo<'ctx> { impl<'ctx> DebugInfo<'ctx> {
@@ -35,19 +73,43 @@ impl<'ctx> DebugInfo<'ctx> {
Self { Self {
compile_unit, compile_unit,
builder, builder,
scope_stack: RefCell::new(ScopeStack::from(compile_unit.as_debug_info_scope())),
} }
} }
/// Prepare an LLVM-IR module for debug-info generation
pub fn initialize_module(
&self,
llvm: &'ctx inkwell::context::Context,
module: &inkwell::module::Module<'ctx>,
) {
let debug_metadata_value = llvm
.i32_type()
.const_int(inkwell::debug_info::debug_metadata_version() as u64, false);
module.add_basic_value_flag(
"Debug Info Version",
inkwell::module::FlagBehavior::Warning,
debug_metadata_value,
);
self.push_scope(self.compilation_unit().get_file().as_debug_info_scope());
}
/// Finalize debug-info for an LLVM-IR module.
pub fn finalize_module(&self) {
self.builder().finalize()
}
/// Creates a function info. /// Creates a function info.
pub fn create_function( pub fn create_function(
&self, &self,
name: &str, name: &str,
) -> anyhow::Result<inkwell::debug_info::DISubprogram<'ctx>> { ) -> anyhow::Result<inkwell::debug_info::DISubprogram<'ctx>> {
let flags = inkwell::debug_info::DIFlagsConstants::ZERO;
let subroutine_type = self.builder.create_subroutine_type( let subroutine_type = self.builder.create_subroutine_type(
self.compile_unit.get_file(), self.compile_unit.get_file(),
Some(self.create_type(revive_common::BIT_LENGTH_FIELD)?), Some(self.create_word_type(Some(flags))?.as_type()),
&[], &[],
inkwell::debug_info::DIFlags::zero(), flags,
); );
let function = self.builder.create_function( let function = self.builder.create_function(
@@ -60,7 +122,7 @@ impl<'ctx> DebugInfo<'ctx> {
true, true,
false, false,
1, 1,
inkwell::debug_info::DIFlags::zero(), flags,
false, false,
); );
@@ -74,24 +136,55 @@ impl<'ctx> DebugInfo<'ctx> {
Ok(function) Ok(function)
} }
/// Creates a primitive type info. /// Creates primitive integer type debug-info.
pub fn create_type( pub fn create_primitive_type(
&self, &self,
bit_length: usize, bit_length: usize,
) -> anyhow::Result<inkwell::debug_info::DIType<'ctx>> { flags: Option<inkwell::debug_info::DIFlags>,
) -> anyhow::Result<inkwell::debug_info::DIBasicType<'ctx>> {
let di_flags = flags.unwrap_or(inkwell::debug_info::DIFlagsConstants::ZERO);
let di_encoding: u32 = 0;
let type_name = String::from("U") + bit_length.to_string().as_str();
self.builder self.builder
.create_basic_type( .create_basic_type(type_name.as_str(), bit_length as u64, di_encoding, di_flags)
"U256",
bit_length as u64,
0,
inkwell::debug_info::DIFlags::zero(),
)
.map(|basic_type| basic_type.as_type())
.map_err(|error| anyhow::anyhow!("Debug info error: {}", error)) .map_err(|error| anyhow::anyhow!("Debug info error: {}", error))
} }
/// Finalizes the builder. /// Returns the debug-info model of word-sized integer types.
pub fn finalize(&self) { pub fn create_word_type(
self.builder.finalize(); &self,
flags: Option<inkwell::debug_info::DIFlags>,
) -> anyhow::Result<inkwell::debug_info::DIBasicType<'ctx>> {
self.create_primitive_type(revive_common::BIT_LENGTH_WORD, flags)
}
/// Return the DIBuilder.
pub fn builder(&self) -> &inkwell::debug_info::DebugInfoBuilder<'ctx> {
&self.builder
}
/// Return the compilation unit. {
pub fn compilation_unit(&self) -> &inkwell::debug_info::DICompileUnit<'ctx> {
&self.compile_unit
}
/// Push a debug-info scope onto the stack.
pub fn push_scope(&self, scope: DIScope<'ctx>) {
self.scope_stack.borrow_mut().push(scope)
}
/// Pop the top of the debug-info scope stack and return it.
pub fn pop_scope(&self) -> Option<DIScope<'ctx>> {
self.scope_stack.borrow_mut().pop()
}
/// Return the top of the debug-info scope stack.
pub fn top_scope(&self) -> Option<DIScope<'ctx>> {
self.scope_stack.borrow().top()
}
/// Return the number of debug-info scopes on the scope stack.
pub fn num_scopes(&self) -> usize {
self.scope_stack.borrow().len()
} }
} }
@@ -17,4 +17,8 @@ impl<'ctx> Declaration<'ctx> {
) -> Self { ) -> Self {
Self { r#type, value } Self { r#type, value }
} }
pub fn function_value(&self) -> inkwell::values::FunctionValue<'ctx> {
self.value
}
} }
@@ -11,6 +11,8 @@ pub mod yul_data;
use std::collections::HashMap; use std::collections::HashMap;
use inkwell::debug_info::AsDIScope;
use crate::optimizer::settings::size_level::SizeLevel; use crate::optimizer::settings::size_level::SizeLevel;
use crate::optimizer::Optimizer; use crate::optimizer::Optimizer;
use crate::polkavm::context::attribute::Attribute; use crate::polkavm::context::attribute::Attribute;
@@ -94,6 +96,14 @@ impl<'ctx> Function<'ctx> {
self.declaration self.declaration
} }
/// Returns the debug-info scope.
pub fn get_debug_scope(&self) -> Option<inkwell::debug_info::DIScope<'ctx>> {
self.declaration()
.function_value()
.get_subprogram()
.map(|scp| scp.as_debug_info_scope())
}
/// Returns the N-th parameter of the function. /// Returns the N-th parameter of the function.
pub fn get_nth_param(&self, index: usize) -> inkwell::values::BasicValueEnum<'ctx> { pub fn get_nth_param(&self, index: usize) -> inkwell::values::BasicValueEnum<'ctx> {
self.declaration() self.declaration()
@@ -54,12 +54,14 @@ where
} }
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> { fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
context.set_current_function(runtime::FUNCTION_DEPLOY_CODE)?; context.set_current_function(runtime::FUNCTION_DEPLOY_CODE, None)?;
context.set_basic_block(context.current_function().borrow().entry_block()); context.set_basic_block(context.current_function().borrow().entry_block());
context.set_code_type(CodeType::Deploy); context.set_code_type(CodeType::Deploy);
self.inner.into_llvm(context)?; self.inner.into_llvm(context)?;
context.set_debug_location(0, 0, None)?;
match context match context
.basic_block() .basic_block()
.get_last_instruction() .get_last_instruction()
@@ -72,8 +74,11 @@ where
} }
context.set_basic_block(context.current_function().borrow().return_block()); context.set_basic_block(context.current_function().borrow().return_block());
context.set_debug_location(0, 0, None)?;
context.build_return(None); context.build_return(None);
context.pop_debug_scope();
Ok(()) Ok(())
} }
} }
@@ -125,6 +125,8 @@ impl Entry {
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
context.set_debug_location(0, 0, None)?;
let is_deploy = context let is_deploy = context
.current_function() .current_function()
.borrow() .borrow()
@@ -214,7 +216,7 @@ where
true, true,
); );
context.set_current_function(runtime::FUNCTION_ENTRY)?; context.set_current_function(runtime::FUNCTION_ENTRY, None)?;
context.set_basic_block(context.current_function().borrow().entry_block()); context.set_basic_block(context.current_function().borrow().entry_block());
Self::initialize_globals(context)?; Self::initialize_globals(context)?;
@@ -225,6 +227,8 @@ where
context.set_basic_block(context.current_function().borrow().return_block()); context.set_basic_block(context.current_function().borrow().return_block());
context.build_unreachable(); context.build_unreachable();
context.pop_debug_scope();
Ok(()) Ok(())
} }
} }
@@ -34,7 +34,7 @@ where
} }
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> { fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
context.set_current_function(runtime::FUNCTION_LOAD_IMMUTABLE_DATA)?; context.set_current_function(runtime::FUNCTION_LOAD_IMMUTABLE_DATA, None)?;
context.set_basic_block(context.current_function().borrow().entry_block()); context.set_basic_block(context.current_function().borrow().entry_block());
let immutable_data_size_pointer = context let immutable_data_size_pointer = context
@@ -111,6 +111,8 @@ where
context.set_basic_block(return_block); context.set_basic_block(return_block);
context.build_return(None); context.build_return(None);
context.pop_debug_scope();
Ok(()) Ok(())
} }
} }
@@ -54,11 +54,14 @@ where
} }
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> { fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
context.set_current_function(runtime::FUNCTION_RUNTIME_CODE)?; context.set_current_function(runtime::FUNCTION_RUNTIME_CODE, None)?;
context.set_basic_block(context.current_function().borrow().entry_block()); context.set_basic_block(context.current_function().borrow().entry_block());
context.set_code_type(CodeType::Runtime); context.set_code_type(CodeType::Runtime);
self.inner.into_llvm(context)?; self.inner.into_llvm(context)?;
context.set_debug_location(0, 0, None)?;
match context match context
.basic_block() .basic_block()
.get_last_instruction() .get_last_instruction()
@@ -73,6 +76,8 @@ where
context.set_basic_block(context.current_function().borrow().return_block()); context.set_basic_block(context.current_function().borrow().return_block());
context.build_unreachable(); context.build_unreachable();
context.pop_debug_scope();
Ok(()) Ok(())
} }
} }
+136 -19
View File
@@ -5,7 +5,7 @@ pub mod argument;
pub mod attribute; pub mod attribute;
pub mod build; pub mod build;
pub mod code_type; pub mod code_type;
// pub mod debug_info; pub mod debug_info;
pub mod evmla_data; pub mod evmla_data;
pub mod function; pub mod function;
pub mod global; pub mod global;
@@ -21,6 +21,8 @@ use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
use std::rc::Rc; use std::rc::Rc;
use inkwell::debug_info::AsDIScope;
use inkwell::debug_info::DIScope;
use inkwell::types::BasicType; use inkwell::types::BasicType;
use inkwell::values::BasicValue; use inkwell::values::BasicValue;
@@ -35,7 +37,7 @@ use self::address_space::AddressSpace;
use self::attribute::Attribute; use self::attribute::Attribute;
use self::build::Build; use self::build::Build;
use self::code_type::CodeType; use self::code_type::CodeType;
// use self::debug_info::DebugInfo; use self::debug_info::DebugInfo;
use self::evmla_data::EVMLAData; use self::evmla_data::EVMLAData;
use self::function::declaration::Declaration as FunctionDeclaration; use self::function::declaration::Declaration as FunctionDeclaration;
use self::function::intrinsics::Intrinsics; use self::function::intrinsics::Intrinsics;
@@ -85,9 +87,9 @@ where
/// Whether to append the metadata hash at the end of bytecode. /// Whether to append the metadata hash at the end of bytecode.
include_metadata_hash: bool, include_metadata_hash: bool,
/// The debug info of the current module. /// The debug info of the current module.
// debug_info: DebugInfo<'ctx>, debug_info: Option<DebugInfo<'ctx>>,
/// The debug configuration telling whether to dump the needed IRs. /// The debug configuration telling whether to dump the needed IRs.
debug_config: Option<DebugConfig>, debug_config: DebugConfig,
/// The Solidity data. /// The Solidity data.
solidity_data: Option<SolidityData>, solidity_data: Option<SolidityData>,
@@ -138,7 +140,7 @@ where
for import in revive_runtime_api::polkavm_imports::IMPORTS { for import in revive_runtime_api::polkavm_imports::IMPORTS {
module module
.get_function(import) .get_function(import)
.expect("should be declared") .unwrap_or_else(|| panic!("{import} import should be declared"))
.set_linkage(inkwell::module::Linkage::External); .set_linkage(inkwell::module::Linkage::External);
} }
} }
@@ -207,7 +209,7 @@ where
optimizer: Optimizer, optimizer: Optimizer,
dependency_manager: Option<D>, dependency_manager: Option<D>,
include_metadata_hash: bool, include_metadata_hash: bool,
debug_config: Option<DebugConfig>, debug_config: DebugConfig,
) -> Self { ) -> Self {
Self::link_stdlib_module(llvm, &module); Self::link_stdlib_module(llvm, &module);
Self::link_polkavm_imports(llvm, &module); Self::link_polkavm_imports(llvm, &module);
@@ -216,6 +218,11 @@ where
let intrinsics = Intrinsics::new(llvm, &module); let intrinsics = Intrinsics::new(llvm, &module);
let llvm_runtime = LLVMRuntime::new(llvm, &module, &optimizer); let llvm_runtime = LLVMRuntime::new(llvm, &module, &optimizer);
let debug_info = debug_config.emit_debug_info.then(|| {
let debug_info = DebugInfo::new(&module);
debug_info.initialize_module(llvm, &module);
debug_info
});
Self { Self {
llvm, llvm,
@@ -232,7 +239,8 @@ where
dependency_manager, dependency_manager,
include_metadata_hash, include_metadata_hash,
// debug_info,
debug_info,
debug_config, debug_config,
solidity_data: None, solidity_data: None,
@@ -255,9 +263,9 @@ where
let target_machine = TargetMachine::new(Target::PVM, self.optimizer.settings())?; let target_machine = TargetMachine::new(Target::PVM, self.optimizer.settings())?;
target_machine.set_target_data(self.module()); target_machine.set_target_data(self.module());
if let Some(ref debug_config) = self.debug_config { self.debug_config
debug_config.dump_llvm_ir_unoptimized(contract_path, self.module())?; .dump_llvm_ir_unoptimized(contract_path, self.module())?;
}
self.verify().map_err(|error| { self.verify().map_err(|error| {
anyhow::anyhow!( anyhow::anyhow!(
"The contract `{}` unoptimized LLVM IR verification error: {}", "The contract `{}` unoptimized LLVM IR verification error: {}",
@@ -275,9 +283,10 @@ where
error error
) )
})?; })?;
if let Some(ref debug_config) = self.debug_config {
debug_config.dump_llvm_ir_optimized(contract_path, self.module())?; self.debug_config
} .dump_llvm_ir_optimized(contract_path, self.module())?;
self.verify().map_err(|error| { self.verify().map_err(|error| {
anyhow::anyhow!( anyhow::anyhow!(
"The contract `{}` optimized LLVM IR verification error: {}", "The contract `{}` optimized LLVM IR verification error: {}",
@@ -296,11 +305,17 @@ where
) )
})?; })?;
let bytecode = revive_linker::link(buffer.as_slice())?; let shared_object = revive_linker::link(buffer.as_slice())?;
self.debug_config
.dump_object(contract_path, &shared_object)?;
let polkavm_bytecode =
revive_linker::polkavm_linker(shared_object, !self.debug_config().emit_debug_info)?;
let build = match crate::polkavm::build_assembly_text( let build = match crate::polkavm::build_assembly_text(
contract_path, contract_path,
&bytecode, &polkavm_bytecode,
metadata_hash, metadata_hash,
self.debug_config(), self.debug_config(),
) { ) {
@@ -422,6 +437,21 @@ where
) -> anyhow::Result<Rc<RefCell<Function<'ctx>>>> { ) -> anyhow::Result<Rc<RefCell<Function<'ctx>>>> {
let value = self.module().add_function(name, r#type, linkage); let value = self.module().add_function(name, r#type, linkage);
if self.debug_info().is_some() {
self.builder().unset_current_debug_location();
let func_scope = match value.get_subprogram() {
None => {
let fn_name = value.get_name().to_str()?;
let scp = self.build_function_debug_info(fn_name, 0)?;
value.set_subprogram(scp);
scp
}
Some(scp) => scp,
};
self.push_debug_scope(func_scope.as_debug_info_scope());
self.set_debug_location(0, 0, Some(func_scope.as_debug_info_scope()))?;
}
let entry_block = self.llvm.append_basic_block(value, "entry"); let entry_block = self.llvm.append_basic_block(value, "entry");
let return_block = self.llvm.append_basic_block(value, "return"); let return_block = self.llvm.append_basic_block(value, "return");
@@ -455,6 +485,8 @@ where
let function = Rc::new(RefCell::new(function)); let function = Rc::new(RefCell::new(function));
self.functions.insert(name.to_string(), function.clone()); self.functions.insert(name.to_string(), function.clone());
self.pop_debug_scope();
Ok(function) Ok(function)
} }
@@ -470,15 +502,95 @@ where
.expect("Must be declared before use") .expect("Must be declared before use")
} }
/// Sets the current active function. /// Sets the current active function. If debug-info generation is enabled,
pub fn set_current_function(&mut self, name: &str) -> anyhow::Result<()> { /// constructs a debug-scope and pushes in on the scope-stack.
pub fn set_current_function(&mut self, name: &str, line: Option<u32>) -> anyhow::Result<()> {
let function = self.functions.get(name).cloned().ok_or_else(|| { let function = self.functions.get(name).cloned().ok_or_else(|| {
anyhow::anyhow!("Failed to activate an undeclared function `{}`", name) anyhow::anyhow!("Failed to activate an undeclared function `{}`", name)
})?; })?;
self.current_function = Some(function); self.current_function = Some(function);
if let Some(scope) = self.current_function().borrow().get_debug_scope() {
self.push_debug_scope(scope);
}
self.set_debug_location(line.unwrap_or_default(), 0, None)?;
Ok(()) Ok(())
} }
/// Builds a debug-info scope for a function.
pub fn build_function_debug_info(
&self,
name: &str,
line_no: u32,
) -> anyhow::Result<inkwell::debug_info::DISubprogram<'ctx>> {
let Some(debug_info) = self.debug_info() else {
anyhow::bail!("expected debug-info builders");
};
let builder = debug_info.builder();
let file = debug_info.compilation_unit().get_file();
let scope = file.as_debug_info_scope();
let flags = inkwell::debug_info::DIFlagsConstants::PUBLIC;
let return_type = debug_info.create_word_type(Some(flags))?.as_type();
let subroutine_type = builder.create_subroutine_type(file, Some(return_type), &[], flags);
Ok(builder.create_function(
scope,
name,
None,
file,
line_no,
subroutine_type,
false,
true,
1,
flags,
false,
))
}
/// Set the debug info location.
///
/// No-op if the emitting debug info is disabled.
///
/// If `scope` is `None` the top scope will be used.
pub fn set_debug_location(
&self,
line: u32,
column: u32,
scope: Option<DIScope<'ctx>>,
) -> anyhow::Result<()> {
let Some(debug_info) = self.debug_info() else {
return Ok(());
};
let scope = match scope {
Some(scp) => scp,
None => debug_info.top_scope().expect("expected a debug-info scope"),
};
let location =
debug_info
.builder()
.create_debug_location(self.llvm(), line, column, scope, None);
self.builder().set_current_debug_location(location);
Ok(())
}
/// Pushes a debug-info scope to the stack.
pub fn push_debug_scope(&self, scope: DIScope<'ctx>) {
if let Some(debug_info) = self.debug_info() {
debug_info.push_scope(scope);
}
}
/// Pops the top of the debug-info scope stack.
pub fn pop_debug_scope(&self) {
if let Some(debug_info) = self.debug_info() {
debug_info.pop_scope();
}
}
/// Pushes a new loop context to the stack. /// Pushes a new loop context to the stack.
pub fn push_loop( pub fn push_loop(
&mut self, &mut self,
@@ -548,9 +660,14 @@ where
.expect("The dependency manager is unset") .expect("The dependency manager is unset")
} }
/// Returns the debug info.
pub fn debug_info(&self) -> Option<&DebugInfo<'ctx>> {
self.debug_info.as_ref()
}
/// Returns the debug config reference. /// Returns the debug config reference.
pub fn debug_config(&self) -> Option<&DebugConfig> { pub fn debug_config(&self) -> &DebugConfig {
self.debug_config.as_ref() &self.debug_config
} }
/// Appends a new basic block to the current function. /// Appends a new basic block to the current function.
@@ -15,7 +15,7 @@ pub fn create_context(
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, None) Context::<DummyDependency>::new(llvm, module, optimizer, None, true, Default::default())
} }
#[test] #[test]
+71 -11
View File
@@ -13,7 +13,7 @@ const REENTRANT_CALL_FLAG: u32 = 0b0000_1000;
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn call<'ctx, D>( pub fn call<'ctx, D>(
context: &mut Context<'ctx, D>, context: &mut Context<'ctx, D>,
_gas: inkwell::values::IntValue<'ctx>, gas: inkwell::values::IntValue<'ctx>,
address: inkwell::values::IntValue<'ctx>, address: inkwell::values::IntValue<'ctx>,
value: Option<inkwell::values::IntValue<'ctx>>, value: Option<inkwell::values::IntValue<'ctx>>,
input_offset: inkwell::values::IntValue<'ctx>, input_offset: inkwell::values::IntValue<'ctx>,
@@ -40,7 +40,7 @@ where
// TODO: What to supply here? Is there a weight to gas? // TODO: What to supply here? Is there a weight to gas?
let _gas = context let _gas = context
.builder() .builder()
.build_int_truncate(_gas, context.integer_type(64), "gas")?; .build_int_truncate(gas, context.integer_type(64), "gas")?;
let input_pointer = context.build_heap_gep(input_offset, input_length)?; let input_pointer = context.build_heap_gep(input_offset, input_length)?;
let output_pointer = context.build_heap_gep(output_offset, output_length)?; let output_pointer = context.build_heap_gep(output_offset, output_length)?;
@@ -102,20 +102,80 @@ where
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn delegate_call<'ctx, D>( pub fn delegate_call<'ctx, D>(
_context: &mut Context<'ctx, D>, context: &mut Context<'ctx, D>,
_gas: inkwell::values::IntValue<'ctx>, gas: inkwell::values::IntValue<'ctx>,
_address: inkwell::values::IntValue<'ctx>, address: inkwell::values::IntValue<'ctx>,
_value: Option<inkwell::values::IntValue<'ctx>>, input_offset: inkwell::values::IntValue<'ctx>,
_input_offset: inkwell::values::IntValue<'ctx>, input_length: inkwell::values::IntValue<'ctx>,
_input_length: inkwell::values::IntValue<'ctx>, output_offset: inkwell::values::IntValue<'ctx>,
_output_offset: inkwell::values::IntValue<'ctx>, output_length: inkwell::values::IntValue<'ctx>,
_output_length: inkwell::values::IntValue<'ctx>,
_constants: Vec<Option<num::BigUint>>, _constants: Vec<Option<num::BigUint>>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>> ) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
todo!() let address_pointer = context.build_address_argument_store(address)?;
let input_offset = context.safe_truncate_int_to_xlen(input_offset)?;
let input_length = context.safe_truncate_int_to_xlen(input_length)?;
let output_offset = context.safe_truncate_int_to_xlen(output_offset)?;
let output_length = context.safe_truncate_int_to_xlen(output_length)?;
// TODO: What to supply here? Is there a weight to gas?
let _gas = context
.builder()
.build_int_truncate(gas, context.integer_type(64), "gas")?;
let input_pointer = context.build_heap_gep(input_offset, input_length)?;
let output_pointer = context.build_heap_gep(output_offset, output_length)?;
let output_length_pointer = context.build_alloca_at_entry(context.xlen_type(), "output_length");
context.build_store(output_length_pointer, output_length)?;
let flags = context.xlen_type().const_int(0u64, false);
let argument_type = revive_runtime_api::calling_convention::delegate_call(context.llvm());
let argument_pointer = context.build_alloca_at_entry(argument_type, "delegate_call_arguments");
let arguments = &[
flags.as_basic_value_enum(),
address_pointer.value.as_basic_value_enum(),
context.integer_const(64, 0).as_basic_value_enum(),
context.integer_const(64, 0).as_basic_value_enum(),
context.sentinel_pointer().value.as_basic_value_enum(),
input_pointer.value.as_basic_value_enum(),
input_length.as_basic_value_enum(),
output_pointer.value.as_basic_value_enum(),
output_length_pointer.value.as_basic_value_enum(),
];
revive_runtime_api::calling_convention::spill(
context.builder(),
argument_pointer.value,
argument_type,
arguments,
)?;
let name = revive_runtime_api::polkavm_imports::DELEGATE_CALL;
let argument_pointer = context.builder().build_ptr_to_int(
argument_pointer.value,
context.xlen_type(),
"delegate_call_argument_pointer",
)?;
let success = context
.build_runtime_call(name, &[argument_pointer.into()])
.unwrap_or_else(|| panic!("{name} should return a value"))
.into_int_value();
let is_success = context.builder().build_int_compare(
inkwell::IntPredicate::EQ,
success,
context.xlen_type().const_zero(),
"is_success",
)?;
Ok(context
.builder()
.build_int_z_extend(is_success, context.word_type(), "success")?
.as_basic_value_enum())
} }
/// Translates the Yul `linkersymbol` instruction. /// Translates the Yul `linkersymbol` instruction.
+14 -3
View File
@@ -74,13 +74,24 @@ where
/// Translates the `block_hash` instruction. /// Translates the `block_hash` instruction.
pub fn block_hash<'ctx, D>( pub fn block_hash<'ctx, D>(
_context: &mut Context<'ctx, D>, context: &mut Context<'ctx, D>,
_index: inkwell::values::IntValue<'ctx>, index: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>> ) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
todo!() 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");
context.build_store(index_ptr, index)?;
context.build_runtime_call(
revive_runtime_api::polkavm_imports::BLOCK_HASH,
&[
index_ptr.to_int(context).into(),
output_pointer.to_int(context).into(),
],
);
context.build_byte_swap(context.build_load(output_pointer, "block_hash")?)
} }
/// Translates the `difficulty` instruction. /// Translates the `difficulty` instruction.
+4 -6
View File
@@ -29,7 +29,7 @@ pub fn build_assembly_text(
contract_path: &str, contract_path: &str,
bytecode: &[u8], bytecode: &[u8],
metadata_hash: Option<[u8; revive_common::BYTE_LENGTH_WORD]>, metadata_hash: Option<[u8; revive_common::BYTE_LENGTH_WORD]>,
debug_config: Option<&DebugConfig>, debug_config: &DebugConfig,
) -> anyhow::Result<Build> { ) -> anyhow::Result<Build> {
let program_blob = ProgramBlob::parse(bytecode.into()) let program_blob = ProgramBlob::parse(bytecode.into())
.map_err(anyhow::Error::msg) .map_err(anyhow::Error::msg)
@@ -49,9 +49,7 @@ pub fn build_assembly_text(
format!("Failed to convert disassembled code to string for contract: {contract_path}") format!("Failed to convert disassembled code to string for contract: {contract_path}")
})?; })?;
if let Some(debug_config) = debug_config { debug_config.dump_assembly(contract_path, &assembly_text)?;
debug_config.dump_assembly(contract_path, &assembly_text)?;
}
Ok(Build::new( Ok(Build::new(
assembly_text.to_owned(), assembly_text.to_owned(),
@@ -98,7 +96,7 @@ pub trait Dependency {
path: &str, path: &str,
optimizer_settings: OptimizerSettings, optimizer_settings: OptimizerSettings,
include_metadata_hash: bool, include_metadata_hash: bool,
debug_config: Option<DebugConfig>, debug_config: DebugConfig,
) -> anyhow::Result<String>; ) -> anyhow::Result<String>;
/// Resolves a full contract path. /// Resolves a full contract path.
@@ -118,7 +116,7 @@ impl Dependency for DummyDependency {
_path: &str, _path: &str,
_optimizer_settings: OptimizerSettings, _optimizer_settings: OptimizerSettings,
_include_metadata_hash: bool, _include_metadata_hash: bool,
_debug_config: Option<DebugConfig>, _debug_config: DebugConfig,
) -> anyhow::Result<String> { ) -> anyhow::Result<String> {
Ok(String::new()) Ok(String::new())
} }
+6 -2
View File
@@ -11,9 +11,9 @@ const TARGET_FLAG: &str = "--target=riscv32";
const TARGET_FLAG: &str = "--target=riscv64"; const TARGET_FLAG: &str = "--target=riscv64";
#[cfg(not(feature = "riscv-64"))] #[cfg(not(feature = "riscv-64"))]
const TARGET_ARCH_FLAG: &str = "-march=rv32em"; const TARGET_ARCH_FLAG: &str = "-march=rv32emac";
#[cfg(feature = "riscv-64")] #[cfg(feature = "riscv-64")]
const TARGET_ARCH_FLAG: &str = "-march=rv64em"; const TARGET_ARCH_FLAG: &str = "-march=rv64emac";
#[cfg(not(feature = "riscv-64"))] #[cfg(not(feature = "riscv-64"))]
const TARGET_ABI_FLAG: &str = "-mabi=ilp32e"; const TARGET_ABI_FLAG: &str = "-mabi=ilp32e";
@@ -36,6 +36,10 @@ fn compile(source_path: &str, bitcode_path: &str) {
TARGET_TRIPLE_FLAG, TARGET_TRIPLE_FLAG,
TARGET_ARCH_FLAG, TARGET_ARCH_FLAG,
TARGET_ABI_FLAG, TARGET_ABI_FLAG,
"-Xclang",
"-target-feature",
"-Xclang",
"+fast-unaligned-access,+xtheadcondmov",
"-fno-exceptions", "-fno-exceptions",
"-ffreestanding", "-ffreestanding",
"-Wall", "-Wall",
@@ -106,3 +106,30 @@ pub fn call(context: &Context) -> StructType {
true, true,
) )
} }
/// Returns a packed struct argument type for the `delegate_call` API.
pub fn delegate_call(context: &Context) -> StructType {
context.struct_type(
&[
// flags: u32,
context.i32_type().as_basic_type_enum(),
// address_ptr:
context.ptr_type(Default::default()).as_basic_type_enum(),
// ref_time_limit: u64,
context.i64_type().as_basic_type_enum(),
// proof_size_limit: u64,
context.i64_type().as_basic_type_enum(),
// deposit_ptr: u32,
context.ptr_type(Default::default()).as_basic_type_enum(),
// input_data_ptr: u32,
context.ptr_type(Default::default()).as_basic_type_enum(),
// input_data_len: u32,
context.i32_type().as_basic_type_enum(),
// output_ptr: u32,
context.ptr_type(Default::default()).as_basic_type_enum(),
// output_len_ptr: u32,
context.ptr_type(Default::default()).as_basic_type_enum(),
],
true,
)
}
+28 -6
View File
@@ -9,7 +9,11 @@
#define POLKAVM_REGS_FOR_TY_void 0 #define POLKAVM_REGS_FOR_TY_void 0
#define POLKAVM_REGS_FOR_TY_i32 1 #define POLKAVM_REGS_FOR_TY_i32 1
#define POLKAVM_REGS_FOR_TY_i64 2 #ifdef _LP64
#define POLKAVM_REGS_FOR_TY_i64 1
#else
#define POLKAVM_REGS_FOR_TY_i64 2
#endif
#define POLKAVM_REGS_FOR_TY_int8_t POLKAVM_REGS_FOR_TY_i32 #define POLKAVM_REGS_FOR_TY_int8_t POLKAVM_REGS_FOR_TY_i32
#define POLKAVM_REGS_FOR_TY_uint8_t POLKAVM_REGS_FOR_TY_i32 #define POLKAVM_REGS_FOR_TY_uint8_t POLKAVM_REGS_FOR_TY_i32
@@ -107,6 +111,26 @@ struct PolkaVM_Metadata {
unsigned char output_regs; unsigned char output_regs;
} __attribute__ ((packed)); } __attribute__ ((packed));
#ifdef _LP64
#define POLKAVM_EXPORT_DEF() \
".quad %[metadata]\n" \
".quad %[function]\n"
#else
#define POLKAVM_EXPORT_DEF() \
".word %[metadata]\n" \
".word %[function]\n"
#endif
#ifdef _LP64
#define POLKAVM_IMPORT_DEF() \
".word 0x0000000b\n" \
".quad %[metadata]\n"
#else
#define POLKAVM_IMPORT_DEF() \
".word 0x0000000b\n" \
".word %[metadata]\n"
#endif
#define POLKAVM_EXPORT(arg_return_ty, fn_name, ...) \ #define POLKAVM_EXPORT(arg_return_ty, fn_name, ...) \
static struct PolkaVM_Metadata POLKAVM_JOIN(fn_name, __EXPORT_METADATA) __attribute__ ((section(".polkavm_metadata"))) = { \ static struct PolkaVM_Metadata POLKAVM_JOIN(fn_name, __EXPORT_METADATA) __attribute__ ((section(".polkavm_metadata"))) = { \
1, 0, sizeof(#fn_name) - 1, #fn_name, POLKAVM_COUNT_REGS(__VA_ARGS__), POLKAVM_COUNT_REGS(arg_return_ty) \ 1, 0, sizeof(#fn_name) - 1, #fn_name, POLKAVM_COUNT_REGS(__VA_ARGS__), POLKAVM_COUNT_REGS(arg_return_ty) \
@@ -115,8 +139,7 @@ static void __attribute__ ((naked, used)) POLKAVM_UNIQUE(polkavm_export_dummy)()
__asm__( \ __asm__( \
".pushsection .polkavm_exports,\"R\",@note\n" \ ".pushsection .polkavm_exports,\"R\",@note\n" \
".byte 1\n" \ ".byte 1\n" \
".word %[metadata]\n" \ POLKAVM_EXPORT_DEF() \
".word %[function]\n" \
".popsection\n" \ ".popsection\n" \
: \ : \
: \ : \
@@ -130,10 +153,9 @@ static void __attribute__ ((naked, used)) POLKAVM_UNIQUE(polkavm_export_dummy)()
static struct PolkaVM_Metadata POLKAVM_JOIN(fn_name, __IMPORT_METADATA) __attribute__ ((section(".polkavm_metadata"))) = { \ static struct PolkaVM_Metadata POLKAVM_JOIN(fn_name, __IMPORT_METADATA) __attribute__ ((section(".polkavm_metadata"))) = { \
1, 0, sizeof(#fn_name) - 1, #fn_name, POLKAVM_COUNT_REGS(__VA_ARGS__), POLKAVM_COUNT_REGS(arg_return_ty) \ 1, 0, sizeof(#fn_name) - 1, #fn_name, POLKAVM_COUNT_REGS(__VA_ARGS__), POLKAVM_COUNT_REGS(arg_return_ty) \
}; \ }; \
static arg_return_ty __attribute__ ((naked, used)) fn_name(POLKAVM_IMPORT_ARGS_IMPL(__VA_ARGS__)) { \ static arg_return_ty __attribute__ ((used, naked)) fn_name(POLKAVM_IMPORT_ARGS_IMPL(__VA_ARGS__)) { \
__asm__( \ __asm__( \
".word 0x0000000b\n" \ POLKAVM_IMPORT_DEF() \
".word %[metadata]\n" \
"ret\n" \ "ret\n" \
: \ : \
: \ : \
+4
View File
@@ -70,10 +70,14 @@ POLKAVM_IMPORT(void, balance, uint32_t)
POLKAVM_IMPORT(void, balance_of, uint32_t, uint32_t) POLKAVM_IMPORT(void, balance_of, uint32_t, uint32_t)
POLKAVM_IMPORT(void, block_hash, uint32_t, uint32_t)
POLKAVM_IMPORT(void, block_number, uint32_t) POLKAVM_IMPORT(void, block_number, uint32_t)
POLKAVM_IMPORT(uint32_t, call, uint32_t) POLKAVM_IMPORT(uint32_t, call, uint32_t)
POLKAVM_IMPORT(uint32_t, delegate_call, uint32_t)
POLKAVM_IMPORT(void, caller, uint32_t) POLKAVM_IMPORT(void, caller, uint32_t)
POLKAVM_IMPORT(void, chain_id, uint32_t) POLKAVM_IMPORT(void, chain_id, uint32_t)
+7 -1
View File
@@ -20,10 +20,14 @@ pub static BALANCE: &str = "balance";
pub static BALANCE_OF: &str = "balance_of"; pub static BALANCE_OF: &str = "balance_of";
pub static BLOCK_HASH: &str = "block_hash";
pub static BLOCK_NUMBER: &str = "block_number"; pub static BLOCK_NUMBER: &str = "block_number";
pub static CALL: &str = "call"; pub static CALL: &str = "call";
pub static DELEGATE_CALL: &str = "delegate_call";
pub static CALLER: &str = "caller"; pub static CALLER: &str = "caller";
pub static CHAIN_ID: &str = "chain_id"; pub static CHAIN_ID: &str = "chain_id";
@@ -62,14 +66,16 @@ pub static VALUE_TRANSFERRED: &str = "value_transferred";
/// 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; 25] = [ pub static IMPORTS: [&str; 27] = [
SBRK, SBRK,
MEMORY_SIZE, MEMORY_SIZE,
ADDRESS, ADDRESS,
BALANCE, BALANCE,
BALANCE_OF, BALANCE_OF,
BLOCK_HASH,
BLOCK_NUMBER, BLOCK_NUMBER,
CALL, CALL,
DELEGATE_CALL,
CALLER, CALLER,
CHAIN_ID, CHAIN_ID,
CODE_SIZE, CODE_SIZE,
+9 -2
View File
@@ -24,7 +24,7 @@ thiserror = { workspace = true }
anyhow = { workspace = true } anyhow = { workspace = true }
which = { workspace = true } which = { workspace = true }
path-slash = { workspace = true } path-slash = { workspace = true }
rayon = { workspace = true } rayon = { workspace = true, optional = true }
serde = { workspace = true } serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
@@ -41,6 +41,13 @@ inkwell = { workspace = true }
revive-common = { workspace = true } revive-common = { workspace = true }
revive-llvm-context = { workspace = true } revive-llvm-context = { workspace = true }
[target.'cfg(target_env = "musl")'.dependencies] [target.'cfg(target_env = "musl")'.dependencies]
mimalloc = { version = "*", default-features = false } mimalloc = { version = "*", default-features = false }
[target.'cfg(target_os = "emscripten")'.dependencies]
libc = { workspace = true }
inkwell = { workspace = true, features = ["target-riscv", "llvm18-0-no-llvm-linking"]}
[features]
parallel = ["rayon"]
default = ["parallel"]
+14 -9
View File
@@ -212,9 +212,10 @@ where
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let full_path = self.full_path().to_owned(); let full_path = self.full_path().to_owned();
if let Some(debug_config) = context.debug_config() { context
debug_config.dump_evmla(full_path.as_str(), self.to_string().as_str())?; .debug_config()
} .dump_evmla(full_path.as_str(), self.to_string().as_str())?;
let deploy_code_blocks = EtherealIR::get_blocks( let deploy_code_blocks = EtherealIR::get_blocks(
context.evmla().version.to_owned(), context.evmla().version.to_owned(),
revive_llvm_context::PolkaVMCodeType::Deploy, revive_llvm_context::PolkaVMCodeType::Deploy,
@@ -228,9 +229,11 @@ where
.ok_or_else(|| anyhow::anyhow!("Runtime code data not found"))? .ok_or_else(|| anyhow::anyhow!("Runtime code data not found"))?
.remove("0") .remove("0")
.expect("Always exists"); .expect("Always exists");
if let Some(debug_config) = context.debug_config() {
debug_config.dump_evmla(full_path.as_str(), data.to_string().as_str())?; context
} .debug_config()
.dump_evmla(full_path.as_str(), data.to_string().as_str())?;
let runtime_code_instructions = match data { let runtime_code_instructions = match data {
Data::Assembly(assembly) => assembly Data::Assembly(assembly) => assembly
.code .code
@@ -253,9 +256,11 @@ where
blocks.extend(runtime_code_blocks); blocks.extend(runtime_code_blocks);
let mut ethereal_ir = let mut ethereal_ir =
EtherealIR::new(context.evmla().version.to_owned(), extra_metadata, blocks)?; EtherealIR::new(context.evmla().version.to_owned(), extra_metadata, blocks)?;
if let Some(debug_config) = context.debug_config() {
debug_config.dump_ethir(full_path.as_str(), ethereal_ir.to_string().as_str())?; context
} .debug_config()
.dump_ethir(full_path.as_str(), ethereal_ir.to_string().as_str())?;
ethereal_ir.declare(context)?; ethereal_ir.declare(context)?;
ethereal_ir.into_llvm(context)?; ethereal_ir.into_llvm(context)?;
@@ -1096,7 +1096,6 @@ where
context, context,
gas, gas,
address, address,
None,
input_offset, input_offset,
input_size, input_size,
output_offset, output_offset,
@@ -1175,7 +1175,7 @@ where
} }
fn into_llvm(self, context: &mut revive_llvm_context::PolkaVMContext<D>) -> anyhow::Result<()> { fn into_llvm(self, context: &mut revive_llvm_context::PolkaVMContext<D>) -> anyhow::Result<()> {
context.set_current_function(self.name.as_str())?; context.set_current_function(self.name.as_str(), None)?;
for (key, blocks) in self.blocks.iter() { for (key, blocks) in self.blocks.iter() {
for (index, block) in blocks.iter().enumerate() { for (index, block) in blocks.iter().enumerate() {
@@ -1297,6 +1297,8 @@ where
} }
} }
context.pop_debug_scope();
Ok(()) Ok(())
} }
} }
+28 -21
View File
@@ -15,15 +15,22 @@ pub use self::build::contract::Contract as ContractBuild;
pub use self::build::Build; pub use self::build::Build;
pub use self::missing_libraries::MissingLibraries; pub use self::missing_libraries::MissingLibraries;
pub use self::process::input::Input as ProcessInput; pub use self::process::input::Input as ProcessInput;
#[cfg(not(target_os = "emscripten"))]
pub use self::process::native_process::NativeProcess;
pub use self::process::output::Output as ProcessOutput; pub use self::process::output::Output as ProcessOutput;
pub use self::process::run as run_process; #[cfg(target_os = "emscripten")]
pub use self::process::EXECUTABLE; pub use self::process::worker_process::WorkerProcess;
pub use self::process::Process;
pub use self::project::contract::Contract as ProjectContract; pub use self::project::contract::Contract as ProjectContract;
pub use self::project::Project; pub use self::project::Project;
pub use self::r#const::*; pub use self::r#const::*;
pub use self::solc::combined_json::contract::Contract as SolcCombinedJsonContract; pub use self::solc::combined_json::contract::Contract as SolcCombinedJsonContract;
pub use self::solc::combined_json::CombinedJson as SolcCombinedJson; pub use self::solc::combined_json::CombinedJson as SolcCombinedJson;
pub use self::solc::pipeline::Pipeline as SolcPipeline; pub use self::solc::pipeline::Pipeline as SolcPipeline;
#[cfg(not(target_os = "emscripten"))]
pub use self::solc::solc_compiler::SolcCompiler;
#[cfg(target_os = "emscripten")]
pub use self::solc::soljson_compiler::SoljsonCompiler;
pub use self::solc::standard_json::input::language::Language as SolcStandardJsonInputLanguage; pub use self::solc::standard_json::input::language::Language as SolcStandardJsonInputLanguage;
pub use self::solc::standard_json::input::settings::metadata::Metadata as SolcStandardJsonInputSettingsMetadata; pub use self::solc::standard_json::input::settings::metadata::Metadata as SolcStandardJsonInputSettingsMetadata;
pub use self::solc::standard_json::input::settings::optimizer::Optimizer as SolcStandardJsonInputSettingsOptimizer; pub use self::solc::standard_json::input::settings::optimizer::Optimizer as SolcStandardJsonInputSettingsOptimizer;
@@ -38,10 +45,10 @@ pub use self::solc::standard_json::output::contract::evm::EVM as SolcStandardJso
pub use self::solc::standard_json::output::contract::Contract as SolcStandardJsonOutputContract; pub use self::solc::standard_json::output::contract::Contract as SolcStandardJsonOutputContract;
pub use self::solc::standard_json::output::Output as SolcStandardJsonOutput; pub use self::solc::standard_json::output::Output as SolcStandardJsonOutput;
pub use self::solc::version::Version as SolcVersion; pub use self::solc::version::Version as SolcVersion;
pub use self::solc::Compiler as SolcCompiler; pub use self::solc::Compiler;
pub use self::version::Version as ResolcVersion; pub use self::version::Version as ResolcVersion;
pub use self::warning::Warning; pub use self::warning::Warning;
#[cfg(not(target_os = "emscripten"))]
pub mod test_utils; pub mod test_utils;
pub mod tests; pub mod tests;
@@ -49,12 +56,12 @@ use std::collections::BTreeSet;
use std::path::PathBuf; use std::path::PathBuf;
/// Runs the Yul mode. /// Runs the Yul mode.
pub fn yul( pub fn yul<T: Compiler>(
input_files: &[PathBuf], input_files: &[PathBuf],
solc: &mut SolcCompiler, solc: &mut T,
optimizer_settings: revive_llvm_context::OptimizerSettings, optimizer_settings: revive_llvm_context::OptimizerSettings,
include_metadata_hash: bool, include_metadata_hash: bool,
debug_config: Option<revive_llvm_context::DebugConfig>, debug_config: revive_llvm_context::DebugConfig,
) -> anyhow::Result<Build> { ) -> anyhow::Result<Build> {
let path = match input_files.len() { let path = match input_files.len() {
1 => input_files.first().expect("Always exists"), 1 => input_files.first().expect("Always exists"),
@@ -65,10 +72,10 @@ pub fn yul(
), ),
}; };
if solc.version()?.default != SolcCompiler::LAST_SUPPORTED_VERSION { if solc.version()?.default != solc::LAST_SUPPORTED_VERSION {
anyhow::bail!( anyhow::bail!(
"The Yul mode is only supported with the most recent version of the Solidity compiler: {}", "The Yul mode is only supported with the most recent version of the Solidity compiler: {}",
SolcCompiler::LAST_SUPPORTED_VERSION, solc::LAST_SUPPORTED_VERSION,
); );
} }
@@ -85,7 +92,7 @@ pub fn llvm_ir(
input_files: &[PathBuf], input_files: &[PathBuf],
optimizer_settings: revive_llvm_context::OptimizerSettings, optimizer_settings: revive_llvm_context::OptimizerSettings,
include_metadata_hash: bool, include_metadata_hash: bool,
debug_config: Option<revive_llvm_context::DebugConfig>, debug_config: revive_llvm_context::DebugConfig,
) -> anyhow::Result<Build> { ) -> anyhow::Result<Build> {
let path = match input_files.len() { let path = match input_files.len() {
1 => input_files.first().expect("Always exists"), 1 => input_files.first().expect("Always exists"),
@@ -105,10 +112,10 @@ pub fn llvm_ir(
/// Runs the standard output mode. /// Runs the standard output mode.
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn standard_output( pub fn standard_output<T: Compiler>(
input_files: &[PathBuf], input_files: &[PathBuf],
libraries: Vec<String>, libraries: Vec<String>,
solc: &mut SolcCompiler, solc: &mut T,
evm_version: Option<revive_common::EVMVersion>, evm_version: Option<revive_common::EVMVersion>,
solc_optimizer_enabled: bool, solc_optimizer_enabled: bool,
optimizer_settings: revive_llvm_context::OptimizerSettings, optimizer_settings: revive_llvm_context::OptimizerSettings,
@@ -119,7 +126,7 @@ pub fn standard_output(
allow_paths: Option<String>, allow_paths: Option<String>,
remappings: Option<BTreeSet<String>>, remappings: Option<BTreeSet<String>>,
suppressed_warnings: Option<Vec<Warning>>, suppressed_warnings: Option<Vec<Warning>>,
debug_config: Option<revive_llvm_context::DebugConfig>, debug_config: revive_llvm_context::DebugConfig,
) -> anyhow::Result<Build> { ) -> anyhow::Result<Build> {
let solc_version = solc.version()?; let solc_version = solc.version()?;
let solc_pipeline = SolcPipeline::new(&solc_version, force_evmla); let solc_pipeline = SolcPipeline::new(&solc_version, force_evmla);
@@ -178,7 +185,7 @@ pub fn standard_output(
libraries, libraries,
solc_pipeline, solc_pipeline,
&solc_version, &solc_version,
debug_config.as_ref(), &debug_config,
)?; )?;
let build = project.compile(optimizer_settings, include_metadata_hash, debug_config)?; let build = project.compile(optimizer_settings, include_metadata_hash, debug_config)?;
@@ -188,14 +195,14 @@ pub fn standard_output(
/// Runs the standard JSON mode. /// Runs the standard JSON mode.
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn standard_json( pub fn standard_json<T: Compiler>(
solc: &mut SolcCompiler, solc: &mut T,
detect_missing_libraries: bool, detect_missing_libraries: bool,
force_evmla: bool, force_evmla: bool,
base_path: Option<String>, base_path: Option<String>,
include_paths: Vec<String>, include_paths: Vec<String>,
allow_paths: Option<String>, allow_paths: Option<String>,
debug_config: Option<revive_llvm_context::DebugConfig>, debug_config: revive_llvm_context::DebugConfig,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let solc_version = solc.version()?; let solc_version = solc.version()?;
let solc_pipeline = SolcPipeline::new(&solc_version, force_evmla); let solc_pipeline = SolcPipeline::new(&solc_version, force_evmla);
@@ -240,7 +247,7 @@ pub fn standard_json(
libraries, libraries,
solc_pipeline, solc_pipeline,
&solc_version, &solc_version,
debug_config.as_ref(), &debug_config,
)?; )?;
if detect_missing_libraries { if detect_missing_libraries {
@@ -256,11 +263,11 @@ pub fn standard_json(
/// Runs the combined JSON mode. /// Runs the combined JSON mode.
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn combined_json( pub fn combined_json<T: Compiler>(
format: String, format: String,
input_files: &[PathBuf], input_files: &[PathBuf],
libraries: Vec<String>, libraries: Vec<String>,
solc: &mut SolcCompiler, solc: &mut T,
evm_version: Option<revive_common::EVMVersion>, evm_version: Option<revive_common::EVMVersion>,
solc_optimizer_enabled: bool, solc_optimizer_enabled: bool,
optimizer_settings: revive_llvm_context::OptimizerSettings, optimizer_settings: revive_llvm_context::OptimizerSettings,
@@ -271,7 +278,7 @@ pub fn combined_json(
allow_paths: Option<String>, allow_paths: Option<String>,
remappings: Option<BTreeSet<String>>, remappings: Option<BTreeSet<String>>,
suppressed_warnings: Option<Vec<Warning>>, suppressed_warnings: Option<Vec<Warning>>,
debug_config: Option<revive_llvm_context::DebugConfig>, debug_config: revive_llvm_context::DebugConfig,
output_directory: Option<PathBuf>, output_directory: Option<PathBuf>,
overwrite: bool, overwrite: bool,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
+2 -2
View File
@@ -19,7 +19,7 @@ pub struct Input {
/// The optimizer settings. /// The optimizer settings.
pub optimizer_settings: revive_llvm_context::OptimizerSettings, pub optimizer_settings: revive_llvm_context::OptimizerSettings,
/// The debug output config. /// The debug output config.
pub debug_config: Option<revive_llvm_context::DebugConfig>, pub debug_config: revive_llvm_context::DebugConfig,
} }
impl Input { impl Input {
@@ -29,7 +29,7 @@ impl Input {
project: Project, project: Project,
include_metadata_hash: bool, include_metadata_hash: bool,
optimizer_settings: revive_llvm_context::OptimizerSettings, optimizer_settings: revive_llvm_context::OptimizerSettings,
debug_config: Option<revive_llvm_context::DebugConfig>, debug_config: revive_llvm_context::DebugConfig,
) -> Self { ) -> Self {
Self { Self {
contract, contract,
+51 -108
View File
@@ -1,125 +1,68 @@
//! Process for compiling a single compilation unit. //! Process for compiling a single compilation unit.
pub mod input; pub mod input;
#[cfg(not(target_os = "emscripten"))]
pub mod native_process;
pub mod output; pub mod output;
#[cfg(target_os = "emscripten")]
pub mod worker_process;
use std::io::Read; use std::io::{Read, Write};
use std::io::Write;
use std::path::PathBuf;
use std::process::Command;
use once_cell::sync::OnceCell;
use self::input::Input; use self::input::Input;
use self::output::Output; use self::output::Output;
/// The overriden executable name used when the compiler is run as a library. pub trait Process {
pub static EXECUTABLE: OnceCell<PathBuf> = OnceCell::new(); /// Read input from `stdin`, compile a contract, and write the output to `stdout`.
fn run(input_file: Option<&mut std::fs::File>) -> anyhow::Result<()> {
let mut stdin = std::io::stdin();
let mut stdout = std::io::stdout();
let mut stderr = std::io::stderr();
/// Read input from `stdin`, compile a contract, and write the output to `stdout`. let mut buffer = Vec::with_capacity(16384);
pub fn run(input_file: Option<&mut std::fs::File>) -> anyhow::Result<()> { match input_file {
let mut stdin = std::io::stdin(); Some(ins) => {
let mut stdout = std::io::stdout(); if let Err(error) = ins.read_to_end(&mut buffer) {
let mut stderr = std::io::stderr(); anyhow::bail!("Failed to read recursive process input file: {:?}", error);
}
let mut buffer = Vec::with_capacity(16384); }
match input_file { None => {
Some(ins) => { if let Err(error) = stdin.read_to_end(&mut buffer) {
if let Err(error) = ins.read_to_end(&mut buffer) { anyhow::bail!(
anyhow::bail!("Failed to read recursive process input file: {:?}", error); "Failed to read recursive process input from stdin: {:?}",
error
)
}
} }
} }
None => {
if let Err(error) = stdin.read_to_end(&mut buffer) {
anyhow::bail!(
"Failed to read recursive process input from stdin: {:?}",
error
)
}
}
}
let input: Input = revive_common::deserialize_from_slice(buffer.as_slice())?; let input: Input = revive_common::deserialize_from_slice(buffer.as_slice())?;
let result = input.contract.compile( let result = input.contract.compile(
input.project, input.project,
input.optimizer_settings, input.optimizer_settings,
input.include_metadata_hash, input.include_metadata_hash,
input.debug_config, input.debug_config,
);
match result {
Ok(build) => {
let output = Output::new(build);
let json = serde_json::to_vec(&output).expect("Always valid");
stdout
.write_all(json.as_slice())
.expect("Stdout writing error");
Ok(())
}
Err(error) => {
let message = error.to_string();
stderr
.write_all(message.as_bytes())
.expect("Stderr writing error");
Err(error)
}
}
}
/// Runs this process recursively to compile a single contract.
pub fn call(input: Input) -> anyhow::Result<Output> {
let input_json = serde_json::to_vec(&input).expect("Always valid");
let executable = match EXECUTABLE.get() {
Some(executable) => executable.to_owned(),
None => std::env::current_exe()?,
};
let mut command = Command::new(executable.as_path());
command.stdin(std::process::Stdio::piped());
command.stdout(std::process::Stdio::piped());
command.stderr(std::process::Stdio::piped());
command.arg("--recursive-process");
let process = command.spawn().map_err(|error| {
anyhow::anyhow!("{:?} subprocess spawning error: {:?}", executable, error)
})?;
#[cfg(debug_assertions)]
if let Some(dbg_config) = &input.debug_config {
dbg_config
.dump_stage_output(&input.contract.path, Some("stage"), &input_json)
.map_err(|error| {
anyhow::anyhow!(
"{:?} failed to log the recursive process output: {:?}",
executable,
error,
)
})?;
}
process
.stdin
.as_ref()
.ok_or_else(|| anyhow::anyhow!("{:?} stdin getting error", executable))?
.write_all(input_json.as_slice())
.map_err(|error| anyhow::anyhow!("{:?} stdin writing error: {:?}", executable, error))?;
let output = process.wait_with_output().map_err(|error| {
anyhow::anyhow!("{:?} subprocess output error: {:?}", executable, error)
})?;
if !output.status.success() {
anyhow::bail!(
"{}",
String::from_utf8_lossy(output.stderr.as_slice()).to_string(),
); );
match result {
Ok(build) => {
let output = Output::new(build);
let json = serde_json::to_vec(&output).expect("Always valid");
stdout
.write_all(json.as_slice())
.expect("Stdout writing error");
Ok(())
}
Err(error) => {
let message = error.to_string();
stderr
.write_all(message.as_bytes())
.expect("Stderr writing error");
Err(error)
}
}
} }
let output: Output = /// Runs this process recursively to compile a single contract.
revive_common::deserialize_from_slice(output.stdout.as_slice()).map_err(|error| { fn call(input: Input) -> anyhow::Result<Output>;
anyhow::anyhow!(
"{:?} subprocess output parsing error: {}",
executable,
error,
)
})?;
Ok(output)
} }
@@ -0,0 +1,76 @@
//! Process for compiling a single compilation unit.
use std::io::Write;
use std::path::PathBuf;
use std::process::Command;
use once_cell::sync::OnceCell;
use super::Input;
use super::Output;
use super::Process;
/// The overriden executable name used when the compiler is run as a library.
pub static EXECUTABLE: OnceCell<PathBuf> = OnceCell::new();
pub struct NativeProcess;
impl Process for NativeProcess {
fn call(input: Input) -> anyhow::Result<Output> {
let input_json = serde_json::to_vec(&input).expect("Always valid");
let executable = match EXECUTABLE.get() {
Some(executable) => executable.to_owned(),
None => std::env::current_exe()?,
};
let mut command = Command::new(executable.as_path());
command.stdin(std::process::Stdio::piped());
command.stdout(std::process::Stdio::piped());
command.stderr(std::process::Stdio::piped());
command.arg("--recursive-process");
let process = command.spawn().map_err(|error| {
anyhow::anyhow!("{:?} subprocess spawning error: {:?}", executable, error)
})?;
#[cfg(debug_assertions)]
input
.debug_config
.dump_stage_output(&input.contract.path, Some("stage"), &input_json)
.map_err(|error| {
anyhow::anyhow!(
"{:?} failed to log the recursive process output: {:?}",
executable,
error,
)
})?;
process
.stdin
.as_ref()
.ok_or_else(|| anyhow::anyhow!("{:?} stdin getting error", executable))?
.write_all(input_json.as_slice())
.map_err(|error| {
anyhow::anyhow!("{:?} stdin writing error: {:?}", executable, error)
})?;
let output = process.wait_with_output().map_err(|error| {
anyhow::anyhow!("{:?} subprocess output error: {:?}", executable, error)
})?;
if !output.status.success() {
anyhow::bail!(
"{}",
String::from_utf8_lossy(output.stderr.as_slice()).to_string(),
);
}
let output: Output = revive_common::deserialize_from_slice(output.stdout.as_slice())
.map_err(|error| {
anyhow::anyhow!(
"{:?} subprocess output parsing error: {}",
executable,
error,
)
})?;
Ok(output)
}
}
@@ -0,0 +1,69 @@
//! Process for compiling a single compilation unit using Web Workers.
use std::ffi::{c_char, c_void, CStr, CString};
use super::Input;
use super::Output;
use super::Process;
use anyhow::Context;
use serde::Deserialize;
#[derive(Deserialize)]
struct Error {
message: String,
}
#[derive(Deserialize)]
struct Success {
data: String,
}
#[derive(Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
enum Response {
Success(Success),
Error(Error),
}
pub struct WorkerProcess;
impl Process for WorkerProcess {
fn call(input: Input) -> anyhow::Result<Output> {
let input_json = serde_json::to_vec(&input).expect("Always valid");
let input_str = String::from_utf8(input_json).expect("Input shall be valid");
// Prepare the input string for the Emscripten function
let input_cstring = CString::new(input_str).expect("CString allocation failed");
// Call the Emscripten function
let output_ptr =
unsafe { resolc_compile(input_cstring.as_ptr(), input_cstring.as_bytes().len()) };
// Convert the output pointer back to a Rust string
let output_str = unsafe {
CStr::from_ptr(output_ptr)
.to_str()
.with_context(|| "Failed to convert C string to Rust string")
.map(str::to_owned)
};
unsafe { libc::free(output_ptr as *mut c_void) };
let output_str = output_str?;
let response: Response = serde_json::from_str(&output_str)
.map_err(|error| anyhow::anyhow!("Worker output parsing error: {}", error,))?;
match response {
Response::Success(out) => {
let output: Output = revive_common::deserialize_from_slice(out.data.as_bytes())
.map_err(|error| {
anyhow::anyhow!("resolc.js subprocess output parsing error: {}", error,)
})?;
Ok(output)
}
Response::Error(err) => anyhow::bail!("Worker error: {}", err.message,),
}
}
}
extern "C" {
fn resolc_compile(input_ptr: *const c_char, input_len: usize) -> *const c_char;
}
+7 -1
View File
@@ -79,7 +79,7 @@ impl Contract {
project: Project, project: Project,
optimizer_settings: revive_llvm_context::OptimizerSettings, optimizer_settings: revive_llvm_context::OptimizerSettings,
include_metadata_hash: bool, include_metadata_hash: bool,
debug_config: Option<revive_llvm_context::DebugConfig>, debug_config: revive_llvm_context::DebugConfig,
) -> anyhow::Result<ContractBuild> { ) -> anyhow::Result<ContractBuild> {
let llvm = inkwell::context::Context::create(); let llvm = inkwell::context::Context::create();
let optimizer = revive_llvm_context::Optimizer::new(optimizer_settings); let optimizer = revive_llvm_context::Optimizer::new(optimizer_settings);
@@ -104,6 +104,7 @@ impl Contract {
let module = match self.ir { let module = match self.ir {
IR::LLVMIR(ref llvm_ir) => { IR::LLVMIR(ref llvm_ir) => {
// Create the output module
let memory_buffer = let memory_buffer =
inkwell::memory_buffer::MemoryBuffer::create_from_memory_range_copy( inkwell::memory_buffer::MemoryBuffer::create_from_memory_range_copy(
llvm_ir.source.as_bytes(), llvm_ir.source.as_bytes(),
@@ -114,6 +115,7 @@ impl Contract {
} }
_ => llvm.create_module(self.path.as_str()), _ => llvm.create_module(self.path.as_str()),
}; };
let mut context = revive_llvm_context::PolkaVMContext::new( let mut context = revive_llvm_context::PolkaVMContext::new(
&llvm, &llvm,
module, module,
@@ -151,6 +153,10 @@ impl Contract {
) )
})?; })?;
if let Some(debug_info) = context.debug_info() {
debug_info.finalize_module()
}
let build = context.build(self.path.as_str(), metadata_hash)?; let build = context.build(self.path.as_str(), metadata_hash)?;
Ok(ContractBuild::new( Ok(ContractBuild::new(
+29 -16
View File
@@ -7,8 +7,8 @@ use std::collections::HashMap;
use std::collections::HashSet; use std::collections::HashSet;
use std::path::Path; use std::path::Path;
use rayon::iter::IntoParallelIterator; #[cfg(feature = "parallel")]
use rayon::iter::ParallelIterator; use rayon::iter::{IntoParallelIterator, ParallelIterator};
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
use sha3::Digest; use sha3::Digest;
@@ -17,9 +17,10 @@ use crate::build::contract::Contract as ContractBuild;
use crate::build::Build; use crate::build::Build;
use crate::missing_libraries::MissingLibraries; use crate::missing_libraries::MissingLibraries;
use crate::process::input::Input as ProcessInput; use crate::process::input::Input as ProcessInput;
use crate::process::Process;
use crate::project::contract::ir::IR; use crate::project::contract::ir::IR;
use crate::solc::version::Version as SolcVersion; use crate::solc::version::Version as SolcVersion;
use crate::solc::Compiler as SolcCompiler; use crate::solc::Compiler;
use crate::yul::lexer::Lexer; use crate::yul::lexer::Lexer;
use crate::yul::parser::statement::object::Object; use crate::yul::parser::statement::object::Object;
@@ -63,21 +64,33 @@ impl Project {
self, self,
optimizer_settings: revive_llvm_context::OptimizerSettings, optimizer_settings: revive_llvm_context::OptimizerSettings,
include_metadata_hash: bool, include_metadata_hash: bool,
debug_config: Option<revive_llvm_context::DebugConfig>, debug_config: revive_llvm_context::DebugConfig,
) -> anyhow::Result<Build> { ) -> anyhow::Result<Build> {
let project = self.clone(); let project = self.clone();
let results: BTreeMap<String, anyhow::Result<ContractBuild>> = self #[cfg(feature = "parallel")]
.contracts let iter = self.contracts.into_par_iter();
.into_par_iter() #[cfg(not(feature = "parallel"))]
let iter = self.contracts.into_iter();
let results: BTreeMap<String, anyhow::Result<ContractBuild>> = iter
.map(|(full_path, contract)| { .map(|(full_path, contract)| {
let process_output = crate::process::call(ProcessInput::new( let process_input = ProcessInput::new(
contract, contract,
project.clone(), project.clone(),
include_metadata_hash, include_metadata_hash,
optimizer_settings.clone(), optimizer_settings.clone(),
debug_config.clone(), debug_config.clone(),
)); );
let process_output = {
#[cfg(target_os = "emscripten")]
{
crate::WorkerProcess::call(process_input)
}
#[cfg(not(target_os = "emscripten"))]
{
crate::NativeProcess::call(process_input)
}
};
(full_path, process_output.map(|output| output.build)) (full_path, process_output.map(|output| output.build))
}) })
.collect(); .collect();
@@ -155,9 +168,9 @@ impl Project {
} }
/// Parses the Yul source code file and returns the source data. /// Parses the Yul source code file and returns the source data.
pub fn try_from_yul_path( pub fn try_from_yul_path<T: Compiler>(
path: &Path, path: &Path,
solc_validator: Option<&SolcCompiler>, solc_validator: Option<&T>,
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self> {
let source_code = std::fs::read_to_string(path) let source_code = std::fs::read_to_string(path)
.map_err(|error| anyhow::anyhow!("Yul file {:?} reading error: {}", path, error))?; .map_err(|error| anyhow::anyhow!("Yul file {:?} reading error: {}", path, error))?;
@@ -166,16 +179,16 @@ impl Project {
/// Parses the test Yul source code string and returns the source data. /// Parses the test Yul source code string and returns the source data.
/// Only for integration testing purposes. /// Only for integration testing purposes.
pub fn try_from_yul_string( pub fn try_from_yul_string<T: Compiler>(
path: &Path, path: &Path,
source_code: &str, source_code: &str,
solc_validator: Option<&SolcCompiler>, solc_validator: Option<&T>,
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self> {
if let Some(solc) = solc_validator { if let Some(solc) = solc_validator {
solc.validate_yul(path)?; solc.validate_yul(path)?;
} }
let source_version = SolcVersion::new_simple(SolcCompiler::LAST_SUPPORTED_VERSION); let source_version = SolcVersion::new_simple(crate::solc::LAST_SUPPORTED_VERSION);
let path = path.to_string_lossy().to_string(); let path = path.to_string_lossy().to_string();
let source_hash = sha3::Keccak256::digest(source_code.as_bytes()).into(); let source_hash = sha3::Keccak256::digest(source_code.as_bytes()).into();
@@ -238,7 +251,7 @@ impl revive_llvm_context::PolkaVMDependency for Project {
identifier: &str, identifier: &str,
optimizer_settings: revive_llvm_context::OptimizerSettings, optimizer_settings: revive_llvm_context::OptimizerSettings,
include_metadata_hash: bool, include_metadata_hash: bool,
debug_config: Option<revive_llvm_context::DebugConfig>, debug_config: revive_llvm_context::DebugConfig,
) -> anyhow::Result<String> { ) -> anyhow::Result<String> {
let contract_path = project.resolve_path(identifier)?; let contract_path = project.resolve_path(identifier)?;
let contract = project let contract = project
+5
View File
@@ -138,6 +138,11 @@ pub struct Arguments {
#[structopt(long = "suppress-warnings")] #[structopt(long = "suppress-warnings")]
pub suppress_warnings: Option<Vec<String>>, pub suppress_warnings: Option<Vec<String>>,
/// Generate source based debug information in the output code file. This only has an effect
/// with the LLVM-IR code generator and is ignored otherwise.
#[structopt(short = 'g')]
pub emit_source_debug_info: bool,
/// Dump all IRs to files in the specified directory. /// Dump all IRs to files in the specified directory.
/// Only for testing and debugging. /// Only for testing and debugging.
#[structopt(long = "debug-output-dir")] #[structopt(long = "debug-output-dir")]
+38 -12
View File
@@ -4,8 +4,11 @@ pub mod arguments;
use std::str::FromStr; use std::str::FromStr;
use revive_solidity::Process;
use self::arguments::Arguments; use self::arguments::Arguments;
#[cfg(feature = "parallel")]
/// The rayon worker stack size. /// The rayon worker stack size.
const RAYON_WORKER_STACK_SIZE: usize = 16 * 1024 * 1024; const RAYON_WORKER_STACK_SIZE: usize = 16 * 1024 * 1024;
@@ -46,6 +49,7 @@ fn main_inner() -> anyhow::Result<()> {
return Ok(()); return Ok(());
} }
#[cfg(feature = "parallel")]
rayon::ThreadPoolBuilder::new() rayon::ThreadPoolBuilder::new()
.stack_size(RAYON_WORKER_STACK_SIZE) .stack_size(RAYON_WORKER_STACK_SIZE)
.build_global() .build_global()
@@ -57,20 +61,34 @@ fn main_inner() -> anyhow::Result<()> {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
if let Some(fname) = arguments.recursive_process_input { if let Some(fname) = arguments.recursive_process_input {
let mut infile = std::fs::File::open(fname)?; let mut infile = std::fs::File::open(fname)?;
return revive_solidity::run_process(Some(&mut infile)); #[cfg(target_os = "emscripten")]
{
return revive_solidity::WorkerProcess::run(Some(&mut infile));
}
#[cfg(not(target_os = "emscripten"))]
{
return revive_solidity::NativeProcess::run(Some(&mut infile));
}
}
#[cfg(target_os = "emscripten")]
{
return revive_solidity::WorkerProcess::run(None);
}
#[cfg(not(target_os = "emscripten"))]
{
return revive_solidity::NativeProcess::run(None);
} }
return revive_solidity::run_process(None);
} }
let debug_config = match arguments.debug_output_directory { let debug_config = match arguments.debug_output_directory {
Some(ref debug_output_directory) => { Some(ref debug_output_directory) => {
std::fs::create_dir_all(debug_output_directory.as_path())?; std::fs::create_dir_all(debug_output_directory.as_path())?;
Some(revive_llvm_context::DebugConfig::new( revive_llvm_context::DebugConfig::new(
debug_output_directory.to_owned(), Some(debug_output_directory.to_owned()),
)) arguments.emit_source_debug_info,
)
} }
None => None, None => revive_llvm_context::DebugConfig::new(None, arguments.emit_source_debug_info),
}; };
let (input_files, remappings) = arguments.split_input_files_and_remappings()?; let (input_files, remappings) = arguments.split_input_files_and_remappings()?;
@@ -82,11 +100,19 @@ fn main_inner() -> anyhow::Result<()> {
None => None, None => None,
}; };
let mut solc = revive_solidity::SolcCompiler::new( let mut solc = {
arguments #[cfg(target_os = "emscripten")]
.solc {
.unwrap_or_else(|| revive_solidity::SolcCompiler::DEFAULT_EXECUTABLE_NAME.to_owned()), revive_solidity::SoljsonCompiler { version: None }
)?; }
#[cfg(not(target_os = "emscripten"))]
{
revive_solidity::SolcCompiler::new(arguments.solc.unwrap_or_else(|| {
revive_solidity::SolcCompiler::DEFAULT_EXECUTABLE_NAME.to_owned()
}))?
}
};
let evm_version = match arguments.evm_version { let evm_version = match arguments.evm_version {
Some(evm_version) => Some(revive_common::EVMVersion::try_from(evm_version.as_str())?), Some(evm_version) => Some(revive_common::EVMVersion::try_from(evm_version.as_str())?),
+24 -266
View File
@@ -2,10 +2,13 @@
pub mod combined_json; pub mod combined_json;
pub mod pipeline; pub mod pipeline;
#[cfg(not(target_os = "emscripten"))]
pub mod solc_compiler;
#[cfg(target_os = "emscripten")]
pub mod soljson_compiler;
pub mod standard_json; pub mod standard_json;
pub mod version; pub mod version;
use std::io::Write;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
@@ -15,285 +18,40 @@ use self::standard_json::input::Input as StandardJsonInput;
use self::standard_json::output::Output as StandardJsonOutput; use self::standard_json::output::Output as StandardJsonOutput;
use self::version::Version; use self::version::Version;
/// The first version of `solc` with the support of standard JSON interface.
pub const FIRST_SUPPORTED_VERSION: semver::Version = semver::Version::new(0, 4, 12);
/// The first version of `solc`, where Yul codegen is considered robust enough.
pub const FIRST_YUL_VERSION: semver::Version = semver::Version::new(0, 8, 0);
/// The first version of `solc`, where `--via-ir` codegen mode is supported.
pub const FIRST_VIA_IR_VERSION: semver::Version = semver::Version::new(0, 8, 13);
/// The last supported version of `solc`.
pub const LAST_SUPPORTED_VERSION: semver::Version = semver::Version::new(0, 8, 28);
/// The Solidity compiler. /// The Solidity compiler.
pub struct Compiler { pub trait Compiler {
/// The binary executable name.
pub executable: String,
/// The lazily-initialized compiler version.
pub version: Option<Version>,
}
impl Compiler {
/// The default executable name.
pub const DEFAULT_EXECUTABLE_NAME: &'static str = "solc";
/// The first version of `solc` with the support of standard JSON interface.
pub const FIRST_SUPPORTED_VERSION: semver::Version = semver::Version::new(0, 4, 12);
/// The first version of `solc`, where Yul codegen is considered robust enough.
pub const FIRST_YUL_VERSION: semver::Version = semver::Version::new(0, 8, 0);
/// The first version of `solc`, where `--via-ir` codegen mode is supported.
pub const FIRST_VIA_IR_VERSION: semver::Version = semver::Version::new(0, 8, 13);
/// The last supported version of `solc`.
pub const LAST_SUPPORTED_VERSION: semver::Version = semver::Version::new(0, 8, 28);
/// A shortcut constructor.
/// Different tools may use different `executable` names. For example, the integration tester
/// uses `solc-<version>` format.
pub fn new(executable: String) -> anyhow::Result<Self> {
if let Err(error) = which::which(executable.as_str()) {
anyhow::bail!(
"The `{executable}` executable not found in ${{PATH}}: {}",
error
);
}
Ok(Self {
executable,
version: None,
})
}
/// Compiles the Solidity `--standard-json` input into Yul IR. /// Compiles the Solidity `--standard-json` input into Yul IR.
pub fn standard_json( fn standard_json(
&mut self, &mut self,
mut input: StandardJsonInput, input: StandardJsonInput,
pipeline: Pipeline, pipeline: Pipeline,
base_path: Option<String>, base_path: Option<String>,
include_paths: Vec<String>, include_paths: Vec<String>,
allow_paths: Option<String>, allow_paths: Option<String>,
) -> anyhow::Result<StandardJsonOutput> { ) -> anyhow::Result<StandardJsonOutput>;
let version = self.version()?;
let mut command = std::process::Command::new(self.executable.as_str());
command.stdin(std::process::Stdio::piped());
command.stdout(std::process::Stdio::piped());
command.arg("--standard-json");
if let Some(base_path) = base_path {
command.arg("--base-path");
command.arg(base_path);
}
for include_path in include_paths.into_iter() {
command.arg("--include-path");
command.arg(include_path);
}
if let Some(allow_paths) = allow_paths {
command.arg("--allow-paths");
command.arg(allow_paths);
}
input.normalize(&version.default);
let suppressed_warnings = input.suppressed_warnings.take().unwrap_or_default();
let input_json = serde_json::to_vec(&input).expect("Always valid");
let process = command.spawn().map_err(|error| {
anyhow::anyhow!("{} subprocess spawning error: {:?}", self.executable, error)
})?;
process
.stdin
.as_ref()
.ok_or_else(|| anyhow::anyhow!("{} stdin getting error", self.executable))?
.write_all(input_json.as_slice())
.map_err(|error| {
anyhow::anyhow!("{} stdin writing error: {:?}", self.executable, error)
})?;
let output = process.wait_with_output().map_err(|error| {
anyhow::anyhow!("{} subprocess output error: {:?}", self.executable, error)
})?;
if !output.status.success() {
anyhow::bail!(
"{} error: {}",
self.executable,
String::from_utf8_lossy(output.stderr.as_slice()).to_string()
);
}
let mut output: StandardJsonOutput =
revive_common::deserialize_from_slice(output.stdout.as_slice()).map_err(|error| {
anyhow::anyhow!(
"{} subprocess output parsing error: {}\n{}",
self.executable,
error,
revive_common::deserialize_from_slice::<serde_json::Value>(
output.stdout.as_slice()
)
.map(|json| serde_json::to_string_pretty(&json).expect("Always valid"))
.unwrap_or_else(
|_| String::from_utf8_lossy(output.stdout.as_slice()).to_string()
),
)
})?;
output.preprocess_ast(&version, pipeline, suppressed_warnings.as_slice())?;
Ok(output)
}
/// The `solc --combined-json abi,hashes...` mirror. /// The `solc --combined-json abi,hashes...` mirror.
pub fn combined_json( fn combined_json(
&self, &self,
paths: &[PathBuf], paths: &[PathBuf],
combined_json_argument: &str, combined_json_argument: &str,
) -> anyhow::Result<CombinedJson> { ) -> anyhow::Result<CombinedJson>;
let mut command = std::process::Command::new(self.executable.as_str());
command.args(paths);
let mut combined_json_flags = Vec::new();
let mut combined_json_fake_flag_pushed = false;
let mut filtered_flags = Vec::with_capacity(3);
for flag in combined_json_argument.split(',') {
match flag {
flag @ "asm" | flag @ "bin" | flag @ "bin-runtime" => filtered_flags.push(flag),
flag => combined_json_flags.push(flag),
}
}
if combined_json_flags.is_empty() {
combined_json_flags.push("ast");
combined_json_fake_flag_pushed = true;
}
command.arg("--combined-json");
command.arg(combined_json_flags.join(","));
let output = command.output().map_err(|error| {
anyhow::anyhow!("{} subprocess error: {:?}", self.executable, error)
})?;
if !output.status.success() {
println!("{}", String::from_utf8_lossy(output.stdout.as_slice()));
println!("{}", String::from_utf8_lossy(output.stderr.as_slice()));
anyhow::bail!(
"{} error: {}",
self.executable,
String::from_utf8_lossy(output.stdout.as_slice()).to_string()
);
}
let mut combined_json: CombinedJson =
revive_common::deserialize_from_slice(output.stdout.as_slice()).map_err(|error| {
anyhow::anyhow!(
"{} subprocess output parsing error: {}\n{}",
self.executable,
error,
revive_common::deserialize_from_slice::<serde_json::Value>(
output.stdout.as_slice()
)
.map(|json| serde_json::to_string_pretty(&json).expect("Always valid"))
.unwrap_or_else(
|_| String::from_utf8_lossy(output.stdout.as_slice()).to_string()
),
)
})?;
for filtered_flag in filtered_flags.into_iter() {
for (_path, contract) in combined_json.contracts.iter_mut() {
match filtered_flag {
"asm" => contract.asm = Some(serde_json::Value::Null),
"bin" => contract.bin = Some("".to_owned()),
"bin-runtime" => contract.bin_runtime = Some("".to_owned()),
_ => continue,
}
}
}
if combined_json_fake_flag_pushed {
combined_json.source_list = None;
combined_json.sources = None;
}
combined_json.remove_evm();
Ok(combined_json)
}
/// The `solc` Yul validator. /// The `solc` Yul validator.
pub fn validate_yul(&self, path: &Path) -> anyhow::Result<()> { fn validate_yul(&self, path: &Path) -> anyhow::Result<()>;
let mut command = std::process::Command::new(self.executable.as_str());
command.arg("--strict-assembly");
command.arg(path);
let output = command.output().map_err(|error| {
anyhow::anyhow!("{} subprocess error: {:?}", self.executable, error)
})?;
if !output.status.success() {
anyhow::bail!(
"{} error: {}",
self.executable,
String::from_utf8_lossy(output.stderr.as_slice()).to_string()
);
}
Ok(())
}
/// The `solc --version` mini-parser. /// The `solc --version` mini-parser.
pub fn version(&mut self) -> anyhow::Result<Version> { fn version(&mut self) -> anyhow::Result<Version>;
if let Some(version) = self.version.as_ref() {
return Ok(version.to_owned());
}
let mut command = std::process::Command::new(self.executable.as_str());
command.arg("--version");
let output = command.output().map_err(|error| {
anyhow::anyhow!("{} subprocess error: {:?}", self.executable, error)
})?;
if !output.status.success() {
anyhow::bail!(
"{} error: {}",
self.executable,
String::from_utf8_lossy(output.stderr.as_slice()).to_string()
);
}
let stdout = String::from_utf8_lossy(output.stdout.as_slice());
let long = stdout
.lines()
.nth(1)
.ok_or_else(|| {
anyhow::anyhow!("{} version parsing: not enough lines", self.executable)
})?
.split(' ')
.nth(1)
.ok_or_else(|| {
anyhow::anyhow!(
"{} version parsing: not enough words in the 2nd line",
self.executable
)
})?
.to_owned();
let default: semver::Version = long
.split('+')
.next()
.ok_or_else(|| {
anyhow::anyhow!("{} version parsing: metadata dropping", self.executable)
})?
.parse()
.map_err(|error| anyhow::anyhow!("{} version parsing: {}", self.executable, error))?;
let l2_revision: Option<semver::Version> = stdout
.lines()
.nth(2)
.and_then(|line| line.split(' ').nth(1))
.and_then(|line| line.split('-').nth(1))
.and_then(|version| version.parse().ok());
let version = Version::new(long, default, l2_revision);
if version.default < Self::FIRST_SUPPORTED_VERSION {
anyhow::bail!(
"`solc` versions <{} are not supported, found {}",
Self::FIRST_SUPPORTED_VERSION,
version.default
);
}
if version.default > Self::LAST_SUPPORTED_VERSION {
anyhow::bail!(
"`solc` versions >{} are not supported, found {}",
Self::LAST_SUPPORTED_VERSION,
version.default
);
}
self.version = Some(version.clone());
Ok(version)
}
} }
+1 -2
View File
@@ -3,7 +3,6 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::solc::version::Version as SolcVersion; use crate::solc::version::Version as SolcVersion;
use crate::solc::Compiler as SolcCompiler;
/// The Solidity compiler pipeline type. /// The Solidity compiler pipeline type.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
@@ -19,7 +18,7 @@ pub enum Pipeline {
impl Pipeline { impl Pipeline {
/// We always use EVMLA for Solidity <=0.7, or if the user does not want to compile via Yul. /// We always use EVMLA for Solidity <=0.7, or if the user does not want to compile via Yul.
pub fn new(solc_version: &SolcVersion, force_evmla: bool) -> Self { pub fn new(solc_version: &SolcVersion, force_evmla: bool) -> Self {
if solc_version.default < SolcCompiler::FIRST_YUL_VERSION || force_evmla { if solc_version.default < crate::solc::FIRST_YUL_VERSION || force_evmla {
Self::EVMLA Self::EVMLA
} else { } else {
Self::Yul Self::Yul
+286
View File
@@ -0,0 +1,286 @@
//! The Solidity compiler.
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use crate::solc::combined_json::CombinedJson;
use crate::solc::pipeline::Pipeline;
use crate::solc::standard_json::input::Input as StandardJsonInput;
use crate::solc::standard_json::output::Output as StandardJsonOutput;
use crate::solc::version::Version;
use super::Compiler;
/// The Solidity compiler.
pub struct SolcCompiler {
/// The binary executable name.
pub executable: String,
/// The lazily-initialized compiler version.
pub version: Option<Version>,
}
impl SolcCompiler {
/// The default executable name.
pub const DEFAULT_EXECUTABLE_NAME: &'static str = "solc";
/// A shortcut constructor.
/// Different tools may use different `executable` names. For example, the integration tester
/// uses `solc-<version>` format.
pub fn new(executable: String) -> anyhow::Result<Self> {
if let Err(error) = which::which(executable.as_str()) {
anyhow::bail!(
"The `{executable}` executable not found in ${{PATH}}: {}",
error
);
}
Ok(Self {
executable,
version: None,
})
}
}
impl Compiler for SolcCompiler {
/// Compiles the Solidity `--standard-json` input into Yul IR.
fn standard_json(
&mut self,
mut input: StandardJsonInput,
pipeline: Pipeline,
base_path: Option<String>,
include_paths: Vec<String>,
allow_paths: Option<String>,
) -> anyhow::Result<StandardJsonOutput> {
let version = self.version()?;
let mut command = std::process::Command::new(self.executable.as_str());
command.stdin(std::process::Stdio::piped());
command.stdout(std::process::Stdio::piped());
command.arg("--standard-json");
if let Some(base_path) = base_path {
command.arg("--base-path");
command.arg(base_path);
}
for include_path in include_paths.into_iter() {
command.arg("--include-path");
command.arg(include_path);
}
if let Some(allow_paths) = allow_paths {
command.arg("--allow-paths");
command.arg(allow_paths);
}
input.normalize(&version.default);
let suppressed_warnings = input.suppressed_warnings.take().unwrap_or_default();
let input_json = serde_json::to_vec(&input).expect("Always valid");
let process = command.spawn().map_err(|error| {
anyhow::anyhow!("{} subprocess spawning error: {:?}", self.executable, error)
})?;
process
.stdin
.as_ref()
.ok_or_else(|| anyhow::anyhow!("{} stdin getting error", self.executable))?
.write_all(input_json.as_slice())
.map_err(|error| {
anyhow::anyhow!("{} stdin writing error: {:?}", self.executable, error)
})?;
let output = process.wait_with_output().map_err(|error| {
anyhow::anyhow!("{} subprocess output error: {:?}", self.executable, error)
})?;
if !output.status.success() {
anyhow::bail!(
"{} error: {}",
self.executable,
String::from_utf8_lossy(output.stderr.as_slice()).to_string()
);
}
let mut output: StandardJsonOutput =
revive_common::deserialize_from_slice(output.stdout.as_slice()).map_err(|error| {
anyhow::anyhow!(
"{} subprocess output parsing error: {}\n{}",
self.executable,
error,
revive_common::deserialize_from_slice::<serde_json::Value>(
output.stdout.as_slice()
)
.map(|json| serde_json::to_string_pretty(&json).expect("Always valid"))
.unwrap_or_else(
|_| String::from_utf8_lossy(output.stdout.as_slice()).to_string()
),
)
})?;
output.preprocess_ast(&version, pipeline, suppressed_warnings.as_slice())?;
Ok(output)
}
/// The `solc --combined-json abi,hashes...` mirror.
fn combined_json(
&self,
paths: &[PathBuf],
combined_json_argument: &str,
) -> anyhow::Result<CombinedJson> {
let mut command = std::process::Command::new(self.executable.as_str());
command.args(paths);
let mut combined_json_flags = Vec::new();
let mut combined_json_fake_flag_pushed = false;
let mut filtered_flags = Vec::with_capacity(3);
for flag in combined_json_argument.split(',') {
match flag {
flag @ "asm" | flag @ "bin" | flag @ "bin-runtime" => filtered_flags.push(flag),
flag => combined_json_flags.push(flag),
}
}
if combined_json_flags.is_empty() {
combined_json_flags.push("ast");
combined_json_fake_flag_pushed = true;
}
command.arg("--combined-json");
command.arg(combined_json_flags.join(","));
let output = command.output().map_err(|error| {
anyhow::anyhow!("{} subprocess error: {:?}", self.executable, error)
})?;
if !output.status.success() {
println!("{}", String::from_utf8_lossy(output.stdout.as_slice()));
println!("{}", String::from_utf8_lossy(output.stderr.as_slice()));
anyhow::bail!(
"{} error: {}",
self.executable,
String::from_utf8_lossy(output.stdout.as_slice()).to_string()
);
}
let mut combined_json: CombinedJson =
revive_common::deserialize_from_slice(output.stdout.as_slice()).map_err(|error| {
anyhow::anyhow!(
"{} subprocess output parsing error: {}\n{}",
self.executable,
error,
revive_common::deserialize_from_slice::<serde_json::Value>(
output.stdout.as_slice()
)
.map(|json| serde_json::to_string_pretty(&json).expect("Always valid"))
.unwrap_or_else(
|_| String::from_utf8_lossy(output.stdout.as_slice()).to_string()
),
)
})?;
for filtered_flag in filtered_flags.into_iter() {
for (_path, contract) in combined_json.contracts.iter_mut() {
match filtered_flag {
"asm" => contract.asm = Some(serde_json::Value::Null),
"bin" => contract.bin = Some("".to_owned()),
"bin-runtime" => contract.bin_runtime = Some("".to_owned()),
_ => continue,
}
}
}
if combined_json_fake_flag_pushed {
combined_json.source_list = None;
combined_json.sources = None;
}
combined_json.remove_evm();
Ok(combined_json)
}
/// The `solc` Yul validator.
fn validate_yul(&self, path: &Path) -> anyhow::Result<()> {
let mut command = std::process::Command::new(self.executable.as_str());
command.arg("--strict-assembly");
command.arg(path);
let output = command.output().map_err(|error| {
anyhow::anyhow!("{} subprocess error: {:?}", self.executable, error)
})?;
if !output.status.success() {
anyhow::bail!(
"{} error: {}",
self.executable,
String::from_utf8_lossy(output.stderr.as_slice()).to_string()
);
}
Ok(())
}
/// The `solc --version` mini-parser.
fn version(&mut self) -> anyhow::Result<Version> {
if let Some(version) = self.version.as_ref() {
return Ok(version.to_owned());
}
let mut command = std::process::Command::new(self.executable.as_str());
command.arg("--version");
let output = command.output().map_err(|error| {
anyhow::anyhow!("{} subprocess error: {:?}", self.executable, error)
})?;
if !output.status.success() {
anyhow::bail!(
"{} error: {}",
self.executable,
String::from_utf8_lossy(output.stderr.as_slice()).to_string()
);
}
let stdout = String::from_utf8_lossy(output.stdout.as_slice());
let long = stdout
.lines()
.nth(1)
.ok_or_else(|| {
anyhow::anyhow!("{} version parsing: not enough lines", self.executable)
})?
.split(' ')
.nth(1)
.ok_or_else(|| {
anyhow::anyhow!(
"{} version parsing: not enough words in the 2nd line",
self.executable
)
})?
.to_owned();
let default: semver::Version = long
.split('+')
.next()
.ok_or_else(|| {
anyhow::anyhow!("{} version parsing: metadata dropping", self.executable)
})?
.parse()
.map_err(|error| anyhow::anyhow!("{} version parsing: {}", self.executable, error))?;
let l2_revision: Option<semver::Version> = stdout
.lines()
.nth(2)
.and_then(|line| line.split(' ').nth(1))
.and_then(|line| line.split('-').nth(1))
.and_then(|version| version.parse().ok());
let version = Version::new(long, default, l2_revision);
if version.default < super::FIRST_SUPPORTED_VERSION {
anyhow::bail!(
"`solc` versions <{} are not supported, found {}",
super::FIRST_SUPPORTED_VERSION,
version.default
);
}
if version.default > super::LAST_SUPPORTED_VERSION {
anyhow::bail!(
"`solc` versions >{} are not supported, found {}",
super::LAST_SUPPORTED_VERSION,
version.default
);
}
self.version = Some(version.clone());
Ok(version)
}
}
@@ -0,0 +1,133 @@
//! The Solidity compiler.
use std::path::Path;
use std::path::PathBuf;
use crate::solc::combined_json::CombinedJson;
use crate::solc::pipeline::Pipeline;
use crate::solc::standard_json::input::Input as StandardJsonInput;
use crate::solc::standard_json::output::Output as StandardJsonOutput;
use crate::solc::version::Version;
use anyhow::Context;
use std::ffi::{c_char, c_void, CStr, CString};
use super::Compiler;
extern "C" {
fn soljson_version() -> *const c_char;
fn soljson_compile(inputPtr: *const c_char, inputLen: usize) -> *const c_char;
}
/// The Solidity compiler.
pub struct SoljsonCompiler {
/// The lazily-initialized compiler version.
pub version: Option<Version>,
}
impl Compiler for SoljsonCompiler {
/// Compiles the Solidity `--standard-json` input into Yul IR.
fn standard_json(
&mut self,
mut input: StandardJsonInput,
pipeline: Pipeline,
_base_path: Option<String>,
_include_paths: Vec<String>,
_allow_paths: Option<String>,
) -> anyhow::Result<StandardJsonOutput> {
let version = self.version()?;
let suppressed_warnings = input.suppressed_warnings.take().unwrap_or_default();
let input_json = serde_json::to_string(&input).expect("Always valid");
let out = Self::compile_standard_json(input_json)?;
let mut output: StandardJsonOutput = revive_common::deserialize_from_slice(out.as_bytes())
.map_err(|error| {
anyhow::anyhow!(
"Soljson output parsing error: {}\n{}",
error,
revive_common::deserialize_from_slice::<serde_json::Value>(out.as_bytes())
.map(|json| serde_json::to_string_pretty(&json).expect("Always valid"))
.unwrap_or_else(|_| String::from_utf8_lossy(out.as_bytes()).to_string()),
)
})?;
output.preprocess_ast(&version, pipeline, suppressed_warnings.as_slice())?;
Ok(output)
}
fn combined_json(
&self,
_paths: &[PathBuf],
_combined_json_argument: &str,
) -> anyhow::Result<CombinedJson> {
unimplemented!();
}
fn validate_yul(&self, _path: &Path) -> anyhow::Result<()> {
unimplemented!();
}
fn version(&mut self) -> anyhow::Result<Version> {
let version = Self::get_soljson_version()?;
let long = version.clone();
let default: semver::Version = version
.split('+')
.next()
.ok_or_else(|| anyhow::anyhow!("Soljson version parsing: metadata dropping"))?
.parse()
.map_err(|error| anyhow::anyhow!("Soljson version parsing: {}", error))?;
let l2_revision: Option<semver::Version> = version
.split('-')
.nth(1)
.and_then(|version| version.parse().ok());
let version = Version::new(long, default, l2_revision);
if version.default < super::FIRST_SUPPORTED_VERSION {
anyhow::bail!(
"`Soljson` versions <{} are not supported, found {}",
super::FIRST_SUPPORTED_VERSION,
version.default
);
}
if version.default > super::LAST_SUPPORTED_VERSION {
anyhow::bail!(
"`Soljson` versions >{} are not supported, found {}",
super::LAST_SUPPORTED_VERSION,
version.default
);
}
self.version = Some(version.clone());
Ok(version)
}
}
impl SoljsonCompiler {
fn get_soljson_version() -> anyhow::Result<String> {
unsafe {
let version_ptr = soljson_version();
let version = CStr::from_ptr(version_ptr)
.to_str()
.with_context(|| "Failed to convert C string to Rust string")
.map(str::to_owned);
libc::free(version_ptr as *mut c_void);
Ok(version?)
}
}
fn compile_standard_json(input: String) -> anyhow::Result<String> {
let c_input = CString::new(input).unwrap();
let c_input_len = c_input.as_bytes().len();
unsafe {
let output_ptr = soljson_compile(c_input.as_ptr(), c_input_len);
let output_json = CStr::from_ptr(output_ptr)
.to_str()
.with_context(|| "Failed to convert C string to Rust string")
.map(str::to_owned);
libc::free(output_ptr as *mut c_void);
Ok(output_json?)
}
}
}
@@ -8,8 +8,8 @@ use std::collections::BTreeMap;
use std::collections::BTreeSet; use std::collections::BTreeSet;
use std::path::PathBuf; use std::path::PathBuf;
use rayon::iter::IntoParallelIterator; #[cfg(feature = "parallel")]
use rayon::iter::ParallelIterator; use rayon::iter::{IntoParallelIterator, ParallelIterator};
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
@@ -64,8 +64,12 @@ impl Input {
via_ir: bool, via_ir: bool,
suppressed_warnings: Option<Vec<Warning>>, suppressed_warnings: Option<Vec<Warning>>,
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self> {
let sources = paths #[cfg(feature = "parallel")]
.into_par_iter() let iter = paths.into_par_iter(); // Parallel iterator
#[cfg(not(feature = "parallel"))]
let iter = paths.iter(); // Sequential iterator
let sources = iter
.map(|path| { .map(|path| {
let source = Source::try_from(path.as_path()).unwrap_or_else(|error| { let source = Source::try_from(path.as_path()).unwrap_or_else(|error| {
panic!("Source code file {path:?} reading error: {error}") panic!("Source code file {path:?} reading error: {error}")
@@ -106,8 +110,12 @@ impl Input {
via_ir: bool, via_ir: bool,
suppressed_warnings: Option<Vec<Warning>>, suppressed_warnings: Option<Vec<Warning>>,
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self> {
let sources = sources #[cfg(feature = "parallel")]
.into_par_iter() let iter = sources.into_par_iter(); // Parallel iterator
#[cfg(not(feature = "parallel"))]
let iter = sources.into_iter(); // Sequential iterator
let sources = iter
.map(|(path, content)| (path, Source::from(content))) .map(|(path, content)| (path, Source::from(content)))
.collect(); .collect();
@@ -56,7 +56,7 @@ impl Output {
libraries: BTreeMap<String, BTreeMap<String, String>>, libraries: BTreeMap<String, BTreeMap<String, String>>,
pipeline: SolcPipeline, pipeline: SolcPipeline,
solc_version: &SolcVersion, solc_version: &SolcVersion,
debug_config: Option<&revive_llvm_context::DebugConfig>, debug_config: &revive_llvm_context::DebugConfig,
) -> anyhow::Result<Project> { ) -> anyhow::Result<Project> {
if let SolcPipeline::EVMLA = pipeline { if let SolcPipeline::EVMLA = pipeline {
self.preprocess_dependencies()?; self.preprocess_dependencies()?;
@@ -90,9 +90,7 @@ impl Output {
continue; continue;
} }
if let Some(debug_config) = debug_config { debug_config.dump_yul(full_path.as_str(), ir_optimized.as_str())?;
debug_config.dump_yul(full_path.as_str(), ir_optimized.as_str())?;
}
let mut lexer = Lexer::new(ir_optimized.to_owned()); let mut lexer = Lexer::new(ir_optimized.to_owned());
let object = Object::parse(&mut lexer, None).map_err(|error| { let object = Object::parse(&mut lexer, None).map_err(|error| {
+22 -10
View File
@@ -9,13 +9,14 @@ use once_cell::sync::Lazy;
use crate::project::Project; use crate::project::Project;
use crate::solc::pipeline::Pipeline as SolcPipeline; use crate::solc::pipeline::Pipeline as SolcPipeline;
use crate::solc::solc_compiler::SolcCompiler;
use crate::solc::standard_json::input::settings::optimizer::Optimizer as SolcStandardJsonInputSettingsOptimizer; use crate::solc::standard_json::input::settings::optimizer::Optimizer as SolcStandardJsonInputSettingsOptimizer;
use crate::solc::standard_json::input::settings::selection::Selection as SolcStandardJsonInputSettingsSelection; use crate::solc::standard_json::input::settings::selection::Selection as SolcStandardJsonInputSettingsSelection;
use crate::solc::standard_json::input::Input as SolcStandardJsonInput; use crate::solc::standard_json::input::Input as SolcStandardJsonInput;
use crate::solc::standard_json::output::contract::evm::bytecode::Bytecode; use crate::solc::standard_json::output::contract::evm::bytecode::Bytecode;
use crate::solc::standard_json::output::contract::evm::bytecode::DeployedBytecode; use crate::solc::standard_json::output::contract::evm::bytecode::DeployedBytecode;
use crate::solc::standard_json::output::Output as SolcStandardJsonOutput; use crate::solc::standard_json::output::Output as SolcStandardJsonOutput;
use crate::solc::Compiler as SolcCompiler; use crate::solc::Compiler;
use crate::warning::Warning; use crate::warning::Warning;
static PVM_BLOB_CACHE: Lazy<Mutex<HashMap<CachedBlob, Vec<u8>>>> = Lazy::new(Default::default); static PVM_BLOB_CACHE: Lazy<Mutex<HashMap<CachedBlob, Vec<u8>>>> = Lazy::new(Default::default);
@@ -23,6 +24,9 @@ static EVM_BLOB_CACHE: Lazy<Mutex<HashMap<CachedBlob, Vec<u8>>>> = Lazy::new(Def
static EVM_RUNTIME_BLOB_CACHE: Lazy<Mutex<HashMap<CachedBlob, Vec<u8>>>> = static EVM_RUNTIME_BLOB_CACHE: Lazy<Mutex<HashMap<CachedBlob, Vec<u8>>>> =
Lazy::new(Default::default); Lazy::new(Default::default);
const DEBUG_CONFIG: revive_llvm_context::DebugConfig =
revive_llvm_context::DebugConfig::new(None, true);
#[derive(Hash, PartialEq, Eq)] #[derive(Hash, PartialEq, Eq)]
struct CachedBlob { struct CachedBlob {
contract_name: String, contract_name: String,
@@ -78,7 +82,8 @@ pub fn build_solidity_with_options(
inkwell::support::enable_llvm_pretty_stack_trace(); inkwell::support::enable_llvm_pretty_stack_trace();
revive_llvm_context::initialize_target(revive_llvm_context::Target::PVM); revive_llvm_context::initialize_target(revive_llvm_context::Target::PVM);
let _ = crate::process::EXECUTABLE.set(PathBuf::from(crate::r#const::DEFAULT_EXECUTABLE_NAME)); let _ = crate::process::native_process::EXECUTABLE
.set(PathBuf::from(crate::r#const::DEFAULT_EXECUTABLE_NAME));
let mut solc = SolcCompiler::new(SolcCompiler::DEFAULT_EXECUTABLE_NAME.to_owned())?; let mut solc = SolcCompiler::new(SolcCompiler::DEFAULT_EXECUTABLE_NAME.to_owned())?;
let solc_version = solc.version()?; let solc_version = solc.version()?;
@@ -102,9 +107,10 @@ pub fn build_solidity_with_options(
let mut output = solc.standard_json(input, pipeline, None, vec![], None)?; let mut output = solc.standard_json(input, pipeline, None, vec![], None)?;
let project = output.try_to_project(sources, libraries, pipeline, &solc_version, None)?; let project =
output.try_to_project(sources, libraries, pipeline, &solc_version, &DEBUG_CONFIG)?;
let build: crate::Build = project.compile(optimizer_settings, false, None)?; let build: crate::Build = project.compile(optimizer_settings, false, DEBUG_CONFIG)?;
build.write_to_standard_json(&mut output, &solc_version)?; build.write_to_standard_json(&mut output, &solc_version)?;
Ok(output) Ok(output)
@@ -122,7 +128,8 @@ pub fn build_solidity_with_options_evm(
inkwell::support::enable_llvm_pretty_stack_trace(); inkwell::support::enable_llvm_pretty_stack_trace();
revive_llvm_context::initialize_target(revive_llvm_context::Target::PVM); revive_llvm_context::initialize_target(revive_llvm_context::Target::PVM);
let _ = crate::process::EXECUTABLE.set(PathBuf::from(crate::r#const::DEFAULT_EXECUTABLE_NAME)); let _ = crate::process::native_process::EXECUTABLE
.set(PathBuf::from(crate::r#const::DEFAULT_EXECUTABLE_NAME));
let mut solc = SolcCompiler::new(SolcCompiler::DEFAULT_EXECUTABLE_NAME.to_owned())?; let mut solc = SolcCompiler::new(SolcCompiler::DEFAULT_EXECUTABLE_NAME.to_owned())?;
let solc_version = solc.version()?; let solc_version = solc.version()?;
@@ -175,7 +182,8 @@ pub fn build_solidity_and_detect_missing_libraries(
inkwell::support::enable_llvm_pretty_stack_trace(); inkwell::support::enable_llvm_pretty_stack_trace();
revive_llvm_context::initialize_target(revive_llvm_context::Target::PVM); revive_llvm_context::initialize_target(revive_llvm_context::Target::PVM);
let _ = crate::process::EXECUTABLE.set(PathBuf::from(crate::r#const::DEFAULT_EXECUTABLE_NAME)); let _ = crate::process::native_process::EXECUTABLE
.set(PathBuf::from(crate::r#const::DEFAULT_EXECUTABLE_NAME));
let mut solc = SolcCompiler::new(SolcCompiler::DEFAULT_EXECUTABLE_NAME.to_owned())?; let mut solc = SolcCompiler::new(SolcCompiler::DEFAULT_EXECUTABLE_NAME.to_owned())?;
let solc_version = solc.version()?; let solc_version = solc.version()?;
@@ -194,7 +202,8 @@ pub fn build_solidity_and_detect_missing_libraries(
let mut output = solc.standard_json(input, pipeline, None, vec![], None)?; let mut output = solc.standard_json(input, pipeline, None, vec![], None)?;
let project = output.try_to_project(sources, libraries, pipeline, &solc_version, None)?; let project =
output.try_to_project(sources, libraries, pipeline, &solc_version, &DEBUG_CONFIG)?;
let missing_libraries = project.get_missing_libraries(); let missing_libraries = project.get_missing_libraries();
missing_libraries.write_to_standard_json(&mut output, &solc.version()?)?; missing_libraries.write_to_standard_json(&mut output, &solc.version()?)?;
@@ -210,9 +219,12 @@ pub fn build_yul(source_code: &str) -> anyhow::Result<()> {
revive_llvm_context::initialize_target(revive_llvm_context::Target::PVM); revive_llvm_context::initialize_target(revive_llvm_context::Target::PVM);
let optimizer_settings = revive_llvm_context::OptimizerSettings::none(); let optimizer_settings = revive_llvm_context::OptimizerSettings::none();
let project = let project = Project::try_from_yul_string::<SolcCompiler>(
Project::try_from_yul_string(PathBuf::from("test.yul").as_path(), source_code, None)?; PathBuf::from("test.yul").as_path(),
let _build = project.compile(optimizer_settings, false, None)?; source_code,
None,
)?;
let _build = project.compile(optimizer_settings, false, DEBUG_CONFIG)?;
Ok(()) Ok(())
} }
@@ -3,8 +3,11 @@ import * as path from 'path';
const outputDir = 'artifacts'; const outputDir = 'artifacts';
const binExtension = ':C.pvm'; const binExtension = ':C.pvm';
const asmExtension = ':C.pvmasm'; const asmExtension = ':C.pvmasm';
const llvmExtension = '.ll';
const contractSolFilename = 'contract.sol'; const contractSolFilename = 'contract.sol';
const contractYulFilename = 'contract.yul'; const contractYulFilename = 'contract.yul';
const contractOptimizedLLVMFilename = contractSolFilename + '.C.optimized';
const contractUnoptimizedLLVMFilename = contractSolFilename + '.C.unoptimized';
const pathToOutputDir = path.join(__dirname, '..', outputDir); const pathToOutputDir = path.join(__dirname, '..', outputDir);
const pathToContracts = path.join(__dirname, '..', 'src', 'contracts'); const pathToContracts = path.join(__dirname, '..', 'src', 'contracts');
const pathToBasicYulContract = path.join(pathToContracts, 'yul', contractYulFilename); const pathToBasicYulContract = path.join(pathToContracts, 'yul', contractYulFilename);
@@ -16,8 +19,11 @@ export const paths = {
outputDir: outputDir, outputDir: outputDir,
binExtension: binExtension, binExtension: binExtension,
asmExtension: asmExtension, asmExtension: asmExtension,
llvmExtension: llvmExtension,
contractSolFilename: contractSolFilename, contractSolFilename: contractSolFilename,
contractYulFilename: contractYulFilename, contractYulFilename: contractYulFilename,
contractOptimizedLLVMFilename: contractOptimizedLLVMFilename,
contractUnoptimizedLLVMFilename: contractUnoptimizedLLVMFilename,
pathToOutputDir: pathToOutputDir, pathToOutputDir: pathToOutputDir,
pathToContracts: pathToContracts, pathToContracts: pathToContracts,
pathToBasicSolContract: pathToBasicSolContract, pathToBasicSolContract: pathToBasicSolContract,
@@ -1,4 +1,4 @@
import {executeCommand, isFolderExist, isFileExist, isFileEmpty} from "../src/helper"; import { executeCommand, isFolderExist, isFileExist, isFileEmpty } from "../src/helper";
import { paths } from '../src/entities'; import { paths } from '../src/entities';
@@ -76,3 +76,69 @@ describe("Default run a command from the help", () => {
expect(result.output).not.toMatch(/([Ee]rror|[Ww]arning|[Ff]ail)/i); expect(result.output).not.toMatch(/([Ee]rror|[Ww]arning|[Ff]ail)/i);
}); });
}); });
describe("Run resolc with source debug information", () => {
const commands = [
`resolc -g ${paths.pathToBasicSolContract} --bin --asm --output-dir "${paths.pathToOutputDir}"`,
`resolc --disable-solc-optimizer -g ${paths.pathToBasicSolContract} --bin --asm --output-dir "${paths.pathToOutputDir}"`
]; // potential issue on resolc with full path on Windows cmd`;
for (var idx in commands) {
const command = commands[idx];
const result = executeCommand(command);
it("Compiler run successful", () => {
expect(result.output).toMatch(/(Compiler run successful.)/i);
});
it("Exit code = 0", () => {
expect(result.exitCode).toBe(0);
});
it("Output dir is created", () => {
expect(isFolderExist(paths.pathToOutputDir)).toBe(true);
});
it("Output files are created", () => { // a bug on windows
expect(isFileExist(paths.pathToOutputDir, paths.contractSolFilename, paths.binExtension)).toBe(true);
expect(isFileExist(paths.pathToOutputDir, paths.contractSolFilename, paths.asmExtension)).toBe(true);
});
it("the output files are not empty", () => {
expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false);
expect(isFileEmpty(paths.pathToSolAsmOutputFile)).toBe(false);
});
it("No 'Error'/'Fail' in the output", () => {
expect(result.output).not.toMatch(/([Ee]rror|[Ff]ail)/i);
});
}
});
describe("Run resolc with source debug information, check LLVM debug-info", () => {
const commands = [
`resolc -g ${paths.pathToBasicSolContract} --debug-output-dir="${paths.pathToOutputDir}"`,
`resolc -g --disable-solc-optimizer ${paths.pathToBasicSolContract} --debug-output-dir="${paths.pathToOutputDir}"`
]; // potential issue on resolc with full path on Windows cmd`;
for (var idx in commands) {
const command = commands[idx];
const result = executeCommand(command);
it("Compiler run successful", () => {
expect(result.output).toMatch(/(Compiler run successful.)/i);
});
it("Exit code = 0", () => {
expect(result.exitCode).toBe(0);
});
it("Output dir is created", () => {
expect(isFolderExist(paths.pathToOutputDir)).toBe(true);
});
it("Output files are created", () => { // a bug on windows
expect(isFileExist(paths.pathToOutputDir, paths.contractOptimizedLLVMFilename, paths.llvmExtension)).toBe(true);
expect(isFileExist(paths.pathToOutputDir, paths.contractUnoptimizedLLVMFilename, paths.llvmExtension)).toBe(true);
});
it("the output files are not empty", () => {
expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false);
expect(isFileEmpty(paths.pathToSolAsmOutputFile)).toBe(false);
});
it("No 'Error'/'Fail' in the output", () => {
expect(result.output).not.toMatch(/([Ee]rror|[Ff]ail)/i);
});
}
});
+15 -5
View File
@@ -21,7 +21,7 @@ pub struct Lexer {
/// The input source code. /// The input source code.
input: String, input: String,
/// The number of characters processed so far. /// The number of characters processed so far.
offset: usize, offset: u32,
/// The current location. /// The current location.
location: Location, location: Location,
/// The peeked lexeme, waiting to be fetched. /// The peeked lexeme, waiting to be fetched.
@@ -48,8 +48,17 @@ impl Lexer {
return Ok(peeked); return Ok(peeked);
} }
while self.offset < self.input.len() { while self.offset
let input = &self.input[self.offset..]; < self
.input
.len()
.try_into()
.map_err(|_| Error::InvalidLexeme {
location: self.location,
sequence: Default::default(),
})?
{
let input = &self.input[(self.offset as usize)..];
if input.starts_with(|character| char::is_ascii_whitespace(&character)) { if input.starts_with(|character| char::is_ascii_whitespace(&character)) {
if input.starts_with('\n') { if input.starts_with('\n') {
@@ -101,12 +110,13 @@ impl Lexer {
return Ok(token); return Ok(token);
} }
let end = self.input[self.offset..] let end = self.input[(self.offset as usize)..]
.find(char::is_whitespace) .find(char::is_whitespace)
.unwrap_or(self.input.len()); .unwrap_or(self.input.len());
return Err(Error::InvalidLexeme { return Err(Error::InvalidLexeme {
location: self.location, location: self.location,
sequence: self.input[self.offset..self.offset + end].to_owned(), sequence: self.input[(self.offset as usize)..(self.offset as usize) + end]
.to_owned(),
}); });
} }
@@ -19,12 +19,20 @@ impl Comment {
let end_position = input.find(Self::END).unwrap_or(input.len()); let end_position = input.find(Self::END).unwrap_or(input.len());
let input = &input[..end_position]; let input = &input[..end_position];
let length = end_position + Self::END.len(); let length = (end_position + Self::END.len())
let lines = input.matches('\n').count(); .try_into()
.expect("the YUL should be of reasonable size");
let lines = input
.matches('\n')
.count()
.try_into()
.expect("the YUL should be of reasonable size");
let columns = match input.rfind('\n') { let columns = match input.rfind('\n') {
Some(new_line) => end_position - (new_line + 1), Some(new_line) => end_position - (new_line + 1),
None => end_position, None => end_position,
}; }
.try_into()
.expect("the YUL should be of reasonable size");
Token::new(Location::new(lines, columns), Lexeme::Comment, length) Token::new(Location::new(lines, columns), Lexeme::Comment, length)
} }
@@ -17,7 +17,9 @@ impl Comment {
/// Returns the comment's length, including the trimmed whitespace around it. /// Returns the comment's length, including the trimmed whitespace around it.
pub fn parse(input: &str) -> Token { pub fn parse(input: &str) -> Token {
let end_position = input.find(Self::END).unwrap_or(input.len()); let end_position = input.find(Self::END).unwrap_or(input.len());
let length = end_position + Self::END.len(); let length = (end_position + Self::END.len())
.try_into()
.expect("the YUL should be of reasonable size");
Token::new(Location::new(1, 1), Lexeme::Comment, length) Token::new(Location::new(1, 1), Lexeme::Comment, length)
} }
@@ -26,7 +26,10 @@ impl Identifier {
let end = input.find(Self::cannot_continue).unwrap_or(input.len()); let end = input.find(Self::cannot_continue).unwrap_or(input.len());
let inner = input[..end].to_string(); let inner = input[..end].to_string();
let length = inner.len(); let length = inner
.len()
.try_into()
.expect("the YUL should be of reasonable size");
if let Some(token) = Keyword::parse(inner.as_str()) { if let Some(token) = Keyword::parse(inner.as_str()) {
return Some(token); return Some(token);
@@ -58,6 +58,9 @@ impl Keyword {
if length != input.len() { if length != input.len() {
return None; return None;
} }
let length = length
.try_into()
.expect("the YUL should be of reasonable size");
Some(Token::new(Location::new(0, length), lexeme, length)) Some(Token::new(Location::new(0, length), lexeme, length))
} }
@@ -54,6 +54,10 @@ impl Integer {
return None; return None;
}; };
let length = length
.try_into()
.expect("the YUL should be of reasonable size");
let token = Token::new( let token = Token::new(
Location::new(0, length), Location::new(0, length),
Lexeme::Literal(Literal::Integer(value)), Lexeme::Literal(Literal::Integer(value)),
@@ -69,6 +69,9 @@ impl String {
.to_owned(); .to_owned();
let literal = Self::new(string, is_hex_string); let literal = Self::new(string, is_hex_string);
let length = length
.try_into()
.expect("the YUL should be of reasonable size");
Some(Token::new( Some(Token::new(
Location::new(0, length), Location::new(0, length),
@@ -7,9 +7,9 @@ use serde::Serialize;
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Eq)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, Eq)]
pub struct Location { pub struct Location {
/// The line number, starting from 1. /// The line number, starting from 1.
pub line: usize, pub line: u32,
/// The column number, starting from 1. /// The column number, starting from 1.
pub column: usize, pub column: u32,
} }
impl Default for Location { impl Default for Location {
@@ -20,13 +20,13 @@ impl Default for Location {
impl Location { impl Location {
/// Creates a default location. /// Creates a default location.
pub fn new(line: usize, column: usize) -> Self { pub fn new(line: u32, column: u32) -> Self {
Self { line, column } Self { line, column }
} }
/// Mutates the location by shifting the original one down by `lines` and /// Mutates the location by shifting the original one down by `lines` and
/// setting the column to `column`. /// setting the column to `column`.
pub fn shift_down(&mut self, lines: usize, column: usize) { pub fn shift_down(&mut self, lines: u32, column: u32) {
if lines == 0 { if lines == 0 {
self.shift_right(column); self.shift_right(column);
return; return;
@@ -37,7 +37,7 @@ impl Location {
} }
/// Mutates the location by shifting the original one rightward by `columns`. /// Mutates the location by shifting the original one rightward by `columns`.
pub fn shift_right(&mut self, columns: usize) { pub fn shift_right(&mut self, columns: u32) {
self.column += columns; self.column += columns;
} }
} }
+2 -2
View File
@@ -15,12 +15,12 @@ pub struct Token {
/// The lexeme. /// The lexeme.
pub lexeme: Lexeme, pub lexeme: Lexeme,
/// The token length, including whitespaces. /// The token length, including whitespaces.
pub length: usize, pub length: u32,
} }
impl Token { impl Token {
/// A shortcut constructor. /// A shortcut constructor.
pub fn new(location: Location, lexeme: Lexeme, length: usize) -> Self { pub fn new(location: Location, lexeme: Lexeme, length: u32) -> Self {
Self { Self {
location, location,
lexeme, lexeme,
+3
View File
@@ -57,4 +57,7 @@ pub enum Error {
/// The list of invalid attributes. /// The list of invalid attributes.
values: BTreeSet<String>, values: BTreeSet<String>,
}, },
/// Invalid code length.
#[error("The line or column length exceed the maximum of u32::MAX")]
InvalidLength,
} }
@@ -47,7 +47,11 @@ impl Assignment {
.into()); .into());
} }
}; };
let length = identifier.inner.len(); let length = identifier
.inner
.len()
.try_into()
.map_err(|_| Error::Parser(ParserError::InvalidLength))?;
match lexer.peek()? { match lexer.peek()? {
Token { Token {
@@ -115,6 +119,8 @@ where
mut self, mut self,
context: &mut revive_llvm_context::PolkaVMContext<D>, context: &mut revive_llvm_context::PolkaVMContext<D>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
context.set_debug_location(self.location.line, 0, None)?;
let value = match self.initializer.into_llvm(context)? { let value = match self.initializer.into_llvm(context)? {
Some(value) => value, Some(value) => value,
None => return Ok(()), None => return Ok(()),
@@ -142,6 +148,8 @@ where
context.build_store(tuple_pointer, value.to_llvm())?; context.build_store(tuple_pointer, value.to_llvm())?;
for (index, binding) in self.bindings.into_iter().enumerate() { for (index, binding) in self.bindings.into_iter().enumerate() {
context.set_debug_location(self.location.line, 0, None)?;
let field_pointer = context.build_gep( let field_pointer = context.build_gep(
tuple_pointer, tuple_pointer,
&[ &[
@@ -5,6 +5,8 @@ use std::collections::HashSet;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
use inkwell::debug_info::AsDIScope;
use crate::yul::error::Error; use crate::yul::error::Error;
use crate::yul::lexer::token::lexeme::symbol::Symbol; use crate::yul::lexer::token::lexeme::symbol::Symbol;
use crate::yul::lexer::token::lexeme::Lexeme; use crate::yul::lexer::token::lexeme::Lexeme;
@@ -153,9 +155,26 @@ where
function.into_llvm(context)?; function.into_llvm(context)?;
} }
context.set_current_function(current_function.as_str())?; context.set_current_function(current_function.as_str(), Some(self.location.line))?;
if let Some(debug_info) = context.debug_info() {
let di_builder = debug_info.builder();
let di_scope = debug_info.top_scope().expect("expected a debug-info scope");
let di_block_scope = di_builder
.create_lexical_block(
di_scope,
debug_info.compilation_unit().get_file(),
self.location.line,
0,
)
.as_debug_info_scope();
context.push_debug_scope(di_block_scope);
context.set_debug_location(self.location.line, 0, None)?;
}
context.set_basic_block(current_block); context.set_basic_block(current_block);
for statement in local_statements.into_iter() { for statement in local_statements.into_iter() {
context.set_debug_location(statement.location().line, 0, None)?;
if context.basic_block().get_terminator().is_some() { if context.basic_block().get_terminator().is_some() {
break; break;
} }
@@ -194,6 +213,8 @@ where
} }
} }
context.pop_debug_scope();
Ok(()) Ok(())
} }
} }
@@ -807,7 +807,6 @@ impl FunctionCall {
context, context,
gas, gas,
address, address,
None,
input_offset, input_offset,
input_size, input_size,
output_offset, output_offset,
@@ -55,7 +55,11 @@ impl Expression {
.into()); .into());
} }
}; };
let length = identifier.inner.len(); let length = identifier
.inner
.len()
.try_into()
.map_err(|_| Error::Parser(ParserError::InvalidLength))?;
match lexer.peek()? { match lexer.peek()? {
Token { Token {
@@ -4,6 +4,7 @@ use std::collections::BTreeSet;
use std::collections::HashSet; use std::collections::HashSet;
use inkwell::types::BasicType; use inkwell::types::BasicType;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
@@ -229,14 +230,15 @@ where
mut self, mut self,
context: &mut revive_llvm_context::PolkaVMContext<D>, context: &mut revive_llvm_context::PolkaVMContext<D>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
context.set_current_function(self.identifier.as_str())?; context.set_current_function(self.identifier.as_str(), Some(self.location.line))?;
let r#return = context.current_function().borrow().r#return();
context.set_basic_block(context.current_function().borrow().entry_block()); context.set_basic_block(context.current_function().borrow().entry_block());
let r#return = context.current_function().borrow().r#return();
match r#return { match r#return {
revive_llvm_context::PolkaVMFunctionReturn::None => {} revive_llvm_context::PolkaVMFunctionReturn::None => {}
revive_llvm_context::PolkaVMFunctionReturn::Primitive { pointer } => { revive_llvm_context::PolkaVMFunctionReturn::Primitive { pointer } => {
let identifier = self.result.pop().expect("Always exists"); let identifier = self.result.pop().expect("Always exists");
let r#type = identifier.r#type.unwrap_or_default(); let r#type = identifier.r#type.unwrap_or_default();
context.build_store(pointer, r#type.into_llvm(context).const_zero())?; context.build_store(pointer, r#type.into_llvm(context).const_zero())?;
context context
@@ -288,6 +290,8 @@ where
} }
self.body.into_llvm(context)?; self.body.into_llvm(context)?;
context.set_debug_location(self.location.line, 0, None)?;
match context match context
.basic_block() .basic_block()
.get_last_instruction() .get_last_instruction()
@@ -314,6 +318,8 @@ where
} }
} }
context.pop_debug_scope();
Ok(()) Ok(())
} }
} }
@@ -2,6 +2,8 @@
use std::collections::HashSet; use std::collections::HashSet;
use inkwell::debug_info::AsDIScope;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
@@ -217,16 +219,30 @@ where
} }
fn into_llvm(self, context: &mut revive_llvm_context::PolkaVMContext<D>) -> anyhow::Result<()> { fn into_llvm(self, context: &mut revive_llvm_context::PolkaVMContext<D>) -> anyhow::Result<()> {
if let Some(debug_info) = context.debug_info() {
let di_builder = debug_info.builder();
let object_name: &str = self.identifier.as_str();
let di_parent_scope = debug_info
.top_scope()
.expect("expected an existing debug-info scope");
let object_scope = di_builder.create_namespace(di_parent_scope, object_name, true);
context.push_debug_scope(object_scope.as_debug_info_scope());
}
if self.identifier.ends_with("_deployed") { if self.identifier.ends_with("_deployed") {
revive_llvm_context::PolkaVMImmutableDataLoadFunction.into_llvm(context)?; revive_llvm_context::PolkaVMImmutableDataLoadFunction.into_llvm(context)?;
revive_llvm_context::PolkaVMRuntimeCodeFunction::new(self.code).into_llvm(context)?; revive_llvm_context::PolkaVMRuntimeCodeFunction::new(self.code).into_llvm(context)?;
} else { } else {
revive_llvm_context::PolkaVMDeployCodeFunction::new(self.code).into_llvm(context)?; revive_llvm_context::PolkaVMDeployCodeFunction::new(self.code).into_llvm(context)?;
} }
context.set_debug_location(self.location.line, 0, None)?;
if let Some(object) = self.inner_object { if let Some(object) = self.inner_object {
object.into_llvm(context)?; object.into_llvm(context)?;
} }
context.set_debug_location(self.location.line, 0, None)?;
context.pop_debug_scope();
Ok(()) Ok(())
} }
@@ -101,7 +101,9 @@ where
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
if self.bindings.len() == 1 { if self.bindings.len() == 1 {
let identifier = self.bindings.remove(0); let identifier = self.bindings.remove(0);
let r#type = identifier.r#type.unwrap_or_default().into_llvm(context); context.set_debug_location(self.location.line, 0, None)?;
let identifier_type = identifier.r#type.clone().unwrap_or_default();
let r#type = identifier_type.into_llvm(context);
let pointer = context.build_alloca(r#type, identifier.inner.as_str()); let pointer = context.build_alloca(r#type, identifier.inner.as_str());
context context
.current_function() .current_function()
@@ -116,7 +118,7 @@ where
.current_function() .current_function()
.borrow_mut() .borrow_mut()
.yul_mut() .yul_mut()
.insert_constant(identifier.inner, constant); .insert_constant(identifier.inner.clone(), constant);
} }
value.to_llvm() value.to_llvm()
@@ -131,6 +133,8 @@ where
} }
for (index, binding) in self.bindings.iter().enumerate() { for (index, binding) in self.bindings.iter().enumerate() {
context.set_debug_location(self.location.line, 0, None)?;
let yul_type = binding let yul_type = binding
.r#type .r#type
.to_owned() .to_owned()
-1
View File
@@ -1 +0,0 @@
book
-6
View File
@@ -1,6 +0,0 @@
[book]
authors = ["Cyrill Leutwiler <cyrill@parity.io>"]
language = "en"
multilingual = false
src = "src"
title = "revive documentation"
-3
View File
@@ -1,3 +0,0 @@
# revive documentation
Welcome to the revive Solidty compiler documentation!
-13
View File
@@ -1,13 +0,0 @@
# Summary
[Introduction](README.md)
- [User guide](./user-guide.md)
- [Installation](./installation.md)
- [CLI guide](./cli.md)
- [Hardhat integration](./hardhat.md)
- [Architecture](./architecture.md)
- [Overview](./overview.md)
- [Runtime](./runtime.md)
- [Differences from EVM](./differences-evm.md)
- [Development](./development.md)
-1
View File
@@ -1 +0,0 @@
# Architecture
-1
View File
@@ -1 +0,0 @@
# CLI guide
-1
View File
@@ -1 +0,0 @@
# Development
-1
View File
@@ -1 +0,0 @@
# Differences from EVM
-1
View File
@@ -1 +0,0 @@
# Differences from EVM
-1
View File
@@ -1 +0,0 @@
# Hardhat integration
-1
View File
@@ -1 +0,0 @@
# Installation
-1
View File
@@ -1 +0,0 @@
# Overview
-3
View File
@@ -1,3 +0,0 @@
# Runtime
revive compiled contract target the new contracts pallet runtime environment API.
-1
View File
@@ -1 +0,0 @@
# User guide
+69
View File
@@ -0,0 +1,69 @@
#!/usr/bin/env bash
set -euo pipefail
INSTALL_DIR="${PWD}/llvm18.0-emscripten"
mkdir -p "${INSTALL_DIR}"
# Check if EMSDK_ROOT is defined
if [ -z "${EMSDK_ROOT:-}" ]; then
echo "Error: EMSDK_ROOT is not defined."
echo "Please set the EMSDK_ROOT environment variable to the root directory of your Emscripten SDK."
exit 1
fi
source "${EMSDK_ROOT}/emsdk_env.sh"
LLVM_SRC="${PWD}/llvm-project"
LLVM_NATIVE="${PWD}/build/llvm-tools"
LLVM_WASM="${PWD}/build/llvm-wasm"
./clone-llvm.sh "${LLVM_SRC}"
# Cross-compiling LLVM requires a native build of "llvm-tblgen", "clang-tblgen" and "llvm-config"
if [ ! -d "${LLVM_NATIVE}" ]; then
cmake -G Ninja \
-S "${LLVM_SRC}/llvm" \
-B "${LLVM_NATIVE}" \
-DCMAKE_BUILD_TYPE=Release \
-DLLVM_TARGETS_TO_BUILD=WebAssembly \
-DLLVM_ENABLE_PROJECTS="clang"
fi
cmake --build "${LLVM_NATIVE}" -- llvm-tblgen clang-tblgen llvm-config
if [ ! -d "${LLVM_WASM}" ]; then
EMCC_DEBUG=2 \
CXXFLAGS="-Dwait4=__syscall_wait4" \
LDFLAGS="-lnodefs.js -s NO_INVOKE_RUN -s EXIT_RUNTIME -s INITIAL_MEMORY=64MB -s ALLOW_MEMORY_GROWTH -s \
EXPORTED_RUNTIME_METHODS=FS,callMain,NODEFS -s MODULARIZE -s EXPORT_ES6 -s WASM_BIGINT" \
emcmake cmake -G Ninja \
-S "${LLVM_SRC}/llvm" \
-B "${LLVM_WASM}" \
-DCMAKE_BUILD_TYPE=Release \
-DLLVM_TARGETS_TO_BUILD='RISCV' \
-DLLVM_ENABLE_PROJECTS="clang;lld" \
-DLLVM_ENABLE_DUMP=OFF \
-DLLVM_ENABLE_ASSERTIONS=OFF \
-DLLVM_ENABLE_EXPENSIVE_CHECKS=OFF \
-DLLVM_ENABLE_BACKTRACES=OFF \
-DLLVM_BUILD_TOOLS=OFF \
-DLLVM_ENABLE_THREADS=OFF \
-DLLVM_BUILD_LLVM_DYLIB=OFF \
-DLLVM_INCLUDE_TESTS=OFF \
-DLLVM_ENABLE_TERMINFO=Off \
-DLLVM_ENABLE_LIBXML2=Off \
-DLLVM_ENABLE_ZLIB=Off \
-DLLVM_ENABLE_ZSTD=Off \
-DLLVM_TABLEGEN="${LLVM_NATIVE}/bin/llvm-tblgen" \
-DCLANG_TABLEGEN="${LLVM_NATIVE}/bin/clang-tblgen" \
-DCMAKE_INSTALL_PREFIX="${INSTALL_DIR}"
fi
cmake --build "${LLVM_WASM}"
cmake --install "${LLVM_WASM}"
cp "${LLVM_NATIVE}/bin/llvm-config" "${INSTALL_DIR}/bin"
echo ""
echo "LLVM cross-compilation for WebAssembly completed successfully."
+49
View File
@@ -0,0 +1,49 @@
var Module = {
stdinData: "",
stdoutCallback: null,
stderrCallback: null,
// Function to set a callback for stdout
setStdoutCallback: function(callback) {
this.stdoutCallback = callback;
},
// Function to set a callback for stderr
setStderrCallback: function(callback) {
this.stderrCallback = callback;
},
// Function to set input data for stdin
setStdinData: function(data) {
this.stdinData = data;
},
// `preRun` is called before the program starts running
preRun: function() {
// Define a custom stdin function
function customStdin() {
if (Module.stdinData.length === 0) {
return null; // End of input (EOF)
}
const char = Module.stdinData.charCodeAt(0);
Module.stdinData = Module.stdinData.slice(1); // Remove the character from input
return char;
}
// Define a custom stdout function
function customStdout(char) {
if (Module.stdoutCallback) {
Module.stdoutCallback(String.fromCharCode(char));
}
}
// Define a custom stderr function
function customStderr(char) {
if (Module.stderrCallback) {
Module.stderrCallback(String.fromCharCode(char));
}
}
FS.init(customStdin, customStdout, customStderr);
},
};
+60
View File
@@ -0,0 +1,60 @@
mergeInto(LibraryManager.library, {
soljson_compile: function(inputPtr, inputLen) {
const inputJson = UTF8ToString(inputPtr, inputLen);
const output = Module.solc.compile(inputJson)
return stringToNewUTF8(output)
},
soljson_version: function() {
var version = Module.solc.version();
return stringToNewUTF8(version)
},
resolc_compile: function(inputPtr, inputLen) {
const { Worker } = require('worker_threads');
const deasync = require('deasync');
var inputJson = UTF8ToString(inputPtr, inputLen);
function compileWithWorker(inputJson, callback) {
return new Promise((resolve, reject) => {
const worker = new Worker(new URL('./worker.js', import.meta.url), {
type: 'module',
});
// Listen for messages from the worker
worker.on('message', (message) => {
resolve(message.output); // Resolve the promise with the output
callback(null, message.output);
worker.terminate(); // Terminate the worker after processing
});
// Listen for errors from the worker
worker.on('error', (error) => {
reject(error);
callback(error);
worker.terminate();
});
// Send the input JSON to the worker
worker.postMessage(inputJson);
});
}
let result = null;
let error = null;
// Use deasync to block until promise resolves
compileWithWorker(inputJson, function (err, res) {
error = err;
result = res;
});
// TODO: deasync is not present in browsers, another solution needs to be implemented
deasync.loopWhile(() => result === null && error === null);
if (error) {
const errorJson = JSON.stringify({ type: 'error', message: error.message || "Unknown error" });
return stringToNewUTF8(errorJson)
}
const resultJson = JSON.stringify({ type: 'success', data: result });
return stringToNewUTF8(resultJson);
},
});
+23
View File
@@ -0,0 +1,23 @@
{
"name": "revive",
"version": "1.0.0",
"description": "Revive compiler",
"main": "run_revive.js",
"type": "module",
"dependencies": {
"deasync": "^0.1.15",
"solc": "^0.8.28"
},
"scripts": {
"build": "cp ../target/wasm32-unknown-emscripten/release/resolc.js ../target/wasm32-unknown-emscripten/release/resolc.wasm ./src && npx rollup -c",
"test": "npm run build && node run_revive.js"
},
"devDependencies": {
"@babel/core": "^7.26.0",
"@babel/preset-env": "^7.26.0",
"@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-node-resolve": "^15.3.0",
"rollup": "^4.27.3",
"rollup-plugin-copy": "^3.5.0"
}
}
+35
View File
@@ -0,0 +1,35 @@
import babel from '@rollup/plugin-babel';
import copy from 'rollup-plugin-copy';
import resolve from '@rollup/plugin-node-resolve';
const outputDirCJS = 'dist/revive-cjs';
const outputDirESM = 'dist/revive-esm';
export default {
input: ['src/resolc.js', 'src/worker.js'], // Adjust this to your main entry file
output: [
{
dir: outputDirCJS,
format: 'cjs',
exports: 'auto',
},
{
dir: outputDirESM,
format: 'esm',
},
],
plugins: [
babel({
exclude: 'node_modules/**',
presets: ['@babel/preset-env'],
babelHelpers: 'inline',
}),
resolve(),
copy({
targets: [
{ src: 'src/resolc.wasm', dest: outputDirCJS },
{ src: 'src/resolc.wasm', dest: outputDirESM },
],
})
],
};
+62
View File
@@ -0,0 +1,62 @@
import solc from 'solc';
// Import the Emscripten module
import createRevive from './dist/revive-esm/resolc.js';
const compilerStandardJsonInput = {
language: 'Solidity',
sources: {
'MyContract.sol': {
content: `
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
contract MyContract {
function greet() public pure returns (string memory) {
return "Hello";
}
}
`,
},
},
settings: {
optimizer: {
enabled: true,
runs: 200,
},
outputSelection: {
'*': {
'*': ['abi'],
},
},
},
};
async function runCompiler() {
const m = await createRevive();
m.solc = solc;
// Set input data for stdin
m.setStdinData(JSON.stringify(compilerStandardJsonInput));
var stdoutString = "";
m.setStdoutCallback(function(char) {
if (char.charCodeAt(0) === '\n') {
console.log("new line")
exit
}
stdoutString += char;
});
var stderrString = "";
m.setStderrCallback(function(char) {
stderrString += char;
});
// Compile the Solidity source code
let x = m.callMain(['--standard-json']);
console.log("Stdout: " + stdoutString)
console.error("Stderr: " + stderrString)
}
runCompiler().catch(err => {
console.error('Error:', err);
});
+32
View File
@@ -0,0 +1,32 @@
import { parentPort } from 'worker_threads';
parentPort.on('message', async (inputJson) => {
const { default: createRevive } = await import(new URL('./resolc.js', import.meta.url));
const revive = await createRevive();
revive.setStdinData(inputJson);
let stdoutString = "";
revive.setStdoutCallback(function(char) {
if (char.charCodeAt(0) === '\n') {
console.log("new line")
exit
}
stdoutString += char;
});
let stderrString = "";
revive.setStderrCallback(function(char) {
stderrString += char;
});
// Call main on the new instance
const output = revive.callMain(['--recursive-process']);
if (stderrString.length > 0) {
// If /err is not empty, throw an error with its content
throw new Error(stderrString);
} else {
parentPort.postMessage({ output: stdoutString });
}
});
+5 -2
View File
@@ -2,9 +2,12 @@
"name": "root", "name": "root",
"private": true, "private": true,
"scripts": { "scripts": {
"test:cli": "npm run test -w crates/solidity/src/tests/cli-tests" "test:cli": "npm run test -w crates/solidity/src/tests/cli-tests",
"build:revive": "npm run build -w js",
"test:revive": "npm run test -w js"
}, },
"workspaces": [ "workspaces": [
"crates/solidity/src/tests/cli-tests" "crates/solidity/src/tests/cli-tests",
"js"
] ]
} }