blob: e1c7919cd54b54ddde69ec9e4dfb3c1f159da784 [file]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
// Shared interpreter loop
//
// This holds the single definition of the interpreter loop.
// It allows for configurable copies of the loop that do extra work without
// impacting the mainline performance. (for example the debug loop can simply
// check a bit without concern for impacting the nondebug mode.)
#if defined(INTERPRETER_ASMJS)
#define INTERPRETER_OPCODE OpCodeAsmJs
#define TRACING_FUNC &InterpreterStackFrame::TraceAsmJsOpCode
#else
#define INTERPRETER_OPCODE OpCode
#define TRACING_FUNC &InterpreterStackFrame::TraceOpCode
#endif
#ifdef PROVIDE_INTERPRETER_STMTS
#define READ_OP ReadOp_WPreviousStmtTracking<INTERPRETER_OPCODE, ByteCodeReader::ReadByteOp, TRACING_FUNC>
#define READ_EXT_OP ReadOp_WPreviousStmtTracking<INTERPRETER_OPCODE, ByteCodeReader::ReadExtOp, TRACING_FUNC>
#else
#define READ_OP ReadOp<INTERPRETER_OPCODE, ByteCodeReader::ReadByteOp, TRACING_FUNC>
#define READ_EXT_OP ReadOp<INTERPRETER_OPCODE, ByteCodeReader::ReadExtOp, TRACING_FUNC>
#endif
#ifdef PROVIDE_DEBUGGING
#define DEBUGGING_LOOP 1
#else
#define DEBUGGING_LOOP 0
#endif
#ifdef PROVIDE_INTERPRETERPROFILE
#define INTERPRETERPROFILE 1
#define PROFILEDOP(prof, unprof) prof
#else
#define INTERPRETERPROFILE 0
#define PROFILEDOP(prof, unprof) unprof
#endif
//two layers of macros are necessary to get arguments to the invocation of the top level macro expanded.
#define CONCAT_TOKENS_AGAIN(loopName, fnSuffix) loopName ## fnSuffix
#define CONCAT_TOKENS(loopName, fnSuffix) CONCAT_TOKENS_AGAIN(loopName, fnSuffix)
#define PROCESS_OPCODE_FN_NAME(fnSuffix) CONCAT_TOKENS(INTERPRETERLOOPNAME, fnSuffix)
const byte* Js::InterpreterStackFrame::PROCESS_OPCODE_FN_NAME(ExtendedOpcodePrefix)(const byte* ip)
{
INTERPRETER_OPCODE op = READ_EXT_OP(ip);
switch (op)
{
#define EXDEF2(x, op, func) PROCESS_##x(op, func)
#define EXDEF3(x, op, func, y) PROCESS_##x(op, func, y)
#define EXDEF2_WMS(x, op, func) PROCESS_##x##_COMMON(op, func, _Small)
#define EXDEF3_WMS(x, op, func, y) PROCESS_##x##_COMMON(op, func, y, _Small)
#define EXDEF4_WMS(x, op, func, y, t) PROCESS_##x##_COMMON(op, func, y, _Small, t)
#include "InterpreterHandler.inl"
default:
// Help the C++ optimizer by declaring that the cases we
// have above are sufficient
AssertMsg(false, "dispatch to bad opcode");
__assume(false);
};
return ip;
}
const byte* Js::InterpreterStackFrame::PROCESS_OPCODE_FN_NAME(MediumLayoutPrefix)(const byte* ip, Var& yieldValue)
{
INTERPRETER_OPCODE op = READ_OP(ip);
switch (op)
{
#ifndef INTERPRETER_ASMJS
case INTERPRETER_OPCODE::Yield:
m_reader.Reg2_Medium(ip);
yieldValue = GetReg(GetFunctionBody()->GetYieldRegister());
break;
#endif
#define DEF2_WMS(x, op, func) PROCESS_##x##_COMMON(op, func, _Medium)
#define DEF3_WMS(x, op, func, y) PROCESS_##x##_COMMON(op, func, y, _Medium)
#define DEF4_WMS(x, op, func, y, t) PROCESS_##x##_COMMON(op, func, y, _Medium, t)
#include "InterpreterHandler.inl"
default:
// Help the C++ optimizer by declaring that the cases we
// have above are sufficient
AssertMsg(false, "dispatch to bad opcode");
__assume(false);
}
return ip;
}
const byte* Js::InterpreterStackFrame::PROCESS_OPCODE_FN_NAME(ExtendedMediumLayoutPrefix)(const byte* ip)
{
INTERPRETER_OPCODE op = READ_EXT_OP(ip);
switch (op)
{
#define EXDEF2_WMS(x, op, func) PROCESS_##x##_COMMON(op, func, _Medium)
#define EXDEF3_WMS(x, op, func, y) PROCESS_##x##_COMMON(op, func, y, _Medium)
#define EXDEF4_WMS(x, op, func, y, t) PROCESS_##x##_COMMON(op, func, y, _Medium, t)
#include "InterpreterHandler.inl"
default:
// Help the C++ optimizer by declaring that the cases we
// have above are sufficient
AssertMsg(false, "dispatch to bad opcode");
__assume(false);
};
return ip;
}
const byte* Js::InterpreterStackFrame::PROCESS_OPCODE_FN_NAME(LargeLayoutPrefix)(const byte* ip, Var& yieldValue)
{
INTERPRETER_OPCODE op = READ_OP(ip);
switch (op)
{
#ifndef INTERPRETER_ASMJS
case INTERPRETER_OPCODE::Yield:
m_reader.Reg2_Large(ip);
yieldValue = GetReg(GetFunctionBody()->GetYieldRegister());
break;
#endif
#define DEF2_WMS(x, op, func) PROCESS_##x##_COMMON(op, func, _Large)
#define DEF3_WMS(x, op, func, y) PROCESS_##x##_COMMON(op, func, y, _Large)
#define DEF4_WMS(x, op, func, y, t) PROCESS_##x##_COMMON(op, func, y, _Large, t)
#include "InterpreterHandler.inl"
default:
// Help the C++ optimizer by declaring that the cases we
// have above are sufficient
AssertMsg(false, "dispatch to bad opcode");
__assume(false);
}
return ip;
}
const byte* Js::InterpreterStackFrame::PROCESS_OPCODE_FN_NAME(ExtendedLargeLayoutPrefix)(const byte* ip)
{
INTERPRETER_OPCODE op = READ_EXT_OP(ip);
switch (op)
{
#define EXDEF2_WMS(x, op, func) PROCESS_##x##_COMMON(op, func, _Large)
#define EXDEF3_WMS(x, op, func, y) PROCESS_##x##_COMMON(op, func, y, _Large)
#define EXDEF4_WMS(x, op, func, y, t) PROCESS_##x##_COMMON(op, func, y, _Large, t)
#include "InterpreterHandler.inl"
default:
// Help the C++ optimizer by declaring that the cases we
// have above are sufficient
AssertMsg(false, "dispatch to bad opcode");
__assume(false);
};
return ip;
}
#if defined (DBG)
// Win8 516184: Huge switch with lots of labels each having a few locals on ARM.DBG causes each occurrence
// of this function (call of a javascript function in interpreter mode) to take 7+KB stack space
// (without optimizations the compiler accounts for ALL locals inside case labels when allocating space on stack
// for locals - SP does not change inside the function). On other platforms this is still huge but better than ARM.
// So, for DBG turn on optimizations to prevent this huge loss of stack.
#pragma optimize("g", on)
#endif
Js::Var Js::InterpreterStackFrame::INTERPRETERLOOPNAME()
{
PROBE_STACK(scriptContext, Js::Constants::MinStackInterpreter);
if (!this->closureInitDone)
{
// If this is the start of the function, then we've waited until after the stack probe above
// to set up the FD/SS pointers, so do it now.
Assert(this->m_reader.GetCurrentOffset() == 0);
this->InitializeClosures();
}
Assert(this->returnAddress != nullptr);
AssertMsg(!this->GetFunctionBody()->GetUsesArgumentsObject() || m_arguments == NULL || Js::ArgumentsObject::Is(m_arguments), "Bad arguments!");
// IP Passing in the interpreter:
// We keep a local copy of the bytecode's instruction pointer and
// pass it by reference to the bytecode reader.
// This allows the optimizer to recognize that the local (held in a register)
// dominates the copy in the reader.
// The effect is our dispatch loop is significantly smaller in the common case
// on optimized builds.
//
// For checked builds this does mean we are incrementing 2 different counters to
// track the ip.
const byte* ip = m_reader.GetIP();
while (true)
{
INTERPRETER_OPCODE op = READ_OP(ip);
#ifdef ENABLE_BASIC_TELEMETRY
if( TELEMETRY_OPCODE_OFFSET_ENABLED )
{
OpcodeTelemetry& opcodeTelemetry = this->scriptContext->GetTelemetry().GetOpcodeTelemetry();
opcodeTelemetry.ProgramLocationFunctionId ( this->function->GetFunctionInfo()->GetLocalFunctionId() );
opcodeTelemetry.ProgramLocationBytecodeOffset( this->m_reader.GetCurrentOffset() );
}
#endif
#if DEBUGGING_LOOP
if (this->scriptContext->GetThreadContext()->GetDebugManager()->stepController.IsActive() &&
this->scriptContext->GetThreadContext()->GetDebugManager()->stepController.IsStepComplete_AllowingFalsePositives(this))
{
// BrLong is used for branch island, we don't want to break over there, as they don't belong to any statement. Just skip this.
if (!InterpreterStackFrame::IsBrLong(op, ip) && !this->m_functionBody->GetUtf8SourceInfo()->GetIsLibraryCode())
{
uint prevOffset = m_reader.GetCurrentOffset();
#if ENABLE_TTD
bool bpTaken = (!this->scriptContext->GetThreadContext()->IsRuntimeInTTDMode()) || this->scriptContext->GetThreadContext()->TTDExecutionInfo->ProcessBPInfoPreBreak(this->m_functionBody, this->scriptContext->GetThreadContext()->TTDLog);
if(bpTaken)
{
InterpreterHaltState haltState(STOP_STEPCOMPLETE, m_functionBody);
this->scriptContext->GetDebugContext()->GetProbeContainer()->DispatchStepHandler(&haltState, &op);
}
#else
InterpreterHaltState haltState(STOP_STEPCOMPLETE, m_functionBody);
this->scriptContext->GetDebugContext()->GetProbeContainer()->DispatchStepHandler(&haltState, &op);
#endif
#if ENABLE_TTD
if(bpTaken && this->scriptContext->GetThreadContext()->IsRuntimeInTTDMode())
{
this->scriptContext->GetThreadContext()->TTDExecutionInfo->ProcessBPInfoPostBreak(this->m_functionBody);
}
#endif
if (prevOffset != m_reader.GetCurrentOffset())
{
// The location of the statement has been changed, setnextstatement was called.
// Reset m_outParams and m_outSp as before SetNext was called, we could be in the middle of StartCall.
// It's fine to do because SetNext can only be done to a statement -- function-level destination,
// and can't land to an expression inside call.
ResetOut();
ip = m_reader.GetIP();
continue;
}
}
}
// The break opcode will be handled later in the switch block.
if (op != OpCode::Break && this->scriptContext->GetThreadContext()->GetDebugManager()->asyncBreakController.IsBreak())
{
if (!InterpreterStackFrame::IsBrLong(op, ip) && !this->m_functionBody->GetUtf8SourceInfo()->GetIsLibraryCode())
{
uint prevOffset = m_reader.GetCurrentOffset();
#if ENABLE_TTD
bool bpTaken = (!this->scriptContext->GetThreadContext()->IsRuntimeInTTDMode()) || this->scriptContext->GetThreadContext()->TTDExecutionInfo->ProcessBPInfoPreBreak(this->m_functionBody, this->scriptContext->GetThreadContext()->TTDLog);
if(bpTaken)
{
InterpreterHaltState haltState(STOP_ASYNCBREAK, m_functionBody);
this->scriptContext->GetDebugContext()->GetProbeContainer()->DispatchAsyncBreak(&haltState);
}
#else
InterpreterHaltState haltState(STOP_ASYNCBREAK, m_functionBody);
this->scriptContext->GetDebugContext()->GetProbeContainer()->DispatchAsyncBreak(&haltState);
#endif
#if ENABLE_TTD
if(bpTaken && this->scriptContext->GetThreadContext()->IsRuntimeInTTDMode())
{
this->scriptContext->GetThreadContext()->TTDExecutionInfo->ProcessBPInfoPostBreak(this->m_functionBody);
}
#endif
if (prevOffset != m_reader.GetCurrentOffset())
{
// The location of the statement has been changed, setnextstatement was called.
ip = m_reader.GetIP();
continue;
}
}
}
SWAP_BP_FOR_OPCODE:
#endif
switch (op)
{
case INTERPRETER_OPCODE::Ret:
{
//
// Return "Reg: 0" as the return-value.
// - JavaScript functions always return a value, and this value is always
// accessible to the caller. For an empty "return;" or exiting the end of the
// function's body, it is assumed that the byte-code author
// (ByteCodeGenerator) will load 'undefined' into R0.
// - If R0 has not explicitly been set, it will contain whatever garbage value
// was last set.
//
this->retOffset = m_reader.GetCurrentOffset();
m_reader.Empty(ip);
return GetReg((RegSlot)0);
}
#ifndef INTERPRETER_ASMJS
case INTERPRETER_OPCODE::Yield:
{
m_reader.Reg2_Small(ip);
return GetReg(GetFunctionBody()->GetYieldRegister());
}
#endif
#define DEF2(x, op, func) PROCESS_##x(op, func)
#define DEF3(x, op, func, y) PROCESS_##x(op, func, y)
#define DEF2_WMS(x, op, func) PROCESS_##x##_COMMON(op, func, _Small)
#define DEF3_WMS(x, op, func, y) PROCESS_##x##_COMMON(op, func, y, _Small)
#define DEF4_WMS(x, op, func, y, t) PROCESS_##x##_COMMON(op, func, y, _Small, t)
#include "InterpreterHandler.inl"
#ifndef INTERPRETER_ASMJS
case INTERPRETER_OPCODE::Leave:
// Return the continuation address to the helper.
// This tells the helper that control left the scope without completing the try/handler,
// which is particularly significant when executing a finally.
m_reader.Empty(ip);
return (Var)this->m_reader.GetCurrentOffset();
case INTERPRETER_OPCODE::LeaveNull:
// Return to the helper without specifying a continuation address,
// indicating that the handler completed without jumping, so exception processing
// should continue.
m_reader.Empty(ip);
return nullptr;
#endif
#if ENABLE_PROFILE_INFO && !defined(INTERPRETER_ASMJS)
// Aborting the current interpreter loop to switch the profile mode
#define CHECK_SWITCH_PROFILE_MODE() if (switchProfileMode) return nullptr;
#else
#define CHECK_SWITCH_PROFILE_MODE()
#endif
#ifndef INTERPRETER_ASMJS
#define CHECK_YIELD_VALUE() if (yieldValue != nullptr) return yieldValue;
#else
#define CHECK_YIELD_VALUE() Unused(yieldValue);
#endif
#define ExtendedCase(opcode) \
case INTERPRETER_OPCODE::opcode: \
ip = PROCESS_OPCODE_FN_NAME(opcode)(ip); \
CHECK_SWITCH_PROFILE_MODE(); \
break;
ExtendedCase(ExtendedOpcodePrefix)
ExtendedCase(ExtendedMediumLayoutPrefix)
ExtendedCase(ExtendedLargeLayoutPrefix)
case INTERPRETER_OPCODE::MediumLayoutPrefix:
{
Var yieldValue = nullptr;
ip = PROCESS_OPCODE_FN_NAME(MediumLayoutPrefix)(ip, yieldValue);
CHECK_YIELD_VALUE();
CHECK_SWITCH_PROFILE_MODE();
break;
}
case INTERPRETER_OPCODE::LargeLayoutPrefix:
{
Var yieldValue = nullptr;
ip = PROCESS_OPCODE_FN_NAME(LargeLayoutPrefix)(ip, yieldValue);
CHECK_YIELD_VALUE();
CHECK_SWITCH_PROFILE_MODE();
break;
}
case INTERPRETER_OPCODE::EndOfBlock:
{
// Note that at this time though ip was advanced by 'OpCode op = ReadByteOp<INTERPRETER_OPCODE>(ip)',
// we haven't advanced m_reader.m_currentLocation yet, thus m_reader.m_currentLocation still points to EndOfBLock,
// and that +1 will point to 1st byte past the buffer.
Assert(m_reader.GetCurrentOffset() + sizeof(byte) == m_functionBody->GetByteCode()->GetLength());
//
// Reached an "OpCode::EndOfBlock" so need to exit this interpreter loop because
// there is no more byte-code to execute.
// - This prevents us from accessing random memory as byte-codes.
// - Functions should contain an "OpCode::Ret" instruction to organize an
// orderly return.
//
#if DEBUGGING_LOOP
// However, during debugging an exception can be skipped which causes the
// statement that caused to exception to be skipped. If this statement is
// the statement that contains the OpCode::Ret then the EndOfBlock will
// be executed. In these cases it is sufficient to return undefined.
return this->scriptContext->GetLibrary()->GetUndefined();
#else
return nullptr;
#endif
}
#ifndef INTERPRETER_ASMJS
case INTERPRETER_OPCODE::Break:
{
#if DEBUGGING_LOOP
// The reader has already advanced the IP:
if (this->m_functionBody->ProbeAtOffset(m_reader.GetCurrentOffset(), &op))
{
uint prevOffset = m_reader.GetCurrentOffset();
#if ENABLE_TTD
bool bpTaken = (!this->scriptContext->GetThreadContext()->IsRuntimeInTTDMode()) || this->scriptContext->GetThreadContext()->TTDExecutionInfo->ProcessBPInfoPreBreak(this->m_functionBody, this->scriptContext->GetThreadContext()->TTDLog);
if(bpTaken)
{
InterpreterHaltState haltState(STOP_BREAKPOINT, m_functionBody);
this->scriptContext->GetDebugContext()->GetProbeContainer()->DispatchProbeHandlers(&haltState);
}
#else
InterpreterHaltState haltState(STOP_BREAKPOINT, m_functionBody);
this->scriptContext->GetDebugContext()->GetProbeContainer()->DispatchProbeHandlers(&haltState);
#endif
#if ENABLE_TTD
if(bpTaken && this->scriptContext->GetThreadContext()->IsRuntimeInTTDMode())
{
this->scriptContext->GetThreadContext()->TTDExecutionInfo->ProcessBPInfoPostBreak(this->m_functionBody);
}
#endif
if (prevOffset != m_reader.GetCurrentOffset())
{
// The location of the statement has been changed, setnextstatement was called.
ip = m_reader.GetIP();
continue;
}
// Jump back to the start of the switch.
goto SWAP_BP_FOR_OPCODE;
}
else
{
#if DEBUGGING_LOOP
// an inline break statement rather than a probe
if (!this->scriptContext->GetThreadContext()->GetDebugManager()->stepController.ContinueFromInlineBreakpoint())
{
uint prevOffset = m_reader.GetCurrentOffset();
#if ENABLE_TTD
bool bpTaken = (!this->scriptContext->GetThreadContext()->IsRuntimeInTTDMode()) || this->scriptContext->GetThreadContext()->TTDExecutionInfo->ProcessBPInfoPreBreak(this->m_functionBody, this->scriptContext->GetThreadContext()->TTDLog);
if(bpTaken)
{
InterpreterHaltState haltState(STOP_INLINEBREAKPOINT, m_functionBody);
this->scriptContext->GetDebugContext()->GetProbeContainer()->DispatchInlineBreakpoint(&haltState);
}
#else
InterpreterHaltState haltState(STOP_INLINEBREAKPOINT, m_functionBody);
this->scriptContext->GetDebugContext()->GetProbeContainer()->DispatchInlineBreakpoint(&haltState);
#endif
#if ENABLE_TTD
if(bpTaken && this->scriptContext->GetThreadContext()->IsRuntimeInTTDMode())
{
this->scriptContext->GetThreadContext()->TTDExecutionInfo->ProcessBPInfoPostBreak(this->m_functionBody);
}
#endif
if (prevOffset != m_reader.GetCurrentOffset())
{
// The location of the statement has been changed, setnextstatement was called.
ip = m_reader.GetIP();
continue;
}
}
#endif
// Consume after dispatching
m_reader.Empty(ip);
}
#else
m_reader.Empty(ip);
#endif
break;
}
#endif
default:
// Help the C++ optimizer by declaring that the cases we
// have above are sufficient
AssertMsg(false, "dispatch to bad opcode");
__assume(false);
}
}
}
#if defined (DBG)
// Restore optimizations to what's specified by the /O switch.
#pragma optimize("", on)
#endif
#undef READ_OP
#undef READ_EXT_OP
#undef TRACING_FUNC
#undef DEBUGGING_LOOP
#undef INTERPRETERPROFILE
#undef PROFILEDOP
#undef INTERPRETER_OPCODE
#undef CHECK_SWITCH_PROFILE_MODE
#undef CHECK_YIELD_VALUE