sp_runtime_interface_proc_macro/runtime_interface/
host_function_interface.rs1use crate::utils::{
25 create_exchangeable_host_function_ident, create_function_ident_with_version,
26 create_host_function_ident, generate_crate_access, get_function_argument_names,
27 get_function_argument_names_and_types_without_ref, get_function_argument_types,
28 get_function_argument_types_ref_and_mut, get_function_argument_types_without_ref,
29 get_function_arguments, get_runtime_interface, RuntimeInterfaceFunction,
30};
31
32use syn::{
33 spanned::Spanned, Error, Ident, ItemTrait, Pat, Result, ReturnType, Signature, TraitItemFn,
34};
35
36use proc_macro2::{Span, TokenStream};
37
38use quote::quote;
39
40use inflector::Inflector;
41
42use std::iter::Iterator;
43
44pub fn generate(trait_def: &ItemTrait, is_wasm_only: bool) -> Result<TokenStream> {
47 let trait_name = &trait_def.ident;
48 let extern_host_function_impls = get_runtime_interface(trait_def)?
49 .latest_versions_to_call()
50 .try_fold(TokenStream::new(), |mut t, (version, method)| {
51 t.extend(generate_extern_host_function(method, version, trait_name)?);
52 Ok::<_, Error>(t)
53 })?;
54 let exchangeable_host_functions = get_runtime_interface(trait_def)?
55 .latest_versions_to_call()
56 .try_fold(TokenStream::new(), |mut t, (_, m)| {
57 t.extend(generate_exchangeable_host_function(m)?);
58 Ok::<_, Error>(t)
59 })?;
60 let host_functions_struct = generate_host_functions_struct(trait_def, is_wasm_only)?;
61
62 Ok(quote! {
63 #[cfg(not(feature = "std"))]
67 mod extern_host_function_impls {
68 use super::*;
69
70 #extern_host_function_impls
71 }
72
73 #exchangeable_host_functions
74
75 #host_functions_struct
76 })
77}
78
79fn generate_extern_host_function(
81 method: &TraitItemFn,
82 version: u32,
83 trait_name: &Ident,
84) -> Result<TokenStream> {
85 let crate_ = generate_crate_access();
86 let args = get_function_arguments(&method.sig);
87 let arg_types = get_function_argument_types_without_ref(&method.sig);
88 let arg_types2 = get_function_argument_types_without_ref(&method.sig);
89 let arg_names = get_function_argument_names(&method.sig);
90 let arg_names2 = get_function_argument_names(&method.sig);
91 let arg_names3 = get_function_argument_names(&method.sig);
92 let function = &method.sig.ident;
93 let ext_function = create_host_function_ident(&method.sig.ident, version, trait_name);
94 let doc_string = format!(
95 " Default extern host function implementation for [`super::{}`].",
96 method.sig.ident,
97 );
98 let return_value = &method.sig.output;
99 let cfg_attrs = method.attrs.iter().filter(|a| a.path().is_ident("cfg"));
100
101 let ffi_return_value = match method.sig.output {
102 ReturnType::Default => quote!(),
103 ReturnType::Type(_, ref ty) => quote! {
104 -> <#ty as #crate_::RIType>::FFIType
105 },
106 };
107
108 let convert_return_value = match return_value {
109 ReturnType::Default => quote!(),
110 ReturnType::Type(_, ref ty) => quote! {
111 <#ty as #crate_::wasm::FromFFIValue>::from_ffi_value(result)
112 },
113 };
114
115 Ok(quote! {
116 #(#cfg_attrs)*
117 #[doc = #doc_string]
118 pub fn #function ( #( #args ),* ) #return_value {
119 #[cfg_attr(any(target_arch = "riscv32", target_arch = "riscv64"), #crate_::polkavm::polkavm_import(abi = #crate_::polkavm::polkavm_abi))]
120 extern "C" {
121 pub fn #ext_function (
122 #( #arg_names: <#arg_types as #crate_::RIType>::FFIType ),*
123 ) #ffi_return_value;
124 }
125
126 #(
128 let #arg_names2 = <#arg_types2 as #crate_::wasm::IntoFFIValue>::into_ffi_value(
129 &#arg_names2,
130 );
131 )*
132
133 let result = unsafe { #ext_function( #( #arg_names3.get() ),* ) };
134
135 #convert_return_value
136 }
137 })
138}
139
140fn generate_exchangeable_host_function(method: &TraitItemFn) -> Result<TokenStream> {
142 let crate_ = generate_crate_access();
143 let arg_types = get_function_argument_types(&method.sig);
144 let function = &method.sig.ident;
145 let exchangeable_function = create_exchangeable_host_function_ident(&method.sig.ident);
146 let doc_string = format!(" Exchangeable host function used by [`{}`].", method.sig.ident);
147 let output = &method.sig.output;
148 let cfg_attrs = method.attrs.iter().filter(|a| a.path().is_ident("cfg"));
149
150 Ok(quote! {
151 #(#cfg_attrs)*
152 #[cfg(not(feature = "std"))]
153 #[allow(non_upper_case_globals)]
154 #[doc = #doc_string]
155 pub static #exchangeable_function : #crate_::wasm::ExchangeableFunction<
156 fn ( #( #arg_types ),* ) #output
157 > = #crate_::wasm::ExchangeableFunction::new(extern_host_function_impls::#function);
158 })
159}
160
161fn generate_host_functions_struct(
164 trait_def: &ItemTrait,
165 is_wasm_only: bool,
166) -> Result<TokenStream> {
167 let crate_ = generate_crate_access();
168
169 let mut host_function_impls = Vec::new();
170 let mut register_bodies = Vec::new();
171 let mut append_hf_bodies = Vec::new();
172
173 for (version, method) in get_runtime_interface(trait_def)?.all_versions() {
174 let (implementation, register_body, append_hf_body) =
175 generate_host_function_implementation(&trait_def.ident, method, version, is_wasm_only)?;
176 host_function_impls.push(implementation);
177 register_bodies.push(register_body);
178 append_hf_bodies.push(append_hf_body);
179 }
180
181 Ok(quote! {
182 #(#host_function_impls)*
183
184 #[cfg(feature = "std")]
186 pub struct HostFunctions;
187
188 #[cfg(feature = "std")]
189 impl #crate_::sp_wasm_interface::HostFunctions for HostFunctions {
190 fn host_functions() -> Vec<&'static dyn #crate_::sp_wasm_interface::Function> {
191 let mut host_functions_list = Vec::new();
192 #(#append_hf_bodies)*
193 host_functions_list
194 }
195
196 #crate_::sp_wasm_interface::if_wasmtime_is_enabled! {
197 fn register_static<T>(registry: &mut T) -> core::result::Result<(), T::Error>
198 where T: #crate_::sp_wasm_interface::HostFunctionRegistry
199 {
200 #(#register_bodies)*
201 Ok(())
202 }
203 }
204 }
205 })
206}
207
208fn generate_host_function_implementation(
214 trait_name: &Ident,
215 method: &RuntimeInterfaceFunction,
216 version: u32,
217 is_wasm_only: bool,
218) -> Result<(TokenStream, TokenStream, TokenStream)> {
219 let name = create_host_function_ident(&method.sig.ident, version, trait_name).to_string();
220 let struct_name = Ident::new(&name.to_pascal_case(), Span::call_site());
221 let crate_ = generate_crate_access();
222 let signature = generate_wasm_interface_signature_for_host_function(&method.sig)?;
223
224 let fn_name = create_function_ident_with_version(&method.sig.ident, version);
225 let ref_and_mut = get_function_argument_types_ref_and_mut(&method.sig);
226
227 let mut ffi_names = Vec::new();
229
230 let mut ffi_args_prototype = Vec::new();
232
233 let mut host_names_with_ref = Vec::new();
237
238 let mut copy_data_into_ref_mut_args = Vec::new();
241
242 let mut convert_args_dynamic_ffi_to_static_ffi = Vec::new();
245
246 let mut convert_args_static_ffi_to_host = Vec::new();
248
249 for ((host_name, host_ty), ref_and_mut) in
250 get_function_argument_names_and_types_without_ref(&method.sig).zip(ref_and_mut)
251 {
252 let ffi_name = generate_ffi_value_var_name(&host_name)?;
253 let host_name_ident = match *host_name {
254 Pat::Ident(ref pat_ident) => pat_ident.ident.clone(),
255 _ => unreachable!("`generate_ffi_value_var_name` above would return an error on `Pat` != `Ident`; qed"),
256 };
257
258 let ffi_ty = quote! { <#host_ty as #crate_::RIType>::FFIType };
259 ffi_args_prototype.push(quote! { #ffi_name: #ffi_ty });
260 ffi_names.push(quote! { #ffi_name });
261
262 let convert_arg_error = format!(
263 "could not marshal the '{}' argument through the WASM FFI boundary while executing '{}' from interface '{}'",
264 host_name_ident,
265 method.sig.ident,
266 trait_name
267 );
268 convert_args_static_ffi_to_host.push(quote! {
269 let mut #host_name = <#host_ty as #crate_::host::FromFFIValue>::from_ffi_value(__function_context__, #ffi_name)
270 .map_err(|err| format!("{}: {}", err, #convert_arg_error))?;
271 });
272
273 let ref_and_mut_tokens =
274 ref_and_mut.map(|(token_ref, token_mut)| quote!(#token_ref #token_mut));
275
276 host_names_with_ref.push(quote! { #ref_and_mut_tokens #host_name });
277
278 if ref_and_mut.map(|(_, token_mut)| token_mut.is_some()).unwrap_or(false) {
279 copy_data_into_ref_mut_args.push(quote! {
280 <#host_ty as #crate_::host::IntoPreallocatedFFIValue>::into_preallocated_ffi_value(
281 #host_name,
282 __function_context__,
283 #ffi_name,
284 )?;
285 });
286 }
287
288 let arg_count_mismatch_error = format!(
289 "missing argument '{}': number of arguments given to '{}' from interface '{}' does not match the expected number of arguments",
290 host_name_ident,
291 method.sig.ident,
292 trait_name
293 );
294 convert_args_dynamic_ffi_to_static_ffi.push(quote! {
295 let #ffi_name = args.next().ok_or_else(|| #arg_count_mismatch_error.to_owned())?;
296 let #ffi_name: #ffi_ty = #crate_::sp_wasm_interface::TryFromValue::try_from_value(#ffi_name)
297 .ok_or_else(|| #convert_arg_error.to_owned())?;
298 });
299 }
300
301 let ffi_return_ty = match &method.sig.output {
302 ReturnType::Type(_, ty) => quote! { <#ty as #crate_::RIType>::FFIType },
303 ReturnType::Default => quote! { () },
304 };
305
306 let convert_return_value_host_to_static_ffi = match &method.sig.output {
307 ReturnType::Type(_, ty) => quote! {
308 let __result__ = <#ty as #crate_::host::IntoFFIValue>::into_ffi_value(
309 __result__,
310 __function_context__
311 );
312 },
313 ReturnType::Default => quote! {
314 let __result__ = Ok(__result__);
315 },
316 };
317
318 let convert_return_value_static_ffi_to_dynamic_ffi = match &method.sig.output {
319 ReturnType::Type(_, _) => quote! {
320 let __result__ = Ok(Some(#crate_::sp_wasm_interface::IntoValue::into_value(__result__)));
321 },
322 ReturnType::Default => quote! {
323 let __result__ = Ok(None);
324 },
325 };
326
327 if is_wasm_only {
328 host_names_with_ref.push(quote! {
329 __function_context__
330 });
331 }
332
333 let cfg_attrs: Vec<_> =
334 method.attrs.iter().filter(|a| a.path().is_ident("cfg")).cloned().collect();
335 if version > 1 && !cfg_attrs.is_empty() {
336 return Err(Error::new(
337 method.span(),
338 "Conditional compilation is not supported for versioned functions",
339 ))
340 }
341
342 let implementation = quote! {
343 #(#cfg_attrs)*
344 #[cfg(feature = "std")]
345 struct #struct_name;
346
347 #(#cfg_attrs)*
348 #[cfg(feature = "std")]
349 impl #struct_name {
350 fn call(
351 __function_context__: &mut dyn #crate_::sp_wasm_interface::FunctionContext,
352 #(#ffi_args_prototype),*
353 ) -> std::result::Result<#ffi_return_ty, String> {
354 #(#convert_args_static_ffi_to_host)*
355 let __result__ = #fn_name(#(#host_names_with_ref),*);
356 #(#copy_data_into_ref_mut_args)*
357 #convert_return_value_host_to_static_ffi
358 __result__
359 }
360 }
361
362 #(#cfg_attrs)*
363 #[cfg(feature = "std")]
364 impl #crate_::sp_wasm_interface::Function for #struct_name {
365 fn name(&self) -> &str {
366 #name
367 }
368
369 fn signature(&self) -> #crate_::sp_wasm_interface::Signature {
370 #signature
371 }
372
373 fn execute(
374 &self,
375 __function_context__: &mut dyn #crate_::sp_wasm_interface::FunctionContext,
376 args: &mut dyn Iterator<Item = #crate_::sp_wasm_interface::Value>,
377 ) -> std::result::Result<Option<#crate_::sp_wasm_interface::Value>, String> {
378 #(#convert_args_dynamic_ffi_to_static_ffi)*
379 let __result__ = Self::call(
380 __function_context__,
381 #(#ffi_names),*
382 )?;
383 #convert_return_value_static_ffi_to_dynamic_ffi
384 __result__
385 }
386 }
387 };
388
389 let register_body = quote! {
390 #(#cfg_attrs)*
391 registry.register_static(
392 #crate_::sp_wasm_interface::Function::name(&#struct_name),
393 |mut caller: #crate_::sp_wasm_interface::wasmtime::Caller<T::State>, #(#ffi_args_prototype),*|
394 -> std::result::Result<#ffi_return_ty, #crate_::sp_wasm_interface::anyhow::Error>
395 {
396 T::with_function_context(caller, move |__function_context__| {
397 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
398 #struct_name::call(
399 __function_context__,
400 #(#ffi_names,)*
401 ).map_err(#crate_::sp_wasm_interface::anyhow::Error::msg)
402 }));
403 match result {
404 Ok(result) => result,
405 Err(panic) => {
406 let message =
407 if let Some(message) = panic.downcast_ref::<String>() {
408 format!("host code panicked while being called by the runtime: {}", message)
409 } else if let Some(message) = panic.downcast_ref::<&'static str>() {
410 format!("host code panicked while being called by the runtime: {}", message)
411 } else {
412 "host code panicked while being called by the runtime".to_owned()
413 };
414 return Err(#crate_::sp_wasm_interface::anyhow::Error::msg(message));
415 }
416 }
417 })
418 }
419 )?;
420 };
421
422 let append_hf_body = quote! {
423 #(#cfg_attrs)*
424 host_functions_list.push(&#struct_name as &dyn #crate_::sp_wasm_interface::Function);
425 };
426
427 Ok((implementation, register_body, append_hf_body))
428}
429
430fn generate_wasm_interface_signature_for_host_function(sig: &Signature) -> Result<TokenStream> {
432 let crate_ = generate_crate_access();
433 let return_value = match &sig.output {
434 ReturnType::Type(_, ty) => quote! {
435 Some( <<#ty as #crate_::RIType>::FFIType as #crate_::sp_wasm_interface::IntoValue>::VALUE_TYPE )
436 },
437 ReturnType::Default => quote!(None),
438 };
439 let arg_types = get_function_argument_types_without_ref(sig).map(|ty| {
440 quote! {
441 <<#ty as #crate_::RIType>::FFIType as #crate_::sp_wasm_interface::IntoValue>::VALUE_TYPE
442 }
443 });
444
445 Ok(quote! {
446 #crate_::sp_wasm_interface::Signature {
447 args: std::borrow::Cow::Borrowed(&[ #( #arg_types ),* ][..]),
448 return_value: #return_value,
449 }
450 })
451}
452
453fn generate_ffi_value_var_name(pat: &Pat) -> Result<Ident> {
455 match pat {
456 Pat::Ident(pat_ident) =>
457 if let Some(by_ref) = pat_ident.by_ref {
458 Err(Error::new(by_ref.span(), "`ref` not supported!"))
459 } else if let Some(sub_pattern) = &pat_ident.subpat {
460 Err(Error::new(sub_pattern.0.span(), "Not supported!"))
461 } else {
462 Ok(Ident::new(&format!("{}_ffi_value", pat_ident.ident), Span::call_site()))
463 },
464 _ => Err(Error::new(pat.span(), "Not supported as variable name!")),
465 }
466}