Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable inlining into functions which have try-catch/try-finally #3832

Merged
merged 1 commit into from
Oct 12, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions lib/Backend/BailOut.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1608,6 +1608,10 @@ BailOutRecord::BailOutHelper(Js::JavascriptCallStackLayout * layout, Js::ScriptF
{
newInstance->OrFlags(Js::InterpreterStackFrameFlags_ProcessingBailOutOnArrayAccessHelperCall);
}
if (isInlinee)
{
newInstance->OrFlags(Js::InterpreterStackFrameFlags_FromInlineeCodeInEHBailOut);
}

ThreadContext *threadContext = newInstance->GetScriptContext()->GetThreadContext();

Expand Down
2 changes: 1 addition & 1 deletion lib/Backend/Func.h
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ class Func

bool DoInline() const
{
return DoGlobOpt() && !GetTopFunc()->HasTry();
return DoGlobOpt();
}

bool DoOptimizeTry() const
Expand Down
12 changes: 12 additions & 0 deletions lib/Backend/GlobOpt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3674,6 +3674,18 @@ GlobOpt::CopyProp(IR::Opnd *opnd, IR::Instr *instr, Value *val, IR::IndirOpnd *p

ValueInfo *valueInfo = val->GetValueInfo();


if (this->func->HasFinally())
{
// s0 = undefined was added on functions with early exit in try-finally functions, that can get copy-proped and case incorrect results
if (instr->m_opcode == Js::OpCode::ArgOut_A_Inline && valueInfo->GetSymStore() &&
valueInfo->GetSymStore()->m_id == 0)
{
// We don't want to copy-prop s0 (return symbol) into inlinee code
return opnd;
}
}

// Constant prop?
int32 intConstantValue;
int64 int64ConstantValue;
Expand Down
2 changes: 0 additions & 2 deletions lib/Backend/Inline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1399,8 +1399,6 @@ bool
Inline::TryOptimizeCallInstrWithFixedMethod(IR::Instr *callInstr, const FunctionJITTimeInfo * inlineeInfo, bool isPolymorphic, bool isBuiltIn, bool isCtor, bool isInlined, bool &safeThis,
bool dontOptimizeJustCheck, uint i /*i-th inlinee at a polymorphic call site*/)
{
Assert(!callInstr->m_func->GetJITFunctionBody()->HasTry());

if (PHASE_OFF(Js::FixedMethodsPhase, callInstr->m_func))
{
return false;
Expand Down
19 changes: 0 additions & 19 deletions lib/Backend/InliningDecider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,6 @@ bool InliningDecider::InlineIntoTopFunc() const
return false;
}

if (this->topFunc->GetHasTry())
{
#if defined(DBG_DUMP) || defined(ENABLE_DEBUG_CONFIG_OPTIONS)
char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
#endif
INLINE_TESTTRACE(_u("INLINING: Skip Inline: Has try\tCaller: %s (%s)\n"), this->topFunc->GetDisplayName(),
this->topFunc->GetDebugNumberSet(debugStringBuffer));
// Glob opt doesn't run on function with try, so we can't generate bailout for it
return false;
}

return InlineIntoInliner(topFunc);
}

Expand Down Expand Up @@ -218,14 +207,6 @@ Js::FunctionInfo *InliningDecider::Inline(Js::FunctionBody *const inliner, Js::F
return nullptr;
}

if (inlinee->GetHasTry())
{
INLINE_TESTTRACE(_u("INLINING: Skip Inline: Has try\tInlinee: %s (%s)\tCaller: %s (%s)\n"),
inlinee->GetDisplayName(), inlinee->GetDebugNumberSet(debugStringBuffer), inliner->GetDisplayName(),
inliner->GetDebugNumberSet(debugStringBuffer2));
return nullptr;
}

// This is a hard limit as the argOuts array is statically sized.
if (inlinee->GetInParamsCount() > Js::InlineeCallInfo::MaxInlineeArgoutCount)
{
Expand Down
8 changes: 8 additions & 0 deletions lib/Backend/LinearScan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1412,6 +1412,14 @@ LinearScan::FillBailOutRecord(IR::Instr * instr)
// To indicate this is a subsequent bailout from an inlinee
bailOutRecord->bailOutOpcode = Js::OpCode::InlineeEnd;
#endif
if (this->func->HasTry())
{
RegionType currentRegionType = this->currentRegion->GetType();
if (currentRegionType == RegionTypeTry || currentRegionType == RegionTypeCatch || currentRegionType == RegionTypeFinally)
{
bailOutRecord->ehBailoutData = this->currentRegion->ehBailoutData;
}
}
funcBailOutData[funcIndex].bailOutRecord->parent = bailOutRecord;
funcIndex--;
funcBailOutData[funcIndex].bailOutRecord = bailOutRecord;
Expand Down
8 changes: 7 additions & 1 deletion lib/Backend/Lower.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19995,7 +19995,13 @@ Lowerer::EHBailoutPatchUp()
this->InsertReturnThunkForRegion(this->currentRegion, restoreReturnValueFromBailoutLabel);
if (instr->HasBailOutInfo())
{
this->SetHasBailedOut(instr);
if (instr->GetBailOutInfo()->bailOutFunc == this->m_func)
{
// We dont set this bit for inlined code, if there was a bailout in the inlined code,
// and an exception was thrown, we want the caller's handler to handle the exception accordingly.
// TODO : Revisit when we start inlining functions with try-catch/try-finally
this->SetHasBailedOut(instr);
}
tmpInstr = this->EmitEHBailoutStackRestore(instr);
this->EmitSaveEHBailoutReturnValueAndJumpToRetThunk(tmpInstr);
if (!restoreReturnFromBailoutEmitted)
Expand Down
1 change: 1 addition & 0 deletions lib/Runtime/Base/ThreadContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ ThreadContext::ThreadContext(AllocationPolicyManager * allocationPolicyManager,
entryExitRecord(nullptr),
leafInterpreterFrame(nullptr),
threadServiceWrapper(nullptr),
tryCatchFrameAddr(nullptr),
temporaryArenaAllocatorCount(0),
temporaryGuestArenaAllocatorCount(0),
crefSContextForDiag(0),
Expand Down
5 changes: 4 additions & 1 deletion lib/Runtime/Base/ThreadContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -678,7 +678,7 @@ class ThreadContext sealed :
ThreadServiceWrapper* threadServiceWrapper;
uint functionCount;
uint sourceInfoCount;

void * tryCatchFrameAddr;
enum RedeferralState
{
InitialRedeferralState,
Expand Down Expand Up @@ -1253,6 +1253,9 @@ class ThreadContext sealed :
uint EnterScriptStart(Js::ScriptEntryExitRecord *, bool doCleanup);
void EnterScriptEnd(Js::ScriptEntryExitRecord *, bool doCleanup);

void * GetTryCatchFrameAddr() { return this->tryCatchFrameAddr; }
void SetTryCatchFrameAddr(void * frameAddr) { this->tryCatchFrameAddr = frameAddr; }

template <bool leaveForHost>
void LeaveScriptStart(void *);
template <bool leaveForHost>
Expand Down
10 changes: 9 additions & 1 deletion lib/Runtime/Language/EHBailoutData.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,15 @@ namespace Js
this->parent = parent;
this->child = nullptr;
}

EHBailoutData(EHBailoutData * other)
{
this->nestingDepth = other->nestingDepth;
this->catchOffset = other->catchOffset;
this->finallyOffset = other->finallyOffset;
this->ht = other->ht;
this->parent = other->parent;
this->child = nullptr;
}
#if ENABLE_NATIVE_CODEGEN
void Fixup(NativeCodeData::DataChunk* chunkList)
{
Expand Down
3 changes: 1 addition & 2 deletions lib/Runtime/Language/InterpreterStackFrame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3284,7 +3284,7 @@ namespace Js
} autoRestore(this);
#endif

if (this->ehBailoutData)
if (this->ehBailoutData && !(m_flags & InterpreterStackFrameFlags_FromInlineeCodeInEHBailOut))
{
if ((m_flags & Js::InterpreterStackFrameFlags_FromBailOut) && !(m_flags & InterpreterStackFrameFlags_ProcessingBailOutFromEHCode))
{
Expand Down Expand Up @@ -6752,7 +6752,6 @@ const byte * InterpreterStackFrame::OP_ProfiledLoopBodyStart(const byte * ip)
// Generator return scenario, so no need to go into the catch block and we must rethrow to propagate the exception to down level
JavascriptExceptionOperators::DoThrow(exception, scriptContext);
}

if (catchOffset != 0)
{
exception = exception->CloneIfStaticExceptionObject(scriptContext);
Expand Down
1 change: 1 addition & 0 deletions lib/Runtime/Language/InterpreterStackFrame.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ namespace Js
InterpreterStackFrameFlags_FromBailOut = 8,
InterpreterStackFrameFlags_ProcessingBailOutOnArrayAccessHelperCall = 0x10,
InterpreterStackFrameFlags_ProcessingBailOutFromEHCode = 0x20,
InterpreterStackFrameFlags_FromInlineeCodeInEHBailOut = 0x40,
InterpreterStackFrameFlags_All = 0xFFFF,
};

Expand Down
38 changes: 38 additions & 0 deletions lib/Runtime/Language/JavascriptExceptionOperators.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,18 @@ namespace Js
m_threadContext->SetIsUserCode(m_previousCatchHandlerToUserCodeStatus);
}

JavascriptExceptionOperators::TryCatchFrameAddrStack::TryCatchFrameAddrStack(ScriptContext* scriptContext, void *frameAddr)
{
m_threadContext = scriptContext->GetThreadContext();
m_prevTryCatchFrameAddr = m_threadContext->GetTryCatchFrameAddr();
scriptContext->GetThreadContext()->SetTryCatchFrameAddr(frameAddr);
}

JavascriptExceptionOperators::TryCatchFrameAddrStack::~TryCatchFrameAddrStack()
{
m_threadContext->SetTryCatchFrameAddr(m_prevTryCatchFrameAddr);
}

bool JavascriptExceptionOperators::CrawlStackForWER(Js::ScriptContext& scriptContext)
{
return Js::Configuration::Global.flags.WERExceptionSupport && !scriptContext.GetThreadContext()->HasCatchHandler();
Expand Down Expand Up @@ -87,6 +99,7 @@ namespace Js
try
{
Js::JavascriptExceptionOperators::AutoCatchHandlerExists autoCatchHandlerExists(scriptContext);
Js::JavascriptExceptionOperators::TryCatchFrameAddrStack tryCatchFrameAddrStack(scriptContext, frame);
continuation = amd64_CallWithFakeFrame(tryAddr, frame, spillSize, argsSize);
}
catch (const Js::JavascriptException& err)
Expand Down Expand Up @@ -215,6 +228,7 @@ namespace Js
try
{
Js::JavascriptExceptionOperators::AutoCatchHandlerExists autoCatchHandlerExists(scriptContext);
Js::JavascriptExceptionOperators::TryCatchFrameAddrStack tryCatchFrameAddrStack(scriptContext, framePtr);
#if defined(_M_ARM)
continuation = arm_CallEhFrame(tryAddr, framePtr, localsPtr, argsSize);
#elif defined(_M_ARM64)
Expand Down Expand Up @@ -365,6 +379,7 @@ namespace Js
try
{
Js::JavascriptExceptionOperators::AutoCatchHandlerExists autoCatchHandlerExists(scriptContext);
Js::JavascriptExceptionOperators::TryCatchFrameAddrStack tryCatchFrameAddrStack(scriptContext, framePtr);

// Adjust the frame pointer and call into the try.
// If the try completes without raising an exception, it will pass back the continuation address.
Expand Down Expand Up @@ -875,7 +890,16 @@ namespace Js

JavascriptExceptionOperators::ThrowExceptionObject(exceptionObject, scriptContext, /*considerPassingToDebugger=*/ true, /*returnAddress=*/ nullptr, resetStack);
}
#if ENABLE_NATIVE_CODEGEN
void JavascriptExceptionOperators::WalkStackForCleaningUpInlineeInfo(ScriptContext *scriptContext, PVOID returnAddress)
{
JavascriptStackWalker walker(scriptContext, /*useEERContext*/ true, returnAddress);

// We have to walk the inlinee frames and clear callinfo count on them on an exception
// At this point inlinedFrameWalker is closed, so we should build it again by calling InlinedFrameWalker::FromPhysicalFrame
walker.WalkAndClearInlineeFrameCallInfoOnException();
}
#endif
void
JavascriptExceptionOperators::WalkStackForExceptionContext(ScriptContext& scriptContext, JavascriptExceptionContext& exceptionContext, Var thrownObject, uint64 stackCrawlLimit, PVOID returnAddress, bool isThrownException, bool resetSatck)
{
Expand Down Expand Up @@ -1090,6 +1114,20 @@ namespace Js
WalkStackForExceptionContext(*scriptContext, exceptionContext, thrownObject, StackCrawlLimitOnThrow(thrownObject, *scriptContext), returnAddress, /*isThrownException=*/ true, resetStack);
exceptionObject->FillError(exceptionContext, scriptContext);
AddStackTraceToObject(thrownObject, exceptionContext.GetStackTrace(), *scriptContext, /*isThrownException=*/ true, resetStack);

// We need to clear callinfo on inlinee virtual frames on an exception.
// We now allow inlining of functions into callers that have try-catch/try-finally.
// When there is an exception inside the inlinee with caller having a try-catch, clear the inlinee callinfo by walking the stack.
// If not, we might have the try-catch inside a loop, and when we execute the loop next time in the interpreter on BailOnException,
// we will see inlined frames as being present even though they are not, because we depend on FrameInfo's callinfo to tell if an inlinee is on the stack,
// and we haven't cleared those bits due to the exception

#if ENABLE_NATIVE_CODEGEN
if (scriptContext->GetThreadContext()->GetTryCatchFrameAddr() != nullptr)
{
WalkStackForCleaningUpInlineeInfo(scriptContext, returnAddress);
}
#endif
}
Assert(!scriptContext ||
// If we disabled implicit calls and we did record an implicit call, do not throw.
Expand Down
14 changes: 14 additions & 0 deletions lib/Runtime/Language/JavascriptExceptionOperators.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,17 @@ namespace Js
~AutoCatchHandlerExists();
};

class TryCatchFrameAddrStack
{
private:
void * m_prevTryCatchFrameAddr;
ThreadContext* m_threadContext;

public:
TryCatchFrameAddrStack(ScriptContext* scriptContext, void *frameAddr);
~TryCatchFrameAddrStack();
};

static void __declspec(noreturn) OP_Throw(Var object, ScriptContext* scriptContext);
static void __declspec(noreturn) Throw(Var object, ScriptContext* scriptContext);
static void __declspec(noreturn) ThrowExceptionObject(Js::JavascriptExceptionObject* exceptionObject, ScriptContext* scriptContext, bool considerPassingToDebugger = false, PVOID returnAddress = NULL, bool resetStack = false);
Expand Down Expand Up @@ -80,6 +91,9 @@ namespace Js
static Var ThrowTypeErrorRestrictedPropertyAccessor(RecyclableObject* function, CallInfo callInfo, ...);
static Var StackTraceAccessor(RecyclableObject* function, CallInfo callInfo, ...);
static void WalkStackForExceptionContext(ScriptContext& scriptContext, JavascriptExceptionContext& exceptionContext, Var thrownObject, uint64 stackCrawlLimit, PVOID returnAddress, bool isThrownException = true, bool resetSatck = false);
#if ENABLE_NATIVE_CODEGEN
static void WalkStackForCleaningUpInlineeInfo(ScriptContext *scriptContext, PVOID returnAddress);
#endif
static void AddStackTraceToObject(Var obj, JavascriptExceptionContext::StackTrace* stackTrace, ScriptContext& scriptContext, bool isThrownException = true, bool resetSatck = false);
static uint64 StackCrawlLimitOnThrow(Var thrownObject, ScriptContext& scriptContext);

Expand Down
30 changes: 29 additions & 1 deletion lib/Runtime/Language/JavascriptStackWalker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -616,7 +616,35 @@ namespace Js
}
return nullptr;
}

#if ENABLE_NATIVE_CODEGEN
void JavascriptStackWalker::WalkAndClearInlineeFrameCallInfoOnException()
{
// Walk the stack and when we find the first JavascriptFrame, we clear the inlinee's callinfo for this frame
// It is sufficient we stop at the first Javascript frame which had the enclosing try-catch
// TODO : Revisit when we start inlining functions with try-catch/try-finally
while (this->Walk(true))
{
if (this->IsJavascriptFrame())
{
if (!this->isNativeLibraryFrame)
{
if (HasInlinedFramesOnStack())
{
for (int index = inlinedFrameWalker.GetFrameCount() - 1; index >= 0; index--)
{
auto inlinedFrame = inlinedFrameWalker.GetFrameAtIndex(index);
inlinedFrame->callInfo.Clear();
}
}
if (this->currentFrame.GetFrame() == this->scriptContext->GetThreadContext()->GetTryCatchFrameAddr())
{
break;
}
}
}
}
}
#endif
// Note: noinline is to make sure that when we unwind to the unwindToAddress, there is at least one frame to unwind.
_NOINLINE
JavascriptStackWalker::JavascriptStackWalker(ScriptContext * scriptContext, bool useEERContext, PVOID returnAddress, bool _forceFullWalk /*=false*/) :
Expand Down
8 changes: 6 additions & 2 deletions lib/Runtime/Language/JavascriptStackWalker.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ namespace Js
uint32 GetBottomMostInlineeOffset() const;
Js::JavascriptFunction *GetBottomMostFunctionObject() const;
void FinalizeStackValues(__in_ecount(argCount) Js::Var args[], size_t argCount) const;
int32 GetFrameCount() { return frameCount; }

private:
enum {
Expand All @@ -135,11 +136,13 @@ namespace Js

};

void Initialize(int32 frameCount, __in_ecount(frameCount) InlinedFrame **frames, Js::ScriptFunction *parent);
public:
InlinedFrame *const GetFrameAtIndex(signed index) const;

private:
void Initialize(int32 frameCount, __in_ecount(frameCount) InlinedFrame **frames, Js::ScriptFunction *parent);
void MoveNext();
InlinedFrame *const GetCurrentFrame() const;
InlinedFrame *const GetFrameAtIndex(signed index) const;

Js::ScriptFunction *parentFunction;
InlinedFrame **frames;
Expand Down Expand Up @@ -233,6 +236,7 @@ namespace Js
void ClearCachedInternalFrameInfo();
void SetCachedInternalFrameInfo(InternalFrameType frameType, JavascriptFunction* function, bool hasInlinedFramesOnStack, bool prevIntFrameIsFromBailout);
InternalFrameInfo GetCachedInternalFrameInfo() const { return this->lastInternalFrameInfo; }
void WalkAndClearInlineeFrameCallInfoOnException();
#endif
bool IsCurrentPhysicalFrameForLoopBody() const;

Expand Down
Loading