referrerpolicy=no-referrer-when-downgrade

sp_runtime_interface_proc_macro/runtime_interface/
bare_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 bare function interface for a given trait definition.
19//!
20//! The bare functions are the ones that will be called by the user. On the native/host side, these
21//! functions directly execute the provided implementation. On the wasm side, these
22//! functions will prepare the parameters for the FFI boundary, call the external host function
23//! exported into wasm and convert back the result.
24//!
25//! [`generate`] is the entry point for generating for each
26//! trait method one bare function.
27//!
28//! [`function_for_method`] generates the bare
29//! function per trait method. Each bare function contains both implementations. The implementations
30//! are feature-gated, so that one is compiled for the native and the other for the wasm side.
31
32use crate::utils::{
33	create_exchangeable_host_function_ident, create_function_ident_with_version,
34	generate_crate_access, get_function_argument_names, get_function_arguments,
35	get_runtime_interface, host_inner_return_ty, pat_ty_to_host_inner, RuntimeInterfaceFunction,
36};
37
38use syn::{
39	parse_quote, spanned::Spanned, FnArg, Ident, ItemTrait, Result, Signature, Token, TraitItemFn,
40};
41
42use proc_macro2::{Span, TokenStream};
43
44use quote::{quote, quote_spanned};
45
46use std::iter;
47
48/// Generate one bare function per trait method. The name of the bare function is equal to the name
49/// of the trait method.
50pub fn generate(trait_def: &ItemTrait, is_wasm_only: bool, tracing: bool) -> Result<TokenStream> {
51	let trait_name = &trait_def.ident;
52	let runtime_interface = get_runtime_interface(trait_def)?;
53
54	// latest version dispatch
55	let token_stream: Result<TokenStream> = runtime_interface.latest_versions_to_call().try_fold(
56		TokenStream::new(),
57		|mut t, (latest_version, method)| {
58			t.extend(function_for_method(method, latest_version, is_wasm_only)?);
59			Ok(t)
60		},
61	);
62
63	// earlier versions compatibility dispatch (only std variant)
64	let result: Result<TokenStream> =
65		runtime_interface
66			.all_versions()
67			.try_fold(token_stream?, |mut t, (version, method)| {
68				t.extend(function_std_impl(trait_name, method, version, is_wasm_only, tracing)?);
69				Ok(t)
70			});
71
72	result
73}
74
75/// Generates the bare function implementation for the given method for the host and wasm side.
76fn function_for_method(
77	method: &RuntimeInterfaceFunction,
78	latest_version: u32,
79	is_wasm_only: bool,
80) -> Result<TokenStream> {
81	let std_impl =
82		if !is_wasm_only { function_std_latest_impl(method, latest_version)? } else { quote!() };
83
84	let no_std_impl = function_no_std_impl(method, is_wasm_only)?;
85
86	Ok(quote! {
87		#std_impl
88
89		#no_std_impl
90	})
91}
92
93/// Generates the bare function implementation for `cfg(substrate_runtime)`.
94fn function_no_std_impl(
95	method: &RuntimeInterfaceFunction,
96	is_wasm_only: bool,
97) -> Result<TokenStream> {
98	let should_trap_on_return = method.should_trap_on_return();
99	let mut method = (*method).clone();
100	crate::utils::unpack_inner_types_in_signature(&mut method.sig);
101
102	let function_name = &method.sig.ident;
103	let host_function_name = create_exchangeable_host_function_ident(&method.sig.ident);
104	let args = get_function_arguments(&method.sig);
105	let arg_names = get_function_argument_names(&method.sig);
106	let return_value = if should_trap_on_return {
107		syn::ReturnType::Type(
108			<Token![->]>::default(),
109			Box::new(syn::TypeNever { bang_token: <Token![!]>::default() }.into()),
110		)
111	} else {
112		method.sig.output.clone()
113	};
114	let maybe_unreachable = if should_trap_on_return {
115		quote! {
116			;
117			#[cfg(target_family = "wasm")]
118			{ core::arch::wasm32::unreachable(); }
119
120			#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))]
121			unsafe { core::arch::asm!("unimp", options(noreturn)); }
122		}
123	} else {
124		quote! {}
125	};
126
127	let attrs = method.attrs.iter().filter(|a| !a.path().is_ident("version"));
128
129	let cfg_wasm_only = if is_wasm_only {
130		quote! { #[cfg(substrate_runtime)] }
131	} else {
132		quote! {}
133	};
134
135	Ok(quote! {
136		#cfg_wasm_only
137		#[cfg(substrate_runtime)]
138		#( #attrs )*
139		pub fn #function_name( #( #args, )* ) #return_value {
140			// Call the host function
141			#host_function_name.get()( #( #arg_names, )* )
142			#maybe_unreachable
143		}
144	})
145}
146
147/// Generate call to latest function version for `cfg(not(substrate_runtime))`
148///
149/// This should generate simple `fn func(..) { func_version_<latest_version>(..) }`.
150fn function_std_latest_impl(method: &TraitItemFn, latest_version: u32) -> Result<TokenStream> {
151	let function_name = &method.sig.ident;
152	let args = get_function_arguments(&method.sig).map(pat_ty_to_host_inner).map(FnArg::Typed);
153	let arg_names = get_function_argument_names(&method.sig).collect::<Vec<_>>();
154	let return_value = host_inner_return_ty(&method.sig.output);
155	let attrs = method.attrs.iter().filter(|a| !a.path().is_ident("version"));
156	let latest_function_name =
157		create_function_ident_with_version(&method.sig.ident, latest_version);
158
159	Ok(quote_spanned! { method.span() =>
160		#[cfg(not(substrate_runtime))]
161		#( #attrs )*
162		pub fn #function_name( #( #args, )* ) #return_value {
163			#latest_function_name(
164				#( #arg_names, )*
165			)
166		}
167	})
168}
169
170/// Generates the bare function implementation for `cfg(not(substrate_runtime))`.
171fn function_std_impl(
172	trait_name: &Ident,
173	method: &TraitItemFn,
174	version: u32,
175	is_wasm_only: bool,
176	tracing: bool,
177) -> Result<TokenStream> {
178	let function_name = create_function_ident_with_version(&method.sig.ident, version);
179	let function_name_str = function_name.to_string();
180
181	let crate_ = generate_crate_access();
182	let args = get_function_arguments(&method.sig)
183		.map(pat_ty_to_host_inner)
184		.map(FnArg::Typed)
185		.chain(
186			// Add the function context as last parameter when this is a wasm only interface.
187			iter::from_fn(|| {
188				if is_wasm_only {
189					Some(parse_quote!(
190						mut __function_context__: &mut dyn #crate_::sp_wasm_interface::FunctionContext
191					))
192				} else {
193					None
194				}
195			})
196			.take(1),
197		);
198	let return_value = host_inner_return_ty(&method.sig.output);
199	let attrs = method.attrs.iter().filter(|a| !a.path().is_ident("version"));
200	// Don't make the function public accessible when this is a wasm only interface.
201	let call_to_trait = generate_call_to_trait(trait_name, method, version, is_wasm_only);
202	let call_to_trait = if !tracing {
203		call_to_trait
204	} else {
205		parse_quote!(
206			#crate_::sp_tracing::within_span! { #crate_::sp_tracing::trace_span!(#function_name_str);
207				#call_to_trait
208			}
209		)
210	};
211
212	Ok(quote_spanned! { method.span() =>
213		#[cfg(not(substrate_runtime))]
214		#( #attrs )*
215		fn #function_name( #( #args, )* ) #return_value {
216			#call_to_trait
217		}
218	})
219}
220
221/// Generate the call to the interface trait.
222fn generate_call_to_trait(
223	trait_name: &Ident,
224	method: &TraitItemFn,
225	version: u32,
226	is_wasm_only: bool,
227) -> TokenStream {
228	let crate_ = generate_crate_access();
229	let method_name = create_function_ident_with_version(&method.sig.ident, version);
230	let expect_msg =
231		format!("`{}` called outside of an Externalities-provided environment.", method_name);
232	let arg_names = get_function_argument_names(&method.sig);
233
234	if takes_self_argument(&method.sig) {
235		let instance = if is_wasm_only {
236			Ident::new("__function_context__", Span::call_site())
237		} else {
238			Ident::new("__externalities__", Span::call_site())
239		};
240
241		let impl_ = quote!( #trait_name::#method_name(&mut #instance, #( #arg_names, )*) );
242
243		if is_wasm_only {
244			quote_spanned! { method.span() => #impl_ }
245		} else {
246			quote_spanned! { method.span() =>
247				#crate_::with_externalities(|mut #instance| #impl_).expect(#expect_msg)
248			}
249		}
250	} else {
251		// The name of the trait the interface trait is implemented for
252		let impl_trait_name = if is_wasm_only {
253			quote!( #crate_::sp_wasm_interface::FunctionContext )
254		} else {
255			quote!( #crate_::Externalities )
256		};
257
258		quote_spanned! { method.span() =>
259			<&mut dyn #impl_trait_name as #trait_name>::#method_name(
260				#( #arg_names, )*
261			)
262		}
263	}
264}
265
266/// Returns if the given `Signature` takes a `self` argument.
267fn takes_self_argument(sig: &Signature) -> bool {
268	matches!(sig.inputs.first(), Some(FnArg::Receiver(_)))
269}