@@ -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
@@ -1257,29 +1232,18 @@ export function focusSubtree(
12571232 const prefixDepth = callNodePath . length ;
12581233 const stackMatches = new Int32Array ( stackTable . length ) ;
12591234 const funcMatchesImplementation = FUNC_MATCHES [ implementation ] ;
1260- const oldStackToNewStack : Map <
1261- IndexIntoStackTable | null ,
1262- IndexIntoStackTable | null
1263- > = new Map ( ) ;
1264- // A root stack's prefix will be null. Maintain that relationship from old to new
1265- // stacks by mapping from null to null.
1266- oldStackToNewStack . set ( null , null ) ;
1267- const newFrameCol = [ ] ;
1268- const newPrefixCol = [ ] ;
1269- const newCategoryCol = [ ] ;
1270- const newSubcategoryCol = [ ] ;
1271- let newLength = 0 ;
1235+ const oldStackToNewStack = new Int32Array ( stackTable . length ) . fill ( - 1 ) ;
1236+ const newPrefixCol : Array < IndexIntoStackTable | null > = [ ] ;
1237+ const keepStack = makeBitSet ( stackTable . length ) ;
12721238 for ( let stackIndex = 0 ; stackIndex < stackTable . length ; stackIndex ++ ) {
12731239 const prefix = stackTable . prefix [ stackIndex ] ;
12741240 const prefixMatchesUpTo = prefix !== null ? stackMatches [ prefix ] : 0 ;
12751241 let stackMatchesUpTo = - 1 ;
12761242 if ( prefixMatchesUpTo !== - 1 ) {
1277- const frame = stackTable . frame [ stackIndex ] ;
1278- const category = stackTable . category [ stackIndex ] ;
1279- const subcategory = stackTable . subcategory [ stackIndex ] ;
12801243 if ( prefixMatchesUpTo === prefixDepth ) {
12811244 stackMatchesUpTo = prefixDepth ;
12821245 } else {
1246+ const frame = stackTable . frame [ stackIndex ] ;
12831247 const funcIndex = frameTable . func [ frame ] ;
12841248 if ( funcIndex === callNodePath [ prefixMatchesUpTo ] ) {
12851249 stackMatchesUpTo = prefixMatchesUpTo + 1 ;
@@ -1288,41 +1252,25 @@ export function focusSubtree(
12881252 }
12891253 }
12901254 if ( stackMatchesUpTo === prefixDepth ) {
1291- const newStackIndex = newLength ++ ;
1292- const newStackPrefix = oldStackToNewStack . get ( prefix ) ;
1293- newPrefixCol [ newStackIndex ] = newStackPrefix ?? null ;
1294- newFrameCol [ newStackIndex ] = frame ;
1295- newCategoryCol [ newStackIndex ] = category ;
1296- newSubcategoryCol [ newStackIndex ] = subcategory ;
1297- oldStackToNewStack . set ( stackIndex , newStackIndex ) ;
1255+ const prefixNewStack =
1256+ prefix === null ? - 1 : oldStackToNewStack [ prefix ] ;
1257+ oldStackToNewStack [ stackIndex ] = newPrefixCol . length ;
1258+ newPrefixCol . push ( prefixNewStack === - 1 ? null : prefixNewStack ) ;
1259+ setBit ( keepStack , stackIndex ) ;
12981260 }
12991261 }
13001262 stackMatches [ stackIndex ] = stackMatchesUpTo ;
13011263 }
13021264
1303- const newStackTable = {
1304- frame : newFrameCol ,
1305- prefix : newPrefixCol ,
1306- category : new Uint8Array ( newCategoryCol ) ,
1307- subcategory :
1308- stackTable . subcategory instanceof Uint8Array
1309- ? new Uint8Array ( newSubcategoryCol )
1310- : new Uint16Array ( newSubcategoryCol ) ,
1311- length : newLength ,
1312- } ;
1313-
1314- return updateThreadStacks ( thread , newStackTable , ( oldStack ) => {
1315- if ( oldStack === null || stackMatches [ oldStack ] !== prefixDepth ) {
1316- return null ;
1317- }
1318- const newStack = oldStackToNewStack . get ( oldStack ) ;
1319- if ( newStack === undefined ) {
1320- throw new Error (
1321- 'Converting from the old stack to a new stack cannot be undefined'
1322- ) ;
1323- }
1324- return newStack ;
1325- } ) ;
1265+ const newStackTable = createStackTableBySkippingDiscarded (
1266+ stackTable ,
1267+ newPrefixCol ,
1268+ keepStack
1269+ ) ;
1270+ return applyTransformOutputToThread (
1271+ { newStackTable, effectOnThreadData : { oldStackToNewStack } } ,
1272+ thread
1273+ ) ;
13261274 } ) ;
13271275}
13281276
@@ -1386,53 +1334,32 @@ export function focusFunction(
13861334) : Thread {
13871335 return timeCode ( 'focusFunction' , ( ) => {
13881336 const { stackTable, frameTable } = thread ;
1389- // A map oldStack -> newStack+1, implemented as a Uint32Array for performance.
1390- // If newStack+1 is zero it means "null", i.e. this stack was filtered out.
1391- // Typed arrays are initialized to zero, which we interpret as null.
1392- const oldStackToNewStackPlusOne = new Uint32Array ( stackTable . length ) ;
1393-
1394- const newFrameCol = [ ] ;
1395- const newPrefixCol = [ ] ;
1396- const newCategoryCol = [ ] ;
1397- const newSubcategoryCol = [ ] ;
1398- let newLength = 0 ;
1337+ const oldStackToNewStack = new Int32Array ( stackTable . length ) . fill ( - 1 ) ;
1338+ const newPrefixCol : Array < IndexIntoStackTable | null > = [ ] ;
1339+ const keepStack = makeBitSet ( stackTable . length ) ;
13991340
14001341 for ( let stackIndex = 0 ; stackIndex < stackTable . length ; stackIndex ++ ) {
14011342 const prefix = stackTable . prefix [ stackIndex ] ;
14021343 const frameIndex = stackTable . frame [ stackIndex ] ;
14031344 const funcIndex = frameTable . func [ frameIndex ] ;
14041345
1405- const newPrefixPlusOne =
1406- prefix === null ? 0 : oldStackToNewStackPlusOne [ prefix ] ;
1407- const newPrefix = newPrefixPlusOne === 0 ? null : newPrefixPlusOne - 1 ;
1408- if ( newPrefix !== null || funcIndex === funcIndexToFocus ) {
1409- const newStackIndex = newLength ++ ;
1410- newPrefixCol [ newStackIndex ] = newPrefix ;
1411- newFrameCol [ newStackIndex ] = frameIndex ;
1412- newCategoryCol [ newStackIndex ] = stackTable . category [ stackIndex ] ;
1413- newSubcategoryCol [ newStackIndex ] = stackTable . subcategory [ stackIndex ] ;
1414- oldStackToNewStackPlusOne [ stackIndex ] = newStackIndex + 1 ;
1346+ const prefixNewStack = prefix === null ? - 1 : oldStackToNewStack [ prefix ] ;
1347+ if ( prefixNewStack !== - 1 || funcIndex === funcIndexToFocus ) {
1348+ oldStackToNewStack [ stackIndex ] = newPrefixCol . length ;
1349+ newPrefixCol . push ( prefixNewStack === - 1 ? null : prefixNewStack ) ;
1350+ setBit ( keepStack , stackIndex ) ;
14151351 }
14161352 }
14171353
1418- const newStackTable = {
1419- frame : newFrameCol ,
1420- prefix : newPrefixCol ,
1421- category : new Uint8Array ( newCategoryCol ) ,
1422- subcategory :
1423- stackTable . subcategory instanceof Uint8Array
1424- ? new Uint8Array ( newSubcategoryCol )
1425- : new Uint16Array ( newSubcategoryCol ) ,
1426- length : newLength ,
1427- } ;
1428-
1429- return updateThreadStacks ( thread , newStackTable , ( oldStack ) => {
1430- if ( oldStack === null ) {
1431- return null ;
1432- }
1433- const newStackPlusOne = oldStackToNewStackPlusOne [ oldStack ] ;
1434- return newStackPlusOne === 0 ? null : newStackPlusOne - 1 ;
1435- } ) ;
1354+ const newStackTable = createStackTableBySkippingDiscarded (
1355+ stackTable ,
1356+ newPrefixCol ,
1357+ keepStack
1358+ ) ;
1359+ return applyTransformOutputToThread (
1360+ { newStackTable, effectOnThreadData : { oldStackToNewStack } } ,
1361+ thread
1362+ ) ;
14361363 } ) ;
14371364}
14381365
@@ -1498,56 +1425,34 @@ export function focusSelf(
14981425export function focusCategory ( thread : Thread , category : IndexIntoCategoryList ) {
14991426 return timeCode ( 'focusCategory' , ( ) => {
15001427 const { stackTable } = thread ;
1501- const oldStackToNewStack : Map <
1502- IndexIntoStackTable | null ,
1503- IndexIntoStackTable | null
1504- > = new Map ( ) ;
1505- oldStackToNewStack . set ( null , null ) ;
1506-
1507- const newFrameCol = [ ] ;
1508- const newPrefixCol = [ ] ;
1509- const newCategoryCol = [ ] ;
1510- const newSubcategoryCol = [ ] ;
1511- let newLength = 0 ;
1428+ const oldStackToNewStack = new Int32Array ( stackTable . length ) . fill ( - 1 ) ;
1429+ const newPrefixCol : Array < IndexIntoStackTable | null > = [ ] ;
1430+ const keepStack = makeBitSet ( stackTable . length ) ;
15121431
15131432 // fill the new stack table with the kept frames
15141433 for ( let stackIndex = 0 ; stackIndex < stackTable . length ; stackIndex ++ ) {
15151434 const prefix = stackTable . prefix [ stackIndex ] ;
1516- const newPrefix = oldStackToNewStack . get ( prefix ) ;
1517- if ( newPrefix === undefined ) {
1518- throw new Error ( 'The prefix should not map to an undefined value' ) ;
1519- }
1435+ const prefixNewStack = prefix === null ? - 1 : oldStackToNewStack [ prefix ] ;
15201436
15211437 if ( stackTable . category [ stackIndex ] !== category ) {
1522- oldStackToNewStack . set ( stackIndex , newPrefix ) ;
1438+ oldStackToNewStack [ stackIndex ] = prefixNewStack ;
15231439 continue ;
15241440 }
15251441
1526- const newStackIndex = newLength ++ ;
1527- newPrefixCol [ newStackIndex ] = newPrefix ;
1528- newFrameCol [ newStackIndex ] = stackTable . frame [ stackIndex ] ;
1529- newCategoryCol [ newStackIndex ] = stackTable . category [ stackIndex ] ;
1530- newSubcategoryCol [ newStackIndex ] = stackTable . subcategory [ stackIndex ] ;
1531- oldStackToNewStack . set ( stackIndex , newStackIndex ) ;
1442+ oldStackToNewStack [ stackIndex ] = newPrefixCol . length ;
1443+ newPrefixCol . push ( prefixNewStack === - 1 ? null : prefixNewStack ) ;
1444+ setBit ( keepStack , stackIndex ) ;
15321445 }
15331446
1534- const newStackTable = {
1535- frame : newFrameCol ,
1536- prefix : newPrefixCol ,
1537- category : new Uint8Array ( newCategoryCol ) ,
1538- subcategory :
1539- stackTable . subcategory instanceof Uint8Array
1540- ? new Uint8Array ( newSubcategoryCol )
1541- : new Uint16Array ( newSubcategoryCol ) ,
1542- length : newLength ,
1543- } ;
1544-
1545- const updated = updateThreadStacks (
1546- thread ,
1547- newStackTable ,
1548- getMapStackUpdater ( oldStackToNewStack )
1447+ const newStackTable = createStackTableBySkippingDiscarded (
1448+ stackTable ,
1449+ newPrefixCol ,
1450+ keepStack
1451+ ) ;
1452+ return applyTransformOutputToThread (
1453+ { newStackTable, effectOnThreadData : { oldStackToNewStack } } ,
1454+ thread
15491455 ) ;
1550- return updated ;
15511456 } ) ;
15521457}
15531458
0 commit comments