Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  TrialInlining.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 * 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/TrialInlining.h"

#include "mozilla/DebugOnly.h"

#include "jit/BaselineCacheIRCompiler.h"
#include "jit/BaselineFrame.h"
#include "jit/BaselineIC.h"
#include "jit/CacheIRCloner.h"
#include "jit/CacheIRHealth.h"
#include "jit/CacheIRWriter.h"
#include "jit/Ion.h"  // TooManyFormalArguments

#include "vm/BytecodeLocation-inl.h"

using mozilla::Maybe;

namespace js {
namespace jit {

bool DoTrialInlining(JSContext* cx, BaselineFrame* frame) {
  RootedScript script(cx, frame->script());
  ICScript* icScript = frame->icScript();
  bool isRecursive = icScript->depth() > 0;

#ifdef JS_CACHEIR_SPEW
  if (cx->spewer().enabled(cx, script, SpewChannel::CacheIRHealthReport)) {
    for (uint32_t i = 0; i < icScript->numICEntries(); i++) {
      ICEntry& entry = icScript->icEntry(i);
      ICFallbackStub* fallbackStub = icScript->fallbackStub(i);

      // If the IC is megamorphic or generic, then we have already
      // spewed the IC report on transition.
      if (!(uint8_t(fallbackStub->state().mode()) > 0)) {
        jit::ICStub* stub = entry.firstStub();
        bool sawNonZeroCount = false;
        while (!stub->isFallback()) {
          uint32_t count = stub->enteredCount();
          if (count > 0 && sawNonZeroCount) {
            CacheIRHealth cih;
            cih.healthReportForIC(cx, &entry, fallbackStub, script,
                                  SpewContext::TrialInlining);
            break;
          }

          if (count > 0 && !sawNonZeroCount) {
            sawNonZeroCount = true;
          }

          stub = stub->toCacheIRStub()->next();
        }
      }
    }
  }
#endif

  if (!script->canIonCompile()) {
    return true;
  }

  // Baseline shouldn't attempt trial inlining in scripts that are too large.
  MOZ_ASSERT_IF(JitOptions.limitScriptSize,
                script->length() <= JitOptions.ionMaxScriptSize);

  const uint32_t MAX_INLINING_DEPTH = 4;
  if (icScript->depth() > MAX_INLINING_DEPTH) {
    return true;
  }

  InliningRoot* root = isRecursive ? icScript->inliningRoot()
                                   : script->jitScript()->inliningRoot();
  if (JitSpewEnabled(JitSpew_WarpTrialInlining)) {
    // Eagerly create the inlining root when it's used in the spew output.
    if (!root) {
      MOZ_ASSERT(!isRecursive);
      root = script->jitScript()->getOrCreateInliningRoot(cx, script);
      if (!root) {
        return false;
      }
    }
    UniqueChars funName;
    if (script->function() && script->function()->fullDisplayAtom()) {
      funName =
          AtomToPrintableString(cx, script->function()->fullDisplayAtom());
    }

    JitSpew(
        JitSpew_WarpTrialInlining,
        "Trial inlining for %s script '%s' (%s:%u:%u (%p)) (inliningRoot=%p)",
        (isRecursive ? "inner" : "outer"),
        funName ? funName.get() : "", script->filename(),
        script->lineno(), script->column().oneOriginValue(), frame->script(),
        root);
    JitSpewIndent spewIndent(JitSpew_WarpTrialInlining);
  }

  TrialInliner inliner(cx, script, icScript);
  return inliner.tryInlining();
}

void TrialInliner::cloneSharedPrefix(ICCacheIRStub* stub,
                                     const uint8_t* endOfPrefix,
                                     CacheIRWriter& writer) {
  CacheIRReader reader(stub->stubInfo());
  CacheIRCloner cloner(stub);
  while (reader.currentPosition() < endOfPrefix) {
    CacheOp op = reader.readOp();
    cloner.cloneOp(op, reader, writer);
  }
}

bool TrialInliner::replaceICStub(ICEntry& entry, ICFallbackStub* fallback,
                                 CacheIRWriter& writer, CacheKind kind) {
  MOZ_ASSERT(fallback->trialInliningState() == TrialInliningState::Candidate);

  fallback->discardStubs(cx()->zone(), &entry);

  // Note: AttachBaselineCacheIRStub never throws an exception.
  ICAttachResult result = AttachBaselineCacheIRStub(
      cx(), writer, kind, script_, icScript_, fallback, "TrialInline");
  if (result == ICAttachResult::Attached) {
    MOZ_ASSERT(fallback->trialInliningState() == TrialInliningState::Inlined);
    return true;
  }

  MOZ_ASSERT(fallback->trialInliningState() == TrialInliningState::Candidate);
  icScript_->removeInlinedChild(fallback->pcOffset());

  if (result == ICAttachResult::OOM) {
    ReportOutOfMemory(cx());
    return false;
  }

  // We failed to attach a new IC stub due to CacheIR size limits. Disable trial
  // inlining for this location and return true.
  MOZ_ASSERT(result == ICAttachResult::TooLarge);
  fallback->setTrialInliningState(TrialInliningState::Failure);
  return true;
}

ICCacheIRStub* TrialInliner::maybeSingleStub(const ICEntry& entry) {
  // Look for a single non-fallback stub followed by stubs with entered-count 0.
  // Allow one optimized stub before the fallback stub to support the
  // CallIRGenerator::emitCalleeGuard optimization where we first try a
  // GuardSpecificFunction guard before falling back to GuardFunctionHasScript.
  ICStub* stub = entry.firstStub();
  if (stub->isFallback()) {
    return nullptr;
  }
  ICStub* next = stub->toCacheIRStub()->next();
  if (next->enteredCount() != 0) {
    return nullptr;
  }

  ICFallbackStub* fallback = nullptr;
  if (next->isFallback()) {
    fallback = next->toFallbackStub();
  } else {
    ICStub* nextNext = next->toCacheIRStub()->next();
    if (!nextNext->isFallback() || nextNext->enteredCount() != 0) {
      return nullptr;
    }
    fallback = nextNext->toFallbackStub();
  }

  if (fallback->trialInliningState() != TrialInliningState::Candidate) {
    return nullptr;
  }

  return stub->toCacheIRStub();
}

Maybe<InlinableOpData> FindInlinableOpData(ICCacheIRStub* stub,
                                           BytecodeLocation loc) {
  if (loc.isInvokeOp()) {
    Maybe<InlinableCallData> call = FindInlinableCallData(stub);
    if (call.isSome()) {
      return call;
    }
  }
  if (loc.isGetPropOp() || loc.isGetElemOp()) {
    Maybe<InlinableGetterData> getter = FindInlinableGetterData(stub);
    if (getter.isSome()) {
      return getter;
    }
  }
  if (loc.isSetPropOp()) {
    Maybe<InlinableSetterData> setter = FindInlinableSetterData(stub);
    if (setter.isSome()) {
      return setter;
    }
  }
  return mozilla::Nothing();
}

Maybe<InlinableCallData> FindInlinableCallData(ICCacheIRStub* stub) {
  Maybe<InlinableCallData> data;

  const CacheIRStubInfo* stubInfo = stub->stubInfo();
  const uint8_t* stubData = stub->stubDataStart();

  ObjOperandId calleeGuardOperand;
  CallFlags flags;
  JSFunction* target = nullptr;

  CacheIRReader reader(stubInfo);
  while (reader.more()) {
    const uint8_t* opStart = reader.currentPosition();

    CacheOp op = reader.readOp();
    CacheIROpInfo opInfo = CacheIROpInfos[size_t(op)];
    uint32_t argLength = opInfo.argLength;
    mozilla::DebugOnly<const uint8_t*> argStart = reader.currentPosition();

    switch (op) {
      case CacheOp::GuardSpecificFunction: {
        // If we see a guard, remember which operand we are guarding.
        MOZ_ASSERT(data.isNothing());
        calleeGuardOperand = reader.objOperandId();
        uint32_t targetOffset = reader.stubOffset();
        (void)reader.stubOffset();  // nargsAndFlags
        uintptr_t rawTarget = stubInfo->getStubRawWord(stubData, targetOffset);
        target = reinterpret_cast<JSFunction*>(rawTarget);
        break;
      }
      case CacheOp::GuardFunctionScript: {
        MOZ_ASSERT(data.isNothing());
        calleeGuardOperand = reader.objOperandId();
        uint32_t targetOffset = reader.stubOffset();
        uintptr_t rawTarget = stubInfo->getStubRawWord(stubData, targetOffset);
        target = reinterpret_cast<BaseScript*>(rawTarget)->function();
        (void)reader.stubOffset();  // nargsAndFlags
        break;
      }
      case CacheOp::CallScriptedFunction: {
        // If we see a call, check if `callee` is the previously guarded
        // operand. If it is, we know the target and can inline.
        ObjOperandId calleeOperand = reader.objOperandId();
        mozilla::DebugOnly<Int32OperandId> argcId = reader.int32OperandId();
        flags = reader.callFlags();
        mozilla::DebugOnly<uint32_t> argcFixed = reader.uint32Immediate();
        MOZ_ASSERT(argcFixed <= MaxUnrolledArgCopy);

        if (calleeOperand == calleeGuardOperand) {
          MOZ_ASSERT(static_cast<OperandId&>(argcId).id() == 0);
          MOZ_ASSERT(data.isNothing());
          data.emplace();
          data->endOfSharedPrefix = opStart;
        }
        break;
      }
      case CacheOp::CallInlinedFunction: {
        ObjOperandId calleeOperand = reader.objOperandId();
        mozilla::DebugOnly<Int32OperandId> argcId = reader.int32OperandId();
        uint32_t icScriptOffset = reader.stubOffset();
        flags = reader.callFlags();
        mozilla::DebugOnly<uint32_t> argcFixed = reader.uint32Immediate();
        MOZ_ASSERT(argcFixed <= MaxUnrolledArgCopy);

        if (calleeOperand == calleeGuardOperand) {
          MOZ_ASSERT(static_cast<OperandId&>(argcId).id() == 0);
          MOZ_ASSERT(data.isNothing());
          data.emplace();
          data->endOfSharedPrefix = opStart;
          uintptr_t rawICScript =
              stubInfo->getStubRawWord(stubData, icScriptOffset);
          data->icScript = reinterpret_cast<ICScript*>(rawICScript);
        }
        break;
      }
      default:
        if (!opInfo.transpile) {
          return mozilla::Nothing();
        }
        if (data.isSome()) {
          MOZ_ASSERT(op == CacheOp::ReturnFromIC);
        }
        reader.skip(argLength);
        break;
    }
    MOZ_ASSERT(argStart + argLength == reader.currentPosition());
  }

  if (data.isSome()) {
    // Warp only supports inlining Standard and FunCall calls.
    if (flags.getArgFormat() != CallFlags::Standard &&
        flags.getArgFormat() != CallFlags::FunCall) {
      return mozilla::Nothing();
    }
    data->calleeOperand = calleeGuardOperand;
    data->callFlags = flags;
    data->target = target;
  }
  return data;
}

Maybe<InlinableGetterData> FindInlinableGetterData(ICCacheIRStub* stub) {
  Maybe<InlinableGetterData> data;

  const CacheIRStubInfo* stubInfo = stub->stubInfo();
  const uint8_t* stubData = stub->stubDataStart();

  CacheIRReader reader(stubInfo);
  while (reader.more()) {
    const uint8_t* opStart = reader.currentPosition();

    CacheOp op = reader.readOp();
    CacheIROpInfo opInfo = CacheIROpInfos[size_t(op)];
    uint32_t argLength = opInfo.argLength;
    mozilla::DebugOnly<const uint8_t*> argStart = reader.currentPosition();

    switch (op) {
      case CacheOp::CallScriptedGetterResult: {
        data.emplace();
        data->receiverOperand = reader.valOperandId();

        uint32_t getterOffset = reader.stubOffset();
        uintptr_t rawTarget = stubInfo->getStubRawWord(stubData, getterOffset);
        data->target = reinterpret_cast<JSFunction*>(rawTarget);

        data->sameRealm = reader.readBool();
        (void)reader.stubOffset();  // nargsAndFlags

        data->endOfSharedPrefix = opStart;
        break;
      }
      case CacheOp::CallInlinedGetterResult: {
        data.emplace();
        data->receiverOperand = reader.valOperandId();

        uint32_t getterOffset = reader.stubOffset();
        uintptr_t rawTarget = stubInfo->getStubRawWord(stubData, getterOffset);
        data->target = reinterpret_cast<JSFunction*>(rawTarget);

        uint32_t icScriptOffset = reader.stubOffset();
        uintptr_t rawICScript =
            stubInfo->getStubRawWord(stubData, icScriptOffset);
        data->icScript = reinterpret_cast<ICScript*>(rawICScript);

        data->sameRealm = reader.readBool();
        (void)reader.stubOffset();  // nargsAndFlags

        data->endOfSharedPrefix = opStart;
        break;
      }
      default:
        if (!opInfo.transpile) {
          return mozilla::Nothing();
        }
        if (data.isSome()) {
          MOZ_ASSERT(op == CacheOp::ReturnFromIC);
        }
        reader.skip(argLength);
        break;
    }
    MOZ_ASSERT(argStart + argLength == reader.currentPosition());
  }

  return data;
}

Maybe<InlinableSetterData> FindInlinableSetterData(ICCacheIRStub* stub) {
  Maybe<InlinableSetterData> data;

  const CacheIRStubInfo* stubInfo = stub->stubInfo();
  const uint8_t* stubData = stub->stubDataStart();

  CacheIRReader reader(stubInfo);
  while (reader.more()) {
    const uint8_t* opStart = reader.currentPosition();

    CacheOp op = reader.readOp();
    CacheIROpInfo opInfo = CacheIROpInfos[size_t(op)];
    uint32_t argLength = opInfo.argLength;
    mozilla::DebugOnly<const uint8_t*> argStart = reader.currentPosition();

    switch (op) {
      case CacheOp::CallScriptedSetter: {
        data.emplace();
        data->receiverOperand = reader.objOperandId();

        uint32_t setterOffset = reader.stubOffset();
        uintptr_t rawTarget = stubInfo->getStubRawWord(stubData, setterOffset);
        data->target = reinterpret_cast<JSFunction*>(rawTarget);

        data->rhsOperand = reader.valOperandId();
        data->sameRealm = reader.readBool();
        (void)reader.stubOffset();  // nargsAndFlags

        data->endOfSharedPrefix = opStart;
        break;
      }
      case CacheOp::CallInlinedSetter: {
        data.emplace();
        data->receiverOperand = reader.objOperandId();

        uint32_t setterOffset = reader.stubOffset();
        uintptr_t rawTarget = stubInfo->getStubRawWord(stubData, setterOffset);
        data->target = reinterpret_cast<JSFunction*>(rawTarget);

        data->rhsOperand = reader.valOperandId();

        uint32_t icScriptOffset = reader.stubOffset();
        uintptr_t rawICScript =
            stubInfo->getStubRawWord(stubData, icScriptOffset);
        data->icScript = reinterpret_cast<ICScript*>(rawICScript);

        data->sameRealm = reader.readBool();
        (void)reader.stubOffset();  // nargsAndFlags

        data->endOfSharedPrefix = opStart;
        break;
      }
      default:
        if (!opInfo.transpile) {
          return mozilla::Nothing();
        }
        if (data.isSome()) {
          MOZ_ASSERT(op == CacheOp::ReturnFromIC);
        }
        reader.skip(argLength);
        break;
    }
    MOZ_ASSERT(argStart + argLength == reader.currentPosition());
  }

  return data;
}

// Return the maximum number of actual arguments that will be passed to the
// target function. This may be an overapproximation, for example when inlining
// js::fun_call we may omit an argument.
static uint32_t GetMaxCalleeNumActuals(BytecodeLocation loc) {
  switch (loc.getOp()) {
    case JSOp::GetProp:
    case JSOp::GetElem:
      // Getters do not pass arguments.
      return 0;

    case JSOp::SetProp:
    case JSOp::StrictSetProp:
      // Setters pass 1 argument.
      return 1;

    case JSOp::Call:
    case JSOp::CallContent:
    case JSOp::CallIgnoresRv:
    case JSOp::CallIter:
    case JSOp::CallContentIter:
    case JSOp::New:
    case JSOp::NewContent:
    case JSOp::SuperCall:
      return loc.getCallArgc();

    default:
      MOZ_CRASH("Unsupported op");
  }
}

/*static*/
bool TrialInliner::IsValidInliningOp(JSOp op) {
  switch (op) {
    case JSOp::GetProp:
    case JSOp::GetElem:
    case JSOp::SetProp:
    case JSOp::StrictSetProp:
    case JSOp::Call:
    case JSOp::CallContent:
    case JSOp::CallIgnoresRv:
    case JSOp::CallIter:
    case JSOp::CallContentIter:
    case JSOp::New:
    case JSOp::NewContent:
    case JSOp::SuperCall:
      return true;
    default:
      break;
  }
  return false;
}

/*static*/
bool TrialInliner::canInline(JSFunction* target, HandleScript caller,
                             BytecodeLocation loc) {
  if (!target->hasJitScript()) {
    JitSpew(JitSpew_WarpTrialInlining, "SKIP: no JIT script");
    return false;
  }
  JSScript* script = target->nonLazyScript();
  if (!script->jitScript()->hasBaselineScript()) {
    JitSpew(JitSpew_WarpTrialInlining, "SKIP: no BaselineScript");
    return false;
  }
  if (script->uninlineable()) {
    JitSpew(JitSpew_WarpTrialInlining, "SKIP: uninlineable flag");
    return false;
  }
  if (!script->canIonCompile()) {
    JitSpew(JitSpew_WarpTrialInlining, "SKIP: can't ion-compile");
    return false;
  }
  if (script->isDebuggee()) {
    JitSpew(JitSpew_WarpTrialInlining, "SKIP: is debuggee");
    return false;
  }
  // Don't inline cross-realm calls.
  if (target->realm() != caller->realm()) {
    JitSpew(JitSpew_WarpTrialInlining, "SKIP: cross-realm call");
    return false;
  }
  if (JitOptions.onlyInlineSelfHosted && !script->selfHosted()) {
    JitSpew(JitSpew_WarpTrialInlining, "SKIP: only inlining self hosted");
    return false;
  }
  if (!IsValidInliningOp(loc.getOp())) {
    JitSpew(JitSpew_WarpTrialInlining, "SKIP: non inlineable op");
    return false;
  }

  uint32_t maxCalleeNumActuals = GetMaxCalleeNumActuals(loc);
  if (maxCalleeNumActuals > ArgumentsObject::MaxInlinedArgs) {
    if (script->needsArgsObj()) {
      JitSpew(JitSpew_WarpTrialInlining,
              "SKIP: needs arguments object with %u actual args (maximum %u)",
              maxCalleeNumActuals, ArgumentsObject::MaxInlinedArgs);
      return false;
    }
    // The GetArgument(n) intrinsic in self-hosted code uses MGetInlinedArgument
    // too, so the same limit applies.
    if (script->usesArgumentsIntrinsics()) {
      JitSpew(JitSpew_WarpTrialInlining,
              "SKIP: uses GetArgument(i) with %u actual args (maximum %u)",
              maxCalleeNumActuals, ArgumentsObject::MaxInlinedArgs);
      return false;
    }
  }

  if (TooManyFormalArguments(target->nargs())) {
    JitSpew(JitSpew_WarpTrialInlining, "SKIP: Too many formal arguments: %u",
            unsigned(target->nargs()));
    return false;
  }

  if (TooManyFormalArguments(maxCalleeNumActuals)) {
    JitSpew(JitSpew_WarpTrialInlining, "SKIP: argc too large: %u",
            unsigned(loc.getCallArgc()));
    return false;
  }

  return true;
}

static bool ShouldUseMonomorphicInlining(JSScript* targetScript) {
  switch (JitOptions.monomorphicInlining) {
    case UseMonomorphicInlining::Default:
      // Use heuristics below.
      break;
    case UseMonomorphicInlining::Always:
      return true;
    case UseMonomorphicInlining::Never:
      return false;
  }

  JitScript* jitScript = targetScript->jitScript();
  ICScript* icScript = jitScript->icScript();

  // Check for any ICs which are not monomorphic. The observation here is that
  // trial inlining can help us a lot in cases where it lets us further
  // specialize a script. But if it's already monomorphic, it's unlikely that
  // we will see significant specialization wins from trial inlining, so we
  // can use a cheaper and simpler inlining strategy.
  for (size_t i = 0; i < icScript->numICEntries(); i++) {
    ICEntry& entry = icScript->icEntry(i);
    ICFallbackStub* fallback = icScript->fallbackStub(i);
    if (fallback->enteredCount() > 0 ||
        fallback->state().mode() != ICState::Mode::Specialized) {
      return false;
    }

    ICStub* firstStub = entry.firstStub();
    if (firstStub != fallback) {
      for (ICStub* next = firstStub->toCacheIRStub()->next(); next;
           next = next->maybeNext()) {
        if (next->enteredCount() != 0) {
          return false;
        }
      }
    }
  }

  return true;
}

TrialInliningDecision TrialInliner::getInliningDecision(JSFunction* target,
                                                        ICCacheIRStub* stub,
                                                        BytecodeLocation loc) {
#ifdef JS_JITSPEW
  if (JitSpewEnabled(JitSpew_WarpTrialInlining)) {
    BaseScript* baseScript =
        target->hasBaseScript() ? target->baseScript() : nullptr;

    UniqueChars funName;
    if (target->maybePartialDisplayAtom()) {
      funName = AtomToPrintableString(cx(), target->maybePartialDisplayAtom());
    }

    JitSpew(JitSpew_WarpTrialInlining,
            "Inlining candidate JSOp::%s (offset=%u): callee script '%s' "
            "(%s:%u:%u)",
            CodeName(loc.getOp()), loc.bytecodeToOffset(script_),
            funName ? funName.get() : "",
            baseScript ? baseScript->filename() : "",
            baseScript ? baseScript->lineno() : 0,
            baseScript ? baseScript->column().oneOriginValue() : 0);
    JitSpewIndent spewIndent(JitSpew_WarpTrialInlining);
  }
#endif

  if (!canInline(target, script_, loc)) {
    return TrialInliningDecision::NoInline;
  }

  // Don't inline (direct) recursive calls. This still allows recursion if
  // called through another function (f => g => f).
  JSScript* targetScript = target->nonLazyScript();
  if (script_ == targetScript) {
    JitSpew(JitSpew_WarpTrialInlining, "SKIP: recursion");
    return TrialInliningDecision::NoInline;
  }

  // Don't inline if the callee has a loop that was hot enough to enter Warp
  // via OSR. This helps prevent getting stuck in Baseline code for a long time.
  if (targetScript->jitScript()->hadIonOSR()) {
    JitSpew(JitSpew_WarpTrialInlining, "SKIP: had OSR");
    return TrialInliningDecision::NoInline;
  }

  // Ensure the total bytecode size does not exceed ionMaxScriptSize.
  size_t newTotalSize =
      inliningRootTotalBytecodeSize() + targetScript->length();
  if (newTotalSize > JitOptions.ionMaxScriptSize) {
    JitSpew(JitSpew_WarpTrialInlining, "SKIP: total size too big");
    return TrialInliningDecision::NoInline;
  }

  uint32_t entryCount = stub->enteredCount();
  if (entryCount < JitOptions.inliningEntryThreshold) {
    JitSpew(JitSpew_WarpTrialInlining, "SKIP: Entry count is %u (minimum %u)",
            unsigned(entryCount), unsigned(JitOptions.inliningEntryThreshold));
    return TrialInliningDecision::NoInline;
  }

  if (!JitOptions.isSmallFunction(targetScript)) {
    if (!targetScript->isInlinableLargeFunction()) {
      JitSpew(JitSpew_WarpTrialInlining, "SKIP: Length is %u (maximum %u)",
              unsigned(targetScript->length()),
              unsigned(JitOptions.smallFunctionMaxBytecodeLength));
      return TrialInliningDecision::NoInline;
    }

    JitSpew(JitSpew_WarpTrialInlining,
            "INFO: Ignored length (%u) of InlinableLargeFunction",
            unsigned(targetScript->length()));
  }

  // Decide between trial inlining or monomorphic inlining.
  if (!ShouldUseMonomorphicInlining(targetScript)) {
    return TrialInliningDecision::Inline;
  }

  JitSpewIndent spewIndent(JitSpew_WarpTrialInlining);
  JitSpew(JitSpew_WarpTrialInlining, "SUCCESS: Inlined monomorphically");
  return TrialInliningDecision::MonomorphicInline;
}

ICScript* TrialInliner::createInlinedICScript(JSFunction* target,
                                              BytecodeLocation loc) {
  MOZ_ASSERT(target->hasJitEntry());
  MOZ_ASSERT(target->hasJitScript());

  InliningRoot* root = getOrCreateInliningRoot();
  if (!root) {
    return nullptr;
  }

  JSScript* targetScript = target->baseScript()->asJSScript();

  // We don't have to check for overflow here because we have already
  // successfully allocated an ICScript with this number of entries
  // when creating the JitScript for the target function, and we
  // checked for overflow then.
  uint32_t fallbackStubsOffset =
      sizeof(ICScript) + targetScript->numICEntries() * sizeof(ICEntry);
  uint32_t allocSize = fallbackStubsOffset +
                       targetScript->numICEntries() * sizeof(ICFallbackStub);

  void* raw = cx()->pod_malloc<uint8_t>(allocSize);
  MOZ_ASSERT(uintptr_t(raw) % alignof(ICScript) == 0);
  if (!raw) {
    return nullptr;
  }

  uint32_t initialWarmUpCount = JitOptions.trialInliningInitialWarmUpCount;

  uint32_t depth = icScript_->depth() + 1;
  UniquePtr<ICScript> inlinedICScript(
      new (raw) ICScript(initialWarmUpCount, fallbackStubsOffset, allocSize,
                         depth, targetScript->length(), root));

  inlinedICScript->initICEntries(cx(), targetScript);

  uint32_t pcOffset = loc.bytecodeToOffset(script_);
  ICScript* result = inlinedICScript.get();
  if (!icScript_->addInlinedChild(cx(), std::move(inlinedICScript), pcOffset)) {
    return nullptr;
  }
  MOZ_ASSERT(result->numICEntries() == targetScript->numICEntries());

  root->addToTotalBytecodeSize(targetScript->length());

  JitSpewIndent spewIndent(JitSpew_WarpTrialInlining);
  JitSpew(JitSpew_WarpTrialInlining,
          "SUCCESS: Outer ICScript: %p Inner ICScript: %p", icScript_, result);

  return result;
}

bool TrialInliner::maybeInlineCall(ICEntry& entry, ICFallbackStub* fallback,
                                   BytecodeLocation loc) {
  ICCacheIRStub* stub = maybeSingleStub(entry);
  if (!stub) {
#ifdef JS_JITSPEW
    if (fallback->numOptimizedStubs() > 1) {
      JitSpew(JitSpew_WarpTrialInlining,
              "Inlining candidate JSOp::%s (offset=%u):", CodeName(loc.getOp()),
              fallback->pcOffset());
      JitSpewIndent spewIndent(JitSpew_WarpTrialInlining);
      JitSpew(JitSpew_WarpTrialInlining, "SKIP: Polymorphic (%u stubs)",
              (unsigned)fallback->numOptimizedStubs());
    }
#endif
    return true;
  }

  MOZ_ASSERT(!icScript_->hasInlinedChild(fallback->pcOffset()));

  // Look for a CallScriptedFunction with a known target.
  Maybe<InlinableCallData> data = FindInlinableCallData(stub);
  if (data.isNothing()) {
    return true;
  }

  MOZ_ASSERT(!data->icScript);

  TrialInliningDecision inlining = getInliningDecision(data->target, stub, loc);
  // Decide whether to inline the target.
  if (inlining == TrialInliningDecision::NoInline) {
    return true;
  }

  if (inlining == TrialInliningDecision::MonomorphicInline) {
    fallback->setTrialInliningState(TrialInliningState::MonomorphicInlined);
    return true;
  }

  ICScript* newICScript = createInlinedICScript(data->target, loc);
  if (!newICScript) {
    return false;
  }

  CacheIRWriter writer(cx());
  Int32OperandId argcId(writer.setInputOperandId(0));
  cloneSharedPrefix(stub, data->endOfSharedPrefix, writer);

  writer.callInlinedFunction(data->calleeOperand, argcId, newICScript,
                             data->callFlags,
                             ClampFixedArgc(loc.getCallArgc()));
  writer.returnFromIC();

  return replaceICStub(entry, fallback, writer, CacheKind::Call);
}

bool TrialInliner::maybeInlineGetter(ICEntry& entry, ICFallbackStub* fallback,
                                     BytecodeLocation loc, CacheKind kind) {
  ICCacheIRStub* stub = maybeSingleStub(entry);
  if (!stub) {
    return true;
  }

  MOZ_ASSERT(!icScript_->hasInlinedChild(fallback->pcOffset()));

  Maybe<InlinableGetterData> data = FindInlinableGetterData(stub);
  if (data.isNothing()) {
    return true;
  }

  MOZ_ASSERT(!data->icScript);

  TrialInliningDecision inlining = getInliningDecision(data->target, stub, loc);
  // Decide whether to inline the target.
  if (inlining == TrialInliningDecision::NoInline) {
    return true;
  }

  if (inlining == TrialInliningDecision::MonomorphicInline) {
    fallback->setTrialInliningState(TrialInliningState::MonomorphicInlined);
    return true;
  }

  ICScript* newICScript = createInlinedICScript(data->target, loc);
  if (!newICScript) {
    return false;
  }

  CacheIRWriter writer(cx());
  ValOperandId valId(writer.setInputOperandId(0));
  if (kind == CacheKind::GetElem) {
    // Register the key operand.
    writer.setInputOperandId(1);
  }
  cloneSharedPrefix(stub, data->endOfSharedPrefix, writer);

  writer.callInlinedGetterResult(data->receiverOperand, data->target,
                                 newICScript, data->sameRealm);
  writer.returnFromIC();

  return replaceICStub(entry, fallback, writer, kind);
}

bool TrialInliner::maybeInlineSetter(ICEntry& entry, ICFallbackStub* fallback,
                                     BytecodeLocation loc, CacheKind kind) {
  ICCacheIRStub* stub = maybeSingleStub(entry);
  if (!stub) {
    return true;
  }

  MOZ_ASSERT(!icScript_->hasInlinedChild(fallback->pcOffset()));

  Maybe<InlinableSetterData> data = FindInlinableSetterData(stub);
  if (data.isNothing()) {
    return true;
  }

  MOZ_ASSERT(!data->icScript);

  TrialInliningDecision inlining = getInliningDecision(data->target, stub, loc);
  // Decide whether to inline the target.
  if (inlining == TrialInliningDecision::NoInline) {
    return true;
  }

  if (inlining == TrialInliningDecision::MonomorphicInline) {
    fallback->setTrialInliningState(TrialInliningState::MonomorphicInlined);
    return true;
  }

  ICScript* newICScript = createInlinedICScript(data->target, loc);
  if (!newICScript) {
    return false;
  }

  CacheIRWriter writer(cx());
  ValOperandId objValId(writer.setInputOperandId(0));
  ValOperandId rhsValId(writer.setInputOperandId(1));
  cloneSharedPrefix(stub, data->endOfSharedPrefix, writer);

  writer.callInlinedSetter(data->receiverOperand, data->target,
                           data->rhsOperand, newICScript, data->sameRealm);
  writer.returnFromIC();

  return replaceICStub(entry, fallback, writer, kind);
}

bool TrialInliner::tryInlining() {
  uint32_t numICEntries = icScript_->numICEntries();
  BytecodeLocation startLoc = script_->location();

  for (uint32_t icIndex = 0; icIndex < numICEntries; icIndex++) {
    ICEntry& entry = icScript_->icEntry(icIndex);
    ICFallbackStub* fallback = icScript_->fallbackStub(icIndex);

    if (!TryFoldingStubs(cx(), fallback, script_, icScript_)) {
      return false;
    }

    BytecodeLocation loc =
        startLoc + BytecodeLocationOffset(fallback->pcOffset());
    JSOp op = loc.getOp();
    switch (op) {
      case JSOp::Call:
      case JSOp::CallContent:
      case JSOp::CallIgnoresRv:
      case JSOp::CallIter:
      case JSOp::CallContentIter:
      case JSOp::New:
      case JSOp::NewContent:
      case JSOp::SuperCall:
        if (!maybeInlineCall(entry, fallback, loc)) {
          return false;
        }
        break;
      case JSOp::GetProp:
        if (!maybeInlineGetter(entry, fallback, loc, CacheKind::GetProp)) {
          return false;
        }
        break;
      case JSOp::GetElem:
        if (!maybeInlineGetter(entry, fallback, loc, CacheKind::GetElem)) {
          return false;
        }
        break;
      case JSOp::SetProp:
      case JSOp::StrictSetProp:
        if (!maybeInlineSetter(entry, fallback, loc, CacheKind::SetProp)) {
          return false;
        }
        break;
      default:
        break;
    }
  }

  return true;
}

InliningRoot* TrialInliner::maybeGetInliningRoot() const {
  if (auto* root = icScript_->inliningRoot()) {
    return root;
  }

  MOZ_ASSERT(!icScript_->isInlined());
  return script_->jitScript()->inliningRoot();
}

InliningRoot* TrialInliner::getOrCreateInliningRoot() {
  if (auto* root = maybeGetInliningRoot()) {
    return root;
  }
  return script_->jitScript()->getOrCreateInliningRoot(cx(), script_);
}

size_t TrialInliner::inliningRootTotalBytecodeSize() const {
  if (auto* root = maybeGetInliningRoot()) {
    return root->totalBytecodeSize();
  }
  return script_->length();
}

bool InliningRoot::addInlinedScript(UniquePtr<ICScript> icScript) {
  return inlinedScripts_.append(std::move(icScript));
}

void InliningRoot::trace(JSTracer* trc) {
  TraceEdge(trc, &owningScript_, "inlining-root-owning-script");
  for (auto& inlinedScript : inlinedScripts_) {
    inlinedScript->trace(trc);
  }
}

bool InliningRoot::traceWeak(JSTracer* trc) {
  bool allSurvived = true;
  for (auto& inlinedScript : inlinedScripts_) {
    if (!inlinedScript->traceWeak(trc)) {
      allSurvived = false;
    }
  }
  return allSurvived;
}

void InliningRoot::purgeInactiveICScripts() {
  mozilla::DebugOnly<uint32_t> totalSize = owningScript_->length();

  for (auto& inlinedScript : inlinedScripts_) {
    if (inlinedScript->active()) {
      totalSize += inlinedScript->bytecodeSize();
    } else {
      MOZ_ASSERT(inlinedScript->bytecodeSize() < totalBytecodeSize_);
      totalBytecodeSize_ -= inlinedScript->bytecodeSize();
    }
  }

  MOZ_ASSERT(totalBytecodeSize_ == totalSize);

  Zone* zone = owningScript_->zone();

  inlinedScripts_.eraseIf([zone](auto& inlinedScript) {
    if (inlinedScript->active()) {
      return false;
    }
    inlinedScript->prepareForDestruction(zone);
    return true;
  });
}

}  // namespace jit
}  // namespace js

Messung V0.5
C=95 H=95 G=94

¤ Dauer der Verarbeitung: 0.18 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge