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_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
44/// Generate the extern host functions for wasm and the `HostFunctions` struct that provides the
45/// implementations for the host functions on the host.
46pub 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		/// The implementations of the extern host functions. This special implementation module
64		/// is required to change the extern host functions signature to
65		/// `unsafe fn name(args) -> ret` to make the function implementations exchangeable.
66		#[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
79/// Generate the extern host function for the given method.
80fn 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			// Generate all wrapped ffi values.
127			#(
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
140/// Generate the host exchangeable function for the given method.
141fn 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
161/// Generate the `HostFunctions` struct that implements `wasm-interface::HostFunctions` to provide
162/// implementations for the extern host functions.
163fn 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		/// Provides implementations for the extern host functions.
185		#[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
208/// Generates the host function struct that implements `wasm_interface::Function` and returns a
209/// static reference to this struct.
210///
211/// When calling from wasm into the host, we will call the `execute` function that calls the native
212/// implementation of the function.
213fn 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	// List of variable names containing WASM FFI-compatible arguments.
228	let mut ffi_names = Vec::new();
229
230	// List of `$name: $ty` tokens containing WASM FFI-compatible arguments.
231	let mut ffi_args_prototype = Vec::new();
232
233	// List of variable names containing arguments already converted into native Rust types.
234	// Also includes the preceding `&` or `&mut`. To be used to call the actual implementation of
235	// the host function.
236	let mut host_names_with_ref = Vec::new();
237
238	// List of code snippets to copy over the results returned from a host function through
239	// any `&mut` arguments back into WASM's linear memory.
240	let mut copy_data_into_ref_mut_args = Vec::new();
241
242	// List of code snippets to convert dynamic FFI args (`Value` enum) into concrete static FFI
243	// types (`u32`, etc.).
244	let mut convert_args_dynamic_ffi_to_static_ffi = Vec::new();
245
246	// List of code snippets to convert static FFI args (`u32`, etc.) into native Rust types.
247	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
430/// Generate the `wasm_interface::Signature` for the given host function `sig`.
431fn 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
453/// Generate the variable name that stores the FFI value.
454fn 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}