@@ -104,8 +104,9 @@ fn profile_insn(bare_opcode: ruby_vminsn_type, ec: EcPtr) {
104104 }
105105
106106 // Once we profile the instruction num_profiles times, we stop profiling it.
107- profile. num_profiles [ profiler. insn_idx ] = profile. num_profiles [ profiler. insn_idx ] . saturating_add ( 1 ) ;
108- if profile. num_profiles [ profiler. insn_idx ] == get_option ! ( num_profiles) {
107+ let entry = profile. entry_mut ( profiler. insn_idx ) ;
108+ entry. num_profiles = entry. num_profiles . saturating_add ( 1 ) ;
109+ if entry. num_profiles == get_option ! ( num_profiles) {
109110 unsafe { rb_zjit_iseq_insn_set ( profiler. iseq , profiler. insn_idx as u32 , bare_opcode) ; }
110111 }
111112}
@@ -118,12 +119,12 @@ pub type TypeDistributionSummary = DistributionSummary<ProfiledType, DISTRIBUTIO
118119
119120/// Profile the Type of top-`n` stack operands
120121fn profile_operands ( profiler : & mut Profiler , profile : & mut IseqProfile , n : usize ) {
121- let types = & mut profile. opnd_types [ profiler. insn_idx ] ;
122- if types . is_empty ( ) {
123- types . resize ( n, TypeDistribution :: new ( ) ) ;
122+ let entry = profile. entry_mut ( profiler. insn_idx ) ;
123+ if entry . opnd_types . is_empty ( ) {
124+ entry . opnd_types . resize ( n, TypeDistribution :: new ( ) ) ;
124125 }
125126
126- for ( i, profile_type) in types . iter_mut ( ) . enumerate ( ) {
127+ for ( i, profile_type) in entry . opnd_types . iter_mut ( ) . enumerate ( ) {
127128 let obj = profiler. peek_at_stack ( ( n - i - 1 ) as isize ) ;
128129 // TODO(max): Handle GC-hidden classes like Array, Hash, etc and make them look normal or
129130 // drop them or something
@@ -134,33 +135,33 @@ fn profile_operands(profiler: &mut Profiler, profile: &mut IseqProfile, n: usize
134135}
135136
136137fn profile_self ( profiler : & mut Profiler , profile : & mut IseqProfile ) {
137- let types = & mut profile. opnd_types [ profiler. insn_idx ] ;
138- if types . is_empty ( ) {
139- types . resize ( 1 , TypeDistribution :: new ( ) ) ;
138+ let entry = profile. entry_mut ( profiler. insn_idx ) ;
139+ if entry . opnd_types . is_empty ( ) {
140+ entry . opnd_types . resize ( 1 , TypeDistribution :: new ( ) ) ;
140141 }
141142 let obj = profiler. peek_at_self ( ) ;
142143 // TODO(max): Handle GC-hidden classes like Array, Hash, etc and make them look normal or
143144 // drop them or something
144145 let ty = ProfiledType :: new ( obj) ;
145146 VALUE :: from ( profiler. iseq ) . write_barrier ( ty. class ( ) ) ;
146- types [ 0 ] . observe ( ty) ;
147+ entry . opnd_types [ 0 ] . observe ( ty) ;
147148}
148149
149150fn profile_block_handler ( profiler : & mut Profiler , profile : & mut IseqProfile ) {
150- let types = & mut profile. opnd_types [ profiler. insn_idx ] ;
151- if types . is_empty ( ) {
152- types . resize ( 1 , TypeDistribution :: new ( ) ) ;
151+ let entry = profile. entry_mut ( profiler. insn_idx ) ;
152+ if entry . opnd_types . is_empty ( ) {
153+ entry . opnd_types . resize ( 1 , TypeDistribution :: new ( ) ) ;
153154 }
154155 let obj = profiler. peek_at_block_handler ( ) ;
155156 let ty = ProfiledType :: object ( obj) ;
156157 VALUE :: from ( profiler. iseq ) . write_barrier ( ty. class ( ) ) ;
157- types [ 0 ] . observe ( ty) ;
158+ entry . opnd_types [ 0 ] . observe ( ty) ;
158159}
159160
160161fn profile_getblockparamproxy ( profiler : & mut Profiler , profile : & mut IseqProfile ) {
161- let types = & mut profile. opnd_types [ profiler. insn_idx ] ;
162- if types . is_empty ( ) {
163- types . resize ( 1 , TypeDistribution :: new ( ) ) ;
162+ let entry = profile. entry_mut ( profiler. insn_idx ) ;
163+ if entry . opnd_types . is_empty ( ) {
164+ entry . opnd_types . resize ( 1 , TypeDistribution :: new ( ) ) ;
164165 }
165166
166167 let level = profiler. insn_opnd ( 1 ) . as_u32 ( ) ;
@@ -170,7 +171,7 @@ fn profile_getblockparamproxy(profiler: &mut Profiler, profile: &mut IseqProfile
170171
171172 let ty = ProfiledType :: object ( untagged) ;
172173 VALUE :: from ( profiler. iseq ) . write_barrier ( ty. class ( ) ) ;
173- types [ 0 ] . observe ( ty) ;
174+ entry . opnd_types [ 0 ] . observe ( ty) ;
174175}
175176
176177fn profile_invokesuper ( profiler : & mut Profiler , profile : & mut IseqProfile ) {
@@ -374,30 +375,61 @@ impl ProfiledType {
374375 }
375376}
376377
378+ /// Per-instruction profile entry, stored sparsely in a sorted Vec.
377379#[ derive( Debug ) ]
378- pub struct IseqProfile {
379- /// Type information of YARV instruction operands, indexed by the instruction index
380- opnd_types : Vec < Vec < TypeDistribution > > ,
380+ struct ProfileEntry {
381+ /// YARV instruction index
382+ insn_idx : u32 ,
383+ /// Type information of YARV instruction operands
384+ opnd_types : Vec < TypeDistribution > ,
385+ /// Number of profiled executions for this YARV instruction
386+ num_profiles : NumProfiles ,
387+ }
381388
382- /// Number of profiled executions for each YARV instruction, indexed by the instruction index
383- num_profiles : Vec < NumProfiles > ,
389+ #[ derive( Debug ) ]
390+ pub struct IseqProfile {
391+ /// Sparse storage of per-instruction profile data, sorted by instruction index.
392+ /// Only instructions that have actually been profiled have entries here.
393+ entries : Vec < ProfileEntry > ,
384394
385395 /// Method entries for `super` calls (stored as VALUE to be GC-safe)
386396 super_cme : HashMap < usize , TypeDistribution >
387397}
388398
389399impl IseqProfile {
390- pub fn new ( iseq_size : u32 ) -> Self {
400+ pub fn new ( ) -> Self {
391401 Self {
392- opnd_types : vec ! [ vec![ ] ; iseq_size as usize ] ,
393- num_profiles : vec ! [ 0 ; iseq_size as usize ] ,
402+ entries : Vec :: new ( ) ,
394403 super_cme : HashMap :: new ( ) ,
395404 }
396405 }
397406
407+ /// Get or create a mutable profile entry for the given instruction index.
408+ fn entry_mut ( & mut self , insn_idx : usize ) -> & mut ProfileEntry {
409+ let idx = insn_idx as u32 ;
410+ match self . entries . binary_search_by_key ( & idx, |e| e. insn_idx ) {
411+ Ok ( i) => & mut self . entries [ i] ,
412+ Err ( i) => {
413+ self . entries . insert ( i, ProfileEntry {
414+ insn_idx : idx,
415+ opnd_types : Vec :: new ( ) ,
416+ num_profiles : 0 ,
417+ } ) ;
418+ & mut self . entries [ i]
419+ }
420+ }
421+ }
422+
423+ /// Get a profile entry for the given instruction index (read-only).
424+ fn entry ( & self , insn_idx : usize ) -> Option < & ProfileEntry > {
425+ let idx = insn_idx as u32 ;
426+ self . entries . binary_search_by_key ( & idx, |e| e. insn_idx )
427+ . ok ( ) . map ( |i| & self . entries [ i] )
428+ }
429+
398430 /// Get profiled operand types for a given instruction index
399431 pub fn get_operand_types ( & self , insn_idx : usize ) -> Option < & [ TypeDistribution ] > {
400- self . opnd_types . get ( insn_idx) . map ( |v| & * * v )
432+ self . entry ( insn_idx) . map ( |e| e . opnd_types . as_slice ( ) ) . filter ( |s| !s . is_empty ( ) )
401433 }
402434
403435 pub fn get_super_method_entry ( & self , insn_idx : usize ) -> Option < * const rb_callable_method_entry_t > {
@@ -413,8 +445,8 @@ impl IseqProfile {
413445
414446 /// Run a given callback with every object in IseqProfile
415447 pub fn each_object ( & self , callback : impl Fn ( VALUE ) ) {
416- for operands in & self . opnd_types {
417- for distribution in operands {
448+ for entry in & self . entries {
449+ for distribution in & entry . opnd_types {
418450 for profiled_type in distribution. each_item ( ) {
419451 // If the type is a GC object, call the callback
420452 callback ( profiled_type. class ) ;
@@ -431,8 +463,8 @@ impl IseqProfile {
431463
432464 /// Run a given callback with a mutable reference to every object in IseqProfile.
433465 pub fn each_object_mut ( & mut self , callback : impl Fn ( & mut VALUE ) ) {
434- for operands in & mut self . opnd_types {
435- for distribution in operands {
466+ for entry in & mut self . entries {
467+ for distribution in & mut entry . opnd_types {
436468 for ref mut profiled_type in distribution. each_item_mut ( ) {
437469 // If the type is a GC object, call the callback
438470 callback ( & mut profiled_type. class ) ;
0 commit comments