Skip to content

Commit

Permalink
Merge branch 'stylus' into merkle-perf
Browse files Browse the repository at this point in the history
  • Loading branch information
eljobe committed May 2, 2024
2 parents cc71b32 + 46c29f5 commit 77866ec
Show file tree
Hide file tree
Showing 39 changed files with 490 additions and 334 deletions.
9 changes: 6 additions & 3 deletions arbitrator/arbutil/src/evm/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@ pub trait EvmApi<D: DataReader>: Send + 'static {
&mut self,
contract: Bytes20,
calldata: &[u8],
gas: u64,
gas_left: u64,
gas_req: u64,
value: Bytes32,
) -> (u32, u64, UserOutcomeKind);

Expand All @@ -113,7 +114,8 @@ pub trait EvmApi<D: DataReader>: Send + 'static {
&mut self,
contract: Bytes20,
calldata: &[u8],
gas: u64,
gas_left: u64,
gas_req: u64,
) -> (u32, u64, UserOutcomeKind);

/// Static-calls the contract at the given address.
Expand All @@ -123,7 +125,8 @@ pub trait EvmApi<D: DataReader>: Send + 'static {
&mut self,
contract: Bytes20,
calldata: &[u8],
gas: u64,
gas_left: u64,
gas_req: u64,
) -> (u32, u64, UserOutcomeKind);

/// Deploys a new contract using the init code provided.
Expand Down
32 changes: 23 additions & 9 deletions arbitrator/arbutil/src/evm/req.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,15 @@ impl<D: DataReader, H: RequestHandler<D>> EvmApiRequestor<D, H> {
call_type: EvmApiMethod,
contract: Bytes20,
input: &[u8],
gas: u64,
gas_left: u64,
gas_req: u64,
value: Bytes32,
) -> (u32, u64, UserOutcomeKind) {
let mut request = Vec::with_capacity(20 + 32 + 8 + input.len());
let mut request = Vec::with_capacity(20 + 32 + 8 + 8 + input.len());
request.extend(contract);
request.extend(value);
request.extend(gas.to_be_bytes());
request.extend(gas_left.to_be_bytes());
request.extend(gas_req.to_be_bytes());
request.extend(input);

let (res, data, cost) = self.request(call_type, &request);
Expand Down Expand Up @@ -164,23 +166,33 @@ impl<D: DataReader, H: RequestHandler<D>> EvmApi<D> for EvmApiRequestor<D, H> {
&mut self,
contract: Bytes20,
input: &[u8],
gas: u64,
gas_left: u64,
gas_req: u64,
value: Bytes32,
) -> (u32, u64, UserOutcomeKind) {
self.call_request(EvmApiMethod::ContractCall, contract, input, gas, value)
self.call_request(
EvmApiMethod::ContractCall,
contract,
input,
gas_left,
gas_req,
value,
)
}

fn delegate_call(
&mut self,
contract: Bytes20,
input: &[u8],
gas: u64,
gas_left: u64,
gas_req: u64,
) -> (u32, u64, UserOutcomeKind) {
self.call_request(
EvmApiMethod::DelegateCall,
contract,
input,
gas,
gas_left,
gas_req,
Bytes32::default(),
)
}
Expand All @@ -189,13 +201,15 @@ impl<D: DataReader, H: RequestHandler<D>> EvmApi<D> for EvmApiRequestor<D, H> {
&mut self,
contract: Bytes20,
input: &[u8],
gas: u64,
gas_left: u64,
gas_req: u64,
) -> (u32, u64, UserOutcomeKind) {
self.call_request(
EvmApiMethod::StaticCall,
contract,
input,
gas,
gas_left,
gas_req,
Bytes32::default(),
)
}
Expand Down
16 changes: 9 additions & 7 deletions arbitrator/jit/src/program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,28 +23,30 @@ pub fn activate(
wasm_size: u32,
pages_ptr: GuestPtr,
asm_estimate_ptr: GuestPtr,
init_gas_ptr: GuestPtr,
cached_init_gas_ptr: GuestPtr,
init_cost_ptr: GuestPtr,
cached_init_cost_ptr: GuestPtr,
version: u16,
debug: u32,
codehash: GuestPtr,
module_hash_ptr: GuestPtr,
gas_ptr: GuestPtr,
err_buf: GuestPtr,
err_buf_len: u32,
) -> Result<u32, Escape> {
let (mut mem, _) = env.jit_env();
let wasm = mem.read_slice(wasm_ptr, wasm_size as usize);
let codehash = &mem.read_bytes32(codehash);
let debug = debug != 0;

let page_limit = mem.read_u16(pages_ptr);
let gas_left = &mut mem.read_u64(gas_ptr);
match Module::activate(&wasm, version, page_limit, debug, gas_left) {
match Module::activate(&wasm, codehash, version, page_limit, debug, gas_left) {
Ok((module, data)) => {
mem.write_u64(gas_ptr, *gas_left);
mem.write_u16(pages_ptr, data.footprint);
mem.write_u32(asm_estimate_ptr, data.asm_estimate);
mem.write_u16(init_gas_ptr, data.init_gas);
mem.write_u16(cached_init_gas_ptr, data.cached_init_gas);
mem.write_u16(init_cost_ptr, data.init_cost);
mem.write_u16(cached_init_cost_ptr, data.cached_init_cost);
mem.write_bytes32(module_hash_ptr, module.hash());
Ok(0)
}
Expand All @@ -55,8 +57,8 @@ pub fn activate(
mem.write_u64(gas_ptr, 0);
mem.write_u16(pages_ptr, 0);
mem.write_u32(asm_estimate_ptr, 0);
mem.write_u16(init_gas_ptr, 0);
mem.write_u16(cached_init_gas_ptr, 0);
mem.write_u16(init_cost_ptr, 0);
mem.write_u16(cached_init_cost_ptr, 0);
mem.write_bytes32(module_hash_ptr, Bytes32::default());
Ok(err_bytes.len() as u32)
}
Expand Down
113 changes: 79 additions & 34 deletions arbitrator/prover/src/binary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::{
},
value::{ArbValueType, FunctionType, IntegerValType, Value},
};
use arbutil::{math::SaturatingSum, Color, DebugColor};
use arbutil::{math::SaturatingSum, Bytes32, Color, DebugColor};
use eyre::{bail, ensure, eyre, Result, WrapErr};
use fnv::{FnvHashMap as HashMap, FnvHashSet as HashSet};
use nom::{
Expand All @@ -20,7 +20,7 @@ use nom::{
};
use serde::{Deserialize, Serialize};
use std::{convert::TryInto, fmt::Debug, hash::Hash, mem, path::Path, str::FromStr};
use wasmer_types::{entity::EntityRef, FunctionIndex, LocalFunctionIndex};
use wasmer_types::{entity::EntityRef, ExportIndex, FunctionIndex, LocalFunctionIndex};
use wasmparser::{
Data, Element, ExternalKind, MemoryType, Name, NameSectionReader, Naming, Operator, Parser,
Payload, TableType, TypeRef, ValType, Validator, WasmFeatures,
Expand Down Expand Up @@ -232,17 +232,27 @@ pub enum ExportKind {
Tag,
}

impl TryFrom<ExternalKind> for ExportKind {
type Error = eyre::Error;

fn try_from(kind: ExternalKind) -> Result<Self> {
impl From<ExternalKind> for ExportKind {
fn from(kind: ExternalKind) -> Self {
use ExternalKind as E;
match kind {
E::Func => Ok(Self::Func),
E::Table => Ok(Self::Table),
E::Memory => Ok(Self::Memory),
E::Global => Ok(Self::Global),
E::Tag => Ok(Self::Tag),
E::Func => Self::Func,
E::Table => Self::Table,
E::Memory => Self::Memory,
E::Global => Self::Global,
E::Tag => Self::Tag,
}
}
}

impl From<ExportIndex> for ExportKind {
fn from(value: ExportIndex) -> Self {
use ExportIndex as E;
match value {
E::Function(_) => Self::Func,
E::Table(_) => Self::Table,
E::Memory(_) => Self::Memory,
E::Global(_) => Self::Global,
}
}
}
Expand Down Expand Up @@ -271,7 +281,7 @@ pub type ExportMap = HashMap<String, (u32, ExportKind)>;
pub struct WasmBinary<'a> {
pub types: Vec<FunctionType>,
pub imports: Vec<FuncImport<'a>>,
/// Maps *local* function indices to global type signatures
/// Maps *local* function indices to global type signatures.
pub functions: Vec<u32>,
pub tables: Vec<TableType>,
pub memories: Vec<MemoryType>,
Expand All @@ -282,6 +292,10 @@ pub struct WasmBinary<'a> {
pub codes: Vec<Code<'a>>,
pub datas: Vec<Data<'a>>,
pub names: NameCustomSection,
/// The source wasm, if known.
pub wasm: Option<&'a [u8]>,
/// Consensus data used to make module hashes unique.
pub extra_data: Vec<u8>,
}

pub fn parse<'a>(input: &'a [u8], path: &'_ Path) -> Result<WasmBinary<'a>> {
Expand Down Expand Up @@ -312,7 +326,10 @@ pub fn parse<'a>(input: &'a [u8], path: &'_ Path) -> Result<WasmBinary<'a>> {
.validate_all(input)
.wrap_err_with(|| eyre!("failed to validate {}", path.to_string_lossy().red()))?;

let mut binary = WasmBinary::default();
let mut binary = WasmBinary {
wasm: Some(input),
..Default::default()
};
let sections: Vec<_> = Parser::new(0).parse_all(input).collect::<Result<_, _>>()?;

for section in sections {
Expand Down Expand Up @@ -390,14 +407,7 @@ pub fn parse<'a>(input: &'a [u8], path: &'_ Path) -> Result<WasmBinary<'a>> {
let name = || name.clone();
binary.names.functions.entry(index).or_insert_with(name);
}

// TODO: we'll only support the types also in wasmparser 0.95+
if matches!(kind, E::Func | E::Table | E::Memory | E::Global | E::Tag) {
let kind = kind.try_into()?;
binary.exports.insert(name, (export.index, kind));
} else {
bail!("unsupported export kind {:?}", export)
}
binary.exports.insert(name, (export.index, kind.into()));
}
}
FunctionSection(functions) => process!(binary.functions, functions),
Expand Down Expand Up @@ -508,18 +518,22 @@ impl<'a> Debug for WasmBinary<'a> {

impl<'a> WasmBinary<'a> {
/// Instruments a user wasm, producing a version bounded via configurable instrumentation.
pub fn instrument(&mut self, compile: &CompileConfig) -> Result<StylusData> {
pub fn instrument(
&mut self,
compile: &CompileConfig,
codehash: &Bytes32,
) -> Result<StylusData> {
let start = StartMover::new(compile.debug.debug_info);
let meter = Meter::new(&compile.pricing);
let dygas = DynamicMeter::new(&compile.pricing);
let depth = DepthChecker::new(compile.bounds);
let bound = HeapBound::new(compile.bounds);
let start = StartMover::default();

start.update_module(self)?;
meter.update_module(self)?;
dygas.update_module(self)?;
depth.update_module(self)?;
bound.update_module(self)?;
start.update_module(self)?;

let count = compile.debug.count_ops.then(Counter::new);
if let Some(count) = &count {
Expand Down Expand Up @@ -550,11 +564,11 @@ impl<'a> WasmBinary<'a> {

// add the instrumentation in the order of application
// note: this must be consistent with native execution
apply!(start);
apply!(meter);
apply!(dygas);
apply!(depth);
apply!(bound);
apply!(start);

if let Some(count) = &count {
apply!(*count);
Expand All @@ -563,29 +577,59 @@ impl<'a> WasmBinary<'a> {
code.expr = build;
}

let wasm = self.wasm.take().unwrap();
self.extra_data.extend(*codehash);
self.extra_data.extend(compile.version.to_be_bytes());

// 4GB maximum implies `footprint` fits in a u16
let footprint = self.memory_info()?.min.0 as u16;

// check the entrypoint
let ty = FunctionType::new([ArbValueType::I32], [ArbValueType::I32]);
let user_main = self.check_func(STYLUS_ENTRY_POINT, ty)?;

// naively assume for now an upper bound of 5Mb
let asm_estimate = 5 * 1024 * 1024;
// predict costs
let funcs = self.codes.len() as u64;
let globals = self.globals.len() as u64;
let wasm_len = wasm.len() as u64;

let data_len: u64 = self.datas.iter().map(|x| x.range.len() as u64).sum();
let elem_len: u64 = self.elements.iter().map(|x| x.range.len() as u64).sum();
let data_len = data_len + elem_len;

let mut type_len = 0;
for index in &self.functions {
let ty = &self.types[*index as usize];
type_len += (ty.inputs.len() + ty.outputs.len()) as u64;
}

let mut asm_estimate: u64 = 512000;
asm_estimate = asm_estimate.saturating_add(funcs.saturating_mul(996829) / 1000);
asm_estimate = asm_estimate.saturating_add(type_len.saturating_mul(11416) / 1000);
asm_estimate = asm_estimate.saturating_add(wasm_len.saturating_mul(62628) / 10000);

let mut cached_init: u64 = 0;
cached_init = cached_init.saturating_add(funcs.saturating_mul(13420) / 100_000);
cached_init = cached_init.saturating_add(type_len.saturating_mul(89) / 100_000);
cached_init = cached_init.saturating_add(wasm_len.saturating_mul(122) / 100_000);
cached_init = cached_init.saturating_add(globals.saturating_mul(1628) / 1000);
cached_init = cached_init.saturating_add(data_len.saturating_mul(75244) / 100_000);
cached_init = cached_init.saturating_add(footprint as u64 * 5);

// TODO: determine safe value
let init_gas = 4096;
let cached_init_gas = 1024;
let mut init = cached_init;
init = init.saturating_add(funcs.saturating_mul(8252) / 1000);
init = init.saturating_add(type_len.saturating_mul(1059) / 1000);
init = init.saturating_add(wasm_len.saturating_mul(1286) / 10_000);

let [ink_left, ink_status] = meter.globals();
let depth_left = depth.globals();
Ok(StylusData {
ink_left: ink_left.as_u32(),
ink_status: ink_status.as_u32(),
depth_left: depth_left.as_u32(),
init_gas,
cached_init_gas,
asm_estimate,
init_cost: init.try_into()?,
cached_init_cost: cached_init.try_into()?,
asm_estimate: asm_estimate.try_into()?,
footprint,
user_main,
})
Expand All @@ -596,9 +640,10 @@ impl<'a> WasmBinary<'a> {
wasm: &'a [u8],
page_limit: u16,
compile: &CompileConfig,
codehash: &Bytes32,
) -> Result<(WasmBinary<'a>, StylusData)> {
let mut bin = parse(wasm, Path::new("user"))?;
let stylus_data = bin.instrument(compile)?;
let stylus_data = bin.instrument(compile, codehash)?;

let Some(memory) = bin.memories.first() else {
bail!("missing memory with export name \"memory\"")
Expand Down
Loading

0 comments on commit 77866ec

Please sign in to comment.