diff --git a/Cargo.lock b/Cargo.lock index 87d9d8825..a06c19ef7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -481,7 +481,7 @@ dependencies = [ "cap-primitives", "cap-std", "io-lifetimes", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -509,7 +509,7 @@ dependencies = [ "ipnet", "maybe-owned", "rustix 0.38.44", - "windows-sys 0.52.0", + "windows-sys 0.59.0", "winx", ] @@ -1150,7 +1150,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1173,7 +1173,7 @@ checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" dependencies = [ "cfg-if", "rustix 1.0.3", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1211,7 +1211,7 @@ checksum = "94e7099f6313ecacbe1256e8ff9d617b75d1bcb16a6fddef94866d225a01a14a" dependencies = [ "io-lifetimes", "rustix 1.0.3", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1935,7 +1935,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2285ddfe3054097ef4b2fe909ef8c3bcd1ea52a8f0d274416caebeef39f04a65" dependencies = [ "io-lifetimes", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1958,7 +1958,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2583,7 +2583,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2901,7 +2901,7 @@ dependencies = [ "libc", "linux-raw-sys 0.4.15", "once_cell", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2914,7 +2914,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.3", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3007,7 +3007,7 @@ dependencies = [ "security-framework 3.2.0", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3422,7 +3422,7 @@ dependencies = [ "fd-lock", "io-lifetimes", "rustix 0.38.44", - "windows-sys 0.52.0", + "windows-sys 0.59.0", "winx", ] @@ -3441,7 +3441,7 @@ dependencies = [ "fastrand", "once_cell", "rustix 1.0.3", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -4807,7 +4807,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -5083,7 +5083,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f3fd376f71958b862e7afb20cfe5a22830e1963462f3a17f49d82a6c1d1f42d" dependencies = [ "bitflags 2.9.0", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -5395,7 +5395,6 @@ dependencies = [ "wasm-tokio", "wasmtime", "wasmtime-wasi", - "wit-parser 0.220.1", "wrpc-introspect", "wrpc-transport", ] diff --git a/crates/runtime-wasmtime/Cargo.toml b/crates/runtime-wasmtime/Cargo.toml index b73e34aa1..9b2aeabb6 100644 --- a/crates/runtime-wasmtime/Cargo.toml +++ b/crates/runtime-wasmtime/Cargo.toml @@ -21,6 +21,5 @@ uuid = { workspace = true, features = ["std", "v7"] } wasm-tokio = { workspace = true } wasmtime = { workspace = true } wasmtime-wasi = { workspace = true } -wit-parser = { workspace = true } wrpc-introspect = { workspace = true } wrpc-transport = { workspace = true } diff --git a/crates/runtime-wasmtime/src/lib.rs b/crates/runtime-wasmtime/src/lib.rs index 1098b14e6..7ce703081 100644 --- a/crates/runtime-wasmtime/src/lib.rs +++ b/crates/runtime-wasmtime/src/lib.rs @@ -1,14 +1,13 @@ #![allow(clippy::type_complexity)] // TODO: https://github.com/bytecodealliance/wrpc/issues/2 use core::any::Any; -use core::borrow::Borrow; use core::fmt; use core::future::Future; use core::iter::zip; use core::pin::pin; use core::time::Duration; -use std::collections::{BTreeMap, HashMap}; +use std::collections::HashMap; use std::sync::Arc; use anyhow::{anyhow, bail, Context as _}; @@ -16,10 +15,10 @@ use bytes::{Bytes, BytesMut}; use futures::future::try_join_all; use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt as _}; use tokio_util::codec::Encoder; -use tracing::{debug, instrument, trace, warn}; +use tracing::{debug, trace}; use uuid::Uuid; -use wasmtime::component::{types, Func, Resource, ResourceAny, ResourceType, Type, Val}; -use wasmtime::{AsContextMut, Engine}; +use wasmtime::component::{Func, Resource, ResourceAny, ResourceType, Type, Val}; +use wasmtime::AsContextMut; use wasmtime_wasi::{IoView, WasiView}; use wrpc_transport::Invoke; @@ -32,48 +31,12 @@ mod codec; mod polyfill; pub mod rpc; mod serve; +mod types; pub use codec::*; pub use polyfill::*; pub use serve::*; - -// this returns the RPC name for a wasmtime function name. -// Unfortunately, the [`types::ComponentFunc`] does not include the kind information and we want to -// avoid (re-)parsing the WIT here. -fn rpc_func_name(name: &str) -> &str { - if let Some(name) = name.strip_prefix("[constructor]") { - name - } else if let Some(name) = name.strip_prefix("[static]") { - name - } else if let Some(name) = name.strip_prefix("[method]") { - name - } else { - name - } -} - -fn rpc_result_type>( - host_resources: &HashMap, HashMap, (ResourceType, ResourceType)>>, - results_ty: impl IntoIterator, -) -> Option> { - let rpc_err_ty = host_resources - .get("wrpc:rpc/error@0.1.0") - .and_then(|instance| instance.get("error")); - let mut results_ty = results_ty.into_iter(); - match ( - rpc_err_ty, - results_ty.next().as_ref().map(Borrow::borrow), - results_ty.next(), - ) { - (Some((guest_rpc_err_ty, host_rpc_err_ty)), Some(Type::Result(result_ty)), None) - if *host_rpc_err_ty == ResourceType::host::() - && result_ty.err() == Some(Type::Own(*guest_rpc_err_ty)) => - { - Some(result_ty.ok()) - } - _ => None, - } -} +pub use types::*; pub struct RemoteResource(pub Bytes); @@ -396,7 +359,7 @@ where let mut buf = BytesMut::default(); let mut deferred = vec![]; match ( - &rpc_result_type(host_resources, results_ty), + CustomReturnType::new(host_resources, results_ty), results.as_slice(), ) { (None, results) => { @@ -409,16 +372,16 @@ where } } // `result<_, rpc-eror>` - (Some(None), [Val::Result(Ok(None))]) => {} + (Some(CustomReturnType::Rpc(None)), [Val::Result(Ok(None))]) => {} // `result` - (Some(Some(ty)), [Val::Result(Ok(Some(v)))]) => { - let mut enc = ValEncoder::new(store.as_context_mut(), ty, guest_resources); + (Some(CustomReturnType::Rpc(Some(ty))), [Val::Result(Ok(Some(v)))]) => { + let mut enc = ValEncoder::new(store.as_context_mut(), &ty, guest_resources); enc.encode(v, &mut buf) .context("failed to encode result value 0") .map_err(CallError::Encode)?; deferred.push(enc.deferred); } - (Some(..), [Val::Result(Err(Some(err)))]) => { + (Some(CustomReturnType::Rpc(..)), [Val::Result(Err(Some(err)))]) => { let Val::Resource(err) = &**err else { return Err(CallError::TypeMismatch(anyhow!( "RPC result error value is not a resource" @@ -466,94 +429,3 @@ where .map_err(CallError::PostReturn)?; Ok(()) } - -/// Recursively iterates the component item type and collects all exported resource types -#[instrument(level = "debug", skip_all)] -pub fn collect_item_resource_exports( - engine: &Engine, - ty: types::ComponentItem, - resources: &mut impl Extend, -) { - match ty { - types::ComponentItem::ComponentFunc(_) - | types::ComponentItem::CoreFunc(_) - | types::ComponentItem::Module(_) - | types::ComponentItem::Type(_) => {} - types::ComponentItem::Component(ty) => { - collect_component_resource_exports(engine, &ty, resources) - } - - types::ComponentItem::ComponentInstance(ty) => { - collect_instance_resource_exports(engine, &ty, resources) - } - types::ComponentItem::Resource(ty) => { - debug!(?ty, "collect resource export"); - resources.extend([ty]) - } - } -} - -/// Recursively iterates the instance type and collects all exported resource types -#[instrument(level = "debug", skip_all)] -pub fn collect_instance_resource_exports( - engine: &Engine, - ty: &types::ComponentInstance, - resources: &mut impl Extend, -) { - for (name, ty) in ty.exports(engine) { - trace!(name, ?ty, "collect instance item resource exports"); - collect_item_resource_exports(engine, ty, resources); - } -} - -/// Recursively iterates the component type and collects all exported resource types -#[instrument(level = "debug", skip_all)] -pub fn collect_component_resource_exports( - engine: &Engine, - ty: &types::Component, - resources: &mut impl Extend, -) { - for (name, ty) in ty.exports(engine) { - trace!(name, ?ty, "collect component item resource exports"); - collect_item_resource_exports(engine, ty, resources); - } -} - -/// Iterates the component type and collects all imported resource types -#[instrument(level = "debug", skip_all)] -pub fn collect_component_resource_imports( - engine: &Engine, - ty: &types::Component, - resources: &mut BTreeMap, HashMap, types::ResourceType>>, -) { - for (name, ty) in ty.imports(engine) { - match ty { - types::ComponentItem::ComponentFunc(..) - | types::ComponentItem::CoreFunc(..) - | types::ComponentItem::Module(..) - | types::ComponentItem::Type(..) - | types::ComponentItem::Component(..) => {} - types::ComponentItem::ComponentInstance(ty) => { - let instance = name; - for (name, ty) in ty.exports(engine) { - if let types::ComponentItem::Resource(ty) = ty { - debug!(instance, name, ?ty, "collect instance resource import"); - if let Some(resources) = resources.get_mut(instance) { - resources.insert(name.into(), ty); - } else { - resources.insert(instance.into(), HashMap::from([(name.into(), ty)])); - } - } - } - } - types::ComponentItem::Resource(ty) => { - debug!(name, "collect component resource import"); - if let Some(resources) = resources.get_mut("") { - resources.insert(name.into(), ty); - } else { - resources.insert("".into(), HashMap::from([(name.into(), ty)])); - } - } - } - } -} diff --git a/crates/runtime-wasmtime/src/polyfill.rs b/crates/runtime-wasmtime/src/polyfill.rs index 47df969e0..cc50981d0 100644 --- a/crates/runtime-wasmtime/src/polyfill.rs +++ b/crates/runtime-wasmtime/src/polyfill.rs @@ -18,7 +18,10 @@ use wasmtime_wasi::WasiView; use wrpc_transport::{Index as _, Invoke, InvokeExt as _}; use crate::rpc::Error; -use crate::{read_value, rpc_func_name, rpc_result_type, ValEncoder, WrpcView, WrpcViewExt as _}; +use crate::{ + read_value, rpc_func_name, CustomReturnType, DynamicResources, ValEncoder, WrpcView, + WrpcViewExt as _, +}; /// Polyfill [`types::ComponentItem`] in a [`LinkerInstance`] using [`wrpc_transport::Invoke`] #[instrument(level = "trace", skip_all)] @@ -27,6 +30,7 @@ pub fn link_item( linker: &mut LinkerInstance, guest_resources: impl Into>, host_resources: impl Into, HashMap, (ResourceType, ResourceType)>>>>, + dynamic_resources: &DynamicResources, ty: types::ComponentItem, instance: impl Into>, name: impl Into>, @@ -62,6 +66,7 @@ where linker, Arc::clone(&guest_resources), Arc::clone(&host_resources), + dynamic_resources, ty, "", name, @@ -79,6 +84,7 @@ where &mut linker, guest_resources, host_resources, + dynamic_resources, ty, name, )?; @@ -94,7 +100,7 @@ where }; ensure!(ty == *guest_ty, "{instance}/{name} resource type mismatch"); - debug!(?instance, ?name, "linking resource"); + debug!(?instance, ?name, "linking host resource"); linker.resource(&name, *host_ty, |_, _| Ok(()))?; } } @@ -108,6 +114,7 @@ pub fn link_instance( linker: &mut LinkerInstance, guest_resources: impl Into>, host_resources: impl Into, HashMap, (ResourceType, ResourceType)>>>>, + dynamic_resources: &DynamicResources, ty: types::ComponentInstance, name: impl Into>, ) -> wasmtime::Result<()> @@ -124,6 +131,7 @@ where linker, Arc::clone(&guest_resources), Arc::clone(&host_resources), + dynamic_resources, ty, Arc::clone(&instance), name, @@ -242,7 +250,7 @@ where let name = name.into(); let guest_resources = guest_resources.into(); let host_resources = host_resources.into(); - match rpc_result_type(&host_resources, ty.results()) { + match CustomReturnType::new(&host_resources, ty.results()) { None => linker.func_new_async(&Arc::clone(&name), move |mut store, params, results| { let ty = ty.clone(); let instance = Arc::clone(&instance); @@ -271,7 +279,7 @@ where ) }), // `result<_, rpc-eror>` - Some(None) => { + Some(CustomReturnType::Rpc(None)) => { linker.func_new_async(&Arc::clone(&name), move |mut store, params, results| { let ty = ty.clone(); let instance = Arc::clone(&instance); @@ -312,7 +320,7 @@ where }) } // `result` - Some(Some(result_ty)) => { + Some(CustomReturnType::Rpc(Some(result_ty))) => { linker.func_new_async(&Arc::clone(&name), move |mut store, params, results| { let ty = ty.clone(); let instance = Arc::clone(&instance); @@ -355,5 +363,6 @@ where ) }) } + Some(CustomReturnType::AsyncReturn(ty)) => todo!("async return"), } } diff --git a/crates/runtime-wasmtime/src/rpc/host/error.rs b/crates/runtime-wasmtime/src/rpc/host/error.rs index f9cc136c3..a22de40ce 100644 --- a/crates/runtime-wasmtime/src/rpc/host/error.rs +++ b/crates/runtime-wasmtime/src/rpc/host/error.rs @@ -15,7 +15,7 @@ impl HostError for WrpcRpcImpl { ) -> wasmtime::Result, Resource>> { let table = self.0.table(); let error = table - .delete::(error) + .delete(error) .context("failed to delete `wasi:io/error.error` from table")?; match error.downcast() { Ok(error) => { diff --git a/crates/runtime-wasmtime/src/types/dynamic.rs b/crates/runtime-wasmtime/src/types/dynamic.rs new file mode 100644 index 000000000..ac467c845 --- /dev/null +++ b/crates/runtime-wasmtime/src/types/dynamic.rs @@ -0,0 +1,278 @@ +use wasmtime::component::ResourceType; + +use super::{DynamicFuture, DynamicReturn, DynamicStream}; + +macro_rules! gen_resource_type { + ($ty:ident, $n:expr) => { + match $n { + 0 => ResourceType::host::<$ty<0>>(), + 1 => ResourceType::host::<$ty<1>>(), + 2 => ResourceType::host::<$ty<2>>(), + 3 => ResourceType::host::<$ty<3>>(), + 4 => ResourceType::host::<$ty<4>>(), + 5 => ResourceType::host::<$ty<5>>(), + 6 => ResourceType::host::<$ty<6>>(), + 7 => ResourceType::host::<$ty<7>>(), + 8 => ResourceType::host::<$ty<8>>(), + 9 => ResourceType::host::<$ty<9>>(), + 10 => ResourceType::host::<$ty<10>>(), + 11 => ResourceType::host::<$ty<11>>(), + 12 => ResourceType::host::<$ty<12>>(), + 13 => ResourceType::host::<$ty<13>>(), + 14 => ResourceType::host::<$ty<14>>(), + 15 => ResourceType::host::<$ty<15>>(), + 16 => ResourceType::host::<$ty<16>>(), + 17 => ResourceType::host::<$ty<17>>(), + 18 => ResourceType::host::<$ty<18>>(), + 19 => ResourceType::host::<$ty<19>>(), + 20 => ResourceType::host::<$ty<20>>(), + 21 => ResourceType::host::<$ty<21>>(), + 22 => ResourceType::host::<$ty<22>>(), + 23 => ResourceType::host::<$ty<23>>(), + 24 => ResourceType::host::<$ty<24>>(), + 25 => ResourceType::host::<$ty<25>>(), + 26 => ResourceType::host::<$ty<26>>(), + 27 => ResourceType::host::<$ty<27>>(), + 28 => ResourceType::host::<$ty<28>>(), + 29 => ResourceType::host::<$ty<29>>(), + 30 => ResourceType::host::<$ty<30>>(), + 31 => ResourceType::host::<$ty<31>>(), + 32 => ResourceType::host::<$ty<32>>(), + 33 => ResourceType::host::<$ty<33>>(), + 34 => ResourceType::host::<$ty<34>>(), + 35 => ResourceType::host::<$ty<35>>(), + 36 => ResourceType::host::<$ty<36>>(), + 37 => ResourceType::host::<$ty<37>>(), + 38 => ResourceType::host::<$ty<38>>(), + 39 => ResourceType::host::<$ty<39>>(), + 40 => ResourceType::host::<$ty<40>>(), + 41 => ResourceType::host::<$ty<41>>(), + 42 => ResourceType::host::<$ty<42>>(), + 43 => ResourceType::host::<$ty<43>>(), + 44 => ResourceType::host::<$ty<44>>(), + 45 => ResourceType::host::<$ty<45>>(), + 46 => ResourceType::host::<$ty<46>>(), + 47 => ResourceType::host::<$ty<47>>(), + 48 => ResourceType::host::<$ty<48>>(), + 49 => ResourceType::host::<$ty<49>>(), + 50 => ResourceType::host::<$ty<50>>(), + 51 => ResourceType::host::<$ty<51>>(), + 52 => ResourceType::host::<$ty<52>>(), + 53 => ResourceType::host::<$ty<53>>(), + 54 => ResourceType::host::<$ty<54>>(), + 55 => ResourceType::host::<$ty<55>>(), + 56 => ResourceType::host::<$ty<56>>(), + 57 => ResourceType::host::<$ty<57>>(), + 58 => ResourceType::host::<$ty<58>>(), + 59 => ResourceType::host::<$ty<59>>(), + 60 => ResourceType::host::<$ty<60>>(), + 61 => ResourceType::host::<$ty<61>>(), + 62 => ResourceType::host::<$ty<62>>(), + 63 => ResourceType::host::<$ty<63>>(), + 64 => ResourceType::host::<$ty<64>>(), + 65 => ResourceType::host::<$ty<65>>(), + 66 => ResourceType::host::<$ty<66>>(), + 67 => ResourceType::host::<$ty<67>>(), + 68 => ResourceType::host::<$ty<68>>(), + 69 => ResourceType::host::<$ty<69>>(), + 70 => ResourceType::host::<$ty<70>>(), + 71 => ResourceType::host::<$ty<71>>(), + 72 => ResourceType::host::<$ty<72>>(), + 73 => ResourceType::host::<$ty<73>>(), + 74 => ResourceType::host::<$ty<74>>(), + 75 => ResourceType::host::<$ty<75>>(), + 76 => ResourceType::host::<$ty<76>>(), + 77 => ResourceType::host::<$ty<77>>(), + 78 => ResourceType::host::<$ty<78>>(), + 79 => ResourceType::host::<$ty<79>>(), + 80 => ResourceType::host::<$ty<80>>(), + 81 => ResourceType::host::<$ty<81>>(), + 82 => ResourceType::host::<$ty<82>>(), + 83 => ResourceType::host::<$ty<83>>(), + 84 => ResourceType::host::<$ty<84>>(), + 85 => ResourceType::host::<$ty<85>>(), + 86 => ResourceType::host::<$ty<86>>(), + 87 => ResourceType::host::<$ty<87>>(), + 88 => ResourceType::host::<$ty<88>>(), + 89 => ResourceType::host::<$ty<89>>(), + 90 => ResourceType::host::<$ty<90>>(), + 91 => ResourceType::host::<$ty<91>>(), + 92 => ResourceType::host::<$ty<92>>(), + 93 => ResourceType::host::<$ty<93>>(), + 94 => ResourceType::host::<$ty<94>>(), + 95 => ResourceType::host::<$ty<95>>(), + 96 => ResourceType::host::<$ty<96>>(), + 97 => ResourceType::host::<$ty<97>>(), + 98 => ResourceType::host::<$ty<98>>(), + 99 => ResourceType::host::<$ty<99>>(), + 100 => ResourceType::host::<$ty<100>>(), + 101 => ResourceType::host::<$ty<101>>(), + 102 => ResourceType::host::<$ty<102>>(), + 103 => ResourceType::host::<$ty<103>>(), + 104 => ResourceType::host::<$ty<104>>(), + 105 => ResourceType::host::<$ty<105>>(), + 106 => ResourceType::host::<$ty<106>>(), + 107 => ResourceType::host::<$ty<107>>(), + 108 => ResourceType::host::<$ty<108>>(), + 109 => ResourceType::host::<$ty<109>>(), + 110 => ResourceType::host::<$ty<110>>(), + 111 => ResourceType::host::<$ty<111>>(), + 112 => ResourceType::host::<$ty<112>>(), + 113 => ResourceType::host::<$ty<113>>(), + 114 => ResourceType::host::<$ty<114>>(), + 115 => ResourceType::host::<$ty<115>>(), + 116 => ResourceType::host::<$ty<116>>(), + 117 => ResourceType::host::<$ty<117>>(), + 118 => ResourceType::host::<$ty<118>>(), + 119 => ResourceType::host::<$ty<119>>(), + 120 => ResourceType::host::<$ty<120>>(), + 121 => ResourceType::host::<$ty<121>>(), + 122 => ResourceType::host::<$ty<122>>(), + 123 => ResourceType::host::<$ty<123>>(), + 124 => ResourceType::host::<$ty<124>>(), + 125 => ResourceType::host::<$ty<125>>(), + 126 => ResourceType::host::<$ty<126>>(), + 127 => ResourceType::host::<$ty<127>>(), + 128 => ResourceType::host::<$ty<128>>(), + 129 => ResourceType::host::<$ty<129>>(), + 130 => ResourceType::host::<$ty<130>>(), + 131 => ResourceType::host::<$ty<131>>(), + 132 => ResourceType::host::<$ty<132>>(), + 133 => ResourceType::host::<$ty<133>>(), + 134 => ResourceType::host::<$ty<134>>(), + 135 => ResourceType::host::<$ty<135>>(), + 136 => ResourceType::host::<$ty<136>>(), + 137 => ResourceType::host::<$ty<137>>(), + 138 => ResourceType::host::<$ty<138>>(), + 139 => ResourceType::host::<$ty<139>>(), + 140 => ResourceType::host::<$ty<140>>(), + 141 => ResourceType::host::<$ty<141>>(), + 142 => ResourceType::host::<$ty<142>>(), + 143 => ResourceType::host::<$ty<143>>(), + 144 => ResourceType::host::<$ty<144>>(), + 145 => ResourceType::host::<$ty<145>>(), + 146 => ResourceType::host::<$ty<146>>(), + 147 => ResourceType::host::<$ty<147>>(), + 148 => ResourceType::host::<$ty<148>>(), + 149 => ResourceType::host::<$ty<149>>(), + 150 => ResourceType::host::<$ty<150>>(), + 151 => ResourceType::host::<$ty<151>>(), + 152 => ResourceType::host::<$ty<152>>(), + 153 => ResourceType::host::<$ty<153>>(), + 154 => ResourceType::host::<$ty<154>>(), + 155 => ResourceType::host::<$ty<155>>(), + 156 => ResourceType::host::<$ty<156>>(), + 157 => ResourceType::host::<$ty<157>>(), + 158 => ResourceType::host::<$ty<158>>(), + 159 => ResourceType::host::<$ty<159>>(), + 160 => ResourceType::host::<$ty<160>>(), + 161 => ResourceType::host::<$ty<161>>(), + 162 => ResourceType::host::<$ty<162>>(), + 163 => ResourceType::host::<$ty<163>>(), + 164 => ResourceType::host::<$ty<164>>(), + 165 => ResourceType::host::<$ty<165>>(), + 166 => ResourceType::host::<$ty<166>>(), + 167 => ResourceType::host::<$ty<167>>(), + 168 => ResourceType::host::<$ty<168>>(), + 169 => ResourceType::host::<$ty<169>>(), + 170 => ResourceType::host::<$ty<170>>(), + 171 => ResourceType::host::<$ty<171>>(), + 172 => ResourceType::host::<$ty<172>>(), + 173 => ResourceType::host::<$ty<173>>(), + 174 => ResourceType::host::<$ty<174>>(), + 175 => ResourceType::host::<$ty<175>>(), + 176 => ResourceType::host::<$ty<176>>(), + 177 => ResourceType::host::<$ty<177>>(), + 178 => ResourceType::host::<$ty<178>>(), + 179 => ResourceType::host::<$ty<179>>(), + 180 => ResourceType::host::<$ty<180>>(), + 181 => ResourceType::host::<$ty<181>>(), + 182 => ResourceType::host::<$ty<182>>(), + 183 => ResourceType::host::<$ty<183>>(), + 184 => ResourceType::host::<$ty<184>>(), + 185 => ResourceType::host::<$ty<185>>(), + 186 => ResourceType::host::<$ty<186>>(), + 187 => ResourceType::host::<$ty<187>>(), + 188 => ResourceType::host::<$ty<188>>(), + 189 => ResourceType::host::<$ty<189>>(), + 190 => ResourceType::host::<$ty<190>>(), + 191 => ResourceType::host::<$ty<191>>(), + 192 => ResourceType::host::<$ty<192>>(), + 193 => ResourceType::host::<$ty<193>>(), + 194 => ResourceType::host::<$ty<194>>(), + 195 => ResourceType::host::<$ty<195>>(), + 196 => ResourceType::host::<$ty<196>>(), + 197 => ResourceType::host::<$ty<197>>(), + 198 => ResourceType::host::<$ty<198>>(), + 199 => ResourceType::host::<$ty<199>>(), + 200 => ResourceType::host::<$ty<200>>(), + 201 => ResourceType::host::<$ty<201>>(), + 202 => ResourceType::host::<$ty<202>>(), + 203 => ResourceType::host::<$ty<203>>(), + 204 => ResourceType::host::<$ty<204>>(), + 205 => ResourceType::host::<$ty<205>>(), + 206 => ResourceType::host::<$ty<206>>(), + 207 => ResourceType::host::<$ty<207>>(), + 208 => ResourceType::host::<$ty<208>>(), + 209 => ResourceType::host::<$ty<209>>(), + 210 => ResourceType::host::<$ty<210>>(), + 211 => ResourceType::host::<$ty<211>>(), + 212 => ResourceType::host::<$ty<212>>(), + 213 => ResourceType::host::<$ty<213>>(), + 214 => ResourceType::host::<$ty<214>>(), + 215 => ResourceType::host::<$ty<215>>(), + 216 => ResourceType::host::<$ty<216>>(), + 217 => ResourceType::host::<$ty<217>>(), + 218 => ResourceType::host::<$ty<218>>(), + 219 => ResourceType::host::<$ty<219>>(), + 220 => ResourceType::host::<$ty<220>>(), + 221 => ResourceType::host::<$ty<221>>(), + 222 => ResourceType::host::<$ty<222>>(), + 223 => ResourceType::host::<$ty<223>>(), + 224 => ResourceType::host::<$ty<224>>(), + 225 => ResourceType::host::<$ty<225>>(), + 226 => ResourceType::host::<$ty<226>>(), + 227 => ResourceType::host::<$ty<227>>(), + 228 => ResourceType::host::<$ty<228>>(), + 229 => ResourceType::host::<$ty<229>>(), + 230 => ResourceType::host::<$ty<230>>(), + 231 => ResourceType::host::<$ty<231>>(), + 232 => ResourceType::host::<$ty<232>>(), + 233 => ResourceType::host::<$ty<233>>(), + 234 => ResourceType::host::<$ty<234>>(), + 235 => ResourceType::host::<$ty<235>>(), + 236 => ResourceType::host::<$ty<236>>(), + 237 => ResourceType::host::<$ty<237>>(), + 238 => ResourceType::host::<$ty<238>>(), + 239 => ResourceType::host::<$ty<239>>(), + 240 => ResourceType::host::<$ty<240>>(), + 241 => ResourceType::host::<$ty<241>>(), + 242 => ResourceType::host::<$ty<242>>(), + 243 => ResourceType::host::<$ty<243>>(), + 244 => ResourceType::host::<$ty<244>>(), + 245 => ResourceType::host::<$ty<245>>(), + 246 => ResourceType::host::<$ty<246>>(), + 247 => ResourceType::host::<$ty<247>>(), + 248 => ResourceType::host::<$ty<248>>(), + 249 => ResourceType::host::<$ty<249>>(), + 250 => ResourceType::host::<$ty<250>>(), + 251 => ResourceType::host::<$ty<251>>(), + 252 => ResourceType::host::<$ty<252>>(), + 253 => ResourceType::host::<$ty<253>>(), + 254 => ResourceType::host::<$ty<254>>(), + 255 => ResourceType::host::<$ty<255>>(), + } + }; +} + +pub fn return_resource(n: u8) -> ResourceType { + gen_resource_type!(DynamicReturn, n) +} + +pub fn future_resource(n: u8) -> ResourceType { + gen_resource_type!(DynamicFuture, n) +} + +pub fn stream_resource(n: u8) -> ResourceType { + gen_resource_type!(DynamicStream, n) +} diff --git a/crates/runtime-wasmtime/src/types/mod.rs b/crates/runtime-wasmtime/src/types/mod.rs new file mode 100644 index 000000000..a47ae11c9 --- /dev/null +++ b/crates/runtime-wasmtime/src/types/mod.rs @@ -0,0 +1,870 @@ +use core::borrow::Borrow; +use core::ops::{Bound, RangeBounds}; + +use std::collections::btree_map; +use std::collections::{BTreeMap, HashMap}; + +use anyhow::{bail, ensure}; +use tracing::{debug, instrument, trace}; +use wasmtime::component::types::{Component, ComponentFunc, ComponentInstance, ComponentItem}; +use wasmtime::component::{ResourceType, Type}; +use wasmtime::Engine; + +use crate::bindings::rpc::error::Error; + +mod dynamic; + +struct DynamicReturn {} +struct DynamicStream {} +struct DynamicFuture {} + +// this returns the RPC name for a wasmtime function name. +// Unfortunately, the [`ComponentFunc`] does not include the kind information and we want to +// avoid (re-)parsing the WIT here. +pub(crate) fn rpc_func_name(name: &str) -> &str { + if let Some(name) = name.strip_prefix("[constructor]") { + name + } else if let Some(name) = name.strip_prefix("[static]") { + name + } else if let Some(name) = name.strip_prefix("[method]") { + name + } else { + name + } +} + +pub(crate) enum CustomReturnType { + Rpc(Option), + AsyncReturn(Option), +} + +impl CustomReturnType { + pub fn new>( + host_resources: &HashMap, HashMap, (ResourceType, ResourceType)>>, + results_ty: impl IntoIterator, + ) -> Option { + let rpc_err_ty = host_resources + .get("wrpc:rpc/error@0.1.0") + .and_then(|instance| instance.get("error")); + let mut results_ty = results_ty.into_iter(); + match ( + rpc_err_ty, + results_ty.next().as_ref().map(Borrow::borrow), + results_ty.next(), + ) { + (Some((guest_rpc_err_ty, host_rpc_err_ty)), Some(Type::Result(result_ty)), None) + if *host_rpc_err_ty == ResourceType::host::() + && result_ty.err() == Some(Type::Own(*guest_rpc_err_ty)) => + { + Some(Self::Rpc(result_ty.ok())) + } + _ => None, + } + } +} + +/// Recursively iterates the component item type and collects all exported resource types +#[instrument(level = "debug", skip_all)] +fn collect_item_resource_exports( + engine: &Engine, + ty: ComponentItem, + resources: &mut impl Extend, +) { + match ty { + ComponentItem::ComponentFunc(_) + | ComponentItem::CoreFunc(_) + | ComponentItem::Module(_) + | ComponentItem::Type(_) => {} + ComponentItem::Component(ty) => collect_component_resource_exports(engine, &ty, resources), + + ComponentItem::ComponentInstance(ty) => { + collect_instance_resource_exports(engine, &ty, resources) + } + ComponentItem::Resource(ty) => { + debug!(?ty, "collect resource export"); + resources.extend([ty]) + } + } +} + +/// Recursively iterates the instance type and collects all exported resource types +#[instrument(level = "debug", skip_all)] +fn collect_instance_resource_exports( + engine: &Engine, + ty: &ComponentInstance, + resources: &mut impl Extend, +) { + for (name, ty) in ty.exports(engine) { + trace!(name, ?ty, "collect instance item resource exports"); + collect_item_resource_exports(engine, ty, resources); + } +} + +/// Recursively iterates the component type and collects all exported resource types +#[instrument(level = "debug", skip_all)] +fn collect_component_resource_exports( + engine: &Engine, + ty: &Component, + resources: &mut impl Extend, +) { + for (name, ty) in ty.exports(engine) { + trace!(name, ?ty, "collect component item resource exports"); + collect_item_resource_exports(engine, ty, resources); + } +} + +#[derive(Clone, Debug, Default)] +pub struct WasiIoStreamResources { + pub input_stream: Box<[ResourceType]>, + pub output_stream: Box<[ResourceType]>, +} + +impl WasiIoStreamResources { + pub fn host_type(&self, ty: &ResourceType) -> Option { + use wasmtime_wasi::bindings::io::streams; + + match ty { + ty if self.input_stream.contains(ty) => { + Some(ResourceType::host::()) + } + ty if self.output_stream.contains(ty) => { + Some(ResourceType::host::()) + } + _ => None, + } + } +} + +#[derive(Clone, Debug, Default)] +pub struct WasiIoResources { + pub error: Box<[ResourceType]>, + pub pollable: Box<[ResourceType]>, + pub input_stream: Box<[ResourceType]>, + pub output_stream: Box<[ResourceType]>, +} + +impl WasiIoResources { + pub fn host_type(&self, ty: &ResourceType) -> Option { + use wasmtime_wasi::bindings::io; + + match ty { + ty if self.error.contains(ty) => Some(ResourceType::host::()), + ty if self.input_stream.contains(ty) => { + Some(ResourceType::host::()) + } + ty if self.output_stream.contains(ty) => { + Some(ResourceType::host::()) + } + ty if self.pollable.contains(ty) => Some(ResourceType::host::()), + _ => None, + } + } +} + +#[derive(Clone, Debug, Default)] +pub struct WrpcRpcTransportResources { + pub incoming_channel: Box<[ResourceType]>, + pub outgoing_channel: Box<[ResourceType]>, + pub invocation: Box<[ResourceType]>, +} + +impl WrpcRpcTransportResources { + pub fn host_type(&self, ty: &ResourceType) -> Option { + use crate::bindings::rpc::transport; + + match ty { + ty if self.incoming_channel.contains(ty) => { + Some(ResourceType::host::()) + } + ty if self.outgoing_channel.contains(ty) => { + Some(ResourceType::host::()) + } + ty if self.invocation.contains(ty) => { + Some(ResourceType::host::()) + } + _ => None, + } + } +} + +#[derive(Clone, Debug, Default)] +pub struct WrpcRpcResources { + pub context: Box<[ResourceType]>, + pub error: Box<[ResourceType]>, + pub incoming_channel: Box<[ResourceType]>, + pub outgoing_channel: Box<[ResourceType]>, + pub invocation: Box<[ResourceType]>, +} + +impl WrpcRpcResources { + pub fn host_type(&self, ty: &ResourceType) -> Option { + use crate::bindings::rpc; + + match ty { + ty if self.error.contains(ty) => Some(ResourceType::host::()), + ty if self.context.contains(ty) => Some(ResourceType::host::()), + ty if self.incoming_channel.contains(ty) => { + Some(ResourceType::host::()) + } + ty if self.outgoing_channel.contains(ty) => { + Some(ResourceType::host::()) + } + ty if self.invocation.contains(ty) => { + Some(ResourceType::host::()) + } + _ => None, + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum DynamicReturnType { + Eq(Type), + Borrowed(ResourceType), +} + +#[derive(Clone, Debug, Default)] +pub struct DynamicResources { + pub returns: Box<[(DynamicReturnType, Vec)]>, + pub futures: Box<[(Type, Vec)]>, + pub streams: Box<[(Type, Vec)]>, +} + +impl DynamicResources { + pub fn host_type(&self, ty: &ResourceType) -> Option { + for (i, (_, returns)) in self.returns.iter().enumerate() { + if returns.contains(ty) { + return i.try_into().map(dynamic::return_resource).ok(); + } + } + for (i, (_, futures)) in self.futures.iter().enumerate() { + if futures.contains(ty) { + return i.try_into().map(dynamic::future_resource).ok(); + } + } + for (i, (_, streams)) in self.streams.iter().enumerate() { + if streams.contains(ty) { + return i.try_into().map(dynamic::stream_resource).ok(); + } + } + None + } +} + +#[derive(Clone, Debug, Default)] +pub struct DynamicResourceCtx { + pub returns: Box<[DynamicReturnType]>, + pub futures: Box<[Type]>, + pub streams: Box<[Type]>, +} + +#[derive(Clone, Debug, Default)] +pub struct WasiextDynamicTypeResources { + pub async_return: Box<[ResourceType]>, + pub dynamic_future: Box<[ResourceType]>, + pub dynamic_stream: Box<[ResourceType]>, + pub stream_send: Box<[ResourceType]>, + pub future_send: Box<[ResourceType]>, +} + +impl WasiextDynamicTypeResources { + pub fn host_type(&self, ty: &ResourceType) -> Option { + // TODO: Set resource types + match ty { + ty if self.async_return.contains(ty) => Some(ResourceType::host::<()>()), + ty if self.dynamic_future.contains(ty) => Some(ResourceType::host::<()>()), + ty if self.dynamic_stream.contains(ty) => Some(ResourceType::host::<()>()), + ty if self.stream_send.contains(ty) => Some(ResourceType::host::<()>()), + ty if self.future_send.contains(ty) => Some(ResourceType::host::<()>()), + _ => None, + } + } +} + +#[derive(Clone, Debug, Default)] +pub struct ComponentTypeInfo { + /// Resource types exported by the component, collected recursively. May contain duplicates. + pub exported_resources: Vec, + /// Resource types imported by the component per top-level instance + pub imported_resources: BTreeMap, HashMap, ResourceType>>, + /// Component function types imported by the component per top-level instance + pub imported_functions: BTreeMap, HashMap, ComponentFunc>>, + /// Component types imported by the component per top-level instance + pub imported_components: BTreeMap, ComponentTypeInfo>, +} + +fn assert_subscribe_signature( + instance: &HashMap, ComponentFunc>, + name: &str, + io_pollables: &[ResourceType], +) -> anyhow::Result<()> { + if let Some(ty) = instance.get(format!("[method]{name}.subscribe").as_str()) { + let mut pty = ty.params(); + let (Some((_, Type::Borrow(..))), None) = (pty.next(), pty.next()) else { + bail!("`subscribe` on resource `{name}` does not take the borrowed resource as the only parameter") + }; + let mut ty = ty.results(); + let (Some(Type::Own(ty)), None) = (ty.next(), ty.next()) else { + bail!("`subscribe` on resource `{name}` does not return a single value") + }; + ensure!( + io_pollables.contains(&ty), + "`subscribe` on resource `{name}` does not return `wasi:io/poll.pollable`" + ); + } + Ok(()) +} + +fn assert_empty_constructor_signature( + instance: &HashMap, ComponentFunc>, + name: &str, +) -> anyhow::Result<()> { + if let Some(ty) = instance.get(format!("[constructor]{name}").as_str()) { + ensure!( + ty.params().next().is_none(), + "constructor of resource `{name}` must not take any arguments as parameters" + ); + let mut ty = ty.results(); + let (Some(Type::Own(..)), None) = (ty.next(), ty.next()) else { + bail!("constructor of resource `{name}` does not return an owned resource") + }; + } + Ok(()) +} + +fn async_return_constructor_import( + instance: &HashMap, ComponentFunc>, + name: &str, +) -> anyhow::Result> { + instance + .get(format!("[constructor]{name}").as_str()) + .map(|ty| { + let mut pty = ty.params(); + let (Some((_, pty)), None) = (pty.next(), pty.next()) else { + bail!( + "constructor of resource `{name}` does not take a single argument as parameter" + ) + }; + let mut rty = ty.results(); + let (Some(Type::Own(rty)), None) = (rty.next(), rty.next()) else { + bail!("constructor of resource `{name}` does not return an owned resource") + }; + Ok((pty, rty)) + }) + .transpose() +} + +fn async_return_await_import( + instance: &HashMap, ComponentFunc>, + name: &str, +) -> anyhow::Result> { + instance + .get(format!("[static]{name}.await").as_str()) + .map(|ty| { + let mut pty = ty.params(); + let (Some((_, Type::Own(pty))), None) = (pty.next(), pty.next()) else { + bail!("`await` on resource `{name}` does not take the owned resource as the only parameter") + }; + let mut rty = ty.results(); + let (Some(rty), None) = (rty.next(), rty.next()) else { + bail!("`await` on resource `{name}` does not return a single value") + }; + Ok((pty, rty)) + }) + .transpose() +} + +fn async_return_import_type( + instance: &HashMap, ComponentFunc>, + name: &str, + io_pollables: &[ResourceType], +) -> anyhow::Result<(ResourceType, DynamicReturnType)> { + assert_subscribe_signature(instance, name, io_pollables)?; + let constructor_ty = async_return_constructor_import(instance, name)?; + let await_ty = async_return_await_import(instance, name)?; + + match (constructor_ty, await_ty) { + (None, None) => { + bail!("`{name}` resource imports neither a constructor, nor `await`") + } + (None, Some((resource_ty, ty))) | (Some((ty, resource_ty)), None) => { + Ok((resource_ty, DynamicReturnType::Eq(ty))) + } + + (Some((ret_ty, ret_resource_ty)), Some((rx_resource_ty, rx_ty))) + if ret_ty == rx_ty && ret_resource_ty == rx_resource_ty => + { + Ok((ret_resource_ty, DynamicReturnType::Eq(ret_ty))) + } + + ( + Some((Type::Borrow(ret_ty), ret_resource_ty)), + Some((rx_resource_ty, Type::Own(rx_ty))), + ) if ret_ty == rx_ty && ret_resource_ty == rx_resource_ty => { + Ok((ret_resource_ty, DynamicReturnType::Borrowed(ret_ty))) + } + (Some(..), Some(..)) => { + bail!("`{name}` resource constructor and `await` type do not match") + } + } +} + +fn dynamic_future_await_import( + instance: &HashMap, ComponentFunc>, + name: &str, +) -> anyhow::Result> { + instance + .get(format!("[method]{name}.await").as_str()) + .map(|ty| { + let mut pty = ty.params(); + let (Some((_, Type::Borrow(pty))), None) = (pty.next(), pty.next()) else { + bail!("`await` on resource `{name}` does not take the borrowed resource as the only parameter") + }; + let mut rty = ty.results(); + let (Some(Type::Option(rty)), None) = (rty.next(), rty.next()) else { + bail!("`await` on resource `{name}` does not return a single optional value") + }; + Ok((pty, rty.ty())) + }) + .transpose() +} + +fn dynamic_future_send_import( + instance: &HashMap, ComponentFunc>, + name: &str, + future_send: &[ResourceType], +) -> anyhow::Result> { + instance + .get(format!("[static]{name}.send").as_str()) + .map(|ty| { + let mut pty = ty.params(); + let (Some((_, Type::Own(pty0))), Some((_, pty1)), None) = + (pty.next(), pty.next(), pty.next()) + else { + bail!("`send` on resource `{name}` does not take the owned resource as the first and value as the second parameter") + }; + let mut rty = ty.results(); + let (Some(Type::Own(ty)), None) = (rty.next(), rty.next()) else { + bail!("`send` on resource `{name}` does not return a single value") + }; + ensure!( + future_send.contains(&ty), + "`send` on resource `{name}` does not return `wasiext:dynamic/types.future-send`" + ); + Ok((pty0, pty1)) + }) + .transpose() +} + +fn dynamic_future_import_type( + instance: &HashMap, ComponentFunc>, + name: &str, + io_pollables: &[ResourceType], + future_send: &[ResourceType], +) -> anyhow::Result<(ResourceType, Type)> { + assert_subscribe_signature(instance, name, io_pollables)?; + assert_empty_constructor_signature(instance, name)?; + + let await_ty = dynamic_future_await_import(instance, name)?; + let send_ty = dynamic_future_send_import(instance, name, &future_send)?; + + match (send_ty, await_ty) { + (None, None) => { + bail!("dynamic future `{name}` resource imports neither `send`, nor `await`") + } + (None, Some((resource_ty, ty))) | (Some((resource_ty, ty)), None) => Ok((resource_ty, ty)), + + (Some((send_resource_ty, send_ty)), Some((rx_resource_ty, rx_ty))) + if send_ty == rx_ty && send_resource_ty == rx_resource_ty => + { + Ok((send_resource_ty, send_ty)) + } + + (Some(..), Some(..)) => { + bail!("dynamic future `{name}` resource `send` and `await` types do not match") + } + } +} + +fn dynamic_stream_receive_import( + instance: &HashMap, ComponentFunc>, + name: &str, +) -> anyhow::Result> { + instance + .get(format!("[method]{name}.receive").as_str()) + .map(|ty| { + let mut pty = ty.params(); + let (Some((_, Type::Borrow(pty))), Some((_, Type::U32)), None) = (pty.next(), pty.next(), pty.next()) else { + bail!("`receive` on resource `{name}` does not take the borrowed resource as the first and u32 count as the second parameter") + }; + let mut rty = ty.results(); + let (Some(Type::List(rty)), None) = (rty.next(), rty.next()) else { + bail!("`receive` on resource `{name}` does not return a list values") + }; + Ok((pty, rty.ty())) + }) + .transpose() +} + +fn dynamic_stream_send_import( + instance: &HashMap, ComponentFunc>, + name: &str, + stream_send: &[ResourceType], +) -> anyhow::Result> { + instance + .get(format!("[method]{name}.send").as_str()) + .map(|ty| { + let mut pty = ty.params(); + let (Some((_, Type::Borrow(pty0))), Some((_, Type::List(pty1))), None) = + (pty.next(), pty.next(), pty.next()) + else { + bail!("`send` on resource `{name}` does not take the borrowed resource as the first and list of values as the second parameter") + }; + let mut rty = ty.results(); + let (Some(Type::Own(ty)), None) = (rty.next(), rty.next()) else { + bail!("`send` on resource `{name}` does not return a single value") + }; + ensure!( + stream_send.contains(&ty), + "`send` on resource `{name}` does not return `wasiext:dynamic/types.stream-send`" + ); + Ok((pty0, pty1.ty())) + }) + .transpose() +} + +fn dynamic_stream_import_type( + instance: &HashMap, ComponentFunc>, + name: &str, + io_pollables: &[ResourceType], + stream_send: &[ResourceType], +) -> anyhow::Result<(ResourceType, Type)> { + assert_subscribe_signature(instance, name, io_pollables)?; + assert_empty_constructor_signature(instance, name)?; + + let receive_ty = dynamic_stream_receive_import(instance, name)?; + let send_ty = dynamic_stream_send_import(instance, name, &stream_send)?; + + match (send_ty, receive_ty) { + (None, None) => { + bail!("dynamic stream `{name}` resource imports neither `send`, nor `receive`") + } + (None, Some((resource_ty, ty))) | (Some((resource_ty, ty)), None) => Ok((resource_ty, ty)), + + (Some((send_resource_ty, send_ty)), Some((rx_resource_ty, rx_ty))) + if send_ty == rx_ty && send_resource_ty == rx_resource_ty => + { + Ok((send_resource_ty, send_ty)) + } + + (Some(..), Some(..)) => { + bail!("dynamic stream `{name}` resource `send` and `receive` types do not match") + } + } +} + +impl ComponentTypeInfo { + pub fn new(engine: &Engine, ty: &Component) -> Self { + let mut exported_resources = Vec::default(); + let mut imported_resources = BTreeMap::<_, HashMap<_, _>>::default(); + let mut imported_functions = BTreeMap::<_, HashMap<_, _>>::default(); + let mut imported_components = BTreeMap::default(); + for (name, ty) in ty.imports(engine) { + match ty { + ComponentItem::CoreFunc(..) + | ComponentItem::Module(..) + | ComponentItem::Type(..) => {} + ComponentItem::ComponentInstance(ty) => { + let instance = name; + for (name, ty) in ty.exports(engine) { + match ty { + ComponentItem::CoreFunc(..) + | ComponentItem::Module(..) + | ComponentItem::ComponentInstance(..) + | ComponentItem::Type(..) => {} + ComponentItem::ComponentFunc(ty) => { + debug!(instance, name, ?ty, "collect instance function import"); + if let Some(imported_functions) = + imported_functions.get_mut(instance) + { + imported_functions.insert(name.into(), ty); + } else { + imported_functions.insert( + instance.into(), + HashMap::from([(name.into(), ty)]), + ); + } + } + ComponentItem::Component(ty) => { + debug!(name, "collect instance component import"); + imported_components.insert(name.into(), Self::new(engine, &ty)); + } + ComponentItem::Resource(ty) => { + debug!(instance, name, ?ty, "collect instance resource import"); + if let Some(imported_resources) = + imported_resources.get_mut(instance) + { + imported_resources.insert(name.into(), ty); + } else { + imported_resources.insert( + instance.into(), + HashMap::from([(name.into(), ty)]), + ); + } + } + } + } + } + ComponentItem::ComponentFunc(ty) => { + debug!(name, "collect component function import"); + if let Some(imported_functions) = imported_functions.get_mut("") { + imported_functions.insert(name.into(), ty); + } else { + imported_functions.insert("".into(), HashMap::from([(name.into(), ty)])); + } + } + ComponentItem::Component(ty) => { + debug!(name, "collect component component import"); + imported_components.insert(name.into(), Self::new(engine, &ty)); + } + ComponentItem::Resource(ty) => { + debug!(name, "collect component resource import"); + if let Some(imported_resources) = imported_resources.get_mut("") { + imported_resources.insert(name.into(), ty); + } else { + imported_resources.insert("".into(), HashMap::from([(name.into(), ty)])); + } + } + } + } + collect_component_resource_exports(engine, ty, &mut exported_resources); + Self { + exported_resources, + imported_resources, + imported_functions, + imported_components, + } + } + + pub fn imported_instance_resources( + &self, + range: impl RangeBounds, + ) -> btree_map::Range<'_, Box, HashMap, ResourceType>> { + self.imported_resources.range(range) + } + + pub fn imported_wasi_io_error_resources(&self) -> Box<[ResourceType]> { + self.imported_instance_resources(( + Bound::Included("wasi:io/error@0.2"), + Bound::Excluded("wasi:io/error@0.3"), + )) + .flat_map(|(_, instance)| instance.get("error").copied()) + .collect() + } + + pub fn imported_wasi_io_pollable_resources(&self) -> Box<[ResourceType]> { + self.imported_instance_resources(( + Bound::Included("wasi:io/poll@0.2"), + Bound::Excluded("wasi:io/poll@0.3"), + )) + .flat_map(|(_, instance)| instance.get("pollable").copied()) + .collect() + } + + pub fn imported_wasi_io_stream_resources(&self) -> WasiIoStreamResources { + let mut input_stream = Vec::default(); + let mut output_stream = Vec::default(); + for (_, instance) in self.imported_instance_resources(( + Bound::Included("wasi:io/streams@0.2"), + Bound::Excluded("wasi:io/streams@0.3"), + )) { + if let Some(ty) = instance.get("input-stream") { + input_stream.push(*ty); + } + if let Some(ty) = instance.get("output-stream") { + output_stream.push(*ty); + } + } + WasiIoStreamResources { + input_stream: input_stream.into(), + output_stream: output_stream.into(), + } + } + + pub fn imported_wasi_io_resources(&self) -> WasiIoResources { + let error = self.imported_wasi_io_error_resources(); + let pollable = self.imported_wasi_io_pollable_resources(); + let WasiIoStreamResources { + input_stream, + output_stream, + } = self.imported_wasi_io_stream_resources(); + WasiIoResources { + error, + pollable, + input_stream, + output_stream, + } + } + + pub fn imported_wrpc_rpc_error_resources(&self) -> Box<[ResourceType]> { + self.imported_instance_resources(( + Bound::Included("wrpc:rpc/error@0.1"), + Bound::Excluded("wrpc:rpc/error@0.2"), + )) + .flat_map(|(_, instance)| instance.get("error").copied()) + .collect() + } + + pub fn imported_wrpc_rpc_context_resources(&self) -> Box<[ResourceType]> { + self.imported_instance_resources(( + Bound::Included("wrpc:rpc/context@0.1"), + Bound::Excluded("wrpc:rpc/context@0.2"), + )) + .flat_map(|(_, instance)| instance.get("context").copied()) + .collect() + } + + pub fn imported_wrpc_rpc_transport_resources(&self) -> WrpcRpcTransportResources { + let mut incoming_channel = Vec::default(); + let mut outgoing_channel = Vec::default(); + let mut invocation = Vec::default(); + for (_, instance) in self.imported_instance_resources(( + Bound::Included("wrpc:rpc/transport@0.1"), + Bound::Excluded("wrpc:rpc/transport@0.2"), + )) { + if let Some(ty) = instance.get("incoming-channel") { + incoming_channel.push(*ty); + } + if let Some(ty) = instance.get("outgoing-channel") { + outgoing_channel.push(*ty); + } + if let Some(ty) = instance.get("invocation") { + invocation.push(*ty); + } + } + WrpcRpcTransportResources { + incoming_channel: incoming_channel.into(), + outgoing_channel: outgoing_channel.into(), + invocation: invocation.into(), + } + } + + pub fn imported_wrpc_rpc_resources(&self) -> WrpcRpcResources { + let error = self.imported_wrpc_rpc_error_resources(); + let context = self.imported_wrpc_rpc_context_resources(); + let WrpcRpcTransportResources { + incoming_channel, + outgoing_channel, + invocation, + } = self.imported_wrpc_rpc_transport_resources(); + WrpcRpcResources { + error, + context, + incoming_channel, + outgoing_channel, + invocation, + } + } + + pub fn imported_wasiext_dynamic_type_resources(&self) -> WasiextDynamicTypeResources { + let mut async_return = Vec::default(); + let mut dynamic_future = Vec::default(); + let mut dynamic_stream = Vec::default(); + let mut future_send = Vec::default(); + let mut stream_send = Vec::default(); + for (_, instance) in self.imported_instance_resources(( + Bound::Included("wasiext:dynamic/types@0.1"), + Bound::Excluded("wasiext:dynamic/types@0.2"), + )) { + if let Some(ty) = instance.get("async-return") { + async_return.push(*ty); + } + if let Some(ty) = instance.get("dynamic-future") { + dynamic_future.push(*ty); + } + if let Some(ty) = instance.get("dynamic-stream") { + dynamic_stream.push(*ty); + } + if let Some(ty) = instance.get("future-send") { + future_send.push(*ty); + } + if let Some(ty) = instance.get("stream-send") { + stream_send.push(*ty); + } + } + WasiextDynamicTypeResources { + async_return: async_return.into(), + dynamic_future: dynamic_future.into(), + dynamic_stream: dynamic_stream.into(), + future_send: future_send.into(), + stream_send: stream_send.into(), + } + } + + pub fn imported_dynamic_resources( + &self, + types: &WasiextDynamicTypeResources, + io_pollables: &[ResourceType], + ) -> anyhow::Result { + let mut returns = Vec::<(_, Vec<_>)>::default(); + let mut futures = Vec::<(_, Vec<_>)>::default(); + let mut streams = Vec::<(_, Vec<_>)>::default(); + for instance in self.imported_functions.values() { + 'outer: for (name, ty) in instance { + let Some(name) = name.strip_prefix("[static]") else { + continue; + }; + let Some((name, "register-dynamic-type")) = name.split_once('.') else { + continue; + }; + let mut ty = ty.results(); + let (Some(Type::Own(ty)), None) = (ty.next(), ty.next()) else { + continue; + }; + if types.async_return.contains(&ty) { + let (resource_ty, ty) = async_return_import_type(instance, name, io_pollables)?; + for (rty, resources) in &mut returns { + if *rty == ty { + resources.push(resource_ty); + continue 'outer; + } + } + returns.push((ty, vec![resource_ty])); + } else if types.dynamic_future.contains(&ty) { + let (resource_ty, ty) = dynamic_future_import_type( + instance, + name, + io_pollables, + &types.future_send, + )?; + for (rty, resources) in &mut futures { + if *rty == ty { + resources.push(resource_ty); + continue 'outer; + } + } + futures.push((ty, vec![resource_ty])); + } else if types.dynamic_stream.contains(&ty) { + let (resource_ty, ty) = dynamic_stream_import_type( + instance, + name, + io_pollables, + &types.stream_send, + )?; + for (rty, resources) in &mut streams { + if *rty == ty { + resources.push(resource_ty); + continue 'outer; + } + } + streams.push((ty, vec![resource_ty])); + } + } + } + Ok(DynamicResources { + returns: returns.into(), + futures: futures.into(), + streams: streams.into(), + }) + } +} diff --git a/crates/runtime-wasmtime/wit/deps.lock b/crates/runtime-wasmtime/wit/deps.lock index aa6cd17c1..f30670f49 100644 --- a/crates/runtime-wasmtime/wit/deps.lock +++ b/crates/runtime-wasmtime/wit/deps.lock @@ -1,3 +1,9 @@ +[dynamic] +url = "https://github.com/wasiext/dynamic/archive/main.tar.gz" +sha256 = "d2990b9cbe92aefb33e74d37aa601eb821214d0b811526598241936620f67f34" +sha512 = "cec5ee06a35512c2fadf8cfb75cb879a5f554baf24eeac070ad1218d6751403a25640ef68505756103c7c87bcd1ea009283b200f849095ba4e0139d05125347b" +deps = ["io"] + [io] sha256 = "7210e5653539a15478f894d4da24cc69d61924cbcba21d2804d69314a88e5a4c" sha512 = "49184a1b0945a889abd52d25271172ed3dc2db6968fcdddb1bab7ee0081f4a3eeee0977ad2291126a37631c0d86eeea75d822fa8af224c422134500bf9f0f2bb" diff --git a/crates/runtime-wasmtime/wit/deps.toml b/crates/runtime-wasmtime/wit/deps.toml index 4b23dfc48..d5ec67655 100644 --- a/crates/runtime-wasmtime/wit/deps.toml +++ b/crates/runtime-wasmtime/wit/deps.toml @@ -1 +1,2 @@ +dynamic = "https://github.com/wasiext/dynamic/archive/main.tar.gz" rpc = "https://github.com/wrpc/rpc/archive/main.tar.gz" diff --git a/crates/runtime-wasmtime/wit/deps/dynamic/dynamic.wit b/crates/runtime-wasmtime/wit/deps/dynamic/dynamic.wit new file mode 100644 index 000000000..c07b1366f --- /dev/null +++ b/crates/runtime-wasmtime/wit/deps/dynamic/dynamic.wit @@ -0,0 +1,24 @@ +package wasiext:dynamic@0.1.0; + +interface types { + use wasi:io/poll@0.2.0.{pollable}; + + resource async-return {} + resource dynamic-future {} + resource dynamic-stream {} + + resource future-send { + subscribe: func() -> pollable; + + // returns false if the receiver was closed before the value was received + await: func() -> bool; + } + + resource stream-send { + subscribe: func() -> pollable; + + // returns 0 if the receiver was closed before any values were received, otherwise + // returns the count of values received + await: func() -> u32; + } +} diff --git a/crates/wasmtime-cli/src/lib.rs b/crates/wasmtime-cli/src/lib.rs index 039c3a6e4..7c569d56c 100644 --- a/crates/wasmtime-cli/src/lib.rs +++ b/crates/wasmtime-cli/src/lib.rs @@ -1,11 +1,10 @@ #![allow(clippy::type_complexity)] use core::iter; -use core::ops::Bound; use core::pin::pin; use core::time::Duration; -use std::collections::{BTreeMap, HashMap}; +use std::collections::HashMap; use std::sync::Arc; use anyhow::{anyhow, bail, Context as _}; @@ -25,8 +24,7 @@ use wasmtime::{Engine, Store}; use wasmtime_wasi::{IoView, ResourceTable, WasiCtx, WasiCtxBuilder, WasiView}; use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpView}; use wrpc_runtime_wasmtime::{ - collect_component_resource_exports, collect_component_resource_imports, link_item, rpc, - RemoteResource, ServeExt as _, SharedResourceTable, WrpcView, + link_item, ComponentTypeInfo, RemoteResource, ServeExt as _, SharedResourceTable, WrpcView, }; use wrpc_transport::{Invoke, Serve}; @@ -208,98 +206,36 @@ where wrpc_runtime_wasmtime::rpc::add_to_linker(&mut linker).context("failed to link `wrpc:rpc`")?; let ty = component.component_type(); - let mut host_resources = BTreeMap::default(); - let mut guest_resources = Vec::new(); - collect_component_resource_imports(&engine, &ty, &mut host_resources); - collect_component_resource_exports(&engine, &ty, &mut guest_resources); - let io_err_tys = host_resources - .range::(( - Bound::Included("wasi:io/error@0.2"), - Bound::Excluded("wasi:io/error@0.3"), - )) - .map(|(name, instance)| { - instance - .get("error") - .copied() - .with_context(|| format!("{name} instance import missing `error` resource")) - }) - .collect::>>()?; - let io_pollable_tys = host_resources - .range::(( - Bound::Included("wasi:io/poll@0.2"), - Bound::Excluded("wasi:io/poll@0.3"), - )) - .map(|(name, instance)| { - instance - .get("pollable") - .copied() - .with_context(|| format!("{name} instance import missing `pollable` resource")) - }) - .collect::>>()?; - let io_input_stream_tys = host_resources - .range::(( - Bound::Included("wasi:io/streams@0.2"), - Bound::Excluded("wasi:io/streams@0.3"), - )) - .map(|(name, instance)| { - instance - .get("input-stream") - .copied() - .with_context(|| format!("{name} instance import missing `input-stream` resource")) - }) - .collect::>>()?; - let io_output_stream_tys = host_resources - .range::(( - Bound::Included("wasi:io/streams@0.2"), - Bound::Excluded("wasi:io/streams@0.3"), - )) - .map(|(name, instance)| { - instance - .get("output-stream") - .copied() - .with_context(|| format!("{name} instance import missing `output-stream` resource")) - }) - .collect::>>()?; - let rpc_err_ty = host_resources - .get("wrpc:rpc/error@0.1.0") - .map(|instance| { - instance - .get("error") - .copied() - .context("`wrpc:rpc/error@0.1.0` instance import missing `error` resource") - }) - .transpose()?; + let info = ComponentTypeInfo::new(&engine, &ty); + let io_tys = info.imported_wasi_io_resources(); + let rpc_tys = info.imported_wrpc_rpc_resources(); + let ext_dyn_tys = info.imported_wasiext_dynamic_type_resources(); + let dyn_resources = info.imported_dynamic_resources(&ext_dyn_tys, &io_tys.pollable)?; // TODO: This should include `wasi:http` resources - let host_resources = host_resources + let imported_resources = info + .imported_resources .into_iter() .map(|(name, instance)| { let instance = instance .into_iter() .map(|(name, ty)| { - let host_ty = match ty { - ty if Some(ty) == rpc_err_ty => ResourceType::host::(), - ty if io_err_tys.contains(&ty) => { - ResourceType::host::() - } - ty if io_input_stream_tys.contains(&ty) => ResourceType::host::< - wasmtime_wasi::bindings::io::streams::InputStream, - >(), - ty if io_output_stream_tys.contains(&ty) => ResourceType::host::< - wasmtime_wasi::bindings::io::streams::OutputStream, - >(), - ty if io_pollable_tys.contains(&ty) => { - ResourceType::host::() - } - _ => ResourceType::host::(), + let host_ty = if let Some(ty) = rpc_tys.host_type(&ty) { + ty + } else if let Some(ty) = io_tys.host_type(&ty) { + ty + } else if let Some(ty) = dyn_resources.host_type(&ty) { + ty + } else { + ResourceType::host::() }; - (name, (ty, host_ty)) + Ok((name, (ty, host_ty))) }) - .collect::>(); - (name, instance) + .collect::>>()?; + Ok((name, instance)) }) - .collect::>(); - let host_resources = Arc::from(host_resources); - let guest_resources = Arc::from(guest_resources); + .collect::>>()?; + let imported_resources = Arc::from(imported_resources); + let exported_resources = Arc::from(info.exported_resources); for (name, item) in ty.imports(&engine) { // Avoid polyfilling instances, for which static bindings are linked match name.split_once('/').map(|(pkg, suffix)| { @@ -340,8 +276,9 @@ where if let Err(err) = link_item( &engine, &mut linker.root(), - Arc::clone(&guest_resources), - Arc::clone(&host_resources), + Arc::clone(&exported_resources), + Arc::clone(&imported_resources), + &dyn_resources, item, "", name, @@ -355,7 +292,7 @@ where let pre = linker .instantiate_pre(&component) .context("failed to pre-instantiate component")?; - Ok((pre, engine, guest_resources, host_resources)) + Ok((pre, engine, exported_resources, imported_resources)) } fn new_store(