| //------------------------------------------------------------------------------------------------------- |
| // Copyright (C) Microsoft. All rights reserved. |
| // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. |
| //------------------------------------------------------------------------------------------------------- |
| #include "RuntimeBasePch.h" |
| #include "FunctionExecutionStateMachine.h" |
| #include "Warnings.h" |
| |
| namespace Js |
| { |
| FunctionExecutionStateMachine::FunctionExecutionStateMachine() : |
| owner(nullptr), |
| executionState(ExecutionState::Interpreter), |
| interpreterLimit(0), |
| autoProfilingInterpreter0Limit(0), |
| profilingInterpreter0Limit(0), |
| autoProfilingInterpreter1Limit(0), |
| simpleJitLimit(0), |
| profilingInterpreter1Limit(0), |
| interpretedCount(0), |
| fullJitThreshold(0), |
| fullJitRequeueThreshold(0), |
| committedProfiledIterations(0), |
| lastInterpretedCount(0) |
| #if DBG |
| ,initializedExecutionModeAndLimits(false) |
| ,hasBeenReinitialized(false) |
| #ifdef ENABLE_SCRIPT_DEBUGGING |
| ,initDebuggerMode(DebuggerMode::NotDebugging) |
| ,reinitDebuggerMode(DebuggerMode::NotDebugging) |
| #endif |
| #endif |
| { |
| } |
| |
| uint16 FunctionExecutionStateMachine::GetDefaultAutoProfilingInterpreter0Limit(bool isCoroutine) const { |
| return isCoroutine ? 0 : static_cast<uint16>(Configuration::Global.flags.AutoProfilingInterpreter0Limit); |
| } |
| |
| uint16 FunctionExecutionStateMachine::GetDefaultProfilingInterpreter0Limit(bool isCoroutine) const { |
| if (isCoroutine) |
| { |
| return static_cast<uint16>(Configuration::Global.flags.AutoProfilingInterpreter0Limit) + |
| static_cast<uint16>(Configuration::Global.flags.AutoProfilingInterpreter1Limit); |
| } |
| |
| return static_cast<uint16>(Configuration::Global.flags.ProfilingInterpreter0Limit); |
| } |
| |
| uint16 FunctionExecutionStateMachine::GetDefaultAutoProfilingInterpreter1Limit(bool isCoroutine) const { |
| return isCoroutine ? 0 : static_cast<uint16>(Configuration::Global.flags.AutoProfilingInterpreter1Limit); |
| } |
| |
| uint16 FunctionExecutionStateMachine::GetDefaultSimpleJitLimit(bool isCoroutine) const { |
| return static_cast<uint16>(Configuration::Global.flags.SimpleJitLimit); |
| } |
| |
| uint16 FunctionExecutionStateMachine::GetDefaultProfilingInterpreter1Limit(bool isCoroutine) const { |
| return static_cast<uint16>(Configuration::Global.flags.ProfilingInterpreter1Limit); |
| } |
| |
| void FunctionExecutionStateMachine::InitializeExecutionModeAndLimits(FunctionBody* functionBody) |
| { |
| #if DBG |
| #ifdef ENABLE_SCRIPT_DEBUGGING |
| if (!initializedExecutionModeAndLimits) |
| { |
| initDebuggerMode = functionBody->GetDebuggerMode(); |
| } |
| #endif |
| initializedExecutionModeAndLimits = true; |
| #endif |
| // Assert we're either uninitialized, or being reinitialized on the same FunctionBody |
| Assert(owner == nullptr || owner == functionBody); |
| owner = functionBody; |
| |
| // AutoProfilingInterpreter might decide to not profile on the first run. For generator |
| // functions, that means we will miss the profiling information on the first run when we resume |
| // back to the function. |
| const bool isCoroutine = functionBody->SkipAutoProfileForCoroutine(); |
| |
| interpreterLimit = 0; |
| autoProfilingInterpreter0Limit = GetDefaultAutoProfilingInterpreter0Limit(isCoroutine); |
| profilingInterpreter0Limit = GetDefaultProfilingInterpreter0Limit(isCoroutine); |
| autoProfilingInterpreter1Limit = GetDefaultAutoProfilingInterpreter1Limit(isCoroutine); |
| simpleJitLimit = GetDefaultSimpleJitLimit(isCoroutine); |
| profilingInterpreter1Limit = GetDefaultProfilingInterpreter1Limit(isCoroutine); |
| |
| // Based on which execution modes are disabled, calculate the number of additional iterations that need to be covered by |
| // the execution mode that will scale with the full JIT threshold |
| uint16 scale = 0; |
| const bool doInterpreterProfile = owner->DoInterpreterProfile(); |
| if (!doInterpreterProfile) |
| { |
| scale += |
| autoProfilingInterpreter0Limit + |
| profilingInterpreter0Limit + |
| autoProfilingInterpreter1Limit + |
| profilingInterpreter1Limit; |
| autoProfilingInterpreter0Limit = 0; |
| profilingInterpreter0Limit = 0; |
| autoProfilingInterpreter1Limit = 0; |
| profilingInterpreter1Limit = 0; |
| } |
| else if (!owner->DoInterpreterAutoProfile()) |
| { |
| scale += autoProfilingInterpreter0Limit + autoProfilingInterpreter1Limit; |
| autoProfilingInterpreter0Limit = 0; |
| autoProfilingInterpreter1Limit = 0; |
| if (!CONFIG_FLAG(NewSimpleJit)) |
| { |
| simpleJitLimit += profilingInterpreter0Limit; |
| profilingInterpreter0Limit = 0; |
| } |
| } |
| if (!owner->DoSimpleJit()) |
| { |
| if (!CONFIG_FLAG(NewSimpleJit) && doInterpreterProfile) |
| { |
| // The old simple JIT is off, but since it does profiling, it will be replaced with the profiling interpreter |
| profilingInterpreter1Limit += simpleJitLimit; |
| } |
| else |
| { |
| scale += simpleJitLimit; |
| } |
| simpleJitLimit = 0; |
| } |
| if (PHASE_OFF(FullJitPhase, owner)) |
| { |
| scale += profilingInterpreter1Limit; |
| profilingInterpreter1Limit = 0; |
| } |
| |
| uint16 fullJitThresholdConfig = GetDefaultFullJitThreshold(isCoroutine); |
| if (!Configuration::Global.flags.EnforceExecutionModeLimits) |
| { |
| /* |
| Scale the full JIT threshold based on some heuristics: |
| - If the % of code in loops is > 50, scale by 1 |
| - Byte-code size of code outside loops |
| - If the size is < 50, scale by 1.2 |
| - If the size is < 100, scale by 1.4 |
| - If the size is >= 100, scale by 1.6 |
| */ |
| const uint loopPercentage = owner->GetByteCodeInLoopCount() * 100 / max(1u, owner->GetByteCodeCount()); |
| const int byteCodeSizeThresholdForInlineCandidate = CONFIG_FLAG(LoopInlineThreshold); |
| bool delayFullJITThisFunc = |
| (CONFIG_FLAG(DelayFullJITSmallFunc) > 0) && (owner->GetByteCodeWithoutLDACount() <= (uint)byteCodeSizeThresholdForInlineCandidate); |
| |
| if (loopPercentage <= 50 || delayFullJITThisFunc) |
| { |
| const uint straightLineSize = owner->GetByteCodeCount() - owner->GetByteCodeInLoopCount(); |
| double fullJitDelayMultiplier; |
| if (delayFullJITThisFunc) |
| { |
| fullJitDelayMultiplier = CONFIG_FLAG(DelayFullJITSmallFunc) / 10.0; |
| } |
| else if (straightLineSize < 50) |
| { |
| fullJitDelayMultiplier = 1.2; |
| } |
| else if (straightLineSize < 100) |
| { |
| fullJitDelayMultiplier = 1.4; |
| } |
| else |
| { |
| fullJitDelayMultiplier = 1.6; |
| } |
| |
| const uint16 newFullJitThreshold = static_cast<uint16>(fullJitThresholdConfig * fullJitDelayMultiplier); |
| scale += newFullJitThreshold - fullJitThresholdConfig; |
| fullJitThresholdConfig = newFullJitThreshold; |
| } |
| } |
| |
| Assert(fullJitThresholdConfig >= scale); |
| fullJitThreshold = fullJitThresholdConfig - scale; |
| SetInterpretedCount(0); |
| SetDefaultInterpreterExecutionMode(); |
| SetFullJitThreshold(fullJitThresholdConfig); |
| TryTransitionToNextInterpreterExecutionMode(); |
| } |
| |
| uint16 FunctionExecutionStateMachine::GetDefaultFullJitThreshold(bool isCoroutine) const |
| { |
| return GetDefaultAutoProfilingInterpreter0Limit(isCoroutine) + |
| GetDefaultProfilingInterpreter0Limit(isCoroutine) + |
| GetDefaultAutoProfilingInterpreter1Limit(isCoroutine) + |
| GetDefaultSimpleJitLimit(isCoroutine) + |
| GetDefaultProfilingInterpreter1Limit(isCoroutine); |
| } |
| |
| void FunctionExecutionStateMachine::ReinitializeExecutionModeAndLimits(FunctionBody* functionBody) |
| { |
| #if DBG |
| hasBeenReinitialized = true; |
| #ifdef ENABLE_SCRIPT_DEBUGGING |
| reinitDebuggerMode = functionBody->GetDebuggerMode(); |
| #endif |
| #endif |
| // TODO: Investigate what it would take to make this invariant hold. Currently fails in AsmJS tests |
| // Assert(initializedExecutionModeAndLimits); |
| |
| fullJitRequeueThreshold = 0; |
| committedProfiledIterations = 0; |
| InitializeExecutionModeAndLimits(functionBody); |
| } |
| |
| bool FunctionExecutionStateMachine::InterpretedSinceCallCountCollection() const |
| { |
| return interpretedCount != lastInterpretedCount; |
| } |
| |
| void FunctionExecutionStateMachine::CollectInterpretedCounts() |
| { |
| lastInterpretedCount = interpretedCount; |
| } |
| |
| ExecutionMode FunctionExecutionStateMachine::GetExecutionMode() const |
| { |
| ExecutionMode executionMode = StateToMode(executionState); |
| |
| VerifyExecutionMode(executionMode); |
| return executionMode; |
| } |
| |
| void FunctionExecutionStateMachine::SetExecutionState(ExecutionState state) |
| { |
| // TODO: Investigate what it would take to make this invariant hold |
| // Assert(state == GetDefaultInterpreterExecutionState() || IsTerminalState(state)); |
| VerifyExecutionMode(StateToMode(state)); |
| executionState = state; |
| } |
| |
| void FunctionExecutionStateMachine::SetAsmJsExecutionMode() |
| { |
| SetExecutionState(ExecutionState::FullJit); |
| } |
| |
| void FunctionExecutionStateMachine::SetDefaultInterpreterExecutionMode() |
| { |
| SetExecutionState(GetDefaultInterpreterExecutionState()); |
| } |
| |
| FunctionExecutionStateMachine::ExecutionState FunctionExecutionStateMachine::GetDefaultInterpreterExecutionState() const |
| { |
| if (!owner->DoInterpreterProfile()) |
| { |
| VerifyExecutionMode(ExecutionMode::Interpreter); |
| return ExecutionState::Interpreter; |
| } |
| else if (owner->DoInterpreterAutoProfile()) |
| { |
| VerifyExecutionMode(ExecutionMode::AutoProfilingInterpreter); |
| return ExecutionState::AutoProfilingInterpreter0; |
| } |
| else |
| { |
| VerifyExecutionMode(ExecutionMode::ProfilingInterpreter); |
| return ExecutionState::ProfilingInterpreter0; |
| } |
| } |
| |
| ExecutionMode FunctionExecutionStateMachine::GetInterpreterExecutionMode(const bool isPostBailout) |
| { |
| Assert(initializedExecutionModeAndLimits); |
| |
| if (isPostBailout && owner->DoInterpreterProfile()) |
| { |
| return ExecutionMode::ProfilingInterpreter; |
| } |
| |
| switch (GetExecutionMode()) |
| { |
| case ExecutionMode::Interpreter: |
| case ExecutionMode::AutoProfilingInterpreter: |
| case ExecutionMode::ProfilingInterpreter: |
| return GetExecutionMode(); |
| |
| case ExecutionMode::SimpleJit: |
| if (CONFIG_FLAG(NewSimpleJit)) |
| { |
| return StateToMode(GetDefaultInterpreterExecutionState()); |
| } |
| // fall through |
| |
| case ExecutionMode::FullJit: |
| { |
| const ExecutionMode executionMode = |
| owner->DoInterpreterProfile() ? ExecutionMode::ProfilingInterpreter : ExecutionMode::Interpreter; |
| VerifyExecutionMode(executionMode); |
| return executionMode; |
| } |
| |
| default: |
| Assert(false); |
| __assume(false); |
| } |
| } |
| |
| bool FunctionExecutionStateMachine::IsInterpreterExecutionMode() const |
| { |
| return GetExecutionMode() <= ExecutionMode::ProfilingInterpreter; |
| } |
| |
| uint16 FunctionExecutionStateMachine::GetSimpleJitExecutedIterations() const |
| { |
| Assert(initializedExecutionModeAndLimits); |
| Assert(GetExecutionMode() == ExecutionMode::SimpleJit); |
| |
| FunctionEntryPointInfo *const simpleJitEntryPointInfo = owner->GetSimpleJitEntryPointInfo(); |
| if (!simpleJitEntryPointInfo) |
| { |
| return 0; |
| } |
| |
| // Simple JIT counts down and transitions on overflow |
| const uint32 callCount = simpleJitEntryPointInfo->callsCount; |
| Assert(simpleJitLimit == 0 ? callCount == 0 : simpleJitLimit > callCount); |
| return callCount == 0 ? |
| static_cast<uint16>(simpleJitLimit) : |
| static_cast<uint16>(simpleJitLimit) - static_cast<uint16>(callCount) - 1; |
| } |
| |
| void FunctionExecutionStateMachine::SetSimpleJitCallCount(const uint16 simpleJitLimit) const |
| { |
| Assert(GetExecutionMode() == ExecutionMode::SimpleJit); |
| Assert(owner->GetDefaultFunctionEntryPointInfo() == owner->GetSimpleJitEntryPointInfo()); |
| |
| // Simple JIT counts down and transitions on overflow |
| const uint8 limit = static_cast<uint8>(min(0xffui16, simpleJitLimit)); |
| owner->GetSimpleJitEntryPointInfo()->callsCount = limit == 0 ? 0 : limit - 1; |
| } |
| |
| void FunctionExecutionStateMachine::SetFullJitRequeueThreshold(const uint16 newFullJitRequeueThreshold) |
| { |
| fullJitRequeueThreshold = newFullJitRequeueThreshold; |
| } |
| |
| void FunctionExecutionStateMachine::SetFullJitThreshold(const uint16 newFullJitThreshold, const bool skipSimpleJit) |
| { |
| Assert(initializedExecutionModeAndLimits); |
| Assert(GetExecutionMode() != ExecutionMode::FullJit); |
| |
| int scale = newFullJitThreshold - fullJitThreshold; |
| if (scale == 0) |
| { |
| VerifyExecutionModeLimits(); |
| return; |
| } |
| fullJitThreshold = newFullJitThreshold; |
| |
| const auto ScaleLimit = [&](uint16 &limit) -> bool |
| { |
| Assert(scale != 0); |
| const int limitScale = max(-static_cast<int>(limit), scale); |
| const int newLimit = limit + limitScale; |
| Assert(static_cast<int>(static_cast<uint16>(newLimit)) == newLimit); |
| limit = static_cast<uint16>(newLimit); |
| scale -= limitScale; |
| Assert(limit == 0 || scale == 0); |
| |
| if (&limit == &simpleJitLimit) |
| { |
| FunctionEntryPointInfo *const simpleJitEntryPointInfo = owner->GetSimpleJitEntryPointInfo(); |
| if (owner->GetDefaultFunctionEntryPointInfo() == simpleJitEntryPointInfo) |
| { |
| Assert(GetExecutionMode() == ExecutionMode::SimpleJit); |
| const int newSimpleJitCallCount = max(0, (int)simpleJitEntryPointInfo->callsCount + limitScale); |
| Assert(static_cast<int>(static_cast<uint16>(newSimpleJitCallCount)) == newSimpleJitCallCount); |
| SetSimpleJitCallCount(static_cast<uint16>(newSimpleJitCallCount)); |
| } |
| } |
| |
| return scale == 0; |
| }; |
| |
| /* |
| Determine which execution mode's limit scales with the full JIT threshold, in order of preference: |
| - New simple JIT |
| - Auto-profiling interpreter 1 |
| - Auto-profiling interpreter 0 |
| - Interpreter |
| - Profiling interpreter 0 (when using old simple JIT) |
| - Old simple JIT |
| - Profiling interpreter 1 |
| - Profiling interpreter 0 (when using new simple JIT) |
| */ |
| const bool doSimpleJit = owner->DoSimpleJit(); |
| const bool doInterpreterProfile = owner->DoInterpreterProfile(); |
| const bool fullyScaled = |
| (CONFIG_FLAG(NewSimpleJit) && doSimpleJit && ScaleLimit(simpleJitLimit)) || |
| ( |
| doInterpreterProfile |
| ? owner->DoInterpreterAutoProfile() && |
| (ScaleLimit(autoProfilingInterpreter1Limit) || ScaleLimit(autoProfilingInterpreter0Limit)) |
| : ScaleLimit(interpreterLimit) |
| ) || |
| ( |
| CONFIG_FLAG(NewSimpleJit) |
| ? doInterpreterProfile && |
| (ScaleLimit(profilingInterpreter1Limit) || ScaleLimit(profilingInterpreter0Limit)) |
| : (doInterpreterProfile && ScaleLimit(profilingInterpreter0Limit)) || |
| (doSimpleJit && ScaleLimit(simpleJitLimit)) || |
| (doInterpreterProfile && ScaleLimit(profilingInterpreter1Limit)) |
| ); |
| Assert(fullyScaled); |
| Assert(scale == 0); |
| |
| if (GetExecutionMode() != ExecutionMode::SimpleJit) |
| { |
| Assert(IsInterpreterExecutionMode()); |
| if (simpleJitLimit != 0 && |
| (skipSimpleJit || simpleJitLimit < DEFAULT_CONFIG_MinSimpleJitIterations) && |
| !PHASE_FORCE(Phase::SimpleJitPhase, owner)) |
| { |
| // Simple JIT code has not yet been generated, and was either requested to be skipped, or the limit was scaled |
| // down too much. Skip simple JIT by moving any remaining iterations to an equivalent interpreter execution |
| // mode. |
| (CONFIG_FLAG(NewSimpleJit) ? autoProfilingInterpreter1Limit : profilingInterpreter1Limit) += simpleJitLimit; |
| simpleJitLimit = 0; |
| TryTransitionToNextInterpreterExecutionMode(); |
| } |
| } |
| |
| VerifyExecutionModeLimits(); |
| } |
| |
| FunctionExecutionStateMachine::ExecutionState FunctionExecutionStateMachine::ModeToState(ExecutionMode mode) const |
| { |
| switch (mode) |
| { |
| case ExecutionMode::AutoProfilingInterpreter: |
| return ExecutionState::AutoProfilingInterpreter0; |
| |
| case ExecutionMode::ProfilingInterpreter: |
| return ExecutionState::ProfilingInterpreter0; |
| |
| case ExecutionMode::SimpleJit: |
| return ExecutionState::SimpleJit; |
| |
| case ExecutionMode::FullJit: |
| return ExecutionState::FullJit; |
| |
| default: |
| Assert(!"Unexpected ExecutionMode for ExecutionState"); |
| // fall through |
| case ExecutionMode::Interpreter: |
| return ExecutionState::Interpreter; |
| } |
| } |
| |
| ExecutionMode FunctionExecutionStateMachine::StateToMode(ExecutionState state) const |
| { |
| switch (state) |
| { |
| case ExecutionState::AutoProfilingInterpreter0: |
| case ExecutionState::AutoProfilingInterpreter1: |
| return ExecutionMode::AutoProfilingInterpreter; |
| |
| case ExecutionState::ProfilingInterpreter0: |
| case ExecutionState::ProfilingInterpreter1: |
| return ExecutionMode::ProfilingInterpreter; |
| |
| case ExecutionState::SimpleJit: |
| return ExecutionMode::SimpleJit; |
| |
| case ExecutionState::FullJit: |
| return ExecutionMode::FullJit; |
| |
| default: |
| Assert(!"Unexpected ExecutionState for ExecutionMode"); |
| // fall through |
| case ExecutionState::Interpreter: |
| return ExecutionMode::Interpreter; |
| } |
| } |
| |
| uint16& FunctionExecutionStateMachine::GetStateLimit(ExecutionState state) |
| { |
| switch (state) |
| { |
| case ExecutionState::Interpreter: |
| return interpreterLimit; |
| |
| case ExecutionState::AutoProfilingInterpreter0: |
| return autoProfilingInterpreter0Limit; |
| |
| case ExecutionState::AutoProfilingInterpreter1: |
| return autoProfilingInterpreter1Limit; |
| |
| case ExecutionState::ProfilingInterpreter0: |
| return profilingInterpreter0Limit; |
| |
| case ExecutionState::ProfilingInterpreter1: |
| return profilingInterpreter1Limit; |
| |
| case ExecutionState::SimpleJit: |
| return simpleJitLimit; |
| |
| default: |
| Assert(!"Unexpected ExecutionState for limit"); |
| return interpreterLimit; |
| } |
| } |
| |
| // An execution state is terminal if the current FunctionExecutionStateMachine's limits |
| // allow the state to continue to run. |
| // FullJit is always a terminal state and is the last terminal state. |
| bool FunctionExecutionStateMachine::IsTerminalState(ExecutionState state) |
| { |
| return state == ExecutionState::FullJit || GetStateLimit(state) != 0; |
| } |
| |
| // Safely moves from one execution mode to another and updates appropriate class members for the next |
| // mode. Note that there are other functions that modify execution state that do not involve this function. |
| // This function transitions ExecutionMode as ExecutionState in the following order: |
| // |
| // +-- Interpreter |
| // | |
| // | AutoProfilingInterpreter --+ |
| // | | ^ | |
| // | | | v |
| // | | | SimpleJit |
| // | v | | |
| // | ProfilingInterpreter <----+ |
| // | | |
| // | | |
| // | v |
| // +-> FullJit |
| // |
| // Transition to the next mode occurs when the limit for the current execution mode reaches 0. |
| // Returns true when a transition occurs (i.e., the execution state was updated since the beginning of |
| // this function call). Otherwise, returns false to indicate no change in state. |
| // See more details of each mode in ExecutionModes.h |
| bool FunctionExecutionStateMachine::TryTransitionToNextExecutionMode() |
| { |
| Assert(initializedExecutionModeAndLimits); |
| |
| bool isStateChanged = false; |
| if (executionState != ExecutionState::FullJit) |
| { |
| bool isTransitionNeeded; |
| uint16& stateLimit = GetStateLimit(executionState); |
| FunctionEntryPointInfo *const simpleJitEntryPointInfo = owner->GetSimpleJitEntryPointInfo(); |
| |
| // Determine if the current state should not transition when |
| // - for non-JITed states, the interpreted count is less than the limit |
| // - for JITed states (specifically, SimpleJIT because it can transition), the callsCount |
| // is non-zero. CallsCount starts at the limit and decrements to 0 to indicate transition. |
| if ((executionState != ExecutionState::SimpleJit && GetInterpretedCount() < stateLimit) |
| || (simpleJitEntryPointInfo != nullptr && simpleJitEntryPointInfo->callsCount > 0)) |
| { |
| // Since the current state is under its limit, no transition is needed. |
| // Simply verify the current state's execution mode before returning. |
| isTransitionNeeded = false; |
| } |
| else |
| { |
| // Since the current state's limit is reached, transition from this state to the next state |
| // First, save data from the current state |
| CommitExecutedIterations(stateLimit, stateLimit); |
| |
| // Then, reset data for the next state |
| SetInterpretedCount(0); |
| |
| isTransitionNeeded = true; |
| } |
| |
| if (isTransitionNeeded) |
| { |
| // Keep advancing the state until a terminal state is found or until there are no more |
| // states to reach. The path of advancement is described in the banner comment above. |
| ExecutionState newState = executionState; |
| while (isTransitionNeeded && !IsTerminalState(newState)) |
| { |
| if (newState != ExecutionState::Interpreter) |
| { |
| // Most states simply advance to the next state |
| newState = static_cast<ExecutionState>(static_cast<uint8>(newState) + 1); |
| } |
| else |
| { |
| // Interpreter advances straight to FullJit |
| newState = ExecutionState::FullJit; |
| } |
| |
| // If FullJit is the next state, but FullJit is disabled, then no transition |
| // is needed. |
| if (newState == ExecutionState::FullJit && PHASE_OFF(FullJitPhase, owner)) |
| { |
| isTransitionNeeded = false; |
| } |
| else |
| { |
| // Otherwise, transition is needed because there is new state available |
| isTransitionNeeded = true; |
| } |
| } |
| |
| // Only update the execution state when the new state is a terminal state |
| if (isTransitionNeeded && IsTerminalState(newState)) |
| { |
| Assert(newState != executionState); |
| SetExecutionState(newState); |
| isStateChanged = true; |
| } |
| } |
| } |
| |
| return isStateChanged; |
| } |
| |
| void FunctionExecutionStateMachine::TryTransitionToNextInterpreterExecutionMode() |
| { |
| Assert(IsInterpreterExecutionMode()); |
| |
| TryTransitionToNextExecutionMode(); |
| SetExecutionState(ModeToState(GetInterpreterExecutionMode(false))); |
| } |
| |
| bool FunctionExecutionStateMachine::TryTransitionToJitExecutionMode() |
| { |
| const ExecutionMode previousExecutionMode = GetExecutionMode(); |
| |
| TryTransitionToNextExecutionMode(); |
| switch (GetExecutionMode()) |
| { |
| case ExecutionMode::SimpleJit: |
| break; |
| |
| case ExecutionMode::FullJit: |
| if (fullJitRequeueThreshold == 0) |
| { |
| break; |
| } |
| --fullJitRequeueThreshold; |
| return false; |
| |
| default: |
| return false; |
| } |
| |
| if (GetExecutionMode() != previousExecutionMode) |
| { |
| owner->TraceExecutionMode(); |
| } |
| return true; |
| } |
| |
| void FunctionExecutionStateMachine::TransitionToSimpleJitExecutionMode() |
| { |
| CommitExecutedIterations(); |
| |
| interpreterLimit = 0; |
| autoProfilingInterpreter0Limit = 0; |
| profilingInterpreter0Limit = 0; |
| autoProfilingInterpreter1Limit = 0; |
| fullJitThreshold = simpleJitLimit + profilingInterpreter1Limit; |
| |
| VerifyExecutionModeLimits(); |
| SetExecutionState(ExecutionState::SimpleJit); |
| } |
| |
| void FunctionExecutionStateMachine::TransitionToFullJitExecutionMode() |
| { |
| CommitExecutedIterations(); |
| |
| interpreterLimit = 0; |
| autoProfilingInterpreter0Limit = 0; |
| profilingInterpreter0Limit = 0; |
| autoProfilingInterpreter1Limit = 0; |
| simpleJitLimit = 0; |
| profilingInterpreter1Limit = 0; |
| fullJitThreshold = 0; |
| |
| VerifyExecutionModeLimits(); |
| SetExecutionState(ExecutionState::FullJit); |
| } |
| |
| void FunctionExecutionStateMachine::SetIsSpeculativeJitCandidate() |
| { |
| // This function is a candidate for speculative JIT. Ensure that it is profiled immediately by transitioning out of the |
| // auto-profiling interpreter mode. |
| if (GetExecutionMode() != ExecutionMode::AutoProfilingInterpreter || GetProfiledIterations() != 0) |
| { |
| return; |
| } |
| |
| owner->TraceExecutionMode("IsSpeculativeJitCandidate (before)"); |
| |
| if (autoProfilingInterpreter0Limit != 0) |
| { |
| (profilingInterpreter0Limit == 0 ? profilingInterpreter0Limit : autoProfilingInterpreter1Limit) += |
| autoProfilingInterpreter0Limit; |
| autoProfilingInterpreter0Limit = 0; |
| } |
| else if (profilingInterpreter0Limit == 0) |
| { |
| profilingInterpreter0Limit += autoProfilingInterpreter1Limit; |
| autoProfilingInterpreter1Limit = 0; |
| } |
| |
| owner->TraceExecutionMode("IsSpeculativeJitCandidate"); |
| |
| TryTransitionToNextInterpreterExecutionMode(); |
| } |
| |
| void FunctionExecutionStateMachine::ResetSimpleJitLimit() |
| { |
| Assert(initializedExecutionModeAndLimits); |
| |
| SetExecutionState(ExecutionState::SimpleJit); |
| |
| const uint16 simpleJitNewLimit = static_cast<uint8>(Configuration::Global.flags.SimpleJitLimit); |
| if (simpleJitLimit < simpleJitNewLimit) |
| { |
| fullJitThreshold += simpleJitNewLimit - simpleJitLimit; |
| simpleJitLimit = simpleJitNewLimit; |
| } |
| |
| SetInterpretedCount(0); |
| } |
| |
| uint16 FunctionExecutionStateMachine::GetProfiledIterations() const |
| { |
| Assert(initializedExecutionModeAndLimits); |
| |
| uint16 profiledIterations = committedProfiledIterations; |
| switch (GetExecutionMode()) |
| { |
| case ExecutionMode::ProfilingInterpreter: |
| { |
| uint32 interpretedCount = GetInterpretedCount(); |
| const uint16 clampedInterpretedCount = |
| interpretedCount <= UINT16_MAX |
| ? static_cast<uint16>(interpretedCount) |
| : UINT16_MAX; |
| const uint16 newProfiledIterations = profiledIterations + clampedInterpretedCount; |
| profiledIterations = newProfiledIterations >= profiledIterations ? newProfiledIterations : UINT16_MAX; |
| break; |
| } |
| |
| case ExecutionMode::SimpleJit: |
| if (!CONFIG_FLAG(NewSimpleJit)) |
| { |
| const uint16 newProfiledIterations = profiledIterations + GetSimpleJitExecutedIterations(); |
| profiledIterations = newProfiledIterations >= profiledIterations ? newProfiledIterations : UINT16_MAX; |
| } |
| break; |
| } |
| return profiledIterations; |
| } |
| |
| void FunctionExecutionStateMachine::CommitExecutedIterations() |
| { |
| Assert(initializedExecutionModeAndLimits); |
| |
| switch (GetExecutionMode()) |
| { |
| case ExecutionMode::Interpreter: |
| CommitExecutedIterations(interpreterLimit, GetInterpretedCount()); |
| break; |
| |
| case ExecutionMode::AutoProfilingInterpreter: |
| CommitExecutedIterations( |
| autoProfilingInterpreter0Limit == 0 && profilingInterpreter0Limit == 0 |
| ? autoProfilingInterpreter1Limit |
| : autoProfilingInterpreter0Limit, |
| GetInterpretedCount()); |
| break; |
| |
| case ExecutionMode::ProfilingInterpreter: |
| CommitExecutedIterations( |
| owner->GetSimpleJitEntryPointInfo() |
| ? profilingInterpreter1Limit |
| : profilingInterpreter0Limit, |
| GetInterpretedCount()); |
| break; |
| |
| case ExecutionMode::SimpleJit: |
| CommitExecutedIterations(simpleJitLimit, GetSimpleJitExecutedIterations()); |
| break; |
| |
| case ExecutionMode::FullJit: |
| break; |
| |
| default: |
| Assert(false); |
| __assume(false); |
| } |
| } |
| |
| void FunctionExecutionStateMachine::CommitExecutedIterations(uint16 &limit, const uint executedIterations) |
| { |
| Assert(initializedExecutionModeAndLimits); |
| Assert( |
| &limit == &interpreterLimit || |
| &limit == &autoProfilingInterpreter0Limit || |
| &limit == &profilingInterpreter0Limit || |
| &limit == &autoProfilingInterpreter1Limit || |
| &limit == &simpleJitLimit || |
| &limit == &profilingInterpreter1Limit); |
| |
| const uint16 clampedExecutedIterations = executedIterations >= limit ? limit : static_cast<uint16>(executedIterations); |
| Assert(fullJitThreshold >= clampedExecutedIterations); |
| fullJitThreshold -= clampedExecutedIterations; |
| limit -= clampedExecutedIterations; |
| VerifyExecutionModeLimits(); |
| |
| if (&limit == &profilingInterpreter0Limit || |
| (!CONFIG_FLAG(NewSimpleJit) && &limit == &simpleJitLimit) || |
| &limit == &profilingInterpreter1Limit) |
| { |
| const uint16 newCommittedProfiledIterations = committedProfiledIterations + clampedExecutedIterations; |
| committedProfiledIterations = |
| newCommittedProfiledIterations >= committedProfiledIterations ? newCommittedProfiledIterations : UINT16_MAX; |
| } |
| } |
| |
| void FunctionExecutionStateMachine::VerifyExecutionModeLimits() const |
| { |
| Assert(initializedExecutionModeAndLimits); |
| Assert( |
| ( |
| interpreterLimit + |
| autoProfilingInterpreter0Limit + |
| profilingInterpreter0Limit + |
| autoProfilingInterpreter1Limit + |
| simpleJitLimit + |
| profilingInterpreter1Limit |
| ) == fullJitThreshold); |
| } |
| |
| void FunctionExecutionStateMachine::VerifyExecutionMode(const ExecutionMode executionMode) const |
| { |
| #if DBG |
| Assert(initializedExecutionModeAndLimits); |
| Assert(executionMode < ExecutionMode::Count); |
| |
| switch (executionMode) |
| { |
| case ExecutionMode::Interpreter: |
| Assert(!owner->DoInterpreterProfile()); |
| break; |
| |
| case ExecutionMode::AutoProfilingInterpreter: |
| Assert(owner->DoInterpreterProfile()); |
| Assert(owner->DoInterpreterAutoProfile()); |
| break; |
| |
| case ExecutionMode::ProfilingInterpreter: |
| Assert(owner->DoInterpreterProfile()); |
| break; |
| |
| case ExecutionMode::SimpleJit: |
| Assert(owner->DoSimpleJit()); |
| break; |
| |
| case ExecutionMode::FullJit: |
| Assert(!PHASE_OFF(FullJitPhase, owner)); |
| break; |
| |
| default: |
| Assert(false); |
| __assume(false); |
| } |
| #else |
| UNREFERENCED_PARAMETER(executionMode); |
| #endif |
| } |
| |
| void FunctionExecutionStateMachine::PrintLimits() const |
| { |
| Output::Print( |
| _u("limits: %hu.%hu.%hu.%hu.%hu = %hu"), |
| interpreterLimit + autoProfilingInterpreter0Limit, |
| profilingInterpreter0Limit, |
| autoProfilingInterpreter1Limit, |
| simpleJitLimit, |
| profilingInterpreter1Limit, |
| fullJitThreshold); |
| } |
| |
| void FunctionExecutionStateMachine::AssertIsInitialized() const |
| { |
| #if DBG |
| Assert(initializedExecutionModeAndLimits); |
| Assert(owner != nullptr); |
| #endif |
| } |
| } |