Skip to main content

wrpc_runtime_wasmtime/
paths.rs

1//! Computation of wRPC asynchronous subscription paths from Wasmtime component types.
2//!
3//! When serving a component function over wRPC, the transport must be told which
4//! nested positions of the parameters carry asynchronous values (streams and
5//! futures) so it can subscribe to their data channels. `wasi:io`
6//! `input-stream`/`output-stream` resources are bridged to wRPC `stream<u8>` by
7//! this runtime, so they are treated as asynchronous here as well.
8//!
9//! Note that at serve time the component has not been instantiated, so a
10//! `wasi:io` stream parameter appears as the component's own *uninstantiated*
11//! resource type rather than the host [`DynInputStream`]/[`DynOutputStream`]
12//! type. We therefore identify those resources by collecting the component's
13//! `wasi:io/streams` imports up front and comparing against them.
14
15use std::collections::{BTreeMap, BTreeSet, VecDeque};
16
17use wasmtime::component::types::{self, Type};
18use wasmtime::component::ResourceType;
19use wasmtime::Engine;
20
21/// Collect the (uninstantiated) resource types of the component's imported
22/// `wasi:io/streams` `input-stream` and `output-stream`, against which
23/// parameter resource types can be compared at serve time.
24///
25/// A `Vec` is used rather than a set because [`ResourceType`] is neither `Ord`
26/// nor `Hash`; the collection holds at most two entries.
27pub fn wasi_io_stream_resources(
28    engine: &Engine,
29    component: &types::Component,
30) -> Vec<ResourceType> {
31    let mut imports = BTreeMap::new();
32    crate::collect_component_resource_imports(engine, component, &mut imports);
33    let mut out = Vec::new();
34    for (instance, resources) in imports {
35        // Instance names are versioned, e.g. `wasi:io/streams@0.2.6`.
36        let base = instance.split('@').next().unwrap_or(&instance);
37        if base != "wasi:io/streams" {
38            continue;
39        }
40        for (name, ty) in resources {
41            if (&*name == "input-stream" || &*name == "output-stream") && !out.contains(&ty) {
42                out.push(ty);
43            }
44        }
45    }
46    out
47}
48
49/// Compute the set of nested asynchronous paths within a single value type, and
50/// whether the type *itself* is asynchronous (a stream or future).
51///
52/// Mirrors `wrpc_introspect::async_paths_ty`, but operates over Wasmtime's
53/// component [`Type`] rather than WIT types. `streams` is the set of resource
54/// types that are bridged to wRPC `stream<u8>` (see [`wasi_io_stream_resources`]).
55fn async_paths(ty: &Type, streams: &[ResourceType]) -> (BTreeSet<VecDeque<Option<usize>>>, bool) {
56    let mut paths = BTreeSet::new();
57    match ty {
58        Type::List(ty) => {
59            let (nested, fut) = async_paths(&ty.ty(), streams);
60            for mut path in nested {
61                path.push_front(None);
62                paths.insert(path);
63            }
64            if fut {
65                paths.insert(VecDeque::from([None]));
66            }
67            (paths, false)
68        }
69        Type::Option(ty) => async_paths(&ty.ty(), streams),
70        Type::Result(ty) => {
71            let mut is_fut = false;
72            if let Some(ty) = ty.ok() {
73                let (nested, fut) = async_paths(&ty, streams);
74                paths.extend(nested);
75                is_fut |= fut;
76            }
77            if let Some(ty) = ty.err() {
78                let (nested, fut) = async_paths(&ty, streams);
79                paths.extend(nested);
80                is_fut |= fut;
81            }
82            (paths, is_fut)
83        }
84        Type::Variant(ty) => {
85            let mut is_fut = false;
86            for case in ty.cases() {
87                if let Some(ty) = case.ty {
88                    let (nested, fut) = async_paths(&ty, streams);
89                    paths.extend(nested);
90                    is_fut |= fut;
91                }
92            }
93            (paths, is_fut)
94        }
95        Type::Tuple(ty) => {
96            for (i, ty) in ty.types().enumerate() {
97                let (nested, fut) = async_paths(&ty, streams);
98                for mut path in nested {
99                    path.push_front(Some(i));
100                    paths.insert(path);
101                }
102                if fut {
103                    paths.insert(VecDeque::from([Some(i)]));
104                }
105            }
106            (paths, false)
107        }
108        Type::Record(ty) => {
109            for (i, field) in ty.fields().enumerate() {
110                let (nested, fut) = async_paths(&field.ty, streams);
111                for mut path in nested {
112                    path.push_front(Some(i));
113                    paths.insert(path);
114                }
115                if fut {
116                    paths.insert(VecDeque::from([Some(i)]));
117                }
118            }
119            (paths, false)
120        }
121        Type::Future(ty) => {
122            if let Some(ty) = ty.ty() {
123                (paths, _) = async_paths(&ty, streams);
124            }
125            (paths, true)
126        }
127        Type::Stream(ty) => {
128            if let Some(ty) = ty.ty() {
129                let (nested, fut) = async_paths(&ty, streams);
130                for mut path in nested {
131                    path.push_front(None);
132                    paths.insert(path);
133                }
134                if fut {
135                    paths.insert(VecDeque::from([None]));
136                }
137            }
138            (paths, true)
139        }
140        Type::Own(ty) | Type::Borrow(ty) if streams.contains(ty) => {
141            // `wasi:io` streams are sent/received as wRPC `stream<u8>`.
142            (paths, true)
143        }
144        _ => (paths, false),
145    }
146}
147
148/// Compute the wRPC subscription paths for a function's parameter list.
149///
150/// Each parameter is treated as an element of a top-level tuple: a parameter at
151/// index `i` whose type carries asynchronous data contributes paths prefixed
152/// with `Some(i)`.
153pub(crate) fn params_async_paths<'a>(
154    params: impl IntoIterator<Item = &'a Type>,
155    streams: &[ResourceType],
156) -> Vec<Box<[Option<usize>]>> {
157    let mut out = BTreeSet::new();
158    for (i, ty) in params.into_iter().enumerate() {
159        let (nested, fut) = async_paths(ty, streams);
160        for mut path in nested {
161            path.push_front(Some(i));
162            out.insert(path);
163        }
164        if fut {
165            out.insert(VecDeque::from([Some(i)]));
166        }
167    }
168    out.into_iter()
169        .map(|path| path.into_iter().collect::<Box<[_]>>())
170        .collect()
171}