Initial wasm support

This commit is contained in:
Sebastian Miasojed
2024-08-29 17:28:31 +02:00
parent 41c8d4e955
commit 5ac67bdc0d
69 changed files with 2277 additions and 8937 deletions
+2 -2
View File
@@ -8,8 +8,8 @@ use std::path::Path;
use serde::Deserialize;
use serde::Serialize;
use crate::solc::combined_json::contract::Contract as CombinedJsonContract;
use crate::solc::standard_json::output::contract::Contract as StandardJsonOutputContract;
use crate::compiler::combined_json::contract::Contract as CombinedJsonContract;
use crate::compiler::standard_json::output::contract::Contract as StandardJsonOutputContract;
/// The Solidity contract build.
#[derive(Debug, Serialize, Deserialize)]
+3 -3
View File
@@ -5,9 +5,9 @@ pub mod contract;
use std::collections::BTreeMap;
use std::path::Path;
use crate::solc::combined_json::CombinedJson;
use crate::solc::standard_json::output::Output as StandardJsonOutput;
use crate::solc::version::Version as SolcVersion;
use crate::compiler::combined_json::CombinedJson;
use crate::compiler::standard_json::output::Output as StandardJsonOutput;
use crate::compiler::version::Version as SolcVersion;
use self::contract::Contract;
+1 -1
View File
@@ -9,9 +9,9 @@ use std::collections::HashSet;
use serde::Deserialize;
use serde::Serialize;
use crate::compiler::standard_json::output::contract::evm::extra_metadata::ExtraMetadata;
use crate::evmla::ethereal_ir::entry_link::EntryLink;
use crate::evmla::ethereal_ir::EtherealIR;
use crate::solc::standard_json::output::contract::evm::extra_metadata::ExtraMetadata;
use self::data::Data;
use self::instruction::name::Name as InstructionName;
@@ -20,13 +20,13 @@ use num::One;
use num::ToPrimitive;
use num::Zero;
use crate::compiler::standard_json::output::contract::evm::extra_metadata::recursive_function::RecursiveFunction;
use crate::compiler::standard_json::output::contract::evm::extra_metadata::ExtraMetadata;
use crate::evmla::assembly::instruction::name::Name as InstructionName;
use crate::evmla::assembly::instruction::Instruction;
use crate::evmla::ethereal_ir::function::block::element::stack::element::Element;
use crate::evmla::ethereal_ir::function::block::element::stack::Stack;
use crate::evmla::ethereal_ir::EtherealIR;
use crate::solc::standard_json::output::contract::evm::extra_metadata::recursive_function::RecursiveFunction;
use crate::solc::standard_json::output::contract::evm::extra_metadata::ExtraMetadata;
use self::block::element::stack::element::Element as StackElement;
use self::block::element::Element as BlockElement;
+1 -1
View File
@@ -7,8 +7,8 @@ use std::collections::BTreeMap;
use std::collections::BTreeSet;
use std::collections::HashMap;
use crate::compiler::standard_json::output::contract::evm::extra_metadata::ExtraMetadata;
use crate::evmla::assembly::instruction::Instruction;
use crate::solc::standard_json::output::contract::evm::extra_metadata::ExtraMetadata;
use self::function::block::Block;
use self::function::r#type::Type as FunctionType;
+42 -32
View File
@@ -1,45 +1,55 @@
//! Solidity to PolkaVM compiler library.
pub(crate) mod build;
#[path = "solc/compiler.rs"]
pub(crate) mod compiler;
pub(crate) mod r#const;
pub(crate) mod evmla;
pub(crate) mod missing_libraries;
pub(crate) mod process;
pub(crate) mod project;
pub(crate) mod solc;
pub(crate) mod warning;
pub(crate) mod yul;
pub use self::build::contract::Contract as ContractBuild;
pub use self::build::Build;
pub use self::compiler::combined_json::contract::Contract as SolcCombinedJsonContract;
pub use self::compiler::combined_json::CombinedJson as SolcCombinedJson;
pub use self::compiler::pipeline::Pipeline as SolcPipeline;
#[cfg(not(target_os = "emscripten"))]
pub use self::compiler::solc::SolcCompiler;
#[cfg(target_os = "emscripten")]
pub use self::compiler::soljson::SoljsonCompiler;
pub use self::compiler::standard_json::input::language::Language as SolcStandardJsonInputLanguage;
pub use self::compiler::standard_json::input::settings::metadata::Metadata as SolcStandardJsonInputSettingsMetadata;
pub use self::compiler::standard_json::input::settings::optimizer::Optimizer as SolcStandardJsonInputSettingsOptimizer;
pub use self::compiler::standard_json::input::settings::selection::file::flag::Flag as SolcStandardJsonInputSettingsSelectionFileFlag;
pub use self::compiler::standard_json::input::settings::selection::file::File as SolcStandardJsonInputSettingsSelectionFile;
pub use self::compiler::standard_json::input::settings::selection::Selection as SolcStandardJsonInputSettingsSelection;
pub use self::compiler::standard_json::input::settings::Settings as SolcStandardJsonInputSettings;
pub use self::compiler::standard_json::input::source::Source as SolcStandardJsonInputSource;
pub use self::compiler::standard_json::input::Input as SolcStandardJsonInput;
pub use self::compiler::standard_json::output::contract::evm::bytecode::Bytecode as SolcStandardJsonOutputContractEVMBytecode;
pub use self::compiler::standard_json::output::contract::evm::EVM as SolcStandardJsonOutputContractEVM;
pub use self::compiler::standard_json::output::contract::Contract as SolcStandardJsonOutputContract;
pub use self::compiler::standard_json::output::Output as SolcStandardJsonOutput;
pub use self::compiler::version::Version as SolcVersion;
pub use self::compiler::Compiler;
pub use self::missing_libraries::MissingLibraries;
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::run as run_process;
pub use self::process::EXECUTABLE;
#[cfg(target_os = "emscripten")]
pub use self::process::worker_process::WorkerProcess;
pub use self::process::Process;
pub use self::project::contract::Contract as ProjectContract;
pub use self::project::Project;
pub use self::r#const::*;
pub use self::solc::combined_json::contract::Contract as SolcCombinedJsonContract;
pub use self::solc::combined_json::CombinedJson as SolcCombinedJson;
pub use self::solc::pipeline::Pipeline as SolcPipeline;
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::optimizer::Optimizer as SolcStandardJsonInputSettingsOptimizer;
pub use self::solc::standard_json::input::settings::selection::file::flag::Flag as SolcStandardJsonInputSettingsSelectionFileFlag;
pub use self::solc::standard_json::input::settings::selection::file::File as SolcStandardJsonInputSettingsSelectionFile;
pub use self::solc::standard_json::input::settings::selection::Selection as SolcStandardJsonInputSettingsSelection;
pub use self::solc::standard_json::input::settings::Settings as SolcStandardJsonInputSettings;
pub use self::solc::standard_json::input::source::Source as SolcStandardJsonInputSource;
pub use self::solc::standard_json::input::Input as SolcStandardJsonInput;
pub use self::solc::standard_json::output::contract::evm::bytecode::Bytecode as SolcStandardJsonOutputContractEVMBytecode;
pub use self::solc::standard_json::output::contract::evm::EVM as SolcStandardJsonOutputContractEVM;
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::version::Version as SolcVersion;
pub use self::solc::Compiler as SolcCompiler;
pub use self::warning::Warning;
#[cfg(target_os = "emscripten")]
pub mod libsolc;
#[cfg(not(target_os = "emscripten"))]
pub mod test_utils;
pub mod tests;
@@ -47,9 +57,9 @@ use std::collections::BTreeSet;
use std::path::PathBuf;
/// Runs the Yul mode.
pub fn yul(
pub fn yul<T: Compiler>(
input_files: &[PathBuf],
solc: &mut SolcCompiler,
solc: &mut T,
optimizer_settings: revive_llvm_context::OptimizerSettings,
is_system_mode: bool,
include_metadata_hash: bool,
@@ -67,10 +77,10 @@ pub fn yul(
let solc_validator = if is_system_mode {
None
} else {
if solc.version()?.default != SolcCompiler::LAST_SUPPORTED_VERSION {
if solc.version()?.default != compiler::LAST_SUPPORTED_VERSION {
anyhow::bail!(
"The Yul mode is only supported with the most recent version of the Solidity compiler: {}",
SolcCompiler::LAST_SUPPORTED_VERSION,
compiler::LAST_SUPPORTED_VERSION,
);
}
@@ -122,10 +132,10 @@ pub fn llvm_ir(
/// Runs the standard output mode.
#[allow(clippy::too_many_arguments)]
pub fn standard_output(
pub fn standard_output<T: Compiler>(
input_files: &[PathBuf],
libraries: Vec<String>,
solc: &mut SolcCompiler,
solc: &mut T,
evm_version: Option<revive_common::EVMVersion>,
solc_optimizer_enabled: bool,
optimizer_settings: revive_llvm_context::OptimizerSettings,
@@ -213,8 +223,8 @@ pub fn standard_output(
/// Runs the standard JSON mode.
#[allow(clippy::too_many_arguments)]
pub fn standard_json(
solc: &mut SolcCompiler,
pub fn standard_json<T: Compiler>(
solc: &mut T,
detect_missing_libraries: bool,
force_evmla: bool,
is_system_mode: bool,
@@ -293,11 +303,11 @@ pub fn standard_json(
/// Runs the combined JSON mode.
#[allow(clippy::too_many_arguments)]
pub fn combined_json(
pub fn combined_json<T: Compiler>(
format: String,
input_files: &[PathBuf],
libraries: Vec<String>,
solc: &mut SolcCompiler,
solc: &mut T,
evm_version: Option<revive_common::EVMVersion>,
solc_optimizer_enabled: bool,
optimizer_settings: revive_llvm_context::OptimizerSettings,
+62
View File
@@ -0,0 +1,62 @@
//! The Solidity compiler library.
use crate::Compiler;
use crate::SoljsonCompiler;
use libc::{c_char, size_t};
use std::ffi::{CStr, CString};
use std::ptr;
static mut VERSION: Option<Box<CString>> = None;
#[no_mangle]
pub extern "C" fn solidity_license() -> *const c_char {
let license = "This is the Revive license.";
CString::new(license).unwrap().into_raw()
}
#[no_mangle]
pub extern "C" fn solidity_version() -> *const c_char {
let mut solc = SoljsonCompiler { version: None };
let version = solc.version().map(|v| v.long).unwrap_or("unknown".to_owned());
// Store the string in a static variable
unsafe {
if VERSION.is_none() {
VERSION = Some(Box::new(CString::new(version).unwrap()));
}
VERSION.as_ref().map_or_else(std::ptr::null, |s| s.as_ptr())
}
}
#[no_mangle]
pub extern "C" fn solidity_alloc(size: size_t) -> *mut c_char {
let buffer = vec![0u8; size].into_boxed_slice();
Box::into_raw(buffer) as *mut c_char
}
#[no_mangle]
pub extern "C" fn solidity_free(data: *mut c_char) {
if !data.is_null() {
unsafe {
CString::from_raw(data);
}
}
}
#[no_mangle]
pub extern "C" fn solidity_compile(
input: *const c_char,
_readCallback: Option<extern "C" fn(*mut libc::c_void, *const c_char, *const c_char, *mut *mut c_char, *mut *mut c_char)>,
_readContext: *mut libc::c_void,
) -> *mut c_char {
let input = unsafe { CStr::from_ptr(input).to_str().unwrap_or("") };
// Mock compilation process
let output = format!("Compiled output for: {}", input);
CString::new(output).unwrap().into_raw()
}
#[no_mangle]
pub extern "C" fn solidity_reset() {
}
+2 -2
View File
@@ -3,8 +3,8 @@
use std::collections::BTreeMap;
use std::collections::HashSet;
use crate::solc::standard_json::output::Output as StandardJsonOutput;
use crate::solc::version::Version as SolcVersion;
use crate::compiler::standard_json::output::Output as StandardJsonOutput;
use crate::compiler::version::Version as SolcVersion;
/// The missing Solidity libraries.
pub struct MissingLibraries {
+7 -120
View File
@@ -1,129 +1,16 @@
//! Process for compiling a single compilation unit.
pub mod input;
#[cfg(not(target_os = "emscripten"))]
pub mod native_process;
pub mod output;
use std::io::Read;
use std::io::Write;
use std::path::PathBuf;
use std::process::Command;
use once_cell::sync::OnceCell;
#[cfg(target_os = "emscripten")]
pub mod worker_process;
use self::input::Input;
use self::output::Output;
/// The overriden executable name used when the compiler is run as a library.
pub static EXECUTABLE: OnceCell<PathBuf> = OnceCell::new();
/// Read input from `stdin`, compile a contract, and write the output to `stdout`.
pub 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();
let mut buffer = Vec::with_capacity(16384);
match input_file {
Some(ins) => {
if let Err(error) = ins.read_to_end(&mut buffer) {
anyhow::bail!("Failed to read recursive process input file: {:?}", 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())?;
if input.enable_test_encoding {
todo!()
}
let result = input.contract.compile(
input.project,
input.optimizer_settings,
input.is_system_mode,
input.include_metadata_hash,
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 {
let _ = 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(),
);
}
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)
pub trait Process {
fn run() -> anyhow::Result<()>;
fn call(input: Input) -> anyhow::Result<Output>;
}
@@ -0,0 +1,106 @@
//! Process for compiling a single compilation unit.
use std::io::Read;
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 {
/// Read input from `stdin`, compile a contract, and write the output to `stdout`.
fn run() -> anyhow::Result<()> {
let mut stdin = std::io::stdin();
let mut stdout = std::io::stdout();
let mut stderr = std::io::stderr();
let mut buffer = Vec::with_capacity(16384);
stdin.read_to_end(&mut buffer).expect("Stdin reading error");
let input: Input = revive_common::deserialize_from_slice(buffer.as_slice())?;
if input.enable_test_encoding {
todo!()
}
let result = input.contract.compile(
input.project,
input.optimizer_settings,
input.is_system_mode,
input.include_metadata_hash,
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.
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)
})?;
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,117 @@
//! Process for compiling a single compilation unit using Web Workers.
use std::ffi::{c_char, c_void, CStr, CString};
use std::fs::File;
use std::io::Read;
use std::io::Write;
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 {
/// Read input from `stdin`, compile a contract, and write the output to `stdout`.
fn run() -> anyhow::Result<()> {
let mut buffer = Vec::with_capacity(16384);
// TODO: Init correctly stdin in emscripten - preload FS conf before module init
let mut stdin = File::open("/in")
.map_err(|error| anyhow::anyhow!("File /in openning error: {}", error))?;
let mut stdout = File::create("/out")
.map_err(|error| anyhow::anyhow!("File /out creating error: {}", error))?;
let mut stderr = File::create("/err")
.map_err(|error| anyhow::anyhow!("File /err creating error: {}", error))?;
stdin.read_to_end(&mut buffer).expect("Stdin reading error");
let input: Input = revive_common::deserialize_from_slice(buffer.as_slice())?;
if input.enable_test_encoding {
todo!()
}
let result = input.contract.compile(
input.project,
input.optimizer_settings,
input.is_system_mode,
input.include_metadata_hash,
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.
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;
}
@@ -5,8 +5,8 @@ use std::collections::HashSet;
use serde::Deserialize;
use serde::Serialize;
use crate::compiler::standard_json::output::contract::evm::extra_metadata::ExtraMetadata;
use crate::evmla::assembly::Assembly;
use crate::solc::standard_json::output::contract::evm::extra_metadata::ExtraMetadata;
/// The contract EVM legacy assembly source code.
#[derive(Debug, Serialize, Deserialize, Clone)]
@@ -9,8 +9,8 @@ use std::collections::HashSet;
use serde::Deserialize;
use serde::Serialize;
use crate::compiler::standard_json::output::contract::evm::extra_metadata::ExtraMetadata;
use crate::evmla::assembly::Assembly;
use crate::solc::standard_json::output::contract::evm::extra_metadata::ExtraMetadata;
use crate::yul::parser::statement::object::Object;
use self::evmla::EVMLA;
+1 -1
View File
@@ -12,8 +12,8 @@ use sha3::Digest;
use revive_llvm_context::PolkaVMWriteLLVM;
use crate::build::contract::Contract as ContractBuild;
use crate::compiler::version::Version as SolcVersion;
use crate::project::Project;
use crate::solc::version::Version as SolcVersion;
use self::ir::IR;
use self::metadata::Metadata;
+29 -15
View File
@@ -7,19 +7,21 @@ use std::collections::HashMap;
use std::collections::HashSet;
use std::path::Path;
use rayon::iter::IntoParallelIterator;
use rayon::iter::ParallelIterator;
#[cfg(feature = "parallel")]
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use serde::Deserialize;
use serde::Serialize;
use sha3::Digest;
use crate::build::contract::Contract as ContractBuild;
use crate::build::Build;
use crate::compiler;
use crate::compiler::version::Version as SolcVersion;
use crate::compiler::Compiler;
use crate::missing_libraries::MissingLibraries;
use crate::process::input::Input as ProcessInput;
use crate::process::Process;
use crate::project::contract::ir::IR;
use crate::solc::version::Version as SolcVersion;
use crate::solc::Compiler as SolcCompiler;
use crate::yul::lexer::Lexer;
use crate::yul::parser::statement::object::Object;
@@ -68,11 +70,14 @@ impl Project {
debug_config: Option<revive_llvm_context::DebugConfig>,
) -> anyhow::Result<Build> {
let project = self.clone();
let results: BTreeMap<String, anyhow::Result<ContractBuild>> = self
.contracts
.into_par_iter()
#[cfg(feature = "parallel")]
let iter = self.contracts.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)| {
let process_output = crate::process::call(ProcessInput::new(
let process_input = ProcessInput::new(
contract,
project.clone(),
is_system_mode,
@@ -80,8 +85,17 @@ impl Project {
bytecode_encoding_testing,
optimizer_settings.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))
})
.collect();
@@ -159,9 +173,9 @@ impl Project {
}
/// 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,
solc_validator: Option<&SolcCompiler>,
solc_validator: Option<&T>,
) -> anyhow::Result<Self> {
let source_code = std::fs::read_to_string(path)
.map_err(|error| anyhow::anyhow!("Yul file {:?} reading error: {}", path, error))?;
@@ -170,16 +184,16 @@ impl Project {
/// Parses the test Yul source code string and returns the source data.
/// Only for integration testing purposes.
pub fn try_from_yul_string(
pub fn try_from_yul_string<T: Compiler>(
path: &Path,
source_code: &str,
solc_validator: Option<&SolcCompiler>,
solc_validator: Option<&T>,
) -> anyhow::Result<Self> {
if let Some(solc) = solc_validator {
solc.validate_yul(path)?;
}
let source_version = SolcVersion::new_simple(SolcCompiler::LAST_SUPPORTED_VERSION);
let source_version = SolcVersion::new_simple(compiler::LAST_SUPPORTED_VERSION);
let path = path.to_string_lossy().to_string();
let source_hash = sha3::Keccak256::digest(source_code.as_bytes()).into();
+6 -30
View File
@@ -4,25 +4,21 @@ use std::collections::BTreeSet;
use std::path::Path;
use std::path::PathBuf;
use clap::Parser;
use path_slash::PathExt;
use structopt::StructOpt;
/// Compiles the provided Solidity input files (or use the standard input if no files
/// are given or "-" is specified as a file name). Outputs the components based on the
/// chosen options, either to the standard output or to files within the designated
/// output directory.
/// Example: resolc ERC20.sol -O3 --bin --output-dir './build/'
#[derive(Debug, Parser)]
#[derive(Debug, StructOpt)]
#[structopt(name = "The PolkaVM Solidity compiler")]
pub struct Arguments {
/// Print the version and exit.
#[structopt(long = "version")]
pub version: bool,
/// Print the licence and exit.
#[structopt(long = "license")]
pub license: bool,
/// Specify the input paths and remappings.
/// If an argument contains a '=', it is considered a remapping.
/// Multiple Solidity files can be passed in the default Solidity mode.
@@ -46,7 +42,7 @@ pub struct Arguments {
pub allow_paths: Option<String>,
/// Create one file per component and contract/file at the specified directory, if given.
#[structopt(short = 'o', long = "output-dir")]
#[structopt(short = "o", long = "output-dir")]
pub output_directory: Option<PathBuf>,
/// Overwrite existing files (used together with -o).
@@ -55,7 +51,7 @@ pub struct Arguments {
/// Set the optimization parameter -O[0 | 1 | 2 | 3 | s | z].
/// Use `3` for best performance and `z` for minimal size.
#[structopt(short = 'O', long = "optimization")]
#[structopt(short = "O", long = "optimization")]
pub optimization: Option<char>,
/// Try to recompile with -Oz if the bytecode is too large.
@@ -85,7 +81,7 @@ pub struct Arguments {
/// Specify addresses of deployable libraries. Syntax: `<libraryName>=<address> [, or whitespace] ...`.
/// Addresses are interpreted as hexadecimal strings prefixed with `0x`.
#[structopt(short = 'l', long = "libraries")]
#[structopt(short = "l", long = "libraries")]
pub libraries: Vec<String>,
/// Output a single JSON document containing the specified information.
@@ -168,12 +164,6 @@ pub struct Arguments {
/// Only for usage from within the compiler.
#[structopt(long = "recursive-process")]
pub recursive_process: bool,
/// Specify the input file to use instead of stdin when --recursive-process is given.
/// This is only intended for use when developing the compiler.
#[cfg(debug_assertions)]
#[structopt(long = "recursive-process-input")]
pub recursive_process_input: Option<String>,
}
impl Default for Arguments {
@@ -185,7 +175,7 @@ impl Default for Arguments {
impl Arguments {
/// A shortcut constructor.
pub fn new() -> Self {
Self::parse()
Self::from_args()
}
/// Validates the arguments.
@@ -195,20 +185,6 @@ impl Arguments {
anyhow::bail!("No other options are allowed while getting the compiler version.");
}
#[cfg(debug_assertions)]
if self.recursive_process_input != None && !self.recursive_process {
anyhow::bail!("--process-input can be only used when --recursive-process is given");
}
#[cfg(debug_assertions)]
if self.recursive_process
&& ((self.recursive_process_input == None && std::env::args().count() > 2)
|| (self.recursive_process_input != None && std::env::args().count() > 4))
{
anyhow::bail!("No other options are allowed in recursive mode.");
}
#[cfg(not(debug_assertions))]
if self.recursive_process && std::env::args().count() > 2 {
anyhow::bail!("No other options are allowed in recursive mode.");
}
+24 -19
View File
@@ -4,8 +4,11 @@ pub mod arguments;
use std::str::FromStr;
use revive_solidity::Process;
use self::arguments::Arguments;
#[cfg(feature = "parallel")]
/// The rayon worker stack size.
const RAYON_WORKER_STACK_SIZE: usize = 16 * 1024 * 1024;
@@ -39,14 +42,7 @@ fn main_inner() -> anyhow::Result<()> {
return Ok(());
}
if arguments.license {
let license_mit = include_str!("../../../../LICENSE-MIT");
let license_apache = include_str!("../../../../LICENSE-APACHE");
println!("{}\n{}\n", license_mit, license_apache);
return Ok(());
}
#[cfg(feature = "parallel")]
rayon::ThreadPoolBuilder::new()
.stack_size(RAYON_WORKER_STACK_SIZE)
.build_global()
@@ -55,13 +51,14 @@ fn main_inner() -> anyhow::Result<()> {
revive_llvm_context::initialize_target(revive_llvm_context::Target::PVM); // TODO: pass from CLI
if arguments.recursive_process {
#[cfg(debug_assertions)]
if let Some(fname) = arguments.recursive_process_input {
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();
}
#[cfg(not(target_os = "emscripten"))]
{
return revive_solidity::NativeProcess::run();
}
return revive_solidity::run_process(None);
}
let debug_config = match arguments.debug_output_directory {
@@ -83,11 +80,19 @@ fn main_inner() -> anyhow::Result<()> {
None => None,
};
let mut solc = revive_solidity::SolcCompiler::new(
arguments
.solc
.unwrap_or_else(|| revive_solidity::SolcCompiler::DEFAULT_EXECUTABLE_NAME.to_owned()),
)?;
let mut solc = {
#[cfg(target_os = "emscripten")]
{
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 {
Some(evm_version) => Some(revive_common::EVMVersion::try_from(evm_version.as_str())?),
+57
View File
@@ -0,0 +1,57 @@
//! The Solidity compiler.
pub mod combined_json;
pub mod pipeline;
#[cfg(not(target_os = "emscripten"))]
pub mod solc;
#[cfg(target_os = "emscripten")]
pub mod soljson;
pub mod standard_json;
pub mod version;
use std::path::Path;
use std::path::PathBuf;
use self::combined_json::CombinedJson;
use self::pipeline::Pipeline;
use self::standard_json::input::Input as StandardJsonInput;
use self::standard_json::output::Output as StandardJsonOutput;
use self::version::Version;
/// The first version of `solc` with the support of standard JSON interface.
const FIRST_SUPPORTED_VERSION: semver::Version = semver::Version::new(0, 4, 12);
/// The first version of `solc`, where Yul codegen is considered robust enough.
const FIRST_YUL_VERSION: semver::Version = semver::Version::new(0, 8, 0);
/// The first version of `solc`, where `--via-ir` codegen mode is supported.
const FIRST_VIA_IR_VERSION: semver::Version = semver::Version::new(0, 8, 13);
/// The last supported version of `solc`.
pub(crate) const LAST_SUPPORTED_VERSION: semver::Version = semver::Version::new(0, 8, 26);
/// The Solidity compiler.
pub trait Compiler {
/// Compiles the Solidity `--standard-json` input into Yul IR.
fn standard_json(
&mut self,
input: StandardJsonInput,
pipeline: Pipeline,
base_path: Option<String>,
include_paths: Vec<String>,
allow_paths: Option<String>,
) -> anyhow::Result<StandardJsonOutput>;
/// The `solc --combined-json abi,hashes...` mirror.
fn combined_json(
&self,
paths: &[PathBuf],
combined_json_argument: &str,
) -> anyhow::Result<CombinedJson>;
/// The `solc` Yul validator.
fn validate_yul(&self, path: &Path) -> anyhow::Result<()>;
/// The `solc --version` mini-parser.
fn version(&mut self) -> anyhow::Result<Version>;
}
+3 -3
View File
@@ -1,7 +1,7 @@
//! The Solidity compiler pipeline type.
use crate::solc::version::Version as SolcVersion;
use crate::solc::Compiler as SolcCompiler;
use crate::compiler;
use crate::compiler::version::Version as SolcVersion;
/// The Solidity compiler pipeline type.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -17,7 +17,7 @@ pub enum Pipeline {
impl Pipeline {
/// 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 {
if solc_version.default < SolcCompiler::FIRST_YUL_VERSION || force_evmla {
if solc_version.default < compiler::FIRST_YUL_VERSION || force_evmla {
Self::EVMLA
} else {
Self::Yul
+287
View File
@@ -0,0 +1,287 @@
//! The Solidity compiler.
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use crate::compiler::combined_json::CombinedJson;
use crate::compiler::pipeline::Pipeline;
use crate::compiler::standard_json::input::Input as StandardJsonInput;
use crate::compiler::standard_json::output::Output as StandardJsonOutput;
use crate::compiler::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())?;
output.remove_evm();
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)
}
}
+132
View File
@@ -0,0 +1,132 @@
//! The Solidity compiler.
use std::path::Path;
use std::path::PathBuf;
use crate::compiler::combined_json::CombinedJson;
use crate::compiler::pipeline::Pipeline;
use crate::compiler::standard_json::input::Input as StandardJsonInput;
use crate::compiler::standard_json::output::Output as StandardJsonOutput;
use crate::compiler::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;
}
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?)
}
}
pub 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?)
}
}
/// 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 = 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())?;
output.remove_evm();
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 = 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)
}
}
@@ -8,15 +8,15 @@ use std::collections::BTreeMap;
use std::collections::BTreeSet;
use std::path::PathBuf;
use rayon::iter::IntoParallelIterator;
use rayon::iter::ParallelIterator;
#[cfg(feature = "parallel")]
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use serde::Deserialize;
use serde::Serialize;
use crate::solc::pipeline::Pipeline as SolcPipeline;
use crate::solc::standard_json::input::settings::metadata::Metadata as SolcStandardJsonInputSettingsMetadata;
use crate::solc::standard_json::input::settings::optimizer::Optimizer as SolcStandardJsonInputSettingsOptimizer;
use crate::solc::standard_json::input::settings::selection::Selection as SolcStandardJsonInputSettingsSelection;
use crate::compiler::pipeline::Pipeline as SolcPipeline;
use crate::compiler::standard_json::input::settings::metadata::Metadata as SolcStandardJsonInputSettingsMetadata;
use crate::compiler::standard_json::input::settings::optimizer::Optimizer as SolcStandardJsonInputSettingsOptimizer;
use crate::compiler::standard_json::input::settings::selection::Selection as SolcStandardJsonInputSettingsSelection;
use crate::warning::Warning;
use self::language::Language;
@@ -64,8 +64,12 @@ impl Input {
via_ir: bool,
suppressed_warnings: Option<Vec<Warning>>,
) -> anyhow::Result<Self> {
let sources = paths
.into_par_iter()
#[cfg(feature = "parallel")]
let iter = paths.into_par_iter(); // Parallel iterator
#[cfg(not(feature = "parallel"))]
let iter = paths.iter(); // Sequential iterator
let sources = iter
.map(|path| {
let source = Source::try_from(path.as_path()).unwrap_or_else(|error| {
panic!("Source code file {path:?} reading error: {error}")
@@ -106,8 +110,12 @@ impl Input {
via_ir: bool,
suppressed_warnings: Option<Vec<Warning>>,
) -> anyhow::Result<Self> {
let sources = sources
.into_par_iter()
#[cfg(feature = "parallel")]
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)))
.collect();
@@ -3,7 +3,7 @@
use serde::Deserialize;
use serde::Serialize;
use crate::solc::pipeline::Pipeline as SolcPipeline;
use crate::compiler::pipeline::Pipeline as SolcPipeline;
/// The `solc --standard-json` expected output selection flag.
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
@@ -7,7 +7,7 @@ use std::collections::HashSet;
use serde::Deserialize;
use serde::Serialize;
use crate::solc::pipeline::Pipeline as SolcPipeline;
use crate::compiler::pipeline::Pipeline as SolcPipeline;
use self::flag::Flag as SelectionFlag;
@@ -5,7 +5,7 @@ pub mod file;
use serde::Deserialize;
use serde::Serialize;
use crate::solc::pipeline::Pipeline as SolcPipeline;
use crate::compiler::pipeline::Pipeline as SolcPipeline;
use self::file::File as FileSelection;
@@ -10,13 +10,13 @@ use serde::Deserialize;
use serde::Serialize;
use sha3::Digest;
use crate::compiler::pipeline::Pipeline as SolcPipeline;
use crate::compiler::version::Version as SolcVersion;
use crate::evmla::assembly::instruction::Instruction;
use crate::evmla::assembly::Assembly;
use crate::project::contract::ir::IR as ProjectContractIR;
use crate::project::contract::Contract as ProjectContract;
use crate::project::Project;
use crate::solc::pipeline::Pipeline as SolcPipeline;
use crate::solc::version::Version as SolcVersion;
use crate::warning::Warning;
use crate::yul::lexer::Lexer;
use crate::yul::parser::statement::object::Object;
@@ -3,9 +3,9 @@
use serde::Deserialize;
use serde::Serialize;
use crate::solc::pipeline::Pipeline as SolcPipeline;
use crate::solc::standard_json::output::error::Error as SolcStandardJsonOutputError;
use crate::solc::version::Version as SolcVersion;
use crate::compiler::pipeline::Pipeline as SolcPipeline;
use crate::compiler::standard_json::output::error::Error as SolcStandardJsonOutputError;
use crate::compiler::version::Version as SolcVersion;
use crate::warning::Warning;
/// The `solc --standard-json` output source.
+19 -114
View File
@@ -1,33 +1,20 @@
//! Common utility used for in frontend and integration tests.
use std::collections::BTreeMap;
use std::collections::BTreeSet;
use std::collections::HashMap;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Mutex;
use once_cell::sync::Lazy;
use crate::compiler::pipeline::Pipeline as SolcPipeline;
use crate::compiler::solc::SolcCompiler;
use crate::compiler::standard_json::input::settings::optimizer::Optimizer as SolcStandardJsonInputSettingsOptimizer;
use crate::compiler::standard_json::input::settings::selection::Selection as SolcStandardJsonInputSettingsSelection;
use crate::compiler::standard_json::input::Input as SolcStandardJsonInput;
use crate::compiler::standard_json::output::contract::evm::bytecode::DeployedBytecode;
use crate::compiler::standard_json::output::Output as SolcStandardJsonOutput;
use crate::compiler::Compiler;
use crate::project::Project;
use crate::solc::pipeline::Pipeline as SolcPipeline;
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::Input as SolcStandardJsonInput;
use crate::solc::standard_json::output::contract::evm::bytecode::DeployedBytecode;
use crate::solc::standard_json::output::Output as SolcStandardJsonOutput;
use crate::solc::Compiler as SolcCompiler;
use crate::warning::Warning;
static PVM_BLOB_CACHE: Lazy<Mutex<HashMap<CachedBlob, Vec<u8>>>> = Lazy::new(Default::default);
static EVM_BLOB_CACHE: Lazy<Mutex<HashMap<CachedBlob, Vec<u8>>>> = Lazy::new(Default::default);
#[derive(Hash, PartialEq, Eq)]
struct CachedBlob {
contract_name: String,
solc_optimizer_enabled: bool,
pipeline: SolcPipeline,
}
/// Checks if the required executables are present in `${PATH}`.
fn check_dependencies() {
for executable in [
@@ -76,7 +63,8 @@ pub fn build_solidity_with_options(
inkwell::support::enable_llvm_pretty_stack_trace();
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 solc_version = solc.version()?;
@@ -125,7 +113,8 @@ pub fn build_solidity_with_options_evm(
inkwell::support::enable_llvm_pretty_stack_trace();
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 solc_version = solc.version()?;
@@ -176,7 +165,8 @@ pub fn build_solidity_and_detect_missing_libraries(
inkwell::support::enable_llvm_pretty_stack_trace();
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 solc_version = solc.version()?;
@@ -221,8 +211,11 @@ pub fn build_yul(source_code: &str) -> anyhow::Result<()> {
revive_llvm_context::initialize_target(revive_llvm_context::Target::PVM);
let optimizer_settings = revive_llvm_context::OptimizerSettings::none();
let project =
Project::try_from_yul_string(PathBuf::from("test.yul").as_path(), source_code, None)?;
let project = Project::try_from_yul_string::<SolcCompiler>(
PathBuf::from("test.yul").as_path(),
source_code,
None,
)?;
let _build = project.compile(optimizer_settings, false, false, false, None)?;
Ok(())
@@ -274,91 +267,3 @@ pub fn check_solidity_warning(
Ok(contains_warning)
}
/// Compile the blob of `contract_name` found in given `source_code`.
/// The `solc` optimizer will be enabled
pub fn compile_blob(contract_name: &str, source_code: &str) -> Vec<u8> {
compile_blob_with_options(contract_name, source_code, true, SolcPipeline::Yul)
}
/// Compile the EVM bin-runtime of `contract_name` found in given `source_code`.
/// The `solc` optimizer will be enabled
pub fn compile_evm_bin_runtime(contract_name: &str, source_code: &str) -> Vec<u8> {
let pipeline = SolcPipeline::Yul;
let solc_optimizer_enabled = true;
let id = CachedBlob {
contract_name: contract_name.to_owned(),
pipeline,
solc_optimizer_enabled,
};
if let Some(blob) = EVM_BLOB_CACHE.lock().unwrap().get(&id) {
return blob.clone();
}
let file_name = "contract.sol";
let contracts = build_solidity_with_options_evm(
[(file_name.into(), source_code.into())].into(),
Default::default(),
None,
pipeline,
solc_optimizer_enabled,
)
.expect("source should compile");
let bin_runtime = &contracts
.get(contract_name)
.unwrap_or_else(|| panic!("contract '{}' didn't produce bin-runtime", contract_name))
.object;
let blob = hex::decode(bin_runtime).expect("bin-runtime shold be hex encoded");
EVM_BLOB_CACHE.lock().unwrap().insert(id, blob.clone());
blob
}
/// Compile the blob of `contract_name` found in given `source_code`.
pub fn compile_blob_with_options(
contract_name: &str,
source_code: &str,
solc_optimizer_enabled: bool,
pipeline: SolcPipeline,
) -> Vec<u8> {
let id = CachedBlob {
contract_name: contract_name.to_owned(),
solc_optimizer_enabled,
pipeline,
};
if let Some(blob) = PVM_BLOB_CACHE.lock().unwrap().get(&id) {
return blob.clone();
}
let file_name = "contract.sol";
let contracts = build_solidity_with_options(
[(file_name.into(), source_code.into())].into(),
Default::default(),
None,
pipeline,
revive_llvm_context::OptimizerSettings::cycles(),
solc_optimizer_enabled,
)
.expect("source should compile")
.contracts
.expect("source should contain at least one contract");
let bytecode = contracts[file_name][contract_name]
.evm
.as_ref()
.expect("source should produce EVM output")
.bytecode
.as_ref()
.expect("source should produce assembly text")
.object
.as_str();
let blob = hex::decode(bytecode).expect("hex encoding should always be valid");
PVM_BLOB_CACHE.lock().unwrap().insert(id, blob.clone());
blob
}
@@ -4,7 +4,7 @@
use std::collections::BTreeMap;
use crate::solc::pipeline::Pipeline as SolcPipeline;
use crate::compiler::pipeline::Pipeline as SolcPipeline;
pub const MAIN_CODE: &str = r#"
// SPDX-License-Identifier: MIT
+1 -1
View File
@@ -5,7 +5,7 @@
use std::collections::BTreeMap;
use crate::solc::pipeline::Pipeline as SolcPipeline;
use crate::compiler::pipeline::Pipeline as SolcPipeline;
#[test]
fn yul() {
+1 -1
View File
@@ -4,7 +4,7 @@
use std::collections::BTreeMap;
use crate::solc::pipeline::Pipeline as SolcPipeline;
use crate::compiler::pipeline::Pipeline as SolcPipeline;
pub const LIBRARY_TEST_SOURCE: &str = r#"
// SPDX-License-Identifier: MIT
+1 -1
View File
@@ -4,7 +4,7 @@
use std::collections::BTreeMap;
use crate::solc::pipeline::Pipeline as SolcPipeline;
use crate::compiler::pipeline::Pipeline as SolcPipeline;
use crate::warning::Warning;
pub const ECRECOVER_TEST_SOURCE: &str = r#"
+1 -1
View File
@@ -4,7 +4,7 @@
use std::collections::BTreeMap;
use crate::solc::pipeline::Pipeline as SolcPipeline;
use crate::compiler::pipeline::Pipeline as SolcPipeline;
pub const SOURCE_CODE: &str = r#"
// SPDX-License-Identifier: MIT
+1 -1
View File
@@ -5,7 +5,7 @@
use std::collections::BTreeMap;
use std::collections::BTreeSet;
use crate::solc::pipeline::Pipeline as SolcPipeline;
use crate::compiler::pipeline::Pipeline as SolcPipeline;
pub const CALLEE_TEST_SOURCE: &str = r#"
// SPDX-License-Identifier: MIT
+1 -1
View File
@@ -4,7 +4,7 @@
use std::collections::BTreeMap;
use crate::solc::pipeline::Pipeline as SolcPipeline;
use crate::compiler::pipeline::Pipeline as SolcPipeline;
#[test]
#[should_panic(expected = "runtimeCode is not supported")]
@@ -4,7 +4,7 @@
use std::collections::BTreeMap;
use crate::solc::pipeline::Pipeline as SolcPipeline;
use crate::compiler::pipeline::Pipeline as SolcPipeline;
#[test]
#[should_panic(expected = "The `CODECOPY` instruction is not supported")]