"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.Referentializer = undefined;

var _environment = require("../environment.js");

var _babelTypes = require("babel-types");

var t = _interopRequireWildcard(_babelTypes);

var _generator = require("../utils/generator.js");

var _invariant = require("../invariant.js");

var _invariant2 = _interopRequireDefault(_invariant);

var _types = require("./types.js");

var _utils = require("./utils.js");

var _realm = require("../realm.js");

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }

/*
 * This class helps fixup names in residual functions for variables that these
 * functions capture from parent scopes.
 * For each ReferentializationScope it creates a _get_scope_binding function
 * that contains the initialization for all of that scope's FunctionInstances
 * which will contain a switch statement with all the initializations.
 */
class Referentializer {
  constructor(realm, options, scopeNameGenerator, referentializedNameGenerator, statistics) {
    this._options = options;
    this.scopeNameGenerator = scopeNameGenerator;
    this.statistics = statistics;

    this.referentializationState = new Map();
    this._referentializedNameGenerator = referentializedNameGenerator;
    this.realm = realm;
  }

  _createReferentializationState() {
    return {
      capturedScopeInstanceIdx: 0,
      capturedScopesArray: t.identifier(this.scopeNameGenerator.generate("main")),
      capturedScopeAccessFunctionId: t.identifier(this.scopeNameGenerator.generate("get_scope_binding")),
      serializedScopes: new Map()
    };
  }

  _getReferentializationState(referentializationScope) {
    return (0, _utils.getOrDefault)(this.referentializationState, referentializationScope, this._createReferentializationState.bind(this));
  }

  // Generate a shared function for accessing captured scope bindings.
  // TODO: skip generating this function if the captured scope is not shared by multiple residual functions.
  createCaptureScopeAccessFunction(referentializationScope) {
    const body = [];
    const selectorParam = t.identifier("__selector");
    const captured = t.identifier("__captured");
    const capturedScopesArray = this._getReferentializationState(referentializationScope).capturedScopesArray;
    const selectorExpression = t.memberExpression(capturedScopesArray, selectorParam, /*Indexer syntax*/true);

    // One switch case for one scope.
    const cases = [];
    const serializedScopes = this._getReferentializationState(referentializationScope).serializedScopes;
    for (const scopeBinding of serializedScopes.values()) {
      const scopeObjectExpression = t.arrayExpression(scopeBinding.initializationValues);
      cases.push(t.switchCase(t.numericLiteral(scopeBinding.id), [t.expressionStatement(t.assignmentExpression("=", captured, scopeObjectExpression)), t.breakStatement()]));
    }
    // Default case.
    cases.push(t.switchCase(null, [t.throwStatement(t.newExpression(t.identifier("Error"), [t.stringLiteral("Unknown scope selector")]))]));

    body.push(t.variableDeclaration("var", [t.variableDeclarator(captured)]));
    body.push(t.switchStatement(selectorParam, cases));
    body.push(t.expressionStatement(t.assignmentExpression("=", selectorExpression, captured)));
    body.push(t.returnStatement(captured));
    const factoryFunction = t.functionExpression(null, [selectorParam], t.blockStatement(body));
    const accessFunctionId = this._getReferentializationState(referentializationScope).capturedScopeAccessFunctionId;
    return t.variableDeclaration("var", [t.variableDeclarator(accessFunctionId, factoryFunction)]);
  }

  _getReferentializationScope(residualBinding) {
    if (residualBinding.potentialReferentializationScopes.has("GLOBAL")) return "GLOBAL";
    if (residualBinding.potentialReferentializationScopes.size > 1) {
      // TODO Revisit for nested optimized functions.
      return "GLOBAL";
    }
    for (let scope of residualBinding.potentialReferentializationScopes) return scope;
    (0, _invariant2.default)(false);
  }

  _getSerializedBindingScopeInstance(residualBinding) {
    let declarativeEnvironmentRecord = residualBinding.declarativeEnvironmentRecord;
    (0, _invariant2.default)(declarativeEnvironmentRecord);

    let referentializationScope = this._getReferentializationScope(residualBinding);

    // figure out if this is accessed only from additional functions
    let refState = this._getReferentializationState(referentializationScope);
    let scope = refState.serializedScopes.get(declarativeEnvironmentRecord);
    if (!scope) {
      scope = {
        name: this.scopeNameGenerator.generate(),
        id: refState.capturedScopeInstanceIdx++,
        initializationValues: [],
        referentializationScope
      };
      refState.serializedScopes.set(declarativeEnvironmentRecord, scope);
    }

    (0, _invariant2.default)(scope.referentializationScope === referentializationScope);
    (0, _invariant2.default)(!residualBinding.scope || residualBinding.scope === scope);
    residualBinding.scope = scope;
    return scope;
  }

  getReferentializedScopeInitialization(scope, scopeName) {
    const capturedScope = scope.capturedScope;
    (0, _invariant2.default)(capturedScope);
    const state = this._getReferentializationState(scope.referentializationScope);
    const funcName = state.capturedScopeAccessFunctionId;
    const scopeArray = state.capturedScopesArray;
    // First get scope array entry and check if it's already initialized.
    // Only if not yet, then call the initialization function.
    const init = t.logicalExpression("||", t.memberExpression(scopeArray, scopeName, true), t.callExpression(funcName, [scopeName]));
    return [t.variableDeclaration("var", [t.variableDeclarator(t.identifier(capturedScope), init)])];
  }

  referentializeBinding(residualBinding, name, instance) {
    if (this._options.simpleClosures) {
      // When simpleClosures is enabled, then space for captured mutable bindings is allocated upfront.
      let serializedBindingId = t.identifier(this._referentializedNameGenerator.generate(name));
      let serializedValue = residualBinding.serializedValue;
      (0, _invariant2.default)(serializedValue);
      let declar = t.variableDeclaration("var", [t.variableDeclarator(serializedBindingId, serializedValue)]);
      instance.initializationStatements.push(declar);
      residualBinding.serializedValue = serializedBindingId;
    } else {
      // When simpleClosures is not enabled, then space for captured mutable bindings is allocated lazily.
      let scope = this._getSerializedBindingScopeInstance(residualBinding);
      let capturedScope = "__captured" + scope.name;
      // Save the serialized value for initialization at the top of
      // the factory.
      // This can serialize more variables than are necessary to execute
      // the function because every function serializes every
      // modified variable of its parent scope. In some cases it could be
      // an improvement to split these variables into multiple
      // scopes.
      const variableIndexInScope = scope.initializationValues.length;
      (0, _invariant2.default)(residualBinding.serializedValue);
      scope.initializationValues.push(residualBinding.serializedValue);
      scope.capturedScope = capturedScope;

      // Replace binding usage with scope references
      residualBinding.serializedValue = t.memberExpression(t.identifier(capturedScope), t.numericLiteral(variableIndexInScope), true // Array style access.
      );
    }

    this.statistics.referentialized++;
  }

  // Cleans all scopes between passes of the serializer
  cleanInstance(instance) {
    instance.initializationStatements = [];
    for (let b of instance.residualFunctionBindings.values()) {
      let binding = b;
      if (binding.referentialized && binding.declarativeEnvironmentRecord) {
        let declarativeEnvironmentRecord = binding.declarativeEnvironmentRecord;
        let referentializationScope = this._getReferentializationScope(binding);

        let refState = this.referentializationState.get(referentializationScope);
        if (refState) {
          let scope = refState.serializedScopes.get(declarativeEnvironmentRecord);
          if (scope) {
            scope.initializationValues = [];
          }
        }
      }
      delete binding.serializedValue;
    }
  }

  referentialize(instance) {
    let residualBindings = instance.residualFunctionBindings;

    for (let residualBinding of residualBindings.values()) {
      if (residualBinding === undefined) continue;
      if (residualBinding.modified) {
        // Initialize captured scope at function call instead of globally
        if (!residualBinding.declarativeEnvironmentRecord) residualBinding.referentialized = true;
        if (!residualBinding.referentialized) {
          if (!this._options.simpleClosures) this._getSerializedBindingScopeInstance(residualBinding);
          residualBinding.referentialized = true;
        }

        (0, _invariant2.default)(residualBinding.referentialized);
        if (residualBinding.declarativeEnvironmentRecord && residualBinding.scope) {
          instance.scopeInstances.set(residualBinding.scope.name, residualBinding.scope);
        }
      }
    }
  }

  createCapturedScopesArrayInitialization(referentializationScope) {
    return t.variableDeclaration("var", [t.variableDeclarator(this._getReferentializationState(referentializationScope).capturedScopesArray, t.callExpression(t.identifier("Array"), [t.numericLiteral(this._getReferentializationState(referentializationScope).capturedScopeInstanceIdx)]))]);
  }
}
exports.Referentializer = Referentializer; /**
                                            * Copyright (c) 2017-present, Facebook, Inc.
                                            * All rights reserved.
                                            *
                                            * This source code is licensed under the BSD-style license found in the
                                            * LICENSE file in the root directory of this source tree. An additional grant
                                            * of patent rights can be found in the PATENTS file in the same directory.
                                            */
//# sourceMappingURL=Referentializer.js.map