diff --git a/docs-developer/CHANGELOG-formats.md b/docs-developer/CHANGELOG-formats.md index d953a669bf..3d88bf1b8e 100644 --- a/docs-developer/CHANGELOG-formats.md +++ b/docs-developer/CHANGELOG-formats.md @@ -6,6 +6,28 @@ Note that this is not an exhaustive list. Processed profile format upgraders can ## Processed profile format +### Version 75 + +The func table (`profile.shared.funcTable`) representation changed, mirroring the v71 frame table change: + +- A new `flags` bitfield column was added. It can be stored as a plain array of numbers or as a `Uint8Array` (for profiles loaded from [JsonSlabs](https://github.com/mstange/json-slabs/) files). The bits are: + - `1 << 0` — `IsJS`: this func is a JavaScript function. + - `1 << 1` — `RelevantForJS`: this func should be treated as "relevant for JS" (e.g. DOM API label funcs). + - `1 << 2` — `HasResource`: `resource[i]` is meaningful. + - `1 << 3` — `HasSource`: `source[i]` is meaningful. + - `1 << 4` — `HasLine`: `lineNumber[i]` is meaningful. + - `1 << 5` — `HasColumn`: `columnNumber[i]` is meaningful. + - `1 << 6` — `HasOriginalLocation`: `originalLocation[i]` is meaningful. +- The `isJS` and `relevantForJS` boolean columns were removed; the `IsJS` and `RelevantForJS` flag bits carry that information. +- The `resource`, `source`, `lineNumber`, `columnNumber`, and `originalLocation` columns are no longer nullable in-band. Each may still be stored as a plain array, but the values are always numbers, and when the corresponding "Has..." flag is unset, the value in the column is ignored (producers typically write `0`). +- The following columns can now optionally be stored as typed arrays: + - `name` (`Int32Array`) + - `resource` (`Int32Array`) + - `source` (`Int32Array`) + - `lineNumber` (`Int32Array`) + - `columnNumber` (`Int32Array`) + - `originalLocation` (`Int32Array`) + ### Version 74 The columns of the native symbol table (`profile.shared.nativeSymbols`) can now optionally be stored as typed arrays, for profiles loaded from [JsonSlabs](https://github.com/mstange/json-slabs/) files (.jslb, .jslb.gz). Regular JS / JSON arrays are still accepted. diff --git a/src/actions/profile-view.ts b/src/actions/profile-view.ts index 6ef3391213..a02aeb2c48 100644 --- a/src/actions/profile-view.ts +++ b/src/actions/profile-view.ts @@ -47,6 +47,7 @@ import { getTrackReferenceFromThreadIndex, } from 'firefox-profiler/profile-logic/tracks'; +import { FuncFlag } from 'firefox-profiler/types'; import type { PreviewSelection, ImplementationFilter, @@ -2096,11 +2097,14 @@ export function handleCallNodeTransformShortcut( break; case 'C': { const { funcTable } = unfilteredThread; - const resourceIndex = funcTable.resource[funcIndex]; + if ((funcTable.flags[funcIndex] & FuncFlag.HasResource) === 0) { + // This func has no resource, so there is nothing to collapse. + return; + } dispatch( addCollapseResourceTransformToStack( threadsKey, - resourceIndex, + funcTable.resource[funcIndex], implementation ) ); diff --git a/src/app-logic/constants.ts b/src/app-logic/constants.ts index 14c3a1c395..6f9cae4e98 100644 --- a/src/app-logic/constants.ts +++ b/src/app-logic/constants.ts @@ -12,7 +12,7 @@ export const GECKO_PROFILE_VERSION = 36; // The current version of the "processed" profile format. // Please don't forget to update the processed profile format changelog in // `docs-developer/CHANGELOG-formats.md`. -export const PROCESSED_PROFILE_VERSION = 74; +export const PROCESSED_PROFILE_VERSION = 75; // The following are the margin sizes for the left and right of the timeline. Independent // components need to share these values. diff --git a/src/app-logic/url-handling.ts b/src/app-logic/url-handling.ts index 76475bf370..2e51d248b9 100644 --- a/src/app-logic/url-handling.ts +++ b/src/app-logic/url-handling.ts @@ -43,6 +43,7 @@ import type { MarkerIndex, SelectedMarkersPerThread, } from 'firefox-profiler/types'; +import { FuncFlag } from 'firefox-profiler/types'; import { decodeUintArrayFromUrlComponent, encodeUintArrayForUrlComponent, @@ -1719,7 +1720,7 @@ function getStackIndexFromVersion3JSCallNodePath( const prefix = offset === 0 ? null : stackIndex - offset; const frameIndex = stackTable.frame[stackIndex]; const funcIndex = frameTable.func[frameIndex]; - const isJS = funcTable.isJS[funcIndex]; + const isJS = (funcTable.flags[funcIndex] & FuncFlag.IsJS) !== 0; // We know that at this point stack table is sorted and the following // condition holds: // assert(prefix === null || prefix < stackIndex); @@ -1762,7 +1763,11 @@ function getVersion4JSCallNodePathFromStackIndex( while (nextStackIndex !== null) { const frameIndex: IndexIntoFrameTable = stackTable.frame[nextStackIndex]; const funcIndex = frameTable.func[frameIndex]; - if (funcTable.isJS[funcIndex] || funcTable.relevantForJS[funcIndex]) { + if ( + (funcTable.flags[funcIndex] & + (FuncFlag.IsJS | FuncFlag.RelevantForJS)) !== + 0 + ) { callNodePath.unshift(funcIndex); } const offset: number = stackTable.prefixOffset[nextStackIndex]; diff --git a/src/components/shared/CallNodeContextMenu.tsx b/src/components/shared/CallNodeContextMenu.tsx index cf7a9c391c..0603484fc4 100644 --- a/src/components/shared/CallNodeContextMenu.tsx +++ b/src/components/shared/CallNodeContextMenu.tsx @@ -56,6 +56,7 @@ import type { Page, SamplesLikeTable, } from 'firefox-profiler/types'; +import { FuncFlag } from 'firefox-profiler/types'; import type { TabSlug } from 'firefox-profiler/app-logic/tabs-handling'; import type { ConnectedProps } from 'firefox-profiler/utils/connect'; @@ -147,7 +148,7 @@ class CallNodeContextMenuImpl extends React.PureComponent { } = rightClickedCallNodeInfo; const funcIndex = callNodeInfo.funcForNode(callNodeIndex); - const isJS = funcTable.isJS[funcIndex]; + const isJS = (funcTable.flags[funcIndex] & FuncFlag.IsJS) !== 0; const stringIndex = funcTable.name[funcIndex]; const functionCall = stringTable.getString(stringIndex); const name = isJS ? functionCall : getFunctionName(functionCall); @@ -184,10 +185,10 @@ class CallNodeContextMenuImpl extends React.PureComponent { } = rightClickedCallNodeInfo; const funcIndex = callNodeInfo.funcForNode(callNodeIndex); - const sourceIndex = funcTable.source[funcIndex]; - if (sourceIndex === null) { + if ((funcTable.flags[funcIndex] & FuncFlag.HasSource) === 0) { return null; } + const sourceIndex = funcTable.source[funcIndex]; const stringIndex = sources.filename[sourceIndex]; return stringTable.getString(stringIndex); } @@ -208,8 +209,15 @@ class CallNodeContextMenuImpl extends React.PureComponent { } = rightClickedCallNodeInfo; const funcIndex = callNodeInfo.funcForNode(callNodeIndex); - const line = funcTable.lineNumber[funcIndex]; - const column = funcTable.columnNumber[funcIndex]; + const funcFlags = funcTable.flags[funcIndex]; + const line = + (funcFlags & FuncFlag.HasLine) !== 0 + ? funcTable.lineNumber[funcIndex] + : null; + const column = + (funcFlags & FuncFlag.HasColumn) !== 0 + ? funcTable.columnNumber[funcIndex] + : null; return { line, column }; } @@ -530,21 +538,22 @@ class CallNodeContextMenuImpl extends React.PureComponent { if (funcIndex === undefined) { return null; } - const isJS = funcTable.isJS[funcIndex]; + const funcFlags = funcTable.flags[funcIndex]; + const isJS = (funcFlags & FuncFlag.IsJS) !== 0; if (isJS) { - const sourceIndex = funcTable.source[funcIndex]; - if (sourceIndex === null) { + if ((funcFlags & FuncFlag.HasSource) === 0) { return null; } + const sourceIndex = funcTable.source[funcIndex]; const fileNameIndex = sources.filename[sourceIndex]; return stringTable.getString(fileNameIndex); } - const resourceIndex = funcTable.resource[funcIndex]; - if (resourceIndex === -1) { + if ((funcFlags & FuncFlag.HasResource) === 0) { return null; } + const resourceIndex = funcTable.resource[funcIndex]; const resNameStringIndex = resourceTable.name[resourceIndex]; return stringTable.getString(resNameStringIndex); } @@ -608,7 +617,7 @@ class CallNodeContextMenuImpl extends React.PureComponent { const categoryIndex = callNodeInfo.categoryForNode(callNodeIndex); const funcIndex = callNodeInfo.funcForNode(callNodeIndex); - const isJS = funcTable.isJS[funcIndex]; + const isJS = (funcTable.flags[funcIndex] & FuncFlag.IsJS) !== 0; const hasCategory = categoryIndex !== -1; // This could be the C++ library, or the JS filename. const nameForResource = this.getNameForSelectedResource(); diff --git a/src/components/tooltip/CallNode.tsx b/src/components/tooltip/CallNode.tsx index 3319f8214a..4ebc6a65c3 100644 --- a/src/components/tooltip/CallNode.tsx +++ b/src/components/tooltip/CallNode.tsx @@ -27,6 +27,7 @@ import type { IndexIntoCategoryList, IndexIntoSubcategoryListForCategory, } from 'firefox-profiler/types'; +import { FuncFlag } from 'firefox-profiler/types'; import type { TimingsForPath, @@ -424,9 +425,10 @@ export class TooltipCallNode extends React.PureComponent { } let resource = null; - const resourceIndex = thread.funcTable.resource[funcIndex]; - - if (resourceIndex !== -1) { + const hasResource = + (thread.funcTable.flags[funcIndex] & FuncFlag.HasResource) !== 0; + if (hasResource) { + const resourceIndex = thread.funcTable.resource[funcIndex]; const resourceNameIndex = thread.resourceTable.name[resourceIndex]; // Because of our use of Grid Layout, all our elements need to be direct // children of the grid parent. That's why we use arrays here, to add @@ -519,9 +521,10 @@ export class TooltipCallNode extends React.PureComponent { stackTypeLabel = 'JavaScript'; break; case 'unsymbolicated': - stackTypeLabel = thread.funcTable.isJS[funcIndex] - ? 'Unsymbolicated native' - : 'Unsymbolicated or generated JIT instructions'; + stackTypeLabel = + (thread.funcTable.flags[funcIndex] & FuncFlag.IsJS) !== 0 + ? 'Unsymbolicated native' + : 'Unsymbolicated or generated JIT instructions'; break; default: throw new Error(`Unknown stack type case "${stackType}".`); diff --git a/src/node-tools/profiler-edit.ts b/src/node-tools/profiler-edit.ts index 2dba4034d3..8cea8104d8 100644 --- a/src/node-tools/profiler-edit.ts +++ b/src/node-tools/profiler-edit.ts @@ -32,6 +32,7 @@ import { type WasmSymbolicationSpec, } from 'firefox-profiler/profile-logic/wasm-symbolication'; import { getThreadsWithMarkersMatchingSearchFilter } from 'firefox-profiler/profile-logic/marker-data'; +import { FuncFlag } from 'firefox-profiler/types/profile'; import type { Profile, RawThread, @@ -124,8 +125,8 @@ export function collectFuncNames(profile: Profile): string[] { const result: string[] = []; for (let i = 0; i < funcTable.length; i++) { let name = stringArray[funcTable.name[i]]; - const sourceIndex = funcTable.source[i]; - if (sourceIndex !== null) { + if ((funcTable.flags[i] & FuncFlag.HasSource) !== 0) { + const sourceIndex = funcTable.source[i]; const filename = stringArray[sources.filename[sourceIndex]]; name += ` (${filename})`; } diff --git a/src/profile-logic/call-tree.ts b/src/profile-logic/call-tree.ts index 29189178db..c4a6360119 100644 --- a/src/profile-logic/call-tree.ts +++ b/src/profile-logic/call-tree.ts @@ -29,7 +29,7 @@ import type { SampleCategoriesAndSubcategories, IndexIntoCategoryList, } from 'firefox-profiler/types'; -import { ResourceType } from 'firefox-profiler/types'; +import { ResourceType, FuncFlag } from 'firefox-profiler/types'; import ExtensionIcon from '../../res/img/svg/extension.svg'; import { formatCallNodeNumber, formatPercent } from '../utils/format-numbers'; @@ -522,9 +522,14 @@ export class CallTree { const subcategoryIndex = this._callNodeInfo.subcategoryForNode(callNodeIndex); const badge = this._getInliningBadge(callNodeIndex, funcName); - const resourceIndex = this._thread.funcTable.resource[funcIndex]; - const resourceType = this._thread.resourceTable.type[resourceIndex]; - const isFrameLabel = resourceIndex === -1; + const funcFlags = this._thread.funcTable.flags[funcIndex]; + const isFrameLabel = (funcFlags & FuncFlag.HasResource) === 0; + const resourceIndex = isFrameLabel + ? -1 + : this._thread.funcTable.resource[funcIndex]; + const resourceType = isFrameLabel + ? -1 + : this._thread.resourceTable.type[resourceIndex]; const libName = this._getOriginAnnotation(funcIndex); const weightType = this._weightType; diff --git a/src/profile-logic/data-structures.ts b/src/profile-logic/data-structures.ts index 257db159ea..944f171404 100644 --- a/src/profile-logic/data-structures.ts +++ b/src/profile-logic/data-structures.ts @@ -21,7 +21,7 @@ import type { RawJsAllocationsTable, RawUnbalancedNativeAllocationsTable, RawBalancedNativeAllocationsTable, - FuncTable, + RawFuncTable, RawMarkerTable, ResourceTable, RawNativeSymbolTable, @@ -35,6 +35,8 @@ import type { IndexIntoFrameTable, IndexIntoFuncTable, IndexIntoLibs, + IndexIntoResourceTable, + IndexIntoSourceTable, IndexIntoStackTable, IndexIntoStringTable, IndexIntoCategoryList, @@ -385,14 +387,24 @@ export function finishRawFrameTableBuilder( }; } -export function getEmptyFuncTable(): FuncTable { +export type RawFuncTableBuilder = { + flags: number[]; + name: IndexIntoStringTable[]; + resource: IndexIntoResourceTable[]; + source: IndexIntoSourceTable[]; + lineNumber: number[]; + columnNumber: number[]; + originalLocation: IndexIntoSourceLocationTable[]; + length: number; +}; + +export function getRawFuncTableBuilder(): RawFuncTableBuilder { return { // Important! // If modifying this structure, please update all callers of this function to ensure // that they are pushing on correctly to the data structure. These pushes may not // be caught by the type system. - isJS: [], - relevantForJS: [], + flags: [], name: [], resource: [], source: [], @@ -403,24 +415,35 @@ export function getEmptyFuncTable(): FuncTable { }; } -export function shallowCloneFuncTable(funcTable: FuncTable): FuncTable { +export function getRawFuncTableBuilderWithExistingContents( + funcTable: RawFuncTable +): RawFuncTableBuilder { return { // Important! // If modifying this structure, please update all callers of this function to ensure // that they are pushing on correctly to the data structure. These pushes may not // be caught by the type system. - isJS: funcTable.isJS.slice(), - relevantForJS: funcTable.relevantForJS.slice(), - name: funcTable.name.slice(), - resource: funcTable.resource.slice(), - source: funcTable.source.slice(), - lineNumber: funcTable.lineNumber.slice(), - columnNumber: funcTable.columnNumber.slice(), - originalLocation: funcTable.originalLocation.slice(), + flags: Array.from(funcTable.flags), + name: Array.from(funcTable.name), + resource: Array.from(funcTable.resource), + source: Array.from(funcTable.source), + lineNumber: Array.from(funcTable.lineNumber), + columnNumber: Array.from(funcTable.columnNumber), + originalLocation: Array.from(funcTable.originalLocation), length: funcTable.length, }; } +export function finishRawFuncTableBuilder( + builder: RawFuncTableBuilder +): RawFuncTable { + return { ...builder }; +} + +export function getEmptyRawFuncTable(): RawFuncTable { + return finishRawFuncTableBuilder(getRawFuncTableBuilder()); +} + export function getEmptySourceLocationTable(): SourceLocationTable { return { source: [], @@ -676,7 +699,7 @@ export function getEmptySharedData(): RawProfileSharedData { return { stackTable: finishRawStackTableBuilder(getRawStackTableBuilder()), frameTable: finishRawFrameTableBuilder(getRawFrameTableBuilder()), - funcTable: getEmptyFuncTable(), + funcTable: getEmptyRawFuncTable(), resourceTable: getEmptyResourceTable(), nativeSymbols: finishRawNativeSymbolTableBuilder( getRawNativeSymbolTableBuilder() diff --git a/src/profile-logic/global-data-collector.ts b/src/profile-logic/global-data-collector.ts index 894347eec7..461d7f8348 100644 --- a/src/profile-logic/global-data-collector.ts +++ b/src/profile-logic/global-data-collector.ts @@ -5,11 +5,12 @@ import { StringTable } from '../utils/string-table'; import { finishRawFrameTableBuilder, + finishRawFuncTableBuilder, finishRawNativeSymbolTableBuilder, finishRawStackTableBuilder, getRawFrameTableBuilder, + getRawFuncTableBuilder, getRawNativeSymbolTableBuilder, - getEmptyFuncTable, getEmptyResourceTable, getEmptySourceTable, getEmptySourceLocationTable, @@ -24,7 +25,6 @@ import type { IndexIntoSourceTable, RawProfileSharedData, SourceTable, - FuncTable, ResourceTable, IndexIntoResourceTable, IndexIntoFuncTable, @@ -33,9 +33,10 @@ import type { Address, Bytes, } from 'firefox-profiler/types'; -import { ResourceType } from 'firefox-profiler/types'; +import { ResourceType, FuncFlag } from 'firefox-profiler/types'; import type { RawFrameTableBuilder, + RawFuncTableBuilder, RawNativeSymbolTableBuilder, RawStackTableBuilder, } from './data-structures'; @@ -56,7 +57,7 @@ export class GlobalDataCollector { _sources: SourceTable = getEmptySourceTable(); _frameTable: RawFrameTableBuilder = getRawFrameTableBuilder(); _stackTableBuilder: RawStackTableBuilder = getRawStackTableBuilder(); - _funcTable: FuncTable = getEmptyFuncTable(); + _funcTable: RawFuncTableBuilder = getRawFuncTableBuilder(); _resourceTable: ResourceTable = getEmptyResourceTable(); _nativeSymbols: RawNativeSymbolTableBuilder = getRawNativeSymbolTableBuilder(); @@ -105,15 +106,33 @@ export class GlobalDataCollector { const funcKey = `${name}-${isJS}-${relevantForJS}-${resource}-${source}-${lineNumber}-${columnNumber}`; let funcIndex = this._funcKeyToFuncIndex.get(funcKey); if (funcIndex === undefined) { + let flags = 0; + if (isJS) { + flags |= FuncFlag.IsJS; + } + if (relevantForJS) { + flags |= FuncFlag.RelevantForJS; + } + if (resource !== -1) { + flags |= FuncFlag.HasResource; + } + if (source !== null) { + flags |= FuncFlag.HasSource; + } + if (lineNumber !== null) { + flags |= FuncFlag.HasLine; + } + if (columnNumber !== null) { + flags |= FuncFlag.HasColumn; + } funcIndex = this._funcTable.length++; + this._funcTable.flags[funcIndex] = flags; this._funcTable.name[funcIndex] = name; - this._funcTable.isJS[funcIndex] = isJS; - this._funcTable.relevantForJS[funcIndex] = relevantForJS; - this._funcTable.resource[funcIndex] = resource; - this._funcTable.source[funcIndex] = source; - this._funcTable.lineNumber[funcIndex] = lineNumber; - this._funcTable.columnNumber[funcIndex] = columnNumber; - this._funcTable.originalLocation[funcIndex] = null; + this._funcTable.resource[funcIndex] = resource === -1 ? 0 : resource; + this._funcTable.source[funcIndex] = source ?? 0; + this._funcTable.lineNumber[funcIndex] = lineNumber ?? 0; + this._funcTable.columnNumber[funcIndex] = columnNumber ?? 0; + this._funcTable.originalLocation[funcIndex] = 0; this._funcKeyToFuncIndex.set(funcKey, funcIndex); } return funcIndex; @@ -299,7 +318,7 @@ export class GlobalDataCollector { const shared: RawProfileSharedData = { stackTable: finishRawStackTableBuilder(this._stackTableBuilder), frameTable: finishRawFrameTableBuilder(this._frameTable), - funcTable: this._funcTable, + funcTable: finishRawFuncTableBuilder(this._funcTable), resourceTable: this._resourceTable, nativeSymbols: finishRawNativeSymbolTableBuilder(this._nativeSymbols), stringArray: this._stringArray, diff --git a/src/profile-logic/import/simpleperf.ts b/src/profile-logic/import/simpleperf.ts index b9d1a56b2c..d536f893ab 100644 --- a/src/profile-logic/import/simpleperf.ts +++ b/src/profile-logic/import/simpleperf.ts @@ -6,7 +6,7 @@ import type { CategoryList, CategoryColor, RawFrameTable, - FuncTable, + RawFuncTable, IndexIntoCategoryList, IndexIntoFrameTable, IndexIntoFuncTable, @@ -19,9 +19,10 @@ import type { RawThread, RawStackTable, } from 'firefox-profiler/types/profile'; -import { FrameFlag } from 'firefox-profiler/types/profile'; +import { FrameFlag, FuncFlag } from 'firefox-profiler/types/profile'; import { - getEmptyFuncTable, + getRawFuncTableBuilder, + finishRawFuncTableBuilder, getEmptyResourceTable, getRawFrameTableBuilder, getRawStackTableBuilder, @@ -29,6 +30,7 @@ import { finishRawSamplesTableBuilder, finishRawStackTableBuilder, type RawFrameTableBuilder, + type RawFuncTableBuilder, type RawStackTableBuilder, getRawSamplesTableBuilder, type RawSamplesTableBuilder, @@ -117,15 +119,15 @@ class FirefoxResourceTable { class FirefoxFuncTable { strings: StringTable; - funcTable: FuncTable = getEmptyFuncTable(); + funcTable: RawFuncTableBuilder = getRawFuncTableBuilder(); funcMap: Map = new Map(); constructor(strings: StringTable) { this.strings = strings; } - toJson(): FuncTable { - return this.funcTable; + toJson(): RawFuncTable { + return finishRawFuncTableBuilder(this.funcTable); } findOrAddFunc(name: string, resourceIndex: number): IndexIntoFuncTable { @@ -135,14 +137,14 @@ class FirefoxFuncTable { let funcIndex = this.funcMap.get(mapKey); if (!funcIndex) { + // Non-JS, native function with an associated resource. + this.funcTable.flags.push(FuncFlag.HasResource); this.funcTable.name.push(nameIndex); - this.funcTable.isJS.push(false); - this.funcTable.relevantForJS.push(false); this.funcTable.resource.push(resourceIndex); - this.funcTable.source.push(null); - this.funcTable.lineNumber.push(null); - this.funcTable.columnNumber.push(null); - this.funcTable.originalLocation.push(null); + this.funcTable.source.push(0); + this.funcTable.lineNumber.push(0); + this.funcTable.columnNumber.push(0); + this.funcTable.originalLocation.push(0); funcIndex = this.funcTable.length++; this.funcMap.set(mapKey, funcIndex); diff --git a/src/profile-logic/insert-stack-labels.ts b/src/profile-logic/insert-stack-labels.ts index a0f30e4c09..038cd0b8bd 100644 --- a/src/profile-logic/insert-stack-labels.ts +++ b/src/profile-logic/insert-stack-labels.ts @@ -9,11 +9,12 @@ import type { Profile, Category, } from '../types/profile'; -import { FrameFlag } from '../types/profile'; +import { FrameFlag, FuncFlag } from '../types/profile'; import { finishRawFrameTableBuilder, + finishRawFuncTableBuilder, getRawFrameTableBuilderWithExistingContents, - shallowCloneFuncTable, + getRawFuncTableBuilderWithExistingContents, } from 'firefox-profiler/profile-logic/data-structures'; import { StringTable } from 'firefox-profiler/utils/string-table'; import { updateRawThreadStacks } from 'firefox-profiler/profile-logic/profile-data'; @@ -108,7 +109,7 @@ export function insertStackLabels( stringArray, } = profile.shared; const frameTable = getRawFrameTableBuilderWithExistingContents(oldFrameTable); - const funcTable = shallowCloneFuncTable(oldFuncTable); + const funcTable = getRawFuncTableBuilderWithExistingContents(oldFuncTable); const stringTable = StringTable.withBackingArray(stringArray); const rootLabelName = 'Root (unaccounted / catch-all)'; @@ -124,14 +125,13 @@ export function insertStackLabels( for (let i = 0; i < allLabelNames.length; i++) { const labelName = allLabelNames[i]; const funcIndex = funcTable.length++; + funcTable.flags[funcIndex] = FuncFlag.RelevantForJS; funcTable.name[funcIndex] = stringTable.indexForString(labelName); - funcTable.resource[funcIndex] = -1; - funcTable.source[funcIndex] = null; - funcTable.lineNumber[funcIndex] = null; - funcTable.columnNumber[funcIndex] = null; - funcTable.originalLocation[funcIndex] = null; - funcTable.isJS[funcIndex] = false; - funcTable.relevantForJS[funcIndex] = true; + funcTable.resource[funcIndex] = 0; + funcTable.source[funcIndex] = 0; + funcTable.lineNumber[funcIndex] = 0; + funcTable.columnNumber[funcIndex] = 0; + funcTable.originalLocation[funcIndex] = 0; const frameIndex = frameTable.length++; frameTable.flags[frameIndex] = FrameFlag.HasCategory; @@ -154,8 +154,8 @@ export function insertStackLabels( // Include the filename (in brackets), if present. This allows matchers // like `onStateChange (chrome://browser/content/tabbrowser/` - const sourceIndex = funcTable.source[funcIndex]; - if (sourceIndex !== null) { + if ((funcTable.flags[funcIndex] & FuncFlag.HasSource) !== 0) { + const sourceIndex = funcTable.source[funcIndex]; const filenameString = stringArray[sources.filename[sourceIndex]]; nameString += ` (${filenameString})`; } @@ -215,8 +215,9 @@ export function insertStackLabels( inheritedLabelFrameIndexAtStack[stackIndex] = labelFrameIndex; stacksToInsertCount++; } else if ( - funcTable.isJS[funcIndex] || - funcTable.relevantForJS[funcIndex] + (funcTable.flags[funcIndex] & + (FuncFlag.IsJS | FuncFlag.RelevantForJS)) !== + 0 ) { labelFrameIndexToInsertAtStack[stackIndex] = null; inheritedLabelFrameIndexAtStack[stackIndex] = null; @@ -281,7 +282,7 @@ export function insertStackLabels( ...profile.shared, stackTable, frameTable: finishRawFrameTableBuilder(frameTable), - funcTable, + funcTable: finishRawFuncTableBuilder(funcTable), }; const newThreads = updateRawThreadStacks(profile.threads, (oldStack) => oldStack !== null ? oldStackToNewStackPlusOne[oldStack] - 1 : null diff --git a/src/profile-logic/js-tracer.ts b/src/profile-logic/js-tracer.ts index b7f8aac500..b88b58b937 100644 --- a/src/profile-logic/js-tracer.ts +++ b/src/profile-logic/js-tracer.ts @@ -6,8 +6,10 @@ import { getRawMarkerTableBuilder, finishRawMarkerTableBuilder, finishRawFrameTableBuilder, + finishRawFuncTableBuilder, finishRawSamplesTableBuilder, finishRawStackTableBuilder, + getRawFuncTableBuilderWithExistingContents, getRawStackTableBuilderWithExistingContents, getRawFrameTableBuilderWithExistingContents, type RawSamplesTableBuilder, @@ -27,7 +29,7 @@ import type { JsTracerTiming, Microseconds, } from 'firefox-profiler/types'; -import { FrameFlag } from 'firefox-profiler/types'; +import { FrameFlag, FuncFlag } from 'firefox-profiler/types'; // See the function below for more information. type ScriptLocationToFuncIndex = Map; @@ -45,13 +47,16 @@ function getScriptLocationToFuncIndex({ }: RawProfileSharedData): ScriptLocationToFuncIndex { const scriptLocationToFuncIndex: ScriptLocationToFuncIndex = new Map(); for (let funcIndex = 0; funcIndex < funcTable.length; funcIndex++) { - if (!funcTable.isJS[funcIndex]) { + const funcFlags = funcTable.flags[funcIndex]; + if ((funcFlags & FuncFlag.IsJS) === 0) { continue; } - const line = funcTable.lineNumber[funcIndex]; - const column = funcTable.columnNumber[funcIndex]; - const sourceIndex = funcTable.source[funcIndex]; - if (column !== null && line !== null && sourceIndex !== null) { + const requiredFlags = + FuncFlag.HasLine | FuncFlag.HasColumn | FuncFlag.HasSource; + if ((funcFlags & requiredFlags) === requiredFlags) { + const line = funcTable.lineNumber[funcIndex]; + const column = funcTable.columnNumber[funcIndex]; + const sourceIndex = funcTable.source[funcIndex]; const urlIndex = sources.filename[sourceIndex]; const fileName = stringArray[urlIndex]; const key = `${fileName}:${line}:${column}`; @@ -520,7 +525,9 @@ export function convertJsTracerToThreadWithoutSamples( samples, }; - const { funcTable } = shared; + const funcTable = getRawFuncTableBuilderWithExistingContents( + shared.funcTable + ); const frameTable = getRawFrameTableBuilderWithExistingContents( shared.frameTable ); @@ -578,14 +585,13 @@ export function convertJsTracerToThreadWithoutSamples( if (generatedFuncIndex === undefined) { // Create a new function only if the event string is different. funcIndex = funcTable.length++; + funcTable.flags.push(FuncFlag.RelevantForJS); funcTable.name.push(eventStringIndex); - funcTable.isJS.push(false); - funcTable.resource.push(-1); - funcTable.relevantForJS.push(true); - funcTable.source.push(null); - funcTable.lineNumber.push(null); - funcTable.columnNumber.push(null); - funcTable.originalLocation.push(null); + funcTable.resource.push(0); + funcTable.source.push(0); + funcTable.lineNumber.push(0); + funcTable.columnNumber.push(0); + funcTable.originalLocation.push(0); funcMap.set(eventStringIndex, funcIndex); } else { @@ -643,9 +649,10 @@ export function convertJsTracerToThreadWithoutSamples( unmatchedEventEnds[unmatchedIndex] = end; } - // Write the augmented stackTable and frameTable back to the shared data. + // Write the augmented stackTable, frameTable and funcTable back to the shared data. shared.stackTable = finishRawStackTableBuilder(stackTable); shared.frameTable = finishRawFrameTableBuilder(frameTable); + shared.funcTable = finishRawFuncTableBuilder(funcTable); thread.samples = finishRawSamplesTableBuilder(samples); thread.markers = finishRawMarkerTableBuilder(markers); diff --git a/src/profile-logic/line-timings.ts b/src/profile-logic/line-timings.ts index 175a9b8383..f792c3b38d 100644 --- a/src/profile-logic/line-timings.ts +++ b/src/profile-logic/line-timings.ts @@ -14,7 +14,7 @@ import type { IndexIntoLineSetTable, SourceLocationTable, } from 'firefox-profiler/types'; -import { FrameFlag } from 'firefox-profiler/types'; +import { FrameFlag, FuncFlag } from 'firefox-profiler/types'; import { SetCollectionBuilder } from 'firefox-profiler/utils/set-collection'; /** @@ -68,21 +68,28 @@ export function getStackLineInfo( // column entirely, and defer the line lookup until we know the source // matches. const frameFlags = frameTable.flags[frame]; + const funcFlags = funcTable.flags[func]; const frameHasOriginalLocation = (frameFlags & FrameFlag.HasOriginalLocation) !== 0; + const funcHasOriginalLocation = + (funcFlags & FuncFlag.HasOriginalLocation) !== 0; const frameOriginalLocationIdx = frameHasOriginalLocation ? frameTable.originalLocation[frame] : -1; - const funcOriginalLocationIdx = funcTable.originalLocation[func]; + const funcOriginalLocationIdx = funcHasOriginalLocation + ? funcTable.originalLocation[func] + : -1; let sourceIndexOfThisStack; if (frameHasOriginalLocation) { sourceIndexOfThisStack = sourceLocationTable.source[frameOriginalLocationIdx]; - } else if (funcOriginalLocationIdx !== null) { + } else if (funcHasOriginalLocation) { sourceIndexOfThisStack = sourceLocationTable.source[funcOriginalLocationIdx]; - } else { + } else if ((funcFlags & FuncFlag.HasSource) !== 0) { sourceIndexOfThisStack = funcTable.source[func]; + } else { + sourceIndexOfThisStack = -1; } const matchesSource = sourceIndexOfThisStack === sourceViewSourceIndex; @@ -93,11 +100,11 @@ export function getStackLineInfo( if (matchesSource) { if (frameHasOriginalLocation) { selfLineOrNull = sourceLocationTable.line[frameOriginalLocationIdx]; - } else if (funcOriginalLocationIdx !== null) { + } else if (funcHasOriginalLocation) { selfLineOrNull = sourceLocationTable.line[funcOriginalLocationIdx]; } else if ((frameFlags & FrameFlag.HasLine) !== 0) { selfLineOrNull = frameTable.line[frame]; - } else { + } else if ((funcFlags & FuncFlag.HasLine) !== 0) { selfLineOrNull = funcTable.lineNumber[func]; } } @@ -224,19 +231,20 @@ export function getTotalLineTimingsForCallNode( // the per-sample object allocation. const funcIndex = frameTable.func[callNodeFrame]; const frameFlags = frameTable.flags[callNodeFrame]; + const funcFlags = funcTable.flags[funcIndex]; let frameLine: number | null; if ((frameFlags & FrameFlag.HasOriginalLocation) !== 0) { frameLine = sourceLocationTable.line[frameTable.originalLocation[callNodeFrame]]; + } else if ((funcFlags & FuncFlag.HasOriginalLocation) !== 0) { + frameLine = + sourceLocationTable.line[funcTable.originalLocation[funcIndex]]; + } else if ((frameFlags & FrameFlag.HasLine) !== 0) { + frameLine = frameTable.line[callNodeFrame]; + } else if ((funcFlags & FuncFlag.HasLine) !== 0) { + frameLine = funcTable.lineNumber[funcIndex]; } else { - const funcOriginalLocationIdx = funcTable.originalLocation[funcIndex]; - if (funcOriginalLocationIdx !== null) { - frameLine = sourceLocationTable.line[funcOriginalLocationIdx]; - } else if ((frameFlags & FrameFlag.HasLine) !== 0) { - frameLine = frameTable.line[callNodeFrame]; - } else { - frameLine = funcTable.lineNumber[funcIndex]; - } + frameLine = null; } const line = frameLine !== null ? frameLine : funcLine; if (line === null) { diff --git a/src/profile-logic/merge-compare.ts b/src/profile-logic/merge-compare.ts index 3304cfcf13..a588e739e9 100644 --- a/src/profile-logic/merge-compare.ts +++ b/src/profile-logic/merge-compare.ts @@ -13,9 +13,10 @@ import { getRawNativeSymbolTableBuilder, finishRawNativeSymbolTableBuilder, finishRawFrameTableBuilder, + finishRawFuncTableBuilder, finishRawSamplesTableBuilder, getRawFrameTableBuilder, - getEmptyFuncTable, + getRawFuncTableBuilder, getRawStackTableBuilder, finishRawStackTableBuilder, getRawMarkerTableBuilder, @@ -55,9 +56,9 @@ import type { IndexIntoStringTable, IndexIntoSourceTable, IndexIntoSourceLocationTable, - FuncTable, RawFrameTable, Lib, + RawFuncTable, RawNativeSymbolTable, ResourceTable, RawSamplesTable, @@ -80,7 +81,7 @@ import type { ProfilerOverhead, ThreadIndex, } from 'firefox-profiler/types'; -import { FrameFlag } from 'firefox-profiler/types'; +import { FrameFlag, FuncFlag } from 'firefox-profiler/types'; import { translateTransformStack } from './transforms'; /** @@ -763,22 +764,18 @@ function _mapNullableString( : null; } -function _mapNullableSource( - sourceIndex: IndexIntoSourceTable | null, +function _mapSource( + sourceIndex: IndexIntoSourceTable, oldSourceToNewSourcePlusOne: TranslationMapForSources -): IndexIntoStringTable | null { - return sourceIndex !== null - ? oldSourceToNewSourcePlusOne[sourceIndex] - 1 - : null; +): IndexIntoSourceTable { + return oldSourceToNewSourcePlusOne[sourceIndex] - 1; } -function _mapNullableOriginalLocation( - originalLocationIndex: IndexIntoSourceLocationTable | null, - oldOriginalLocationToNewPlusOne: TranslationMapForOriginalLocation -): IndexIntoSourceLocationTable | null { - return originalLocationIndex !== null - ? oldOriginalLocationToNewPlusOne[originalLocationIndex] - 1 - : null; +function _mapResource( + resourceIndex: IndexIntoResourceTable, + oldResourceToNewResourcePlusOne: TranslationMapForResources +): IndexIntoResourceTable { + return oldResourceToNewResourcePlusOne[resourceIndex] - 1; } function _mapOriginalLocation( @@ -788,16 +785,6 @@ function _mapOriginalLocation( return oldOriginalLocationToNewPlusOne[originalLocationIndex] - 1; } -function _mapFuncResource( - resourceIndex: IndexIntoResourceTable | -1, - oldResourceToNewResourcePlusOne: TranslationMapForResources -): IndexIntoResourceTable | -1 { - if (resourceIndex === -1) { - return -1; - } - return oldResourceToNewResourcePlusOne[resourceIndex] - 1; -} - function _mapFunc( funcIndex: IndexIntoFuncTable, oldFuncToNewFuncPlusOne: TranslationMapForFuncs @@ -953,10 +940,10 @@ function mergeFuncTables( translationMapsForSources: TranslationMapForSources[], translationMapsForOriginalLocation: TranslationMapForOriginalLocation[], translationMapsForStrings: TranslationMapForStrings[] -): { funcTable: FuncTable; translationMaps: TranslationMapForFuncs[] } { +): { funcTable: RawFuncTable; translationMaps: TranslationMapForFuncs[] } { const mapOfInsertedFuncs = new Map(); const translationMaps: TranslationMapForFuncs[] = []; - const newFuncTable = getEmptyFuncTable(); + const newFuncTable = getRawFuncTableBuilder(); profiles.forEach((profile, profileIndex) => { const { funcTable } = profile.shared; @@ -969,14 +956,15 @@ function mergeFuncTables( const oldFuncToNewFuncPlusOne = new Int32Array(funcTable.length); for (let i = 0; i < funcTable.length; i++) { - const sourceIndex = _mapNullableSource( - funcTable.source[i], - oldSourceToNewSourcePlusOne - ); - const resourceIndex = _mapFuncResource( - funcTable.resource[i], - oldResourceToNewResourcePlusOne - ); + const flags = funcTable.flags[i]; + const sourceIndex = + (flags & FuncFlag.HasSource) !== 0 + ? _mapSource(funcTable.source[i], oldSourceToNewSourcePlusOne) + : 0; + const resourceIndex = + (flags & FuncFlag.HasResource) !== 0 + ? _mapResource(funcTable.resource[i], oldResourceToNewResourcePlusOne) + : 0; const nameIndex = _mapString( funcTable.name[i], oldStringToNewStringPlusOne @@ -991,7 +979,12 @@ function mergeFuncTables( // number as well. // 3. Label frames: they have no resource, only a name. So we can't do // better than this. - const funcKey = [nameIndex, resourceIndex, lineNumber].join('#'); + const funcKey = [ + nameIndex, + resourceIndex, + (flags & FuncFlag.HasResource) !== 0 ? 1 : 0, + (flags & FuncFlag.HasLine) !== 0 ? lineNumber : 'nil', + ].join('#'); const insertedFuncIndex = mapOfInsertedFuncs.get(funcKey); if (insertedFuncIndex !== undefined) { oldFuncToNewFuncPlusOne[i] = insertedFuncIndex + 1; @@ -1000,18 +993,19 @@ function mergeFuncTables( mapOfInsertedFuncs.set(funcKey, newFuncTable.length); oldFuncToNewFuncPlusOne[i] = newFuncTable.length + 1; - newFuncTable.isJS.push(funcTable.isJS[i]); + newFuncTable.flags.push(flags); newFuncTable.name.push(nameIndex); newFuncTable.resource.push(resourceIndex); - newFuncTable.relevantForJS.push(funcTable.relevantForJS[i]); newFuncTable.source.push(sourceIndex); newFuncTable.lineNumber.push(lineNumber); newFuncTable.columnNumber.push(funcTable.columnNumber[i]); newFuncTable.originalLocation.push( - _mapNullableOriginalLocation( - funcTable.originalLocation[i], - oldOriginalLocationToNewPlusOne - ) + (flags & FuncFlag.HasOriginalLocation) !== 0 + ? _mapOriginalLocation( + funcTable.originalLocation[i], + oldOriginalLocationToNewPlusOne + ) + : 0 ); newFuncTable.length++; @@ -1020,7 +1014,10 @@ function mergeFuncTables( translationMaps.push(oldFuncToNewFuncPlusOne); }); - return { funcTable: newFuncTable, translationMaps }; + return { + funcTable: finishRawFuncTableBuilder(newFuncTable), + translationMaps, + }; } /** diff --git a/src/profile-logic/process-profile.ts b/src/profile-logic/process-profile.ts index 4ad47bdc35..b3ad6342d3 100644 --- a/src/profile-logic/process-profile.ts +++ b/src/profile-logic/process-profile.ts @@ -2161,7 +2161,7 @@ function convertSharedTablesEligibleColumns( shared: RawProfileSharedData, categories: CategoryList | undefined ): RawProfileSharedData { - const { stackTable, frameTable, nativeSymbols } = shared; + const { stackTable, frameTable, funcTable, nativeSymbols } = shared; return { ...shared, stackTable: { @@ -2186,6 +2186,16 @@ function convertSharedTablesEligibleColumns( column: toInt32Array(frameTable.column), originalLocation: toInt32Array(frameTable.originalLocation), }, + funcTable: { + length: funcTable.length, + flags: toUint8Array(funcTable.flags), + name: toInt32Array(funcTable.name), + resource: toInt32Array(funcTable.resource), + source: toInt32Array(funcTable.source), + lineNumber: toInt32Array(funcTable.lineNumber), + columnNumber: toInt32Array(funcTable.columnNumber), + originalLocation: toInt32Array(funcTable.originalLocation), + }, nativeSymbols: computeNativeSymbolTableFromRawNativeSymbolTable(nativeSymbols), }; diff --git a/src/profile-logic/processed-profile-versioning.ts b/src/profile-logic/processed-profile-versioning.ts index 3a1f1ff2a6..8fb6eda4e0 100644 --- a/src/profile-logic/processed-profile-versioning.ts +++ b/src/profile-logic/processed-profile-versioning.ts @@ -3510,6 +3510,85 @@ const _upgraders: { } } }, + [75]: (profile: any) => { + // The func table representation changed, mirroring the v71 frame table + // change: + // - A new `flags` bitfield column was added (Uint8Array or plain array). + // - The `isJS` and `relevantForJS` boolean columns were removed; the + // IsJS / RelevantForJS flag bits carry the same information. + // - The `resource`, `source`, `lineNumber`, `columnNumber`, and + // `originalLocation` columns are no longer nullable in-band. When the + // corresponding "Has..." flag is not set, the value in the column is + // ignored and can be any placeholder (we write 0). + // - All columns may now optionally be stored as typed arrays + // (Int32Array for the non-flags columns). + + // A snapshot of the `FuncFlag` enum as of version 75. Don't refer to the + // current `FuncFlag` enum here; upgraders must keep working even if later + // versions renumber or remove flags. + const IsJS = 1 << 0; + const RelevantForJS = 1 << 1; + const HasResource = 1 << 2; + const HasSource = 1 << 3; + const HasLine = 1 << 4; + const HasColumn = 1 << 5; + const HasOriginalLocation = 1 << 6; + + const { funcTable } = profile.shared; + const { + isJS, + relevantForJS, + resource, + source, + lineNumber, + columnNumber, + originalLocation, + length, + } = funcTable; + const flags = new Array(length); + for (let i = 0; i < length; i++) { + let f = 0; + if (isJS[i]) { + f |= IsJS; + } + if (relevantForJS[i]) { + f |= RelevantForJS; + } + if ( + resource[i] !== -1 && + resource[i] !== null && + resource[i] !== undefined + ) { + f |= HasResource; + } else { + resource[i] = 0; + } + if (source[i] !== null && source[i] !== undefined) { + f |= HasSource; + } else { + source[i] = 0; + } + if (lineNumber[i] !== null && lineNumber[i] !== undefined) { + f |= HasLine; + } else { + lineNumber[i] = 0; + } + if (columnNumber[i] !== null && columnNumber[i] !== undefined) { + f |= HasColumn; + } else { + columnNumber[i] = 0; + } + if (originalLocation[i] !== null && originalLocation[i] !== undefined) { + f |= HasOriginalLocation; + } else { + originalLocation[i] = 0; + } + flags[i] = f; + } + funcTable.flags = flags; + delete funcTable.isJS; + delete funcTable.relevantForJS; + }, // If you add a new upgrader here, please document the change in // `docs-developer/CHANGELOG-formats.md`. }; diff --git a/src/profile-logic/profile-compacting.ts b/src/profile-logic/profile-compacting.ts index d232638fd1..43e647073e 100644 --- a/src/profile-logic/profile-compacting.ts +++ b/src/profile-logic/profile-compacting.ts @@ -14,14 +14,14 @@ import type { IndexIntoStackTable, RawStackTable, RawFrameTable, - FuncTable, + RawFuncTable, ResourceTable, RawNativeSymbolTable, Lib, SourceTable, SourceLocationTable, } from 'firefox-profiler/types'; -import { FrameFlag } from 'firefox-profiler/types'; +import { FrameFlag, FuncFlag } from 'firefox-profiler/types'; import { assertExhaustiveCheck, ensureExists, @@ -79,10 +79,6 @@ type ColumnDescription = | { type: 'NO_REF' } : | { type: 'INDEX_REF'; referencedTable: TableCompactionState } - | { - type: 'INDEX_REF_OR_NEG_ONE'; - referencedTable: TableCompactionState; - } | { type: 'NO_REF' }); type TableDescription = { @@ -120,10 +116,6 @@ const ColDesc = { type: 'INDEX_REF_OR_NULL' as const, referencedTable, }), - indexRefOrNegOne: (referencedTable: TableCompactionState) => ({ - type: 'INDEX_REF_OR_NEG_ONE' as const, - referencedTable, - }), selfPrefixOffset: () => ({ type: 'SELF_RELATIVE_PARENT' as const }), noRef: () => ({ type: 'NO_REF' as const }), // Like noRef, but the compacted column is always a typed array of the given @@ -231,15 +223,20 @@ export function computeCompactedProfile( FrameFlag.HasOriginalLocation ), }; - const funcTableDesc: TableDescription = { - name: ColDesc.indexRef(tcs.stringArray), - isJS: ColDesc.noRef(), - relevantForJS: ColDesc.noRef(), - resource: ColDesc.indexRefOrNegOne(tcs.resourceTable), - source: ColDesc.indexRefOrNull(tcs.sources), + const funcTableDesc: TableDescription = { + flags: ColDesc.noRef(), + name: ColDesc.indexRefInt32(tcs.stringArray), + resource: ColDesc.indexRefInt32GatedByFlag( + tcs.resourceTable, + FuncFlag.HasResource + ), + source: ColDesc.indexRefInt32GatedByFlag(tcs.sources, FuncFlag.HasSource), lineNumber: ColDesc.noRef(), columnNumber: ColDesc.noRef(), - originalLocation: ColDesc.indexRefOrNull(tcs.sourceLocationTable), + originalLocation: ColDesc.indexRefInt32GatedByFlag( + tcs.sourceLocationTable, + FuncFlag.HasOriginalLocation + ), }; const sourceLocationTableDesc: TableDescription = { source: ColDesc.indexRef(tcs.sources), @@ -417,13 +414,6 @@ function _markTableAndComputeTranslation( break; case 'SELF_RELATIVE_PARENT': break; // already handled in the first pass - case 'INDEX_REF_OR_NEG_ONE': - markColumnWithNegOneableFields( - col, - markBuffer, - desc.referencedTable.markBuffer - ); - break; case 'NO_REF': case 'NO_REF_TYPED': break; @@ -482,21 +472,6 @@ function markSelfColumnPrefixOffset( } } -function markColumnWithNegOneableFields( - col: Array, - shouldMark: BitSet, - markBuf: BitSet -) { - for (let i = 0; i < col.length; i++) { - if (checkBit(shouldMark, i)) { - const val = col[i]; - if (val !== -1) { - setBit(markBuf, val); - } - } - } -} - function markColumnGatedByFlag( col: Array | Int32Array, flagCol: Array | Uint8Array, @@ -634,14 +609,6 @@ function _compactTable( newLength ); break; - case 'INDEX_REF_OR_NEG_ONE': - result[key] = _compactColIndexOrNegOne( - oldCol, - markBuffer, - desc.referencedTable.oldIndexToNewIndexPlusOne, - newLength - ); - break; case 'NO_REF': result[key] = _compactColCopy(oldCol, markBuffer, newLength); break; @@ -762,23 +729,6 @@ function _compactColIndexOrNull( return newCol; } -function _compactColIndexOrNegOne( - oldCol: (number | -1)[], - markBuffer: BitSet, - oldIndexToNewIndexPlusOne: Int32Array, - newLength: number -): (number | -1)[] { - const newCol: (number | -1)[] = new Array(newLength); - let newIndex = 0; - for (let i = 0; i < oldCol.length; i++) { - if (checkBit(markBuffer, i)) { - const val = oldCol[i]; - newCol[newIndex++] = val !== -1 ? oldIndexToNewIndexPlusOne[val] - 1 : -1; - } - } - return newCol; -} - function _compactColSelfPrefixOffset( oldCol: Int32Array, markBuffer: BitSet, diff --git a/src/profile-logic/profile-data.ts b/src/profile-logic/profile-data.ts index b9c829fe7d..6ed16f8483 100644 --- a/src/profile-logic/profile-data.ts +++ b/src/profile-logic/profile-data.ts @@ -11,7 +11,6 @@ import { finishRawStackTableBuilder, getEmptyCallNodeTable, getRawFrameTableBuilderWithExistingContents, - shallowCloneFuncTable, } from './data-structures'; import { CallNodeInfoNonInverted, @@ -58,6 +57,7 @@ import type { RawFrameTable, FrameTable, FuncTable, + RawFuncTable, NativeSymbolTable, RawNativeSymbolTable, ResourceTable, @@ -110,7 +110,12 @@ import type { SampleCategoriesAndSubcategories, SourceLocationTable, } from 'firefox-profiler/types'; -import { SelectedState, ResourceType, FrameFlag } from 'firefox-profiler/types'; +import { + SelectedState, + ResourceType, + FrameFlag, + FuncFlag, +} from 'firefox-profiler/types'; import type { CallNodeInfo, SuffixOrderIndex } from './call-node-info'; import { toFloat64Array, @@ -1781,8 +1786,9 @@ export function computeTransformOutputForImplementationFilter( stackTable, frameTable, (funcIndex) => { + const funcFlags = funcTable.flags[funcIndex]; // Return quickly if this is a JS frame. - if (funcTable.isJS[funcIndex]) { + if ((funcFlags & FuncFlag.IsJS) !== 0) { return false; } // Regular C++ functions are associated with a resource that describes the @@ -1793,7 +1799,7 @@ export function computeTransformOutputForImplementationFilter( funcTable.name[funcIndex] ); const isProbablyJitCode = - funcTable.resource[funcIndex] === -1 && + (funcFlags & FuncFlag.HasResource) === 0 && locationString.startsWith('0x'); return !isProbablyJitCode; } @@ -1804,7 +1810,9 @@ export function computeTransformOutputForImplementationFilter( frameTable, (funcIndex) => { return ( - funcTable.isJS[funcIndex] || funcTable.relevantForJS[funcIndex] + (funcTable.flags[funcIndex] & + (FuncFlag.IsJS | FuncFlag.RelevantForJS)) !== + 0 ); } ); @@ -2028,8 +2036,9 @@ export function computeFuncMatchesSearchString( return true; } - const sourceIndex = funcTable.source[func]; - if (sourceIndex !== null) { + const funcFlags = funcTable.flags[func]; + if ((funcFlags & FuncFlag.HasSource) !== 0) { + const sourceIndex = funcTable.source[func]; const urlIndex = sources.filename[sourceIndex]; const fileNameString = stringTable.getString(urlIndex); if (fileNameString.toLowerCase().includes(lowercaseSearchString)) { @@ -2037,8 +2046,8 @@ export function computeFuncMatchesSearchString( } } - const resourceIndex = funcTable.resource[func]; - if (resourceIndex !== -1) { + if ((funcFlags & FuncFlag.HasResource) !== 0) { + const resourceIndex = funcTable.resource[func]; const resourceNameIndex = resourceTable.name[resourceIndex]; const resourceNameString = stringTable.getString(resourceNameIndex); if (resourceNameString.toLowerCase().includes(lowercaseSearchString)) { @@ -3609,36 +3618,49 @@ export function getOriginalPositionForFrame( }; } - if (sourceLocationTable !== null) { + const funcFlags = funcTable.flags[funcIndex]; + if ( + sourceLocationTable !== null && + (funcFlags & FuncFlag.HasOriginalLocation) !== 0 + ) { const funcOriginalLocationIdx = funcTable.originalLocation[funcIndex]; - if (funcOriginalLocationIdx !== null) { - return { - source: sourceLocationTable.source[funcOriginalLocationIdx], - line: sourceLocationTable.line[funcOriginalLocationIdx], - column: sourceLocationTable.column[funcOriginalLocationIdx], - }; - } + return { + source: sourceLocationTable.source[funcOriginalLocationIdx], + line: sourceLocationTable.line[funcOriginalLocationIdx], + column: sourceLocationTable.column[funcOriginalLocationIdx], + }; } + const funcSource = + (funcFlags & FuncFlag.HasSource) !== 0 ? funcTable.source[funcIndex] : null; + const funcLine = + (funcFlags & FuncFlag.HasLine) !== 0 + ? funcTable.lineNumber[funcIndex] + : null; + const funcColumn = + (funcFlags & FuncFlag.HasColumn) !== 0 + ? funcTable.columnNumber[funcIndex] + : null; + if (frameIndex !== null) { const frameFlags = frameTable.flags[frameIndex]; return { - source: funcTable.source[funcIndex], + source: funcSource, line: (frameFlags & FrameFlag.HasLine) !== 0 ? frameTable.line[frameIndex] - : funcTable.lineNumber[funcIndex], + : funcLine, column: (frameFlags & FrameFlag.HasColumn) !== 0 ? frameTable.column[frameIndex] - : funcTable.columnNumber[funcIndex], + : funcColumn, }; } return { - source: funcTable.source[funcIndex], - line: funcTable.lineNumber[funcIndex], - column: funcTable.columnNumber[funcIndex], + source: funcSource, + line: funcLine, + column: funcColumn, }; } @@ -3659,8 +3681,8 @@ export function getOriginAnnotationForFunc( ): string { let resourceType = null; let origin = null; - const resourceIndex = funcTable.resource[funcIndex]; - if (resourceIndex !== -1) { + if ((funcTable.flags[funcIndex] & FuncFlag.HasResource) !== 0) { + const resourceIndex = funcTable.resource[funcIndex]; resourceType = resourceTable.type[resourceIndex]; const resourceNameIndex = resourceTable.name[resourceIndex]; origin = stringTable.getString(resourceNameIndex); @@ -3726,10 +3748,41 @@ export function getOriginAnnotationForFunc( * These are used by the "collapse resource" transform. */ export function reserveFunctionsForCollapsedResources( - originalFuncTable: FuncTable, + originalFuncTable: RawFuncTable, resourceTable: ResourceTable ): FuncTableWithReservedFunctions { - const funcTable = shallowCloneFuncTable(originalFuncTable); + // We reserve exactly one func per resource, so we know the final length up + // front. Allocate the derived columns at that length and copy the original + // contents into them, instead of going through a RawFuncTableBuilder; the + // latter would copy the entire funcTable twice, once into plain arrays and + // once back into typed arrays. + const originalLength = originalFuncTable.length; + const length = originalLength + resourceTable.length; + const flags = new Uint8Array(length); + const name = new Int32Array(length); + const resource = new Int32Array(length); + const source = new Int32Array(length); + const lineNumber = new Int32Array(length); + const columnNumber = new Int32Array(length); + const originalLocation = new Int32Array(length); + flags.set(originalFuncTable.flags); + name.set(originalFuncTable.name); + resource.set(originalFuncTable.resource); + source.set(originalFuncTable.source); + lineNumber.set(originalFuncTable.lineNumber); + columnNumber.set(originalFuncTable.columnNumber); + originalLocation.set(originalFuncTable.originalLocation); + const funcTable = { + flags, + name, + resource, + source, + lineNumber, + columnNumber, + originalLocation, + length, + }; + const reservedFunctionsForResources = new Map< IndexIntoResourceTable, IndexIntoFuncTable @@ -3746,24 +3799,19 @@ export function reserveFunctionsForCollapsedResources( resourceIndex++ ) { const resourceType = resourceTable.type[resourceIndex]; - const name = resourceTable.name[resourceIndex]; const isJS = jsResourceTypes.includes(resourceType); - const funcIndex = funcTable.length; - funcTable.isJS.push(isJS); - funcTable.relevantForJS.push(isJS); - funcTable.name.push(name); - funcTable.resource.push(resourceIndex); - funcTable.source.push(null); - funcTable.lineNumber.push(null); - funcTable.columnNumber.push(null); - funcTable.originalLocation.push(null); - funcTable.length++; + const funcIndex = originalLength + resourceIndex; + // The source, lineNumber, columnNumber and originalLocation columns keep + // the zero they were allocated with; the corresponding flags are unset, so + // those values are ignored. + flags[funcIndex] = isJS + ? FuncFlag.HasResource | FuncFlag.IsJS | FuncFlag.RelevantForJS + : FuncFlag.HasResource; + name[funcIndex] = resourceTable.name[resourceIndex]; + resource[funcIndex] = resourceIndex; reservedFunctionsForResources.set(resourceIndex, funcIndex); } - return { - funcTable, - reservedFunctionsForResources, - }; + return { funcTable, reservedFunctionsForResources }; } /** @@ -4597,7 +4645,20 @@ export function findAddressProofForFile( ): AddressProof | null { const { libs } = profile; const { frameTable, funcTable } = profile.shared; - const func = funcTable.source.indexOf(sourceIndex); + // Scan for the func manually rather than using `funcTable.source.indexOf`: + // the `source` column only carries a meaningful value for funcs which have + // the `HasSource` flag set, and holds an arbitrary value (usually zero) for + // all other funcs. An `indexOf` would happily match one of those. + let func = -1; + for (let i = 0; i < funcTable.length; i++) { + if ( + (funcTable.flags[i] & FuncFlag.HasSource) !== 0 && + funcTable.source[i] === sourceIndex + ) { + func = i; + break; + } + } if (func === -1) { return null; } @@ -4947,6 +5008,19 @@ export function computeNativeSymbolTableFromRawNativeSymbolTable( }; } +export function computeFuncTableFromRawFuncTable(raw: RawFuncTable): FuncTable { + return { + flags: toUint8Array(raw.flags), + name: toInt32Array(raw.name), + resource: toInt32Array(raw.resource), + source: toInt32Array(raw.source), + lineNumber: toInt32Array(raw.lineNumber), + columnNumber: toInt32Array(raw.columnNumber), + originalLocation: toInt32Array(raw.originalLocation), + length: raw.length, + }; +} + export function computeStackTableFromRawStackTable( rawStackTable: RawStackTable, frameTable: FrameTable, diff --git a/src/profile-logic/sanitize.ts b/src/profile-logic/sanitize.ts index d7d04dd472..5b0adeb2bc 100644 --- a/src/profile-logic/sanitize.ts +++ b/src/profile-logic/sanitize.ts @@ -6,7 +6,8 @@ import { getEmptyExtensions, getRawMarkerTableBuilderFromExisting, finishRawMarkerTableBuilder, - shallowCloneFuncTable, + getRawFuncTableBuilderWithExistingContents, + finishRawFuncTableBuilder, } from './data-structures'; import { computeCompactedProfile } from './profile-compacting'; import { StringTable } from '../utils/string-table'; @@ -37,7 +38,7 @@ import type { IndexIntoResourceTable, ProfileIndexTranslationMaps, } from 'firefox-profiler/types'; -import { FrameFlag } from 'firefox-profiler/types'; +import { FrameFlag, FuncFlag } from 'firefox-profiler/types'; export type SanitizeProfileResult = { readonly profile: Profile; @@ -186,8 +187,8 @@ export function sanitizePII( if (sanitizedFuncIndexesToFrameIndex.size) { const resourcesToBeSanitized = new Set(); - const newFuncTable = (newShared.funcTable = - shallowCloneFuncTable(funcTable)); + const newFuncTable = + getRawFuncTableBuilderWithExistingContents(funcTable); const newFrameTable = (newShared.frameTable = { ...frameTable, flags: Array.from(frameTable.flags), @@ -208,13 +209,16 @@ export function sanitizePII( const name = stringTable.indexForString( `` ); + // Preserve IsJS/RelevantForJS from the original; strip everything else. + const oldFlags = funcTable.flags[funcIndex]; + const preservedMask = FuncFlag.IsJS | FuncFlag.RelevantForJS; + newFuncTable.flags.push(oldFlags & preservedMask); newFuncTable.name.push(name); - newFuncTable.isJS.push(funcTable.isJS[funcIndex]); - newFuncTable.relevantForJS.push(funcTable.isJS[funcIndex]); - newFuncTable.resource.push(-1); - newFuncTable.source.push(null); - newFuncTable.lineNumber.push(null); - newFuncTable.columnNumber.push(null); + newFuncTable.resource.push(0); + newFuncTable.source.push(0); + newFuncTable.lineNumber.push(0); + newFuncTable.columnNumber.push(0); + newFuncTable.originalLocation.push(0); newFuncTable.length++; frameIndexes.forEach( @@ -227,13 +231,22 @@ export function sanitizePII( const name = stringTable.indexForString(``); newFuncTable.name[funcIndex] = name; - newFuncTable.source[funcIndex] = null; - if (newFuncTable.resource[funcIndex] >= 0) { + const clearMask = ~( + FuncFlag.HasResource | + FuncFlag.HasSource | + FuncFlag.HasLine | + FuncFlag.HasColumn | + FuncFlag.HasOriginalLocation + ); + if ((newFuncTable.flags[funcIndex] & FuncFlag.HasResource) !== 0) { resourcesToBeSanitized.add(newFuncTable.resource[funcIndex]); } - newFuncTable.resource[funcIndex] = -1; - newFuncTable.lineNumber[funcIndex] = null; - newFuncTable.columnNumber[funcIndex] = null; + newFuncTable.flags[funcIndex] &= clearMask; + newFuncTable.resource[funcIndex] = 0; + newFuncTable.source[funcIndex] = 0; + newFuncTable.lineNumber[funcIndex] = 0; + newFuncTable.columnNumber[funcIndex] = 0; + newFuncTable.originalLocation[funcIndex] = 0; } // In both cases, nullify some information in all frames. @@ -253,9 +266,12 @@ export function sanitizePII( name: resourceTable.name.slice(), host: resourceTable.host.slice(), }); - const remainingResources = new Set( - newFuncTable.resource - ); + const remainingResources = new Set(); + for (let i = 0; i < newFuncTable.length; i++) { + if ((newFuncTable.flags[i] & FuncFlag.HasResource) !== 0) { + remainingResources.add(newFuncTable.resource[i]); + } + } for (const resourceIndex of resourcesToBeSanitized) { if (!remainingResources.has(resourceIndex)) { // This resource was used only by sanitized functions. Sanitize it @@ -268,6 +284,8 @@ export function sanitizePII( } } } + + newShared.funcTable = finishRawFuncTableBuilder(newFuncTable); } // First we'll loop the stack table and populate a typed array with a value diff --git a/src/profile-logic/source-map-symbolication.ts b/src/profile-logic/source-map-symbolication.ts index 3d899b562b..677640726c 100644 --- a/src/profile-logic/source-map-symbolication.ts +++ b/src/profile-logic/source-map-symbolication.ts @@ -68,8 +68,9 @@ import { finishRawFrameTableBuilder, - shallowCloneFuncTable, + finishRawFuncTableBuilder, getRawFrameTableBuilderWithExistingContents, + getRawFuncTableBuilderWithExistingContents, shallowCloneSourceLocationTable, } from './data-structures'; import { StringTable } from '../utils/string-table'; @@ -91,14 +92,14 @@ import type { FunctionScope } from './source-map-scope-tree'; import type { IndexIntoSourceTable, IndexIntoFuncTable, + RawFuncTable, IndexIntoFrameTable, - FuncTable, RawFrameTable, RawProfileSharedData, SourceLocationTable, SourceTable, } from '../types'; -import { FrameFlag } from '../types'; +import { FrameFlag, FuncFlag } from '../types'; import type { NullableMappedPosition } from 'source-map'; import type { SourceMapConsumer } from './source-map-store'; import type { @@ -135,7 +136,7 @@ type ParsedSource = { // tables from the current shared state at apply time. export type SourceMapSymbolicationInput = { frameTable: RawFrameTable; - funcTable: FuncTable; + funcTable: RawFuncTable; sourceLocationTable: SourceLocationTable; sources: SourceTable; stringArray: string[]; @@ -188,7 +189,7 @@ export function symbolicateWithSourceMaps( */ function _identifyToSymbolicate( frameTable: RawFrameTable, - funcTable: FuncTable, + funcTable: RawFuncTable, sources: SourceTable ): { funcsToSymbolicate: IndexIntoFuncTable[]; @@ -211,10 +212,13 @@ function _identifyToSymbolicate( eligibility = _isFuncSymbolicable(funcIndex, funcTable, sources) ? 1 : 2; funcEligibility[funcIndex] = eligibility; - if (eligibility === 1 && funcTable.originalLocation[funcIndex] === null) { - const funcLine = funcTable.lineNumber[funcIndex]; - const funcCol = funcTable.columnNumber[funcIndex]; - if (funcLine !== null && funcCol !== null) { + if (eligibility === 1) { + const funcFlags = funcTable.flags[funcIndex]; + if ( + (funcFlags & FuncFlag.HasOriginalLocation) === 0 && + (funcFlags & FuncFlag.HasLine) !== 0 && + (funcFlags & FuncFlag.HasColumn) !== 0 + ) { funcsToSymbolicate.push(funcIndex); } } @@ -237,16 +241,14 @@ function _identifyToSymbolicate( function _isFuncSymbolicable( funcIndex: IndexIntoFuncTable, - funcTable: FuncTable, + funcTable: RawFuncTable, sources: SourceTable ): boolean { - if (!funcTable.isJS[funcIndex]) { + const flags = funcTable.flags[funcIndex]; + if ((flags & FuncFlag.IsJS) === 0 || (flags & FuncFlag.HasSource) === 0) { return false; } const sourceIndex = funcTable.source[funcIndex]; - if (sourceIndex === null) { - return false; - } return sources.sourceMapURL[sourceIndex] !== null; } @@ -337,19 +339,19 @@ function _buildSourceMapSymbolicationResponse( // Function definitions. These get an original source file. for (const funcIndex of funcsToSymbolicate) { - const sourceIndex = funcTable.source[funcIndex]; - if (sourceIndex === null) { + const funcFlags = funcTable.flags[funcIndex]; + const requiredFlags = + FuncFlag.HasSource | FuncFlag.HasLine | FuncFlag.HasColumn; + if ((funcFlags & requiredFlags) !== requiredFlags) { continue; } + const sourceIndex = funcTable.source[funcIndex]; const consumer = sourceMapStore.getConsumer(sourceIndex); if (consumer === null) { continue; } const line = funcTable.lineNumber[funcIndex]; const column = funcTable.columnNumber[funcIndex]; - if (line === null || column === null) { - continue; - } const remap = _remapPosition( consumer, line, @@ -407,10 +409,11 @@ function _buildSourceMapSymbolicationResponse( // inlined code it may differ (e.g. a function from utils.ts inlined into // app.ts maps frames back to utils.ts). for (const frameIndex of framesToSymbolicate) { - const sourceIndex = funcTable.source[frameTable.func[frameIndex]]; - if (sourceIndex === null) { + const funcIndex = frameTable.func[frameIndex]; + if ((funcTable.flags[funcIndex] & FuncFlag.HasSource) === 0) { continue; } + const sourceIndex = funcTable.source[funcIndex]; const consumer = sourceMapStore.getConsumer(sourceIndex); if (consumer === null) { continue; @@ -956,7 +959,7 @@ export function applySourceMapSymbolicationResponse( shared: RawProfileSharedData, response: SourceMapSymbolicationResponse ): { - newFuncTable: FuncTable; + newFuncTable: RawFuncTable; newFrameTable: RawFrameTable; newSourceLocationTable: SourceLocationTable; newSources: SourceTable; @@ -965,7 +968,7 @@ export function applySourceMapSymbolicationResponse( const { funcTable, frameTable, sourceLocationTable, sources, stringArray } = shared; - const newFuncTable = shallowCloneFuncTable(funcTable); + const newFuncTable = getRawFuncTableBuilderWithExistingContents(funcTable); const newFrameTable = getRawFrameTableBuilderWithExistingContents(frameTable); const newSourceLocationTable = shallowCloneSourceLocationTable(sourceLocationTable); @@ -1006,7 +1009,7 @@ export function applySourceMapSymbolicationResponse( // A concurrent run got here first. Skip the whole entry: the row and // the name go together; writing a name without a sourceLocationTable row // would be inconsistent. - if (newFuncTable.originalLocation[funcIndex] !== null) { + if ((newFuncTable.flags[funcIndex] & FuncFlag.HasOriginalLocation) !== 0) { continue; } const sourceIndex = urlToSourceIndex.get(resolution.originalSource); @@ -1019,6 +1022,7 @@ export function applySourceMapSymbolicationResponse( newSourceLocationTable.column.push(resolution.originalColumn); newSourceLocationTable.length++; newFuncTable.originalLocation[funcIndex] = rowIndex; + newFuncTable.flags[funcIndex] |= FuncFlag.HasOriginalLocation; if (resolution.name !== null) { newFuncTable.name[funcIndex] = stringTable.indexForString( resolution.name @@ -1053,7 +1057,7 @@ export function applySourceMapSymbolicationResponse( } return { - newFuncTable, + newFuncTable: finishRawFuncTableBuilder(newFuncTable), newFrameTable: finishRawFrameTableBuilder(newFrameTable), newSourceLocationTable, newSources, diff --git a/src/profile-logic/symbolication.ts b/src/profile-logic/symbolication.ts index 76a7dd71a1..8bc6ccf39a 100644 --- a/src/profile-logic/symbolication.ts +++ b/src/profile-logic/symbolication.ts @@ -4,14 +4,16 @@ import { getRawStackTableBuilder, finishRawFrameTableBuilder, + finishRawFuncTableBuilder, finishRawStackTableBuilder, - shallowCloneFuncTable, + getRawFuncTableBuilderWithExistingContents, finishRawNativeSymbolTableBuilder, getRawNativeSymbolTableBuilderWithExistingContents, getRawFrameTableBuilderWithExistingContents, } from './data-structures'; import type { RawFrameTableBuilder, + RawFuncTableBuilder, RawNativeSymbolTableBuilder, } from './data-structures'; import { SymbolsNotFoundError } from './errors'; @@ -21,7 +23,6 @@ import type { RawProfileSharedData, RawThread, RawStackTable, - FuncTable, SourceTable, IndexIntoFuncTable, IndexIntoFrameTable, @@ -34,7 +35,7 @@ import type { CallNodePath, Lib, } from 'firefox-profiler/types'; -import { FrameFlag } from 'firefox-profiler/types'; +import { FrameFlag, FuncFlag } from 'firefox-profiler/types'; import type { AbstractSymbolStore, AddressResult, @@ -243,7 +244,7 @@ export type FuncToFuncsMap = Map; // These are created once per batch and mutated in place by each step. type SymbolicationTables = { frameTable: RawFrameTableBuilder; - funcTable: FuncTable; + funcTable: RawFuncTableBuilder; nativeSymbols: RawNativeSymbolTableBuilder; sources: SourceTable; // Maps a filename string index to the index of the native (id === null) @@ -350,11 +351,11 @@ function getSymbolicationInfo( funcsByLib.set(libIndex, funcs); } funcs.add(funcIndex); - if (!resourceForLib.has(libIndex)) { - const resourceIndex = funcTable.resource[funcIndex]; - if (resourceIndex !== -1) { - resourceForLib.set(libIndex, resourceIndex); - } + if ( + !resourceForLib.has(libIndex) && + (funcTable.flags[funcIndex] & FuncFlag.HasResource) !== 0 + ) { + resourceForLib.set(libIndex, funcTable.resource[funcIndex]); } } @@ -546,7 +547,9 @@ export function applySymbolicationSteps( const frameTable = getRawFrameTableBuilderWithExistingContents( oldShared.frameTable ); - const funcTable = shallowCloneFuncTable(oldShared.funcTable); + const funcTable = getRawFuncTableBuilderWithExistingContents( + oldShared.funcTable + ); const nativeSymbols = getRawNativeSymbolTableBuilderWithExistingContents( oldShared.nativeSymbols ); @@ -583,7 +586,7 @@ export function applySymbolicationSteps( let shared: RawProfileSharedData = { ...oldShared, frameTable: finishRawFrameTableBuilder(frameTable), - funcTable, + funcTable: finishRawFuncTableBuilder(funcTable), nativeSymbols: finishRawNativeSymbolTableBuilder(nativeSymbols), }; @@ -838,8 +841,8 @@ function _partiallyApplySymbolicationStep( if (addressResult === undefined) { const symbolName = nativeSymbols.name[nativeSymbolIndex]; let fileNameIndex = null; - const sourceIndex = funcTable.source[oldFunc]; - if (sourceIndex !== null) { + if ((funcTable.flags[oldFunc] & FuncFlag.HasSource) !== 0) { + const sourceIndex = funcTable.source[oldFunc]; fileNameIndex = sources.filename[sourceIndex]; } addressResult = { @@ -885,18 +888,31 @@ function _partiallyApplySymbolicationStep( let funcIndex = funcKeyToFuncMap.get(funcKey); if (funcIndex === undefined) { funcIndex = availableFuncIter.next().value; + const preservedFlagsMask = FuncFlag.IsJS | FuncFlag.RelevantForJS; if (funcIndex === undefined) { // Need a new func. funcIndex = funcTable.length; - funcTable.isJS[funcIndex] = funcTable.isJS[oldFunc]; - funcTable.relevantForJS[funcIndex] = funcTable.relevantForJS[oldFunc]; + funcTable.flags[funcIndex] = + (funcTable.flags[oldFunc] & preservedFlagsMask) | + FuncFlag.HasResource; funcTable.resource[funcIndex] = resourceIndex; - funcTable.source[funcIndex] = null; - funcTable.lineNumber[funcIndex] = null; - funcTable.columnNumber[funcIndex] = null; - funcTable.originalLocation[funcIndex] = null; + funcTable.source[funcIndex] = 0; + funcTable.lineNumber[funcIndex] = 0; + funcTable.columnNumber[funcIndex] = 0; + funcTable.originalLocation[funcIndex] = 0; // The name field will be filled below. funcTable.length++; + } else { + // Reuse an existing func slot: preserve IsJS/RelevantForJS, set + // HasResource, and clear the other flag bits since we're overwriting + // the source/line/column/originalLocation columns below. + funcTable.flags[funcIndex] = + (funcTable.flags[funcIndex] & preservedFlagsMask) | + FuncFlag.HasResource; + funcTable.resource[funcIndex] = resourceIndex; + funcTable.lineNumber[funcIndex] = 0; + funcTable.columnNumber[funcIndex] = 0; + funcTable.originalLocation[funcIndex] = 0; } funcTable.name[funcIndex] = functionStringIndex; // Store filename in sources table if we have one @@ -916,8 +932,10 @@ function _partiallyApplySymbolicationStep( sourceIndexForNativeFilename.set(fileNameStringIndex, sourceIndex); } funcTable.source[funcIndex] = sourceIndex; + funcTable.flags[funcIndex] |= FuncFlag.HasSource; } else { - funcTable.source[funcIndex] = null; + funcTable.source[funcIndex] = 0; + funcTable.flags[funcIndex] &= ~FuncFlag.HasSource; } funcKeyToFuncMap.set(funcKey, funcIndex); } diff --git a/src/profile-logic/transforms.ts b/src/profile-logic/transforms.ts index 39d0bb18fd..2a8f54642c 100644 --- a/src/profile-logic/transforms.ts +++ b/src/profile-logic/transforms.ts @@ -47,7 +47,7 @@ import type { CategoryList, ProfileIndexTranslationMaps, } from 'firefox-profiler/types'; -import { FrameFlag } from 'firefox-profiler/types'; +import { FrameFlag, FuncFlag } from 'firefox-profiler/types'; import type { CallNodeInfo } from 'firefox-profiler/profile-logic/call-node-info'; import type { StringTable } from 'firefox-profiler/utils/string-table'; import { @@ -717,7 +717,10 @@ function _collapseResourceInCallNodePath( callNodePath // Map any collapsed functions into the collapsedFuncIndex .map((pathFuncIndex) => { - return funcTable.resource[pathFuncIndex] === resourceIndex + const hasResource = + (funcTable.flags[pathFuncIndex] & FuncFlag.HasResource) !== 0; + return hasResource && + funcTable.resource[pathFuncIndex] === resourceIndex ? collapsedFuncIndex : pathFuncIndex; }) @@ -990,8 +993,10 @@ export function collapseResource( const newFrameTableFuncCol = frameTable.func.slice(); for (let i = 0; i < frameTable.length; i++) { const funcIndex = frameTable.func[i]; - const resourceIndex = funcTable.resource[funcIndex]; - if (resourceIndex === resourceIndexToCollapse) { + if ( + (funcTable.flags[funcIndex] & FuncFlag.HasResource) !== 0 && + funcTable.resource[funcIndex] === resourceIndexToCollapse + ) { newFrameTableFuncCol[i] = collapsedFuncIndex; } } @@ -1200,8 +1205,9 @@ const FUNC_MATCHES = { combined: (_thread: Thread, _funcIndex: IndexIntoFuncTable) => true, cpp: (thread: Thread, funcIndex: IndexIntoFuncTable): boolean => { const { funcTable, stringTable } = thread; + const funcFlags = funcTable.flags[funcIndex]; // Return quickly if this is a JS frame. - if (thread.funcTable.isJS[funcIndex]) { + if ((funcFlags & FuncFlag.IsJS) !== 0) { return false; } @@ -1211,13 +1217,15 @@ const FUNC_MATCHES = { // frames are not associated with a shared library and thus have no resource const locationString = stringTable.getString(funcTable.name[funcIndex]); const isProbablyJitCode = - funcTable.resource[funcIndex] === -1 && locationString.startsWith('0x'); + (funcFlags & FuncFlag.HasResource) === 0 && + locationString.startsWith('0x'); return !isProbablyJitCode; }, js: (thread: Thread, funcIndex: IndexIntoFuncTable): boolean => { return ( - thread.funcTable.isJS[funcIndex] || - thread.funcTable.relevantForJS[funcIndex] + (thread.funcTable.flags[funcIndex] & + (FuncFlag.IsJS | FuncFlag.RelevantForJS)) !== + 0 ); }, }; @@ -1657,7 +1665,7 @@ export function getBacktraceItemsForStack( return { funcName: stringTable.getString(funcTable.name[funcIndex]), category, - isFrameLabel: funcTable.resource[funcIndex] === -1, + isFrameLabel: (funcTable.flags[funcIndex] & FuncFlag.HasResource) === 0, origin: getOriginAnnotationForFunc( funcIndex, frameIndex, diff --git a/src/profile-logic/wasm-symbolication.ts b/src/profile-logic/wasm-symbolication.ts index a28d227e4f..f831302529 100644 --- a/src/profile-logic/wasm-symbolication.ts +++ b/src/profile-logic/wasm-symbolication.ts @@ -10,6 +10,7 @@ // placeholders in the profile's funcTable. import type { Profile } from 'firefox-profiler/types/profile'; +import { FuncFlag } from 'firefox-profiler/types/profile'; import { StringTable } from 'firefox-profiler/utils/string-table'; export interface WasmSymbolicationSpec { @@ -190,8 +191,11 @@ export function applyWasmSymbolication( let updated = 0; let missingNames = 0; for (let f = 0; f < funcTable.length; f++) { + if ((funcTable.flags[f] & FuncFlag.HasSource) === 0) { + continue; + } const sourceIdx = funcTable.source[f]; - if (sourceIdx === null || !sourceIndexSet.has(sourceIdx)) { + if (!sourceIndexSet.has(sourceIdx)) { continue; } const oldName = stringArray[funcTable.name[f]]; diff --git a/src/profile-query/function-annotate.ts b/src/profile-query/function-annotate.ts index fec5c6b289..f9125ef45b 100644 --- a/src/profile-query/function-annotate.ts +++ b/src/profile-query/function-annotate.ts @@ -21,6 +21,7 @@ import { getNativeSymbolsForFunc, findAddressProofForFile, getOriginalPositionForFrame, + computeFuncTableFromRawFuncTable, } from 'firefox-profiler/profile-logic/profile-data'; import { fetchAssembly } from 'firefox-profiler/utils/fetch-assembly'; import { fetchSource } from 'firefox-profiler/utils/fetch-source'; @@ -33,6 +34,7 @@ import type { SamplesLikeTable, Thread, } from 'firefox-profiler/types'; +import { FuncFlag } from 'firefox-profiler/types'; import type { FunctionAnnotateResult, AnnotateMode, @@ -81,8 +83,8 @@ async function fetchSourceAnnotation( contextOption: string ): Promise { const warnings: string[] = []; - const compiledSourceIndex = profile.shared.funcTable.source[funcIndex]; - if (compiledSourceIndex === null) { + const rawFuncTable = profile.shared.funcTable; + if ((rawFuncTable.flags[funcIndex] & FuncFlag.HasSource) === 0) { if (mode === 'src') { warnings.push( `Function ${functionHandle} has no source index. Use --mode asm for assembly view.` @@ -90,6 +92,7 @@ async function fetchSourceAnnotation( } return { annotation: null, warnings }; } + const compiledSourceIndex = rawFuncTable.source[funcIndex]; const { stackTable, @@ -338,7 +341,8 @@ export async function functionAnnotate( ): Promise { const state = store.getState(); const profile = getProfile(state); - const { funcTable, stringArray, resourceTable } = profile.shared; + const { stringArray, resourceTable } = profile.shared; + const funcTable = computeFuncTableFromRawFuncTable(profile.shared.funcTable); const funcIndex = parseFunctionHandle(functionHandle, funcTable.length); const funcName = stringArray[funcTable.name[funcIndex]]; diff --git a/src/profile-query/function-list.ts b/src/profile-query/function-list.ts index 4ba06cfd90..78772a2dff 100644 --- a/src/profile-query/function-list.ts +++ b/src/profile-query/function-list.ts @@ -9,7 +9,7 @@ import type { FuncTable, ResourceTable, } from 'firefox-profiler/types'; -import { ResourceType, FrameFlag } from 'firefox-profiler/types'; +import { ResourceType, FrameFlag, FuncFlag } from 'firefox-profiler/types'; import { getFunctionHandle } from './function-map'; /** @@ -46,11 +46,11 @@ export function getLibNameForFunc( resourceTable: ResourceTable, stringArray: string[] ): string | null { + if ((funcTable.flags[funcIndex] & FuncFlag.HasResource) === 0) { + return null; + } const resourceIndex = funcTable.resource[funcIndex]; - if ( - resourceIndex === -1 || - resourceTable.type[resourceIndex] !== ResourceType.Library - ) { + if (resourceTable.type[resourceIndex] !== ResourceType.Library) { return null; } return stringArray[resourceTable.name[resourceIndex]]; @@ -384,8 +384,8 @@ export function formatFunctionNameWithLibrary( ); // The func's resource carries the library name for native code, and the // origin / URL for JS code. - const resourceIndex = thread.funcTable.resource[funcIndex]; - if (resourceIndex !== -1) { + if ((thread.funcTable.flags[funcIndex] & FuncFlag.HasResource) !== 0) { + const resourceIndex = thread.funcTable.resource[funcIndex]; const resourceName = thread.stringTable.getString( thread.resourceTable.name[resourceIndex] ); diff --git a/src/profile-query/index.ts b/src/profile-query/index.ts index 0b6826c452..9e17c68560 100644 --- a/src/profile-query/index.ts +++ b/src/profile-query/index.ts @@ -147,7 +147,11 @@ import type { } from './types'; import type { CallTreeCollectionOptions } from './formatters/call-tree'; -import { getThreadsKey } from 'firefox-profiler/profile-logic/profile-data'; +import { + getThreadsKey, + computeFuncTableFromRawFuncTable, +} from 'firefox-profiler/profile-logic/profile-data'; +import { FuncFlag } from 'firefox-profiler/types'; import type { Store } from '../types/store'; function toSourceEntry(source: EligibleSource): SourceEntry { @@ -1280,7 +1284,10 @@ export class ProfileQuerier { ): Promise> { const state = this._store.getState(); const profile = getProfile(state); - const { funcTable, resourceTable, stringArray } = profile.shared; + const { resourceTable, stringArray } = profile.shared; + const funcTable = computeFuncTableFromRawFuncTable( + profile.shared.funcTable + ); // Look up the function const funcIndex = parseFunctionHandle(functionHandle, funcTable.length); @@ -1309,19 +1316,24 @@ export class ProfileQuerier { ): Promise> { const state = this._store.getState(); const profile = getProfile(state); - const { funcTable, resourceTable, stringArray } = profile.shared; + const { resourceTable, stringArray } = profile.shared; + const funcTable = computeFuncTableFromRawFuncTable( + profile.shared.funcTable + ); // Look up the function const funcIndex = parseFunctionHandle(functionHandle, funcTable.length); const funcName = stringArray[funcTable.name[funcIndex]]; - const resourceIndex = funcTable.resource[funcIndex]; - const isJS = funcTable.isJS[funcIndex]; - const relevantForJS = funcTable.relevantForJS[funcIndex]; + const funcFlags = funcTable.flags[funcIndex]; + const isJS = (funcFlags & FuncFlag.IsJS) !== 0; + const relevantForJS = (funcFlags & FuncFlag.RelevantForJS) !== 0; + const hasResource = (funcFlags & FuncFlag.HasResource) !== 0; let resource: FunctionInfoResult['resource']; let library: FunctionInfoResult['library']; - if (resourceIndex !== -1) { + if (hasResource) { + const resourceIndex = funcTable.resource[funcIndex]; resource = { name: stringArray[resourceTable.name[resourceIndex]], index: resourceIndex, diff --git a/src/selectors/per-thread/index.ts b/src/selectors/per-thread/index.ts index 5460a7a95d..af4d8b081c 100644 --- a/src/selectors/per-thread/index.ts +++ b/src/selectors/per-thread/index.ts @@ -32,6 +32,7 @@ import type { ThreadsKey, State, } from 'firefox-profiler/types'; +import { FuncFlag } from 'firefox-profiler/types'; import type { TimingsForPath } from '../../profile-logic/profile-data'; @@ -217,7 +218,7 @@ export const selectedNodeSelectors: NodeSelectors = (() => { } const funcIndex = ProfileData.getLeafFuncIndex(selectedPath); - return funcTable.isJS[funcIndex]; + return (funcTable.flags[funcIndex] & FuncFlag.IsJS) !== 0; } ); diff --git a/src/selectors/profile.ts b/src/selectors/profile.ts index 2d03725270..d879f17dac 100644 --- a/src/selectors/profile.ts +++ b/src/selectors/profile.ts @@ -21,6 +21,7 @@ import { computeTabToThreadIndexesMap, computeStackTableFromRawStackTable, computeFrameTableFromRawFrameTable, + computeFuncTableFromRawFuncTable, computeNativeSymbolTableFromRawNativeSymbolTable, reserveFunctionsForCollapsedResources, computeSamplesTableFromRawSamplesTable, @@ -292,6 +293,11 @@ export const getNativeSymbolTable: Selector = createSelector( computeNativeSymbolTableFromRawNativeSymbolTable ); +export const getFuncTable: Selector = createSelector( + (state: State) => getRawProfileSharedData(state).funcTable, + computeFuncTableFromRawFuncTable +); + export const getStackTable: Selector = createSelector( (state: State) => getRawProfileSharedData(state).stackTable, getFrameTable, diff --git a/src/test/components/CallNodeContextMenu.test.tsx b/src/test/components/CallNodeContextMenu.test.tsx index 0e032e73cf..a5062bd4a1 100644 --- a/src/test/components/CallNodeContextMenu.test.tsx +++ b/src/test/components/CallNodeContextMenu.test.tsx @@ -28,6 +28,7 @@ import { updateBrowserConnectionStatus } from 'firefox-profiler/actions/app'; import { simulateWebChannel } from '../fixtures/mocks/web-channel'; import { retrieveProfileFromBrowser } from '../../actions/receive-profile'; import type { GeckoProfile } from 'firefox-profiler/types'; +import { FuncFlag } from 'firefox-profiler/types'; describe('calltree/CallNodeContextMenu', function () { // Provide a store with a useful profile to assert context menu operations off of. @@ -71,15 +72,19 @@ describe('calltree/CallNodeContextMenu', function () { const { funcTable } = profile.shared; + const jsFuncFlags = + FuncFlag.HasSource | FuncFlag.HasLine | FuncFlag.HasColumn; const funcIndexA = funcNames.indexOf('A.js'); funcTable.source[funcIndexA] = sourceIndex; funcTable.lineNumber[funcIndexA] = 1; funcTable.columnNumber[funcIndexA] = 111; + funcTable.flags[funcIndexA] |= jsFuncFlags; const funcIndexB = funcNames.indexOf('B.js'); funcTable.source[funcIndexB] = sourceIndex; funcTable.lineNumber[funcIndexB] = 2; funcTable.columnNumber[funcIndexB] = 222; + funcTable.flags[funcIndexB] |= jsFuncFlags; const store = storeWithProfile(profile); store.dispatch(changeRightClickedCallNode(0, [funcIndexA, funcIndexB])); diff --git a/src/test/components/FlameGraph.test.tsx b/src/test/components/FlameGraph.test.tsx index 5d39138f5e..b91f50cd39 100644 --- a/src/test/components/FlameGraph.test.tsx +++ b/src/test/components/FlameGraph.test.tsx @@ -48,6 +48,7 @@ import { mockRaf } from '../fixtures/mocks/request-animation-frame'; import { autoMockElementSize } from '../fixtures/mocks/element-size'; import type { CssPixels } from 'firefox-profiler/types'; +import { FuncFlag } from 'firefox-profiler/types'; const GRAPH_WIDTH = 200; const GRAPH_HEIGHT = 300; @@ -356,6 +357,8 @@ function setupFlameGraph() { funcTable.lineNumber[funcIndex] = funcIndex + 10; funcTable.columnNumber[funcIndex] = funcIndex + 100; funcTable.source[funcIndex] = defaultSourceIndex; + funcTable.flags[funcIndex] |= + FuncFlag.HasLine | FuncFlag.HasColumn | FuncFlag.HasSource; } funcTable.source[funcNamesDict.B] = bSourceIndex; diff --git a/src/test/fixtures/profile-summary.ts b/src/test/fixtures/profile-summary.ts index 6f35567fc5..45478b3168 100644 --- a/src/test/fixtures/profile-summary.ts +++ b/src/test/fixtures/profile-summary.ts @@ -8,9 +8,9 @@ import type { IndexIntoStackTable, RawStackTable, RawFrameTable, - FuncTable, + RawFuncTable, } from 'firefox-profiler/types'; -import { FrameFlag } from 'firefox-profiler/types'; +import { FrameFlag, FuncFlag } from 'firefox-profiler/types'; /** * Walks a raw profile and asserts a set of structural invariants. Throws @@ -82,14 +82,13 @@ export function assertProfileIntegrity(profile: Profile): void { `funcTable.name[${fn}] = ${name} is not a valid integer index in [0, ${stringCount})` ); } - const res = funcTable.resource[fn]; - if ( - !Number.isInteger(res) || - (res !== -1 && (res < 0 || res >= resourceTable.length)) - ) { - throw new Error( - `funcTable.resource[${fn}] = ${res} is not a valid integer index in [0, ${resourceTable.length}) or -1` - ); + if ((funcTable.flags[fn] & FuncFlag.HasResource) !== 0) { + const res = funcTable.resource[fn]; + if (!Number.isInteger(res) || res < 0 || res >= resourceTable.length) { + throw new Error( + `funcTable.resource[${fn}] = ${res} is not a valid integer index in [0, ${resourceTable.length})` + ); + } } } @@ -459,7 +458,7 @@ function renderStackPath( stack: IndexIntoStackTable | null, stackTable: RawStackTable, frameTable: RawFrameTable, - funcTable: FuncTable, + funcTable: RawFuncTable, stringArray: string[] ): string { if (stack === null) { diff --git a/src/test/fixtures/profiles/call-nodes.ts b/src/test/fixtures/profiles/call-nodes.ts index dfe3282647..eee03d3455 100644 --- a/src/test/fixtures/profiles/call-nodes.ts +++ b/src/test/fixtures/profiles/call-nodes.ts @@ -43,14 +43,13 @@ export default function getProfile(): Profile { // Be explicit about table creation so flow errors are really readable. const funcTable: FuncTable = { - name: funcNames, - isJS: Array(funcNames.length).fill(false), - resource: Array(funcNames.length).fill(-1), - relevantForJS: Array(funcNames.length).fill(false), - source: Array(funcNames.length).fill(null), - lineNumber: Array(funcNames.length).fill(null), - columnNumber: Array(funcNames.length).fill(null), - originalLocation: Array(funcNames.length).fill(null), + flags: new Uint8Array(funcNames.length), + name: new Int32Array(funcNames), + resource: new Int32Array(funcNames.length), + source: new Int32Array(funcNames.length), + lineNumber: new Int32Array(funcNames.length), + columnNumber: new Int32Array(funcNames.length), + originalLocation: new Int32Array(funcNames.length), length: funcNames.length, }; diff --git a/src/test/fixtures/utils.ts b/src/test/fixtures/utils.ts index c955a2d233..cabb0e3707 100644 --- a/src/test/fixtures/utils.ts +++ b/src/test/fixtures/utils.ts @@ -19,6 +19,7 @@ import { createThreadFromDerivedTables, computeStackTableFromRawStackTable, computeFrameTableFromRawFrameTable, + computeFuncTableFromRawFuncTable, computeNativeSymbolTableFromRawNativeSymbolTable, computeSamplesTableFromRawSamplesTable, computeJsAllocationsTableFromRawJsAllocationsTable, @@ -154,6 +155,7 @@ export function computeThreadFromRawThread( shared.frameTable, categories ); + const funcTable = computeFuncTableFromRawFuncTable(shared.funcTable); const nativeSymbols = computeNativeSymbolTableFromRawNativeSymbolTable( shared.nativeSymbols ); @@ -188,7 +190,7 @@ export function computeThreadFromRawThread( samples, stackTable, frameTable, - shared.funcTable, + funcTable, nativeSymbols, shared.resourceTable, stringTable, diff --git a/src/test/integration/profiler-edit/__snapshots__/profiler-edit.test.ts.snap b/src/test/integration/profiler-edit/__snapshots__/profiler-edit.test.ts.snap index 82decd756c..f666a08608 100644 --- a/src/test/integration/profiler-edit/__snapshots__/profiler-edit.test.ts.snap +++ b/src/test/integration/profiler-edit/__snapshots__/profiler-edit.test.ts.snap @@ -87,7 +87,7 @@ Object { "markerSchema": Array [], "oscpu": "macOS 14.6.1", "pausedRanges": Array [], - "preprocessedProfileVersion": 74, + "preprocessedProfileVersion": 75, "processType": 0, "product": "a.out", "sampleUnits": Object { @@ -593,50 +593,50 @@ Object { }, "funcTable": Object { "columnNumber": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], - "isJS": Array [ - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, + "flags": Array [ + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, ], "length": 13, "lineNumber": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], "name": Array [ 6, @@ -654,34 +654,19 @@ Object { 18, ], "originalLocation": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ], - "relevantForJS": Array [ - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], "resource": Array [ 0, @@ -699,19 +684,19 @@ Object { 5, ], "source": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], }, "nativeSymbols": Object { @@ -1488,7 +1473,7 @@ Object { "markerSchema": Array [], "oscpu": "macOS 14.6.1", "pausedRanges": Array [], - "preprocessedProfileVersion": 74, + "preprocessedProfileVersion": 75, "processType": 0, "product": "a.out", "sampleUnits": Object { @@ -1994,50 +1979,50 @@ Object { }, "funcTable": Object { "columnNumber": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], - "isJS": Array [ - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, + "flags": Array [ + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, ], "length": 13, "lineNumber": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], "name": Array [ 6, @@ -2055,34 +2040,19 @@ Object { 18, ], "originalLocation": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ], - "relevantForJS": Array [ - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], "resource": Array [ 0, @@ -2100,19 +2070,19 @@ Object { 5, ], "source": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], }, "nativeSymbols": Object { @@ -2889,7 +2859,7 @@ Object { "markerSchema": Array [], "oscpu": "macOS 14.6.1", "pausedRanges": Array [], - "preprocessedProfileVersion": 74, + "preprocessedProfileVersion": 75, "processType": 0, "product": "a.out", "sampleUnits": Object { @@ -3395,50 +3365,50 @@ Object { }, "funcTable": Object { "columnNumber": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], - "isJS": Array [ - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, + "flags": Array [ + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, ], "length": 13, "lineNumber": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], "name": Array [ 6, @@ -3456,34 +3426,19 @@ Object { 18, ], "originalLocation": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ], - "relevantForJS": Array [ - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], "resource": Array [ 0, @@ -3501,19 +3456,19 @@ Object { 5, ], "source": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], }, "nativeSymbols": Object { @@ -4290,7 +4245,7 @@ Object { "markerSchema": Array [], "oscpu": "macOS 14.6.1", "pausedRanges": Array [], - "preprocessedProfileVersion": 74, + "preprocessedProfileVersion": 75, "processType": 0, "product": "a.out", "sampleUnits": Object { @@ -4796,50 +4751,50 @@ Object { }, "funcTable": Object { "columnNumber": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], - "isJS": Array [ - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, + "flags": Array [ + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, ], "length": 13, "lineNumber": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], "name": Array [ 6, @@ -4857,34 +4812,19 @@ Object { 18, ], "originalLocation": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ], - "relevantForJS": Array [ - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], "resource": Array [ 0, @@ -4902,19 +4842,19 @@ Object { 5, ], "source": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], }, "nativeSymbols": Object { diff --git a/src/test/store/__snapshots__/profile-view.test.ts.snap b/src/test/store/__snapshots__/profile-view.test.ts.snap index eed3a0100e..f020e95137 100644 --- a/src/test/store/__snapshots__/profile-view.test.ts.snap +++ b/src/test/store/__snapshots__/profile-view.test.ts.snap @@ -436,7 +436,7 @@ Object { "oscpu": "", "physicalCPUs": 0, "platform": "", - "preprocessedProfileVersion": 74, + "preprocessedProfileVersion": 75, "processType": 0, "product": "Firefox", "sourceURL": "", @@ -581,38 +581,38 @@ Object { }, "funcTable": Object { "columnNumber": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], - "isJS": Array [ - false, - false, - false, - false, - false, - false, - false, - false, - false, + "flags": Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], "length": 9, "lineNumber": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], "name": Array [ 0, @@ -626,48 +626,37 @@ Object { 8, ], "originalLocation": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - ], - "relevantForJS": Array [ - false, - false, - false, - false, - false, - false, - false, - false, - false, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], "resource": Array [ - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], "source": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], }, "nativeSymbols": Object { @@ -2561,41 +2550,41 @@ CallTree { ], }, "funcTable": Object { - "columnNumber": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, + "columnNumber": Int32Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], - "isJS": Array [ - false, - false, - false, - false, - false, - false, - false, - false, - false, + "flags": Uint8Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], "length": 9, - "lineNumber": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, + "lineNumber": Int32Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], - "name": Array [ + "name": Int32Array [ 0, 1, 2, @@ -2606,49 +2595,38 @@ CallTree { 7, 8, ], - "originalLocation": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - ], - "relevantForJS": Array [ - false, - false, - false, - false, - false, - false, - false, - false, - false, + "originalLocation": Int32Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], - "resource": Array [ - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, + "resource": Int32Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], - "source": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, + "source": Int32Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], }, "isJsTracer": undefined, @@ -2959,41 +2937,41 @@ Object { ], }, "funcTable": Object { - "columnNumber": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - ], - "isJS": Array [ - false, - false, - false, - false, - false, - false, - false, - false, - false, + "columnNumber": Int32Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + "flags": Uint8Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], "length": 9, - "lineNumber": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - ], - "name": Array [ + "lineNumber": Int32Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + "name": Int32Array [ 0, 1, 2, @@ -3004,49 +2982,38 @@ Object { 7, 8, ], - "originalLocation": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - ], - "relevantForJS": Array [ - false, - false, - false, - false, - false, - false, - false, - false, - false, - ], - "resource": Array [ - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, + "originalLocation": Int32Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], - "source": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, + "resource": Int32Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + "source": Int32Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], }, "isJsTracer": undefined, @@ -3437,41 +3404,41 @@ Object { ], }, "funcTable": Object { - "columnNumber": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - ], - "isJS": Array [ - false, - false, - false, - false, - false, - false, - false, - false, - false, + "columnNumber": Int32Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + "flags": Uint8Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], "length": 9, - "lineNumber": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - ], - "name": Array [ + "lineNumber": Int32Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + "name": Int32Array [ 0, 1, 2, @@ -3482,49 +3449,38 @@ Object { 7, 8, ], - "originalLocation": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - ], - "relevantForJS": Array [ - false, - false, - false, - false, - false, - false, - false, - false, - false, - ], - "resource": Array [ - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, + "originalLocation": Int32Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + "resource": Int32Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], - "source": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, + "source": Int32Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], }, "isJsTracer": undefined, @@ -3821,41 +3777,41 @@ Object { ], }, "funcTable": Object { - "columnNumber": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - ], - "isJS": Array [ - false, - false, - false, - false, - false, - false, - false, - false, - false, + "columnNumber": Int32Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + "flags": Uint8Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], "length": 9, - "lineNumber": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - ], - "name": Array [ + "lineNumber": Int32Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + "name": Int32Array [ 0, 1, 2, @@ -3866,49 +3822,38 @@ Object { 7, 8, ], - "originalLocation": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - ], - "relevantForJS": Array [ - false, - false, - false, - false, - false, - false, - false, - false, - false, - ], - "resource": Array [ - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, + "originalLocation": Int32Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], - "source": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, + "resource": Int32Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + "source": Int32Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], }, "isJsTracer": undefined, @@ -4217,41 +4162,41 @@ Object { ], }, "funcTable": Object { - "columnNumber": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - ], - "isJS": Array [ - false, - false, - false, - false, - false, - false, - false, - false, - false, + "columnNumber": Int32Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + "flags": Uint8Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], "length": 9, - "lineNumber": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - ], - "name": Array [ + "lineNumber": Int32Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + "name": Int32Array [ 0, 1, 2, @@ -4262,49 +4207,38 @@ Object { 7, 8, ], - "originalLocation": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - ], - "relevantForJS": Array [ - false, - false, - false, - false, - false, - false, - false, - false, - false, - ], - "resource": Array [ - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, + "originalLocation": Int32Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + "resource": Int32Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], - "source": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, + "source": Int32Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], }, "isJsTracer": undefined, diff --git a/src/test/store/js-tracer.test.ts b/src/test/store/js-tracer.test.ts index ca5e5335e2..1f30bb9643 100644 --- a/src/test/store/js-tracer.test.ts +++ b/src/test/store/js-tracer.test.ts @@ -19,6 +19,7 @@ import { } from '../fixtures/profiles/processed-profile'; import type { Profile } from 'firefox-profiler/types'; +import { FuncFlag } from 'firefox-profiler/types'; describe('jsTracerFixed', function () { function fixTiming(events: TestDefinedJsTracerEvent[]) { @@ -349,6 +350,11 @@ describe('selectors/getJsTracerTiming', function () { const fooLine = 3; const fooColumn = 5; const { shared } = profile; + const jsFuncFlags = + FuncFlag.IsJS | + FuncFlag.HasLine | + FuncFlag.HasColumn | + FuncFlag.HasSource; shared.funcTable.lineNumber[foo] = fooLine; shared.funcTable.columnNumber[foo] = fooColumn; @@ -357,6 +363,7 @@ describe('selectors/getJsTracerTiming', function () { shared.sources, fooUrlIndex ); + shared.funcTable.flags[foo] |= jsFuncFlags; const bar = funcNamesDict['Bar.js']; const barLine = 7; @@ -368,6 +375,7 @@ describe('selectors/getJsTracerTiming', function () { shared.sources, barUrlIndex ); + shared.funcTable.flags[bar] |= jsFuncFlags; const baz = funcNamesDict['Baz.js']; // Use bar's line and column information. @@ -378,6 +386,7 @@ describe('selectors/getJsTracerTiming', function () { shared.sources, bazUrlIndex ); + shared.funcTable.flags[baz] |= jsFuncFlags; // Manually update the JS tracer events to point to the right column numbers. jsTracer.line[2] = fooLine; diff --git a/src/test/store/source-map-symbolication.test.ts b/src/test/store/source-map-symbolication.test.ts index cdc798c639..f279cbf1ad 100644 --- a/src/test/store/source-map-symbolication.test.ts +++ b/src/test/store/source-map-symbolication.test.ts @@ -35,7 +35,7 @@ import { getProfileFromTextSamples } from '../fixtures/profiles/processed-profil import type { BrowserConnection } from '../../app-logic/browser-connection'; import type { Profile } from 'firefox-profiler/types'; -import { FrameFlag } from 'firefox-profiler/types'; +import { FrameFlag, FuncFlag } from 'firefox-profiler/types'; import type { WorkerInput, WorkerOutput, @@ -133,6 +133,7 @@ function makeProfileWithJsSources(sources: SourceDescriptor[]): Profile { for (let i = 0; i < sources.length; i++) { funcTable.lineNumber[i] = 1; funcTable.columnNumber[i] = 10; + funcTable.flags[i] |= FuncFlag.HasLine | FuncFlag.HasColumn; frameTable.flags[i] |= FrameFlag.HasLine | FrameFlag.HasColumn; frameTable.line[i] = 1; frameTable.column[i] = 15; diff --git a/src/test/unit/__snapshots__/profile-conversion.test.ts.snap b/src/test/unit/__snapshots__/profile-conversion.test.ts.snap index fa71c3fb91..4f9d85b213 100644 --- a/src/test/unit/__snapshots__/profile-conversion.test.ts.snap +++ b/src/test/unit/__snapshots__/profile-conversion.test.ts.snap @@ -42,7 +42,7 @@ Object { "RefreshDriverTick", "Network", ], - "preprocessedProfileVersion": 74, + "preprocessedProfileVersion": 75, "product": "ART Trace (Android)", "symbolicated": true, "version": 36, @@ -1022,7 +1022,7 @@ Object { "RefreshDriverTick", "Network", ], - "preprocessedProfileVersion": 74, + "preprocessedProfileVersion": 75, "product": "ART Trace (Android)", "symbolicated": true, "version": 36, @@ -2305,7 +2305,7 @@ Object { "markerSchemaNames": Array [ "EventDispatch", ], - "preprocessedProfileVersion": 74, + "preprocessedProfileVersion": 75, "product": "Chrome Trace", "symbolicated": true, "version": 36, @@ -2697,7 +2697,7 @@ Object { "markerSchemaNames": Array [ "EventDispatch", ], - "preprocessedProfileVersion": 74, + "preprocessedProfileVersion": 75, "product": "Chrome Trace", "symbolicated": true, "version": 36, @@ -3086,7 +3086,7 @@ Object { "markerSchemaNames": Array [ "EventDispatch", ], - "preprocessedProfileVersion": 74, + "preprocessedProfileVersion": 75, "product": "Chrome Trace", "symbolicated": true, "version": 36, @@ -3187,7 +3187,7 @@ Object { "markerSchemaNames": Array [ "EventDispatch", ], - "preprocessedProfileVersion": 74, + "preprocessedProfileVersion": 75, "product": "Chrome Trace", "symbolicated": true, "version": 36, @@ -3540,7 +3540,7 @@ Object { "markerSchemaNames": Array [ "EventDispatch", ], - "preprocessedProfileVersion": 74, + "preprocessedProfileVersion": 75, "product": "Chrome Trace", "symbolicated": true, "version": 36, @@ -3605,7 +3605,7 @@ Object { "markerSchemaNames": Array [ "EventDispatch", ], - "preprocessedProfileVersion": 74, + "preprocessedProfileVersion": 75, "product": "Chrome Trace", "symbolicated": true, "version": 36, @@ -3759,7 +3759,7 @@ Object { "markerSchemaNames": Array [ "EventDispatch", ], - "preprocessedProfileVersion": 74, + "preprocessedProfileVersion": 75, "product": "Chrome Trace", "symbolicated": true, "version": 36, @@ -3817,7 +3817,7 @@ Object { "importedFrom": undefined, "interval": 1, "markerSchemaNames": Array [], - "preprocessedProfileVersion": 74, + "preprocessedProfileVersion": 75, "product": "Firefox", "symbolicated": true, "version": 36, @@ -4207,7 +4207,7 @@ Object { "importedFrom": undefined, "interval": 1, "markerSchemaNames": Array [], - "preprocessedProfileVersion": 74, + "preprocessedProfileVersion": 75, "product": "Firefox", "symbolicated": true, "version": 36, @@ -4265,7 +4265,7 @@ Object { "importedFrom": undefined, "interval": 1, "markerSchemaNames": Array [], - "preprocessedProfileVersion": 74, + "preprocessedProfileVersion": 75, "product": "Firefox", "symbolicated": true, "version": 36, @@ -4323,7 +4323,7 @@ Object { "importedFrom": undefined, "interval": 1, "markerSchemaNames": Array [], - "preprocessedProfileVersion": 74, + "preprocessedProfileVersion": 75, "product": "Firefox", "symbolicated": true, "version": 36, @@ -4643,7 +4643,7 @@ Object { "importedFrom": "Simpleperf", "interval": 0, "markerSchemaNames": Array [], - "preprocessedProfileVersion": 74, + "preprocessedProfileVersion": 75, "product": "com.example.sampleapplication", "symbolicated": undefined, "version": 30, @@ -5019,7 +5019,7 @@ Object { "importedFrom": "Simpleperf", "interval": 0, "markerSchemaNames": Array [], - "preprocessedProfileVersion": 74, + "preprocessedProfileVersion": 75, "product": "com.example.sampleapplication", "symbolicated": undefined, "version": 30, @@ -5319,7 +5319,7 @@ Object { "importedFrom": "dhat", "interval": 1, "markerSchemaNames": Array [], - "preprocessedProfileVersion": 74, + "preprocessedProfileVersion": 75, "product": "target/debug/examples/work_log (dhat)", "symbolicated": true, "version": 36, @@ -5452,7 +5452,7 @@ Object { "importedFrom": undefined, "interval": 1, "markerSchemaNames": Array [], - "preprocessedProfileVersion": 74, + "preprocessedProfileVersion": 75, "product": "Flamegraph", "symbolicated": true, "version": 36, @@ -5510,7 +5510,7 @@ Object { "importedFrom": undefined, "interval": 1, "markerSchemaNames": Array [], - "preprocessedProfileVersion": 74, + "preprocessedProfileVersion": 75, "product": "Flamegraph", "symbolicated": true, "version": 36, diff --git a/src/test/unit/__snapshots__/profile-upgrading.test.ts.snap b/src/test/unit/__snapshots__/profile-upgrading.test.ts.snap index 285f72f448..a4865443e4 100644 --- a/src/test/unit/__snapshots__/profile-upgrading.test.ts.snap +++ b/src/test/unit/__snapshots__/profile-upgrading.test.ts.snap @@ -40,7 +40,7 @@ Object { "oscpu": undefined, "physicalCPUs": undefined, "platform": undefined, - "preprocessedProfileVersion": 74, + "preprocessedProfileVersion": 75, "processType": 0, "product": "Firefox", "sampleUnits": undefined, @@ -2310,227 +2310,227 @@ Object { }, "funcTable": Object { "columnNumber": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], - "isJS": Array [ - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, + "flags": Array [ + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, ], "length": 72, "lineNumber": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], "name": Array [ 1, @@ -2549,210 +2549,136 @@ Object { 33, 35, 38, - 40, - 42, - 44, - 46, - 49, - 51, - 53, - 55, - 7, - 60, - 62, - 64, - 66, - 68, - 70, - 72, - 74, - 76, - 78, - 80, - 82, - 84, - 86, - 88, - 90, - 92, - 94, - 96, - 98, - 100, - 102, - 104, - 106, - 108, - 110, - 112, - 114, - 116, - 119, - 121, - 123, - 125, - 127, - 129, - 131, - 133, - 135, - 137, - 139, - 141, - 143, - 145, - 147, - 149, - 151, - 153, - ], - "originalLocation": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, + 40, + 42, + 44, + 46, + 49, + 51, + 53, + 55, + 7, + 60, + 62, + 64, + 66, + 68, + 70, + 72, + 74, + 76, + 78, + 80, + 82, + 84, + 86, + 88, + 90, + 92, + 94, + 96, + 98, + 100, + 102, + 104, + 106, + 108, + 110, + 112, + 114, + 116, + 119, + 121, + 123, + 125, + 127, + 129, + 131, + 133, + 135, + 137, + 139, + 141, + 143, + 145, + 147, + 149, + 151, + 153, ], - "relevantForJS": Array [ - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, + "originalLocation": Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], "resource": Array [ 0, @@ -2829,78 +2755,78 @@ Object { 1, ], "source": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], }, "nativeSymbols": Object { @@ -7867,7 +7793,7 @@ Object { "misc": "rv:48.0", "oscpu": "Intel Mac OS X 10.11", "platform": "Macintosh", - "preprocessedProfileVersion": 74, + "preprocessedProfileVersion": 75, "processType": 0, "product": "Firefox", "stackwalk": 1, @@ -8134,32 +8060,32 @@ Object { }, "funcTable": Object { "columnNumber": Array [ - null, - null, - null, - null, - null, - null, - null, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], - "isJS": Array [ - false, - false, - false, - false, - true, - false, - false, + "flags": Array [ + 0, + 4, + 4, + 0, + 29, + 4, + 4, ], "length": 7, "lineNumber": Array [ - null, - null, - null, - null, + 0, + 0, + 0, + 0, 34, - null, - null, + 0, + 0, ], "name": Array [ 5, @@ -8171,40 +8097,31 @@ Object { 7, ], "originalLocation": Array [ - null, - null, - null, - null, - null, - null, - null, - ], - "relevantForJS": Array [ - false, - false, - false, - false, - false, - false, - false, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], "resource": Array [ - -1, 0, 0, - -1, + 0, + 0, 1, 2, 2, ], "source": Array [ - null, - null, - null, - null, 0, - null, - null, + 0, + 0, + 0, + 0, + 0, + 0, ], }, "nativeSymbols": Object { @@ -9295,7 +9212,7 @@ Object { "misc": "rv:48.0", "oscpu": "Intel Mac OS X 10.11", "platform": "Macintosh", - "preprocessedProfileVersion": 74, + "preprocessedProfileVersion": 75, "processType": 0, "product": "Firefox", "stackwalk": 1, @@ -9584,38 +9501,38 @@ Object { }, "funcTable": Object { "columnNumber": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], - "isJS": Array [ - false, - false, - false, - false, - true, - false, - false, - false, - false, + "flags": Array [ + 0, + 4, + 4, + 0, + 29, + 4, + 4, + 2, + 2, ], "length": 9, "lineNumber": Array [ - null, - null, - null, - null, + 0, + 0, + 0, + 0, 34, - null, - null, - null, - null, + 0, + 0, + 0, + 0, ], "name": Array [ 6, @@ -9629,48 +9546,37 @@ Object { 15, ], "originalLocation": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - ], - "relevantForJS": Array [ - false, - false, - false, - false, - false, - false, - false, - true, - true, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], "resource": Array [ - -1, 0, 0, - -1, + 0, + 0, 1, 2, 2, - -1, - -1, + 0, + 0, ], "source": Array [ - null, - null, - null, - null, 0, - null, - null, - null, - null, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], }, "nativeSymbols": Object { @@ -10893,7 +10799,7 @@ Object { "misc": "rv:48.0", "oscpu": "Intel Mac OS X 10.11", "platform": "Macintosh", - "preprocessedProfileVersion": 74, + "preprocessedProfileVersion": 75, "processType": 0, "product": "Firefox", "stackwalk": 1, @@ -11208,38 +11114,38 @@ Object { }, "funcTable": Object { "columnNumber": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], - "isJS": Array [ - false, - false, - false, - false, - true, - false, - false, - false, - false, + "flags": Array [ + 0, + 4, + 4, + 0, + 29, + 4, + 4, + 2, + 2, ], "length": 9, "lineNumber": Array [ - null, - null, - null, - null, + 0, + 0, + 0, + 0, 34, - null, - null, - null, - null, + 0, + 0, + 0, + 0, ], "name": Array [ 7, @@ -11253,48 +11159,37 @@ Object { 16, ], "originalLocation": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - ], - "relevantForJS": Array [ - false, - false, - false, - false, - false, - false, - false, - true, - true, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], "resource": Array [ - -1, 0, 0, - -1, + 0, + 0, 1, 2, 2, - -1, - -1, + 0, + 0, ], "source": Array [ - null, - null, - null, - null, 0, - null, - null, - null, - null, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, ], }, "nativeSymbols": Object { diff --git a/src/test/unit/line-timings.test.ts b/src/test/unit/line-timings.test.ts index 4823e44fe4..c6e1b241ae 100644 --- a/src/test/unit/line-timings.test.ts +++ b/src/test/unit/line-timings.test.ts @@ -20,7 +20,7 @@ import type { IndexIntoCategoryList, LineNumber, } from 'firefox-profiler/types'; -import { FrameFlag } from 'firefox-profiler/types'; +import { FrameFlag, FuncFlag } from 'firefox-profiler/types'; describe('getStackLineInfo', function () { it('computes results for all stacks', function () { @@ -144,6 +144,7 @@ describe('getLineTimings for getStackLineInfo', function () { // Set funcTable.lineNumber to a value for the func of that frame const func = frameTable.func[leafFrame]; funcTable.lineNumber[func] = 35; + funcTable.flags[func] |= FuncFlag.HasLine; const fileStringIndex = stringTable.indexForString('file.js'); const fileSourceIndex = thread.sources.filename.indexOf(fileStringIndex); @@ -202,6 +203,8 @@ describe('getLineTimings for getStackLineInfo', function () { sourceLocationTable.length += 2; funcTable.originalLocation[funcAIndex] = funcAOriginalLocationIdx; funcTable.originalLocation[funcBIndex] = funcBOriginalLocationIdx; + funcTable.flags[funcAIndex] |= FuncFlag.HasOriginalLocation; + funcTable.flags[funcBIndex] |= FuncFlag.HasOriginalLocation; const stackLineInfo = getStackLineInfo( stackTable, @@ -394,6 +397,7 @@ describe('getTotalLineTimingsForCallNode', function () { // Set funcTable.lineNumber to a value for the func of that frame const func = frameTable.func[leafFrame]; funcTable.lineNumber[func] = 35; + funcTable.flags[func] |= FuncFlag.HasLine; // Compute the line timings for the child call node. // The fallback should use funcTable.lineNumber[func] = 35 diff --git a/src/test/unit/merge-compare.test.ts b/src/test/unit/merge-compare.test.ts index 6b4fc0a3c4..0c03235189 100644 --- a/src/test/unit/merge-compare.test.ts +++ b/src/test/unit/merge-compare.test.ts @@ -18,7 +18,7 @@ import { ensureExists } from 'firefox-profiler/utils/types'; import { getTimeRangeIncludingAllThreads } from 'firefox-profiler/profile-logic/profile-data'; import { StringTable } from '../../utils/string-table'; import type { RawProfileSharedData, Profile } from 'firefox-profiler/types'; -import { ResourceType, FrameFlag } from 'firefox-profiler/types'; +import { ResourceType, FrameFlag, FuncFlag } from 'firefox-profiler/types'; import { callTreeFromProfile, formatTree } from '../fixtures/utils'; import { storeWithProfile } from '../fixtures/stores'; import { addTransformToStack } from '../../actions/profile-view'; @@ -1051,7 +1051,9 @@ describe('mergeProfilesForDiffing with source tables', function () { sharedA.sourceLocationTable.column.push(22); sharedA.sourceLocationTable.length = 1; sharedA.funcTable.originalLocation[0] = 0; + sharedA.funcTable.flags[0] |= FuncFlag.HasOriginalLocation; sharedA.frameTable.originalLocation[0] = 0; + sharedA.frameTable.flags[0] |= FrameFlag.HasOriginalLocation; const sharedB = profileB.profile.shared; sharedB.sourceLocationTable.source.push(0); @@ -1059,7 +1061,9 @@ describe('mergeProfilesForDiffing with source tables', function () { sharedB.sourceLocationTable.column.push(44); sharedB.sourceLocationTable.length = 1; sharedB.funcTable.originalLocation[0] = 0; + sharedB.funcTable.flags[0] |= FuncFlag.HasOriginalLocation; sharedB.frameTable.originalLocation[0] = 0; + sharedB.frameTable.flags[0] |= FrameFlag.HasOriginalLocation; const profileState = stateFromLocation({ pathname: '/public/fakehash1/', @@ -1098,10 +1102,14 @@ describe('mergeProfilesForDiffing with source tables', function () { expect(funcAIndex).toBeGreaterThanOrEqual(0); expect(funcBIndex).toBeGreaterThanOrEqual(0); + expect(funcTable.flags[funcAIndex] & FuncFlag.HasOriginalLocation).not.toBe( + 0 + ); + expect(funcTable.flags[funcBIndex] & FuncFlag.HasOriginalLocation).not.toBe( + 0 + ); const funcAOriginalLocationIdx = funcTable.originalLocation[funcAIndex]; const funcBOriginalLocationIdx = funcTable.originalLocation[funcBIndex]; - expect(funcAOriginalLocationIdx).not.toBeNull(); - expect(funcBOriginalLocationIdx).not.toBeNull(); expect( sourceLocationTable.line[ensureExists(funcAOriginalLocationIdx)] ).toBe(11); @@ -1143,8 +1151,9 @@ describe('mergeProfilesForDiffing with source tables', function () { expect(sourceLocationTable.length).toBe(0); expect(funcTable.originalLocation).toHaveLength(funcTable.length); expect(frameTable.originalLocation).toHaveLength(frameTable.length); - for (const v of funcTable.originalLocation) { - expect(v).toBeNull(); + // No HasOriginalLocation bits should be set on any func. + for (let i = 0; i < funcTable.length; i++) { + expect(funcTable.flags[i] & FuncFlag.HasOriginalLocation).toBe(0); } // On the frame table, no HasOriginalLocation bits should be set. for (let i = 0; i < frameTable.length; i++) { diff --git a/src/test/unit/process-profile.test.ts b/src/test/unit/process-profile.test.ts index ad6ec61389..71014b9934 100644 --- a/src/test/unit/process-profile.test.ts +++ b/src/test/unit/process-profile.test.ts @@ -39,7 +39,7 @@ import type { IndexIntoStackTable, GeckoSamples, } from 'firefox-profiler/types'; -import { FrameFlag } from 'firefox-profiler/types'; +import { FrameFlag, FuncFlag } from 'firefox-profiler/types'; describe('extract functions and resource from location strings', function () { // These location strings are turned into the proper funcs. @@ -133,17 +133,26 @@ describe('extract functions and resource from location strings', function () { const locationName = locations[locationIndex]; const funcName = stringTable.getString(funcTable.name[funcIndex]); - const resourceIndex = funcTable.resource[funcIndex]; - const isJS = funcTable.isJS[funcIndex]; - const sourceIndex = funcTable.source[funcIndex]; - const fileNameIndex = - sourceIndex !== null ? sources.filename[sourceIndex] : null; + const funcFlags = funcTable.flags[funcIndex]; + const hasResource = (funcFlags & FuncFlag.HasResource) !== 0; + const resourceIndex = hasResource ? funcTable.resource[funcIndex] : -1; + const isJS = (funcFlags & FuncFlag.IsJS) !== 0; + const hasSource = (funcFlags & FuncFlag.HasSource) !== 0; + const fileNameIndex = hasSource + ? sources.filename[funcTable.source[funcIndex]] + : null; const fileName = fileNameIndex === null ? null : stringTable.getString(fileNameIndex); - const lineNumber = funcTable.lineNumber[funcIndex]; - const columnNumber = funcTable.columnNumber[funcIndex]; + const lineNumber = + (funcFlags & FuncFlag.HasLine) !== 0 + ? funcTable.lineNumber[funcIndex] + : null; + const columnNumber = + (funcFlags & FuncFlag.HasColumn) !== 0 + ? funcTable.columnNumber[funcIndex] + : null; let resourceName, host, resourceType; - if (resourceIndex === -1) { + if (!hasResource) { resourceName = null; host = null; resourceType = null; diff --git a/src/test/unit/profile-data.test.ts b/src/test/unit/profile-data.test.ts index a3423b1a21..4d17eb69d8 100644 --- a/src/test/unit/profile-data.test.ts +++ b/src/test/unit/profile-data.test.ts @@ -65,7 +65,12 @@ import type { IndexIntoCategoryList, IndexIntoNativeSymbolTable, } from 'firefox-profiler/types'; -import { SelectedState, ResourceType, FrameFlag } from 'firefox-profiler/types'; +import { + SelectedState, + ResourceType, + FrameFlag, + FuncFlag, +} from 'firefox-profiler/types'; describe('string-table', function () { const u = StringTable.withBackingArray(['foo', 'bar', 'baz']); @@ -290,7 +295,8 @@ describe('process-profile', function () { expect(shared.frameTable.address[22]).toEqual(0x1a45); expect(shared.frameTable.address[24]).toEqual(0xf84); expect(shared.frameTable.address[25]).toEqual(0xf84); - const funcTableNames = shared.funcTable.name.map( + const funcTableNames = Array.from( + shared.funcTable.name, (nameIndex) => shared.stringArray[nameIndex] ); expect(funcTableNames[0]).toEqual('(root)'); @@ -895,7 +901,7 @@ describe('filter-by-implementation', function () { } const frameIndex = filteredThread.stackTable.frame[stackIndex]; const funcIndex = filteredThread.frameTable.func[frameIndex]; - return filteredThread.funcTable.isJS[funcIndex]; + return (filteredThread.funcTable.flags[funcIndex] & FuncFlag.IsJS) !== 0; } it('will return the same thread if filtering to "all"', function () { diff --git a/src/test/unit/profile-tree.test.ts b/src/test/unit/profile-tree.test.ts index 93e2c75f4c..48c82e95e1 100644 --- a/src/test/unit/profile-tree.test.ts +++ b/src/test/unit/profile-tree.test.ts @@ -14,6 +14,7 @@ import { import { computeFlameGraphRows } from '../../profile-logic/flame-graph'; import { computeFrameTableFromRawFrameTable, + computeFuncTableFromRawFuncTable, getCallNodeInfo, getInvertedCallNodeInfo, getOriginAnnotationForFunc, @@ -21,7 +22,7 @@ import { filterRawThreadSamplesToRange, getSampleIndexToCallNodeIndex, } from '../../profile-logic/profile-data'; -import { ResourceType, FrameFlag } from 'firefox-profiler/types'; +import { ResourceType, FrameFlag, FuncFlag } from 'firefox-profiler/types'; import { callTreeFromProfile, functionListTreeFromProfile, @@ -694,9 +695,14 @@ describe('origin annotation', function () { const resourceIndex = shared.resourceTable.length; const funcIndex = funcNames.indexOf(funcName); shared.funcTable.resource[funcIndex] = resourceIndex; - shared.funcTable.source[funcIndex] = location - ? addSourceToTable(shared.sources, stringTable.indexForString(location)) - : null; + shared.funcTable.flags[funcIndex] |= FuncFlag.HasResource; + if (location) { + shared.funcTable.source[funcIndex] = addSourceToTable( + shared.sources, + stringTable.indexForString(location) + ); + shared.funcTable.flags[funcIndex] |= FuncFlag.HasSource; + } shared.resourceTable.name.push(stringTable.indexForString(name)); shared.resourceTable.host.push( host ? stringTable.indexForString(host) : null @@ -727,7 +733,7 @@ describe('origin annotation', function () { funcNames.indexOf(funcName), null, computeFrameTableFromRawFrameTable(shared.frameTable, undefined), - shared.funcTable, + computeFuncTableFromRawFuncTable(shared.funcTable), shared.resourceTable, stringTable, shared.sources @@ -771,6 +777,8 @@ describe('getOriginAnnotationForFunc with originalLocation', function () { shared.funcTable.source[0] = bundleIndex; shared.funcTable.lineNumber[0] = 1; shared.funcTable.columnNumber[0] = 100; + shared.funcTable.flags[0] |= + FuncFlag.HasSource | FuncFlag.HasLine | FuncFlag.HasColumn; // The text sample produces one frame referencing func 0. Give it a // compiled position so tier-3 fallback has something meaningful to surface. @@ -803,7 +811,7 @@ describe('getOriginAnnotationForFunc with originalLocation', function () { 0, 0, computeFrameTableFromRawFrameTable(shared.frameTable, undefined), - shared.funcTable, + computeFuncTableFromRawFuncTable(shared.funcTable), shared.resourceTable, stringTable, shared.sources, @@ -834,6 +842,7 @@ describe('getOriginAnnotationForFunc with originalLocation', function () { 10, 4 ); + shared.funcTable.flags[0] |= FuncFlag.HasOriginalLocation; expect(callOrigin(null)).toEqual('http://example.com/original.ts:10:4'); }); @@ -860,6 +869,8 @@ describe('getOriginalPositionForFrame', function () { shared.funcTable.source[0] = bundleIndex; shared.funcTable.lineNumber[0] = 1; shared.funcTable.columnNumber[0] = 100; + shared.funcTable.flags[0] |= + FuncFlag.HasSource | FuncFlag.HasLine | FuncFlag.HasColumn; shared.frameTable.flags[0] |= FrameFlag.HasLine | FrameFlag.HasColumn; shared.frameTable.line[0] = 5; shared.frameTable.column[0] = 10; @@ -893,7 +904,7 @@ describe('getOriginalPositionForFrame', function () { 0, 0, computeFrameTableFromRawFrameTable(shared.frameTable, undefined), - shared.funcTable, + computeFuncTableFromRawFuncTable(shared.funcTable), shared.sourceLocationTable ) ).toEqual({ source: originalIndex, line: 42, column: 7 }); @@ -906,12 +917,13 @@ describe('getOriginalPositionForFrame', function () { 10, 4 ); + shared.funcTable.flags[0] |= FuncFlag.HasOriginalLocation; expect( getOriginalPositionForFrame( 0, 0, computeFrameTableFromRawFrameTable(shared.frameTable, undefined), - shared.funcTable, + computeFuncTableFromRawFuncTable(shared.funcTable), shared.sourceLocationTable ) ).toEqual({ source: originalIndex, line: 10, column: 4 }); @@ -924,7 +936,7 @@ describe('getOriginalPositionForFrame', function () { 0, 0, computeFrameTableFromRawFrameTable(shared.frameTable, undefined), - shared.funcTable, + computeFuncTableFromRawFuncTable(shared.funcTable), shared.sourceLocationTable ) ).toEqual({ source: bundleIndex, line: 5, column: 10 }); @@ -940,7 +952,7 @@ describe('getOriginalPositionForFrame', function () { 0, 0, computeFrameTableFromRawFrameTable(shared.frameTable, undefined), - shared.funcTable, + computeFuncTableFromRawFuncTable(shared.funcTable), shared.sourceLocationTable ) ).toEqual({ source: bundleIndex, line: 1, column: 100 }); @@ -953,12 +965,13 @@ describe('getOriginalPositionForFrame', function () { 10, 4 ); + shared.funcTable.flags[0] |= FuncFlag.HasOriginalLocation; expect( getOriginalPositionForFrame( null, 0, computeFrameTableFromRawFrameTable(shared.frameTable, undefined), - shared.funcTable, + computeFuncTableFromRawFuncTable(shared.funcTable), shared.sourceLocationTable ) ).toEqual({ source: originalIndex, line: 10, column: 4 }); @@ -972,12 +985,13 @@ describe('getOriginalPositionForFrame', function () { 10, 4 ); + shared.funcTable.flags[0] |= FuncFlag.HasOriginalLocation; expect( getOriginalPositionForFrame( 0, 0, computeFrameTableFromRawFrameTable(shared.frameTable, undefined), - shared.funcTable, + computeFuncTableFromRawFuncTable(shared.funcTable), null ) ).toEqual({ source: bundleIndex, line: 5, column: 10 }); diff --git a/src/test/url-handling.test.ts b/src/test/url-handling.test.ts index 044983ca6d..72e1a1f257 100644 --- a/src/test/url-handling.test.ts +++ b/src/test/url-handling.test.ts @@ -44,6 +44,7 @@ import type { IndexIntoSourceTable, BottomBoxInfo, } from 'firefox-profiler/types'; +import { FuncFlag } from 'firefox-profiler/types'; import getNiceProfile from './fixtures/profiles/call-nodes'; import queryString from 'query-string'; import { @@ -915,9 +916,8 @@ describe('url upgrading', function () { G.js `); - profile.shared.funcTable.relevantForJS[ - funcNamesDictPerThread.DrelevantForJs - ] = true; + profile.shared.funcTable.flags[funcNamesDictPerThread.DrelevantForJs] |= + FuncFlag.RelevantForJS; const callNodePathBefore = [ funcNamesDictPerThread['B.js'], @@ -967,9 +967,8 @@ describe('url upgrading', function () { E.js `); - profile.shared.funcTable.relevantForJS[ - funcNamesDictPerThread.BrelevantForJs - ] = true; + profile.shared.funcTable.flags[funcNamesDictPerThread.BrelevantForJs] |= + FuncFlag.RelevantForJS; const callNodePathBefore = [ funcNamesDictPerThread['C.js'], @@ -1018,9 +1017,8 @@ describe('url upgrading', function () { F.js `); - profile.shared.funcTable.relevantForJS[ - funcNamesDictPerThread.BrelevantForJs - ] = true; + profile.shared.funcTable.flags[funcNamesDictPerThread.BrelevantForJs] |= + FuncFlag.RelevantForJS; const callNodePathBefore = [ funcNamesDictPerThread['D.js'], @@ -1068,9 +1066,8 @@ describe('url upgrading', function () { G.js E.js `); - profile.shared.funcTable.relevantForJS[ - funcNamesDictPerThread.CrelevantForJs - ] = true; + profile.shared.funcTable.flags[funcNamesDictPerThread.CrelevantForJs] |= + FuncFlag.RelevantForJS; const callNodePathBefore = [ funcNamesDictPerThread['B.js'], @@ -1118,9 +1115,8 @@ describe('url upgrading', function () { G.js E.js `); - profile.shared.funcTable.relevantForJS[ - funcNamesDictPerThread.BrelevantForJs - ] = true; + profile.shared.funcTable.flags[funcNamesDictPerThread.BrelevantForJs] |= + FuncFlag.RelevantForJS; const callNodePathBefore = [ funcNamesDictPerThread['C.js'], diff --git a/src/types/actions.ts b/src/types/actions.ts index 888a3b05a9..01d57310b3 100644 --- a/src/types/actions.ts +++ b/src/types/actions.ts @@ -13,7 +13,7 @@ import type { IndexIntoCategoryList, PageList, IndexIntoSourceTable, - FuncTable, + RawFuncTable, RawFrameTable, SourceLocationTable, SourceTable, @@ -456,7 +456,7 @@ type ReceiveProfileAction = | { readonly type: 'SOURCE_MAP_SYMBOLICATION_FAILED' } | { readonly type: 'BULK_SOURCE_MAP_SYMBOLICATION'; - readonly newFuncTable: FuncTable; + readonly newFuncTable: RawFuncTable; readonly newFrameTable: RawFrameTable; readonly newSourceLocationTable: SourceLocationTable; readonly newSources: SourceTable; diff --git a/src/types/profile-derived.ts b/src/types/profile-derived.ts index fb74792a12..b93f55826d 100644 --- a/src/types/profile-derived.ts +++ b/src/types/profile-derived.ts @@ -20,7 +20,6 @@ import type { ProcessType, PausedRange, RawMarkerTable, - FuncTable, ResourceTable, JsTracerTable, IndexIntoStackTable, @@ -305,6 +304,27 @@ export type FrameTable = { length: number; }; +/** + * The `FuncTable` type of the derived thread. + * + * Differs from `RawFuncTable` in that all columns are always stored as typed + * arrays. In `RawFuncTable`, these columns may be either regular arrays or + * typed arrays, since regular arrays are convenient during construction. + * + * See the comment on `RawFuncTable` for the semantics of the `flags` column + * and how it relates to the other columns. + */ +export type FuncTable = { + flags: Uint8Array; + name: Int32Array; + resource: Int32Array; + source: Int32Array; + lineNumber: Int32Array; + columnNumber: Int32Array; + originalLocation: Int32Array; + length: number; +}; + /** * The `NativeSymbolTable` type of the derived thread. * diff --git a/src/types/profile.ts b/src/types/profile.ts index 6414dc43f0..8337046d64 100644 --- a/src/types/profile.ts +++ b/src/types/profile.ts @@ -398,36 +398,69 @@ export type RawFrameTable = { * were created upfront to become orphaned, as the frames that originally referred * to them get reassigned to the canonical func for their actual function. */ -export type FuncTable = { - // The function name. - name: Array; +/** + * Bit flags for the `flags` column of the RawFuncTable / FuncTable. + * Each flag indicates whether the corresponding column carries a meaningful + * value for that func; when a flag bit is unset, the value in the associated + * column is ignored. + */ +export const FuncFlag = { + // Set when this func is a JavaScript function. + IsJS: 1 << 0, + // Set when this func should be treated as "relevant for JS" (e.g. DOM API + // label funcs). Non-JavaScript functions can be marked as "relevant for JS" + // so that they show up in JavaScript stack views. IsJS implies RelevantForJS + // in practice but the bit is stored explicitly. + RelevantForJS: 1 << 1, + // Set when `resource` is meaningful (i.e. this func has an associated + // resource, not the sentinel). + HasResource: 1 << 2, + // Set when `source` is meaningful (this func has an associated source file). + HasSource: 1 << 3, + // Set when `lineNumber` is meaningful. + HasLine: 1 << 4, + // Set when `columnNumber` is meaningful. + HasColumn: 1 << 5, + // Set when `originalLocation` is meaningful. + HasOriginalLocation: 1 << 6, +} as const; + +export type FuncFlags = number; + +export type RawFuncTable = { + // A bitfield with one bit per optional column, drawn from `FuncFlag`. + // For each row, the flag bit tells you whether the value in the + // corresponding column is meaningful. When a bit is unset, the value in the + // corresponding column has no meaning; producers can store any value there + // (typically 0) and consumers must ignore it. + flags: number[] | Uint8Array; - // isJS and relevantForJS describe the function type. Non-JavaScript functions - // can be marked as "relevant for JS" so that for example DOM API label functions - // will show up in any JavaScript stack views. - // It may be worth combining these two fields into one: - // https://github.com/firefox-devtools/profiler/issues/2543 - isJS: Array; - relevantForJS: Array; + // The function name. + name: Array | Int32Array; // The resource describes "Which bag of code did this function come from?". // For JS functions, the resource is of type addon, webhost, otherhost, or url. // For native functions, the resource is of type library. - // For labels and for other unidentified functions, we set the resource to -1. - resource: Array; + // Only meaningful when `HasResource` is set in `flags`. + resource: Array | Int32Array; // These are non-null for JS functions only. The line and column describe the // location of the *start* of the JS function. As for the information about which // which lines / columns inside the function were actually hit during execution, // that information is stored in the frameTable, not in the funcTable. - source: Array; - lineNumber: Array; - columnNumber: Array; + // Only meaningful when `HasSource` is set. + source: Array | Int32Array; + // Only meaningful when `HasLine` is set. + lineNumber: Array | Int32Array; + // Only meaningful when `HasColumn` is set. + columnNumber: Array | Int32Array; - // Index into the sourceLocationTable, or null if not source-mapped. - // Points to the original source file, line, and column for this function's - // definition. - originalLocation: Array; + // Index into the sourceLocationTable, pointing at the original source file, + // line, and column for this function's definition. Only meaningful when + // `HasOriginalLocation` is set. + originalLocation: + | Array + | Int32Array; length: number; }; @@ -1153,7 +1186,7 @@ export type SourceLocationTable = { export type RawProfileSharedData = { stackTable: RawStackTable; frameTable: RawFrameTable; - funcTable: FuncTable; + funcTable: RawFuncTable; resourceTable: ResourceTable; nativeSymbols: RawNativeSymbolTable; // Strings for profiles are collected into a single table, and are referred to by