/* 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 browser/devtools/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 `types` 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;
};

/**
 * Returns a boolean indicating if the passed in OptimizationSite
 * has a "good" outcome at the end of its attempted strategies.
 *
 * @param {Array<string>} stringTable
 * @return {boolean}
 */

OptimizationSite.prototype.hasSuccessfulOutcome = function () {
  let attempts = this.getAttempts();
  let lastOutcome = attempts[attempts.length - 1].outcome;
  return OptimizationSite.isSuccessfulOutcome(lastOutcome);
};

/**
 * Returns the last attempted OptimizationAttempt for this OptimizationSite.
 *
 * @return {Array<OptimizationAttempt>}
 */

OptimizationSite.prototype.getAttempts = function () {
  return this.data.attempts;
};

/**
 * Returns all IonTypes in this OptimizationSite.
 *
 * @return {Array<IonType>}
 */

OptimizationSite.prototype.getIonTypes = function () {
  return this.data.types;
};


/**
 * 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.
  let sites = [];

  for (let rawSite of rawSites) {
    let existingSite = sites.find((site) => site.data === rawSite);
    if (existingSite) {
      existingSite.samples++;
    } else {
      sites.push(new OptimizationSite(sites.length, rawSite));
    }
  }

  // Inflate the optimization information.
  for (let site of sites) {
    let data = site.data;
    let STRATEGY_SLOT = data.attempts.schema.strategy;
    let OUTCOME_SLOT = data.attempts.schema.outcome;

    site.data = {
      attempts: data.attempts.data.map((a) => {
        return {
          strategy: stringTable[a[STRATEGY_SLOT]],
          outcome: stringTable[a[OUTCOME_SLOT]]
        }
      }),

      types: data.types.map((t) => {
        return {
          typeset: maybeTypeset(t.typeset, stringTable),
          site: stringTable[t.site],
          mirType: stringTable[t.mirType]
        };
      }),

      propertyName: maybeString(stringTable, data.propertyName),
      line: data.line,
      column: data.column
    };
  }

  this.optimizationSites = sites.sort((a, b) => b.samples - a.samples);;
};

/**
 * Takes an "outcome" string from an OptimizationAttempt and returns
 * a boolean indicating whether or not its a successful outcome.
 *
 * @return {boolean}
 */

OptimizationSite.isSuccessfulOutcome = JITOptimizations.isSuccessfulOutcome = function (outcome) {
  return !!~SUCCESSFUL_OUTCOMES.indexOf(outcome);
};

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
    };
  });
}

exports.OptimizationSite = OptimizationSite;
exports.JITOptimizations = JITOptimizations;
