From a95dc5c24f595651f6b4dc846ce3708a93bfeb70 Mon Sep 17 00:00:00 2001 From: Pat Hickey
(func: F, witness: Option>) -> Self where T: 'static, P: ComponentNamedList + Lift + 'static, R: ComponentNamedList + Lower + 'static, F: Fn(StoreContextMut<'_, T>, P) -> HostResult, { - Self(func) + Self(func, witness) } } -impl HostFn for StaticHostFn +impl HostFn for StaticHostFn where T: 'static, F: Fn(StoreContextMut<'_, T>, P) -> HostResult, @@ -671,20 +680,20 @@ where /// /// This is intended for more-dynamic use cases than `StaticHostFn` above such /// as demos, gluing things together quickly, and `wast` testing. -struct DynamicHostFn(F); +struct DynamicHostFn(F, Option>); -impl DynamicHostFn { - fn new(func: F) -> Self +impl DynamicHostFn { + fn new(func: F, witness: Option>) -> Self where T: 'static, F: Fn(StoreContextMut<'_, T>, ComponentFunc, Vec, usize) -> HostResult>, { - Self(func) + Self(func, witness) } } impl HostFn), Vec> - for DynamicHostFn + for DynamicHostFn where T: 'static, F: Fn(StoreContextMut<'_, T>, ComponentFunc, Vec, usize) -> HostResult>, diff --git a/crates/wasmtime/src/runtime/component/func/witness.rs b/crates/wasmtime/src/runtime/component/func/witness.rs new file mode 100644 index 000000000000..17176ad6c287 --- /dev/null +++ b/crates/wasmtime/src/runtime/component/func/witness.rs @@ -0,0 +1,57 @@ + +use crate::prelude::*; +use core::marker::PhantomData; +use alloc::sync::Arc; + +pub struct Witness { + param_witness: Arc) -> Result<()> + Send + Sync>, + result_witness: Arc) -> Result<()> + Send + Sync>, + position: Arc>, +} +impl Witness { + pub fn new( + param: impl Fn(&mut T, &[String], ParamWitness<'_>) -> Result<()> + Send + Sync + 'static, + result: impl Fn(&mut T, &[String], ResultWitness<'_>) -> Result<()> + Send + Sync + 'static, + ) -> Self { + Self { + param_witness: Arc::new(param), + result_witness: Arc::new(result), + position: Arc::new(Vec::new()), + } + } + pub fn in_instance(&self, name: &str) -> Self { + let mut position: Vec = (&*self.position).clone(); + position.push(name.to_owned()); + Self { + param_witness: self.param_witness.clone(), + result_witness: self.result_witness.clone(), + position: Arc::new(position), + } + } +} + +impl Clone for Witness { + fn clone(&self) -> Self { + Self { + param_witness: self.param_witness.clone(), + result_witness: self.result_witness.clone(), + position: self.position.clone(), + } + } +} + +pub struct ParamWitness<'a> { + _marker: PhantomData<&'a ()>, +} + +impl<'a> ParamWitness<'a> { + +} + +pub struct ResultWitness<'a> { + _marker: PhantomData<&'a ()>, +} + +impl<'a> ResultWitness<'a> { + +} diff --git a/crates/wasmtime/src/runtime/component/linker.rs b/crates/wasmtime/src/runtime/component/linker.rs index faf1e21f9a39..18b8fa6f1ca2 100644 --- a/crates/wasmtime/src/runtime/component/linker.rs +++ b/crates/wasmtime/src/runtime/component/linker.rs @@ -1,6 +1,6 @@ #[cfg(feature = "component-model-async")] use crate::component::concurrent::Accessor; -use crate::component::func::HostFunc; +use crate::component::func::{HostFunc, Witness}; use crate::component::instance::RuntimeImport; use crate::component::matching::{InstanceType, TypeChecker}; use crate::component::types; @@ -64,6 +64,7 @@ pub struct Linker { map: NameMap, path: Vec, allow_shadowing: bool, + witness: Option>, _marker: marker::PhantomData T>, } @@ -75,6 +76,7 @@ impl Clone for Linker { map: self.map.clone(), path: self.path.clone(), allow_shadowing: self.allow_shadowing, + witness: self.witness.as_ref().map(|w| w.clone()), _marker: self._marker, } } @@ -98,6 +100,7 @@ pub struct LinkerInstance<'a, T: 'static> { strings: &'a mut Strings, map: &'a mut NameMap, allow_shadowing: bool, + witness: Option>, _marker: marker::PhantomData T>, } @@ -109,7 +112,7 @@ pub(crate) enum Definition { Resource(ResourceType, Arc), } -impl Linker { +impl Linker { /// Creates a new linker for the [`Engine`] specified with no items defined /// within it. pub fn new(engine: &Engine) -> Linker { @@ -119,6 +122,7 @@ impl Linker { map: NameMap::default(), allow_shadowing: false, path: Vec::new(), + witness: None, _marker: marker::PhantomData, } } @@ -147,10 +151,17 @@ impl Linker { strings: &mut self.strings, map: &mut self.map, allow_shadowing: self.allow_shadowing, + witness: self.witness.clone(), _marker: self._marker, } } + /// Set a `Witness` that will be applied to all functions defined in this + /// linker. + pub fn set_instance(&mut self, witness: Option>) { + self.witness = witness; + } + /// Returns a builder for the named instance specified. /// /// # Errors @@ -314,7 +325,7 @@ impl Linker { use wasmtime_environ::component::ComponentTypes; use wasmtime_environ::component::TypeDef; // Recursively stub out all imports of the component with a function that traps. - fn stub_item( + fn stub_item( linker: &mut LinkerInstance, item_name: &str, item_def: &TypeDef, @@ -374,7 +385,7 @@ impl Linker { } } -impl LinkerInstance<'_, T> { +impl LinkerInstance<'_, T> { fn as_mut(&mut self) -> LinkerInstance<'_, T> { LinkerInstance { engine: self.engine, @@ -383,6 +394,7 @@ impl LinkerInstance<'_, T> { strings: self.strings, map: self.map, allow_shadowing: self.allow_shadowing, + witness: self.witness.as_ref().map(|w| w.clone()), _marker: self._marker, } } @@ -422,7 +434,13 @@ impl LinkerInstance<'_, T> { Params: ComponentNamedList + Lift + 'static, Return: ComponentNamedList + Lower + 'static, { - self.insert(name, Definition::Func(HostFunc::func_wrap(func)))?; + self.insert( + name, + Definition::Func(HostFunc::func_wrap( + func, + self.witness.as_ref().map(|w| w.in_instance(name)), + )), + )?; Ok(()) } @@ -469,7 +487,13 @@ impl LinkerInstance<'_, T> { Params: ComponentNamedList + Lift + 'static, Return: ComponentNamedList + Lower + 'static, { - self.insert(name, Definition::Func(HostFunc::func_wrap_async(f)))?; + self.insert( + name, + Definition::Func(HostFunc::func_wrap_async( + f, + self.witness.as_ref().map(|w| w.in_instance(name)), + )), + )?; Ok(()) } @@ -534,7 +558,13 @@ impl LinkerInstance<'_, T> { if !self.engine.tunables().concurrency_support { bail!("concurrent host functions require `Config::concurrency_support`"); } - self.insert(name, Definition::Func(HostFunc::func_wrap_concurrent(f)))?; + self.insert( + name, + Definition::Func(HostFunc::func_wrap_concurrent( + f, + self.witness.as_ref().map(|w| w.in_instance(name)), + )), + )?; Ok(()) } @@ -645,7 +675,13 @@ impl LinkerInstance<'_, T> { + Sync + 'static, ) -> Result<()> { - self.insert(name, Definition::Func(HostFunc::func_new(func)))?; + self.insert( + name, + Definition::Func(HostFunc::func_new( + func, + self.witness.as_ref().map(|w| w.in_instance(name)), + )), + )?; Ok(()) } @@ -668,7 +704,13 @@ impl LinkerInstance<'_, T> { + Sync + 'static, { - self.insert(name, Definition::Func(HostFunc::func_new_async(func)))?; + self.insert( + name, + Definition::Func(HostFunc::func_new_async( + func, + self.witness.as_ref().map(|w| w.in_instance(name)), + )), + )?; Ok(()) } @@ -696,7 +738,13 @@ impl LinkerInstance<'_, T> { if !self.engine.tunables().concurrency_support { bail!("concurrent host functions require `Config::concurrency_support`"); } - self.insert(name, Definition::Func(HostFunc::func_new_concurrent(f)))?; + self.insert( + name, + Definition::Func(HostFunc::func_new_concurrent( + f, + self.witness.as_ref().map(|w| w.in_instance(name)), + )), + )?; Ok(()) } @@ -814,6 +862,7 @@ impl LinkerInstance<'_, T> { /// Same as [`LinkerInstance::instance`] except with different lifetime /// parameters. pub fn into_instance(mut self, name: &str) -> Result { + let n = name; let name = self.insert(name, Definition::Instance(NameMap::default()))?; self.map = match self.map.raw_get_mut(&name) { Some(Definition::Instance(map)) => map, @@ -822,6 +871,7 @@ impl LinkerInstance<'_, T> { self.path.truncate(self.path_len); self.path.push(name); self.path_len += 1; + self.witness.as_mut().map(|w| w.in_instance(n)); Ok(self) } From ae1ef8d29aac669262f2880d4d5966df525cdc5f Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Wed, 25 Feb 2026 14:14:21 -0800 Subject: [PATCH 2/2] notes on how to move forward... --- .../src/runtime/component/func/host.rs | 2 +- .../src/runtime/component/func/witness.rs | 144 +++++++++++++++++- 2 files changed, 139 insertions(+), 7 deletions(-) diff --git a/crates/wasmtime/src/runtime/component/func/host.rs b/crates/wasmtime/src/runtime/component/func/host.rs index 178902e2adba..d621932a058c 100644 --- a/crates/wasmtime/src/runtime/component/func/host.rs +++ b/crates/wasmtime/src/runtime/component/func/host.rs @@ -268,7 +268,7 @@ impl HostFunc { } /// Argument to [`HostFn::lift_params`] -enum Source<'a> { +pub(super) enum Source<'a> { /// The parameters come from flat wasm arguments which are provided here. Flat(&'a [ValRaw]), /// The parameters come from linear memory at the provided offset, which is diff --git a/crates/wasmtime/src/runtime/component/func/witness.rs b/crates/wasmtime/src/runtime/component/func/witness.rs index 17176ad6c287..20f9f67c5056 100644 --- a/crates/wasmtime/src/runtime/component/func/witness.rs +++ b/crates/wasmtime/src/runtime/component/func/witness.rs @@ -1,7 +1,7 @@ - +use crate::component::func::{LiftContext, Source, TypeFuncIndex}; use crate::prelude::*; -use core::marker::PhantomData; use alloc::sync::Arc; +use core::marker::PhantomData; pub struct Witness { param_witness: Arc) -> Result<()> + Send + Sync>, @@ -28,6 +28,13 @@ impl Witness { position: Arc::new(position), } } + + pub fn params(&self, t: &mut T, pw: ParamWitness<'_>) -> Result<()> { + (self.param_witness)(t, &self.position, pw) + } + pub fn results(&self, t: &mut T, rw: ResultWitness<'_>) -> Result<()> { + (self.result_witness)(t, &self.position, rw) + } } impl Clone for Witness { @@ -41,17 +48,142 @@ impl Clone for Witness { } pub struct ParamWitness<'a> { - _marker: PhantomData<&'a ()>, + cx: LazyCtx<'a>, } impl<'a> ParamWitness<'a> { + pub(super) fn new(cx: &'a LiftContext<'a>, ty: TypeFuncIndex, src: &'a Source<'a>) -> Self { + Self { + cx: LazyCtx { cx, ty, src }, + } + } + pub fn values(&self) -> impl Iterator)> { + // TODO FIXME + [].into_iter() + } +} +// Can we make a Func::new_lazy that takes &[LazyValue] and returns Val, and +// then Func::new is implemented in terms of Func::new_lazy + +struct LazyCtx<'a> { + // This will probably need to get threaded into each call by hand because + // its the store + cx: &'a LiftContext<'a>, + ty: TypeFuncIndex, + // This might remain and be the 'a ... + src: &'a Source<'a>, } -pub struct ResultWitness<'a> { - _marker: PhantomData<&'a ()>, +pub struct LazyValue<'a> { + // This will need to be out of here actually and just be a cursor into the + // sourcer. the 'a might go away + cx: LazyCtx<'a>, +} + +impl<'a> LazyValue<'a> { + fn ty(&self) -> crate::component::Type { + todo!() + } + fn force(&self) -> Result> { + todo!() + } +} + +// Each leaf of LVal has a private +/* + fn to_val(&self, store: &mut Store) -> Result { + // The resource arms can be turned into a Val only once. Once they + // have been, its illegal to ever do so again... + } +*/ +// And then there is a Val::from_lazy(LazyValue, &mut Store) +// the Host has to force an owned value from the guest table to the host +// table, or else you break the component model semantics. By taking on the +// use of the Func::new_lazy apis the host has the additional responsibility +// to not get that wrong! +// + +// Document saying Resources have extra rules +// +// All of Val built on top of this +// +// Everything related to Lifting a Val is built here. +// +// New linker method gives you access to this raw/lazy world, which is +// documented to be extra dangerous. +// +// All of this is the basis for introspection, after the initial bits land. + +// This reflects Val except everywhere there is a Box or Vec there +// is instead a LazyValue. +pub enum LVal<'a> { + U32(u32), + // .. and all the other atoms .. + // + // Each layer of every type has a method to_val: LazyString -> String, + // LazyList -> WasmList, LazyRecord -> Record, + // + String(LazyString<'a>), + List(LazyList<'a>), + // XXX actually LazyRecord here... + Record(Vec<(&'a str, LazyValue<'a>)>), + Resource(LazyResource<'a>), +} + +pub struct LazyString<'a> { + cx: LazyCtx<'a>, +} + +impl<'a> LazyString<'a> { + pub fn force(&self) -> Result<&str> { + todo!() + } } -impl<'a> ResultWitness<'a> { +pub struct LazyList<'a> { + cx: LazyCtx<'a>, +} +impl<'a> LazyList<'a> { + fn elem_ty(&self) -> crate::component::Type { + todo!() + } + pub fn len(&self) -> Result { + todo!() + } + pub fn get(&self, ix: usize) -> Result> { + todo!() + } + pub fn elems(&self) -> Result>> { + // TODO FIXME + Ok([].into_iter()) + } + pub fn as_slice_u8(&self) -> Result<&[u8]> { + todo!() + } } + +pub struct LazyResource<'a> { + cx: LazyCtx<'a>, +} +impl<'a> LazyResource<'a> { + pub fn ty(&self) -> crate::component::Type { + todo!() + } + pub fn repr(&self) -> u32 { + todo!() + } + // This is the one operation that needs to mutate the LiftContext, so this + // would make us need a &mut passed around to all + // the forces. + pub fn force(&self, store: &mut crate::Store) -> Result { + todo!() + } +} + +pub struct ResultWitness<'a> { + _marker: PhantomData<&'a ()>, +} + +impl<'a> ResultWitness<'a> {}