mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-22 16:11:08 +00:00
generation of real benchmark functions for benchmarking v2 (#13224)
* function generation with _name working, need to modify signature * WIP * support custom BenchmarkResult<T> type * full support for BenchmarkResult<T> on benchmark function defs * support () return type for benchmark function defs that don't use ? * uncomment * fix where clause handling * fix benchmark function call bodies * proper parsing of return type * add UI tests for bad return type * fix detection of missing last_stmt with defined return type * UI tests covering missing last_stmt * properly detect and complain about empty benchmark function defs * fix missing Comma in Result<T, BenchmarkError> parsing + test * add additional UI test * allow complex path for BenchmarkResult and BenchmarkError in fn defs * add UI tests covering complex path for BenchmarkResult, BenchmarkError * retain doc comments and attributes * also add attributes to struct * add docs for benchmark function definition support * fix imports on benchmark example * fix issue with unused variables in extrinsic call fn def * fix up docs * remove support for v2::BenchmarkResult because it was confusing * fix typo * remove ability to use custom T for Result<T, BenchmarkError> in v2 * use missing call error instead of empty_fn() * remove unneeded match statement * Add a proper QED Co-authored-by: Keith Yeung <kungfukeith11@gmail.com> * fix other QED Co-authored-by: Keith Yeung <kungfukeith11@gmail.com> * cargo fmt * add an explicit error for non TypePath as return type * tweak error warning and add a UI test for non TypePath return * remove comment * add docs about T and I generic params * improve docs referring to section "below" * pull out return type checking logic into its own function * pull out params parsing into its own function * pull out call_def parsing into its own function * add doc comment for missing_call() * replace spaces with tabs * add a result-based example to the benchmarking examples --------- Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>
This commit is contained in:
@@ -225,7 +225,7 @@ mod benchmarks {
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn force_unreserve() {
|
||||
fn force_unreserve() -> Result<(), BenchmarkError> {
|
||||
let user: T::AccountId = account("user", 0, SEED);
|
||||
let user_lookup = T::Lookup::unlookup(user.clone());
|
||||
|
||||
@@ -244,6 +244,8 @@ mod benchmarks {
|
||||
|
||||
assert!(Balances::<T, I>::reserved_balance(&user).is_zero());
|
||||
assert_eq!(Balances::<T, I>::free_balance(&user), balance);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite! {
|
||||
|
||||
@@ -114,21 +114,22 @@ pub use v1::*;
|
||||
/// Within a `#[benchmarks]` or `#[instance_benchmarks]` module, you can define individual
|
||||
/// benchmarks using the `#[benchmark]` attribute, as shown in the example above.
|
||||
///
|
||||
/// The `#[benchmark]` attribute expects a function definition with a blank return type and
|
||||
/// zero or more arguments whose names are valid
|
||||
/// [BenchmarkParameter](`crate::BenchmarkParameter`) parameters, such as `x`, `y`, `a`, `b`,
|
||||
/// etc., and whose param types must implement [ParamRange](`v2::ParamRange`). At the moment
|
||||
/// the only valid type that implements [ParamRange](`v2::ParamRange`) is
|
||||
/// [Linear](`v2::Linear`).
|
||||
/// The `#[benchmark]` attribute expects a function definition with a blank return type (or a
|
||||
/// return type compatible with `Result<(), BenchmarkError>`, as discussed below) and zero or
|
||||
/// more arguments whose names are valid [BenchmarkParameter](`crate::BenchmarkParameter`)
|
||||
/// parameters, such as `x`, `y`, `a`, `b`, etc., and whose param types must implement
|
||||
/// [ParamRange](`v2::ParamRange`). At the moment the only valid type that implements
|
||||
/// [ParamRange](`v2::ParamRange`) is [Linear](`v2::Linear`).
|
||||
///
|
||||
/// The valid syntax for defining a [Linear](`v2::Linear`)is `Linear<A, B>` where `A`, and `B`
|
||||
/// The valid syntax for defining a [Linear](`v2::Linear`) is `Linear<A, B>` where `A`, and `B`
|
||||
/// are valid integer literals (that fit in a `u32`), such that `B` >= `A`.
|
||||
///
|
||||
/// Note that the benchmark function definition does not actually expand as a function
|
||||
/// definition, but rather is used to automatically create a number of impls and structs
|
||||
/// required by the benchmarking engine. For this reason, the visibility of the function
|
||||
/// definition as well as the return type are not used for any purpose and are discarded by the
|
||||
/// expansion code.
|
||||
/// Anywhere within a benchmark function you may use the generic `T: Config` parameter as well
|
||||
/// as `I` in the case of an `#[instance_benchmarks]` module. You should not add these to the
|
||||
/// function signature as this will be handled automatically for you based on whether this is a
|
||||
/// `#[benchmarks]` or `#[instance_benchmarks]` module and whatever [where clause](#where-clause)
|
||||
/// you have defined for the the module. You should not manually add any generics to the
|
||||
/// signature of your benchmark function.
|
||||
///
|
||||
/// Also note that the `// setup code` and `// verification code` comments shown above are not
|
||||
/// required and are included simply for demonstration purposes.
|
||||
@@ -189,10 +190,10 @@ pub use v1::*;
|
||||
///
|
||||
/// #### `skip_meta`
|
||||
///
|
||||
/// Specifies that the benchmarking framework should not analyze the storage keys that
|
||||
/// Specifies that the benchmarking framework should not analyze the storage keys that the
|
||||
/// benchmarked code read or wrote. This useful to suppress the prints in the form of unknown
|
||||
/// 0x… in case a storage key that does not have metadata. Note that this skips the analysis
|
||||
/// of all accesses, not just ones without metadata.
|
||||
/// 0x… in case a storage key that does not have metadata. Note that this skips the analysis of
|
||||
/// all accesses, not just ones without metadata.
|
||||
///
|
||||
/// ## Where Clause
|
||||
///
|
||||
@@ -231,6 +232,79 @@ pub use v1::*;
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## Benchmark Function Generation
|
||||
///
|
||||
/// The benchmark function definition that you provide is used to automatically create a number
|
||||
/// of impls and structs required by the benchmarking engine. Additionally, a benchmark
|
||||
/// function is also generated that resembles the function definition you provide, with a few
|
||||
/// modifications:
|
||||
/// 1. The function name is transformed from i.e. `original_name` to `_original_name` so as not
|
||||
/// to collide with the struct `original_name` that is created for some of the benchmarking
|
||||
/// engine impls.
|
||||
/// 2. Appropriate `T: Config` and `I` (if this is an instance benchmark) generics are added to
|
||||
/// the function automatically during expansion, so you should not add these manually on
|
||||
/// your function definition (but you may make use of `T` and `I` anywhere within your
|
||||
/// benchmark function, in any of the three sections (setup, call, verification).
|
||||
/// 3. Arguments such as `u: Linear<10, 100>` are converted to `u: u32` to make the function
|
||||
/// directly callable.
|
||||
/// 4. A `verify: bool` param is added as the last argument. Specifying `true` will result in
|
||||
/// the verification section of your function executing, while a value of `false` will skip
|
||||
/// verification.
|
||||
/// 5. If you specify a return type on the function definition, it must conform to the [rules
|
||||
/// below](#support-for-result-benchmarkerror-and-the--operator), and the last statement of
|
||||
/// the function definition must resolve to something compatible with `Result<(),
|
||||
/// BenchmarkError>`.
|
||||
///
|
||||
/// The reason we generate an actual function as part of the expansion is to allow the compiler
|
||||
/// to enforce several constraints that would otherwise be difficult to enforce and to reduce
|
||||
/// developer confusion (especially regarding the use of the `?` operator, as covered below).
|
||||
///
|
||||
/// Note that any attributes, comments, and doc comments attached to your benchmark function
|
||||
/// definition are also carried over onto the resulting benchmark function and the struct for
|
||||
/// that benchmark. As a result you should be careful about what attributes you attach here as
|
||||
/// they will be replicated in multiple places.
|
||||
///
|
||||
/// ### Support for `Result<(), BenchmarkError>` and the `?` operator
|
||||
///
|
||||
/// You may optionally specify `Result<(), BenchmarkError>` as the return type of your
|
||||
/// benchmark function definition. If you do so, you must return a compatible `Result<(),
|
||||
/// BenchmarkError>` as the *last statement* of your benchmark function definition. You may
|
||||
/// also use the `?` operator throughout your benchmark function definition if you choose to
|
||||
/// follow this route. See the example below:
|
||||
///
|
||||
/// ```ignore
|
||||
/// #![cfg(feature = "runtime-benchmarks")]
|
||||
///
|
||||
/// use super::{mock_helpers::*, Pallet as MyPallet};
|
||||
/// use frame_benchmarking::v2::*;
|
||||
///
|
||||
/// #[benchmarks]
|
||||
/// mod benchmarks {
|
||||
/// use super::*;
|
||||
///
|
||||
/// #[benchmark]
|
||||
/// fn bench_name(x: Linear<5, 25>) -> Result<(), BenchmarkError> {
|
||||
/// // setup code
|
||||
/// let z = x + 4;
|
||||
/// let caller = whitelisted_caller();
|
||||
///
|
||||
/// // note we can make use of the ? operator here because of the return type
|
||||
/// something(z)?;
|
||||
///
|
||||
/// #[extrinsic_call]
|
||||
/// extrinsic_name(SystemOrigin::Signed(caller), other, arguments);
|
||||
///
|
||||
/// // verification code
|
||||
/// assert_eq!(MyPallet::<T>::my_var(), z);
|
||||
///
|
||||
/// // we must return a valid `Result<(), BenchmarkError>` as the last line of our benchmark
|
||||
/// // function definition. This line is not included as part of the verification code that
|
||||
/// // appears above it.
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub mod v2 {
|
||||
pub use super::*;
|
||||
pub use frame_support_procedural::{
|
||||
@@ -240,7 +314,7 @@ pub mod v2 {
|
||||
// Used in #[benchmark] implementation to ensure that benchmark function arguments
|
||||
// implement [`ParamRange`].
|
||||
#[doc(hidden)]
|
||||
pub use static_assertions::assert_impl_all;
|
||||
pub use static_assertions::{assert_impl_all, assert_type_eq_all};
|
||||
|
||||
/// Used by the new benchmarking code to specify that a benchmarking variable is linear
|
||||
/// over some specified range, i.e. `Linear<0, 1_000>` means that the corresponding variable
|
||||
|
||||
@@ -21,11 +21,7 @@
|
||||
#![cfg(feature = "runtime-benchmarks")]
|
||||
|
||||
use crate::*;
|
||||
use frame_benchmarking::v1::{
|
||||
impl_benchmark_test_suite,
|
||||
v2::{benchmarks, Linear},
|
||||
whitelisted_caller,
|
||||
};
|
||||
use frame_benchmarking::v2::*;
|
||||
use frame_system::RawOrigin;
|
||||
|
||||
// To actually run this benchmark on pallet-example-basic, we need to put this pallet into the
|
||||
@@ -55,19 +51,31 @@ mod benchmarks {
|
||||
assert_eq!(Pallet::<T>::dummy(), Some(value))
|
||||
}
|
||||
|
||||
// An example method that returns a Result that can be called within a benchmark
|
||||
fn example_result_method() -> Result<(), BenchmarkError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// This will measure the execution time of `accumulate_dummy`.
|
||||
// The benchmark execution phase is shorthanded. When the name of the benchmark case is the same
|
||||
// as the extrinsic call. `_(...)` is used to represent the extrinsic name.
|
||||
// The benchmark verification phase is omitted.
|
||||
#[benchmark]
|
||||
fn accumulate_dummy() {
|
||||
fn accumulate_dummy() -> Result<(), BenchmarkError> {
|
||||
let value = 1000u32.into();
|
||||
// The caller account is whitelisted for DB reads/write by the benchmarking macro.
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
|
||||
// an example of calling something result-based within a benchmark using the ? operator
|
||||
// this necessitates specifying the `Result<(), BenchmarkError>` return type
|
||||
example_result_method()?;
|
||||
|
||||
// You can use `_` if the name of the Call matches the benchmark name.
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(caller), value);
|
||||
|
||||
// need this to be compatible with the return type
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// You can write helper functions in here since its a normal Rust module.
|
||||
|
||||
@@ -25,11 +25,13 @@ use quote::{quote, quote_spanned, ToTokens};
|
||||
use syn::{
|
||||
parenthesized,
|
||||
parse::{Nothing, ParseStream},
|
||||
parse_quote,
|
||||
punctuated::Punctuated,
|
||||
spanned::Spanned,
|
||||
token::{Colon2, Comma, Gt, Lt, Paren},
|
||||
Attribute, Error, Expr, ExprBlock, ExprCall, ExprPath, FnArg, Item, ItemFn, ItemMod, LitInt,
|
||||
Pat, Path, PathArguments, PathSegment, Result, Stmt, Token, Type, WhereClause,
|
||||
Pat, Path, PathArguments, PathSegment, Result, ReturnType, Signature, Stmt, Token, Type,
|
||||
TypePath, Visibility, WhereClause,
|
||||
};
|
||||
|
||||
mod keywords {
|
||||
@@ -41,6 +43,8 @@ mod keywords {
|
||||
custom_keyword!(extra);
|
||||
custom_keyword!(extrinsic_call);
|
||||
custom_keyword!(skip_meta);
|
||||
custom_keyword!(BenchmarkError);
|
||||
custom_keyword!(Result);
|
||||
}
|
||||
|
||||
/// This represents the raw parsed data for a param definition such as `x: Linear<10, 20>`.
|
||||
@@ -145,62 +149,121 @@ struct BenchmarkDef {
|
||||
setup_stmts: Vec<Stmt>,
|
||||
call_def: BenchmarkCallDef,
|
||||
verify_stmts: Vec<Stmt>,
|
||||
last_stmt: Option<Stmt>,
|
||||
extra: bool,
|
||||
skip_meta: bool,
|
||||
fn_sig: Signature,
|
||||
fn_vis: Visibility,
|
||||
fn_attrs: Vec<Attribute>,
|
||||
}
|
||||
|
||||
impl BenchmarkDef {
|
||||
/// Constructs a [`BenchmarkDef`] by traversing an existing [`ItemFn`] node.
|
||||
pub fn from(item_fn: &ItemFn, extra: bool, skip_meta: bool) -> Result<BenchmarkDef> {
|
||||
let mut params: Vec<ParamDef> = Vec::new();
|
||||
/// used to parse something compatible with `Result<T, E>`
|
||||
#[derive(Parse)]
|
||||
struct ResultDef {
|
||||
_result_kw: keywords::Result,
|
||||
_lt: Token![<],
|
||||
unit: Type,
|
||||
_comma: Comma,
|
||||
e_type: TypePath,
|
||||
_gt: Token![>],
|
||||
}
|
||||
|
||||
// parse params such as "x: Linear<0, 1>"
|
||||
for arg in &item_fn.sig.inputs {
|
||||
let invalid_param = |span| {
|
||||
return Err(Error::new(span, "Invalid benchmark function param. A valid example would be `x: Linear<5, 10>`.", ))
|
||||
};
|
||||
|
||||
let FnArg::Typed(arg) = arg else { return invalid_param(arg.span()) };
|
||||
let Pat::Ident(ident) = &*arg.pat else { return invalid_param(arg.span()) };
|
||||
|
||||
// check param name
|
||||
let var_span = ident.span();
|
||||
let invalid_param_name = || {
|
||||
/// Ensures that `ReturnType` is a `Result<(), BenchmarkError>`, if specified
|
||||
fn ensure_valid_return_type(item_fn: &ItemFn) -> Result<()> {
|
||||
if let ReturnType::Type(_, typ) = &item_fn.sig.output {
|
||||
let non_unit = |span| return Err(Error::new(span, "expected `()`"));
|
||||
let Type::Path(TypePath { path, qself: _ }) = &**typ else {
|
||||
return Err(Error::new(
|
||||
typ.span(),
|
||||
"Only `Result<(), BenchmarkError>` or a blank return type is allowed on benchmark function definitions",
|
||||
))
|
||||
};
|
||||
let seg = path
|
||||
.segments
|
||||
.last()
|
||||
.expect("to be parsed as a TypePath, it must have at least one segment; qed");
|
||||
let res: ResultDef = syn::parse2(seg.to_token_stream())?;
|
||||
// ensure T in Result<T, E> is ()
|
||||
let Type::Tuple(tup) = res.unit else { return non_unit(res.unit.span()) };
|
||||
if !tup.elems.is_empty() {
|
||||
return non_unit(tup.span())
|
||||
}
|
||||
let TypePath { path, qself: _ } = res.e_type;
|
||||
let seg = path
|
||||
.segments
|
||||
.last()
|
||||
.expect("to be parsed as a TypePath, it must have at least one segment; qed");
|
||||
syn::parse2::<keywords::BenchmarkError>(seg.to_token_stream())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parses params such as `x: Linear<0, 1>`
|
||||
fn parse_params(item_fn: &ItemFn) -> Result<Vec<ParamDef>> {
|
||||
let mut params: Vec<ParamDef> = Vec::new();
|
||||
for arg in &item_fn.sig.inputs {
|
||||
let invalid_param = |span| {
|
||||
return Err(Error::new(
|
||||
span,
|
||||
"Invalid benchmark function param. A valid example would be `x: Linear<5, 10>`.",
|
||||
))
|
||||
};
|
||||
|
||||
let FnArg::Typed(arg) = arg else { return invalid_param(arg.span()) };
|
||||
let Pat::Ident(ident) = &*arg.pat else { return invalid_param(arg.span()) };
|
||||
|
||||
// check param name
|
||||
let var_span = ident.span();
|
||||
let invalid_param_name = || {
|
||||
return Err(Error::new(
|
||||
var_span,
|
||||
"Benchmark parameter names must consist of a single lowercase letter (a-z) and no other characters.",
|
||||
))
|
||||
};
|
||||
let name = ident.ident.to_token_stream().to_string();
|
||||
if name.len() > 1 {
|
||||
return invalid_param_name()
|
||||
};
|
||||
let Some(name_char) = name.chars().next() else { return invalid_param_name() };
|
||||
if !name_char.is_alphabetic() || !name_char.is_lowercase() {
|
||||
return invalid_param_name()
|
||||
}
|
||||
|
||||
// parse type
|
||||
let typ = &*arg.ty;
|
||||
let Type::Path(tpath) = typ else { return invalid_param(typ.span()) };
|
||||
let Some(segment) = tpath.path.segments.last() else { return invalid_param(typ.span()) };
|
||||
let args = segment.arguments.to_token_stream().into();
|
||||
let Ok(args) = syn::parse::<RangeArgs>(args) else { return invalid_param(typ.span()) };
|
||||
let Ok(start) = args.start.base10_parse::<u32>() else { return invalid_param(args.start.span()) };
|
||||
let Ok(end) = args.end.base10_parse::<u32>() else { return invalid_param(args.end.span()) };
|
||||
|
||||
if end < start {
|
||||
return Err(Error::new(
|
||||
args.start.span(),
|
||||
"The start of a `ParamRange` must be less than or equal to the end",
|
||||
))
|
||||
}
|
||||
|
||||
params.push(ParamDef { name, typ: typ.clone(), start, end });
|
||||
};
|
||||
let name = ident.ident.to_token_stream().to_string();
|
||||
if name.len() > 1 {
|
||||
return invalid_param_name()
|
||||
};
|
||||
let Some(name_char) = name.chars().next() else { return invalid_param_name() };
|
||||
if !name_char.is_alphabetic() || !name_char.is_lowercase() {
|
||||
return invalid_param_name()
|
||||
}
|
||||
|
||||
// #[extrinsic_call] / #[block] handling
|
||||
let call_defs = item_fn.block.stmts.iter().enumerate().filter_map(|(i, child)| {
|
||||
// parse type
|
||||
let typ = &*arg.ty;
|
||||
let Type::Path(tpath) = typ else { return invalid_param(typ.span()) };
|
||||
let Some(segment) = tpath.path.segments.last() else { return invalid_param(typ.span()) };
|
||||
let args = segment.arguments.to_token_stream().into();
|
||||
let Ok(args) = syn::parse::<RangeArgs>(args) else { return invalid_param(typ.span()) };
|
||||
let Ok(start) = args.start.base10_parse::<u32>() else { return invalid_param(args.start.span()) };
|
||||
let Ok(end) = args.end.base10_parse::<u32>() else { return invalid_param(args.end.span()) };
|
||||
|
||||
if end < start {
|
||||
return Err(Error::new(
|
||||
args.start.span(),
|
||||
"The start of a `ParamRange` must be less than or equal to the end",
|
||||
))
|
||||
}
|
||||
|
||||
params.push(ParamDef { name, typ: typ.clone(), start, end });
|
||||
}
|
||||
Ok(params)
|
||||
}
|
||||
|
||||
/// Used in several places where the `#[extrinsic_call]` or `#[body]` annotation is missing
|
||||
fn missing_call<T>(item_fn: &ItemFn) -> Result<T> {
|
||||
return Err(Error::new(
|
||||
item_fn.block.brace_token.span,
|
||||
"No valid #[extrinsic_call] or #[block] annotation could be found in benchmark function body."
|
||||
))
|
||||
}
|
||||
|
||||
/// Finds the `BenchmarkCallDef` and its index (within the list of stmts for the fn) and
|
||||
/// returns them. Also handles parsing errors for invalid / extra call defs. AKA this is
|
||||
/// general handling for `#[extrinsic_call]` and `#[block]`
|
||||
fn parse_call_def(item_fn: &ItemFn) -> Result<(usize, BenchmarkCallDef)> {
|
||||
// #[extrinsic_call] / #[block] handling
|
||||
let call_defs = item_fn.block.stmts.iter().enumerate().filter_map(|(i, child)| {
|
||||
if let Stmt::Semi(Expr::Call(expr_call), _semi) = child {
|
||||
// #[extrinsic_call] case
|
||||
expr_call.attrs.iter().enumerate().find_map(|(k, attr)| {
|
||||
@@ -234,25 +297,60 @@ impl BenchmarkDef {
|
||||
None
|
||||
}
|
||||
}).collect::<Result<Vec<_>>>()?;
|
||||
let (i, call_def) = match &call_defs[..] {
|
||||
[(i, call_def)] => (*i, call_def.clone()), // = 1
|
||||
[] => return Err(Error::new( // = 0
|
||||
item_fn.block.brace_token.span,
|
||||
"No valid #[extrinsic_call] or #[block] annotation could be found in benchmark function body."
|
||||
)),
|
||||
_ => return Err(Error::new( // > 1
|
||||
Ok(match &call_defs[..] {
|
||||
[(i, call_def)] => (*i, call_def.clone()), // = 1
|
||||
[] => return missing_call(item_fn),
|
||||
_ =>
|
||||
return Err(Error::new(
|
||||
call_defs[1].1.attr_span(),
|
||||
"Only one #[extrinsic_call] or #[block] attribute is allowed per benchmark."
|
||||
"Only one #[extrinsic_call] or #[block] attribute is allowed per benchmark.",
|
||||
)),
|
||||
})
|
||||
}
|
||||
|
||||
impl BenchmarkDef {
|
||||
/// Constructs a [`BenchmarkDef`] by traversing an existing [`ItemFn`] node.
|
||||
pub fn from(item_fn: &ItemFn, extra: bool, skip_meta: bool) -> Result<BenchmarkDef> {
|
||||
let params = parse_params(item_fn)?;
|
||||
ensure_valid_return_type(item_fn)?;
|
||||
let (i, call_def) = parse_call_def(&item_fn)?;
|
||||
|
||||
let (verify_stmts, last_stmt) = match item_fn.sig.output {
|
||||
ReturnType::Default =>
|
||||
// no return type, last_stmt should be None
|
||||
(Vec::from(&item_fn.block.stmts[(i + 1)..item_fn.block.stmts.len()]), None),
|
||||
ReturnType::Type(_, _) => {
|
||||
// defined return type, last_stmt should be Result<(), BenchmarkError>
|
||||
// compatible and should not be included in verify_stmts
|
||||
if i + 1 >= item_fn.block.stmts.len() {
|
||||
return Err(Error::new(
|
||||
item_fn.block.span(),
|
||||
"Benchmark `#[block]` or `#[extrinsic_call]` item cannot be the \
|
||||
last statement of your benchmark function definition if you have \
|
||||
defined a return type. You should return something compatible \
|
||||
with Result<(), BenchmarkError> (i.e. `Ok(())`) as the last statement \
|
||||
or change your signature to a blank return type.",
|
||||
))
|
||||
}
|
||||
let Some(stmt) = item_fn.block.stmts.last() else { return missing_call(item_fn) };
|
||||
(
|
||||
Vec::from(&item_fn.block.stmts[(i + 1)..item_fn.block.stmts.len() - 1]),
|
||||
Some(stmt.clone()),
|
||||
)
|
||||
},
|
||||
};
|
||||
|
||||
Ok(BenchmarkDef {
|
||||
params,
|
||||
setup_stmts: Vec::from(&item_fn.block.stmts[0..i]),
|
||||
call_def,
|
||||
verify_stmts: Vec::from(&item_fn.block.stmts[(i + 1)..item_fn.block.stmts.len()]),
|
||||
verify_stmts,
|
||||
last_stmt,
|
||||
extra,
|
||||
skip_meta,
|
||||
fn_sig: item_fn.sig.clone(),
|
||||
fn_vis: item_fn.vis.clone(),
|
||||
fn_attrs: item_fn.attrs.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -643,6 +741,7 @@ fn expand_benchmark(
|
||||
let traits = quote!(#krate::frame_support::traits);
|
||||
let setup_stmts = benchmark_def.setup_stmts;
|
||||
let verify_stmts = benchmark_def.verify_stmts;
|
||||
let last_stmt = benchmark_def.last_stmt;
|
||||
let test_ident = Ident::new(format!("test_{}", name.to_string()).as_str(), Span::call_site());
|
||||
|
||||
// unroll params (prepare for quoting)
|
||||
@@ -661,7 +760,8 @@ fn expand_benchmark(
|
||||
true => quote!(T: Config<I>, I: 'static),
|
||||
};
|
||||
|
||||
let (pre_call, post_call) = match benchmark_def.call_def {
|
||||
// used in the benchmarking impls
|
||||
let (pre_call, post_call, fn_call_body) = match &benchmark_def.call_def {
|
||||
BenchmarkCallDef::ExtrinsicCall { origin, expr_call, attr_span: _ } => {
|
||||
let mut expr_call = expr_call.clone();
|
||||
|
||||
@@ -705,36 +805,97 @@ fn expand_benchmark(
|
||||
qself: None,
|
||||
path: Path { leading_colon: None, segments: punct },
|
||||
});
|
||||
|
||||
let pre_call = quote! {
|
||||
let __call = Call::<#type_use_generics>::#expr_call;
|
||||
let __benchmarked_call_encoded = #codec::Encode::encode(&__call);
|
||||
};
|
||||
let post_call = quote! {
|
||||
let __call_decoded = <Call<#type_use_generics> as #codec::Decode>
|
||||
::decode(&mut &__benchmarked_call_encoded[..])
|
||||
.expect("call is encoded above, encoding must be correct");
|
||||
let __origin = #origin.into();
|
||||
<Call<#type_use_generics> as #traits::UnfilteredDispatchable>::dispatch_bypass_filter(
|
||||
__call_decoded,
|
||||
__origin,
|
||||
)
|
||||
};
|
||||
(
|
||||
// (pre_call, post_call):
|
||||
// (pre_call, post_call, fn_call_body):
|
||||
pre_call.clone(),
|
||||
quote!(#post_call?;),
|
||||
quote! {
|
||||
let __call = Call::<#type_use_generics>::#expr_call;
|
||||
let __benchmarked_call_encoded = #codec::Encode::encode(&__call);
|
||||
},
|
||||
quote! {
|
||||
let __call_decoded = <Call<#type_use_generics> as #codec::Decode>
|
||||
::decode(&mut &__benchmarked_call_encoded[..])
|
||||
.expect("call is encoded above, encoding must be correct");
|
||||
let __origin = #origin.into();
|
||||
<Call<#type_use_generics> as #traits::UnfilteredDispatchable>::dispatch_bypass_filter(
|
||||
__call_decoded,
|
||||
__origin,
|
||||
)?;
|
||||
#pre_call
|
||||
#post_call.unwrap();
|
||||
},
|
||||
)
|
||||
},
|
||||
BenchmarkCallDef::Block { block, attr_span: _ } => (quote!(), quote!(#block)),
|
||||
BenchmarkCallDef::Block { block, attr_span: _ } =>
|
||||
(quote!(), quote!(#block), quote!(#block)),
|
||||
};
|
||||
|
||||
let vis = benchmark_def.fn_vis;
|
||||
|
||||
// remove #[benchmark] attribute
|
||||
let fn_attrs: Vec<&Attribute> = benchmark_def
|
||||
.fn_attrs
|
||||
.iter()
|
||||
.filter(|attr| !syn::parse2::<keywords::benchmark>(attr.path.to_token_stream()).is_ok())
|
||||
.collect();
|
||||
|
||||
// modify signature generics, ident, and inputs, e.g:
|
||||
// before: `fn bench(u: Linear<1, 100>) -> Result<(), BenchmarkError>`
|
||||
// after: `fn _bench <T: Config<I>, I: 'static>(u: u32, verify: bool) -> Result<(),
|
||||
// BenchmarkError>`
|
||||
let mut sig = benchmark_def.fn_sig;
|
||||
sig.generics = parse_quote!(<#type_impl_generics>);
|
||||
if !where_clause.is_empty() {
|
||||
sig.generics.where_clause = parse_quote!(where #where_clause);
|
||||
}
|
||||
sig.ident =
|
||||
Ident::new(format!("_{}", name.to_token_stream().to_string()).as_str(), Span::call_site());
|
||||
let mut fn_param_inputs: Vec<TokenStream2> =
|
||||
param_names.iter().map(|name| quote!(#name: u32)).collect();
|
||||
fn_param_inputs.push(quote!(verify: bool));
|
||||
sig.inputs = parse_quote!(#(#fn_param_inputs),*);
|
||||
|
||||
// used in instance() impl
|
||||
let impl_last_stmt = match &last_stmt {
|
||||
Some(stmt) => quote!(#stmt),
|
||||
None => quote!(Ok(())),
|
||||
};
|
||||
|
||||
let fn_def = quote! {
|
||||
#(
|
||||
#fn_attrs
|
||||
)*
|
||||
#vis #sig {
|
||||
#(
|
||||
#setup_stmts
|
||||
)*
|
||||
#fn_call_body
|
||||
if verify {
|
||||
#(
|
||||
#verify_stmts
|
||||
)*
|
||||
}
|
||||
#last_stmt
|
||||
}
|
||||
};
|
||||
|
||||
// generate final quoted tokens
|
||||
let res = quote! {
|
||||
// benchmark function definition
|
||||
#fn_def
|
||||
|
||||
// compile-time assertions that each referenced param type implements ParamRange
|
||||
#(
|
||||
#home::assert_impl_all!(#param_types: #home::ParamRange);
|
||||
)*
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
#(
|
||||
#fn_attrs
|
||||
)*
|
||||
struct #name;
|
||||
|
||||
#[allow(unused_variables)]
|
||||
@@ -773,7 +934,7 @@ fn expand_benchmark(
|
||||
#verify_stmts
|
||||
)*
|
||||
}
|
||||
Ok(())
|
||||
#impl_last_stmt
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
use frame_benchmarking::v2::*;
|
||||
#[allow(unused_imports)]
|
||||
use frame_support_test::Config;
|
||||
|
||||
#[benchmarks]
|
||||
mod benches {
|
||||
use super::*;
|
||||
|
||||
#[benchmark]
|
||||
fn bench() -> Result<(), BenchmarkException> {
|
||||
let a = 2 + 2;
|
||||
#[block]
|
||||
{}
|
||||
assert_eq!(a, 4);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: expected `BenchmarkError`
|
||||
--> tests/benchmark_ui/bad_return_non_benchmark_err.rs:10:27
|
||||
|
|
||||
10 | fn bench() -> Result<(), BenchmarkException> {
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
@@ -0,0 +1,17 @@
|
||||
use frame_benchmarking::v2::*;
|
||||
#[allow(unused_imports)]
|
||||
use frame_support_test::Config;
|
||||
|
||||
#[benchmarks]
|
||||
mod benchmarks {
|
||||
use super::*;
|
||||
|
||||
#[benchmark]
|
||||
fn bench() -> (String, u32) {
|
||||
#[block]
|
||||
{}
|
||||
(String::from("hey"), 23)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: Only `Result<(), BenchmarkError>` or a blank return type is allowed on benchmark function definitions
|
||||
--> tests/benchmark_ui/bad_return_non_type_path.rs:10:16
|
||||
|
|
||||
10 | fn bench() -> (String, u32) {
|
||||
| ^^^^^^^^^^^^^
|
||||
@@ -0,0 +1,15 @@
|
||||
use frame_benchmarking::v2::*;
|
||||
#[allow(unused_imports)]
|
||||
use frame_support_test::Config;
|
||||
|
||||
#[benchmarks]
|
||||
mod benchmarks {
|
||||
#[benchmark]
|
||||
fn bench() -> Result<u32, BenchmarkError> {
|
||||
#[block]
|
||||
{}
|
||||
Ok(10)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: expected `()`
|
||||
--> tests/benchmark_ui/bad_return_non_unit_t.rs:8:23
|
||||
|
|
||||
8 | fn bench() -> Result<u32, BenchmarkError> {
|
||||
| ^^^
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
use frame_benchmarking::v2::*;
|
||||
#[allow(unused_imports)]
|
||||
use frame_support_test::Config;
|
||||
|
||||
#[benchmarks]
|
||||
mod benches {
|
||||
use super::*;
|
||||
|
||||
fn something() -> Result<(), BenchmarkError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn bench() {
|
||||
something()?;
|
||||
#[block]
|
||||
{}
|
||||
assert_eq!(2 + 2, 4);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `FromResidual`)
|
||||
--> tests/benchmark_ui/bad_return_type_blank_with_question.rs:15:14
|
||||
|
|
||||
5 | #[benchmarks]
|
||||
| ------------- this function should return `Result` or `Option` to accept `?`
|
||||
...
|
||||
15 | something()?;
|
||||
| ^ cannot use the `?` operator in a function that returns `()`
|
||||
|
|
||||
= help: the trait `FromResidual<Result<std::convert::Infallible, frame_benchmarking::BenchmarkError>>` is not implemented for `()`
|
||||
@@ -0,0 +1,16 @@
|
||||
use frame_benchmarking::v2::*;
|
||||
#[allow(unused_imports)]
|
||||
use frame_support_test::Config;
|
||||
|
||||
#[benchmarks]
|
||||
mod benches {
|
||||
use super::*;
|
||||
|
||||
#[benchmark]
|
||||
fn bench() -> Result<(), BenchmarkError> {
|
||||
#[block]
|
||||
{}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,9 @@
|
||||
error: Benchmark `#[block]` or `#[extrinsic_call]` item cannot be the last statement of your benchmark function definition if you have defined a return type. You should return something compatible with Result<(), BenchmarkError> (i.e. `Ok(())`) as the last statement or change your signature to a blank return type.
|
||||
--> tests/benchmark_ui/bad_return_type_no_last_stmt.rs:10:43
|
||||
|
|
||||
10 | fn bench() -> Result<(), BenchmarkError> {
|
||||
| ______________________________________________^
|
||||
11 | | #[block]
|
||||
12 | | {}
|
||||
13 | | }
|
||||
| |_____^
|
||||
@@ -0,0 +1,19 @@
|
||||
use frame_benchmarking::v2::*;
|
||||
#[allow(unused_imports)]
|
||||
use frame_support_test::Config;
|
||||
|
||||
#[benchmarks]
|
||||
mod benches {
|
||||
use super::*;
|
||||
|
||||
#[benchmark]
|
||||
fn bench(y: Linear<1, 2>) -> String {
|
||||
let a = 2 + 2;
|
||||
#[block]
|
||||
{}
|
||||
assert_eq!(a, 4);
|
||||
String::from("test")
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: expected `Result`
|
||||
--> tests/benchmark_ui/bad_return_type_non_result.rs:10:31
|
||||
|
|
||||
10 | fn bench(y: Linear<1, 2>) -> String {
|
||||
| ^^^^^^
|
||||
@@ -0,0 +1,18 @@
|
||||
use frame_benchmarking::v2::*;
|
||||
#[allow(unused_imports)]
|
||||
use frame_support_test::Config;
|
||||
|
||||
#[benchmarks]
|
||||
mod benches {
|
||||
use super::*;
|
||||
|
||||
#[benchmark]
|
||||
fn bench() -> Option<BenchmarkError> {
|
||||
#[block]
|
||||
{}
|
||||
assert_eq!(2 + 2, 4);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: expected `Result`
|
||||
--> tests/benchmark_ui/bad_return_type_option.rs:10:16
|
||||
|
|
||||
10 | fn bench() -> Option<BenchmarkError> {
|
||||
| ^^^^^^
|
||||
@@ -0,0 +1,13 @@
|
||||
use frame_benchmarking::v2::*;
|
||||
#[allow(unused_imports)]
|
||||
use frame_support_test::Config;
|
||||
|
||||
#[benchmarks]
|
||||
mod benches {
|
||||
use super::*;
|
||||
|
||||
#[benchmark]
|
||||
fn bench() {}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: No valid #[extrinsic_call] or #[block] annotation could be found in benchmark function body.
|
||||
--> tests/benchmark_ui/empty_function.rs:10:13
|
||||
|
|
||||
10 | fn bench() {}
|
||||
| ^^
|
||||
@@ -7,7 +7,9 @@ mod benches {
|
||||
use super::*;
|
||||
|
||||
#[benchmark]
|
||||
fn bench() {}
|
||||
fn bench() {
|
||||
assert_eq!(2 + 2, 4);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
error: No valid #[extrinsic_call] or #[block] annotation could be found in benchmark function body.
|
||||
--> tests/benchmark_ui/missing_call.rs:10:13
|
||||
|
|
||||
10 | fn bench() {}
|
||||
| ^^
|
||||
10 | fn bench() {
|
||||
| ________________^
|
||||
11 | | assert_eq!(2 + 2, 4);
|
||||
12 | | }
|
||||
| |_____^
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
use frame_benchmarking::v2::*;
|
||||
#[allow(unused_imports)]
|
||||
use frame_support_test::Config;
|
||||
|
||||
#[benchmarks]
|
||||
mod benches {
|
||||
use super::*;
|
||||
|
||||
#[benchmark]
|
||||
fn bench() -> Result<(), frame_benchmarking::v2::BenchmarkError> {
|
||||
#[block]
|
||||
{}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,16 @@
|
||||
use frame_benchmarking::v2::*;
|
||||
#[allow(unused_imports)]
|
||||
use frame_support_test::Config;
|
||||
|
||||
#[benchmarks]
|
||||
mod benches {
|
||||
use super::*;
|
||||
|
||||
#[benchmark]
|
||||
fn bench() {
|
||||
#[block]
|
||||
{}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
use frame_benchmarking::v2::*;
|
||||
#[allow(unused_imports)]
|
||||
use frame_support_test::Config;
|
||||
|
||||
#[benchmarks]
|
||||
mod benches {
|
||||
use super::*;
|
||||
|
||||
#[benchmark]
|
||||
fn bench() -> Result<(), frame_benchmarking::v2::BenchmarkError> {
|
||||
#[block]
|
||||
{}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,18 @@
|
||||
use frame_benchmarking::v2::*;
|
||||
use frame_support_test::Config;
|
||||
|
||||
#[benchmarks]
|
||||
mod benches {
|
||||
use super::*;
|
||||
|
||||
#[benchmark]
|
||||
fn bench() -> Result<(), BenchmarkError> {
|
||||
let a = 2 + 2;
|
||||
#[block]
|
||||
{}
|
||||
assert_eq!(a, 4);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
Reference in New Issue
Block a user