Skip to content

Commit

Permalink
Merge pull request #111 from smiasojed/resolc.js
Browse files Browse the repository at this point in the history
Add compilation to NodeJS module
  • Loading branch information
smiasojed authored Nov 26, 2024
2 parents 01b5ed5 + 4c0c74f commit 1eb1083
Show file tree
Hide file tree
Showing 29 changed files with 1,319 additions and 440 deletions.
80 changes: 80 additions & 0 deletions .github/workflows/build-revive-wasm.yml
Original file line number Diff line number Diff line change
@@ -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 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@
/*.s
/llvm-project
/llvm18.0
/llvm18.0-emscripten
node_modules
artifacts
tmp
package-lock.json
/*.html
/js/src/resolc.*
/js/dist/
/build
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 22 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
.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-bin:
Expand All @@ -8,6 +22,11 @@ install-bin:
install-npm:
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
ifeq ($(origin REVIVE_INSTALL_DIR), undefined)
REVIVE_INSTALL_DIR=`pwd`/release/revive-debian
Expand Down Expand Up @@ -58,4 +77,6 @@ clean:
rm -rf node_modules ; \
rm -rf crates/solidity/src/tests/cli-tests/artifacts ; \
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}
31 changes: 29 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,41 @@ resolc --version

### 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

Please consult the [Makefile](Makefile) targets to learn how to run tests and benchmarks.
Ensure that your branch passes `make test` locally when submitting a pull request.

## 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
```
10 changes: 3 additions & 7 deletions build-llvm.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,13 @@ set -euo pipefail
INSTALL_DIR="${PWD}/llvm18.0"
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
LLVM_SRC_PREFIX=${PWD}/llvm-project
LLVM_SRC_DIR=${LLVM_SRC_PREFIX}/llvm
LLVM_BUILD_DIR=${PWD}/build/llvm

./clone-llvm.sh "${LLVM_SRC_PREFIX}"

if [ ! -d ${LLVM_BUILD_DIR} ] ; then
mkdir -p ${LLVM_BUILD_DIR}
fi
Expand Down
18 changes: 18 additions & 0 deletions clone-llvm.sh
Original file line number Diff line number Diff line change
@@ -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
84 changes: 76 additions & 8 deletions crates/lld-sys/build.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
fn llvm_config(arg: &str) -> String {
let output = std::process::Command::new("llvm-config")
use std::{
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])
.output()
.unwrap_or_else(|_| panic!("`llvm-config {arg}` failed"));
Expand All @@ -8,8 +22,11 @@ fn llvm_config(arg: &str) -> String {
.unwrap_or_else(|_| panic!("output of `llvm-config {arg}` should be utf8"))
}

fn set_rustc_link_flags() {
println!("cargo:rustc-link-search=native={}", llvm_config("--libdir"));
fn set_rustc_link_flags(llvm_config_path: &Path) {
println!(
"cargo:rustc-link-search=native={}",
llvm_config(llvm_config_path, "--libdir")
);

for lib in [
"lldELF",
Expand All @@ -22,27 +39,78 @@ fn set_rustc_link_flags() {
"LLVMTargetParser",
"LLVMBinaryFormat",
"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}");
}

#[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=tinfo");
}
}

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()
.fold(&mut cc::Build::new(), |builder, flag| builder.flag(flag))
.flag("-Wno-unused-parameter")
.cpp(true)
.file("src/linker.cpp")
.compile("liblinker.a");

set_rustc_link_flags();
set_rustc_link_flags(&llvm_config_path);

println!("cargo:rerun-if-changed=build.rs");
}
11 changes: 9 additions & 2 deletions crates/solidity/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ thiserror = { workspace = true }
anyhow = { workspace = true }
which = { workspace = true }
path-slash = { workspace = true }
rayon = { workspace = true }
rayon = { workspace = true, optional = true }

serde = { workspace = true }
serde_json = { workspace = true }
Expand All @@ -41,6 +41,13 @@ inkwell = { workspace = true }
revive-common = { workspace = true }
revive-llvm-context = { workspace = true }


[target.'cfg(target_env = "musl")'.dependencies]
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"]
Loading

0 comments on commit 1eb1083

Please sign in to comment.