diff --git a/crates/core/src/host/wasm_common.rs b/crates/core/src/host/wasm_common.rs index ac2df19340..c717c06e20 100644 --- a/crates/core/src/host/wasm_common.rs +++ b/crates/core/src/host/wasm_common.rs @@ -1,4 +1,3 @@ -pub mod abi; pub mod module_host_actor; use crate::error::{DBError, IndexError, NodesError}; @@ -226,7 +225,6 @@ impl FuncNames { #[error(transparent)] pub enum ModuleCreationError { WasmCompileError(anyhow::Error), - Abi(#[from] abi::AbiVersionError), Init(#[from] module_host_actor::InitializationError), } diff --git a/crates/core/src/host/wasm_common/abi.rs b/crates/core/src/host/wasm_common/abi.rs deleted file mode 100644 index 2c1f442ebb..0000000000 --- a/crates/core/src/host/wasm_common/abi.rs +++ /dev/null @@ -1,110 +0,0 @@ -use super::{STDB_ABI_IS_ADDR_SYM, STDB_ABI_SYM}; -use std::collections::HashMap; - -pub use spacetimedb_lib::VersionTuple; - -const ABIVER_GLOBAL_TY: wasmparser::GlobalType = wasmparser::GlobalType { - content_type: wasmparser::ValType::I32, - mutable: false, -}; - -/// wasm-runtime-agnostic function to extract spacetime ABI version from a module -/// wasm_module must be a valid wasm module -pub fn determine_spacetime_abi(wasm_module: &[u8]) -> Result { - let mut parser = wasmparser::Parser::new(0); - let mut data = wasm_module; - let (mut globals, mut exports, mut datas) = (None, None, None); - loop { - let payload = match parser.parse(data, true).map_err(|_| AbiVersionError::Malformed)? { - wasmparser::Chunk::NeedMoreData(_) => unreachable!("determine_spacetime_abi:NeedMoreData"), - wasmparser::Chunk::Parsed { consumed, payload } => { - data = &data[consumed..]; - payload - } - }; - match payload { - wasmparser::Payload::GlobalSection(rdr) => globals = Some(rdr), - wasmparser::Payload::ExportSection(rdr) => exports = Some(rdr), - wasmparser::Payload::DataSection(rdr) => datas = Some(rdr), - wasmparser::Payload::CodeSectionStart { size, .. } => { - data = &data[size as usize..]; - parser.skip_section() - } - wasmparser::Payload::End(_) => break, - _ => {} - } - } - - let (globals, exports) = globals.zip(exports).ok_or(AbiVersionError::NoVersion)?; - - // TODO: wasmparser Validator should provide access to exports map? bytecodealliance/wasm-tools#806 - - let exports = exports - .into_iter() - .map(|exp| { - let exp = exp.map_err(|_| AbiVersionError::Malformed)?; - Ok((exp.name, exp)) - }) - .collect::, AbiVersionError>>()?; - - let export = exports.get(STDB_ABI_SYM).ok_or(AbiVersionError::NoVersion)?; - // LLVM can only output statics as addresses into the data section currently, so we need to do - // resolve the address if it is one - let export_is_addr = exports.contains_key(STDB_ABI_IS_ADDR_SYM); - - if export.kind != wasmparser::ExternalKind::Global { - return Err(AbiVersionError::NoVersion); - } - - let global = globals - .into_iter() - .nth(export.index as usize) - .and_then(Result::ok) - .ok_or(AbiVersionError::Malformed)?; - - if global.ty != ABIVER_GLOBAL_TY { - return Err(AbiVersionError::Malformed); - } - let mut op_rdr = global.init_expr.get_operators_reader(); - let Ok(wasmparser::Operator::I32Const { value }) = op_rdr.read() else { - return Err(AbiVersionError::Malformed); - }; - let ver = if export_is_addr { - datas - .into_iter() - .flatten() - .find_map(|data| { - let data = data.ok()?; - let wasmparser::DataKind::Active { - memory_index: 0, - offset_expr, - } = data.kind - else { - return None; - }; - let offset_op = offset_expr.get_operators_reader().read().ok()?; - let wasmparser::Operator::I32Const { value: offset } = offset_op else { - return None; - }; - let idx = value.checked_sub(offset)?; - let slice = data.data.get(idx as usize..)?.get(..4)?; - Some(u32::from_le_bytes( - slice.try_into().unwrap(/* we passed constant length above */), - )) - }) - .ok_or(AbiVersionError::Malformed)? - } else { - value as u32 - }; - Ok(VersionTuple::from_u32(ver)) -} - -#[derive(thiserror::Error, Debug)] -pub enum AbiVersionError { - #[error("module doesn't indicate spacetime ABI version")] - NoVersion, - #[error("abi version is malformed somehow (out-of-bounds, etc)")] - Malformed, - #[error("abi version {got} is not supported (host implements {implement})")] - UnsupportedVersion { got: VersionTuple, implement: VersionTuple }, -} diff --git a/crates/core/src/host/wasm_common/module_host_actor.rs b/crates/core/src/host/wasm_common/module_host_actor.rs index 7d6963c852..7f533c7494 100644 --- a/crates/core/src/host/wasm_common/module_host_actor.rs +++ b/crates/core/src/host/wasm_common/module_host_actor.rs @@ -11,7 +11,7 @@ use bytes::Bytes; use nonempty::NonEmpty; use spacetimedb_lib::buffer::DecodeError; use spacetimedb_lib::identity::AuthCtx; -use spacetimedb_lib::{bsatn, Address, IndexType, ModuleDef}; +use spacetimedb_lib::{bsatn, Address, IndexType, ModuleDef, VersionTuple}; use spacetimedb_vm::expr::CrudExpr; use crate::client::ClientConnectionSender; @@ -100,8 +100,20 @@ pub(crate) struct WasmModuleHostActor { energy_monitor: Arc, } +#[derive(thiserror::Error, Debug)] +pub enum AbiVersionError { + #[error("module doesn't indicate spacetime ABI version")] + NoVersion, + #[error("abi version is malformed somehow (out-of-bounds, etc)")] + Malformed, + #[error("abi version {got} is not supported (host implements {implement})")] + UnsupportedVersion { got: VersionTuple, implement: VersionTuple }, +} + #[derive(thiserror::Error, Debug)] pub enum InitializationError { + #[error(transparent)] + Abi(#[from] AbiVersionError), #[error(transparent)] Validation(#[from] ValidationError), #[error("setup function returned an error: {0}")] diff --git a/crates/core/src/host/wasmer/mod.rs b/crates/core/src/host/wasmer/mod.rs index ff4951939a..ddbbb61ac2 100644 --- a/crates/core/src/host/wasmer/mod.rs +++ b/crates/core/src/host/wasmer/mod.rs @@ -15,7 +15,7 @@ mod wasmer_module; use wasmer_module::WasmerModule; use super::scheduler::Scheduler; -use super::wasm_common::{abi, module_host_actor::WasmModuleHostActor, ModuleCreationError}; +use super::wasm_common::{module_host_actor::WasmModuleHostActor, ModuleCreationError}; use super::{EnergyMonitor, EnergyQuanta}; pub fn make_actor( @@ -47,15 +47,6 @@ pub fn make_actor( let module = Module::new(&engine, program_bytes).map_err(|e| ModuleCreationError::WasmCompileError(e.into()))?; - let abi = abi::determine_spacetime_abi(program_bytes)?; - - if !WasmerModule::IMPLEMENTED_ABI.supports(abi) { - return Err(ModuleCreationError::Abi(abi::AbiVersionError::UnsupportedVersion { - implement: WasmerModule::IMPLEMENTED_ABI, - got: abi, - })); - } - let module = WasmerModule::new(module, engine); WasmModuleHostActor::new(dbic, module_hash, module, scheduler, energy_monitor).map_err(Into::into) diff --git a/crates/core/src/host/wasmer/wasmer_module.rs b/crates/core/src/host/wasmer/wasmer_module.rs index 898ddb79a3..5b43537680 100644 --- a/crates/core/src/host/wasmer/wasmer_module.rs +++ b/crates/core/src/host/wasmer/wasmer_module.rs @@ -1,13 +1,14 @@ use super::wasm_instance_env::WasmInstanceEnv; use super::Mem; use crate::host::instance_env::InstanceEnv; -use crate::host::wasm_common::module_host_actor::{DescribeError, InitializationError}; +use crate::host::wasm_common::module_host_actor::{AbiVersionError, DescribeError, InitializationError}; use crate::host::wasm_common::*; use crate::host::{EnergyQuanta, Timestamp}; use bytes::Bytes; +use spacetimedb_lib::VersionTuple; use wasmer::{ imports, AsStoreMut, Engine, ExternType, Function, FunctionEnv, Imports, Instance, Module, RuntimeError, Store, - TypedFunction, + TypedFunction, WasmPtr, }; use wasmer_middlewares::metering as wasmer_metering; @@ -45,7 +46,7 @@ impl WasmerModule { WasmerModule { module, engine } } - pub const IMPLEMENTED_ABI: abi::VersionTuple = abi::VersionTuple::new(4, 0); + pub const IMPLEMENTED_ABI: VersionTuple = VersionTuple::new(4, 0); fn imports(&self, store: &mut Store, env: &FunctionEnv) -> Imports { const _: () = assert!(WasmerModule::IMPLEMENTED_ABI.eq(spacetimedb_lib::MODULE_ABI_VERSION)); @@ -174,6 +175,43 @@ impl module_host_actor::WasmInstancePre for WasmerModule { .map_err(|err| InitializationError::Instantiation(err.into()))?; let mem = Mem::extract(&instance.exports).unwrap(); + + // We could (and did in the past) parse the ABI version manually before the instantiation, + // but it gets complicated in presence of wasm-opt optimisations which might split encoded + // versions like `[...other data...]\00\00\03\00[...other data...]` by zeroes + // into several segments, so there is no single data segment containing the entire version. + // Instead, it's more reliable to extract the version from an instantiated module + // when all the data segments are loaded into the flat memory at correct offsets. + let abi_version = instance + .exports + .get_global(STDB_ABI_SYM) + .map_err(|_| AbiVersionError::NoVersion)?; + + let mut abi_version = match abi_version.get(&mut store) { + wasmer::Value::I32(x) => x as u32, + _ => return Err(AbiVersionError::Malformed.into()), + }; + + let abi_is_addr = instance.exports.get_global(STDB_ABI_IS_ADDR_SYM).is_ok(); + if abi_is_addr { + abi_version = u32::from_le_bytes( + mem.read_bytes(&store, WasmPtr::new(abi_version), 4) + .ok() + .and_then(|bytes| bytes.try_into().ok()) + .ok_or(AbiVersionError::Malformed)?, + ); + } + + let abi_version = VersionTuple::from_u32(abi_version); + + if !WasmerModule::IMPLEMENTED_ABI.supports(abi_version) { + return Err(AbiVersionError::UnsupportedVersion { + implement: WasmerModule::IMPLEMENTED_ABI, + got: abi_version, + } + .into()); + } + env.as_mut(&mut store).mem = Some(mem); // Note: this budget is just for initializers