diff --git a/Cargo.lock b/Cargo.lock index 1a97402..f2554a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1677,6 +1677,7 @@ dependencies = [ "revm-jit-llvm", "revm-primitives", "rustc-hash", + "tempfile", "tracing", ] diff --git a/benches/src/main.rs b/benches/src/main.rs index 558acdf..ce4462d 100644 --- a/benches/src/main.rs +++ b/benches/src/main.rs @@ -1,7 +1,9 @@ use clap::{Parser, ValueEnum}; use color_eyre::{eyre::eyre, Result}; use revm_jit::{ - debug_time, eyre::Context, new_llvm_backend, EvmCompiler, EvmContext, OptimizationLevel, + debug_time, + eyre::{ensure, Context}, + new_llvm_backend, EvmCompiler, EvmContext, OptimizationLevel, }; use revm_jit_benches::Bench; use revm_primitives::{hex, Bytes, Env, SpecId}; @@ -51,7 +53,7 @@ fn main() -> Result<()> { let context = revm_jit::llvm::inkwell::context::Context::create(); let backend = new_llvm_backend(&context, cli.aot, opt_level)?; let mut compiler = EvmCompiler::new(backend); - compiler.set_dump_to(Some(PathBuf::from("./tmp/revm-jit"))); + compiler.set_dump_to(Some(PathBuf::from("tmp/revm-jit"))); compiler.set_module_name(&cli.bench_name); compiler.set_disable_gas(cli.no_gas); compiler.set_frame_pointers(true); @@ -97,12 +99,21 @@ fn main() -> Result<()> { let f_id = compiler.translate(Some(name), bytecode, spec_id)?; if cli.aot { - let mut path = compiler.out_dir().unwrap().to_path_buf(); - path.push(cli.bench_name); - path.push("a.o"); - let mut file = std::fs::File::create(&path)?; + let mut out_dir = compiler.out_dir().unwrap().to_path_buf(); + out_dir.push(cli.bench_name); + + let obj = out_dir.join("a.o"); + let mut file = std::fs::File::create(&obj)?; compiler.write_object(&mut file)?; - eprintln!("Wrote object file to {}", path.display()); + ensure!(obj.exists(), "Failed to write object file"); + eprintln!("Compiled object file to {}", obj.display()); + + let so = out_dir.join("a.so"); + let linker = revm_jit::Linker::new(); + linker.link(&so, [obj.to_str().unwrap()])?; + ensure!(so.exists(), "Failed to link object file"); + eprintln!("Linked shared object file to {}", so.display()); + return Ok(()); } diff --git a/crates/revm-jit-llvm/src/lib.rs b/crates/revm-jit-llvm/src/lib.rs index a50386a..7538e94 100644 --- a/crates/revm-jit-llvm/src/lib.rs +++ b/crates/revm-jit-llvm/src/lib.rs @@ -114,7 +114,7 @@ impl<'ctx> EvmLlvmBackend<'ctx> { &cpu.to_string_lossy(), &features.to_string_lossy(), opt_level, - if aot { RelocMode::Default } else { RelocMode::PIC }, + if aot { RelocMode::DynamicNoPic } else { RelocMode::PIC }, if aot { CodeModel::Default } else { CodeModel::JITDefault }, ) .ok_or_else(|| eyre::eyre!("failed to create target machine"))?; diff --git a/crates/revm-jit/Cargo.toml b/crates/revm-jit/Cargo.toml index ed226fd..ef23e43 100644 --- a/crates/revm-jit/Cargo.toml +++ b/crates/revm-jit/Cargo.toml @@ -35,6 +35,7 @@ tracing.workspace = true [dev-dependencies] revm-jit-context = { workspace = true, features = ["host-ext-any"] } paste = "1.0" +tempfile = "3.10" [features] default = ["llvm"] diff --git a/crates/revm-jit/src/lib.rs b/crates/revm-jit/src/lib.rs index c54ce5f..cab1400 100644 --- a/crates/revm-jit/src/lib.rs +++ b/crates/revm-jit/src/lib.rs @@ -16,6 +16,9 @@ pub use bytecode::*; mod compiler; pub use compiler::EvmCompiler; +mod linker; +pub use linker::Linker; + #[cfg(test)] mod tests; diff --git a/crates/revm-jit/src/linker.rs b/crates/revm-jit/src/linker.rs new file mode 100644 index 0000000..6a43877 --- /dev/null +++ b/crates/revm-jit/src/linker.rs @@ -0,0 +1,117 @@ +use std::path::{Path, PathBuf}; + +/// EVM bytecode compiler linker. +#[derive(Debug)] +pub struct Linker { + cc: Option, +} + +impl Default for Linker { + fn default() -> Self { + Self::new() + } +} + +impl Linker { + /// Creates a new linker. + pub fn new() -> Self { + Self { cc: None } + } + + /// Sets the C compiler to use for linking. + pub fn with_cc(mut self, cc: impl Into) -> Self { + self.cc = Some(cc.into()); + self + } + + /// Links the given object files into a shared library at the given path. + pub fn link( + &self, + out: &Path, + objects: impl IntoIterator>, + ) -> std::io::Result<()> { + debug_time!("link", || self.link_inner(out, objects)) + } + + fn link_inner( + &self, + out: &Path, + objects: impl IntoIterator>, + ) -> std::io::Result<()> { + let storage; + let cc = match &self.cc { + Some(cc) => cc, + None => { + let str = match std::env::var_os("CC") { + Some(cc) => { + storage = cc; + storage.as_os_str() + } + None => "cc".as_ref(), + }; + Path::new(str) + } + }; + + let mut cmd = std::process::Command::new(cc); + cmd.arg("-o").arg(out); + cmd.arg("-shared"); + cmd.arg("-O3"); + if !cfg!(debug_assertions) { + cmd.arg("-Wl,--gc-sections"); + cmd.arg("-Wl,--strip-all"); + } + // Link libc and the builtins. + cmd.arg("-lc"); + // TODO + // cmd.arg("-Ltarget/release/").arg("-lrevm_jit_builtins"); + cmd.args(objects); + debug!(?cmd, "linking"); + let output = cmd.output()?; + if !output.status.success() { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + format!("cc failed with {output:#?}"), + )); + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use revm_primitives::SpecId; + use std::fs::File; + + #[test] + fn basic() { + let tmp = tempfile::tempdir().expect("could not create temp dir"); + let obj = tmp.path().join("out.o"); + let so = tmp.path().join("out.so"); + + // Compile and build object file. + let cx = crate::llvm::inkwell::context::Context::create(); + let opt_level = revm_jit_backend::OptimizationLevel::Aggressive; + let backend = crate::new_llvm_backend(&cx, true, opt_level).unwrap(); + let mut compiler = crate::EvmCompiler::new(backend); + if let Err(e) = compiler.translate(Some("link_test_basic"), &[], SpecId::CANCUN) { + panic!("failed to compile: {e}"); + } + + { + let mut f = File::create(&obj).unwrap(); + if let Err(e) = compiler.write_object(&mut f) { + panic!("failed to write object: {e}"); + } + } + assert!(obj.exists()); + + // Link object to shared library. + let linker = Linker::new(); + if let Err(e) = linker.link(&so, [&obj]) { + panic!("failed to link: {e}"); + } + assert!(so.exists()); + } +}