resolc crate (#328)

- Factor the YUL crate out of `revive-solidity`.
- `revive-solidity` is in reality not a Solidity implementation but the
revive solidity compiler driver (`resolc`). By renaming we not only get
this straight but also a binary with the same name as the crate which
should be less confusing.

---------

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
This commit is contained in:
xermicus authored and GitHub committed 2025-05-27 09:48:43 +02:00
1 parent 090e3ac13c
commit bd4e108bb0
99 files changed
+599 -624

No files matched your search

+51
View File
@@ -0,0 +1,51 @@
//! Process for compiling a single compilation unit.
//! The input data.
use revive_solc_json_interface::SolcStandardJsonInputSettingsPolkaVMMemory;
use serde::Deserialize;
use serde::Serialize;
use crate::project::contract::Contract;
use crate::project::Project;
/// The input data.
#[derive(Debug, Serialize, Deserialize)]
pub struct Input {
/// The contract representation.
pub contract: Contract,
/// The project representation.
pub project: Project,
/// Whether to append the metadata hash.
pub include_metadata_hash: bool,
/// The optimizer settings.
pub optimizer_settings: revive_llvm_context::OptimizerSettings,
/// The debug output config.
pub debug_config: revive_llvm_context::DebugConfig,
/// The extra LLVM arguments give used for manual control.
pub llvm_arguments: Vec<String>,
/// The PVM memory configuration.
pub memory_config: SolcStandardJsonInputSettingsPolkaVMMemory,
}
impl Input {
/// A shortcut constructor.
pub fn new(
contract: Contract,
project: Project,
include_metadata_hash: bool,
optimizer_settings: revive_llvm_context::OptimizerSettings,
debug_config: revive_llvm_context::DebugConfig,
llvm_arguments: Vec<String>,
memory_config: SolcStandardJsonInputSettingsPolkaVMMemory,
) -> Self {
Self {
contract,
project,
include_metadata_hash,
optimizer_settings,
debug_config,
llvm_arguments,
memory_config,
}
}
}
+77
View File
@@ -0,0 +1,77 @@
//! Process for compiling a single compilation unit.
pub mod input;
#[cfg(not(target_os = "emscripten"))]
pub mod native_process;
pub mod output;
#[cfg(target_os = "emscripten")]
pub mod worker_process;
use std::io::{Read, Write};
use self::input::Input;
use self::output::Output;
pub trait Process {
/// 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();
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())?;
revive_llvm_context::initialize_llvm(
revive_llvm_context::Target::PVM,
crate::DEFAULT_EXECUTABLE_NAME,
&input.llvm_arguments,
);
let result = input.contract.compile(
input.project,
input.optimizer_settings,
input.include_metadata_hash,
input.debug_config,
&input.llvm_arguments,
input.memory_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>;
}
@@ -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)
}
}
+21
View File
@@ -0,0 +1,21 @@
//! Process for compiling a single compilation unit.
//! The output data.
use serde::Deserialize;
use serde::Serialize;
use crate::build::contract::Contract as ContractBuild;
/// The output data.
#[derive(Debug, Serialize, Deserialize)]
pub struct Output {
/// The contract build.
pub build: ContractBuild,
}
impl Output {
/// A shortcut constructor.
pub fn new(build: ContractBuild) -> Self {
Self { build }
}
}
@@ -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;
}