referrerpolicy=no-referrer-when-downgrade

sp_runtime_interface_proc_macro/runtime_interface/
host_function_interface.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Generates the extern host functions and the implementation for these host functions.
19//!
20//! The extern host functions will be called by the bare function interface from the Wasm side.
21//! The implementation of these host functions will be called on the host side from the Wasm
22//! executor. These implementations call the bare function interface.
23
24use 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
43/// Generate the extern host functions for wasm and the `HostFunctions` struct that provides the
44/// implementations for the host functions on the host.
45pub 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		/// The implementations of the extern host functions. This special implementation module
63		/// is required to change the extern host functions signature to
64		/// `unsafe fn name(args) -> ret` to make the function implementations exchangeable.
65		#[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
78/// Generate the extern host function for the given method.
79fn 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 in the reverse order to construction.
141	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
162/// Generate the host exchangeable function for the given method.
163fn 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
186/// Generate the `HostFunctions` struct that implements `wasm-interface::HostFunctions` to provide
187/// implementations for the extern host functions.
188fn 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		/// Provides implementations for the extern host functions.
210		#[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
233/// Generates the host function struct that implements `wasm_interface::Function` and returns a
234/// static reference to this struct.
235///
236/// When calling from wasm into the host, we will call the `execute` function that calls the native
237/// implementation of the function.
238fn 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	// List of variable names containing WASM FFI-compatible arguments.
252	let mut ffi_names = Vec::new();
253
254	// List of `$name: $ty` tokens containing WASM FFI-compatible arguments.
255	let mut ffi_args_prototype = Vec::new();
256
257	// List of variable names containing arguments already converted into native Rust types.
258	// Also includes the preceding `&` or `&mut`. To be used to call the actual implementation of
259	// the host function.
260	let mut host_names_with_ref = Vec::new();
261
262	// List of code snippets to copy over the results returned from a host function through
263	// any `&mut` arguments back into WASM's linear memory.
264	let mut copy_data_into_ref_mut_args = Vec::new();
265
266	// List of code snippets to convert dynamic FFI args (`Value` enum) into concrete static FFI
267	// types (`u32`, etc.).
268	let mut convert_args_dynamic_ffi_to_static_ffi = Vec::new();
269
270	// List of code snippets to convert static FFI args (`u32`, etc.) into native Rust types.
271	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
444/// Generate the `wasm_interface::Signature` for the given host function `sig`.
445fn 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
467/// Generate the variable name that stores the FFI value.
468fn 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}