Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ fn generate_extern_host_function(
method.sig.ident,
);
let return_value = &method.sig.output;
let cfg_attrs = method.attrs.iter().filter(|a| a.path().is_ident("cfg"));
Copy link
Contributor

@michalkucharczyk michalkucharczyk May 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this filtering is repeated multiple times, maybe it is worth putting it to some shared util?


let ffi_return_value = match method.sig.output {
ReturnType::Default => quote!(),
Expand All @@ -112,6 +113,7 @@ fn generate_extern_host_function(
};

Ok(quote! {
#(#cfg_attrs)*
#[doc = #doc_string]
pub fn #function ( #( #args ),* ) #return_value {
extern "C" {
Expand Down Expand Up @@ -143,8 +145,10 @@ fn generate_exchangeable_host_function(method: &TraitItemFn) -> Result<TokenStre
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(not(feature = "std"))]
#[allow(non_upper_case_globals)]
#[doc = #doc_string]
Expand All @@ -163,14 +167,15 @@ fn generate_host_functions_struct(
let crate_ = generate_crate_access();

let mut host_function_impls = Vec::new();
let mut host_function_names = 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, name, register_body) =
let (implementation, register_body, append_hf_body) =
generate_host_function_implementation(&trait_def.ident, method, version, is_wasm_only)?;
host_function_impls.push(implementation);
host_function_names.push(name);
register_bodies.push(register_body);
append_hf_bodies.push(append_hf_body);
}

Ok(quote! {
Expand All @@ -183,7 +188,9 @@ fn generate_host_functions_struct(
#[cfg(feature = "std")]
impl #crate_::sp_wasm_interface::HostFunctions for HostFunctions {
fn host_functions() -> Vec<&'static dyn #crate_::sp_wasm_interface::Function> {
vec![ #( &#host_function_names as &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! {
Expand All @@ -208,7 +215,7 @@ fn generate_host_function_implementation(
method: &RuntimeInterfaceFunction,
version: u32,
is_wasm_only: bool,
) -> Result<(TokenStream, Ident, TokenStream)> {
) -> 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();
Expand Down Expand Up @@ -323,10 +330,21 @@ fn generate_host_function_implementation(
});
}

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(feature = "std")]
struct #struct_name;

#(#cfg_attrs)*
#[cfg(feature = "std")]
impl #struct_name {
fn call(
Expand All @@ -341,6 +359,7 @@ fn generate_host_function_implementation(
}
}

#(#cfg_attrs)*
#[cfg(feature = "std")]
impl #crate_::sp_wasm_interface::Function for #struct_name {
fn name(&self) -> &str {
Expand Down Expand Up @@ -368,6 +387,7 @@ fn generate_host_function_implementation(
};

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),*|
Expand Down Expand Up @@ -399,7 +419,12 @@ fn generate_host_function_implementation(
)?;
};

Ok((implementation, struct_name, register_body))
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`.
Expand Down
76 changes: 61 additions & 15 deletions primitives/runtime-interface/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,10 +180,19 @@ pub use sp_std;
/// None => self.clear_storage(&[1, 2, 3, 4]),
/// }
/// }
///
/// /// A function can be gated behind a configuration (`cfg`) attribute.
/// /// To prevent ambiguity and confusion about what will be the final exposed host
/// /// functions list, conditionally compiled functions can't be versioned.
/// /// That is, conditionally compiled functions with `version`s greater than 1
/// /// are not allowed.
/// #[cfg(feature = "experimental-function")]
/// fn gated_call(data: &[u8]) -> Vec<u8> {
/// [42].to_vec()
/// }
/// }
/// ```
///
///
/// The given example will generate roughly the following code for native:
///
/// ```
Expand All @@ -197,6 +206,8 @@ pub use sp_std;
/// fn call_version_2(data: &[u8]) -> Vec<u8>;
/// fn call_version_3(data: &[u8]) -> Vec<u8>;
/// fn set_or_clear_version_1(&mut self, optional: Option<Vec<u8>>);
/// #[cfg(feature = "experimental-function")]
/// fn gated_call_version_1(data: &[u8]) -> Vec<u8>;
/// }
///
/// impl Interface for &mut dyn sp_externalities::Externalities {
Expand All @@ -209,6 +220,8 @@ pub use sp_std;
/// None => self.clear_storage(&[1, 2, 3, 4]),
/// }
/// }
/// #[cfg(feature = "experimental-function")]
/// fn gated_call_version_1(data: &[u8]) -> Vec<u8> { [42].to_vec() }
/// }
///
/// pub fn call(data: &[u8]) -> Vec<u8> {
Expand Down Expand Up @@ -237,6 +250,16 @@ pub use sp_std;
/// .expect("`set_or_clear` called outside of an Externalities-provided environment.")
/// }
///
/// #[cfg(feature = "experimental-function")]
/// pub fn gated_call(data: &[u8]) -> Vec<u8> {
/// gated_call_version_1(data)
/// }
///
/// #[cfg(feature = "experimental-function")]
/// fn gated_call_version_1(data: &[u8]) -> Vec<u8> {
/// <&mut dyn sp_externalities::Externalities as Interface>::gated_call_version_1(data)
/// }
///
/// /// This type implements the `HostFunctions` trait (from `sp-wasm-interface`) and
/// /// provides the host implementation for the wasm side. The host implementation converts the
/// /// arguments from wasm to native and calls the corresponding native function.
Expand All @@ -247,28 +270,43 @@ pub use sp_std;
/// }
/// ```
///
///
/// The given example will generate roughly the following code for wasm:
///
/// ```
/// mod interface {
/// mod extern_host_functions_impls {
/// extern "C" {
/// /// Every function is exported as `ext_TRAIT_NAME_FUNCTION_NAME_version_VERSION`.
/// ///
/// /// `TRAIT_NAME` is converted into snake case.
/// ///
/// /// The type for each argument of the exported function depends on
/// /// `<ARGUMENT_TYPE as RIType>::FFIType`.
/// ///
/// /// `data` holds the pointer and the length to the `[u8]` slice.
/// pub fn ext_Interface_call_version_1(data: u64) -> u64;
/// /// `optional` holds the pointer and the length of the encoded value.
/// pub fn ext_Interface_set_or_clear_version_1(optional: u64);
/// /// Every function is exported by the native code as `ext_FUNCTION_NAME_version_VERSION`.
/// ///
/// /// The type for each argument of the exported function depends on
/// /// `<ARGUMENT_TYPE as RIType>::FFIType`.
/// ///
/// /// `key` holds the pointer and the length to the `data` slice.
/// pub fn call(data: &[u8]) -> Vec<u8> {
/// extern "C" { pub fn ext_call_version_2(key: u64); }
/// // Should call into extenal `ext_call_version_2(<[u8] as IntoFFIValue>::into_ffi_value(key))`
/// // But this is too much to replicate in a doc test so here we just return a dummy vector.
/// // Note that we jump into the latest version not marked as `register_only` (i.e. version 2).
/// Vec::new()
/// }
///
/// /// `key` holds the pointer and the length of the `option` value.
/// pub fn set_or_clear(option: Option<Vec<u8>>) {
/// extern "C" { pub fn ext_set_or_clear_version_1(key: u64); }
/// // Same as above
/// }
///
/// /// `key` holds the pointer and the length to the `data` slice.
/// #[cfg(feature = "experimental-function")]
/// pub fn gated_call(data: &[u8]) -> Vec<u8> {
/// extern "C" { pub fn ext_gated_call_version_1(key: u64); }
/// /// Same as above
/// Vec::new()
/// }
/// }
///
/// /// The type is actually `ExchangeableFunction` (from `sp-runtime-interface`).
/// /// The type is actually `ExchangeableFunction` (from `sp-runtime-interface`) and
/// /// by default this is initialized to jump into the corresponding function in
/// /// `extern_host_functions_impls`.
/// ///
/// /// This can be used to replace the implementation of the `call` function.
/// /// Instead of calling into the host, the callee will automatically call the other
Expand All @@ -279,6 +317,8 @@ pub use sp_std;
/// /// `host_call.replace_implementation(some_other_impl)`
/// pub static host_call: () = ();
/// pub static host_set_or_clear: () = ();
/// #[cfg(feature = "experimental-feature")]
/// pub static gated_call: () = ();
///
/// pub fn call(data: &[u8]) -> Vec<u8> {
/// // This is the actual call: `host_call.get()(data)`
Expand All @@ -291,6 +331,12 @@ pub use sp_std;
/// pub fn set_or_clear(optional: Option<Vec<u8>>) {
/// // Same as above
/// }
///
/// #[cfg(feature = "experimental-feature")]
/// pub fn gated_call(data: &[u8]) -> Vec<u8> {
/// // Same as above
/// Vec::new()
/// }
/// }
/// ```
///
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use sp_runtime_interface::runtime_interface;

#[runtime_interface]
trait Test {
fn foo() {}

#[cfg(feature = "bar-feature")]
fn bar() {}

#[cfg(not(feature = "bar-feature"))]
fn qux() {}
}

fn main() {
test::foo();
test::bar();
test::qux();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
error[E0425]: cannot find function `bar` in module `test`
--> tests/ui/no_feature_gated_method.rs:16:8
|
16 | test::bar();
| ^^^ not found in `test`
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use sp_runtime_interface::runtime_interface;

#[runtime_interface]
trait Test {
fn foo() {}

#[version(2)]
#[cfg(feature = "foo-feature")]
fn foo() {}
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
error: Conditional compilation is not supported for versioned functions
--> tests/ui/no_versioned_conditional_build.rs:7:2
|
7 | #[version(2)]
| ^