1- use std:: collections:: HashSet ;
1+ use std:: collections:: { HashMap , HashSet } ;
2+ use std:: sync:: LazyLock ;
23
34use blockifier:: blockifier_versioned_constants:: {
45 RawVersionedConstants ,
6+ ResourcesParams ,
7+ VariableCallDataFactor ,
58 VariableResourceParams ,
69 VersionedConstants ,
710} ;
@@ -36,10 +39,9 @@ use crate::test_manager::{TestBuilder, TestBuilderConfig, FUNDED_ACCOUNT_ADDRESS
3639use crate :: tests:: NON_TRIVIAL_RESOURCE_BOUNDS ;
3740
3841// TODO(Dori): Delete this, or at least reduce it to a minimal set of unmeasurable syscalls.
39- const UNMEASURABLE_SYSCALLS : [ Selector ; 34 ] = [
42+ const UNMEASURABLE_SYSCALLS : [ Selector ; 33 ] = [
4043 Selector :: DelegateCall ,
4144 Selector :: DelegateL1Handler ,
42- Selector :: Deploy ,
4345 Selector :: EmitEvent ,
4446 Selector :: GetBlockHash ,
4547 Selector :: GetBlockNumber ,
@@ -73,6 +75,22 @@ const UNMEASURABLE_SYSCALLS: [Selector; 34] = [
7375 Selector :: StorageWrite ,
7476] ;
7577
78+ /// Store a mapping from a linearly-charged syscall, with the number of "linear elements" in it's
79+ /// base measurement. For example, if we measure the base and linear costs of a [Selector::Deploy]
80+ /// by running:
81+ /// ```
82+ /// deploy_syscall(stable_class_hash, 3, array![2, 0, 0].span(), true).unwrap_syscall();
83+ /// deploy_syscall(stable_class_hash, 3, array![3, 0, 0, 0].span(), true).unwrap_syscall();
84+ /// ```
85+ /// ... then the linear factor is exactly the difference between the two measurements, because the
86+ /// second measurement has one more linear element (4) than the first measurement (3). However, the
87+ /// first call has 3 elements in it's calldata, so to compute the estimated cost of a zero-element
88+ /// call (base) we need to subtract three times the linear factor from the first measurement.
89+ /// Note: Keccak does not store the linear factor in the same entry in the versioned constants, but
90+ /// it does have a measurable linear factor stored under [Selector::KeccakRound].
91+ static SYSCALLS_WITH_LINEAR_FACTOR : LazyLock < HashMap < Selector , usize > > =
92+ LazyLock :: new ( || HashMap :: from ( [ ( Selector :: Deploy , 1 ) , ( Selector :: MetaTxV0 , 1 ) ] ) ) ;
93+
7694/// Measure the OS overhead for each syscall, and compare the results with the latest VC.
7795///
7896/// This test relies on the [starknet_os::hint_processor::os_logger::OsLogger] to capture the
@@ -194,48 +212,79 @@ async fn test_os_resources_regression() {
194212 test_output. perform_default_validations ( ) ;
195213
196214 // Extract syscall resources consumed per syscall.
197- let syscall_traces = test_output. runner_output . txs_trace . last ( ) . unwrap ( ) . get_syscalls ( ) ;
198-
199- // Measure each syscall overhead. If the syscall incurs an inner call, subtract the inner call
200- // overhead.
201215 let mut inner_calls_iter = inner_calls. into_iter ( ) ;
202- let mut visited_syscalls = HashSet :: new ( ) ;
203- let measurements : IndexMap < Selector , ExecutionResources > = syscall_traces
216+ let syscall_traces = test_output . runner_output . txs_trace . last ( ) . unwrap ( ) . get_syscalls ( ) ;
217+ let mut syscalls_iter = syscall_traces
204218 . iter ( )
205- . filter_map ( |syscall_trace| {
206- let selector = syscall_trace. get_selector ( ) ;
207- if UNMEASURABLE_SYSCALLS . contains ( & selector) {
208- return None ;
209- }
219+ . filter ( |syscall_trace| !UNMEASURABLE_SYSCALLS . contains ( & syscall_trace. get_selector ( ) ) ) ;
220+ let mut measurements: IndexMap < Selector , VariableResourceParams > = IndexMap :: new ( ) ;
221+ // If the syscall incurs an inner call, subtract the inner call overhead.
222+ let mut fetch_inner_resources = |selector : Selector | -> ExecutionResources {
223+ // We assume no inner calls have nested inner calls (all inner calls are leaves).
224+ if selector. is_calling_syscall ( ) {
225+ // TODO(Dori): Take opcodes (like blake) into account, instead of using the vm_resources
226+ // field.
227+ inner_calls_iter. next ( ) . unwrap ( ) . resources . vm_resources
228+ } else {
229+ ExecutionResources :: default ( )
230+ }
231+ } ;
232+ while let Some ( syscall_trace) = syscalls_iter. next ( ) {
233+ let selector = syscall_trace. get_selector ( ) ;
210234
211- // Ensure we don't visit the same syscall twice.
212- assert ! (
213- !visited_syscalls. contains( & selector) ,
214- "Syscall {selector:?} was visited twice."
215- ) ;
216- visited_syscalls. insert ( selector) ;
235+ // Ensure we don't visit the same syscall more than once.
236+ assert ! (
237+ measurements. get( & selector) . is_none( ) ,
238+ "Syscall {selector:?} was visited again, unexpectedly."
239+ ) ;
217240
218- // If this syscall incurs an inner call, it should be the next inner call in the
219- // iterator. We assume no inner calls have nested inner calls (all inner calls are
220- // leaves).
221- let inner_overhead = if selector. is_calling_syscall ( ) {
222- // TODO(Dori): Take opcodes (like blake) into account, instead of using the
223- // vm_resources field.
224- inner_calls_iter. next ( ) . unwrap ( ) . resources . vm_resources
225- } else {
226- ExecutionResources :: default ( )
227- } ;
241+ // If this syscall incurs an inner call, it should be the next inner call in the
242+ // iterator.
243+ let inner_overhead = fetch_inner_resources ( selector) ;
244+ // The resources measured here are one of two types: constant, or base cost of a syscall
245+ // with a linear factor.
246+ let mut resources =
247+ ( syscall_trace. get_resources ( ) . unwrap ( ) - & inner_overhead) . filter_unused_builtins ( ) ;
228248
229- Some ( (
249+ // If this if a syscall with a linear factor, the next syscall should be the linear cost.
250+ // Otherwise, this syscall has a constant cost.
251+ let syscall_cost = if let Some ( linear_count_in_base) =
252+ SYSCALLS_WITH_LINEAR_FACTOR . get ( & selector)
253+ {
254+ let next_syscall_trace = syscalls_iter. next ( ) . unwrap ( ) ;
255+ assert_eq ! (
230256 selector,
231- ( syscall_trace. get_resources ( ) . unwrap ( ) - & inner_overhead) . filter_unused_builtins ( ) ,
232- ) )
233- } )
234- . collect ( ) ;
257+ next_syscall_trace. get_selector( ) ,
258+ "Expected next syscall to be the same as the current syscall {selector:?}, but \
259+ got {:?}.",
260+ next_syscall_trace. get_selector( )
261+ ) ;
262+ let next_inner_overhead = fetch_inner_resources ( selector) ;
263+ let next_resources = ( next_syscall_trace. get_resources ( ) . unwrap ( )
264+ - & next_inner_overhead)
265+ . filter_unused_builtins ( ) ;
266+ let linear_factor_resources = ( & next_resources - & resources) . filter_unused_builtins ( ) ;
267+
268+ // Linear factor is computed; deduct the linear overhead from the base cost to get the
269+ // real base cost.
270+ resources = ( & resources - & ( & linear_factor_resources * * linear_count_in_base) )
271+ . filter_unused_builtins ( ) ;
272+
273+ VariableResourceParams :: WithFactor ( ResourcesParams {
274+ constant : resources,
275+ // Syscalls with a linear factor have an unscaled linear factor cost.
276+ calldata_factor : VariableCallDataFactor :: Unscaled ( linear_factor_resources) ,
277+ } )
278+ } else {
279+ VariableResourceParams :: Constant ( resources)
280+ } ;
281+
282+ measurements. insert ( selector, syscall_cost) ;
283+ }
235284
236285 // Make sure we covered all syscalls we expect to.
237286 assert_eq ! (
238- visited_syscalls ,
287+ HashSet :: from_iter ( measurements . keys ( ) . cloned ( ) ) ,
239288 Selector :: iter( )
240289 . collect:: <HashSet <_>>( )
241290 . difference( & UNMEASURABLE_SYSCALLS . iter( ) . cloned( ) . collect:: <HashSet <_>>( ) )
@@ -248,10 +297,7 @@ async fn test_os_resources_regression() {
248297 let mut raw_vc: RawVersionedConstants =
249298 serde_json:: from_str ( VersionedConstants :: json_str ( & version) . unwrap ( ) ) . unwrap ( ) ;
250299 for ( syscall, resources) in measurements {
251- raw_vc
252- . os_resources
253- . execute_syscalls
254- . insert ( syscall, VariableResourceParams :: Constant ( resources) ) ;
300+ raw_vc. os_resources . execute_syscalls . insert ( syscall, resources) ;
255301 }
256302 expect_file ! [ VersionedConstants :: json_path( & version) . unwrap( ) ]
257303 . assert_eq ( & raw_vc. to_string_pretty ( ) ) ;
0 commit comments