/* 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/. */

#include "jit/JitHints-inl.h"

#include "gc/Pretenuring.h"
#include "jit/BaselineIC.h"
#include "jit/JitScript.h"

#include "vm/BytecodeLocation-inl.h"
#include "vm/JSScript-inl.h"

using namespace js;
using namespace js::jit;

JitHintsMap::~JitHintsMap() {
  while (!ionHintQueue_.isEmpty()) {
    IonHint* e = ionHintQueue_.popFirst();
    js_delete(e);
  }
  ionHintMap_.clear();
}

JitHintsMap::IonHint* JitHintsMap::addIonHint(ScriptKey key,
                                              ScriptToHintMap::AddPtr& p) {
  UniquePtr<IonHint> hint = MakeUnique<IonHint>(key);
  if (!hint) {
    return nullptr;
  }

  if (!ionHintMap_.add(p, key, hint.get())) {
    return nullptr;
  }

  ionHintQueue_.insertBack(hint.get());

  if (ionHintMap_.count() > IonHintMaxEntries) {
    IonHint* h = ionHintQueue_.popFirst();
    ionHintMap_.remove(h->key());
    js_delete(h);
  }

  return hint.release();
}

void JitHintsMap::updateAsRecentlyUsed(IonHint* hint) {
  hint->remove();
  ionHintQueue_.insertBack(hint);
}

bool JitHintsMap::recordIonCompilation(JSScript* script) {
  ScriptKey key = getScriptKey(script);
  if (!key) {
    return true;
  }

  // Only add hints for scripts that will be eager baseline compiled.
  if (!baselineHintMap_.mightContain(key)) {
    return true;
  }

  auto p = ionHintMap_.lookupForAdd(key);
  IonHint* hint = nullptr;
  if (p) {
    hint = p->value();
    updateAsRecentlyUsed(hint);
  } else {
    hint = addIonHint(key, p);
    if (!hint) {
      return false;
    }
  }

  uint32_t threshold = IonHintEagerThresholdValue(
      script->warmUpCountAtLastICStub(),
      script->jitScript()->hasPretenuredAllocSites());

  // Megamorphic hints cause ICs to transition sooner, lowering
  // warmUpCountAtLastICStub. Allow the threshold to decrease in that case.
  if (threshold > hint->threshold() || hint->hasICModeHints()) {
    hint->setThreshold(threshold);
    script->jitScript()->setIonThreshold(threshold);
  }

  uint32_t numEntries = script->jitScript()->numICEntries();

  // Record Megamorphic IC hints.
  if (!hint->hasICModeHints()) {
    uint32_t numToRecord = std::min(numEntries, ICModeHintMaxEntries);
    for (uint32_t i = 0; i < numToRecord; i++) {
      if (script->jitScript()->fallbackStub(i)->state().mode() ==
          ICState::Mode::Megamorphic) {
        hint->setMegamorphicHint(i);
      }
    }
    hint->numICModeHints_ = numToRecord;
  }
  return true;
}

// static
uint32_t JitHintsMap::IonHintEagerThresholdValue(uint32_t lastStubCounter,
                                                 bool hasPretenuredAllocSites) {
  // Use the counter when the last IC stub was attached as the initial
  // threshold.
  uint32_t eagerThreshold = lastStubCounter;

  // Ensure we stay in baseline long enough to pretenure alloc sites.
  if (hasPretenuredAllocSites) {
    eagerThreshold =
        std::max(eagerThreshold, uint32_t(gc::NormalSiteAttentionThreshold));
  }

  // Add 10 for some wiggle room and to safeguard against cases where the
  // lastStubCounter is 0.
  eagerThreshold += 10;

  // Do not exceed the default Ion threshold value set in the options.
  return std::min(eagerThreshold, JitOptions.normalIonWarmUpThreshold);
}

bool JitHintsMap::getIonThresholdHint(JSScript* script,
                                      uint32_t& thresholdOut) {
  ScriptKey key = getScriptKey(script);
  if (key) {
    auto p = ionHintMap_.lookup(key);
    if (p) {
      IonHint* hint = p->value();
      // If the threshold is 0, the hint only contains
      // monomorphic inlining location information and
      // may not have entered Ion before.
      if (hint->threshold() != 0) {
        updateAsRecentlyUsed(hint);
        thresholdOut = hint->threshold();
        return true;
      }
    }
  }
  return false;
}

void JitHintsMap::recordInvalidation(JSScript* script) {
  ScriptKey key = getScriptKey(script);
  if (key) {
    auto p = ionHintMap_.lookup(key);
    if (p) {
      IonHint* hint = p->value();
      hint->incThreshold(InvalidationThresholdIncrement);
      hint->resetICHints();
      if (script->hasJitScript()) {
        script->jitScript()->setIonThreshold(hint->threshold());
      }
    }
  }
}

bool JitHintsMap::addMonomorphicInlineLocation(JSScript* script,
                                               BytecodeLocation loc) {
  ScriptKey key = getScriptKey(script);
  if (!key) {
    return true;
  }

  // Only add inline hints for scripts that will be eager baseline compiled.
  if (!baselineHintMap_.mightContain(key)) {
    return true;
  }

  auto p = ionHintMap_.lookupForAdd(key);
  IonHint* hint = nullptr;
  if (p) {
    hint = p->value();
  } else {
    hint = addIonHint(key, p);
    if (!hint) {
      return false;
    }
  }

  if (!hint->hasSpaceForMonomorphicInlineEntry()) {
    return true;
  }

  uint32_t offset = loc.bytecodeToOffset(script);
  return hint->addMonomorphicInlineOffset(offset);
}

bool JitHintsMap::hasMonomorphicInlineHintAtOffset(JSScript* script,
                                                   uint32_t offset) {
  ScriptKey key = getScriptKey(script);
  if (!key) {
    return false;
  }

  auto p = ionHintMap_.lookup(key);
  if (p) {
    return p->value()->hasMonomorphicInlineOffset(offset);
  }

  return false;
}

bool JitHintsMap::shouldTransitionMegamorphic(JSScript* script,
                                              ICScript* icScript,
                                              ICFallbackStub* stub) {
  ScriptKey key = getScriptKey(script);
  if (!key) {
    return false;
  }

  auto p = ionHintMap_.lookup(key);
  if (!p) {
    return false;
  }

  IonHint* hint = p->value();
  if (!hint->hasICModeHints()) {
    return false;
  }

  ICEntry* icEntry = icScript->icEntryForStub(stub);
  uint32_t index = icEntry - icScript->icEntries();

  if (index >= hint->numICModeHints()) {
    return false;
  }

  if (!hint->isMegamorphicHint(index)) {
    return false;
  }
  if (stub->state().mode() >= ICState::Mode::Megamorphic) {
    return false;
  }

  return true;
}
