feat: initialize Kurdistan SDK - independent fork of Polkadot SDK

This commit is contained in:
2025-12-13 15:44:15 +03:00
commit e4778b4576
6838 changed files with 1847450 additions and 0 deletions
@@ -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()
}
@@ -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(_)))
}
@@ -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)
}
@@ -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(..) => {},
}
}
}