diff --git a/src/profile-logic/call-tree.ts b/src/profile-logic/call-tree.ts index 57495bec52..75edb0346b 100644 --- a/src/profile-logic/call-tree.ts +++ b/src/profile-logic/call-tree.ts @@ -34,7 +34,7 @@ import { ResourceType } from 'firefox-profiler/types'; import ExtensionIcon from '../../res/img/svg/extension.svg'; import { formatCallNodeNumber, formatPercent } from '../utils/format-numbers'; import { assertExhaustiveCheck, ensureExists } from '../utils/types'; -import { checkBit } from '../utils/bitset'; +import { type BitSet, checkBit, makeBitSet, setBit } from '../utils/bitset'; import * as ProfileData from './profile-data'; import type { CallTreeSummaryStrategy } from '../types/actions'; import type { CallNodeInfo, CallNodeInfoInverted } from './call-node-info'; @@ -43,7 +43,7 @@ import { getBottomBoxInfoForCallNode } from './bottom-box'; type CallNodeChildren = IndexIntoCallNodeTable[]; export type CallTreeTimingsNonInverted = { - callNodeHasChildren: Uint8Array; + callNodeHasChildren: BitSet; self: Float64Array; total: Float64Array; rootTotalSummary: number; // sum of absolute values, this is used for computing percentages @@ -61,7 +61,7 @@ export type CallTreeTimingsInverted = { rootTotalSummary: number; sortedRoots: IndexIntoFuncTable[]; totalPerRootFunc: Float64Array; - hasChildrenPerRootFunc: Uint8Array; + hasChildrenPerRootFunc: BitSet; }; export type CallTreeTimingsFunctionList = { @@ -119,7 +119,7 @@ export class CallTreeInternalNonInverted implements CallTreeInternal { _callNodeInfo: CallNodeInfo; _callNodeTable: CallNodeTable; _callTreeTimings: CallTreeTimingsNonInverted; - _callNodeHasChildren: Uint8Array; // A table column matching the callNodeTable + _callNodeHasChildren: BitSet; // A table column matching the callNodeTable constructor( callNodeInfo: CallNodeInfo, @@ -157,9 +157,12 @@ export class CallTreeInternalNonInverted implements CallTreeInternal { childCallNodeIndex = this._callNodeTable.nextSibling[childCallNodeIndex] ) { const childTotalSummary = this._callTreeTimings.total[childCallNodeIndex]; - const childHasChildren = this._callNodeHasChildren[childCallNodeIndex]; + const childHasChildren = checkBit( + this._callNodeHasChildren, + childCallNodeIndex + ); - if (childTotalSummary !== 0 || childHasChildren !== 0) { + if (childTotalSummary !== 0 || childHasChildren) { children.push(childCallNodeIndex); } } @@ -172,7 +175,7 @@ export class CallTreeInternalNonInverted implements CallTreeInternal { } hasChildren(callNodeIndex: IndexIntoCallNodeTable): boolean { - return this._callNodeHasChildren[callNodeIndex] !== 0; + return checkBit(this._callNodeHasChildren, callNodeIndex); } getSelfAndTotal(callNodeIndex: IndexIntoCallNodeTable): SelfAndTotal { @@ -242,7 +245,7 @@ class CallTreeInternalInverted implements CallTreeInternal { _callNodeSelf: Float64Array; _rootNodes: IndexIntoCallNodeTable[]; _totalPerRootFunc: Float64Array; - _hasChildrenPerRootFunc: Uint8Array; + _hasChildrenPerRootFunc: BitSet; _totalAndHasChildrenPerNonRootNode: Map< IndexIntoCallNodeTable, TotalAndHasChildren @@ -268,7 +271,7 @@ class CallTreeInternalInverted implements CallTreeInternal { hasChildren(callNodeIndex: IndexIntoCallNodeTable): boolean { if (this._callNodeInfo.isRoot(callNodeIndex)) { - return this._hasChildrenPerRootFunc[callNodeIndex] !== 0; + return checkBit(this._hasChildrenPerRootFunc, callNodeIndex); } return this._getTotalAndHasChildren(callNodeIndex).hasChildren; } @@ -790,8 +793,8 @@ export function computeCallTreeTimingsInverted( const callNodeTableFuncCol = callNodeTable.func; const callNodeTableDepthCol = callNodeTable.depth; const totalPerRootFunc = new Float64Array(funcCount); - const hasChildrenPerRootFunc = new Uint8Array(funcCount); - const seenPerRootFunc = new Uint8Array(funcCount); + const hasChildrenPerRootFunc = makeBitSet(funcCount); + const seenPerRootFunc = makeBitSet(funcCount); const sortedRoots = []; for (let i = 0; i < callNodeSelf.length; i++) { const self = callNodeSelf[i]; @@ -805,12 +808,12 @@ export function computeCallTreeTimingsInverted( const func = callNodeTableFuncCol[i]; totalPerRootFunc[func] += self; - if (seenPerRootFunc[func] === 0) { - seenPerRootFunc[func] = 1; + if (!checkBit(seenPerRootFunc, func)) { + setBit(seenPerRootFunc, func); sortedRoots.push(func); } if (callNodeTableDepthCol[i] !== 0) { - hasChildrenPerRootFunc[func] = 1; + setBit(hasChildrenPerRootFunc, func); } } sortedRoots.sort( @@ -861,7 +864,7 @@ export function computeCallTreeTimingsNonInverted( // Compute the following variables: const callNodeTotal = new Float64Array(callNodeTable.length); - const callNodeHasChildren = new Uint8Array(callNodeTable.length); + const callNodeHasChildren = makeBitSet(callNodeTable.length); // We loop the call node table in reverse, so that we find the children // before their parents, and the total is known at the time we reach a @@ -874,7 +877,7 @@ export function computeCallTreeTimingsNonInverted( // callNodeTotal[callNodeIndex] is the sum of our children's totals. // Compute this node's total by adding this node's self. const total = callNodeTotal[callNodeIndex] + callNodeSelf[callNodeIndex]; - const hasChildren = callNodeHasChildren[callNodeIndex] !== 0; + const hasChildren = checkBit(callNodeHasChildren, callNodeIndex); const hasTotalValue = total !== 0; callNodeTotal[callNodeIndex] = total; @@ -886,7 +889,7 @@ export function computeCallTreeTimingsNonInverted( const prefixCallNode = callNodeTable.prefix[callNodeIndex]; if (prefixCallNode !== -1) { callNodeTotal[prefixCallNode] += total; - callNodeHasChildren[prefixCallNode] = 1; + setBit(callNodeHasChildren, prefixCallNode); } } @@ -940,7 +943,7 @@ function _computeFuncTotal( // The set of "functions with potentially non-zero totals", stored as an array. const seenFuncs = []; // seenPerFunc[func] stores whether seenFuncs.includes(func). - const seenPerFunc = new Uint8Array(funcCount); + const seenPerFunc = makeBitSet(funcCount); // We loop the call node table in reverse, so that we find the children // before their parents, and the total is known at the time we reach a @@ -979,8 +982,8 @@ function _computeFuncTotal( funcTotal[func] += total; // Add this func to the set of funcs with potentially non-zero totals. - if (seenPerFunc[func] === 0) { - seenPerFunc[func] = 1; + if (!checkBit(seenPerFunc, func)) { + setBit(seenPerFunc, func); seenFuncs.push(func); } } diff --git a/src/profile-logic/profile-data.ts b/src/profile-logic/profile-data.ts index 49a521c9f1..51edacc88a 100644 --- a/src/profile-logic/profile-data.ts +++ b/src/profile-logic/profile-data.ts @@ -520,7 +520,7 @@ function _computeCallNodeTableExtraColumns( const innerWindowIDCol = new Float64Array(callNodeCount); const inlinedIntoCol = new Int32Array(callNodeCount); - const haveFilled = new Uint8Array(callNodeCount); + const haveFilled = makeBitSet(callNodeCount); for (let stackIndex = 0; stackIndex < stackCount; stackIndex++) { const category = stackTableCategoryCol[stackIndex]; @@ -530,7 +530,7 @@ function _computeCallNodeTableExtraColumns( const callNodeIndex = stackIndexToCallNodeIndex[stackIndex]; - if (haveFilled[callNodeIndex] === 0) { + if (!checkBit(haveFilled, callNodeIndex)) { funcCol[callNodeIndex] = frameTableFuncCol[frameIndex]; categoryCol[callNodeIndex] = category; @@ -544,7 +544,7 @@ function _computeCallNodeTableExtraColumns( innerWindowIDCol[callNodeIndex] = innerWindowID; } - haveFilled[callNodeIndex] = 1; + setBit(haveFilled, callNodeIndex); } else { // Resolve category conflicts, by resetting a conflicting subcategory or // category to the default category. diff --git a/src/profile-logic/symbolication.ts b/src/profile-logic/symbolication.ts index 3186fb7b3a..ae423e317e 100644 --- a/src/profile-logic/symbolication.ts +++ b/src/profile-logic/symbolication.ts @@ -32,6 +32,7 @@ import type { import { PathSet } from '../utils/path'; import { StringTable } from '../utils/string-table'; import { updateRawThreadStacks } from './profile-data'; +import { type BitSet, makeBitSet, setBit, checkBit } from '../utils/bitset'; // Contains functions to symbolicate a profile. @@ -411,7 +412,7 @@ function finishSymbolicationForLib( // - stack F with frame 5 function _computeStackTableWithAddedExpansionStacks( stackTable: RawStackTable, - shouldStacksWithThisOldFrameBeRemoved: Uint8Array, + shouldStacksWithThisOldFrameBeRemoved: BitSet, frameIndexToInlineExpansionFrames: Map< IndexIntoFrameTable, IndexIntoFrameTable[] @@ -427,7 +428,7 @@ function _computeStackTableWithAddedExpansionStacks( const oldPrefix = stackTable.prefix[stack]; const newPrefixOrMinusOne = oldPrefix === null ? -1 : oldStackToNewStack[oldPrefix]; - if (shouldStacksWithThisOldFrameBeRemoved[oldFrame] !== 0) { + if (checkBit(shouldStacksWithThisOldFrameBeRemoved, oldFrame)) { // Don't add this stack node to the new stack table. Instead, make it // so that this node's children use our prefix as their prefix. oldStackToNewStack[stack] = newPrefixOrMinusOne; @@ -470,7 +471,7 @@ export function applySymbolicationSteps( } { const oldFuncToNewFuncsMap: FuncToFuncsMap = new Map(); const frameCount = oldShared.frameTable.length; - const shouldStacksWithThisFrameBeRemoved = new Uint8Array(frameCount); + const shouldStacksWithThisFrameBeRemoved = makeBitSet(frameCount); const frameIndexToInlineExpansionFrames = new Map< IndexIntoFrameTable, IndexIntoFrameTable[] @@ -541,7 +542,7 @@ function _partiallyApplySymbolicationStep( shared: RawProfileSharedData, symbolicationStepInfo: SymbolicationStepInfo, oldFuncToNewFuncsMap: FuncToFuncsMap, - shouldStacksWithThisFrameBeRemoved: Uint8Array, + shouldStacksWithThisFrameBeRemoved: BitSet, frameIndexToInlineExpansionFrames: Map< IndexIntoFrameTable, IndexIntoFrameTable[] @@ -585,7 +586,7 @@ function _partiallyApplySymbolicationStep( for (const frameIndex of allFramesForThisLib) { if (oldFrameTable.inlineDepth[frameIndex] > 0) { inlinedFrames.push(frameIndex); - shouldStacksWithThisFrameBeRemoved[frameIndex] = 1; + setBit(shouldStacksWithThisFrameBeRemoved, frameIndex); } else { nonInlinedFrames.push(frameIndex); } diff --git a/src/profile-logic/transforms.ts b/src/profile-logic/transforms.ts index 604b7f1179..771991998e 100644 --- a/src/profile-logic/transforms.ts +++ b/src/profile-logic/transforms.ts @@ -54,6 +54,7 @@ import { translateFuncIndex, translateResourceIndex, } from './index-translation'; +import { checkBit, makeBitSet, setBit } from 'firefox-profiler/utils/bitset'; /** * This file contains the functions and logic for working with and applying transforms @@ -926,9 +927,7 @@ export function dropFunction( const { stackTable, frameTable } = thread; // Go through each stack, and label it as containing the function or not. - // stackContainsFunc is a stackIndex => bool map, implemented as a U8 typed - // array for better performance. 0 means false, 1 means true. - const stackContainsFunc = new Uint8Array(stackTable.length); + const stackContainsFunc = makeBitSet(stackTable.length); for (let stackIndex = 0; stackIndex < stackTable.length; stackIndex++) { const prefix = stackTable.prefix[stackIndex]; const frameIndex = stackTable.frame[stackIndex]; @@ -937,15 +936,15 @@ export function dropFunction( // This is the function we want to remove. funcIndex === funcIndexToDrop || // The parent of this stack contained the function. - (prefix !== null && stackContainsFunc[prefix] === 1) + (prefix !== null && checkBit(stackContainsFunc, prefix)) ) { - stackContainsFunc[stackIndex] = 1; + setBit(stackContainsFunc, stackIndex); } } return updateThreadStacks(thread, stackTable, (stack) => // Drop the stacks that contain that function. - stack !== null && stackContainsFunc[stack] === 1 ? null : stack + stack !== null && checkBit(stackContainsFunc, stack) ? null : stack ); } @@ -1214,19 +1213,19 @@ export function collapseFunctionSubtree( ): Thread { const { stackTable, frameTable } = thread; const oldStackToNewStack = new Int32Array(stackTable.length); - const isInCollapsedSubtree = new Uint8Array(stackTable.length); + const isInCollapsedSubtree = makeBitSet(stackTable.length); for (let stackIndex = 0; stackIndex < stackTable.length; stackIndex++) { const prefix = stackTable.prefix[stackIndex]; - if (prefix !== null && isInCollapsedSubtree[prefix] !== 0) { + if (prefix !== null && checkBit(isInCollapsedSubtree, prefix)) { oldStackToNewStack[stackIndex] = oldStackToNewStack[prefix]; - isInCollapsedSubtree[stackIndex] = 1; + setBit(isInCollapsedSubtree, stackIndex); } else { oldStackToNewStack[stackIndex] = stackIndex; const frameIndex = stackTable.frame[stackIndex]; const funcIndex = frameTable.func[frameIndex]; if (funcToCollapse === funcIndex) { - isInCollapsedSubtree[stackIndex] = 1; + setBit(isInCollapsedSubtree, stackIndex); } } } @@ -1740,7 +1739,7 @@ export function funcHasRecursiveCall( funcToCheck: IndexIntoFuncTable ) { // Set of stack indices that are funcToCheck or have a funcToCheck ancestor. - const ancestorOfCallNodeContainsFuncToCheck = new Uint8Array( + const ancestorOfCallNodeContainsFuncToCheck = makeBitSet( callNodeTable.length ); @@ -1748,16 +1747,16 @@ export function funcHasRecursiveCall( const prefix = callNodeTable.prefix[i]; const funcIndex = callNodeTable.func[i]; const recursivePrefix = - prefix !== -1 && ancestorOfCallNodeContainsFuncToCheck[prefix] !== 0; + prefix !== -1 && checkBit(ancestorOfCallNodeContainsFuncToCheck, prefix); if (funcToCheck === funcIndex) { if (recursivePrefix) { // This function matches and so did one of its ancestors. return true; } - ancestorOfCallNodeContainsFuncToCheck[i] = 1; + setBit(ancestorOfCallNodeContainsFuncToCheck, i); } else if (recursivePrefix) { - ancestorOfCallNodeContainsFuncToCheck[i] = 1; + setBit(ancestorOfCallNodeContainsFuncToCheck, i); } } return false; diff --git a/src/test/store/__snapshots__/profile-view.test.ts.snap b/src/test/store/__snapshots__/profile-view.test.ts.snap index f04baf9582..179d6e9125 100644 --- a/src/test/store/__snapshots__/profile-view.test.ts.snap +++ b/src/test/store/__snapshots__/profile-view.test.ts.snap @@ -2069,16 +2069,8 @@ CallTree { "_children": Array [], "_displayDataByIndex": Map {}, "_internal": CallTreeInternalNonInverted { - "_callNodeHasChildren": Uint8Array [ - 1, - 1, - 0, - 0, - 0, - 1, - 0, - 0, - 0, + "_callNodeHasChildren": Int32Array [ + 35, ], "_callNodeInfo": CallNodeInfoNonInverted { "_cache": Map {}, @@ -2301,16 +2293,8 @@ CallTree { ], }, "_callTreeTimings": Object { - "callNodeHasChildren": Uint8Array [ - 1, - 1, - 0, - 0, - 0, - 1, - 0, - 0, - 0, + "callNodeHasChildren": Int32Array [ + 35, ], "rootTotalSummary": 2, "self": Float64Array [ diff --git a/src/test/unit/profile-tree.test.ts b/src/test/unit/profile-tree.test.ts index f50cd99fe8..f71d94784d 100644 --- a/src/test/unit/profile-tree.test.ts +++ b/src/test/unit/profile-tree.test.ts @@ -20,6 +20,7 @@ import { getSampleIndexToCallNodeIndex, } from '../../profile-logic/profile-data'; import { ResourceType } from 'firefox-profiler/types'; +import { makeBitSet, setBit } from '../../utils/bitset'; import { callTreeFromProfile, functionListTreeFromProfile, @@ -79,11 +80,16 @@ describe('unfiltered call tree', function () { callNodeInfo.getCallNodeTable().length ) ); + const expectedHasChildren = makeBitSet(9); + // Nodes 0, 1, 2, 3, 5, 7 have children. + for (const i of [0, 1, 2, 3, 5, 7]) { + setBit(expectedHasChildren, i); + } expect(callTreeTimings).toEqual({ type: 'NON_INVERTED', timings: { rootTotalSummary: 3, - callNodeHasChildren: new Uint8Array([1, 1, 1, 1, 0, 1, 0, 1, 0]), + callNodeHasChildren: expectedHasChildren, self: new Float64Array([0, 0, 0, 0, 1, 0, 1, 0, 1]), total: new Float64Array([3, 3, 2, 1, 1, 1, 1, 1, 1]), },