@@ -10,7 +10,6 @@ import {
1010 toValidImplementationFilter ,
1111 updateThreadStacks ,
1212 updateThreadStacksByGeneratingNewStackColumns ,
13- getMapStackUpdater ,
1413 getOriginAnnotationForFunc ,
1514 createStackTableBySkippingDiscarded ,
1615 applyTransformOutputToThread ,
@@ -759,102 +758,78 @@ export function mergeCallNode(
759758) : Thread {
760759 return timeCode ( 'mergeCallNode' , ( ) => {
761760 const { stackTable, frameTable } = thread ;
762- // Depth here is 0 indexed.
763- const depthAtCallNodePathLeaf = callNodePath . length - 1 ;
764- const oldStackToNewStack : Map <
761+
762+ // Maps merged stacks to their effective parent (the stack that samples pointing
763+ // to the merged stack should be attributed to). Only contains entries for merged
764+ // stacks; the vast majority of stacks are not merged and map to themselves.
765+ const mergedStackToEffectiveParent = new Map <
765766 IndexIntoStackTable | null ,
766767 IndexIntoStackTable | null
767- > = new Map ( ) ;
768- // A root stack's prefix will be null. Maintain that relationship from old to new
769- // stacks by mapping from null to null.
770- oldStackToNewStack . set ( null , null ) ;
771- const newFrameCol = [ ] ;
772- const newPrefixCol = [ ] ;
773- const newCategoryCol = [ ] ;
774- const newSubcategoryCol = [ ] ;
775- let newLength = 0 ;
776- // Provide two arrays to efficiently cache values for the algorithm. This probably
777- // could be refactored to use only one array here.
778- const stackDepths = [ ] ;
779- const stackMatches = [ ] ;
768+ > ( ) ;
769+ const newPrefixCol = new Array < IndexIntoStackTable | null > (
770+ stackTable . length
771+ ) ;
772+
780773 const funcMatchesImplementation = FUNC_MATCHES [ implementation ] ;
774+
775+ const callNodePathLength = callNodePath . length ;
776+ // A map to keep track of whether a stack matches (part of) the call node path.
777+ // If undefined: no match
778+ // Otherwise: length of the partial match, including this stack
779+ // All values are < callNodePathLength.
780+ const partialMatchLengthAtStack = new Map <
781+ IndexIntoStackTable | null ,
782+ number
783+ > ( ) ;
784+ partialMatchLengthAtStack . set ( null , 0 ) ;
781785 for ( let stackIndex = 0 ; stackIndex < stackTable . length ; stackIndex ++ ) {
782786 const prefix = stackTable . prefix [ stackIndex ] ;
787+
788+ // If our prefix got merged away, remap it to its parent.
789+ const parentOfPrefix = mergedStackToEffectiveParent . get ( prefix ) ;
790+ const effectivePrefix =
791+ parentOfPrefix !== undefined ? parentOfPrefix : prefix ;
792+ newPrefixCol [ stackIndex ] = effectivePrefix ;
793+
794+ const prefixPartialMatchLength = partialMatchLengthAtStack . get ( prefix ) ;
795+ if ( prefixPartialMatchLength === undefined ) {
796+ // No match, nothing else to do here
797+ continue ;
798+ }
799+
800+ // Now we know that this stack's prefix was a (partial) match for our CallNodePath.
783801 const frameIndex = stackTable . frame [ stackIndex ] ;
784- const category = stackTable . category [ stackIndex ] ;
785- const subcategory = stackTable . subcategory [ stackIndex ] ;
786802 const funcIndex = frameTable . func [ frameIndex ] ;
787803
788- const doesPrefixMatch = prefix === null ? true : stackMatches [ prefix ] ;
789- const prefixDepth : number = prefix === null ? - 1 : stackDepths [ prefix ] ;
790- const currentFuncOnPath = callNodePath [ prefixDepth + 1 ] ;
791-
792- let doMerge = false ;
793- let stackDepth : number = prefixDepth ;
794- let doesMatchCallNodePath ;
795- if ( doesPrefixMatch && stackDepth < depthAtCallNodePathLeaf ) {
796- // This stack's prefixes were in our CallNodePath.
797- if ( currentFuncOnPath === funcIndex ) {
798- // This stack's function matches too!
799- doesMatchCallNodePath = true ;
800- if ( stackDepth + 1 === depthAtCallNodePathLeaf ) {
801- // Holy cow, we found a match for our merge operation and can merge this stack.
802- doMerge = true ;
803- } else {
804- // Since we found a match, increase the stack depth. This should match
805- // the depth of the implementation filtered stacks.
806- stackDepth ++ ;
807- }
808- } else if ( ! funcMatchesImplementation ( thread , funcIndex ) ) {
809- // This stack's function does not match the CallNodePath, however it's not part
810- // of the CallNodePath's implementation filter. Go ahead and keep it.
811- doesMatchCallNodePath = true ;
804+ if ( funcIndex === callNodePath [ prefixPartialMatchLength ] ) {
805+ // This stack's function matches too!
806+ const matchLength = prefixPartialMatchLength + 1 ;
807+ if ( matchLength === callNodePathLength ) {
808+ // The entire path matched and we found a node that needs to be merged away.
809+ mergedStackToEffectiveParent . set ( stackIndex , effectivePrefix ) ;
812810 } else {
813- // While all of the predecessors matched, this stack's function does not :(
814- doesMatchCallNodePath = false ;
811+ // Not reached the end yet, store the partial match length.
812+ partialMatchLengthAtStack . set ( stackIndex , matchLength ) ;
815813 }
814+ } else if ( ! funcMatchesImplementation ( thread , funcIndex ) ) {
815+ // This stack's function does not match the CallNodePath, however it's not part
816+ // of the CallNodePath's implementation filter. Inherit the parent's partial
817+ // match length
818+ partialMatchLengthAtStack . set ( stackIndex , prefixPartialMatchLength ) ;
816819 } else {
817- // This stack is not part of a matching branch of the tree.
818- doesMatchCallNodePath = false ;
819- }
820- stackMatches [ stackIndex ] = doesMatchCallNodePath ;
821- stackDepths [ stackIndex ] = stackDepth ;
822-
823- // Map the oldStackToNewStack, and only push on the stacks that aren't merged.
824- if ( doMerge ) {
825- const newStackPrefix = oldStackToNewStack . get ( prefix ) ;
826- oldStackToNewStack . set (
827- stackIndex ,
828- newStackPrefix === undefined ? null : newStackPrefix
829- ) ;
830- } else {
831- const newStackIndex = newLength ++ ;
832- const newStackPrefix = oldStackToNewStack . get ( prefix ) ;
833- newPrefixCol [ newStackIndex ] =
834- newStackPrefix === undefined ? null : newStackPrefix ;
835- newFrameCol [ newStackIndex ] = frameIndex ;
836- newCategoryCol [ newStackIndex ] = category ;
837- newSubcategoryCol [ newStackIndex ] = subcategory ;
838- oldStackToNewStack . set ( stackIndex , newStackIndex ) ;
820+ // While all of the predecessors matched, this stack's function does not :(
839821 }
840822 }
841823
842824 const newStackTable = {
843- frame : newFrameCol ,
825+ ... stackTable ,
844826 prefix : newPrefixCol ,
845- category : new Uint8Array ( newCategoryCol ) ,
846- subcategory :
847- stackTable . subcategory instanceof Uint8Array
848- ? new Uint8Array ( newSubcategoryCol )
849- : new Uint16Array ( newSubcategoryCol ) ,
850- length : newLength ,
851827 } ;
852828
853- return updateThreadStacks (
854- thread ,
855- newStackTable ,
856- getMapStackUpdater ( oldStackToNewStack )
857- ) ;
829+ return updateThreadStacks ( thread , newStackTable , ( oldStack ) => {
830+ const effectiveParent = mergedStackToEffectiveParent . get ( oldStack ) ;
831+ return effectiveParent !== undefined ? effectiveParent : oldStack ;
832+ } ) ;
858833 } ) ;
859834}
860835
0 commit comments