1+ use core:: hint:: cold_path;
2+
13#[ cfg( not( feature = "std" ) ) ]
24#[ allow( unused_imports) ]
35use super :: no_std_floats:: NoStdFloatExt ;
@@ -17,9 +19,6 @@ use crate::instance::ModuleInstanceInner;
1719use crate :: interpreter:: Value128 ;
1820use crate :: * ;
1921
20- #[ cfg( feature = "std" ) ]
21- const TIME_BUDGET_CHECK_INTERVAL : usize = 2048 ;
22- const FUEL_ACCOUNTING_INTERVAL : usize = 1024 ;
2322const FUEL_COST_CALL_TOTAL : u32 = 5 ;
2423
2524pub ( crate ) struct Executor < ' store , const BUDGETED : bool > {
@@ -119,11 +118,14 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> {
119118
120119 let next = match self . func . instructions . 0 . get ( self . cf . instr_ptr as usize ) {
121120 Some ( instr) => instr,
122- None => unreachable ! (
123- "Instruction pointer out of bounds: {} ({} instructions)" ,
124- self . cf. instr_ptr,
125- self . func. instructions. 0 . len( )
126- ) ,
121+ None => {
122+ cold_path ( ) ;
123+ unreachable ! (
124+ "Instruction pointer out of bounds: {} ({} instructions)" ,
125+ self . cf. instr_ptr,
126+ self . func. instructions. 0 . len( )
127+ )
128+ }
127129 } ;
128130
129131 #[ rustfmt:: skip]
@@ -722,7 +724,11 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> {
722724 }
723725
724726 let res = self . store . stack . values . enter_locals ( & wasm_func. func . params , & wasm_func. func . locals ) ;
725- let locals_base = res. map_err ( |err| if IS_RETURN_CALL { err } else { Error :: Trap ( Trap :: CallStackOverflow ) } ) ?;
727+ let locals_base = res. map_err ( |err| {
728+ cold_path ( ) ;
729+ if IS_RETURN_CALL { err } else { Error :: Trap ( Trap :: CallStackOverflow ) }
730+ } ) ?;
731+
726732 let new_call_frame = CallFrame :: new ( func_addr, wasm_func. owner , locals_base, wasm_func. func . locals ) ;
727733
728734 if !IS_RETURN_CALL {
@@ -763,7 +769,10 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> {
763769 }
764770
765771 let res = self . store . stack . values . enter_locals ( & params, & locals) ;
766- let locals_base = res. map_err ( |err| if IS_RETURN_CALL { err } else { Error :: Trap ( Trap :: CallStackOverflow ) } ) ?;
772+ let locals_base = res. map_err ( |err| {
773+ cold_path ( ) ;
774+ if IS_RETURN_CALL { err } else { Error :: Trap ( Trap :: CallStackOverflow ) }
775+ } ) ?;
767776 let new_call_frame = CallFrame :: new ( self . cf . func_addr , self . cf . module_addr , locals_base, locals) ;
768777
769778 if !IS_RETURN_CALL {
@@ -781,15 +790,23 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> {
781790 let table_idx: u32 = self . store . stack . values . pop :: < i32 > ( ) as u32 ;
782791 let table = self . store . state . get_table ( self . module . resolve_table_addr ( table_addr) ) ;
783792 assert ! ( table. kind. element_type == WasmType :: RefFunc , "table is not of type funcref" ) ;
784- let table =
785- table. get ( table_idx) . map_err ( |_| Error :: from ( Trap :: UndefinedElement { index : table_idx as usize } ) ) ?;
786- table. addr ( ) . ok_or_else ( || Error :: from ( Trap :: UninitializedElement { index : table_idx as usize } ) ) ?
793+
794+ let table = table. get ( table_idx) . map_err ( |_| {
795+ cold_path ( ) ;
796+ Error :: from ( Trap :: UndefinedElement { index : table_idx as usize } )
797+ } ) ?;
798+
799+ table. addr ( ) . ok_or_else ( || {
800+ cold_path ( ) ;
801+ Error :: from ( Trap :: UninitializedElement { index : table_idx as usize } )
802+ } ) ?
787803 } ;
788804
789805 let call_ty = self . module . func_ty ( type_addr) ;
790806 match self . store . state . get_func ( func_ref) {
791807 crate :: FunctionInstance :: Wasm ( wasm_func) => {
792808 if wasm_func. ty ( ) != call_ty {
809+ cold_path ( ) ;
793810 return Err ( Trap :: IndirectCallTypeMismatch {
794811 actual : wasm_func. ty ( ) . clone ( ) ,
795812 expected : call_ty. clone ( ) ,
@@ -801,6 +818,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> {
801818 }
802819 crate :: FunctionInstance :: Host ( host_func) => {
803820 if host_func. ty != * call_ty {
821+ cold_path ( ) ;
804822 return Err ( Trap :: IndirectCallTypeMismatch {
805823 actual : host_func. ty . clone ( ) ,
806824 expected : call_ty. clone ( ) ,
@@ -833,6 +851,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> {
833851 fn local_mem_addr < const N : usize > ( & self , memarg : MemoryArg , addr_local : u8 ) -> Result < usize > {
834852 let addr = u64:: from ( self . store . stack . values . local_get :: < u32 > ( & self . cf , u16:: from ( addr_local) ) ) ;
835853 let Some ( Ok ( addr) ) = memarg. offset ( ) . checked_add ( addr) . map ( |a| a. try_into ( ) ) else {
854+ cold_path ( ) ;
836855 return Err ( Error :: Trap ( Trap :: MemoryOutOfBounds { offset : addr as usize , len : N , max : 0 } ) ) ;
837856 } ;
838857 Ok ( addr)
@@ -850,9 +869,11 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> {
850869 let addr = u64:: from ( self . store . stack . values . local_get :: < u32 > ( & self . cf , u16:: from ( addr_local) ) ) ;
851870 let value = cast ( self . store . stack . values . local_get :: < T > ( & self . cf , u16:: from ( value_local) ) ) . to_mem_bytes ( ) ;
852871 let Some ( effective_addr) = memarg. offset ( ) . checked_add ( addr) else {
872+ cold_path ( ) ;
853873 return Err ( Error :: Trap ( Trap :: MemoryOutOfBounds { offset : addr as usize , len : N , max : 0 } ) ) ;
854874 } ;
855875 let Ok ( effective_addr) = usize:: try_from ( effective_addr) else {
876+ cold_path ( ) ;
856877 return Err ( Error :: Trap ( Trap :: MemoryOutOfBounds { offset : addr as usize , len : N , max : 0 } ) ) ;
857878 } ;
858879 mem. store ( effective_addr, value. len ( ) , & value) ?;
@@ -970,14 +991,19 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> {
970991 let data_len = data. data . as_ref ( ) . map_or ( 0 , |d| d. len ( ) ) ;
971992
972993 if ( ( size + offset) as usize > data_len) || ( ( dst + size) as usize > mem. len ( ) ) {
994+ cold_path ( ) ;
973995 return Err ( Trap :: MemoryOutOfBounds { offset : offset as usize , len : size as usize , max : data_len } . into ( ) ) ;
974996 }
975997
976998 if size == 0 {
977999 return Ok ( ( ) ) ;
9781000 }
9791001
980- let Some ( data) = & data. data else { return Err ( Trap :: MemoryOutOfBounds { offset : 0 , len : 0 , max : 0 } . into ( ) ) } ;
1002+ let Some ( data) = & data. data else {
1003+ cold_path ( ) ;
1004+ return Err ( Trap :: MemoryOutOfBounds { offset : 0 , len : 0 , max : 0 } . into ( ) ) ;
1005+ } ;
1006+
9811007 mem. store ( dst as usize , size as usize , & data[ offset as usize ..( ( offset + size) as usize ) ] )
9821008 }
9831009 fn exec_table_copy ( & mut self , dst_table : u32 , src_table : u32 ) -> Result < ( ) > {
@@ -1012,6 +1038,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> {
10121038 let val = self . store . stack . values . pop :: < i32 > ( ) as u64 ;
10131039 let mem = self . store . state . get_mem ( self . module . resolve_mem_addr ( mem_addr) ) ;
10141040 let Some ( Ok ( addr) ) = offset. checked_add ( val) . map ( TryInto :: try_into) else {
1041+ cold_path ( ) ;
10151042 return Err ( Error :: Trap ( Trap :: MemoryOutOfBounds { offset : val as usize , len : LOAD_SIZE , max : 0 } ) ) ;
10161043 } ;
10171044 let val = mem. load_as :: < LOAD_SIZE , LOAD > ( addr) ?. to_mem_bytes ( ) ;
@@ -1038,11 +1065,8 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> {
10381065 self . store . stack . values . pop :: < i32 > ( ) as u32 as u64
10391066 } ;
10401067
1041- let Some ( addr) = base. checked_add ( offset) else {
1042- return Err ( Error :: Trap ( Trap :: MemoryOutOfBounds { offset : base as usize , len : LOAD_SIZE , max : 0 } ) ) ;
1043- } ;
1044-
1045- let Ok ( addr) = usize:: try_from ( addr) else {
1068+ let Some ( Ok ( addr) ) = base. checked_add ( offset) . map ( usize:: try_from) else {
1069+ cold_path ( ) ;
10461070 return Err ( Error :: Trap ( Trap :: MemoryOutOfBounds { offset : base as usize , len : LOAD_SIZE , max : 0 } ) ) ;
10471071 } ;
10481072
@@ -1068,10 +1092,8 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> {
10681092 false => self . store . stack . values . pop :: < i32 > ( ) as u32 as u64 ,
10691093 } ;
10701094
1071- let Some ( effective_addr) = offset. checked_add ( addr) else {
1072- return Err ( Error :: Trap ( Trap :: MemoryOutOfBounds { offset : addr as usize , len : N , max : 0 } ) ) ;
1073- } ;
1074- let Ok ( effective_addr) = usize:: try_from ( effective_addr) else {
1095+ let Some ( Ok ( effective_addr) ) = offset. checked_add ( addr) . map ( usize:: try_from) else {
1096+ cold_path ( ) ;
10751097 return Err ( Error :: Trap ( Trap :: MemoryOutOfBounds { offset : addr as usize , len : N , max : 0 } ) ) ;
10761098 } ;
10771099
@@ -1095,15 +1117,12 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> {
10951117 false => u64:: from ( self . store . stack . values . pop :: < i32 > ( ) as u32 ) ,
10961118 } ;
10971119
1098- let Some ( effective_addr) = offset. checked_add ( addr) else {
1099- return Err ( Error :: Trap ( Trap :: MemoryOutOfBounds { offset : addr as usize , len : N , max : 0 } ) ) ;
1100- } ;
1101- let Ok ( effective_addr) = usize:: try_from ( effective_addr) else {
1120+ let Some ( Ok ( effective_addr) ) = offset. checked_add ( addr) . map ( usize:: try_from) else {
1121+ cold_path ( ) ;
11021122 return Err ( Error :: Trap ( Trap :: MemoryOutOfBounds { offset : addr as usize , len : N , max : 0 } ) ) ;
11031123 } ;
11041124
11051125 mem. store ( effective_addr, val. len ( ) , & val) ?;
1106-
11071126 Ok ( ( ) )
11081127 }
11091128
@@ -1148,6 +1167,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> {
11481167 let table_len = table. size ( ) ;
11491168
11501169 if size < 0 || ( ( size + offset) as usize > elem_len) || ( ( dst + size) > table_len) {
1170+ cold_path ( ) ;
11511171 return Err ( Trap :: TableOutOfBounds { offset : offset as usize , len : size as usize , max : elem_len } . into ( ) ) ;
11521172 }
11531173
@@ -1156,10 +1176,12 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> {
11561176 }
11571177
11581178 if let ElementKind :: Active { .. } = elem. kind {
1179+ cold_path ( ) ;
11591180 return Err ( Error :: Other ( "table.init with active element" . to_string ( ) ) ) ;
11601181 }
11611182
11621183 let Some ( items) = elem. items . as_ref ( ) else {
1184+ cold_path ( ) ;
11631185 return Err ( Trap :: TableOutOfBounds { offset : 0 , len : 0 , max : 0 } . into ( ) ) ;
11641186 } ;
11651187
@@ -1187,6 +1209,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> {
11871209 let i = self . store . stack . values . pop :: < i32 > ( ) ;
11881210
11891211 if i + n > table. size ( ) {
1212+ cold_path ( ) ;
11901213 return Err ( Error :: Trap ( Trap :: TableOutOfBounds {
11911214 offset : i as usize ,
11921215 len : n as usize ,
@@ -1205,10 +1228,13 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> {
12051228impl < ' store > Executor < ' store , false > {
12061229 #[ inline( always) ]
12071230 pub ( crate ) fn run_to_completion ( & mut self ) -> Result < ( ) > {
1208- if self . exec :: < { usize:: MAX } > ( ) ?. is_some ( ) {
1209- return Ok ( ( ) ) ;
1231+ // direct loop+match has worse codegen than a fixed for loop here for some reason (like ~5% worse)
1232+ // ideally we use `loop_match` / `become` once thats stabilized
1233+ loop {
1234+ if self . exec :: < 1024 > ( ) ?. is_some ( ) {
1235+ return Ok ( ( ) ) ;
1236+ }
12101237 }
1211- unreachable ! ( ) ;
12121238 }
12131239
12141240 #[ cfg( feature = "std" ) ]
@@ -1221,7 +1247,7 @@ impl<'store> Executor<'store, false> {
12211247 }
12221248
12231249 loop {
1224- if self . exec :: < TIME_BUDGET_CHECK_INTERVAL > ( ) ?. is_some ( ) {
1250+ if self . exec :: < 1024 > ( ) ?. is_some ( ) {
12251251 return Ok ( ExecState :: Completed ) ;
12261252 }
12271253
@@ -1241,11 +1267,11 @@ impl<'store> Executor<'store, true> {
12411267 }
12421268
12431269 loop {
1244- if self . exec :: < FUEL_ACCOUNTING_INTERVAL > ( ) ?. is_some ( ) {
1270+ if self . exec :: < 1024 > ( ) ?. is_some ( ) {
12451271 return Ok ( ExecState :: Completed ) ;
12461272 }
12471273
1248- self . store . execution_fuel = self . store . execution_fuel . saturating_sub ( FUEL_ACCOUNTING_INTERVAL as u32 ) ;
1274+ self . store . execution_fuel = self . store . execution_fuel . saturating_sub ( 1024_u32 ) ;
12491275 if self . store . execution_fuel == 0 {
12501276 return Ok ( ExecState :: Suspended ( self . cf ) ) ;
12511277 }
0 commit comments