Skip to content

Commit 74415c3

Browse files
davxykoute
authored andcommitted
Support for cfg attributes in host functions definitions (paritytech#14189)
* Support cfg attribute in host functions definitions * Added test to check feature gated methods are not included * Versioned conditional compiled host function are forbidden * Improve runtime-interface macro docs * Fix doc * Apply review suggestion Co-authored-by: Koute <[email protected]> * Better error message * Rust fmt * More refinements to the docs --------- Co-authored-by: Koute <[email protected]>
1 parent 7ae1aaa commit 74415c3

File tree

6 files changed

+132
-21
lines changed

6 files changed

+132
-21
lines changed

primitives/runtime-interface/proc-macro/src/runtime_interface/host_function_interface.rs

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ fn generate_extern_host_function(
9696
method.sig.ident,
9797
);
9898
let return_value = &method.sig.output;
99+
let cfg_attrs = method.attrs.iter().filter(|a| a.path().is_ident("cfg"));
99100

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

114115
Ok(quote! {
116+
#(#cfg_attrs)*
115117
#[doc = #doc_string]
116118
pub fn #function ( #( #args ),* ) #return_value {
117119
extern "C" {
@@ -143,8 +145,10 @@ fn generate_exchangeable_host_function(method: &TraitItemFn) -> Result<TokenStre
143145
let exchangeable_function = create_exchangeable_host_function_ident(&method.sig.ident);
144146
let doc_string = format!(" Exchangeable host function used by [`{}`].", method.sig.ident);
145147
let output = &method.sig.output;
148+
let cfg_attrs = method.attrs.iter().filter(|a| a.path().is_ident("cfg"));
146149

147150
Ok(quote! {
151+
#(#cfg_attrs)*
148152
#[cfg(not(feature = "std"))]
149153
#[allow(non_upper_case_globals)]
150154
#[doc = #doc_string]
@@ -163,14 +167,15 @@ fn generate_host_functions_struct(
163167
let crate_ = generate_crate_access();
164168

165169
let mut host_function_impls = Vec::new();
166-
let mut host_function_names = Vec::new();
167170
let mut register_bodies = Vec::new();
171+
let mut append_hf_bodies = Vec::new();
172+
168173
for (version, method) in get_runtime_interface(trait_def)?.all_versions() {
169-
let (implementation, name, register_body) =
174+
let (implementation, register_body, append_hf_body) =
170175
generate_host_function_implementation(&trait_def.ident, method, version, is_wasm_only)?;
171176
host_function_impls.push(implementation);
172-
host_function_names.push(name);
173177
register_bodies.push(register_body);
178+
append_hf_bodies.push(append_hf_body);
174179
}
175180

176181
Ok(quote! {
@@ -183,7 +188,9 @@ fn generate_host_functions_struct(
183188
#[cfg(feature = "std")]
184189
impl #crate_::sp_wasm_interface::HostFunctions for HostFunctions {
185190
fn host_functions() -> Vec<&'static dyn #crate_::sp_wasm_interface::Function> {
186-
vec![ #( &#host_function_names as &dyn #crate_::sp_wasm_interface::Function ),* ]
191+
let mut host_functions_list = Vec::new();
192+
#(#append_hf_bodies)*
193+
host_functions_list
187194
}
188195

189196
#crate_::sp_wasm_interface::if_wasmtime_is_enabled! {
@@ -208,7 +215,7 @@ fn generate_host_function_implementation(
208215
method: &RuntimeInterfaceFunction,
209216
version: u32,
210217
is_wasm_only: bool,
211-
) -> Result<(TokenStream, Ident, TokenStream)> {
218+
) -> Result<(TokenStream, TokenStream, TokenStream)> {
212219
let name = create_host_function_ident(&method.sig.ident, version, trait_name).to_string();
213220
let struct_name = Ident::new(&name.to_pascal_case(), Span::call_site());
214221
let crate_ = generate_crate_access();
@@ -323,10 +330,21 @@ fn generate_host_function_implementation(
323330
});
324331
}
325332

333+
let cfg_attrs: Vec<_> =
334+
method.attrs.iter().filter(|a| a.path().is_ident("cfg")).cloned().collect();
335+
if version > 1 && !cfg_attrs.is_empty() {
336+
return Err(Error::new(
337+
method.span(),
338+
"Conditional compilation is not supported for versioned functions",
339+
))
340+
}
341+
326342
let implementation = quote! {
343+
#(#cfg_attrs)*
327344
#[cfg(feature = "std")]
328345
struct #struct_name;
329346

347+
#(#cfg_attrs)*
330348
#[cfg(feature = "std")]
331349
impl #struct_name {
332350
fn call(
@@ -341,6 +359,7 @@ fn generate_host_function_implementation(
341359
}
342360
}
343361

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

370389
let register_body = quote! {
390+
#(#cfg_attrs)*
371391
registry.register_static(
372392
#crate_::sp_wasm_interface::Function::name(&#struct_name),
373393
|mut caller: #crate_::sp_wasm_interface::wasmtime::Caller<T::State>, #(#ffi_args_prototype),*|
@@ -399,7 +419,12 @@ fn generate_host_function_implementation(
399419
)?;
400420
};
401421

402-
Ok((implementation, struct_name, register_body))
422+
let append_hf_body = quote! {
423+
#(#cfg_attrs)*
424+
host_functions_list.push(&#struct_name as &dyn #crate_::sp_wasm_interface::Function);
425+
};
426+
427+
Ok((implementation, register_body, append_hf_body))
403428
}
404429

405430
/// Generate the `wasm_interface::Signature` for the given host function `sig`.

primitives/runtime-interface/src/lib.rs

Lines changed: 61 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -180,10 +180,19 @@ pub use sp_std;
180180
/// None => self.clear_storage(&[1, 2, 3, 4]),
181181
/// }
182182
/// }
183+
///
184+
/// /// A function can be gated behind a configuration (`cfg`) attribute.
185+
/// /// To prevent ambiguity and confusion about what will be the final exposed host
186+
/// /// functions list, conditionally compiled functions can't be versioned.
187+
/// /// That is, conditionally compiled functions with `version`s greater than 1
188+
/// /// are not allowed.
189+
/// #[cfg(feature = "experimental-function")]
190+
/// fn gated_call(data: &[u8]) -> Vec<u8> {
191+
/// [42].to_vec()
192+
/// }
183193
/// }
184194
/// ```
185195
///
186-
///
187196
/// The given example will generate roughly the following code for native:
188197
///
189198
/// ```
@@ -197,6 +206,8 @@ pub use sp_std;
197206
/// fn call_version_2(data: &[u8]) -> Vec<u8>;
198207
/// fn call_version_3(data: &[u8]) -> Vec<u8>;
199208
/// fn set_or_clear_version_1(&mut self, optional: Option<Vec<u8>>);
209+
/// #[cfg(feature = "experimental-function")]
210+
/// fn gated_call_version_1(data: &[u8]) -> Vec<u8>;
200211
/// }
201212
///
202213
/// impl Interface for &mut dyn sp_externalities::Externalities {
@@ -209,6 +220,8 @@ pub use sp_std;
209220
/// None => self.clear_storage(&[1, 2, 3, 4]),
210221
/// }
211222
/// }
223+
/// #[cfg(feature = "experimental-function")]
224+
/// fn gated_call_version_1(data: &[u8]) -> Vec<u8> { [42].to_vec() }
212225
/// }
213226
///
214227
/// pub fn call(data: &[u8]) -> Vec<u8> {
@@ -237,6 +250,16 @@ pub use sp_std;
237250
/// .expect("`set_or_clear` called outside of an Externalities-provided environment.")
238251
/// }
239252
///
253+
/// #[cfg(feature = "experimental-function")]
254+
/// pub fn gated_call(data: &[u8]) -> Vec<u8> {
255+
/// gated_call_version_1(data)
256+
/// }
257+
///
258+
/// #[cfg(feature = "experimental-function")]
259+
/// fn gated_call_version_1(data: &[u8]) -> Vec<u8> {
260+
/// <&mut dyn sp_externalities::Externalities as Interface>::gated_call_version_1(data)
261+
/// }
262+
///
240263
/// /// This type implements the `HostFunctions` trait (from `sp-wasm-interface`) and
241264
/// /// provides the host implementation for the wasm side. The host implementation converts the
242265
/// /// arguments from wasm to native and calls the corresponding native function.
@@ -247,28 +270,43 @@ pub use sp_std;
247270
/// }
248271
/// ```
249272
///
250-
///
251273
/// The given example will generate roughly the following code for wasm:
252274
///
253275
/// ```
254276
/// mod interface {
255277
/// mod extern_host_functions_impls {
256-
/// extern "C" {
257-
/// /// Every function is exported as `ext_TRAIT_NAME_FUNCTION_NAME_version_VERSION`.
258-
/// ///
259-
/// /// `TRAIT_NAME` is converted into snake case.
260-
/// ///
261-
/// /// The type for each argument of the exported function depends on
262-
/// /// `<ARGUMENT_TYPE as RIType>::FFIType`.
263-
/// ///
264-
/// /// `data` holds the pointer and the length to the `[u8]` slice.
265-
/// pub fn ext_Interface_call_version_1(data: u64) -> u64;
266-
/// /// `optional` holds the pointer and the length of the encoded value.
267-
/// pub fn ext_Interface_set_or_clear_version_1(optional: u64);
278+
/// /// Every function is exported by the native code as `ext_FUNCTION_NAME_version_VERSION`.
279+
/// ///
280+
/// /// The type for each argument of the exported function depends on
281+
/// /// `<ARGUMENT_TYPE as RIType>::FFIType`.
282+
/// ///
283+
/// /// `key` holds the pointer and the length to the `data` slice.
284+
/// pub fn call(data: &[u8]) -> Vec<u8> {
285+
/// extern "C" { pub fn ext_call_version_2(key: u64); }
286+
/// // Should call into extenal `ext_call_version_2(<[u8] as IntoFFIValue>::into_ffi_value(key))`
287+
/// // But this is too much to replicate in a doc test so here we just return a dummy vector.
288+
/// // Note that we jump into the latest version not marked as `register_only` (i.e. version 2).
289+
/// Vec::new()
290+
/// }
291+
///
292+
/// /// `key` holds the pointer and the length of the `option` value.
293+
/// pub fn set_or_clear(option: Option<Vec<u8>>) {
294+
/// extern "C" { pub fn ext_set_or_clear_version_1(key: u64); }
295+
/// // Same as above
296+
/// }
297+
///
298+
/// /// `key` holds the pointer and the length to the `data` slice.
299+
/// #[cfg(feature = "experimental-function")]
300+
/// pub fn gated_call(data: &[u8]) -> Vec<u8> {
301+
/// extern "C" { pub fn ext_gated_call_version_1(key: u64); }
302+
/// /// Same as above
303+
/// Vec::new()
268304
/// }
269305
/// }
270306
///
271-
/// /// The type is actually `ExchangeableFunction` (from `sp-runtime-interface`).
307+
/// /// The type is actually `ExchangeableFunction` (from `sp-runtime-interface`) and
308+
/// /// by default this is initialized to jump into the corresponding function in
309+
/// /// `extern_host_functions_impls`.
272310
/// ///
273311
/// /// This can be used to replace the implementation of the `call` function.
274312
/// /// Instead of calling into the host, the callee will automatically call the other
@@ -279,6 +317,8 @@ pub use sp_std;
279317
/// /// `host_call.replace_implementation(some_other_impl)`
280318
/// pub static host_call: () = ();
281319
/// pub static host_set_or_clear: () = ();
320+
/// #[cfg(feature = "experimental-feature")]
321+
/// pub static gated_call: () = ();
282322
///
283323
/// pub fn call(data: &[u8]) -> Vec<u8> {
284324
/// // This is the actual call: `host_call.get()(data)`
@@ -291,6 +331,12 @@ pub use sp_std;
291331
/// pub fn set_or_clear(optional: Option<Vec<u8>>) {
292332
/// // Same as above
293333
/// }
334+
///
335+
/// #[cfg(feature = "experimental-feature")]
336+
/// pub fn gated_call(data: &[u8]) -> Vec<u8> {
337+
/// // Same as above
338+
/// Vec::new()
339+
/// }
294340
/// }
295341
/// ```
296342
///
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
use sp_runtime_interface::runtime_interface;
2+
3+
#[runtime_interface]
4+
trait Test {
5+
fn foo() {}
6+
7+
#[cfg(feature = "bar-feature")]
8+
fn bar() {}
9+
10+
#[cfg(not(feature = "bar-feature"))]
11+
fn qux() {}
12+
}
13+
14+
fn main() {
15+
test::foo();
16+
test::bar();
17+
test::qux();
18+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
error[E0425]: cannot find function `bar` in module `test`
2+
--> tests/ui/no_feature_gated_method.rs:16:8
3+
|
4+
16 | test::bar();
5+
| ^^^ not found in `test`
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
use sp_runtime_interface::runtime_interface;
2+
3+
#[runtime_interface]
4+
trait Test {
5+
fn foo() {}
6+
7+
#[version(2)]
8+
#[cfg(feature = "foo-feature")]
9+
fn foo() {}
10+
}
11+
12+
fn main() {}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
error: Conditional compilation is not supported for versioned functions
2+
--> tests/ui/no_versioned_conditional_build.rs:7:2
3+
|
4+
7 | #[version(2)]
5+
| ^

0 commit comments

Comments
 (0)