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, 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(not(feature = "std"))`.
94fn function_no_std_impl(
95	method: &RuntimeInterfaceFunction,
96	is_wasm_only: bool,
97) -> Result<TokenStream> {
98	let function_name = &method.sig.ident;
99	let host_function_name = create_exchangeable_host_function_ident(&method.sig.ident);
100	let args = get_function_arguments(&method.sig);
101	let arg_names = get_function_argument_names(&method.sig);
102	let return_value = if method.should_trap_on_return() {
103		syn::ReturnType::Type(
104			<Token![->]>::default(),
105			Box::new(syn::TypeNever { bang_token: <Token![!]>::default() }.into()),
106		)
107	} else {
108		method.sig.output.clone()
109	};
110	let maybe_unreachable = if method.should_trap_on_return() {
111		quote! {
112			;
113			#[cfg(target_family = "wasm")]
114			{ core::arch::wasm32::unreachable(); }
115
116			#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))]
117			unsafe { core::arch::asm!("unimp", options(noreturn)); }
118		}
119	} else {
120		quote! {}
121	};
122
123	let attrs = method.attrs.iter().filter(|a| !a.path().is_ident("version"));
124
125	let cfg_wasm_only = if is_wasm_only {
126		quote! { #[cfg(substrate_runtime)] }
127	} else {
128		quote! {}
129	};
130
131	Ok(quote! {
132		#cfg_wasm_only
133		#[cfg(not(feature = "std"))]
134		#( #attrs )*
135		pub fn #function_name( #( #args, )* ) #return_value {
136			// Call the host function
137			#host_function_name.get()( #( #arg_names, )* )
138			#maybe_unreachable
139		}
140	})
141}
142
143/// Generate call to latest function version for `cfg((feature = "std")`
144///
145/// This should generate simple `fn func(..) { func_version_<latest_version>(..) }`.
146fn function_std_latest_impl(method: &TraitItemFn, latest_version: u32) -> Result<TokenStream> {
147	let function_name = &method.sig.ident;
148	let args = get_function_arguments(&method.sig).map(FnArg::Typed);
149	let arg_names = get_function_argument_names(&method.sig).collect::<Vec<_>>();
150	let return_value = &method.sig.output;
151	let attrs = method.attrs.iter().filter(|a| !a.path().is_ident("version"));
152	let latest_function_name =
153		create_function_ident_with_version(&method.sig.ident, latest_version);
154
155	Ok(quote_spanned! { method.span() =>
156		#[cfg(feature = "std")]
157		#( #attrs )*
158		pub fn #function_name( #( #args, )* ) #return_value {
159			#latest_function_name(
160				#( #arg_names, )*
161			)
162		}
163	})
164}
165
166/// Generates the bare function implementation for `cfg(feature = "std")`.
167fn function_std_impl(
168	trait_name: &Ident,
169	method: &TraitItemFn,
170	version: u32,
171	is_wasm_only: bool,
172	tracing: bool,
173) -> Result<TokenStream> {
174	let function_name = create_function_ident_with_version(&method.sig.ident, version);
175	let function_name_str = function_name.to_string();
176
177	let crate_ = generate_crate_access();
178	let args = get_function_arguments(&method.sig).map(FnArg::Typed).chain(
179		// Add the function context as last parameter when this is a wasm only interface.
180		iter::from_fn(|| {
181			if is_wasm_only {
182				Some(parse_quote!(
183					mut __function_context__: &mut dyn #crate_::sp_wasm_interface::FunctionContext
184				))
185			} else {
186				None
187			}
188		})
189		.take(1),
190	);
191	let return_value = &method.sig.output;
192	let attrs = method.attrs.iter().filter(|a| !a.path().is_ident("version"));
193	// Don't make the function public accessible when this is a wasm only interface.
194	let call_to_trait = generate_call_to_trait(trait_name, method, version, is_wasm_only);
195	let call_to_trait = if !tracing {
196		call_to_trait
197	} else {
198		parse_quote!(
199			#crate_::sp_tracing::within_span! { #crate_::sp_tracing::trace_span!(#function_name_str);
200				#call_to_trait
201			}
202		)
203	};
204
205	Ok(quote_spanned! { method.span() =>
206		#[cfg(feature = "std")]
207		#( #attrs )*
208		fn #function_name( #( #args, )* ) #return_value {
209			#call_to_trait
210		}
211	})
212}
213
214/// Generate the call to the interface trait.
215fn generate_call_to_trait(
216	trait_name: &Ident,
217	method: &TraitItemFn,
218	version: u32,
219	is_wasm_only: bool,
220) -> TokenStream {
221	let crate_ = generate_crate_access();
222	let method_name = create_function_ident_with_version(&method.sig.ident, version);
223	let expect_msg =
224		format!("`{}` called outside of an Externalities-provided environment.", method_name);
225	let arg_names = get_function_argument_names(&method.sig);
226
227	if takes_self_argument(&method.sig) {
228		let instance = if is_wasm_only {
229			Ident::new("__function_context__", Span::call_site())
230		} else {
231			Ident::new("__externalities__", Span::call_site())
232		};
233
234		let impl_ = quote!( #trait_name::#method_name(&mut #instance, #( #arg_names, )*) );
235
236		if is_wasm_only {
237			quote_spanned! { method.span() => #impl_ }
238		} else {
239			quote_spanned! { method.span() =>
240				#crate_::with_externalities(|mut #instance| #impl_).expect(#expect_msg)
241			}
242		}
243	} else {
244		// The name of the trait the interface trait is implemented for
245		let impl_trait_name = if is_wasm_only {
246			quote!( #crate_::sp_wasm_interface::FunctionContext )
247		} else {
248			quote!( #crate_::Externalities )
249		};
250
251		quote_spanned! { method.span() =>
252			<&mut dyn #impl_trait_name as #trait_name>::#method_name(
253				#( #arg_names, )*
254			)
255		}
256	}
257}
258
259/// Returns if the given `Signature` takes a `self` argument.
260fn takes_self_argument(sig: &Signature) -> bool {
261	matches!(sig.inputs.first(), Some(FnArg::Receiver(_)))
262}