diff --git a/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/host_function_interface.rs b/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/host_function_interface.rs index f3cdcf7fd54a99b82dca138f8b2b567c6f607593..77a9e56eecba5c91e3a36534f8f79da6ead30107 100644 --- a/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/host_function_interface.rs +++ b/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/host_function_interface.rs @@ -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")); let ffi_return_value = match method.sig.output { ReturnType::Default => quote!(), @@ -112,6 +113,7 @@ fn generate_extern_host_function( }; Ok(quote! { + #(#cfg_attrs)* #[doc = #doc_string] pub fn #function ( #( #args ),* ) #return_value { extern "C" { @@ -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] @@ -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! { @@ -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! { @@ -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(); @@ -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( @@ -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 { @@ -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),*| @@ -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`. diff --git a/substrate/primitives/runtime-interface/src/lib.rs b/substrate/primitives/runtime-interface/src/lib.rs index 058801522a4f0d258897359eb6249bbc0d0f19ff..1f1638880bb6c8655573ad260aac9cd40e6a5592 100644 --- a/substrate/primitives/runtime-interface/src/lib.rs +++ b/substrate/primitives/runtime-interface/src/lib.rs @@ -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: /// /// ``` @@ -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 { @@ -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> { @@ -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. @@ -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 @@ -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)` @@ -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() +/// } /// } /// ``` /// diff --git a/substrate/primitives/runtime-interface/tests/ui/no_feature_gated_method.rs b/substrate/primitives/runtime-interface/tests/ui/no_feature_gated_method.rs new file mode 100644 index 0000000000000000000000000000000000000000..51e45f178f0c5d1163fd6549dacb8f16ce61bd33 --- /dev/null +++ b/substrate/primitives/runtime-interface/tests/ui/no_feature_gated_method.rs @@ -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(); +} diff --git a/substrate/primitives/runtime-interface/tests/ui/no_feature_gated_method.stderr b/substrate/primitives/runtime-interface/tests/ui/no_feature_gated_method.stderr new file mode 100644 index 0000000000000000000000000000000000000000..e8accd62fc68dec8680e093e7b6c0f7b0fc21291 --- /dev/null +++ b/substrate/primitives/runtime-interface/tests/ui/no_feature_gated_method.stderr @@ -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` diff --git a/substrate/primitives/runtime-interface/tests/ui/no_versioned_conditional_build.rs b/substrate/primitives/runtime-interface/tests/ui/no_versioned_conditional_build.rs new file mode 100644 index 0000000000000000000000000000000000000000..a4a8a5804bee342f5c224f7ccf886f1cda53ad72 --- /dev/null +++ b/substrate/primitives/runtime-interface/tests/ui/no_versioned_conditional_build.rs @@ -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() {} diff --git a/substrate/primitives/runtime-interface/tests/ui/no_versioned_conditional_build.stderr b/substrate/primitives/runtime-interface/tests/ui/no_versioned_conditional_build.stderr new file mode 100644 index 0000000000000000000000000000000000000000..6f50e14278de56abf824522cc5e1d63d63419d7e --- /dev/null +++ b/substrate/primitives/runtime-interface/tests/ui/no_versioned_conditional_build.stderr @@ -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)] + | ^