Skip to content

Commit

Permalink
feat: Add ability to import methods guarded by protocol feature (#3826)
Browse files Browse the repository at this point in the history
Modifies `wrapped_import` macro to add an ability to introduce new host methods.
This change doesn't modify the protocol.

## Test plan:
- CI
- #3827 for an example of adding a new import
  • Loading branch information
Evgeny Kuzyakov authored Jan 21, 2021
1 parent 527d743 commit 0ef1b85
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 67 deletions.
146 changes: 81 additions & 65 deletions runtime/near-vm-runner/src/imports.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use near_primitives::version::ProtocolVersion;
use near_vm_logic::VMLogic;

use std::ffi::c_void;
Expand All @@ -24,93 +25,105 @@ macro_rules! rust2wasm {
}

macro_rules! wrapped_imports {
( $( $func:ident < [ $( $arg_name:ident : $arg_type:ident ),* ] -> [ $( $returns:ident ),* ] >, )* ) => {
( $($(#[$feature_name:tt, $feature:ident])* $func:ident < [ $( $arg_name:ident : $arg_type:ident ),* ] -> [ $( $returns:ident ),* ] >, )* ) => {
pub mod wasmer_ext {
use near_vm_logic::VMLogic;
use wasmer_runtime::Ctx;
type VMResult<T> = ::std::result::Result<T, near_vm_logic::VMLogicError>;
$(
#[allow(unused_parens)]
pub fn $func( ctx: &mut Ctx, $( $arg_name: $arg_type ),* ) -> VMResult<($( $returns ),*)> {
let logic: &mut VMLogic<'_> = unsafe { &mut *(ctx.data as *mut VMLogic<'_>) };
logic.$func( $( $arg_name, )* )
}
)*
use near_vm_logic::VMLogic;
use wasmer_runtime::Ctx;
type VMResult<T> = ::std::result::Result<T, near_vm_logic::VMLogicError>;
$(
#[allow(unused_parens)]
$(#[cfg(feature = $feature_name)])*
pub fn $func( ctx: &mut Ctx, $( $arg_name: $arg_type ),* ) -> VMResult<($( $returns ),*)> {
let logic: &mut VMLogic<'_> = unsafe { &mut *(ctx.data as *mut VMLogic<'_>) };
logic.$func( $( $arg_name, )* )
}
)*
}

#[cfg(feature = "wasmtime_vm")]
pub mod wasmtime_ext {
use near_vm_logic::{VMLogic, VMLogicError};
use std::ffi::c_void;
use std::cell::{RefCell, UnsafeCell};
use wasmtime::Trap;
use near_vm_logic::{VMLogic, VMLogicError};
use std::ffi::c_void;
use std::cell::{RefCell, UnsafeCell};
use wasmtime::Trap;

thread_local! {
pub static CALLER_CONTEXT: UnsafeCell<*mut c_void> = UnsafeCell::new(0 as *mut c_void);
pub static EMBEDDER_ERROR: RefCell<Option<VMLogicError>> = RefCell::new(None);
}
thread_local! {
pub static CALLER_CONTEXT: UnsafeCell<*mut c_void> = UnsafeCell::new(0 as *mut c_void);
pub static EMBEDDER_ERROR: RefCell<Option<VMLogicError>> = RefCell::new(None);
}

type VMResult<T> = ::std::result::Result<T, Trap>;
$(
#[allow(unused_parens)]
#[cfg(feature = "wasmtime_vm")]
pub fn $func( $( $arg_name: rust2wasm!($arg_type) ),* ) -> VMResult<($( rust2wasm!($returns)),*)> {
let data = CALLER_CONTEXT.with(|caller_context| {
unsafe {
*caller_context.get()
}
});
let logic: &mut VMLogic<'_> = unsafe { &mut *(data as *mut VMLogic<'_>) };
match logic.$func( $( $arg_name as $arg_type, )* ) {
Ok(result) => Ok(result as ($( rust2wasm!($returns) ),* ) ),
Err(err) => {
// Wasmtime doesn't have proper mechanism for wrapping custom errors
// into traps. So, just store error into TLS and use special exit code here.
EMBEDDER_ERROR.with(|embedder_error| {
*embedder_error.borrow_mut() = Some(err)
});
Err(Trap::i32_exit(239))
type VMResult<T> = ::std::result::Result<T, Trap>;
$(
#[allow(unused_parens)]
#[cfg(all(feature = "wasmtime_vm" $(, feature = $feature_name)*))]
pub fn $func( $( $arg_name: rust2wasm!($arg_type) ),* ) -> VMResult<($( rust2wasm!($returns)),*)> {
let data = CALLER_CONTEXT.with(|caller_context| {
unsafe {
*caller_context.get()
}
});
let logic: &mut VMLogic<'_> = unsafe { &mut *(data as *mut VMLogic<'_>) };
match logic.$func( $( $arg_name as $arg_type, )* ) {
Ok(result) => Ok(result as ($( rust2wasm!($returns) ),* ) ),
Err(err) => {
// Wasmtime doesn't have proper mechanism for wrapping custom errors
// into traps. So, just store error into TLS and use special exit code here.
EMBEDDER_ERROR.with(|embedder_error| {
*embedder_error.borrow_mut() = Some(err)
});
Err(Trap::i32_exit(239))
}
}
}
}
)*
)*
}

pub(crate) fn build_wasmer(memory: wasmer_runtime::memory::Memory, logic: &mut VMLogic<'_>) ->
wasmer_runtime::ImportObject {
#[allow(unused_variables)]
pub(crate) fn build_wasmer(
memory: wasmer_runtime::memory::Memory,
logic: &mut VMLogic<'_>,
protocol_version: ProtocolVersion,
) -> wasmer_runtime::ImportObject {
let raw_ptr = logic as *mut _ as *mut c_void;
let import_reference = ImportReference(raw_ptr);
wasmer_runtime::imports! {
move || {
let dtor = (|_: *mut c_void| {}) as fn(*mut c_void);
(import_reference.0, dtor)
},
"env" => {
"memory" => memory,
$(
stringify!($func) => wasmer_runtime::func!(wasmer_ext::$func),
)*
},
}
let mut import_object = wasmer_runtime::ImportObject::new_with_data(move || {
let dtor = (|_: *mut c_void| {}) as fn(*mut c_void);
(import_reference.0, dtor)
});

let mut ns = wasmer_runtime_core::import::Namespace::new();
ns.insert("memory", memory);
$({
$(#[cfg(feature = $feature_name)])*
if true $(&& near_primitives::checked_feature!($feature_name, $feature, protocol_version))* {
ns.insert(stringify!($func), wasmer_runtime::func!(wasmer_ext::$func));
}
})*

import_object.register("env", ns);
import_object
}

#[cfg(feature = "wasmtime_vm")]
#[allow(unused_variables)]
pub(crate) fn link_wasmtime(
linker: &mut wasmtime::Linker,
memory: wasmtime::Memory,
raw_logic: *mut c_void,
) {
linker: &mut wasmtime::Linker,
memory: wasmtime::Memory,
raw_logic: *mut c_void,
protocol_version: ProtocolVersion,
) {
wasmtime_ext::CALLER_CONTEXT.with(|caller_context| {
unsafe {
*caller_context.get() = raw_logic
}
});
linker.define("env", "memory", memory).
expect("cannot define memory");
$(
linker.func("env", stringify!($func), wasmtime_ext::$func).
expect("cannot link external");
)*
linker.define("env", "memory", memory).expect("cannot define memory");
$({
$(#[cfg(feature = $feature_name)])*
if true $(&& near_primitives::checked_feature!($feature_name, $feature, protocol_version))* {
linker.func("env", stringify!($func), wasmtime_ext::$func).expect("cannot link external");
}
})*
}

#[cfg(feature = "wasmtime_vm")]
Expand Down Expand Up @@ -264,4 +277,7 @@ wrapped_imports! {
// ###############
validator_stake<[account_id_len: u64, account_id_ptr: u64, stake_ptr: u64] -> []>,
validator_total_stake<[stake_ptr: u64] -> []>,
// ###############
// An example to add a protocol feature guarded host method
// #["protocol_feature_evm", EVM] test_api<[a: u64] -> []>,
}
2 changes: 1 addition & 1 deletion runtime/near-vm-runner/src/wasmer_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ pub fn run_wasmer<'a>(
);
}

let import_object = imports::build_wasmer(memory_copy, &mut logic);
let import_object = imports::build_wasmer(memory_copy, &mut logic, current_protocol_version);

let method_name = match std::str::from_utf8(method_name) {
Ok(x) => x,
Expand Down
2 changes: 1 addition & 1 deletion runtime/near-vm-runner/src/wasmtime_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ pub mod wasmtime_runner {
// Unfortunately, due to the Wasmtime implementation we have to do tricks with the
// lifetimes of the logic instance and pass raw pointers here.
let raw_logic = &mut logic as *mut _ as *mut c_void;
imports::link_wasmtime(&mut linker, memory_copy, raw_logic);
imports::link_wasmtime(&mut linker, memory_copy, raw_logic, current_protocol_version);
let func_name = match str::from_utf8(method_name) {
Ok(name) => name,
Err(_) => {
Expand Down

0 comments on commit 0ef1b85

Please sign in to comment.