summaryrefslogtreecommitdiffstats
path: root/devtools/client/performance/modules/logic/jit.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/performance/modules/logic/jit.js')
-rw-r--r--devtools/client/performance/modules/logic/jit.js350
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;