rpc/natives/
catch_each.rs1use scripting::checked_ref::CheckedAnyRef;
28use scripting::runtime::{
29 EngineError, alloc_pair_chain, alloc_string_ref, classify_runtime_error, err_code_and_message,
30};
31use wasmtime::{AnyRef, AsContextMut, Caller, Func, Linker, Rooted, StructRef, Val};
32
33use crate::session::SessionData;
34
35pub fn register(linker: &mut Linker<SessionData>) -> wasmtime::Result<()> {
36 linker.func_wrap_async(
37 "nomi",
38 "__nomi_catch_each",
39 |mut caller: Caller<'_, SessionData>,
40 (cb, env, items): (
41 Option<Func>,
42 Option<Rooted<AnyRef>>,
43 Option<Rooted<StructRef>>,
44 )|
45 -> Box<
46 dyn std::future::Future<Output = wasmtime::Result<Option<Rooted<StructRef>>>> + Send,
47 > {
48 Box::new(async move {
49 let cb =
50 cb.ok_or_else(|| wasmtime::Error::msg("catch-each: closure funcref is null"))?;
51 let item_anyrefs = collect_items(&mut caller, items)?;
52 let mut result_cells: Vec<Rooted<AnyRef>> = Vec::with_capacity(item_anyrefs.len());
53 for item in item_anyrefs {
54 let cell = invoke_one(&mut caller, &cb, env, item).await?;
55 result_cells.push(cell);
56 }
57 alloc_pair_chain(&mut caller, result_cells).await
58 })
59 },
60 )?;
61 Ok(())
62}
63
64fn collect_items(
68 caller: &mut Caller<'_, SessionData>,
69 head: Option<Rooted<StructRef>>,
70) -> wasmtime::Result<Vec<Rooted<AnyRef>>> {
71 let mut out: Vec<Rooted<AnyRef>> = Vec::new();
72 let mut cursor = head;
73 while let Some(node) = cursor {
74 let car_val = node.field(caller.as_context_mut(), 0)?;
75 let car = match car_val {
76 Val::AnyRef(Some(any)) => any,
77 Val::AnyRef(None) => {
78 return Err(wasmtime::Error::msg(
79 "catch-each: items list contains a null car",
80 ));
81 }
82 _ => {
83 return Err(wasmtime::Error::msg(
84 "catch-each: items list car field is not anyref",
85 ));
86 }
87 };
88 out.push(car);
89 let cdr_val = node.field(caller.as_context_mut(), 1)?;
90 cursor = match cdr_val {
91 Val::AnyRef(Some(any)) => Some(any.checked_struct(caller.as_context_mut())?),
92 _ => None,
93 };
94 }
95 Ok(out)
96}
97
98async fn invoke_one(
103 caller: &mut Caller<'_, SessionData>,
104 cb: &Func,
105 env: Option<Rooted<AnyRef>>,
106 item: Rooted<AnyRef>,
107) -> wasmtime::Result<Rooted<AnyRef>> {
108 let mut results = [Val::AnyRef(None)];
109 let outcome = cb
110 .call_async(
111 caller.as_context_mut(),
112 &[Val::AnyRef(env), Val::AnyRef(Some(item))],
113 &mut results,
114 )
115 .await;
116 match outcome {
117 Ok(()) => {
118 let value_any = match results[0] {
119 Val::AnyRef(any) => any,
120 _ => {
121 return Err(wasmtime::Error::msg(
122 "catch-each: closure returned non-anyref Val variant",
123 ));
124 }
125 };
126 build_ok_cell(caller, value_any).await
127 }
128 Err(err) => match classify_runtime_error(&err) {
129 EngineError::OutOfFuel | EngineError::EpochInterrupt => Err(err),
130 classified => build_err_cell(caller, &classified).await,
131 },
132 }
133}
134
135async fn build_ok_cell(
140 caller: &mut Caller<'_, SessionData>,
141 value: Option<Rooted<AnyRef>>,
142) -> wasmtime::Result<Rooted<AnyRef>> {
143 let tag = string_anyref(caller, b"ok")?;
144 let mut elems = vec![tag];
145 if let Some(v) = value {
146 elems.push(v);
147 }
148 pair_chain_to_anyref(caller, elems).await
149}
150
151async fn build_err_cell(
153 caller: &mut Caller<'_, SessionData>,
154 classified: &EngineError,
155) -> wasmtime::Result<Rooted<AnyRef>> {
156 let tag = string_anyref(caller, b"err")?;
157 let (code, message) = err_code_and_message(classified);
158 let code_any = string_anyref(caller, code.as_bytes())?;
159 let message_any = string_anyref(caller, message.as_bytes())?;
160 pair_chain_to_anyref(caller, vec![tag, code_any, message_any]).await
161}
162
163async fn pair_chain_to_anyref(
164 caller: &mut Caller<'_, SessionData>,
165 elems: Vec<Rooted<AnyRef>>,
166) -> wasmtime::Result<Rooted<AnyRef>> {
167 let head = alloc_pair_chain(caller, elems)
168 .await?
169 .ok_or_else(|| wasmtime::Error::msg("catch-each: built an empty result cell"))?;
170 Ok(head.to_anyref())
171}
172
173fn string_anyref(
174 caller: &mut Caller<'_, SessionData>,
175 bytes: &[u8],
176) -> wasmtime::Result<Rooted<AnyRef>> {
177 Ok(alloc_string_ref(caller, bytes)?.to_anyref())
178}