#![allow(clippy::expect_used)]
use core::hint::black_box;
use std::sync::Arc;
use criterion::BatchSize;
use criterion::BenchmarkId;
use criterion::Criterion;
use criterion::Throughput;
use criterion::criterion_group;
use criterion::criterion_main;
use polyplug::Runtime;
use polyplug_abi::AbiError;
use polyplug_abi::DispatchMechanisms;
use polyplug_abi::DispatchType;
use polyplug_abi::GuestContractInstance;
use polyplug_abi::GuestContractInterface;
use polyplug_abi::HostApi;
use polyplug_abi::NativeDispatch;
use polyplug_abi::PluginDescriptor;
use polyplug_abi::StringView;
use polyplug_abi::types::Version;
use polyplug_utils::BundleId;
use polyplug_utils::GuestContractId;
#[repr(C)]
struct AddArgs {
a: u32,
b: u32,
}
unsafe extern "C" fn bench_add(
_instance: GuestContractInstance,
args: *const (),
out: *mut (),
out_err: *mut AbiError,
) {
unsafe {
let a: &AddArgs = &*(args as *const AddArgs);
*(out as *mut u32) = a.a.wrapping_add(a.b);
}
if !out_err.is_null() {
unsafe { out_err.write(AbiError::ok()) };
}
}
unsafe extern "C" fn noop_create_instance(
_loader_data: polyplug_abi::dispatch::VmLoaderData,
_host: *const HostApi,
_args: *const (),
out_instance: *mut GuestContractInstance,
) {
if !out_instance.is_null() {
unsafe { out_instance.write(GuestContractInstance::null()) };
}
}
unsafe extern "C" fn noop_destroy_instance(
_loader_data: polyplug_abi::dispatch::VmLoaderData,
_host: *const HostApi,
_instance: GuestContractInstance,
) {
}
fn leak_native_interface(contract_id: u64) -> &'static GuestContractInterface {
let functions: &'static [*const (); 1] = Box::leak(Box::new([bench_add as *const ()]));
Box::leak(Box::new(GuestContractInterface {
contract_id: GuestContractId::from_u64(contract_id),
contract_version: Version {
major: 1,
minor: 0,
patch: 0,
},
dispatch_type: DispatchType::Native,
create_instance: noop_create_instance,
destroy_instance: noop_destroy_instance,
dispatch: DispatchMechanisms {
native: NativeDispatch {
function_count: 1,
functions: functions.as_ptr(),
},
},
}))
}
fn runtime_with_provider(contract_id: u64) -> Arc<Runtime> {
let runtime: Arc<Runtime> = Runtime::builder()
.build()
.expect("bare runtime build should succeed");
let interface: &'static GuestContractInterface = leak_native_interface(contract_id);
let descriptor: PluginDescriptor = PluginDescriptor {
name: StringView::from_static(b"cold-start-provider"),
contract_name: StringView::from_static(b"coldstart.contract"),
version: Version {
major: 1,
minor: 0,
patch: 0,
},
};
unsafe {
runtime.registry().register_guest_contract(
descriptor,
interface,
"coldstart.contract".to_owned(),
BundleId::from_u64(0x3333_u64),
)
}
.expect("provider registration should succeed");
runtime
}
fn find_resolve_dispatch(runtime: &Runtime, contract_id: u64) -> u32 {
let handle = runtime
.find_guest_contract(contract_id, 0)
.expect("find_guest_contract must succeed");
let interface_ptr: *const GuestContractInterface = runtime
.resolve_guest_contract(handle)
.expect("resolve_guest_contract must succeed");
let interface: &GuestContractInterface = unsafe { &*interface_ptr };
dispatch_fn0(interface)
}
fn dispatch_fn0(interface: &GuestContractInterface) -> u32 {
let fn_ptr: *const () = unsafe { *interface.dispatch.native.functions };
let dispatch_fn: unsafe extern "C" fn(
GuestContractInstance,
*const (),
*mut (),
*mut AbiError,
) = unsafe { core::mem::transmute(fn_ptr) };
let instance: GuestContractInstance = GuestContractInstance {
data: core::ptr::null_mut(),
contract_id: GuestContractId::from_u64(interface.contract_id.id()),
};
let args: AddArgs = AddArgs {
a: 42_u32,
b: 57_u32,
};
let mut out: u32 = 0_u32;
let mut err: AbiError = AbiError::ok();
unsafe {
dispatch_fn(
instance,
&args as *const AddArgs as *const (),
&mut out as *mut u32 as *mut (),
&mut err,
)
};
black_box(err);
out
}
fn bench_cold_start(c: &mut Criterion) {
let contract_id: u64 = GuestContractId::new("coldstart.contract", 1).id();
let mut group: criterion::BenchmarkGroup<'_, criterion::measurement::WallTime> =
c.benchmark_group("cold_start");
group.throughput(Throughput::Elements(1));
group.bench_function(BenchmarkId::new("cold", "first_dispatch"), |b| {
b.iter_batched(
|| runtime_with_provider(contract_id),
|runtime: Arc<Runtime>| {
let sum: u32 = find_resolve_dispatch(&runtime, black_box(contract_id));
black_box(sum);
runtime
},
BatchSize::SmallInput,
);
});
let warm_runtime: Arc<Runtime> = runtime_with_provider(contract_id);
group.bench_function(BenchmarkId::new("warm", "find_resolve_dispatch"), |b| {
b.iter(|| {
let sum: u32 = find_resolve_dispatch(&warm_runtime, black_box(contract_id));
black_box(sum);
});
});
let cached_handle = warm_runtime
.find_guest_contract(contract_id, 0)
.expect("find for cached bench");
let cached_iface_ptr: *const GuestContractInterface = warm_runtime
.resolve_guest_contract(cached_handle)
.expect("resolve for cached bench");
let cached_iface: &GuestContractInterface = unsafe { &*cached_iface_ptr };
group.bench_function(BenchmarkId::new("warm", "cached_dispatch"), |b| {
b.iter(|| {
let sum: u32 = dispatch_fn0(black_box(cached_iface));
black_box(sum);
});
});
group.finish();
core::mem::forget(warm_runtime);
}
criterion_group!(benches, bench_cold_start);
criterion_main!(benches);