diff --git a/internal/api/bindings.h b/internal/api/bindings.h index 6bb960b4d..bf03c9e24 100644 --- a/internal/api/bindings.h +++ b/internal/api/bindings.h @@ -139,8 +139,9 @@ typedef struct ByteSliceView { * // … * # let mut error_msg = UnmanagedVector::default(); * # let mut used_gas = 0_u64; + * # let read_db = db.vtable.read_db.unwrap(); * - * let go_error: GoError = (db.vtable.read_db)( + * let go_error: GoError = read_db( * db.state, * db.gas_meter, * &mut used_gas as *mut u64, @@ -253,7 +254,7 @@ typedef struct iterator_t { uint64_t iterator_index; } iterator_t; -typedef struct Iterator_vtable { +typedef struct IteratorVtable { int32_t (*next)(struct iterator_t, struct gas_meter_t*, uint64_t*, @@ -270,15 +271,15 @@ typedef struct Iterator_vtable { uint64_t*, struct UnmanagedVector*, struct UnmanagedVector*); -} Iterator_vtable; +} IteratorVtable; typedef struct GoIter { struct gas_meter_t *gas_meter; struct iterator_t state; - struct Iterator_vtable vtable; + struct IteratorVtable vtable; } GoIter; -typedef struct Db_vtable { +typedef struct DbVtable { int32_t (*read_db)(struct db_t*, struct gas_meter_t*, uint64_t*, @@ -304,19 +305,19 @@ typedef struct Db_vtable { int32_t, struct GoIter*, struct UnmanagedVector*); -} Db_vtable; +} DbVtable; typedef struct Db { struct gas_meter_t *gas_meter; struct db_t *state; - struct Db_vtable vtable; + struct DbVtable vtable; } Db; typedef struct api_t { uint8_t _private[0]; } api_t; -typedef struct GoApi_vtable { +typedef struct GoApiVtable { int32_t (*humanize_address)(const struct api_t*, struct U8SliceView, struct UnmanagedVector*, @@ -327,29 +328,29 @@ typedef struct GoApi_vtable { struct UnmanagedVector*, struct UnmanagedVector*, uint64_t*); -} GoApi_vtable; +} GoApiVtable; typedef struct GoApi { const struct api_t *state; - struct GoApi_vtable vtable; + struct GoApiVtable vtable; } GoApi; typedef struct querier_t { uint8_t _private[0]; } querier_t; -typedef struct Querier_vtable { +typedef struct QuerierVtable { int32_t (*query_external)(const struct querier_t*, uint64_t, uint64_t*, struct U8SliceView, struct UnmanagedVector*, struct UnmanagedVector*); -} Querier_vtable; +} QuerierVtable; typedef struct GoQuerier { const struct querier_t *state; - struct Querier_vtable vtable; + struct QuerierVtable vtable; } GoQuerier; typedef struct GasReport { diff --git a/internal/api/callbacks.go b/internal/api/callbacks.go index 2f945dfbc..dc5db74b4 100644 --- a/internal/api/callbacks.go +++ b/internal/api/callbacks.go @@ -89,7 +89,7 @@ func recoverPanic(ret *C.GoError) { /****** DB ********/ -var db_vtable = C.Db_vtable{ +var db_vtable = C.DbVtable{ read_db: C.any_function_t(C.cGet_cgo), write_db: C.any_function_t(C.cSet_cgo), remove_db: C.any_function_t(C.cDelete_cgo), @@ -124,7 +124,7 @@ func buildDB(state *DBState, gm *types.GasMeter) C.Db { } } -var iterator_vtable = C.Iterator_vtable{ +var iterator_vtable = C.IteratorVtable{ next: C.any_function_t(C.cNext_cgo), next_key: C.any_function_t(C.cNextKey_cgo), next_value: C.any_function_t(C.cNextValue_cgo), @@ -358,7 +358,7 @@ func nextPart(ref C.iterator_t, gasMeter *C.gas_meter_t, usedGas *cu64, output * return C.GoError_None } -var api_vtable = C.GoApi_vtable{ +var api_vtable = C.GoApiVtable{ humanize_address: C.any_function_t(C.cHumanAddress_cgo), canonicalize_address: C.any_function_t(C.cCanonicalAddress_cgo), } @@ -429,7 +429,7 @@ func cCanonicalAddress(ptr *C.api_t, src C.U8SliceView, dest *C.UnmanagedVector, /****** Go Querier ********/ -var querier_vtable = C.Querier_vtable{ +var querier_vtable = C.QuerierVtable{ query_external: C.any_function_t(C.cQueryExternal_cgo), } diff --git a/libwasmvm/bindings.h b/libwasmvm/bindings.h index 6bb960b4d..bf03c9e24 100644 --- a/libwasmvm/bindings.h +++ b/libwasmvm/bindings.h @@ -139,8 +139,9 @@ typedef struct ByteSliceView { * // … * # let mut error_msg = UnmanagedVector::default(); * # let mut used_gas = 0_u64; + * # let read_db = db.vtable.read_db.unwrap(); * - * let go_error: GoError = (db.vtable.read_db)( + * let go_error: GoError = read_db( * db.state, * db.gas_meter, * &mut used_gas as *mut u64, @@ -253,7 +254,7 @@ typedef struct iterator_t { uint64_t iterator_index; } iterator_t; -typedef struct Iterator_vtable { +typedef struct IteratorVtable { int32_t (*next)(struct iterator_t, struct gas_meter_t*, uint64_t*, @@ -270,15 +271,15 @@ typedef struct Iterator_vtable { uint64_t*, struct UnmanagedVector*, struct UnmanagedVector*); -} Iterator_vtable; +} IteratorVtable; typedef struct GoIter { struct gas_meter_t *gas_meter; struct iterator_t state; - struct Iterator_vtable vtable; + struct IteratorVtable vtable; } GoIter; -typedef struct Db_vtable { +typedef struct DbVtable { int32_t (*read_db)(struct db_t*, struct gas_meter_t*, uint64_t*, @@ -304,19 +305,19 @@ typedef struct Db_vtable { int32_t, struct GoIter*, struct UnmanagedVector*); -} Db_vtable; +} DbVtable; typedef struct Db { struct gas_meter_t *gas_meter; struct db_t *state; - struct Db_vtable vtable; + struct DbVtable vtable; } Db; typedef struct api_t { uint8_t _private[0]; } api_t; -typedef struct GoApi_vtable { +typedef struct GoApiVtable { int32_t (*humanize_address)(const struct api_t*, struct U8SliceView, struct UnmanagedVector*, @@ -327,29 +328,29 @@ typedef struct GoApi_vtable { struct UnmanagedVector*, struct UnmanagedVector*, uint64_t*); -} GoApi_vtable; +} GoApiVtable; typedef struct GoApi { const struct api_t *state; - struct GoApi_vtable vtable; + struct GoApiVtable vtable; } GoApi; typedef struct querier_t { uint8_t _private[0]; } querier_t; -typedef struct Querier_vtable { +typedef struct QuerierVtable { int32_t (*query_external)(const struct querier_t*, uint64_t, uint64_t*, struct U8SliceView, struct UnmanagedVector*, struct UnmanagedVector*); -} Querier_vtable; +} QuerierVtable; typedef struct GoQuerier { const struct querier_t *state; - struct Querier_vtable vtable; + struct QuerierVtable vtable; } GoQuerier; typedef struct GasReport { diff --git a/libwasmvm/src/api.rs b/libwasmvm/src/api.rs index e62f39fe1..b1784d8e0 100644 --- a/libwasmvm/src/api.rs +++ b/libwasmvm/src/api.rs @@ -2,6 +2,7 @@ use cosmwasm_vm::{BackendApi, BackendError, BackendResult, GasInfo}; use crate::error::GoError; use crate::memory::{U8SliceView, UnmanagedVector}; +use crate::Vtable; // this represents something passed in from the caller side of FFI // in this case a struct with go function pointers @@ -13,29 +14,35 @@ pub struct api_t { // These functions should return GoError but because we don't trust them here, we treat the return value as i32 // and then check it when converting to GoError manually #[repr(C)] -#[derive(Copy, Clone)] -pub struct GoApi_vtable { - pub humanize_address: extern "C" fn( - *const api_t, - U8SliceView, - *mut UnmanagedVector, // human output - *mut UnmanagedVector, // error message output - *mut u64, - ) -> i32, - pub canonicalize_address: extern "C" fn( - *const api_t, - U8SliceView, - *mut UnmanagedVector, // canonical output - *mut UnmanagedVector, // error message output - *mut u64, - ) -> i32, +#[derive(Copy, Clone, Default)] +pub struct GoApiVtable { + pub humanize_address: Option< + extern "C" fn( + *const api_t, + U8SliceView, + *mut UnmanagedVector, // human output + *mut UnmanagedVector, // error message output + *mut u64, + ) -> i32, + >, + pub canonicalize_address: Option< + extern "C" fn( + *const api_t, + U8SliceView, + *mut UnmanagedVector, // canonical output + *mut UnmanagedVector, // error message output + *mut u64, + ) -> i32, + >, } +impl Vtable for GoApiVtable {} + #[repr(C)] #[derive(Copy, Clone)] pub struct GoApi { pub state: *const api_t, - pub vtable: GoApi_vtable, + pub vtable: GoApiVtable, } // We must declare that these are safe to Send, to use in wasm. @@ -50,7 +57,11 @@ impl BackendApi for GoApi { let mut output = UnmanagedVector::default(); let mut error_msg = UnmanagedVector::default(); let mut used_gas = 0_u64; - let go_error: GoError = (self.vtable.canonicalize_address)( + let canonicalize_address = self + .vtable + .canonicalize_address + .expect("vtable function 'canonicalize_address' not set"); + let go_error: GoError = canonicalize_address( self.state, U8SliceView::new(Some(human.as_bytes())), &mut output as *mut UnmanagedVector, @@ -79,7 +90,11 @@ impl BackendApi for GoApi { let mut output = UnmanagedVector::default(); let mut error_msg = UnmanagedVector::default(); let mut used_gas = 0_u64; - let go_error: GoError = (self.vtable.humanize_address)( + let humanize_address = self + .vtable + .humanize_address + .expect("vtable function 'humanize_address' not set"); + let go_error: GoError = humanize_address( self.state, U8SliceView::new(Some(canonical)), &mut output as *mut UnmanagedVector, diff --git a/libwasmvm/src/db.rs b/libwasmvm/src/db.rs index c34fec57f..f762c92ed 100644 --- a/libwasmvm/src/db.rs +++ b/libwasmvm/src/db.rs @@ -1,6 +1,7 @@ use crate::gas_meter::gas_meter_t; use crate::iterator::GoIter; use crate::memory::{U8SliceView, UnmanagedVector}; +use crate::vtables::Vtable; // this represents something passed in from the caller side of FFI #[repr(C)] @@ -11,48 +12,59 @@ pub struct db_t { // These functions should return GoError but because we don't trust them here, we treat the return value as i32 // and then check it when converting to GoError manually #[repr(C)] -pub struct Db_vtable { - pub read_db: extern "C" fn( - *mut db_t, - *mut gas_meter_t, - *mut u64, - U8SliceView, - *mut UnmanagedVector, // result output - *mut UnmanagedVector, // error message output - ) -> i32, - pub write_db: extern "C" fn( - *mut db_t, - *mut gas_meter_t, - *mut u64, - U8SliceView, - U8SliceView, - *mut UnmanagedVector, // error message output - ) -> i32, - pub remove_db: extern "C" fn( - *mut db_t, - *mut gas_meter_t, - *mut u64, - U8SliceView, - *mut UnmanagedVector, // error message output - ) -> i32, +#[derive(Default)] +pub struct DbVtable { + pub read_db: Option< + extern "C" fn( + *mut db_t, + *mut gas_meter_t, + *mut u64, + U8SliceView, + *mut UnmanagedVector, // result output + *mut UnmanagedVector, // error message output + ) -> i32, + >, + pub write_db: Option< + extern "C" fn( + *mut db_t, + *mut gas_meter_t, + *mut u64, + U8SliceView, + U8SliceView, + *mut UnmanagedVector, // error message output + ) -> i32, + >, + pub remove_db: Option< + extern "C" fn( + *mut db_t, + *mut gas_meter_t, + *mut u64, + U8SliceView, + *mut UnmanagedVector, // error message output + ) -> i32, + >, // order -> Ascending = 1, Descending = 2 // Note: we cannot set gas_meter on the returned GoIter due to cgo memory safety. // Since we have the pointer in rust already, we must set that manually - pub scan_db: extern "C" fn( - *mut db_t, - *mut gas_meter_t, - *mut u64, - U8SliceView, - U8SliceView, - i32, - *mut GoIter, - *mut UnmanagedVector, // error message output - ) -> i32, + pub scan_db: Option< + extern "C" fn( + *mut db_t, + *mut gas_meter_t, + *mut u64, + U8SliceView, + U8SliceView, + i32, + *mut GoIter, + *mut UnmanagedVector, // error message output + ) -> i32, + >, } +impl Vtable for DbVtable {} + #[repr(C)] pub struct Db { pub gas_meter: *mut gas_meter_t, pub state: *mut db_t, - pub vtable: Db_vtable, + pub vtable: DbVtable, } diff --git a/libwasmvm/src/iterator.rs b/libwasmvm/src/iterator.rs index 00b350e29..7f8de7729 100644 --- a/libwasmvm/src/iterator.rs +++ b/libwasmvm/src/iterator.rs @@ -4,6 +4,7 @@ use cosmwasm_vm::{BackendError, BackendResult, GasInfo}; use crate::error::GoError; use crate::gas_meter::gas_meter_t; use crate::memory::UnmanagedVector; +use crate::vtables::Vtable; // Iterator maintains integer references to some tables on the Go side #[repr(C)] @@ -18,7 +19,7 @@ pub struct iterator_t { // and then check it when converting to GoError manually #[repr(C)] #[derive(Default)] -pub struct Iterator_vtable { +pub struct IteratorVtable { pub next: Option< extern "C" fn( iterator_t, @@ -49,11 +50,13 @@ pub struct Iterator_vtable { >, } +impl Vtable for IteratorVtable {} + #[repr(C)] pub struct GoIter { pub gas_meter: *mut gas_meter_t, pub state: iterator_t, - pub vtable: Iterator_vtable, + pub vtable: IteratorVtable, } impl GoIter { @@ -61,17 +64,15 @@ impl GoIter { GoIter { gas_meter, state: iterator_t::default(), - vtable: Iterator_vtable::default(), + vtable: IteratorVtable::default(), } } pub fn next(&mut self) -> BackendResult> { - let Some(next) = self.vtable.next else { - let result = Err(BackendError::unknown( - "iterator vtable function 'next' not set", - )); - return (result, GasInfo::free()); - }; + let next = self + .vtable + .next + .expect("iterator vtable function 'next' not set"); let mut output_key = UnmanagedVector::default(); let mut output_value = UnmanagedVector::default(); @@ -116,22 +117,18 @@ impl GoIter { } pub fn next_key(&mut self) -> BackendResult>> { - let Some(next_key) = self.vtable.next_key else { - let result = Err(BackendError::unknown( - "iterator vtable function 'next_key' not set", - )); - return (result, GasInfo::free()); - }; + let next_key = self + .vtable + .next_key + .expect("iterator vtable function 'next_key' not set"); self.next_key_or_val(next_key) } pub fn next_value(&mut self) -> BackendResult>> { - let Some(next_value) = self.vtable.next_value else { - let result = Err(BackendError::unknown( - "iterator vtable function 'next_value' not set", - )); - return (result, GasInfo::free()); - }; + let next_value = self + .vtable + .next_value + .expect("iterator vtable function 'next_value' not set"); self.next_key_or_val(next_value) } diff --git a/libwasmvm/src/lib.rs b/libwasmvm/src/lib.rs index 2e431fe30..8edf84e20 100644 --- a/libwasmvm/src/lib.rs +++ b/libwasmvm/src/lib.rs @@ -16,17 +16,20 @@ mod storage; mod test_utils; mod tests; mod version; +mod vtables; // We only interact with this crate via `extern "C"` interfaces, not those public // exports. There are no guarantees those exports are stable. // We keep them here such that we can access them in the docs (`cargo doc`). -pub use api::GoApi; +pub use api::{GoApi, GoApiVtable}; pub use cache::{cache_t, load_wasm}; -pub use db::{db_t, Db}; +pub use db::{db_t, Db, DbVtable}; pub use error::GoError; pub use gas_report::GasReport; +pub use iterator::IteratorVtable; pub use memory::{ destroy_unmanaged_vector, new_unmanaged_vector, ByteSliceView, U8SliceView, UnmanagedVector, }; -pub use querier::GoQuerier; +pub use querier::{GoQuerier, QuerierVtable}; pub use storage::GoStorage; +pub use vtables::Vtable; diff --git a/libwasmvm/src/memory.rs b/libwasmvm/src/memory.rs index 24d6e91b8..7df702249 100644 --- a/libwasmvm/src/memory.rs +++ b/libwasmvm/src/memory.rs @@ -156,8 +156,9 @@ impl U8SliceView { /// // … /// # let mut error_msg = UnmanagedVector::default(); /// # let mut used_gas = 0_u64; +/// # let read_db = db.vtable.read_db.unwrap(); /// -/// let go_error: GoError = (db.vtable.read_db)( +/// let go_error: GoError = read_db( /// db.state, /// db.gas_meter, /// &mut used_gas as *mut u64, diff --git a/libwasmvm/src/querier.rs b/libwasmvm/src/querier.rs index 3d22a48a7..ba40faabf 100644 --- a/libwasmvm/src/querier.rs +++ b/libwasmvm/src/querier.rs @@ -3,6 +3,7 @@ use cosmwasm_vm::{BackendResult, GasInfo, Querier}; use crate::error::GoError; use crate::memory::{U8SliceView, UnmanagedVector}; +use crate::Vtable; // this represents something passed in from the caller side of FFI #[repr(C)] @@ -12,24 +13,28 @@ pub struct querier_t { } #[repr(C)] -#[derive(Clone)] -pub struct Querier_vtable { +#[derive(Clone, Default)] +pub struct QuerierVtable { // We return errors through the return buffer, but may return non-zero error codes on panic - pub query_external: extern "C" fn( - *const querier_t, - u64, - *mut u64, - U8SliceView, - *mut UnmanagedVector, // result output - *mut UnmanagedVector, // error message output - ) -> i32, + pub query_external: Option< + extern "C" fn( + *const querier_t, + u64, + *mut u64, + U8SliceView, + *mut UnmanagedVector, // result output + *mut UnmanagedVector, // error message output + ) -> i32, + >, } +impl Vtable for QuerierVtable {} + #[repr(C)] #[derive(Clone)] pub struct GoQuerier { pub state: *const querier_t, - pub vtable: Querier_vtable, + pub vtable: QuerierVtable, } // TODO: check if we can do this safer... @@ -44,7 +49,11 @@ impl Querier for GoQuerier { let mut output = UnmanagedVector::default(); let mut error_msg = UnmanagedVector::default(); let mut used_gas = 0_u64; - let go_result: GoError = (self.vtable.query_external)( + let query_external = self + .vtable + .query_external + .expect("vtable function 'query_external' not set"); + let go_result: GoError = query_external( self.state, gas_limit, &mut used_gas as *mut u64, diff --git a/libwasmvm/src/storage.rs b/libwasmvm/src/storage.rs index fc7a75046..0a22061dd 100644 --- a/libwasmvm/src/storage.rs +++ b/libwasmvm/src/storage.rs @@ -28,7 +28,12 @@ impl Storage for GoStorage { let mut output = UnmanagedVector::default(); let mut error_msg = UnmanagedVector::default(); let mut used_gas = 0_u64; - let go_error: GoError = (self.db.vtable.read_db)( + let read_db = self + .db + .vtable + .read_db + .expect("vtable function 'read_db' not set"); + let go_error: GoError = read_db( self.db.state, self.db.gas_meter, &mut used_gas as *mut u64, @@ -67,7 +72,12 @@ impl Storage for GoStorage { let mut error_msg = UnmanagedVector::default(); let mut iter = GoIter::new(self.db.gas_meter); let mut used_gas = 0_u64; - let go_error: GoError = (self.db.vtable.scan_db)( + let scan_db = self + .db + .vtable + .scan_db + .expect("vtable function 'scan_db' not set"); + let go_error: GoError = scan_db( self.db.state, self.db.gas_meter, &mut used_gas as *mut u64, @@ -138,7 +148,12 @@ impl Storage for GoStorage { fn set(&mut self, key: &[u8], value: &[u8]) -> BackendResult<()> { let mut error_msg = UnmanagedVector::default(); let mut used_gas = 0_u64; - let go_error: GoError = (self.db.vtable.write_db)( + let write_db = self + .db + .vtable + .write_db + .expect("vtable function 'write_db' not set"); + let go_error: GoError = write_db( self.db.state, self.db.gas_meter, &mut used_gas as *mut u64, @@ -166,7 +181,12 @@ impl Storage for GoStorage { fn remove(&mut self, key: &[u8]) -> BackendResult<()> { let mut error_msg = UnmanagedVector::default(); let mut used_gas = 0_u64; - let go_error: GoError = (self.db.vtable.remove_db)( + let remove_db = self + .db + .vtable + .remove_db + .expect("vtable function 'remove_db' not set"); + let go_error: GoError = remove_db( self.db.state, self.db.gas_meter, &mut used_gas as *mut u64, diff --git a/libwasmvm/src/vtables.rs b/libwasmvm/src/vtables.rs new file mode 100644 index 000000000..642021874 --- /dev/null +++ b/libwasmvm/src/vtables.rs @@ -0,0 +1,46 @@ +/// Vtables are a collection of free function. Those functions are created in Go and +/// then assigned via function pointers that Rust can call. The functions do not +/// change during the lifetime of the object. +/// +/// Since the functions are free, a single function is used for multiple instances. +/// In fact, in Go we create a single global vtable variable holding and owning the +/// function the vtables in Rust point to. +/// +/// The `Vtable` trait is created to find those vtables troughout the codebase. +/// +/// ## Nullability +/// +/// Since all functions are represented as a function pointer, they are naturally +/// nullable. I.e. in Go we can always set them to `nil`. For this reason the functions +/// are all wrapped in Options, e.g. +/// +/// ``` +/// # use wasmvm::UnmanagedVector; +/// # struct iterator_t; +/// # struct gas_meter_t; +/// pub struct IteratorVtable { +/// pub next: Option< +/// extern "C" fn( +/// iterator_t, +/// *mut gas_meter_t, +/// *mut u64, +/// *mut UnmanagedVector, // key output +/// *mut UnmanagedVector, // value output +/// *mut UnmanagedVector, // error message output +/// ) -> i32, +/// >, +/// // ... +/// } +/// ``` +/// +/// Or to say it in the words of [the Rust documentation](https://doc.rust-lang.org/nomicon/ffi.html#the-nullable-pointer-optimization): +/// +/// > So `Option c_int>` is a correct way to represent a nullable function pointer using the C ABI (corresponding to the C type `int (*)(int))`. +/// +/// Since all vtable fields are nullable, we can easily demand them +/// to have a Default implementation. This sometimes is handy when a +/// type is created in Rust and then filled in Go. +/// +/// Missing vtable fields always indicate a lifecycle bug in wasmvm and +/// should be treated with a panic. +pub trait Vtable: Default {}