diff options
Diffstat (limited to 'devtools/client/performance/modules/logic/jit.js')
-rw-r--r-- | devtools/client/performance/modules/logic/jit.js | 350 |
1 files changed, 350 insertions, 0 deletions
diff --git a/devtools/client/performance/modules/logic/jit.js b/devtools/client/performance/modules/logic/jit.js new file mode 100644 index 0000000000..5063dd4a8b --- /dev/null +++ b/devtools/client/performance/modules/logic/jit.js @@ -0,0 +1,350 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +// An outcome of an OptimizationAttempt that is considered successful. +const SUCCESSFUL_OUTCOMES = [ + "GenericSuccess", + "Inlined", + "DOM", + "Monomorphic", + "Polymorphic", +]; + +/** + * Model representing JIT optimization sites from the profiler + * for a frame (represented by a FrameNode). Requires optimization data from + * a profile, which is an array of RawOptimizationSites. + * + * When the ThreadNode for the profile iterates over the samples' frames, each + * frame's optimizations are accumulated in their respective FrameNodes. Each + * FrameNode may contain many different optimization sites. One sample may + * pick up optimization X on line Y in the frame, with the next sample + * containing optimization Z on line W in the same frame, as each frame is + * only function. + * + * An OptimizationSite contains a record of how many times the + * RawOptimizationSite was sampled, as well as the unique id based off of the + * original profiler array, and the RawOptimizationSite itself as a reference. + * @see devtools/client/performance/modules/logic/tree-model.js + * + * @struct RawOptimizationSite + * A structure describing a location in a script that was attempted to be optimized. + * Contains all the IonTypes observed, and the sequence of OptimizationAttempts that + * were attempted, and the line and column in the script. This is retrieved from the + * profiler after a recording, and our base data structure. Should always be referenced, + * and unmodified. + * + * Note that propertyName is an index into a string table, which needs to be + * provided in order for the raw optimization site to be inflated. + * + * @type {Array<IonType>} types + * @type {Array<OptimizationAttempt>} attempts + * @type {?number} propertyName + * @type {number} line + * @type {number} column + * + * + * @struct IonType + * IonMonkey attempts to classify each value in an optimization site by some type. + * Based off of the observed types for a value (like a variable that could be a + * string or an instance of an object), it determines what kind of type it should be + * classified as. Each IonType here contains an array of all ObservedTypes under `types`, + * the Ion type that IonMonkey decided this value should be (Int32, Object, etc.) as + * `mirType`, and the component of this optimization type that this value refers to -- + * like a "getter" optimization, `a[b]`, has site `a` (the "Receiver") and `b` + * (the "Index"). + * + * Generally the more ObservedTypes, the more deoptimized this OptimizationSite is. + * There could be no ObservedTypes, in which case `typeset` is undefined. + * + * @type {?Array<ObservedType>} typeset + * @type {string} site + * @type {string} mirType + * + * + * @struct ObservedType + * When IonMonkey attempts to determine what type a value is, it checks on each sample. + * The ObservedType can be thought of in more of JavaScripty-terms, rather than C++. + * The `keyedBy` property is a high level description of the type, like "primitive", + * "constructor", "function", "singleton", "alloc-site" (that one is a bit more weird). + * If the `keyedBy` type is a function or constructor, the ObservedType should have a + * `name` property, referring to the function or constructor name from the JS source. + * If IonMonkey can determine the origin of this type (like where the constructor is + * defined), the ObservedType will also have `location` and `line` properties, but + * `location` can sometimes be non-URL strings like "self-hosted" or a memory location + * like "102ca7880", or no location at all, and maybe `line` is 0 or undefined. + * + * @type {string} keyedBy + * @type {?string} name + * @type {?string} location + * @type {?string} line + * + * + * @struct OptimizationAttempt + * Each RawOptimizationSite contains an array of OptimizationAttempts. Generally, + * IonMonkey goes through a series of strategies for each kind of optimization, starting + * from most-niche and optimized, to the less-optimized, but more general strategies -- + * for example, a getter opt may first try to optimize for the scenario of a getter on an + * `arguments` object -- that will fail most of the time, as most objects are not + * arguments objects, but it will attempt several strategies in order until it finds a + * strategy that works, or fails. Even in the best scenarios, some attempts will fail + * (like the arguments getter example), which is OK, as long as some attempt succeeds + * (with the earlier attempts preferred, as those are more optimized). In an + * OptimizationAttempt structure, we store just the `strategy` name and `outcome` name, + * both from enums in js/public/TrackedOptimizationInfo.h as TRACKED_STRATEGY_LIST and + * TRACKED_OUTCOME_LIST, respectively. An array of successful outcome strings are above + * in SUCCESSFUL_OUTCOMES. + * + * @see js/public/TrackedOptimizationInfo.h + * + * @type {string} strategy + * @type {string} outcome + */ + +/* + * A wrapper around RawOptimizationSite to record sample count and ID (referring to the + * index of where this is in the initially seeded optimizations data), so we don't mutate + * the original data from the profiler. Provides methods to access the underlying + * optimization data easily, so understanding the semantics of JIT data isn't necessary. + * + * @constructor + * + * @param {Array<RawOptimizationSite>} optimizations + * @param {number} optsIndex + * + * @type {RawOptimizationSite} data + * @type {number} samples + * @type {number} id + */ + +const OptimizationSite = function(id, opts) { + this.id = id; + this.data = opts; + this.samples = 1; +}; + +/** + * Constructor for JITOptimizations. A collection of OptimizationSites for a frame. + * + * @constructor + * @param {Array<RawOptimizationSite>} rawSites + * Array of raw optimization sites. + * @param {Array<string>} stringTable + * Array of strings from the profiler used to inflate + * JIT optimizations. Do not modify this! + */ + +const JITOptimizations = function(rawSites, stringTable) { + // Build a histogram of optimization sites. + const sites = []; + + for (const rawSite of rawSites) { + const existingSite = sites.find(site => site.data === rawSite); + if (existingSite) { + existingSite.samples++; + } else { + sites.push(new OptimizationSite(sites.length, rawSite)); + } + } + + // Inflate the optimization information. + for (const site of sites) { + const data = site.data; + const STRATEGY_SLOT = data.attempts.schema.strategy; + const OUTCOME_SLOT = data.attempts.schema.outcome; + const attempts = data.attempts.data.map(a => { + return { + id: site.id, + strategy: stringTable[a[STRATEGY_SLOT]], + outcome: stringTable[a[OUTCOME_SLOT]], + }; + }); + const types = data.types.map(t => { + const typeset = maybeTypeset(t.typeset, stringTable); + if (typeset) { + typeset.forEach(ts => { + ts.id = site.id; + }); + } + + return { + id: site.id, + typeset, + site: stringTable[t.site], + mirType: stringTable[t.mirType], + }; + }); + // Add IDs to to all children objects, so we can correllate sites when + // just looking at a specific type, attempt, etc.. + attempts.id = types.id = site.id; + + site.data = { + attempts, + types, + propertyName: maybeString(stringTable, data.propertyName), + line: data.line, + column: data.column, + }; + } + + this.optimizationSites = sites.sort((a, b) => b.samples - a.samples); +}; + +/** + * Make JITOptimizations iterable. + */ +JITOptimizations.prototype = { + [Symbol.iterator]: function*() { + yield* this.optimizationSites; + }, + + get length() { + return this.optimizationSites.length; + }, +}; + +/** + * Takes an "outcome" string from an OptimizationAttempt and returns + * a boolean indicating whether or not its a successful outcome. + * + * @return {boolean} + */ + +function isSuccessfulOutcome(outcome) { + return !!~SUCCESSFUL_OUTCOMES.indexOf(outcome); +} + +/** + * Takes an OptimizationSite. Returns a boolean indicating if the passed + * in OptimizationSite has a "good" outcome at the end of its attempted strategies. + * + * @param {OptimizationSite} optimizationSite + * @return {boolean} + */ + +function hasSuccessfulOutcome(optimizationSite) { + const attempts = optimizationSite.data.attempts; + const lastOutcome = attempts[attempts.length - 1].outcome; + return isSuccessfulOutcome(lastOutcome); +} + +function maybeString(stringTable, index) { + return index ? stringTable[index] : undefined; +} + +function maybeTypeset(typeset, stringTable) { + if (!typeset) { + return undefined; + } + return typeset.map(ty => { + return { + keyedBy: maybeString(stringTable, ty.keyedBy), + name: maybeString(stringTable, ty.name), + location: maybeString(stringTable, ty.location), + line: ty.line, + }; + }); +} + +// Map of optimization implementation names to an enum. +const IMPLEMENTATION_MAP = { + interpreter: 0, + baseline: 1, + ion: 2, +}; +const IMPLEMENTATION_NAMES = Object.keys(IMPLEMENTATION_MAP); + +/** + * Takes data from a FrameNode and computes rendering positions for + * a stacked mountain graph, to visualize JIT optimization tiers over time. + * + * @param {FrameNode} frameNode + * The FrameNode who's optimizations we're iterating. + * @param {Array<number>} sampleTimes + * An array of every sample time within the range we're counting. + * From a ThreadNode's `sampleTimes` property. + * @param {number} bucketSize + * Size of each bucket in milliseconds. + * `duration / resolution = bucketSize` in OptimizationsGraph. + * @return {?Array<object>} + */ +function createTierGraphDataFromFrameNode(frameNode, sampleTimes, bucketSize) { + const tierData = frameNode.getTierData(); + const stringTable = frameNode._stringTable; + const output = []; + let implEnum; + + let tierDataIndex = 0; + let nextOptSample = tierData[tierDataIndex]; + + // Bucket data + let samplesInCurrentBucket = 0; + let currentBucketStartTime = sampleTimes[0]; + let bucket = []; + + // Store previous data point so we can have straight vertical lines + let previousValues; + + // Iterate one after the samples, so we can finalize the last bucket + for (let i = 0; i <= sampleTimes.length; i++) { + const sampleTime = sampleTimes[i]; + + // If this sample is in the next bucket, or we're done + // checking sampleTimes and on the last iteration, finalize previous bucket + if ( + sampleTime >= currentBucketStartTime + bucketSize || + i >= sampleTimes.length + ) { + const dataPoint = {}; + dataPoint.values = []; + dataPoint.delta = currentBucketStartTime; + + // Map the opt site counts as a normalized percentage (0-1) + // of its count in context of total samples this bucket + for (let j = 0; j < IMPLEMENTATION_NAMES.length; j++) { + dataPoint.values[j] = (bucket[j] || 0) / (samplesInCurrentBucket || 1); + } + + // Push the values from the previous bucket to the same time + // as the current bucket so we get a straight vertical line. + if (previousValues) { + const data = Object.create(null); + data.values = previousValues; + data.delta = currentBucketStartTime; + output.push(data); + } + + output.push(dataPoint); + + // Set the new start time of this bucket and reset its count + currentBucketStartTime += bucketSize; + samplesInCurrentBucket = 0; + previousValues = dataPoint.values; + bucket = []; + } + + // If this sample observed an optimization in this frame, record it + if (nextOptSample && nextOptSample.time === sampleTime) { + // If no implementation defined, it was the "interpreter". + implEnum = + IMPLEMENTATION_MAP[ + stringTable[nextOptSample.implementation] || "interpreter" + ]; + bucket[implEnum] = (bucket[implEnum] || 0) + 1; + nextOptSample = tierData[++tierDataIndex]; + } + + samplesInCurrentBucket++; + } + + return output; +} + +exports.createTierGraphDataFromFrameNode = createTierGraphDataFromFrameNode; +exports.OptimizationSite = OptimizationSite; +exports.JITOptimizations = JITOptimizations; +exports.hasSuccessfulOutcome = hasSuccessfulOutcome; +exports.isSuccessfulOutcome = isSuccessfulOutcome; +exports.SUCCESSFUL_OUTCOMES = SUCCESSFUL_OUTCOMES; |