feat: initialize Kurdistan SDK - independent fork of Polkadot SDK
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
[package]
|
||||
name = "sp-runtime-interface-proc-macro"
|
||||
version = "17.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
description = "This crate provides procedural macros for usage within the context of the Substrate runtime interface."
|
||||
documentation = "https://docs.rs/sp-runtime-interface-proc-macro"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
Inflector = { workspace = true }
|
||||
expander = { workspace = true }
|
||||
proc-macro-crate = { workspace = true }
|
||||
proc-macro2 = { workspace = true }
|
||||
quote = { workspace = true }
|
||||
syn = { features = ["extra-traits", "fold", "full", "visit"], workspace = true }
|
||||
@@ -0,0 +1,80 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! This crate provides procedural macros for usage within the context of the Substrate runtime
|
||||
//! interface.
|
||||
//!
|
||||
//! It provides the [`#[runtime_interface]`](attr.runtime_interface.html) attribute macro
|
||||
//! for generating the runtime interfaces.
|
||||
|
||||
use syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
parse_macro_input, ItemTrait, Result, Token,
|
||||
};
|
||||
|
||||
mod runtime_interface;
|
||||
mod utils;
|
||||
|
||||
struct Options {
|
||||
wasm_only: bool,
|
||||
tracing: bool,
|
||||
}
|
||||
|
||||
impl Options {
|
||||
fn unpack(self) -> (bool, bool) {
|
||||
(self.wasm_only, self.tracing)
|
||||
}
|
||||
}
|
||||
impl Default for Options {
|
||||
fn default() -> Self {
|
||||
Options { wasm_only: false, tracing: true }
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for Options {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let mut res = Self::default();
|
||||
while !input.is_empty() {
|
||||
let lookahead = input.lookahead1();
|
||||
if lookahead.peek(runtime_interface::keywords::wasm_only) {
|
||||
let _ = input.parse::<runtime_interface::keywords::wasm_only>();
|
||||
res.wasm_only = true;
|
||||
} else if lookahead.peek(runtime_interface::keywords::no_tracing) {
|
||||
let _ = input.parse::<runtime_interface::keywords::no_tracing>();
|
||||
res.tracing = false;
|
||||
} else if lookahead.peek(Token![,]) {
|
||||
let _ = input.parse::<Token![,]>();
|
||||
} else {
|
||||
return Err(lookahead.error());
|
||||
}
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn runtime_interface(
|
||||
attrs: proc_macro::TokenStream,
|
||||
input: proc_macro::TokenStream,
|
||||
) -> proc_macro::TokenStream {
|
||||
let trait_def = parse_macro_input!(input as ItemTrait);
|
||||
let (wasm_only, tracing) = parse_macro_input!(attrs as Options).unpack();
|
||||
|
||||
runtime_interface::runtime_interface_impl(trait_def, wasm_only, tracing)
|
||||
.unwrap_or_else(|e| e.to_compile_error())
|
||||
.into()
|
||||
}
|
||||
+269
@@ -0,0 +1,269 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Generates the bare function interface for a given trait definition.
|
||||
//!
|
||||
//! The bare functions are the ones that will be called by the user. On the native/host side, these
|
||||
//! functions directly execute the provided implementation. On the wasm side, these
|
||||
//! functions will prepare the parameters for the FFI boundary, call the external host function
|
||||
//! exported into wasm and convert back the result.
|
||||
//!
|
||||
//! [`generate`] is the entry point for generating for each
|
||||
//! trait method one bare function.
|
||||
//!
|
||||
//! [`function_for_method`] generates the bare
|
||||
//! function per trait method. Each bare function contains both implementations. The implementations
|
||||
//! are feature-gated, so that one is compiled for the native and the other for the wasm side.
|
||||
|
||||
use crate::utils::{
|
||||
create_exchangeable_host_function_ident, create_function_ident_with_version,
|
||||
generate_crate_access, get_function_argument_names, get_function_arguments,
|
||||
get_runtime_interface, host_inner_return_ty, pat_ty_to_host_inner, RuntimeInterfaceFunction,
|
||||
};
|
||||
|
||||
use syn::{
|
||||
parse_quote, spanned::Spanned, FnArg, Ident, ItemTrait, Result, Signature, Token, TraitItemFn,
|
||||
};
|
||||
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
|
||||
use quote::{quote, quote_spanned};
|
||||
|
||||
use std::iter;
|
||||
|
||||
/// Generate one bare function per trait method. The name of the bare function is equal to the name
|
||||
/// of the trait method.
|
||||
pub fn generate(trait_def: &ItemTrait, is_wasm_only: bool, tracing: bool) -> Result<TokenStream> {
|
||||
let trait_name = &trait_def.ident;
|
||||
let runtime_interface = get_runtime_interface(trait_def)?;
|
||||
|
||||
// latest version dispatch
|
||||
let token_stream: Result<TokenStream> = runtime_interface.latest_versions_to_call().try_fold(
|
||||
TokenStream::new(),
|
||||
|mut t, (latest_version, method)| {
|
||||
t.extend(function_for_method(method, latest_version, is_wasm_only)?);
|
||||
Ok(t)
|
||||
},
|
||||
);
|
||||
|
||||
// earlier versions compatibility dispatch (only std variant)
|
||||
let result: Result<TokenStream> =
|
||||
runtime_interface
|
||||
.all_versions()
|
||||
.try_fold(token_stream?, |mut t, (version, method)| {
|
||||
t.extend(function_std_impl(trait_name, method, version, is_wasm_only, tracing)?);
|
||||
Ok(t)
|
||||
});
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Generates the bare function implementation for the given method for the host and wasm side.
|
||||
fn function_for_method(
|
||||
method: &RuntimeInterfaceFunction,
|
||||
latest_version: u32,
|
||||
is_wasm_only: bool,
|
||||
) -> Result<TokenStream> {
|
||||
let std_impl =
|
||||
if !is_wasm_only { function_std_latest_impl(method, latest_version)? } else { quote!() };
|
||||
|
||||
let no_std_impl = function_no_std_impl(method, is_wasm_only)?;
|
||||
|
||||
Ok(quote! {
|
||||
#std_impl
|
||||
|
||||
#no_std_impl
|
||||
})
|
||||
}
|
||||
|
||||
/// Generates the bare function implementation for `cfg(substrate_runtime)`.
|
||||
fn function_no_std_impl(
|
||||
method: &RuntimeInterfaceFunction,
|
||||
is_wasm_only: bool,
|
||||
) -> Result<TokenStream> {
|
||||
let should_trap_on_return = method.should_trap_on_return();
|
||||
let mut method = (*method).clone();
|
||||
crate::utils::unpack_inner_types_in_signature(&mut method.sig);
|
||||
|
||||
let function_name = &method.sig.ident;
|
||||
let host_function_name = create_exchangeable_host_function_ident(&method.sig.ident);
|
||||
let args = get_function_arguments(&method.sig);
|
||||
let arg_names = get_function_argument_names(&method.sig);
|
||||
let return_value = if should_trap_on_return {
|
||||
syn::ReturnType::Type(
|
||||
<Token![->]>::default(),
|
||||
Box::new(syn::TypeNever { bang_token: <Token![!]>::default() }.into()),
|
||||
)
|
||||
} else {
|
||||
method.sig.output.clone()
|
||||
};
|
||||
let maybe_unreachable = if should_trap_on_return {
|
||||
quote! {
|
||||
;
|
||||
#[cfg(target_family = "wasm")]
|
||||
{ core::arch::wasm32::unreachable(); }
|
||||
|
||||
#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))]
|
||||
unsafe { core::arch::asm!("unimp", options(noreturn)); }
|
||||
}
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
|
||||
let attrs = method.attrs.iter().filter(|a| !a.path().is_ident("version"));
|
||||
|
||||
let cfg_wasm_only = if is_wasm_only {
|
||||
quote! { #[cfg(substrate_runtime)] }
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
|
||||
Ok(quote! {
|
||||
#cfg_wasm_only
|
||||
#[cfg(substrate_runtime)]
|
||||
#( #attrs )*
|
||||
pub fn #function_name( #( #args, )* ) #return_value {
|
||||
// Call the host function
|
||||
#host_function_name.get()( #( #arg_names, )* )
|
||||
#maybe_unreachable
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Generate call to latest function version for `cfg(not(substrate_runtime))`
|
||||
///
|
||||
/// This should generate simple `fn func(..) { func_version_<latest_version>(..) }`.
|
||||
fn function_std_latest_impl(method: &TraitItemFn, latest_version: u32) -> Result<TokenStream> {
|
||||
let function_name = &method.sig.ident;
|
||||
let args = get_function_arguments(&method.sig).map(pat_ty_to_host_inner).map(FnArg::Typed);
|
||||
let arg_names = get_function_argument_names(&method.sig).collect::<Vec<_>>();
|
||||
let return_value = host_inner_return_ty(&method.sig.output);
|
||||
let attrs = method.attrs.iter().filter(|a| !a.path().is_ident("version"));
|
||||
let latest_function_name =
|
||||
create_function_ident_with_version(&method.sig.ident, latest_version);
|
||||
|
||||
Ok(quote_spanned! { method.span() =>
|
||||
#[cfg(not(substrate_runtime))]
|
||||
#( #attrs )*
|
||||
pub fn #function_name( #( #args, )* ) #return_value {
|
||||
#latest_function_name(
|
||||
#( #arg_names, )*
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Generates the bare function implementation for `cfg(not(substrate_runtime))`.
|
||||
fn function_std_impl(
|
||||
trait_name: &Ident,
|
||||
method: &TraitItemFn,
|
||||
version: u32,
|
||||
is_wasm_only: bool,
|
||||
tracing: bool,
|
||||
) -> Result<TokenStream> {
|
||||
let function_name = create_function_ident_with_version(&method.sig.ident, version);
|
||||
let function_name_str = function_name.to_string();
|
||||
|
||||
let crate_ = generate_crate_access();
|
||||
let args = get_function_arguments(&method.sig)
|
||||
.map(pat_ty_to_host_inner)
|
||||
.map(FnArg::Typed)
|
||||
.chain(
|
||||
// Add the function context as last parameter when this is a wasm only interface.
|
||||
iter::from_fn(|| {
|
||||
if is_wasm_only {
|
||||
Some(parse_quote!(
|
||||
mut __function_context__: &mut dyn #crate_::sp_wasm_interface::FunctionContext
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.take(1),
|
||||
);
|
||||
let return_value = host_inner_return_ty(&method.sig.output);
|
||||
let attrs = method.attrs.iter().filter(|a| !a.path().is_ident("version"));
|
||||
// Don't make the function public accessible when this is a wasm only interface.
|
||||
let call_to_trait = generate_call_to_trait(trait_name, method, version, is_wasm_only);
|
||||
let call_to_trait = if !tracing {
|
||||
call_to_trait
|
||||
} else {
|
||||
parse_quote!(
|
||||
#crate_::sp_tracing::within_span! { #crate_::sp_tracing::trace_span!(#function_name_str);
|
||||
#call_to_trait
|
||||
}
|
||||
)
|
||||
};
|
||||
|
||||
Ok(quote_spanned! { method.span() =>
|
||||
#[cfg(not(substrate_runtime))]
|
||||
#( #attrs )*
|
||||
fn #function_name( #( #args, )* ) #return_value {
|
||||
#call_to_trait
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Generate the call to the interface trait.
|
||||
fn generate_call_to_trait(
|
||||
trait_name: &Ident,
|
||||
method: &TraitItemFn,
|
||||
version: u32,
|
||||
is_wasm_only: bool,
|
||||
) -> TokenStream {
|
||||
let crate_ = generate_crate_access();
|
||||
let method_name = create_function_ident_with_version(&method.sig.ident, version);
|
||||
let expect_msg =
|
||||
format!("`{}` called outside of an Externalities-provided environment.", method_name);
|
||||
let arg_names = get_function_argument_names(&method.sig);
|
||||
|
||||
if takes_self_argument(&method.sig) {
|
||||
let instance = if is_wasm_only {
|
||||
Ident::new("__function_context__", Span::call_site())
|
||||
} else {
|
||||
Ident::new("__externalities__", Span::call_site())
|
||||
};
|
||||
|
||||
let impl_ = quote!( #trait_name::#method_name(&mut #instance, #( #arg_names, )*) );
|
||||
|
||||
if is_wasm_only {
|
||||
quote_spanned! { method.span() => #impl_ }
|
||||
} else {
|
||||
quote_spanned! { method.span() =>
|
||||
#crate_::with_externalities(|mut #instance| #impl_).expect(#expect_msg)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// The name of the trait the interface trait is implemented for
|
||||
let impl_trait_name = if is_wasm_only {
|
||||
quote!( #crate_::sp_wasm_interface::FunctionContext )
|
||||
} else {
|
||||
quote!( #crate_::Externalities )
|
||||
};
|
||||
|
||||
quote_spanned! { method.span() =>
|
||||
<&mut dyn #impl_trait_name as #trait_name>::#method_name(
|
||||
#( #arg_names, )*
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns if the given `Signature` takes a `self` argument.
|
||||
fn takes_self_argument(sig: &Signature) -> bool {
|
||||
matches!(sig.inputs.first(), Some(FnArg::Receiver(_)))
|
||||
}
|
||||
+480
@@ -0,0 +1,480 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Generates the extern host functions and the implementation for these host functions.
|
||||
//!
|
||||
//! The extern host functions will be called by the bare function interface from the Wasm side.
|
||||
//! The implementation of these host functions will be called on the host side from the Wasm
|
||||
//! executor. These implementations call the bare function interface.
|
||||
|
||||
use crate::utils::{
|
||||
create_exchangeable_host_function_ident, create_function_ident_with_version,
|
||||
create_host_function_ident, generate_crate_access, get_function_argument_names,
|
||||
get_function_argument_names_and_types, get_function_argument_types, get_function_arguments,
|
||||
get_runtime_interface, RuntimeInterfaceFunction,
|
||||
};
|
||||
|
||||
use syn::{
|
||||
spanned::Spanned, Error, Ident, ItemTrait, Pat, Result, ReturnType, Signature, TraitItemFn,
|
||||
};
|
||||
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
|
||||
use quote::quote;
|
||||
|
||||
use inflector::Inflector;
|
||||
|
||||
use std::iter::Iterator;
|
||||
|
||||
/// Generate the extern host functions for wasm and the `HostFunctions` struct that provides the
|
||||
/// implementations for the host functions on the host.
|
||||
pub fn generate(trait_def: &ItemTrait, is_wasm_only: bool) -> Result<TokenStream> {
|
||||
let trait_name = &trait_def.ident;
|
||||
let extern_host_function_impls = get_runtime_interface(trait_def)?
|
||||
.latest_versions_to_call()
|
||||
.try_fold(TokenStream::new(), |mut t, (version, method)| {
|
||||
t.extend(generate_extern_host_function(method, version, trait_name)?);
|
||||
Ok::<_, Error>(t)
|
||||
})?;
|
||||
let exchangeable_host_functions = get_runtime_interface(trait_def)?
|
||||
.latest_versions_to_call()
|
||||
.try_fold(TokenStream::new(), |mut t, (_, m)| {
|
||||
t.extend(generate_exchangeable_host_function(m)?);
|
||||
Ok::<_, Error>(t)
|
||||
})?;
|
||||
let host_functions_struct = generate_host_functions_struct(trait_def, is_wasm_only)?;
|
||||
|
||||
Ok(quote! {
|
||||
/// The implementations of the extern host functions. This special implementation module
|
||||
/// is required to change the extern host functions signature to
|
||||
/// `unsafe fn name(args) -> ret` to make the function implementations exchangeable.
|
||||
#[cfg(substrate_runtime)]
|
||||
mod extern_host_function_impls {
|
||||
use super::*;
|
||||
|
||||
#extern_host_function_impls
|
||||
}
|
||||
|
||||
#exchangeable_host_functions
|
||||
|
||||
#host_functions_struct
|
||||
})
|
||||
}
|
||||
|
||||
/// Generate the extern host function for the given method.
|
||||
fn generate_extern_host_function(
|
||||
method: &TraitItemFn,
|
||||
version: u32,
|
||||
trait_name: &Ident,
|
||||
) -> Result<TokenStream> {
|
||||
let crate_ = generate_crate_access();
|
||||
|
||||
let mut unpacked_sig = method.sig.clone();
|
||||
crate::utils::unpack_inner_types_in_signature(&mut unpacked_sig);
|
||||
let unpacked_args = get_function_arguments(&unpacked_sig);
|
||||
let unpacked_return_value = &unpacked_sig.output;
|
||||
|
||||
let arg_types = get_function_argument_types(&method.sig);
|
||||
let arg_names = get_function_argument_names(&method.sig);
|
||||
let function = &method.sig.ident;
|
||||
let ext_function = create_host_function_ident(&method.sig.ident, version, trait_name);
|
||||
let doc_string = format!(
|
||||
" Default extern host function implementation for [`super::{}`].",
|
||||
method.sig.ident,
|
||||
);
|
||||
let return_value = &method.sig.output;
|
||||
let cfg_attrs = method.attrs.iter().filter(|a| a.path().is_ident("cfg"));
|
||||
|
||||
let ffi_return_value = match method.sig.output {
|
||||
ReturnType::Default => quote!(),
|
||||
ReturnType::Type(_, ref ty) => quote! {
|
||||
-> <#ty as #crate_::RIType>::FFIType
|
||||
},
|
||||
};
|
||||
|
||||
let convert_return_value = match return_value {
|
||||
ReturnType::Default => quote! { __runtime_interface_result_ },
|
||||
ReturnType::Type(_, ref ty) => quote! {
|
||||
<#ty as #crate_::wasm::FromFFIValue>::from_ffi_value(__runtime_interface_result_)
|
||||
},
|
||||
};
|
||||
|
||||
let mut call_into_ffi_value = Vec::new();
|
||||
let mut drop_args = Vec::new();
|
||||
let mut ffi_names = Vec::new();
|
||||
for (nth, arg) in get_function_arguments(&method.sig).enumerate() {
|
||||
let arg_name = &arg.pat;
|
||||
let arg_ty = &arg.ty;
|
||||
let ffi_name =
|
||||
Ident::new(&format!("__runtime_interface_ffi_value_{}_", nth), arg.pat.span());
|
||||
let destructor_name =
|
||||
Ident::new(&format!("__runtime_interface_arg_destructor_{}_", nth), arg.pat.span());
|
||||
|
||||
ffi_names.push(ffi_name.clone());
|
||||
|
||||
call_into_ffi_value.push(quote! {
|
||||
let mut #arg_name = #arg_name;
|
||||
let (#ffi_name, #destructor_name) = <#arg_ty as #crate_::wasm::IntoFFIValue>::into_ffi_value(&mut #arg_name);
|
||||
});
|
||||
|
||||
drop_args.push(quote! {
|
||||
#[allow(dropping_copy_types)]
|
||||
::core::mem::drop(#destructor_name);
|
||||
});
|
||||
}
|
||||
|
||||
// Drop in the reverse order to construction.
|
||||
drop_args.reverse();
|
||||
|
||||
Ok(quote! {
|
||||
#(#cfg_attrs)*
|
||||
#[doc = #doc_string]
|
||||
pub fn #function ( #( #unpacked_args ),* ) #unpacked_return_value {
|
||||
#[cfg_attr(any(target_arch = "riscv32", target_arch = "riscv64"), #crate_::polkavm::polkavm_import(abi = #crate_::polkavm::polkavm_abi))]
|
||||
extern "C" {
|
||||
pub fn #ext_function (
|
||||
#( #arg_names: <#arg_types as #crate_::RIType>::FFIType ),*
|
||||
) #ffi_return_value;
|
||||
}
|
||||
|
||||
#(#call_into_ffi_value)*
|
||||
let __runtime_interface_result_ = unsafe { #ext_function( #( #ffi_names ),* ) };
|
||||
#(#drop_args)*
|
||||
#convert_return_value
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Generate the host exchangeable function for the given method.
|
||||
fn generate_exchangeable_host_function(method: &TraitItemFn) -> Result<TokenStream> {
|
||||
let mut method = method.clone();
|
||||
crate::utils::unpack_inner_types_in_signature(&mut method.sig);
|
||||
|
||||
let crate_ = generate_crate_access();
|
||||
let arg_types = get_function_argument_types(&method.sig);
|
||||
let function = &method.sig.ident;
|
||||
let exchangeable_function = create_exchangeable_host_function_ident(&method.sig.ident);
|
||||
let doc_string = format!(" Exchangeable host function used by [`{}`].", method.sig.ident);
|
||||
let output = &method.sig.output;
|
||||
let cfg_attrs = method.attrs.iter().filter(|a| a.path().is_ident("cfg"));
|
||||
|
||||
Ok(quote! {
|
||||
#(#cfg_attrs)*
|
||||
#[cfg(substrate_runtime)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
#[doc = #doc_string]
|
||||
pub static #exchangeable_function : #crate_::wasm::ExchangeableFunction<
|
||||
fn ( #( #arg_types ),* ) #output
|
||||
> = #crate_::wasm::ExchangeableFunction::new(extern_host_function_impls::#function);
|
||||
})
|
||||
}
|
||||
|
||||
/// Generate the `HostFunctions` struct that implements `wasm-interface::HostFunctions` to provide
|
||||
/// implementations for the extern host functions.
|
||||
fn generate_host_functions_struct(
|
||||
trait_def: &ItemTrait,
|
||||
is_wasm_only: bool,
|
||||
) -> Result<TokenStream> {
|
||||
let crate_ = generate_crate_access();
|
||||
|
||||
let mut host_function_impls = Vec::new();
|
||||
let mut register_bodies = Vec::new();
|
||||
let mut append_hf_bodies = Vec::new();
|
||||
|
||||
for (version, method) in get_runtime_interface(trait_def)?.all_versions() {
|
||||
let (implementation, register_body, append_hf_body) =
|
||||
generate_host_function_implementation(&trait_def.ident, method, version, is_wasm_only)?;
|
||||
host_function_impls.push(implementation);
|
||||
register_bodies.push(register_body);
|
||||
append_hf_bodies.push(append_hf_body);
|
||||
}
|
||||
|
||||
Ok(quote! {
|
||||
#(#host_function_impls)*
|
||||
|
||||
/// Provides implementations for the extern host functions.
|
||||
#[cfg(not(substrate_runtime))]
|
||||
pub struct HostFunctions;
|
||||
|
||||
#[cfg(not(substrate_runtime))]
|
||||
impl #crate_::sp_wasm_interface::HostFunctions for HostFunctions {
|
||||
fn host_functions() -> Vec<&'static dyn #crate_::sp_wasm_interface::Function> {
|
||||
let mut host_functions_list = Vec::new();
|
||||
#(#append_hf_bodies)*
|
||||
host_functions_list
|
||||
}
|
||||
|
||||
#crate_::sp_wasm_interface::if_wasmtime_is_enabled! {
|
||||
fn register_static<T>(registry: &mut T) -> core::result::Result<(), T::Error>
|
||||
where T: #crate_::sp_wasm_interface::HostFunctionRegistry
|
||||
{
|
||||
#(#register_bodies)*
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Generates the host function struct that implements `wasm_interface::Function` and returns a
|
||||
/// static reference to this struct.
|
||||
///
|
||||
/// When calling from wasm into the host, we will call the `execute` function that calls the native
|
||||
/// implementation of the function.
|
||||
fn generate_host_function_implementation(
|
||||
trait_name: &Ident,
|
||||
method: &RuntimeInterfaceFunction,
|
||||
version: u32,
|
||||
is_wasm_only: bool,
|
||||
) -> Result<(TokenStream, TokenStream, TokenStream)> {
|
||||
let name = create_host_function_ident(&method.sig.ident, version, trait_name).to_string();
|
||||
let struct_name = Ident::new(&name.to_pascal_case(), Span::call_site());
|
||||
let crate_ = generate_crate_access();
|
||||
let signature = generate_wasm_interface_signature_for_host_function(&method.sig)?;
|
||||
|
||||
let fn_name = create_function_ident_with_version(&method.sig.ident, version);
|
||||
|
||||
// List of variable names containing WASM FFI-compatible arguments.
|
||||
let mut ffi_names = Vec::new();
|
||||
|
||||
// List of `$name: $ty` tokens containing WASM FFI-compatible arguments.
|
||||
let mut ffi_args_prototype = Vec::new();
|
||||
|
||||
// List of variable names containing arguments already converted into native Rust types.
|
||||
// Also includes the preceding `&` or `&mut`. To be used to call the actual implementation of
|
||||
// the host function.
|
||||
let mut host_names_with_ref = Vec::new();
|
||||
|
||||
// List of code snippets to copy over the results returned from a host function through
|
||||
// any `&mut` arguments back into WASM's linear memory.
|
||||
let mut copy_data_into_ref_mut_args = Vec::new();
|
||||
|
||||
// List of code snippets to convert dynamic FFI args (`Value` enum) into concrete static FFI
|
||||
// types (`u32`, etc.).
|
||||
let mut convert_args_dynamic_ffi_to_static_ffi = Vec::new();
|
||||
|
||||
// List of code snippets to convert static FFI args (`u32`, etc.) into native Rust types.
|
||||
let mut convert_args_static_ffi_to_host = Vec::new();
|
||||
|
||||
for (host_name, host_ty) in get_function_argument_names_and_types(&method.sig) {
|
||||
let ffi_name = generate_ffi_value_var_name(&host_name)?;
|
||||
let host_name_ident = match *host_name {
|
||||
Pat::Ident(ref pat_ident) => pat_ident.ident.clone(),
|
||||
_ => unreachable!("`generate_ffi_value_var_name` above would return an error on `Pat` != `Ident`; qed"),
|
||||
};
|
||||
|
||||
let ffi_ty = quote! { <#host_ty as #crate_::RIType>::FFIType };
|
||||
ffi_args_prototype.push(quote! { #ffi_name: #ffi_ty });
|
||||
ffi_names.push(quote! { #ffi_name });
|
||||
|
||||
let convert_arg_error = format!(
|
||||
"could not marshal the '{}' argument through the WASM FFI boundary while executing '{}' from interface '{}'",
|
||||
host_name_ident,
|
||||
method.sig.ident,
|
||||
trait_name
|
||||
);
|
||||
convert_args_static_ffi_to_host.push(quote! {
|
||||
let mut #host_name = <#host_ty as #crate_::host::FromFFIValue>::from_ffi_value(__function_context__, #ffi_name)
|
||||
.map_err(|err| #crate_::alloc::format!("{}: {}", err, #convert_arg_error))?;
|
||||
});
|
||||
|
||||
host_names_with_ref.push(
|
||||
quote! { <#host_ty as #crate_::host::FromFFIValue>::take_from_owned(&mut #host_name) },
|
||||
);
|
||||
copy_data_into_ref_mut_args.push(quote! {
|
||||
<#host_ty as #crate_::host::FromFFIValue>::write_back_into_runtime(#host_name, __function_context__, #ffi_name)?;
|
||||
});
|
||||
|
||||
let arg_count_mismatch_error = format!(
|
||||
"missing argument '{}': number of arguments given to '{}' from interface '{}' does not match the expected number of arguments",
|
||||
host_name_ident,
|
||||
method.sig.ident,
|
||||
trait_name
|
||||
);
|
||||
convert_args_dynamic_ffi_to_static_ffi.push(quote! {
|
||||
let #ffi_name = args.next().ok_or_else(|| #crate_::alloc::borrow::ToOwned::to_owned(#arg_count_mismatch_error))?;
|
||||
let #ffi_name: #ffi_ty = #crate_::sp_wasm_interface::TryFromValue::try_from_value(#ffi_name)
|
||||
.ok_or_else(|| #crate_::alloc::borrow::ToOwned::to_owned(#convert_arg_error))?;
|
||||
});
|
||||
}
|
||||
|
||||
let ffi_return_ty = match &method.sig.output {
|
||||
ReturnType::Type(_, ty) => quote! { <#ty as #crate_::RIType>::FFIType },
|
||||
ReturnType::Default => quote! { () },
|
||||
};
|
||||
|
||||
let convert_return_value_host_to_static_ffi = match &method.sig.output {
|
||||
ReturnType::Type(_, ty) => quote! {
|
||||
let __result__ = <#ty as #crate_::host::IntoFFIValue>::into_ffi_value(
|
||||
__result__,
|
||||
__function_context__
|
||||
);
|
||||
},
|
||||
ReturnType::Default => quote! {
|
||||
let __result__ = Ok(__result__);
|
||||
},
|
||||
};
|
||||
|
||||
let convert_return_value_static_ffi_to_dynamic_ffi = match &method.sig.output {
|
||||
ReturnType::Type(_, _) => quote! {
|
||||
let __result__ = Ok(Some(#crate_::sp_wasm_interface::IntoValue::into_value(__result__)));
|
||||
},
|
||||
ReturnType::Default => quote! {
|
||||
let __result__ = Ok(None);
|
||||
},
|
||||
};
|
||||
|
||||
if is_wasm_only {
|
||||
host_names_with_ref.push(quote! {
|
||||
__function_context__
|
||||
});
|
||||
}
|
||||
|
||||
let cfg_attrs: Vec<_> =
|
||||
method.attrs.iter().filter(|a| a.path().is_ident("cfg")).cloned().collect();
|
||||
if version > 1 && !cfg_attrs.is_empty() {
|
||||
return Err(Error::new(
|
||||
method.span(),
|
||||
"Conditional compilation is not supported for versioned functions",
|
||||
));
|
||||
}
|
||||
|
||||
let implementation = quote! {
|
||||
#(#cfg_attrs)*
|
||||
#[cfg(not(substrate_runtime))]
|
||||
struct #struct_name;
|
||||
|
||||
#(#cfg_attrs)*
|
||||
#[cfg(not(substrate_runtime))]
|
||||
impl #struct_name {
|
||||
fn call(
|
||||
__function_context__: &mut dyn #crate_::sp_wasm_interface::FunctionContext,
|
||||
#(#ffi_args_prototype),*
|
||||
) -> ::core::result::Result<#ffi_return_ty, #crate_::alloc::string::String> {
|
||||
#(#convert_args_static_ffi_to_host)*
|
||||
let __result__ = #fn_name(#(#host_names_with_ref),*);
|
||||
#(#copy_data_into_ref_mut_args)*
|
||||
#convert_return_value_host_to_static_ffi
|
||||
__result__
|
||||
}
|
||||
}
|
||||
|
||||
#(#cfg_attrs)*
|
||||
#[cfg(not(substrate_runtime))]
|
||||
impl #crate_::sp_wasm_interface::Function for #struct_name {
|
||||
fn name(&self) -> &str {
|
||||
#name
|
||||
}
|
||||
|
||||
fn signature(&self) -> #crate_::sp_wasm_interface::Signature {
|
||||
#signature
|
||||
}
|
||||
|
||||
fn execute(
|
||||
&self,
|
||||
__function_context__: &mut dyn #crate_::sp_wasm_interface::FunctionContext,
|
||||
args: &mut dyn Iterator<Item = #crate_::sp_wasm_interface::Value>,
|
||||
) -> ::core::result::Result<Option<#crate_::sp_wasm_interface::Value>, #crate_::alloc::string::String> {
|
||||
#(#convert_args_dynamic_ffi_to_static_ffi)*
|
||||
let __result__ = Self::call(
|
||||
__function_context__,
|
||||
#(#ffi_names),*
|
||||
)?;
|
||||
#convert_return_value_static_ffi_to_dynamic_ffi
|
||||
__result__
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let register_body = quote! {
|
||||
#(#cfg_attrs)*
|
||||
registry.register_static(
|
||||
#crate_::sp_wasm_interface::Function::name(&#struct_name),
|
||||
|mut caller: #crate_::sp_wasm_interface::wasmtime::Caller<T::State>, #(#ffi_args_prototype),*|
|
||||
-> ::core::result::Result<#ffi_return_ty, #crate_::sp_wasm_interface::anyhow::Error>
|
||||
{
|
||||
T::with_function_context(caller, move |__function_context__| {
|
||||
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
#struct_name::call(
|
||||
__function_context__,
|
||||
#(#ffi_names,)*
|
||||
).map_err(#crate_::sp_wasm_interface::anyhow::Error::msg)
|
||||
}));
|
||||
match result {
|
||||
Ok(result) => result,
|
||||
Err(panic) => {
|
||||
let message =
|
||||
if let Some(message) = panic.downcast_ref::<#crate_::alloc::string::String>() {
|
||||
#crate_::alloc::format!("host code panicked while being called by the runtime: {}", message)
|
||||
} else if let Some(message) = panic.downcast_ref::<&'static str>() {
|
||||
#crate_::alloc::format!("host code panicked while being called by the runtime: {}", message)
|
||||
} else {
|
||||
#crate_::alloc::borrow::ToOwned::to_owned("host code panicked while being called by the runtime")
|
||||
};
|
||||
return Err(#crate_::sp_wasm_interface::anyhow::Error::msg(message));
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
)?;
|
||||
};
|
||||
|
||||
let append_hf_body = quote! {
|
||||
#(#cfg_attrs)*
|
||||
host_functions_list.push(&#struct_name as &dyn #crate_::sp_wasm_interface::Function);
|
||||
};
|
||||
|
||||
Ok((implementation, register_body, append_hf_body))
|
||||
}
|
||||
|
||||
/// Generate the `wasm_interface::Signature` for the given host function `sig`.
|
||||
fn generate_wasm_interface_signature_for_host_function(sig: &Signature) -> Result<TokenStream> {
|
||||
let crate_ = generate_crate_access();
|
||||
let return_value = match &sig.output {
|
||||
ReturnType::Type(_, ty) => quote! {
|
||||
Some( <<#ty as #crate_::RIType>::FFIType as #crate_::sp_wasm_interface::IntoValue>::VALUE_TYPE )
|
||||
},
|
||||
ReturnType::Default => quote!(None),
|
||||
};
|
||||
let arg_types = get_function_argument_types(sig).map(|ty| {
|
||||
quote! {
|
||||
<<#ty as #crate_::RIType>::FFIType as #crate_::sp_wasm_interface::IntoValue>::VALUE_TYPE
|
||||
}
|
||||
});
|
||||
|
||||
Ok(quote! {
|
||||
#crate_::sp_wasm_interface::Signature {
|
||||
args: #crate_::alloc::borrow::Cow::Borrowed(&[ #( #arg_types ),* ][..]),
|
||||
return_value: #return_value,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Generate the variable name that stores the FFI value.
|
||||
fn generate_ffi_value_var_name(pat: &Pat) -> Result<Ident> {
|
||||
match pat {
|
||||
Pat::Ident(pat_ident) =>
|
||||
if let Some(by_ref) = pat_ident.by_ref {
|
||||
Err(Error::new(by_ref.span(), "`ref` not supported!"))
|
||||
} else if let Some(sub_pattern) = &pat_ident.subpat {
|
||||
Err(Error::new(sub_pattern.0.span(), "Not supported!"))
|
||||
} else {
|
||||
Ok(Ident::new(&format!("{}_ffi_value", pat_ident.ident), Span::call_site()))
|
||||
},
|
||||
_ => Err(Error::new(pat.span(), "Not supported as variable name!")),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::utils::generate_runtime_interface_include;
|
||||
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
|
||||
use syn::{Ident, ItemTrait, Result};
|
||||
|
||||
use inflector::Inflector;
|
||||
|
||||
use quote::quote;
|
||||
|
||||
mod bare_function_interface;
|
||||
mod host_function_interface;
|
||||
mod trait_decl_impl;
|
||||
|
||||
/// Custom keywords supported by the `runtime_interface` attribute.
|
||||
pub mod keywords {
|
||||
// Custom keyword `wasm_only` that can be given as attribute to [`runtime_interface`].
|
||||
syn::custom_keyword!(wasm_only);
|
||||
// Disable tracing-macros added to the [`runtime_interface`] by specifying this optional entry
|
||||
syn::custom_keyword!(no_tracing);
|
||||
}
|
||||
|
||||
/// Implementation of the `runtime_interface` attribute.
|
||||
///
|
||||
/// It expects the trait definition the attribute was put above and if this should be an wasm only
|
||||
/// interface.
|
||||
pub fn runtime_interface_impl(
|
||||
trait_def: ItemTrait,
|
||||
is_wasm_only: bool,
|
||||
tracing: bool,
|
||||
) -> Result<TokenStream> {
|
||||
let bare_functions = bare_function_interface::generate(&trait_def, is_wasm_only, tracing)?;
|
||||
let crate_include = generate_runtime_interface_include();
|
||||
let mod_name = Ident::new(&trait_def.ident.to_string().to_snake_case(), Span::call_site());
|
||||
let trait_decl_impl = trait_decl_impl::process(&trait_def, is_wasm_only)?;
|
||||
let host_functions = host_function_interface::generate(&trait_def, is_wasm_only)?;
|
||||
let vis = trait_def.vis;
|
||||
let attrs = &trait_def.attrs;
|
||||
|
||||
let res = quote! {
|
||||
#( #attrs )*
|
||||
#vis mod #mod_name {
|
||||
use super::*;
|
||||
#crate_include
|
||||
|
||||
#bare_functions
|
||||
|
||||
#trait_decl_impl
|
||||
|
||||
#host_functions
|
||||
}
|
||||
};
|
||||
|
||||
let res = expander::Expander::new("runtime_interface")
|
||||
.dry(std::env::var("EXPAND_MACROS").is_err())
|
||||
.verbose(true)
|
||||
.write_to_out_dir(res)
|
||||
.expect("Does not fail because of IO in OUT_DIR; qed");
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Checks the trait declaration, makes the trait declaration module local, removes all method
|
||||
//! default implementations and implements the trait for `&mut dyn Externalities`.
|
||||
|
||||
use crate::utils::{
|
||||
create_function_ident_with_version, generate_crate_access, get_function_argument_types,
|
||||
get_runtime_interface,
|
||||
};
|
||||
|
||||
use syn::{
|
||||
fold::{self, Fold},
|
||||
spanned::Spanned,
|
||||
Error, Generics, ItemTrait, Receiver, Result, TraitItemFn, Type, Visibility,
|
||||
};
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
|
||||
use quote::quote;
|
||||
|
||||
/// Process the given trait definition, by checking that the definition is valid, fold it to the
|
||||
/// essential definition and implement this essential definition for `dyn Externalities`.
|
||||
pub fn process(trait_def: &ItemTrait, is_wasm_only: bool) -> Result<TokenStream> {
|
||||
let impl_trait = impl_trait_for_externalities(trait_def, is_wasm_only)?;
|
||||
let essential_trait_def = declare_essential_trait(trait_def)?;
|
||||
|
||||
Ok(quote! {
|
||||
#impl_trait
|
||||
|
||||
#essential_trait_def
|
||||
})
|
||||
}
|
||||
|
||||
/// Converts the given trait definition into the essential trait definition without method
|
||||
/// default implementations and visibility set to inherited.
|
||||
struct ToEssentialTraitDef {
|
||||
/// All errors found while doing the conversion.
|
||||
errors: Vec<Error>,
|
||||
methods: Vec<TraitItemFn>,
|
||||
}
|
||||
|
||||
impl ToEssentialTraitDef {
|
||||
fn new() -> Self {
|
||||
ToEssentialTraitDef { errors: vec![], methods: vec![] }
|
||||
}
|
||||
|
||||
fn into_methods(self) -> Result<Vec<TraitItemFn>> {
|
||||
let mut errors = self.errors;
|
||||
let methods = self.methods;
|
||||
if let Some(first_error) = errors.pop() {
|
||||
Err(errors.into_iter().fold(first_error, |mut o, n| {
|
||||
o.combine(n);
|
||||
o
|
||||
}))
|
||||
} else {
|
||||
Ok(methods)
|
||||
}
|
||||
}
|
||||
|
||||
fn process(&mut self, method: &TraitItemFn, version: u32) {
|
||||
let mut folded = self.fold_trait_item_fn(method.clone());
|
||||
folded.sig.ident = create_function_ident_with_version(&folded.sig.ident, version);
|
||||
crate::utils::unpack_inner_types_in_signature(&mut folded.sig);
|
||||
self.methods.push(folded);
|
||||
}
|
||||
|
||||
fn push_error<S: Spanned>(&mut self, span: &S, msg: &str) {
|
||||
self.errors.push(Error::new(span.span(), msg));
|
||||
}
|
||||
|
||||
fn error_on_generic_parameters(&mut self, generics: &Generics) {
|
||||
if let Some(param) = generics.params.first() {
|
||||
self.push_error(param, "Generic parameters not supported.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Fold for ToEssentialTraitDef {
|
||||
fn fold_trait_item_fn(&mut self, mut method: TraitItemFn) -> TraitItemFn {
|
||||
if method.default.take().is_none() {
|
||||
self.push_error(&method, "Methods need to have an implementation.");
|
||||
}
|
||||
|
||||
let arg_types = get_function_argument_types(&method.sig);
|
||||
arg_types
|
||||
.filter_map(|ty| match *ty {
|
||||
Type::ImplTrait(impl_trait) => Some(impl_trait),
|
||||
_ => None,
|
||||
})
|
||||
.for_each(|invalid| self.push_error(&invalid, "`impl Trait` syntax not supported."));
|
||||
|
||||
self.error_on_generic_parameters(&method.sig.generics);
|
||||
|
||||
method.attrs.retain(|a| !a.path().is_ident("version"));
|
||||
|
||||
fold::fold_trait_item_fn(self, method)
|
||||
}
|
||||
|
||||
fn fold_item_trait(&mut self, mut trait_def: ItemTrait) -> ItemTrait {
|
||||
self.error_on_generic_parameters(&trait_def.generics);
|
||||
|
||||
trait_def.vis = Visibility::Inherited;
|
||||
fold::fold_item_trait(self, trait_def)
|
||||
}
|
||||
|
||||
fn fold_receiver(&mut self, receiver: Receiver) -> Receiver {
|
||||
if receiver.reference.is_none() {
|
||||
self.push_error(&receiver, "Taking `Self` by value is not allowed.");
|
||||
}
|
||||
|
||||
fold::fold_receiver(self, receiver)
|
||||
}
|
||||
}
|
||||
|
||||
fn declare_essential_trait(trait_def: &ItemTrait) -> Result<TokenStream> {
|
||||
let trait_ = &trait_def.ident;
|
||||
|
||||
if let Some(param) = trait_def.generics.params.first() {
|
||||
return Err(Error::new(param.span(), "Generic parameters not supported."));
|
||||
}
|
||||
|
||||
let interface = get_runtime_interface(trait_def)?;
|
||||
let mut folder = ToEssentialTraitDef::new();
|
||||
for (version, interface_method) in interface.all_versions() {
|
||||
folder.process(interface_method, version);
|
||||
}
|
||||
let methods = folder.into_methods()?;
|
||||
|
||||
Ok(quote! {
|
||||
trait #trait_ {
|
||||
#( #methods )*
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Implements the given trait definition for `dyn Externalities`.
|
||||
fn impl_trait_for_externalities(trait_def: &ItemTrait, is_wasm_only: bool) -> Result<TokenStream> {
|
||||
let trait_ = &trait_def.ident;
|
||||
let crate_ = generate_crate_access();
|
||||
let interface = get_runtime_interface(trait_def)?;
|
||||
let methods = interface.all_versions().map(|(version, method)| {
|
||||
let mut cloned = (*method).clone();
|
||||
cloned.attrs.retain(|a| !a.path().is_ident("version"));
|
||||
cloned.sig.ident = create_function_ident_with_version(&cloned.sig.ident, version);
|
||||
crate::utils::unpack_inner_types_in_signature(&mut cloned.sig);
|
||||
cloned
|
||||
});
|
||||
|
||||
let impl_type = if is_wasm_only {
|
||||
quote!( &mut dyn #crate_::sp_wasm_interface::FunctionContext )
|
||||
} else {
|
||||
quote!( &mut dyn #crate_::Externalities )
|
||||
};
|
||||
|
||||
Ok(quote! {
|
||||
#[cfg(not(substrate_runtime))]
|
||||
impl #trait_ for #impl_type {
|
||||
#( #methods )*
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,379 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Util function used by this crate.
|
||||
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
|
||||
use syn::{
|
||||
parse::Parse, parse_quote, spanned::Spanned, token, Error, FnArg, Ident, ItemTrait, LitInt,
|
||||
Pat, PatType, Result, Signature, TraitItem, TraitItemFn, Type,
|
||||
};
|
||||
|
||||
use proc_macro_crate::{crate_name, FoundCrate};
|
||||
|
||||
use std::{
|
||||
collections::{btree_map::Entry, BTreeMap},
|
||||
env,
|
||||
};
|
||||
|
||||
use quote::quote;
|
||||
|
||||
use inflector::Inflector;
|
||||
|
||||
mod attributes {
|
||||
syn::custom_keyword!(register_only);
|
||||
}
|
||||
|
||||
/// A concrete, specific version of a runtime interface function.
|
||||
pub struct RuntimeInterfaceFunction {
|
||||
item: TraitItemFn,
|
||||
should_trap_on_return: bool,
|
||||
}
|
||||
|
||||
impl std::ops::Deref for RuntimeInterfaceFunction {
|
||||
type Target = TraitItemFn;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.item
|
||||
}
|
||||
}
|
||||
|
||||
impl RuntimeInterfaceFunction {
|
||||
fn new(item: &TraitItemFn) -> Result<Self> {
|
||||
let mut item = item.clone();
|
||||
let mut should_trap_on_return = false;
|
||||
item.attrs.retain(|attr| {
|
||||
if attr.path().is_ident("trap_on_return") {
|
||||
should_trap_on_return = true;
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
|
||||
if should_trap_on_return && !matches!(item.sig.output, syn::ReturnType::Default) {
|
||||
return Err(Error::new(
|
||||
item.sig.ident.span(),
|
||||
"Methods marked as #[trap_on_return] cannot return anything",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(Self { item, should_trap_on_return })
|
||||
}
|
||||
|
||||
pub fn should_trap_on_return(&self) -> bool {
|
||||
self.should_trap_on_return
|
||||
}
|
||||
}
|
||||
|
||||
/// Runtime interface function with all associated versions of this function.
|
||||
struct RuntimeInterfaceFunctionSet {
|
||||
latest_version_to_call: Option<u32>,
|
||||
versions: BTreeMap<u32, RuntimeInterfaceFunction>,
|
||||
}
|
||||
|
||||
impl RuntimeInterfaceFunctionSet {
|
||||
fn new(version: VersionAttribute, trait_item: &TraitItemFn) -> Result<Self> {
|
||||
Ok(Self {
|
||||
latest_version_to_call: version.is_callable().then_some(version.version),
|
||||
versions: BTreeMap::from([(
|
||||
version.version,
|
||||
RuntimeInterfaceFunction::new(trait_item)?,
|
||||
)]),
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the latest version of this runtime interface function plus the actual function
|
||||
/// implementation.
|
||||
///
|
||||
/// This isn't required to be the latest version, because a runtime interface function can be
|
||||
/// annotated with `register_only` to ensure that the host exposes the host function but it
|
||||
/// isn't used when compiling the runtime.
|
||||
pub fn latest_version_to_call(&self) -> Option<(u32, &RuntimeInterfaceFunction)> {
|
||||
self.latest_version_to_call.map(|v| {
|
||||
(
|
||||
v,
|
||||
self.versions.get(&v).expect(
|
||||
"If latest_version_to_call has a value, the key with this value is in the versions; qed",
|
||||
),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Add a different version of the function.
|
||||
fn add_version(&mut self, version: VersionAttribute, trait_item: &TraitItemFn) -> Result<()> {
|
||||
if let Some(existing_item) = self.versions.get(&version.version) {
|
||||
let mut err = Error::new(trait_item.span(), "Duplicated version attribute");
|
||||
err.combine(Error::new(
|
||||
existing_item.span(),
|
||||
"Previous version with the same number defined here",
|
||||
));
|
||||
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
self.versions
|
||||
.insert(version.version, RuntimeInterfaceFunction::new(trait_item)?);
|
||||
if self.latest_version_to_call.map_or(true, |v| v < version.version) &&
|
||||
version.is_callable()
|
||||
{
|
||||
self.latest_version_to_call = Some(version.version);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// All functions of a runtime interface grouped by the function names.
|
||||
pub struct RuntimeInterface {
|
||||
items: BTreeMap<syn::Ident, RuntimeInterfaceFunctionSet>,
|
||||
}
|
||||
|
||||
impl RuntimeInterface {
|
||||
/// Returns an iterator over all runtime interface function
|
||||
/// [`latest_version_to_call`](RuntimeInterfaceFunctionSet::latest_version).
|
||||
pub fn latest_versions_to_call(
|
||||
&self,
|
||||
) -> impl Iterator<Item = (u32, &RuntimeInterfaceFunction)> {
|
||||
self.items.iter().filter_map(|(_, item)| item.latest_version_to_call())
|
||||
}
|
||||
|
||||
pub fn all_versions(&self) -> impl Iterator<Item = (u32, &RuntimeInterfaceFunction)> {
|
||||
self.items
|
||||
.iter()
|
||||
.flat_map(|(_, item)| item.versions.iter())
|
||||
.map(|(v, i)| (*v, i))
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates the include for the runtime-interface crate.
|
||||
pub fn generate_runtime_interface_include() -> TokenStream {
|
||||
match crate_name("sp-runtime-interface") {
|
||||
Ok(FoundCrate::Itself) => quote!(),
|
||||
Ok(FoundCrate::Name(crate_name)) => {
|
||||
let crate_name = Ident::new(&crate_name, Span::call_site());
|
||||
quote!(
|
||||
#[doc(hidden)]
|
||||
extern crate #crate_name as proc_macro_runtime_interface;
|
||||
)
|
||||
},
|
||||
Err(e) => {
|
||||
let err = Error::new(Span::call_site(), e).to_compile_error();
|
||||
quote!( #err )
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates the access to the `sp-runtime-interface` crate.
|
||||
pub fn generate_crate_access() -> TokenStream {
|
||||
if env::var("CARGO_PKG_NAME").unwrap() == "sp-runtime-interface" {
|
||||
quote!(sp_runtime_interface)
|
||||
} else {
|
||||
quote!(proc_macro_runtime_interface)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create the exchangeable host function identifier for the given function name.
|
||||
pub fn create_exchangeable_host_function_ident(name: &Ident) -> Ident {
|
||||
Ident::new(&format!("host_{}", name), Span::call_site())
|
||||
}
|
||||
|
||||
/// Create the host function identifier for the given function name.
|
||||
pub fn create_host_function_ident(name: &Ident, version: u32, trait_name: &Ident) -> Ident {
|
||||
Ident::new(
|
||||
&format!("ext_{}_{}_version_{}", trait_name.to_string().to_snake_case(), name, version),
|
||||
Span::call_site(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Create the host function identifier for the given function name.
|
||||
pub fn create_function_ident_with_version(name: &Ident, version: u32) -> Ident {
|
||||
Ident::new(&format!("{}_version_{}", name, version), Span::call_site())
|
||||
}
|
||||
|
||||
/// Returns the function arguments of the given `Signature`, minus any `self` arguments.
|
||||
pub fn get_function_arguments(sig: &Signature) -> impl Iterator<Item = PatType> + '_ {
|
||||
sig.inputs
|
||||
.iter()
|
||||
.filter_map(|a| match a {
|
||||
FnArg::Receiver(_) => None,
|
||||
FnArg::Typed(pat_type) => Some(pat_type),
|
||||
})
|
||||
.enumerate()
|
||||
.map(|(i, arg)| {
|
||||
let mut res = arg.clone();
|
||||
if let Pat::Wild(wild) = &*arg.pat {
|
||||
let ident =
|
||||
Ident::new(&format!("__runtime_interface_generated_{}_", i), wild.span());
|
||||
|
||||
res.pat = Box::new(parse_quote!( #ident ))
|
||||
}
|
||||
|
||||
res
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the function argument names of the given `Signature`, minus any `self`.
|
||||
pub fn get_function_argument_names(sig: &Signature) -> impl Iterator<Item = Box<Pat>> + '_ {
|
||||
get_function_arguments(sig).map(|pt| pt.pat)
|
||||
}
|
||||
|
||||
/// Returns the function argument types of the given `Signature`, minus any `Self` type.
|
||||
pub fn get_function_argument_types(sig: &Signature) -> impl Iterator<Item = Box<Type>> + '_ {
|
||||
get_function_arguments(sig).map(|pt| pt.ty)
|
||||
}
|
||||
|
||||
/// Returns the function argument names and types, minus any `self`.
|
||||
pub fn get_function_argument_names_and_types(
|
||||
sig: &Signature,
|
||||
) -> impl Iterator<Item = (Box<Pat>, Box<Type>)> + '_ {
|
||||
get_function_arguments(sig).map(|pt| (pt.pat, pt.ty))
|
||||
}
|
||||
|
||||
/// Returns an iterator over all trait methods for the given trait definition.
|
||||
fn get_trait_methods(trait_def: &ItemTrait) -> impl Iterator<Item = &TraitItemFn> {
|
||||
trait_def.items.iter().filter_map(|i| match i {
|
||||
TraitItem::Fn(ref method) => Some(method),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
/// The version attribute that can be found above a runtime interface function.
|
||||
///
|
||||
/// Supports the following formats:
|
||||
/// - `#[version(1)]`
|
||||
/// - `#[version(1, register_only)]`
|
||||
///
|
||||
/// While this struct is only for parsing the inner parts inside the `()`.
|
||||
struct VersionAttribute {
|
||||
version: u32,
|
||||
register_only: Option<attributes::register_only>,
|
||||
}
|
||||
|
||||
impl VersionAttribute {
|
||||
/// Is this function version callable?
|
||||
fn is_callable(&self) -> bool {
|
||||
self.register_only.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for VersionAttribute {
|
||||
fn default() -> Self {
|
||||
Self { version: 1, register_only: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for VersionAttribute {
|
||||
fn parse(input: syn::parse::ParseStream) -> Result<Self> {
|
||||
let version: LitInt = input.parse()?;
|
||||
let register_only = if input.peek(token::Comma) {
|
||||
let _ = input.parse::<token::Comma>();
|
||||
Some(input.parse()?)
|
||||
} else {
|
||||
if !input.is_empty() {
|
||||
return Err(Error::new(input.span(), "Unexpected token, expected `,`."));
|
||||
}
|
||||
|
||||
None
|
||||
};
|
||||
|
||||
Ok(Self { version: version.base10_parse()?, register_only })
|
||||
}
|
||||
}
|
||||
|
||||
/// Return [`VersionAttribute`], if present.
|
||||
fn get_item_version(item: &TraitItemFn) -> Result<Option<VersionAttribute>> {
|
||||
item.attrs
|
||||
.iter()
|
||||
.find(|attr| attr.path().is_ident("version"))
|
||||
.map(|attr| attr.parse_args())
|
||||
.transpose()
|
||||
}
|
||||
|
||||
/// Returns all runtime interface members, with versions.
|
||||
pub fn get_runtime_interface(trait_def: &ItemTrait) -> Result<RuntimeInterface> {
|
||||
let mut functions: BTreeMap<syn::Ident, RuntimeInterfaceFunctionSet> = BTreeMap::new();
|
||||
|
||||
for item in get_trait_methods(trait_def) {
|
||||
let name = item.sig.ident.clone();
|
||||
let version = get_item_version(item)?.unwrap_or_default();
|
||||
|
||||
if version.version < 1 {
|
||||
return Err(Error::new(item.span(), "Version needs to be at least `1`."));
|
||||
}
|
||||
|
||||
match functions.entry(name.clone()) {
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert(RuntimeInterfaceFunctionSet::new(version, item)?);
|
||||
},
|
||||
Entry::Occupied(mut entry) => {
|
||||
entry.get_mut().add_version(version, item)?;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
for function in functions.values() {
|
||||
let mut next_expected = 1;
|
||||
for (version, item) in function.versions.iter() {
|
||||
if next_expected != *version {
|
||||
return Err(Error::new(
|
||||
item.span(),
|
||||
format!(
|
||||
"Unexpected version attribute: missing version '{}' for this function",
|
||||
next_expected
|
||||
),
|
||||
));
|
||||
}
|
||||
next_expected += 1;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(RuntimeInterface { items: functions })
|
||||
}
|
||||
|
||||
pub fn host_inner_arg_ty(ty: &syn::Type) -> syn::Type {
|
||||
let crate_ = generate_crate_access();
|
||||
syn::parse2::<syn::Type>(quote! { <#ty as #crate_::RIType>::Inner })
|
||||
.expect("parsing doesn't fail")
|
||||
}
|
||||
|
||||
pub fn pat_ty_to_host_inner(mut pat: syn::PatType) -> syn::PatType {
|
||||
pat.ty = Box::new(host_inner_arg_ty(&pat.ty));
|
||||
pat
|
||||
}
|
||||
|
||||
pub fn host_inner_return_ty(ty: &syn::ReturnType) -> syn::ReturnType {
|
||||
let crate_ = generate_crate_access();
|
||||
match ty {
|
||||
syn::ReturnType::Default => syn::ReturnType::Default,
|
||||
syn::ReturnType::Type(ref arrow, ref ty) =>
|
||||
syn::parse2::<syn::ReturnType>(quote! { #arrow <#ty as #crate_::RIType>::Inner })
|
||||
.expect("parsing doesn't fail"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unpack_inner_types_in_signature(sig: &mut syn::Signature) {
|
||||
sig.output = crate::utils::host_inner_return_ty(&sig.output);
|
||||
for arg in sig.inputs.iter_mut() {
|
||||
match arg {
|
||||
syn::FnArg::Typed(ref mut pat_ty) => {
|
||||
*pat_ty = crate::utils::pat_ty_to_host_inner(pat_ty.clone());
|
||||
},
|
||||
syn::FnArg::Receiver(..) => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user