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