From 564cb56dc03baa715194d953947f2652fd4a4106 Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Thu, 20 Jun 2024 17:25:33 -0600 Subject: [PATCH 01/12] stylus: internally split compile from activate also introduce compilation target --- arbitrator/prover/src/programs/config.rs | 38 +++++++++++++----------- arbitrator/stylus/src/cache.rs | 4 +-- arbitrator/stylus/src/lib.rs | 35 +++++++++++++++------- arbitrator/stylus/src/native.rs | 22 ++++++++------ arbos/programs/native.go | 27 ++++++++++++----- 5 files changed, 81 insertions(+), 45 deletions(-) diff --git a/arbitrator/prover/src/programs/config.rs b/arbitrator/prover/src/programs/config.rs index 0b5ce17475..308e21865a 100644 --- a/arbitrator/prover/src/programs/config.rs +++ b/arbitrator/prover/src/programs/config.rs @@ -17,7 +17,7 @@ use { meter::Meter, start::StartMover, MiddlewareWrapper, }, std::sync::Arc, - wasmer::{Cranelift, CraneliftOptLevel, Engine, Store}, + wasmer::{Cranelift, CraneliftOptLevel, Engine, Store, Target}, wasmer_compiler_singlepass::Singlepass, }; @@ -181,17 +181,19 @@ impl CompileConfig { } #[cfg(feature = "native")] - pub fn store(&self) -> Store { - let mut compiler: Box = match self.debug.cranelift { + pub fn engine(&self, target: Target) -> Engine { + use wasmer::sys::EngineBuilder; + + let mut wasmer_config: Box = match self.debug.cranelift { true => { - let mut compiler = Cranelift::new(); - compiler.opt_level(CraneliftOptLevel::Speed); - Box::new(compiler) + let mut wasmer_config = Cranelift::new(); + wasmer_config.opt_level(CraneliftOptLevel::Speed); + Box::new(wasmer_config) } false => Box::new(Singlepass::new()), }; - compiler.canonicalize_nans(true); - compiler.enable_verifier(); + wasmer_config.canonicalize_nans(true); + wasmer_config.enable_verifier(); let start = MiddlewareWrapper::new(StartMover::new(self.debug.debug_info)); let meter = MiddlewareWrapper::new(Meter::new(&self.pricing)); @@ -201,22 +203,24 @@ impl CompileConfig { // add the instrumentation in the order of application // note: this must be consistent with the prover - compiler.push_middleware(Arc::new(start)); - compiler.push_middleware(Arc::new(meter)); - compiler.push_middleware(Arc::new(dygas)); - compiler.push_middleware(Arc::new(depth)); - compiler.push_middleware(Arc::new(bound)); + wasmer_config.push_middleware(Arc::new(start)); + wasmer_config.push_middleware(Arc::new(meter)); + wasmer_config.push_middleware(Arc::new(dygas)); + wasmer_config.push_middleware(Arc::new(depth)); + wasmer_config.push_middleware(Arc::new(bound)); if self.debug.count_ops { let counter = Counter::new(); - compiler.push_middleware(Arc::new(MiddlewareWrapper::new(counter))); + wasmer_config.push_middleware(Arc::new(MiddlewareWrapper::new(counter))); } - Store::new(compiler) + EngineBuilder::new(wasmer_config) + .set_target(Some(target)) + .into() } #[cfg(feature = "native")] - pub fn engine(&self) -> Engine { - self.store().engine().clone() + pub fn store(&self, target: Target) -> Store { + Store::new(self.engine(target)) } } diff --git a/arbitrator/stylus/src/cache.rs b/arbitrator/stylus/src/cache.rs index 06739f2219..180b61e9ee 100644 --- a/arbitrator/stylus/src/cache.rs +++ b/arbitrator/stylus/src/cache.rs @@ -8,7 +8,7 @@ use lru::LruCache; use parking_lot::Mutex; use prover::programs::config::CompileConfig; use std::{collections::HashMap, num::NonZeroUsize}; -use wasmer::{Engine, Module, Store}; +use wasmer::{Engine, Module, Store, Target}; lazy_static! { static ref INIT_CACHE: Mutex = Mutex::new(InitCache::new(256)); @@ -120,7 +120,7 @@ impl InitCache { } drop(cache); - let engine = CompileConfig::version(version, debug).engine(); + let engine = CompileConfig::version(version, debug).engine(Target::default()); let module = unsafe { Module::deserialize_unchecked(&engine, module)? }; let item = CacheItem::new(module, engine); diff --git a/arbitrator/stylus/src/lib.rs b/arbitrator/stylus/src/lib.rs index 3c53359f8b..f2f7b481d3 100644 --- a/arbitrator/stylus/src/lib.rs +++ b/arbitrator/stylus/src/lib.rs @@ -140,7 +140,6 @@ pub unsafe extern "C" fn stylus_activate( version: u16, debug: bool, output: *mut RustBytes, - asm_len: *mut usize, codehash: *const Bytes32, module_hash: *mut Bytes32, stylus_data: *mut StylusData, @@ -152,18 +151,34 @@ pub unsafe extern "C" fn stylus_activate( let codehash = &*codehash; let gas = &mut *gas; - let (asm, module, info) = - match native::activate(wasm, codehash, version, page_limit, debug, gas) { - Ok(val) => val, - Err(err) => return output.write_err(err), - }; - *asm_len = asm.len(); + let (module, info) = match native::activate(wasm, codehash, version, page_limit, debug, gas) { + Ok(val) => val, + Err(err) => return output.write_err(err), + }; + *module_hash = module.hash(); *stylus_data = info; - let mut data = asm; - data.extend(&*module.into_bytes()); - output.write(data); + output.write(module.into_bytes()); + UserOutcomeKind::Success +} + +#[no_mangle] +pub unsafe extern "C" fn stylus_compile( + wasm: GoSliceData, + version: u16, + debug: bool, + output: *mut RustBytes, +) -> UserOutcomeKind { + let wasm = wasm.slice(); + let output = &mut *output; + + let asm = match native::compile(wasm, version, debug) { + Ok(val) => val, + Err(err) => return output.write_err(err), + }; + + output.write(asm); UserOutcomeKind::Success } diff --git a/arbitrator/stylus/src/native.rs b/arbitrator/stylus/src/native.rs index 2858d59fdc..f8d2a481a4 100644 --- a/arbitrator/stylus/src/native.rs +++ b/arbitrator/stylus/src/native.rs @@ -33,7 +33,7 @@ use std::{ ops::{Deref, DerefMut}, }; use wasmer::{ - imports, AsStoreMut, Function, FunctionEnv, Instance, Memory, Module, Pages, Store, + imports, AsStoreMut, Function, FunctionEnv, Instance, Memory, Module, Pages, Store, Target, TypedFunction, Value, WasmTypeList, }; use wasmer_vm::VMExtern; @@ -98,7 +98,7 @@ impl> NativeInstance { evm_data: EvmData, ) -> Result { let env = WasmEnv::new(compile, None, evm, evm_data); - let store = env.compile.store(); + let store = env.compile.store(Target::default()); let module = unsafe { Module::deserialize_unchecked(&store, module)? }; Self::from_module(module, store, env) } @@ -139,7 +139,7 @@ impl> NativeInstance { config: StylusConfig, ) -> Result { let env = WasmEnv::new(compile.clone(), Some(config), evm_api, evm_data); - let store = env.compile.store(); + let store = env.compile.store(Target::default()); let wat_or_wasm = std::fs::read(path)?; let module = Module::new(&store, wat_or_wasm)?; Self::from_module(module, store, env) @@ -347,8 +347,8 @@ impl> StartlessMachine for NativeInstance { } } -pub fn module(wasm: &[u8], compile: CompileConfig) -> Result> { - let mut store = compile.store(); +pub fn module(wasm: &[u8], compile: CompileConfig, target: Target) -> Result> { + let mut store = compile.store(target); let module = Module::new(&store, wasm)?; macro_rules! stub { (u8 <- $($types:tt)+) => { @@ -441,14 +441,18 @@ pub fn activate( page_limit: u16, debug: bool, gas: &mut u64, -) -> Result<(Vec, ProverModule, StylusData)> { - let compile = CompileConfig::version(version, debug); +) -> Result<(ProverModule, StylusData)> { let (module, stylus_data) = ProverModule::activate(wasm, codehash, version, page_limit, debug, gas)?; - let asm = match self::module(wasm, compile) { + Ok((module, stylus_data)) +} + +pub fn compile(wasm: &[u8], version: u16, debug: bool) -> Result> { + let compile = CompileConfig::version(version, debug); + let asm = match self::module(wasm, compile, Target::default()) { Ok(asm) => asm, Err(err) => util::panic_with_wasm(wasm, err), }; - Ok((asm, module, stylus_data)) + Ok(asm) } diff --git a/arbos/programs/native.go b/arbos/programs/native.go index ffb27cb6c0..53d8f64022 100644 --- a/arbos/programs/native.go +++ b/arbos/programs/native.go @@ -73,25 +73,41 @@ func activateProgramInternal( gasLeft *uint64, ) (*activationInfo, []byte, []byte, error) { output := &rustBytes{} - asmLen := usize(0) moduleHash := &bytes32{} stylusData := &C.StylusData{} codeHash := hashToBytes32(codehash) - status := userStatus(C.stylus_activate( + status_mod := userStatus(C.stylus_activate( goSlice(wasm), u16(page_limit), u16(version), cbool(debug), output, - &asmLen, &codeHash, moduleHash, stylusData, (*u64)(gasLeft), )) - data, msg, err := status.toResult(output.intoBytes(), debug) + module, msg, err := status_mod.toResult(output.intoBytes(), debug) + if err != nil { + if debug { + log.Warn("activation failed", "err", err, "msg", msg, "program", addressForLogging) + } + if errors.Is(err, vm.ErrExecutionReverted) { + return nil, nil, nil, fmt.Errorf("%w: %s", ErrProgramActivation, msg) + } + return nil, nil, nil, err + } + + status_asm := userStatus(C.stylus_compile( + goSlice(wasm), + u16(version), + cbool(debug), + output, + )) + + asm, msg, err := status_asm.toResult(output.intoBytes(), debug) if err != nil { if debug { log.Warn("activation failed", "err", err, "msg", msg, "program", addressForLogging) @@ -103,9 +119,6 @@ func activateProgramInternal( } hash := moduleHash.toHash() - split := int(asmLen) - asm := data[:split] - module := data[split:] info := &activationInfo{ moduleHash: hash, From a9c627536096dc5487ac1714b090e3137180c7eb Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Thu, 20 Jun 2024 18:36:18 -0600 Subject: [PATCH 02/12] update stylus function documentation --- arbitrator/stylus/src/lib.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/arbitrator/stylus/src/lib.rs b/arbitrator/stylus/src/lib.rs index f2f7b481d3..7e2c45f63f 100644 --- a/arbitrator/stylus/src/lib.rs +++ b/arbitrator/stylus/src/lib.rs @@ -122,9 +122,9 @@ impl RustBytes { } } -/// Instruments and "activates" a user wasm. +/// "activates" a user wasm. /// -/// The `output` is either the serialized asm & module pair or an error string. +/// The `output` is either the module or an error string. /// Returns consensus info such as the module hash and footprint on success. /// /// Note that this operation costs gas and is limited by the amount supplied via the `gas` pointer. @@ -163,6 +163,14 @@ pub unsafe extern "C" fn stylus_activate( UserOutcomeKind::Success } +/// "compiles" a user wasm. +/// +/// The `output` is either the asm or an error string. +/// Returns consensus info such as the module hash and footprint on success. +/// +/// # Safety +/// +/// `output` must not be null. #[no_mangle] pub unsafe extern "C" fn stylus_compile( wasm: GoSliceData, From 4f52b170f91da76132aaec2bd63b3adcf6c3eb2a Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Mon, 24 Jun 2024 16:20:06 -0600 Subject: [PATCH 03/12] stylus: introduce target_cache --- arbitrator/stylus/src/target_cache.rs | 73 +++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 arbitrator/stylus/src/target_cache.rs diff --git a/arbitrator/stylus/src/target_cache.rs b/arbitrator/stylus/src/target_cache.rs new file mode 100644 index 0000000000..9e1d1eb1f5 --- /dev/null +++ b/arbitrator/stylus/src/target_cache.rs @@ -0,0 +1,73 @@ +// Copyright 2022-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +use eyre::{eyre, OptionExt, Result}; +use lazy_static::lazy_static; +use parking_lot::RwLock; +use std::{collections::HashMap, str::FromStr}; +use wasmer_types::{CpuFeature, Target, Triple}; + +use crate::cache::InitCache; + +lazy_static! { + static ref TARGET_CACHE: RwLock> = RwLock::new(HashMap::new()); +} + +fn target_from_string(input: String) -> Result { + let mut parts = input.split('+'); + + let Some(trip_sting) = parts.next() else { + return Err(eyre!("no architecture")); + }; + + let trip = match Triple::from_str(trip_sting) { + Ok(val) => val, + Err(e) => return Err(eyre!(e)), + }; + + let mut features = CpuFeature::set(); + for flag in parts { + features.insert(CpuFeature::from_str(flag)?); + } + + Ok(Target::new(trip, features)) +} + +pub fn target_cache_set(name: String, description: String, native: bool) -> Result<()> { + let target = target_from_string(description)?; + + if native { + if !target.is_native() { + return Err(eyre!("arch not native")); + } + let flags_not_supported = Target::default() + .cpu_features() + .complement() + .intersection(*target.cpu_features()); + if !flags_not_supported.is_empty() { + let mut err_message = String::new(); + err_message.push_str("cpu flags not supported on local cpu for: "); + for item in flags_not_supported.iter() { + err_message.push('+'); + err_message.push_str(&item.to_string()); + } + return Err(eyre!(err_message)); + } + InitCache::set_target(target.clone()) + } + + TARGET_CACHE.write().insert(name, target); + + Ok(()) +} + +pub fn target_cache_get(name: &str) -> Result { + if name.len() == 0 { + return Ok(InitCache::target()); + } + TARGET_CACHE + .read() + .get(name) + .cloned() + .ok_or_eyre("arch not set") +} From 9e9924501d04a5f4d7cbc1abba3dc80133f4444b Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Mon, 24 Jun 2024 16:21:50 -0600 Subject: [PATCH 04/12] use explicit targets and target cache --- arbitrator/jit/src/stylus_backend.rs | 12 ++++++-- arbitrator/stylus/src/cache.rs | 13 ++++++++- arbitrator/stylus/src/lib.rs | 42 +++++++++++++++++++++++++++- arbitrator/stylus/src/native.rs | 10 ++++--- arbitrator/stylus/src/test/mod.rs | 11 ++++++-- arbos/programs/native.go | 1 + 6 files changed, 79 insertions(+), 10 deletions(-) diff --git a/arbitrator/jit/src/stylus_backend.rs b/arbitrator/jit/src/stylus_backend.rs index 61dbf258d4..cb0440a257 100644 --- a/arbitrator/jit/src/stylus_backend.rs +++ b/arbitrator/jit/src/stylus_backend.rs @@ -24,6 +24,7 @@ use std::{ thread::JoinHandle, }; use stylus::{native::NativeInstance, run::RunProgram}; +use wasmer::Target; struct MessageToCothread { result: Vec, @@ -143,8 +144,15 @@ pub fn exec_wasm( let evm_api = EvmApiRequestor::new(cothread); - let mut instance = - unsafe { NativeInstance::deserialize(&module, compile.clone(), evm_api, evm_data) }?; + let mut instance = unsafe { + NativeInstance::deserialize( + &module, + compile.clone(), + evm_api, + evm_data, + Target::default(), + ) + }?; let thread = thread::spawn(move || { let outcome = instance.run_main(&calldata, config, ink); diff --git a/arbitrator/stylus/src/cache.rs b/arbitrator/stylus/src/cache.rs index 180b61e9ee..62db786ac0 100644 --- a/arbitrator/stylus/src/cache.rs +++ b/arbitrator/stylus/src/cache.rs @@ -23,6 +23,7 @@ macro_rules! cache { pub struct InitCache { long_term: HashMap, lru: LruCache, + target: Target, } #[derive(Clone, Copy, Hash, PartialEq, Eq)] @@ -68,6 +69,7 @@ impl InitCache { Self { long_term: HashMap::new(), lru: LruCache::new(NonZeroUsize::new(size).unwrap()), + target: Target::default(), } } @@ -77,6 +79,14 @@ impl InitCache { .resize(NonZeroUsize::new(size.try_into().unwrap()).unwrap()) } + pub fn set_target(target: Target) { + cache!().target = target; + } + + pub fn target() -> Target { + cache!().target.clone() + } + /// Retrieves a cached value, updating items as necessary. pub fn get(module_hash: Bytes32, version: u16, debug: bool) -> Option<(Module, Store)> { let mut cache = cache!(); @@ -118,9 +128,10 @@ impl InitCache { } return Ok(item.data()); } + let target = cache.target.clone(); drop(cache); - let engine = CompileConfig::version(version, debug).engine(Target::default()); + let engine = CompileConfig::version(version, debug).engine(target); let module = unsafe { Module::deserialize_unchecked(&engine, module)? }; let item = CacheItem::new(module, engine); diff --git a/arbitrator/stylus/src/lib.rs b/arbitrator/stylus/src/lib.rs index 7e2c45f63f..2929821433 100644 --- a/arbitrator/stylus/src/lib.rs +++ b/arbitrator/stylus/src/lib.rs @@ -18,6 +18,7 @@ use native::NativeInstance; use prover::programs::{prelude::*, StylusData}; use run::RunProgram; use std::{marker::PhantomData, mem, ptr}; +use target_cache::{target_cache_get, target_cache_set}; pub use brotli; pub use prover; @@ -29,6 +30,7 @@ pub mod run; mod cache; mod evm_api; +mod target_cache; mod util; #[cfg(test)] @@ -176,12 +178,18 @@ pub unsafe extern "C" fn stylus_compile( wasm: GoSliceData, version: u16, debug: bool, + name: GoSliceData, output: *mut RustBytes, ) -> UserOutcomeKind { let wasm = wasm.slice(); let output = &mut *output; + let name = String::from_utf8_unchecked(name.slice().to_vec()); + let target = match target_cache_get(&name) { + Ok(val) => val, + Err(err) => return output.write_err(err), + }; - let asm = match native::compile(wasm, version, debug) { + let asm = match native::compile(wasm, version, debug, target) { Ok(val) => val, Err(err) => return output.write_err(err), }; @@ -190,6 +198,38 @@ pub unsafe extern "C" fn stylus_compile( UserOutcomeKind::Success } +/// sets target index to a string +/// +/// String format is: Triple+CpuFeature+CpuFeature.. +/// +/// # Safety +/// +/// `output` must not be null. +#[no_mangle] +pub unsafe extern "C" fn stylus_target_set( + name: GoSliceData, + description: GoSliceData, + output: *mut RustBytes, + native: bool, +) -> UserOutcomeKind { + let output = &mut *output; + let name = match String::from_utf8(name.slice().to_vec()) { + Ok(val) => val, + Err(err) => return output.write_err(err.into()), + }; + + let desc_str = match String::from_utf8(description.slice().to_vec()) { + Ok(val) => val, + Err(err) => return output.write_err(err.into()), + }; + + if let Err(err) = target_cache_set(name, desc_str, native) { + return output.write_err(err); + }; + + UserOutcomeKind::Success +} + /// Calls an activated user program. /// /// # Safety diff --git a/arbitrator/stylus/src/native.rs b/arbitrator/stylus/src/native.rs index f8d2a481a4..21646a0e71 100644 --- a/arbitrator/stylus/src/native.rs +++ b/arbitrator/stylus/src/native.rs @@ -96,9 +96,10 @@ impl> NativeInstance { compile: CompileConfig, evm: E, evm_data: EvmData, + target: Target, ) -> Result { let env = WasmEnv::new(compile, None, evm, evm_data); - let store = env.compile.store(Target::default()); + let store = env.compile.store(target); let module = unsafe { Module::deserialize_unchecked(&store, module)? }; Self::from_module(module, store, env) } @@ -137,9 +138,10 @@ impl> NativeInstance { evm_data: EvmData, compile: &CompileConfig, config: StylusConfig, + target: Target, ) -> Result { let env = WasmEnv::new(compile.clone(), Some(config), evm_api, evm_data); - let store = env.compile.store(Target::default()); + let store = env.compile.store(target); let wat_or_wasm = std::fs::read(path)?; let module = Module::new(&store, wat_or_wasm)?; Self::from_module(module, store, env) @@ -448,9 +450,9 @@ pub fn activate( Ok((module, stylus_data)) } -pub fn compile(wasm: &[u8], version: u16, debug: bool) -> Result> { +pub fn compile(wasm: &[u8], version: u16, debug: bool, target: Target) -> Result> { let compile = CompileConfig::version(version, debug); - let asm = match self::module(wasm, compile, Target::default()) { + let asm = match self::module(wasm, compile, target) { Ok(asm) => asm, Err(err) => util::panic_with_wasm(wasm, err), }; diff --git a/arbitrator/stylus/src/test/mod.rs b/arbitrator/stylus/src/test/mod.rs index d7f3248d31..cfc0ab23b7 100644 --- a/arbitrator/stylus/src/test/mod.rs +++ b/arbitrator/stylus/src/test/mod.rs @@ -16,7 +16,7 @@ use rand::prelude::*; use std::{collections::HashMap, path::Path, sync::Arc}; use wasmer::{ imports, wasmparser::Operator, CompilerConfig, Function, FunctionEnv, Imports, Instance, - Module, Store, + Module, Store, Target, }; use wasmer_compiler_singlepass::Singlepass; @@ -86,7 +86,14 @@ impl TestInstance { config: StylusConfig, ) -> Result<(Self, TestEvmApi)> { let (mut evm, evm_data) = TestEvmApi::new(compile.clone()); - let native = Self::from_path(path, evm.clone(), evm_data, compile, config)?; + let native = Self::from_path( + path, + evm.clone(), + evm_data, + compile, + config, + Target::default(), + )?; let footprint = native.memory().ty(&native.store).minimum.0 as u16; evm.set_pages(footprint); Ok((native, evm)) diff --git a/arbos/programs/native.go b/arbos/programs/native.go index 53d8f64022..0de63009ac 100644 --- a/arbos/programs/native.go +++ b/arbos/programs/native.go @@ -104,6 +104,7 @@ func activateProgramInternal( goSlice(wasm), u16(version), cbool(debug), + goSlice([]byte{}), output, )) From db424976d7139794ca5f166b7b47150929d65130 Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Mon, 24 Jun 2024 19:07:29 -0600 Subject: [PATCH 05/12] stylus compile: support non-native archs and dont panic --- arbitrator/stylus/src/native.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/arbitrator/stylus/src/native.rs b/arbitrator/stylus/src/native.rs index 21646a0e71..a2b31ed596 100644 --- a/arbitrator/stylus/src/native.rs +++ b/arbitrator/stylus/src/native.rs @@ -430,7 +430,6 @@ pub fn module(wasm: &[u8], compile: CompileConfig, target: Target) -> Result Result> { let compile = CompileConfig::version(version, debug); - let asm = match self::module(wasm, compile, target) { - Ok(asm) => asm, - Err(err) => util::panic_with_wasm(wasm, err), - }; - Ok(asm) + self::module(wasm, compile, target) } From 035fddb52a7eec66c3005f33239f86890962471e Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Mon, 24 Jun 2024 19:23:14 -0600 Subject: [PATCH 06/12] programs: add testCompileArch --- .../{constant_test.go => cgo_test.go} | 7 ++ arbos/programs/testcompile.go | 119 ++++++++++++++++++ 2 files changed, 126 insertions(+) rename arbos/programs/{constant_test.go => cgo_test.go} (72%) create mode 100644 arbos/programs/testcompile.go diff --git a/arbos/programs/constant_test.go b/arbos/programs/cgo_test.go similarity index 72% rename from arbos/programs/constant_test.go rename to arbos/programs/cgo_test.go index fe29bcf3d9..c0c8953f62 100644 --- a/arbos/programs/constant_test.go +++ b/arbos/programs/cgo_test.go @@ -11,3 +11,10 @@ func TestConstants(t *testing.T) { t.Fatal(err) } } + +func TestCompileArch(t *testing.T) { + err := testCompileArch() + if err != nil { + t.Fatal(err) + } +} diff --git a/arbos/programs/testcompile.go b/arbos/programs/testcompile.go new file mode 100644 index 0000000000..36c90f1e3b --- /dev/null +++ b/arbos/programs/testcompile.go @@ -0,0 +1,119 @@ +// Copyright 2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +//go:build !wasm +// +build !wasm + +package programs + +// This file exists because cgo isn't allowed in tests + +/* +#cgo CFLAGS: -g -Wall -I../../target/include/ +#include "arbitrator.h" +*/ +import "C" +import ( + "fmt" + "os" + "runtime" + + "github.com/wasmerio/wasmer-go/wasmer" +) + +func testCompileArch() error { + + nativeArm64 := false + nativeAmd64 := false + + arm64CompileName := []byte("arm64") + amd64CompileName := []byte("amd64") + + arm64TargetString := []byte("arm64-linux-unknown+neon") + amd64TargetString := []byte("x86_64-linux-unknown+sse4.2") + + if runtime.GOOS == "linux" { + if runtime.GOARCH == "amd64" { + nativeAmd64 = true + } + if runtime.GOARCH == "arm64" { + nativeArm64 = true + } + } + + output := &rustBytes{} + + status := C.stylus_target_set(goSlice(arm64CompileName), + goSlice(arm64TargetString), + output, + cbool(nativeArm64)) + + if status != 0 { + return fmt.Errorf("failed setting compilation target arm: %v", string(output.intoBytes())) + } + + status = C.stylus_target_set(goSlice(amd64CompileName), + goSlice(amd64TargetString), + output, + cbool(nativeAmd64)) + + if status != 0 { + return fmt.Errorf("failed setting compilation target amd: %v", string(output.intoBytes())) + } + + source, err := os.ReadFile("../../arbitrator/stylus/tests/memory.wat") + if err != nil { + return fmt.Errorf("failed reading stylus contract: %w", err) + } + + wasm, err := wasmer.Wat2Wasm(string(source)) + if err != nil { + return err + } + + status = C.stylus_compile( + goSlice(wasm), + u16(1), + cbool(true), + goSlice([]byte("booga")), + output, + ) + if status == 0 { + return fmt.Errorf("succeeded compiling non-existent arch: %v", string(output.intoBytes())) + } + + status = C.stylus_compile( + goSlice(wasm), + u16(1), + cbool(true), + goSlice([]byte{}), + output, + ) + if status != 0 { + return fmt.Errorf("failed compiling native: %v", string(output.intoBytes())) + } + + status = C.stylus_compile( + goSlice(wasm), + u16(1), + cbool(true), + goSlice(arm64CompileName), + output, + ) + if status != 0 { + return fmt.Errorf("failed compiling arm: %v", string(output.intoBytes())) + } + + status = C.stylus_compile( + goSlice(wasm), + u16(1), + cbool(true), + goSlice(amd64CompileName), + output, + ) + if status != 0 { + return fmt.Errorf("failed compiling amd: %v", string(output.intoBytes())) + } + + return nil +} From 89bcd0729803d352dfe51a29cf5ad9479d4a7e56 Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Mon, 24 Jun 2024 19:23:42 -0600 Subject: [PATCH 07/12] arbos/programs: improve code in activation --- arbos/programs/native.go | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/arbos/programs/native.go b/arbos/programs/native.go index 0de63009ac..65073220a9 100644 --- a/arbos/programs/native.go +++ b/arbos/programs/native.go @@ -100,23 +100,17 @@ func activateProgramInternal( return nil, nil, nil, err } - status_asm := userStatus(C.stylus_compile( + status_asm := C.stylus_compile( goSlice(wasm), u16(version), cbool(debug), goSlice([]byte{}), output, - )) + ) - asm, msg, err := status_asm.toResult(output.intoBytes(), debug) - if err != nil { - if debug { - log.Warn("activation failed", "err", err, "msg", msg, "program", addressForLogging) - } - if errors.Is(err, vm.ErrExecutionReverted) { - return nil, nil, nil, fmt.Errorf("%w: %s", ErrProgramActivation, msg) - } - return nil, nil, nil, err + asm := output.intoBytes() + if status_asm != 0 { + return nil, nil, nil, fmt.Errorf("%w: %s", ErrProgramActivation, string(asm)) } hash := moduleHash.toHash() From da573341d62a756b0c8b9763eb8fb5ae8fee30b5 Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Mon, 24 Jun 2024 19:53:53 -0600 Subject: [PATCH 08/12] clippy --- arbitrator/stylus/src/native.rs | 2 +- arbitrator/stylus/src/target_cache.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/arbitrator/stylus/src/native.rs b/arbitrator/stylus/src/native.rs index a2b31ed596..c1bc06dbb0 100644 --- a/arbitrator/stylus/src/native.rs +++ b/arbitrator/stylus/src/native.rs @@ -4,7 +4,7 @@ use crate::{ cache::InitCache, env::{MeterData, WasmEnv}, - host, util, + host, }; use arbutil::{ evm::{ diff --git a/arbitrator/stylus/src/target_cache.rs b/arbitrator/stylus/src/target_cache.rs index 9e1d1eb1f5..94b3101383 100644 --- a/arbitrator/stylus/src/target_cache.rs +++ b/arbitrator/stylus/src/target_cache.rs @@ -62,7 +62,7 @@ pub fn target_cache_set(name: String, description: String, native: bool) -> Resu } pub fn target_cache_get(name: &str) -> Result { - if name.len() == 0 { + if name.is_empty() { return Ok(InitCache::target()); } TARGET_CACHE From 07aebbfd216e353686ab8e673bc62b0d38b74393 Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Tue, 25 Jun 2024 17:49:30 -0600 Subject: [PATCH 09/12] testCompileArch: add load/store --- arbos/programs/cgo_test.go | 23 +++++- arbos/programs/testcompile.go | 147 +++++++++++++++++++++++++++++++--- 2 files changed, 155 insertions(+), 15 deletions(-) diff --git a/arbos/programs/cgo_test.go b/arbos/programs/cgo_test.go index c0c8953f62..6506af75dc 100644 --- a/arbos/programs/cgo_test.go +++ b/arbos/programs/cgo_test.go @@ -1,9 +1,17 @@ // Copyright 2024, Offchain Labs, Inc. // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE +//go:build !wasm +// +build !wasm + package programs -import "testing" +import ( + "fmt" + "os" + "strings" + "testing" +) func TestConstants(t *testing.T) { err := testConstants() @@ -13,8 +21,19 @@ func TestConstants(t *testing.T) { } func TestCompileArch(t *testing.T) { - err := testCompileArch() + compile_env := os.Getenv("TEST_COMPILE") + if compile_env == "" { + fmt.Print("use TEST_COMPILE=[STORE|LOAD] to allow store/load in compile test") + } + store := strings.Contains(compile_env, "STORE") + err := testCompileArch(store) if err != nil { t.Fatal(err) } + if store || strings.Contains(compile_env, "LOAD") { + err = testCompileLoad() + if err != nil { + t.Fatal(err) + } + } } diff --git a/arbos/programs/testcompile.go b/arbos/programs/testcompile.go index 36c90f1e3b..3ee22a76bf 100644 --- a/arbos/programs/testcompile.go +++ b/arbos/programs/testcompile.go @@ -11,6 +11,13 @@ package programs /* #cgo CFLAGS: -g -Wall -I../../target/include/ #include "arbitrator.h" + +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; +typedef size_t usize; + +void handleReqWrap(usize api, u32 req_type, RustSlice *data, u64 *out_cost, GoSliceData *out_result, GoSliceData *out_raw_data); */ import "C" import ( @@ -21,10 +28,18 @@ import ( "github.com/wasmerio/wasmer-go/wasmer" ) -func testCompileArch() error { +func isNativeArm() bool { + return runtime.GOOS == "linux" && runtime.GOARCH == "arm64" +} + +func isNativeX86() bool { + return runtime.GOOS == "linux" && runtime.GOARCH == "amd64" +} + +func testCompileArch(store bool) error { - nativeArm64 := false - nativeAmd64 := false + nativeArm64 := isNativeArm() + nativeAmd64 := isNativeX86() arm64CompileName := []byte("arm64") amd64CompileName := []byte("amd64") @@ -32,17 +47,13 @@ func testCompileArch() error { arm64TargetString := []byte("arm64-linux-unknown+neon") amd64TargetString := []byte("x86_64-linux-unknown+sse4.2") - if runtime.GOOS == "linux" { - if runtime.GOARCH == "amd64" { - nativeAmd64 = true - } - if runtime.GOARCH == "arm64" { - nativeArm64 = true - } - } - output := &rustBytes{} + _, err := fmt.Print("starting test.. native arm? ", nativeArm64, " amd? ", nativeAmd64, " GOARCH/GOOS: ", runtime.GOARCH+"/"+runtime.GOOS, "\n") + if err != nil { + return err + } + status := C.stylus_target_set(goSlice(arm64CompileName), goSlice(arm64TargetString), output, @@ -61,7 +72,7 @@ func testCompileArch() error { return fmt.Errorf("failed setting compilation target amd: %v", string(output.intoBytes())) } - source, err := os.ReadFile("../../arbitrator/stylus/tests/memory.wat") + source, err := os.ReadFile("../../arbitrator/stylus/tests/add.wat") if err != nil { return fmt.Errorf("failed reading stylus contract: %w", err) } @@ -71,6 +82,17 @@ func testCompileArch() error { return err } + if store { + _, err := fmt.Print("storing compiled files to ../../target/testdata/\n") + if err != nil { + return err + } + err = os.MkdirAll("../../target/testdata", 0644) + if err != nil { + return err + } + } + status = C.stylus_compile( goSlice(wasm), u16(1), @@ -92,6 +114,17 @@ func testCompileArch() error { if status != 0 { return fmt.Errorf("failed compiling native: %v", string(output.intoBytes())) } + if store && !nativeAmd64 && !nativeArm64 { + _, err := fmt.Printf("writing host file\n") + if err != nil { + return err + } + + err = os.WriteFile("../../target/testdata/host.bin", output.intoBytes(), 0644) + if err != nil { + return err + } + } status = C.stylus_compile( goSlice(wasm), @@ -103,6 +136,17 @@ func testCompileArch() error { if status != 0 { return fmt.Errorf("failed compiling arm: %v", string(output.intoBytes())) } + if store { + _, err := fmt.Printf("writing arm file\n") + if err != nil { + return err + } + + err = os.WriteFile("../../target/testdata/arm64.bin", output.intoBytes(), 0644) + if err != nil { + return err + } + } status = C.stylus_compile( goSlice(wasm), @@ -114,6 +158,83 @@ func testCompileArch() error { if status != 0 { return fmt.Errorf("failed compiling amd: %v", string(output.intoBytes())) } + if store { + _, err := fmt.Printf("writing amd64 file\n") + if err != nil { + return err + } + + err = os.WriteFile("../../target/testdata/amd64.bin", output.intoBytes(), 0644) + if err != nil { + return err + } + } return nil } + +func testCompileLoad() error { + filePath := "../../target/testdata/host.bin" + if isNativeArm() { + filePath = "../../target/testdata/arm64.bin" + } + if isNativeX86() { + filePath = "../../target/testdata/amd64.bin" + } + + _, err := fmt.Print("starting load test. FilePath: ", filePath, " GOARCH/GOOS: ", runtime.GOARCH+"/"+runtime.GOOS, "\n") + if err != nil { + return err + } + + localAsm, err := os.ReadFile(filePath) + if err != nil { + return err + } + + calldata := []byte{} + + evmData := EvmData{} + progParams := ProgParams{ + MaxDepth: 10000, + InkPrice: 1, + DebugMode: true, + } + reqHandler := C.NativeRequestHandler{ + handle_request_fptr: (*[0]byte)(C.handleReqWrap), + id: 0, + } + + inifiniteGas := u64(0xfffffffffffffff) + + output := &rustBytes{} + + _, err = fmt.Print("launching program..\n") + if err != nil { + return err + } + + status := userStatus(C.stylus_call( + goSlice(localAsm), + goSlice(calldata), + progParams.encode(), + reqHandler, + evmData.encode(), + cbool(true), + output, + &inifiniteGas, + u32(0), + )) + + _, err = fmt.Print("returned: ", status, "\n") + if err != nil { + return err + } + + _, msg, err := status.toResult(output.intoBytes(), true) + if status == userFailure { + err = fmt.Errorf("%w: %v", err, msg) + } + + return err +} From b4f1c0a85697210ffa91aa1968b53f4a6c8a0469 Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Tue, 25 Jun 2024 17:56:33 -0600 Subject: [PATCH 10/12] fix dir permissions --- arbos/programs/testcompile.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arbos/programs/testcompile.go b/arbos/programs/testcompile.go index 3ee22a76bf..fa2311c850 100644 --- a/arbos/programs/testcompile.go +++ b/arbos/programs/testcompile.go @@ -87,7 +87,7 @@ func testCompileArch(store bool) error { if err != nil { return err } - err = os.MkdirAll("../../target/testdata", 0644) + err = os.MkdirAll("../../target/testdata", 0755) if err != nil { return err } From e78c7b96a9abdcb01c86a4afd64a4524c85c6b03 Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Tue, 25 Jun 2024 18:03:11 -0600 Subject: [PATCH 11/12] TesstCompileArch: add documentation --- arbos/programs/cgo_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arbos/programs/cgo_test.go b/arbos/programs/cgo_test.go index 6506af75dc..c0e146d98d 100644 --- a/arbos/programs/cgo_test.go +++ b/arbos/programs/cgo_test.go @@ -20,6 +20,11 @@ func TestConstants(t *testing.T) { } } +// normal test will not write anything to disk +// to test cross-compilation: +// * run test with TEST_COMPILE=STORE on one machine +// * copy target/testdata to the other machine +// * run test with TEST_COMPILE=LOAD on the other machine func TestCompileArch(t *testing.T) { compile_env := os.Getenv("TEST_COMPILE") if compile_env == "" { From 831650861ff705c5fbae6e57553dd9f42e5e9072 Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Tue, 25 Jun 2024 18:19:11 -0600 Subject: [PATCH 12/12] fix arbitrator tests --- arbitrator/stylus/src/test/api.rs | 6 ++++-- arbitrator/stylus/src/test/misc.rs | 4 ++-- arbitrator/stylus/src/test/mod.rs | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/arbitrator/stylus/src/test/api.rs b/arbitrator/stylus/src/test/api.rs index 92d7317918..074eb8cfab 100644 --- a/arbitrator/stylus/src/test/api.rs +++ b/arbitrator/stylus/src/test/api.rs @@ -14,6 +14,7 @@ use eyre::Result; use parking_lot::Mutex; use prover::programs::{memory::MemoryModel, prelude::*}; use std::{collections::HashMap, sync::Arc}; +use wasmer::Target; use super::TestInstance; @@ -53,7 +54,7 @@ impl TestEvmApi { pub fn deploy(&mut self, address: Bytes20, config: StylusConfig, name: &str) -> Result<()> { let file = format!("tests/{name}/target/wasm32-unknown-unknown/release/{name}.wasm"); let wasm = std::fs::read(file)?; - let module = native::module(&wasm, self.compile.clone())?; + let module = native::module(&wasm, self.compile.clone(), Target::default())?; self.contracts.lock().insert(address, module); self.configs.lock().insert(address, config); Ok(()) @@ -113,7 +114,8 @@ impl EvmApi for TestEvmApi { let mut native = unsafe { let contracts = self.contracts.lock(); let module = contracts.get(&contract).unwrap(); - TestInstance::deserialize(module, compile, self.clone(), evm_data).unwrap() + TestInstance::deserialize(module, compile, self.clone(), evm_data, Target::default()) + .unwrap() }; let ink = config.pricing.gas_to_ink(gas); diff --git a/arbitrator/stylus/src/test/misc.rs b/arbitrator/stylus/src/test/misc.rs index ae44a885f0..92c4394ae3 100644 --- a/arbitrator/stylus/src/test/misc.rs +++ b/arbitrator/stylus/src/test/misc.rs @@ -9,12 +9,12 @@ use crate::{ }; use eyre::Result; use prover::programs::{prelude::*, start::StartMover}; -use wasmer::{imports, Function}; +use wasmer::{imports, Function, Target}; #[test] fn test_bulk_memory() -> Result<()> { let (compile, config, ink) = test_configs(); - let mut store = compile.store(); + let mut store = compile.store(Target::default()); let filename = "../prover/test-cases/bulk-memory.wat"; let imports = imports! { "env" => { diff --git a/arbitrator/stylus/src/test/mod.rs b/arbitrator/stylus/src/test/mod.rs index cfc0ab23b7..a6033ee7bd 100644 --- a/arbitrator/stylus/src/test/mod.rs +++ b/arbitrator/stylus/src/test/mod.rs @@ -33,7 +33,7 @@ type TestInstance = NativeInstance; impl TestInstance { fn new_test(path: &str, compile: CompileConfig) -> Result { - let mut store = compile.store(); + let mut store = compile.store(Target::default()); let imports = imports! { "test" => { "noop" => Function::new_typed(&mut store, || {}),