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