Skip to content

Commit

Permalink
feat: improve DX around statically linked bytecodes, add example (#37)
Browse files Browse the repository at this point in the history
* feat: improve DX around statically linked bytecodes, add example

* chore: clippy
  • Loading branch information
DaniPopes authored Jun 22, 2024
1 parent 9c361e4 commit 649fd9c
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 14 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

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

90 changes: 83 additions & 7 deletions crates/revmc-context/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,40 @@ impl dyn HostExt {
}
}

/// Declare [`RawEvmCompilerFn`] functions in an `extern "C"` block.
///
/// # Examples
///
/// ```no_run
/// use revmc_context::{extern_revmc, EvmCompilerFn};
///
/// extern_revmc! {
/// /// A simple function that returns `Continue`.
/// pub fn test_fn;
/// }
///
/// let test_fn = EvmCompilerFn::new(test_fn);
/// ```
#[macro_export]
macro_rules! extern_revmc {
($( $(#[$attr:meta])* $vis:vis fn $name:ident; )+) => {
#[allow(improper_ctypes)]
extern "C" {
$(
$(#[$attr])*
$vis fn $name(
gas: *mut $crate::private::revm_interpreter::Gas,
stack: *mut $crate::EvmStack,
stack_len: *mut usize,
env: *const $crate::private::revm_primitives::Env,
contract: *const $crate::private::revm_interpreter::Contract,
ecx: *mut $crate::EvmContext<'_>,
) -> $crate::private::revm_interpreter::InstructionResult;
)+
}
};
}

/// The raw function signature of a bytecode function.
///
/// Prefer using [`EvmCompilerFn`] instead of this type. See [`EvmCompilerFn::call`] for more
Expand All @@ -154,6 +188,20 @@ pub type RawEvmCompilerFn = unsafe extern "C" fn(
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct EvmCompilerFn(RawEvmCompilerFn);

impl From<RawEvmCompilerFn> for EvmCompilerFn {
#[inline]
fn from(f: RawEvmCompilerFn) -> Self {
Self::new(f)
}
}

impl From<EvmCompilerFn> for RawEvmCompilerFn {
#[inline]
fn from(f: EvmCompilerFn) -> Self {
f.into_inner()
}
}

impl EvmCompilerFn {
/// Wraps the function.
#[inline]
Expand Down Expand Up @@ -693,6 +741,14 @@ fn option_as_mut_ptr<T>(opt: Option<&mut T>) -> *mut T {
}
}

// Macro re-exports.
// Not public API.
#[doc(hidden)]
pub mod private {
pub use revm_interpreter;
pub use revm_primitives;
}

#[cfg(test)]
mod tests {
use super::*;
Expand All @@ -705,19 +761,30 @@ mod tests {
assert_eq!(usize::try_from(&mut word), Ok(0));
}

#[cfg(not(feature = "host-ext-any"))]
extern "C" fn test_fn(
extern_revmc! {
#[link_name = "__test_fn"]
fn test_fn;
}

#[no_mangle]
extern "C" fn __test_fn(
_gas: *mut Gas,
_stack: *mut EvmStack,
_stack_len: *mut usize,
_env: *mut Env,
_env: *const Env,
_contract: *const Contract,
_ecx: *mut EvmContext<'_>,
) -> InstructionResult {
InstructionResult::Continue
}

#[cfg(not(feature = "host-ext-any"))]
#[test]
fn extern_macro() {
let _f1 = EvmCompilerFn::new(test_fn);
let _f2 = EvmCompilerFn::new(__test_fn);
assert_eq!(test_fn as usize, __test_fn as usize);
}

#[test]
fn borrowing_host() {
#[allow(unused)]
Expand All @@ -730,7 +797,10 @@ mod tests {
fn env_mut(&mut self) -> &mut Env {
self.0
}
fn load_account(&mut self, address: Address) -> Option<(bool, bool)> {
fn load_account(
&mut self,
address: Address,
) -> Option<revm_interpreter::LoadAccountResult> {
unimplemented!()
}
fn block_hash(&mut self, number: U256) -> Option<revm_primitives::B256> {
Expand All @@ -739,7 +809,7 @@ mod tests {
fn balance(&mut self, address: Address) -> Option<(U256, bool)> {
unimplemented!()
}
fn code(&mut self, address: Address) -> Option<(revm_primitives::Bytecode, bool)> {
fn code(&mut self, address: Address) -> Option<(revm_primitives::Bytes, bool)> {
unimplemented!()
}
fn code_hash(&mut self, address: Address) -> Option<(revm_primitives::B256, bool)> {
Expand Down Expand Up @@ -774,8 +844,14 @@ mod tests {
}
}

#[allow(unused_mut)]
let mut env = Env::default();
let mut host = BHost(&mut env);
#[cfg(not(feature = "host-ext-any"))]
let env = &mut env;
#[cfg(feature = "host-ext-any")]
let env = Box::leak(Box::new(env));

let mut host = BHost(env);
let f = EvmCompilerFn::new(test_fn);
let mut interpreter = Interpreter::new(Contract::default(), u64::MAX, false);

Expand Down
2 changes: 2 additions & 0 deletions crates/revmc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ pub use linker::Linker;
#[cfg(test)]
mod tests;

#[allow(ambiguous_glob_reexports)]
#[doc(inline)]
pub use revmc_backend::*;
#[allow(ambiguous_glob_reexports)]
#[doc(inline)]
pub use revmc_context::*;

Expand Down
1 change: 1 addition & 0 deletions examples/compiler/build.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
fn main() {
// Emit the configuration to run compiled bytecodes.
revmc_build::emit();
}
4 changes: 2 additions & 2 deletions examples/compiler/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ use std::path::PathBuf;

#[derive(Parser)]
struct Cli {
#[arg(long)]
#[arg(long, required_unless_present = "code_path")]
code: Option<String>,
#[arg(long, conflicts_with = "code", required_unless_present = "code")]
#[arg(long, conflicts_with = "code")]
code_path: Option<PathBuf>,
}

Expand Down
2 changes: 2 additions & 0 deletions examples/runner/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ repository.workspace = true
exclude.workspace = true

[build-dependencies]
revmc = { workspace = true, features = ["llvm-prefer-dynamic"] }
revmc-build.workspace = true
cc = "1.0"

[dependencies]
revmc-builtins = { workspace = true, default-features = false }
Expand Down
26 changes: 25 additions & 1 deletion examples/runner/build.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,27 @@
fn main() {
use revmc::{new_llvm_backend, primitives::SpecId, EvmCompiler, OptimizationLevel, Result};
use std::path::PathBuf;

fn main() -> Result<()> {
// Emit the configuration to run compiled bytecodes.
// This not used if we are only using statically linked bytecodes.
revmc_build::emit();

// Compile and statically link a bytecode.
let name = "fibonacci";
let bytecode = revmc::primitives::hex!(
"5f355f60015b8215601a578181019150909160019003916005565b9150505f5260205ff3"
);
println!("cargo:rustc-env=FIB_HASH={}", revmc::primitives::keccak256(bytecode));

let out_dir = PathBuf::from(std::env::var("OUT_DIR")?);
let context = revmc::llvm::inkwell::context::Context::create();
let backend = new_llvm_backend(&context, true, OptimizationLevel::Aggressive)?;
let mut compiler = EvmCompiler::new(backend);
compiler.translate(Some(name), &bytecode, SpecId::CANCUN)?;
let object = out_dir.join(name).with_extension("o");
compiler.write_object_to_file(&object)?;

cc::Build::new().object(&object).static_flag(true).compile(name);

Ok(())
}
22 changes: 18 additions & 4 deletions examples/runner/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,26 @@

extern crate alloc;

// This dependency is needed to export the necessary symbols used by the compiled bytecodes,
// This dependency is needed to define the necessary symbols used by the compiled bytecodes,
// but we don't use it directly, so silence the unused crate depedency warning.
use revmc_builtins as _;

use alloc::sync::Arc;
use revm::{handler::register::EvmHandler, primitives::B256, Database};
use revmc_context::EvmCompilerFn;

pub fn get_evm<'a, DB: Database + 'static>(db: DB) -> revm::Evm<'a, ExternalContext, DB> {
// The bytecode we statically linked.
const FIB_HASH: B256 =
match revm::primitives::hex::const_decode_to_array(env!("FIB_HASH").as_bytes()) {
Ok(hash) => B256::new(hash),
Err(_err) => panic!(),
};
revmc_context::extern_revmc! {
fn fibonacci;
}

/// Build a [`revm::Evm`] with a custom handler that can call compiled functions.
pub fn build_evm<'a, DB: Database + 'static>(db: DB) -> revm::Evm<'a, ExternalContext, DB> {
revm::Evm::builder()
.with_db(db)
.with_external_context(ExternalContext::new())
Expand All @@ -28,8 +39,11 @@ impl ExternalContext {
}

fn get_function(&self, bytecode_hash: B256) -> Option<EvmCompilerFn> {
// Some way to get the function, either linked statically or dynamically.
let _ = bytecode_hash;
// Can use any mapping between bytecode hash and function.
if bytecode_hash == FIB_HASH {
return Some(EvmCompilerFn::new(fibonacci));
}

None
}
}
Expand Down

0 comments on commit 649fd9c

Please sign in to comment.