blob: 95f9e6bbdb09d234c9d9327d0189765cfd2ebda1 [file] [log] [blame]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft Corporation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#include "Backend.h"
#ifdef ENABLE_SCRIPT_DEBUGGING
#include "Debug/DebuggingFlags.h"
#include "Debug/DiagProbe.h"
#include "Debug/DebugManager.h"
#endif
// Parser includes
#include "RegexCommon.h"
#include "RegexPattern.h"
#include "ExternalLowerer.h"
#include "Types/DynamicObjectPropertyEnumerator.h"
#include "Types/JavascriptStaticEnumerator.h"
#include "Library/ForInObjectEnumerator.h"
///----------------------------------------------------------------------------
///
/// Lowerer::Lower
///
/// Lowerer's main entrypoint. Lowers this function..
///
///----------------------------------------------------------------------------
void
Lowerer::Lower()
{
this->m_func->StopMaintainByteCodeOffset();
NoRecoverMemoryJitArenaAllocator localAlloc(_u("BE-Lower"), this->m_func->m_alloc->GetPageAllocator(), Js::Throw::OutOfMemory);
this->m_alloc = &localAlloc;
BVSparse<JitArenaAllocator> localInitializedTempSym(&localAlloc);
this->initializedTempSym = &localInitializedTempSym;
BVSparse<JitArenaAllocator> localAddToLiveOnBackEdgeSyms(&localAlloc);
this->addToLiveOnBackEdgeSyms = &localAddToLiveOnBackEdgeSyms;
Assert(this->m_func->GetCloneMap() == nullptr);
m_lowererMD.Init(this);
bool defaultDoFastPath = this->m_func->DoFastPaths();
bool loopFastPath = this->m_func->DoLoopFastPaths();
if (m_func->HasAnyStackNestedFunc())
{
EnsureStackFunctionListStackSym();
}
if (m_func->DoStackFrameDisplay() && !m_func->IsLoopBody())
{
AllocStackClosure();
}
AllocStackForInObjectEnumeratorArray();
if (m_func->IsJitInDebugMode())
{
// Initialize metadata of local var slots.
// Too late to wait until Register Allocator, as we need the offset when lowerering bailout for debugger.
int32 hasLocalVarChangedOffset = m_func->GetHasLocalVarChangedOffset();
if (hasLocalVarChangedOffset != Js::Constants::InvalidOffset)
{
// MOV [EBP + m_func->GetHasLocalVarChangedOffset()], 0
StackSym* sym = StackSym::New(TyInt8, m_func);
sym->m_offset = hasLocalVarChangedOffset;
sym->m_allocated = true;
IR::Opnd* opnd1 = IR::SymOpnd::New(sym, TyInt8, m_func);
IR::Opnd* opnd2 = IR::IntConstOpnd::New(0, TyInt8, m_func);
Lowerer::InsertMove(opnd1, opnd2, m_func->GetFunctionEntryInsertionPoint());
#ifdef DBG
// Pre-fill all local slots with a pattern. This will help identify non-initialized/garbage var values.
// Note that in the beginning of the function in bytecode we should initialize all locals to undefined.
uint32 localSlotCount = m_func->GetJITFunctionBody()->GetEndNonTempLocalIndex() - m_func->GetJITFunctionBody()->GetFirstNonTempLocalIndex();
for (uint i = 0; i < localSlotCount; ++i)
{
int offset = m_func->GetLocalVarSlotOffset(i);
IRType opnd1Type;
#if defined(TARGET_32)
opnd1Type = TyInt32;
opnd2 = IR::IntConstOpnd::New(Func::c_debugFillPattern4, opnd1Type, m_func);
#else
opnd1Type = TyInt64;
opnd2 = IR::IntConstOpnd::New(Func::c_debugFillPattern8, opnd1Type, m_func);
#endif
sym = StackSym::New(opnd1Type, m_func);
sym->m_offset = offset;
sym->m_allocated = true;
opnd1 = IR::SymOpnd::New(sym, opnd1Type, m_func);
Lowerer::InsertMove(opnd1, opnd2, m_func->GetFunctionEntryInsertionPoint());
}
#endif
}
Assert(!m_func->HasAnyStackNestedFunc());
}
this->LowerRange(m_func->m_headInstr, m_func->m_tailInstr, defaultDoFastPath, loopFastPath);
#if DBG && GLOBAL_ENABLE_WRITE_BARRIER
// TODO: (leish)(swb) implement for arm
#if defined(_M_IX86) || defined(_M_AMD64)
if (CONFIG_FLAG(ForceSoftwareWriteBarrier) && CONFIG_FLAG(VerifyBarrierBit))
{
// find out all write barrier setting instr, call Recycler::WBSetBit for verification purpose
// should do this in LowererMD::GenerateWriteBarrier, however, can't insert call instruction there
FOREACH_INSTR_EDITING(instr, instrNext, m_func->m_headInstr)
if (instr->m_src1 && instr->m_src1->IsAddrOpnd())
{
IR::AddrOpnd* addrOpnd = instr->m_src1->AsAddrOpnd();
if (addrOpnd->GetAddrOpndKind() == IR::AddrOpndKindWriteBarrierCardTable)
{
auto& leaInstr = instr->m_prev->m_prev->m_prev;
auto& movInstr = instr->m_prev->m_prev;
auto& shrInstr = instr->m_prev;
Assert(leaInstr->m_opcode == Js::OpCode::LEA);
Assert(movInstr->m_opcode == Js::OpCode::MOV);
Assert(shrInstr->m_opcode == Js::OpCode::SHR);
m_lowererMD.LoadHelperArgument(movInstr, leaInstr->m_dst);
IR::Instr* instrCall = IR::Instr::New(Js::OpCode::Call, m_func);
movInstr->InsertBefore(instrCall);
m_lowererMD.ChangeToHelperCall(instrCall, IR::HelperWriteBarrierSetVerifyBit);
}
}
NEXT_INSTR_EDITING
}
#endif
#endif
this->m_func->ClearCloneMap();
if (m_func->HasAnyStackNestedFunc())
{
EnsureZeroLastStackFunctionNext();
}
if (!m_func->IsSimpleJit())
{
#if 0 // TODO michhol oop jit, reenable assert
Js::EntryPointInfo* entryPointInfo = this->m_func->m_workItem->GetEntryPoint();
Assert(entryPointInfo->GetJitTransferData() != nullptr && !entryPointInfo->GetJitTransferData()->GetIsReady());
#endif
}
this->initializedTempSym = nullptr;
this->m_alloc = nullptr;
this->m_func->DisableConstandAddressLoadHoist();
}
void
Lowerer::LowerRange(IR::Instr *instrStart, IR::Instr *instrEnd, bool defaultDoFastPath, bool defaultDoLoopFastPath)
{
bool noMathFastPath;
bool noFieldFastPath;
bool isStrictMode = this->m_func->GetJITFunctionBody()->IsStrictMode();
noFieldFastPath = !defaultDoFastPath;
noMathFastPath = !defaultDoFastPath;
#if DBG_DUMP
char16 * globOptInstrString = nullptr;
#endif
FOREACH_INSTR_BACKWARD_EDITING_IN_RANGE(instr, instrPrev, instrEnd, instrStart)
{
// Try to peep this`
instr = this->PreLowerPeepInstr(instr, &instrPrev);
#if DBG
IR::Instr * verifyLegalizeInstrNext = instr->m_next;
m_currentInstrOpCode = instr->m_opcode;
#endif
// If we have debugger bailout as part of real instr (not separate BailForDebugger instr),
// extract/split out BailOutForDebugger into separate instr, if needed.
// The instr can have just debugger bailout, or debugger bailout + other shared bailout.
// Note that by the time we get here, we should not have aux-only bailout (in globopt we promote it to normal bailout).
if (m_func->IsJitInDebugMode() && instr->HasBailOutInfo() &&
(((instr->GetBailOutKind() & IR::BailOutForDebuggerBits) && instr->m_opcode != Js::OpCode::BailForDebugger) ||
instr->HasAuxBailOut()))
{
instr = this->SplitBailForDebugger(instr); // Change instr, as returned is the one we need to lower next.
instrPrev = instr->m_prev; // Change just in case if instr got changed.
}
#if DBG_DUMP
if (!instr->IsLowered() && !instr->IsLabelInstr()
&& (CONFIG_FLAG(ForcePostLowerGlobOptInstrString) ||
PHASE_DUMP(Js::LowererPhase, m_func) ||
PHASE_DUMP(Js::LinearScanPhase, m_func) ||
PHASE_DUMP(Js::RegAllocPhase, m_func) ||
PHASE_DUMP(Js::PeepsPhase, m_func) ||
PHASE_DUMP(Js::LayoutPhase, m_func) ||
PHASE_DUMP(Js::EmitterPhase, m_func) ||
PHASE_DUMP(Js::EncoderPhase, m_func) ||
PHASE_DUMP(Js::BackEndPhase, m_func)))
{
if(instr->m_next && instr->m_next->m_opcode != Js::OpCode::StatementBoundary && !instr->m_next->IsLabelInstr())
{
instr->m_next->globOptInstrString = globOptInstrString;
}
globOptInstrString = instr->DumpString();
}
#endif
if (instr->IsBranchInstr() && !instr->AsBranchInstr()->IsMultiBranch() && instr->AsBranchInstr()->GetTarget()->m_isLoopTop)
{
Loop * loop = instr->AsBranchInstr()->GetTarget()->GetLoop();
if (this->outerMostLoopLabel == nullptr && !loop->isProcessed)
{
while (loop && loop->GetLoopTopInstr()) // some loops are optimized away so that they are not loops anymore.
// They do, however, stay in the loop graph but don't have loop top labels assigned to them
{
this->outerMostLoopLabel = loop->GetLoopTopInstr();
Assert(this->outerMostLoopLabel->m_isLoopTop);
// landing pad must fall through to the loop
Assert(this->outerMostLoopLabel->m_prev->HasFallThrough());
loop = loop->parent;
}
this->initializedTempSym->ClearAll();
}
noFieldFastPath = !defaultDoLoopFastPath;
noMathFastPath = !defaultDoLoopFastPath;
}
#ifdef INLINE_CACHE_STATS
if(PHASE_STATS1(Js::PolymorphicInlineCachePhase))
{
// Always use the slow path, so we can track property accesses
noFieldFastPath = true;
}
#endif
#if DBG
if (instr->HasBailOutInfo())
{
IR::BailOutKind bailoutKind = instr->GetBailOutKind();
if (BailOutInfo::IsBailOutOnImplicitCalls(bailoutKind))
{
this->helperCallCheckState = (HelperCallCheckState)(this->helperCallCheckState | HelperCallCheckState_ImplicitCallsBailout);
}
if ((bailoutKind & IR::BailOutOnArrayAccessHelperCall) != 0 &&
instr->m_opcode != Js::OpCode::Memcopy &&
instr->m_opcode != Js::OpCode::Memset)
{
this->helperCallCheckState = (HelperCallCheckState)(this->helperCallCheckState | HelperCallCheckState_NoHelperCalls);
}
}
#endif
switch (instr->m_opcode)
{
case Js::OpCode::LdHandlerScope:
this->LowerUnaryHelperMem(instr, IR::HelperScrObj_LdHandlerScope);
break;
case Js::OpCode::InitSetFld:
instrPrev = this->LowerStFld(instr, IR::HelperOP_InitSetter, IR::HelperOP_InitSetter, false);
break;
case Js::OpCode::InitGetFld:
instrPrev = this->LowerStFld(instr, IR::HelperOP_InitGetter, IR::HelperOP_InitGetter, false);
break;
case Js::OpCode::InitProto:
instrPrev = this->LowerStFld(instr, IR::HelperOP_InitProto, IR::HelperOP_InitProto, false);
break;
case Js::OpCode::LdArgCnt:
this->LoadArgumentCount(instr);
break;
case Js::OpCode::LdStackArgPtr:
this->LoadStackArgPtr(instr);
break;
case Js::OpCode::LdHeapArguments:
case Js::OpCode::LdLetHeapArguments:
instrPrev = m_lowererMD.LoadHeapArguments(instr);
break;
case Js::OpCode::LdHeapArgsCached:
case Js::OpCode::LdLetHeapArgsCached:
m_lowererMD.LoadHeapArgsCached(instr);
break;
case Js::OpCode::InvalCachedScope:
this->LowerBinaryHelper(instr, IR::HelperOP_InvalidateCachedScope);
break;
case Js::OpCode::InitCachedScope:
if (instr->m_func->GetJITFunctionBody()->GetDoScopeObjectCreation() || !instr->m_func->IsStackArgsEnabled())
{
instrPrev = this->LowerInitCachedScope(instr);
}
else
{
instr->ReplaceSrc1(IR::AddrOpnd::NewNull(instr->m_func));
instr->m_opcode = Js::OpCode::Ld_A;
instrPrev = instr;
if (PHASE_TRACE1(Js::StackArgFormalsOptPhase))
{
Output::Print(_u("StackArgFormals : %s (%d) :Removing Scope object creation in Lowerer and replacing it with MOV NULL. \n"), instr->m_func->GetJITFunctionBody()->GetDisplayName(), instr->m_func->GetFunctionNumber());
Output::Flush();
}
}
break;
case Js::OpCode::NewScopeObject:
{
Func * currFunc = instr->m_func;
if (currFunc->GetJITFunctionBody()->GetDoScopeObjectCreation() || !currFunc->IsStackArgsEnabled())
{
//Call Helper that creates scope object and does type transition for the formals
if (currFunc->IsStackArgsEnabled() && currFunc->GetJITFunctionBody()->GetInParamsCount() != 1)
{
// s3 = formals are let decls
this->m_lowererMD.LoadHelperArgument(instr, IR::IntConstOpnd::New(currFunc->GetHasNonSimpleParams() ? TRUE : FALSE, TyUint8, currFunc));
// s2 = current function.
IR::Opnd * paramOpnd = LoadFunctionBodyOpnd(instr);
this->m_lowererMD.LoadHelperArgument(instr, paramOpnd);
m_lowererMD.ChangeToHelperCallMem(instr, IR::HelperOP_NewScopeObjectWithFormals);
}
else
{
m_lowererMD.ChangeToHelperCallMem(instr, IR::HelperOP_NewScopeObject);
}
}
else
{
instr->SetSrc1(IR::AddrOpnd::NewNull(instr->m_func));
instr->m_opcode = Js::OpCode::Ld_A;
instrPrev = instr;
if (PHASE_TRACE1(Js::StackArgFormalsOptPhase))
{
Output::Print(_u("StackArgFormals : %s (%d) :Removing Scope object creation in Lowerer and replacing it with MOV NULL. \n"), currFunc->GetJITFunctionBody()->GetDisplayName(), currFunc->GetFunctionNumber());
Output::Flush();
}
}
break;
}
case Js::OpCode::NewStackScopeSlots:
this->LowerNewScopeSlots(instr, m_func->DoStackScopeSlots());
break;
case Js::OpCode::NewScopeSlots:
this->LowerNewScopeSlots(instr, false);
break;
case Js::OpCode::InitLocalClosure:
// Real initialization of the stack pointers happens on entry to the function, so this instruction
// (which exists to provide a def in the IR) can go away.
instr->Remove();
break;
case Js::OpCode::NewScopeSlotsWithoutPropIds:
this->LowerBinaryHelperMemWithFuncBody(instr, IR::HelperOP_NewScopeSlotsWithoutPropIds);
break;
case Js::OpCode::NewBlockScope:
m_lowererMD.ChangeToHelperCallMem(instr, IR::HelperOP_NewBlockScope);
break;
case Js::OpCode::NewPseudoScope:
m_lowererMD.ChangeToHelperCallMem(instr, IR::HelperOP_NewPseudoScope);
break;
case Js::OpCode::CloneInnerScopeSlots:
this->LowerUnaryHelperMem(instr, IR::HelperOP_CloneInnerScopeSlots);
break;
case Js::OpCode::CloneBlockScope:
this->LowerUnaryHelperMem(instr, IR::HelperOP_CloneBlockScope);
break;
case Js::OpCode::GetCachedFunc:
this->LowerGetCachedFunc(instr);
break;
case Js::OpCode::BrFncCachedScopeEq:
case Js::OpCode::BrFncCachedScopeNeq:
this->LowerBrFncCachedScopeEq(instr);
break;
case Js::OpCode::CommitScope:
this->LowerCommitScope(instr);
break;
case Js::OpCode::LdFldForTypeOf:
instrPrev = GenerateCompleteLdFld<false>(instr, !noFieldFastPath, IR::HelperOp_PatchGetValueForTypeOf, IR::HelperOp_PatchGetValuePolymorphicForTypeOf,
IR::HelperOp_PatchGetValueForTypeOf, IR::HelperOp_PatchGetValuePolymorphicForTypeOf);
break;
case Js::OpCode::LdFld:
case Js::OpCode::LdFldForCallApplyTarget:
instrPrev = GenerateCompleteLdFld<false>(instr, !noFieldFastPath, IR::HelperOp_PatchGetValue, IR::HelperOp_PatchGetValuePolymorphic,
IR::HelperOp_PatchGetValue, IR::HelperOp_PatchGetValuePolymorphic);
break;
case Js::OpCode::LdSuperFld:
instrPrev = GenerateCompleteLdFld<false>(instr, !noFieldFastPath, IR::HelperOp_PatchGetValueWithThisPtr, IR::HelperOp_PatchGetValuePolymorphicWithThisPtr,
IR::HelperOp_PatchGetValueWithThisPtr, IR::HelperOp_PatchGetValuePolymorphicWithThisPtr);
break;
case Js::OpCode::LdRootFld:
instrPrev = GenerateCompleteLdFld<true>(instr, !noFieldFastPath, IR::HelperOp_PatchGetRootValue, IR::HelperOp_PatchGetRootValuePolymorphic,
IR::HelperOp_PatchGetRootValue, IR::HelperOp_PatchGetRootValuePolymorphic);
break;
case Js::OpCode::LdRootFldForTypeOf:
instrPrev = GenerateCompleteLdFld<true>(instr, !noFieldFastPath, IR::HelperOp_PatchGetRootValueForTypeOf, IR::HelperOp_PatchGetRootValuePolymorphicForTypeOf,
IR::HelperOp_PatchGetRootValueForTypeOf, IR::HelperOp_PatchGetRootValuePolymorphicForTypeOf);
break;
case Js::OpCode::LdMethodFldPolyInlineMiss:
instrPrev = LowerLdFld(instr, IR::HelperOp_PatchGetMethod, IR::HelperOp_PatchGetMethodPolymorphic, true, nullptr, true);
break;
case Js::OpCode::LdMethodFld:
instrPrev = GenerateCompleteLdFld<false>(instr, !noFieldFastPath, IR::HelperOp_PatchGetMethod, IR::HelperOp_PatchGetMethodPolymorphic,
IR::HelperOp_PatchGetMethod, IR::HelperOp_PatchGetMethodPolymorphic);
break;
case Js::OpCode::LdRootMethodFld:
instrPrev = GenerateCompleteLdFld<true>(instr, !noFieldFastPath, IR::HelperOp_PatchGetRootMethod, IR::HelperOp_PatchGetRootMethodPolymorphic,
IR::HelperOp_PatchGetRootMethod, IR::HelperOp_PatchGetRootMethodPolymorphic);
break;
case Js::OpCode::ScopedLdMethodFld:
// "Scoped" in ScopedLdMethodFld is a bit of a misnomer because it doesn't look through a scope chain.
// Instead the op is to allow for either a LdRootMethodFld or LdMethodFld depending on whether the
// object is the root object or not.
instrPrev = GenerateCompleteLdFld<false>(instr, !noFieldFastPath, IR::HelperOp_ScopedGetMethod, IR::HelperOp_ScopedGetMethodPolymorphic,
IR::HelperOp_ScopedGetMethod, IR::HelperOp_ScopedGetMethodPolymorphic);
break;
case Js::OpCode::LdMethodFromFlags:
{
Assert(instr->HasBailOutInfo());
bool success = GenerateFastLdMethodFromFlags(instr);
AssertMsg(success, "Not expected to generate helper block here");
break;
}
case Js::OpCode::CheckFixedFld:
AssertMsg(!PHASE_OFF(Js::FixedMethodsPhase, instr->m_func) || !PHASE_OFF(Js::UseFixedDataPropsPhase, instr->m_func), "CheckFixedFld with fixed prop(Data|Method) phase disabled?");
this->GenerateCheckFixedFld(instr);
break;
case Js::OpCode::CheckPropertyGuardAndLoadType:
instrPrev = this->GeneratePropertyGuardCheckBailoutAndLoadType(instr);
break;
case Js::OpCode::CheckObjType:
this->GenerateCheckObjType(instr);
break;
case Js::OpCode::AdjustObjType:
case Js::OpCode::AdjustObjTypeReloadAuxSlotPtr:
this->LowerAdjustObjType(instr);
break;
case Js::OpCode::DeleteFld:
instrPrev = this->LowerDelFld(instr, IR::HelperOp_DeleteProperty, false, false);
break;
case Js::OpCode::DeleteRootFld:
instrPrev = this->LowerDelFld(instr, IR::HelperOp_DeleteRootProperty, false, false);
break;
case Js::OpCode::DeleteFldStrict:
instrPrev = this->LowerDelFld(instr, IR::HelperOp_DeleteProperty, false, true);
break;
case Js::OpCode::DeleteRootFldStrict:
instrPrev = this->LowerDelFld(instr, IR::HelperOp_DeleteRootProperty, false, true);
break;
case Js::OpCode::ScopedLdFldForTypeOf:
if (!noFieldFastPath)
{
m_lowererMD.GenerateFastScopedLdFld(instr);
}
instrPrev = this->LowerScopedLdFld(instr, IR::HelperOp_PatchGetPropertyForTypeOfScoped, true);
break;
case Js::OpCode::ScopedLdFld:
if (!noFieldFastPath)
{
m_lowererMD.GenerateFastScopedLdFld(instr);
}
instrPrev = this->LowerScopedLdFld(instr, IR::HelperOp_PatchGetPropertyScoped, true);
break;
case Js::OpCode::ScopedLdInst:
instrPrev = this->LowerScopedLdInst(instr, IR::HelperOp_GetInstanceScoped);
break;
case Js::OpCode::ScopedDeleteFld:
instrPrev = this->LowerScopedDelFld(instr, IR::HelperOp_DeletePropertyScoped, false, false);
break;
case Js::OpCode::ScopedDeleteFldStrict:
instrPrev = this->LowerScopedDelFld(instr, IR::HelperOp_DeletePropertyScoped, false, true);
break;
case Js::OpCode::NewScFunc:
instrPrev = this->LowerNewScFunc(instr);
break;
case Js::OpCode::NewScFuncHomeObj:
instrPrev = this->LowerNewScFuncHomeObj(instr);
break;
case Js::OpCode::NewScGenFunc:
instrPrev = this->LowerNewScGenFunc(instr);
break;
case Js::OpCode::NewScGenFuncHomeObj:
instrPrev = this->LowerNewScGenFuncHomeObj(instr);
break;
case Js::OpCode::StFld:
instrPrev = GenerateCompleteStFld(instr, !noFieldFastPath, IR::HelperOp_PatchPutValueNoLocalFastPath, IR::HelperOp_PatchPutValueNoLocalFastPathPolymorphic,
IR::HelperOp_PatchPutValue, IR::HelperOp_PatchPutValuePolymorphic, true, Js::PropertyOperation_None);
break;
case Js::OpCode::StSuperFld:
instrPrev = GenerateCompleteStFld(instr, !noFieldFastPath, IR::HelperOp_PatchPutValueWithThisPtrNoLocalFastPath, IR::HelperOp_PatchPutValueWithThisPtrNoLocalFastPathPolymorphic,
IR::HelperOp_PatchPutValueWithThisPtr, IR::HelperOp_PatchPutValueWithThisPtrPolymorphic, true, isStrictMode ? Js::PropertyOperation_StrictMode : Js::PropertyOperation_None);
break;
case Js::OpCode::StRootFld:
instrPrev = GenerateCompleteStFld(instr, !noFieldFastPath, IR::HelperOp_PatchPutRootValueNoLocalFastPath, IR::HelperOp_PatchPutRootValueNoLocalFastPathPolymorphic,
IR::HelperOp_PatchPutRootValue, IR::HelperOp_PatchPutRootValuePolymorphic, true, Js::PropertyOperation_Root);
break;
case Js::OpCode::StFldStrict:
instrPrev = GenerateCompleteStFld(instr, !noFieldFastPath, IR::HelperOp_PatchPutValueNoLocalFastPath, IR::HelperOp_PatchPutValueNoLocalFastPathPolymorphic,
IR::HelperOp_PatchPutValue, IR::HelperOp_PatchPutValuePolymorphic, true, Js::PropertyOperation_StrictMode);
break;
case Js::OpCode::StRootFldStrict:
instrPrev = GenerateCompleteStFld(instr, !noFieldFastPath, IR::HelperOp_PatchPutRootValueNoLocalFastPath, IR::HelperOp_PatchPutRootValueNoLocalFastPathPolymorphic,
IR::HelperOp_PatchPutRootValue, IR::HelperOp_PatchPutRootValuePolymorphic, true, Js::PropertyOperation_StrictModeRoot);
break;
case Js::OpCode::InitFld:
case Js::OpCode::InitRootFld:
instrPrev = GenerateCompleteStFld(instr, !noFieldFastPath, IR::HelperOp_PatchInitValue, IR::HelperOp_PatchInitValuePolymorphic,
IR::HelperOp_PatchInitValue, IR::HelperOp_PatchInitValuePolymorphic, false, Js::PropertyOperation_None);
break;
case Js::OpCode::ScopedInitFunc:
instrPrev = this->LowerScopedStFld(instr, IR::HelperOp_InitFuncScoped, false);
break;
case Js::OpCode::ScopedStFld:
case Js::OpCode::ScopedStFldStrict:
if (!noFieldFastPath)
{
m_lowererMD.GenerateFastScopedStFld(instr);
}
instrPrev = this->LowerScopedStFld(instr, IR::HelperOp_PatchSetPropertyScoped, true, true,
instr->m_opcode == Js::OpCode::ScopedStFld ? Js::PropertyOperation_None : Js::PropertyOperation_StrictMode);
break;
case Js::OpCode::ConsoleScopedStFld:
case Js::OpCode::ConsoleScopedStFldStrict:
{
if (!noFieldFastPath)
{
m_lowererMD.GenerateFastScopedStFld(instr);
}
Js::PropertyOperationFlags flags = static_cast<Js::PropertyOperationFlags>((instr->m_opcode == Js::OpCode::ConsoleScopedStFld ? Js::PropertyOperation_None : Js::PropertyOperation_StrictMode) | Js::PropertyOperation_AllowUndeclInConsoleScope);
instrPrev = this->LowerScopedStFld(instr, IR::HelperOp_ConsolePatchSetPropertyScoped, true, true, flags);
break;
}
case Js::OpCode::LdStr:
m_lowererMD.ChangeToAssign(instr);
break;
case Js::OpCode::CloneStr:
{
GenerateGetImmutableOrScriptUnreferencedString(instr->GetSrc1()->AsRegOpnd(), instr, IR::HelperOp_CompoundStringCloneForAppending, false);
instr->Remove();
break;
}
case Js::OpCode::NewScObjArray:
instrPrev = this->LowerNewScObjArray(instr);
break;
case Js::OpCode::NewScObject:
case Js::OpCode::NewScObjectSpread:
case Js::OpCode::NewScObjArraySpread:
instrPrev = this->LowerNewScObject(instr, true, true);
break;
case Js::OpCode::NewScObjectNoCtor:
instrPrev = this->LowerNewScObject(instr, false, true);
break;
case Js::OpCode::NewScObjectNoCtorFull:
instrPrev = this->LowerNewScObject(instr, false, true, true);
break;
case Js::OpCode::GetNewScObject:
instrPrev = this->LowerGetNewScObject(instr);
break;
case Js::OpCode::UpdateNewScObjectCache:
instrPrev = instr->m_prev;
this->LowerUpdateNewScObjectCache(instr, instr->GetSrc2(), instr->GetSrc1(), true /* isCtorFunction */);
instr->Remove();
break;
case Js::OpCode::NewScObjectSimple:
this->LowerNewScObjectSimple(instr);
break;
case Js::OpCode::NewScObjectLiteral:
this->LowerNewScObjectLiteral(instr);
break;
case Js::OpCode::LdPropIds:
m_lowererMD.ChangeToAssign(instr);
break;
case Js::OpCode::StArrSegItem_A:
instrPrev = this->LowerArraySegmentVars(instr);
break;
case Js::OpCode::InlineMathAcos:
m_lowererMD.GenerateFastInlineBuiltInCall(instr, IR::HelperDirectMath_Acos);
break;
case Js::OpCode::InlineMathAsin:
m_lowererMD.GenerateFastInlineBuiltInCall(instr, IR::HelperDirectMath_Asin);
break;
case Js::OpCode::InlineMathAtan:
m_lowererMD.GenerateFastInlineBuiltInCall(instr, IR::HelperDirectMath_Atan);
break;
case Js::OpCode::InlineMathAtan2:
m_lowererMD.GenerateFastInlineBuiltInCall(instr, IR::HelperDirectMath_Atan2);
break;
case Js::OpCode::InlineMathCos:
m_lowererMD.GenerateFastInlineBuiltInCall(instr, IR::HelperDirectMath_Cos);
break;
case Js::OpCode::InlineMathExp:
m_lowererMD.GenerateFastInlineBuiltInCall(instr, IR::HelperDirectMath_Exp);
break;
case Js::OpCode::InlineMathLog:
m_lowererMD.GenerateFastInlineBuiltInCall(instr, IR::HelperDirectMath_Log);
break;
case Js::OpCode::InlineMathPow:
m_lowererMD.GenerateFastInlineBuiltInCall(instr, IR::HelperDirectMath_Pow);
break;
case Js::OpCode::InlineMathSin:
m_lowererMD.GenerateFastInlineBuiltInCall(instr, IR::HelperDirectMath_Sin);
break;
case Js::OpCode::InlineMathSqrt:
m_lowererMD.GenerateFastInlineBuiltInCall(instr, (IR::JnHelperMethod)0);
break;
case Js::OpCode::InlineMathTan:
m_lowererMD.GenerateFastInlineBuiltInCall(instr, IR::HelperDirectMath_Tan);
break;
case Js::OpCode::InlineMathFloor:
#if defined(ASMJS_PLAT) && (defined(_M_X64) || defined(_M_IX86))
if (!AutoSystemInfo::Data.SSE4_1Available() && instr->m_func->GetJITFunctionBody()->IsAsmJsMode())
{
m_lowererMD.HelperCallForAsmMathBuiltin(instr, IR::HelperDirectMath_FloorFlt, IR::HelperDirectMath_FloorDb);
break;
}
#endif
m_lowererMD.GenerateFastInlineBuiltInCall(instr, (IR::JnHelperMethod)0);
break;
case Js::OpCode::InlineMathCeil:
#if defined(ASMJS_PLAT) && (defined(_M_X64) || defined(_M_IX86))
if (!AutoSystemInfo::Data.SSE4_1Available() && instr->m_func->GetJITFunctionBody()->IsAsmJsMode())
{
m_lowererMD.HelperCallForAsmMathBuiltin(instr, IR::HelperDirectMath_CeilFlt, IR::HelperDirectMath_CeilDb);
break;
}
#endif
m_lowererMD.GenerateFastInlineBuiltInCall(instr, (IR::JnHelperMethod)0);
break;
case Js::OpCode::InlineMathRound:
m_lowererMD.GenerateFastInlineBuiltInCall(instr, (IR::JnHelperMethod)0);
break;
case Js::OpCode::InlineMathAbs:
m_lowererMD.GenerateFastInlineBuiltInCall(instr, (IR::JnHelperMethod)0);
break;
case Js::OpCode::InlineMathImul:
GenerateFastInlineMathImul(instr);
break;
case Js::OpCode::Ctz:
GenerateCtz(instr);
break;
case Js::OpCode::PopCnt:
GeneratePopCnt(instr);
break;
case Js::OpCode::InlineMathClz:
GenerateFastInlineMathClz(instr);
break;
case Js::OpCode::InlineMathFround:
GenerateFastInlineMathFround(instr);
break;
case Js::OpCode::Reinterpret_Prim:
LowerReinterpretPrimitive(instr);
break;
case Js::OpCode::InlineMathMin:
case Js::OpCode::InlineMathMax:
m_lowererMD.GenerateFastInlineBuiltInCall(instr, (IR::JnHelperMethod)0);
break;
case Js::OpCode::InlineMathRandom:
this->GenerateFastInlineBuiltInMathRandom(instr);
break;
#ifdef ENABLE_DOM_FAST_PATH
case Js::OpCode::DOMFastPathGetter:
this->LowerFastInlineDOMFastPathGetter(instr);
break;
#endif
case Js::OpCode::InlineArrayPush:
this->GenerateFastInlineArrayPush(instr);
break;
case Js::OpCode::InlineArrayPop:
this->GenerateFastInlineArrayPop(instr);
break;
//Now retrieve the function object from the ArgOut_A_InlineSpecialized instruction opcode to push it on the stack after all the other arguments have been pushed.
//The lowering of the direct call to helper is handled by GenerateDirectCall (architecture specific).
case Js::OpCode::CallDirect:
{
IR::Opnd * src1 = instr->GetSrc1();
Assert(src1->IsHelperCallOpnd());
switch (src1->AsHelperCallOpnd()->m_fnHelper)
{
case IR::JnHelperMethod::HelperString_Split:
case IR::JnHelperMethod::HelperString_Match:
GenerateFastInlineStringSplitMatch(instr);
break;
case IR::JnHelperMethod::HelperRegExp_Exec:
GenerateFastInlineRegExpExec(instr);
break;
case IR::JnHelperMethod::HelperGlobalObject_ParseInt:
GenerateFastInlineGlobalObjectParseInt(instr);
break;
case IR::JnHelperMethod::HelperString_FromCharCode:
GenerateFastInlineStringFromCharCode(instr);
break;
case IR::JnHelperMethod::HelperString_FromCodePoint:
GenerateFastInlineStringFromCodePoint(instr);
break;
case IR::JnHelperMethod::HelperString_CharAt:
GenerateFastInlineStringCharCodeAt(instr, Js::BuiltinFunction::JavascriptString_CharAt);
break;
case IR::JnHelperMethod::HelperString_CharCodeAt:
GenerateFastInlineStringCharCodeAt(instr, Js::BuiltinFunction::JavascriptString_CharCodeAt);
break;
case IR::JnHelperMethod::HelperString_Replace:
GenerateFastInlineStringReplace(instr);
break;
case IR::JnHelperMethod::HelperObject_HasOwnProperty:
this->GenerateFastInlineHasOwnProperty(instr);
break;
case IR::JnHelperMethod::HelperArray_IsArray:
this->GenerateFastInlineIsArray(instr);
break;
}
instrPrev = LowerCallDirect(instr);
break;
}
case Js::OpCode::CallIDynamic:
{
Js::CallFlags flags = instr->GetDst() ? Js::CallFlags_Value : Js::CallFlags_NotUsed;
instrPrev = this->LowerCallIDynamic(instr, (ushort)flags);
break;
}
case Js::OpCode::CallIDynamicSpread:
{
Js::CallFlags flags = instr->GetDst() ? Js::CallFlags_Value : Js::CallFlags_NotUsed;
instrPrev = this->LowerCallIDynamicSpread(instr, (ushort)flags);
break;
}
case Js::OpCode::CallI:
case Js::OpCode::CallINew:
case Js::OpCode::CallIFixed:
case Js::OpCode::CallINewTargetNew:
{
Js::CallFlags flags = Js::CallFlags_None;
if (instr->isCtorCall)
{
flags = Js::CallFlags_New;
}
else
{
if (instr->m_opcode == Js::OpCode::CallINew)
{
flags = Js::CallFlags_New;
}
else if (instr->m_opcode == Js::OpCode::CallINewTargetNew)
{
flags = (Js::CallFlags) (Js::CallFlags_New | Js::CallFlags_ExtraArg | Js::CallFlags_NewTarget);
}
if (instr->GetDst())
{
flags = (Js::CallFlags) (flags | Js::CallFlags_Value);
}
else
{
flags = (Js::CallFlags) (flags | Js::CallFlags_NotUsed);
}
}
if (!PHASE_OFF(Js::CallFastPathPhase, this->m_func) && !noMathFastPath)
{
// We shouldn't have turned this instruction into a fixed method call if we're calling one of the
// built-ins we still inline in the lowerer.
Assert(instr->m_opcode != Js::OpCode::CallIFixed || !Func::IsBuiltInInlinedInLowerer(instr->GetSrc1()));
// Disable InlineBuiltInLibraryCall as it does not work well with 2nd chance reg alloc
// and may invalidate live on back edge data by introducing refs across loops. See Winblue Bug: 577641
//// Callee may still be a library built-in; if so, generate it inline.
//if (this->InlineBuiltInLibraryCall(instr))
//{
// m_lowererMD.LowerCallI(instr, (ushort)flags, true /*isHelper*/);
//}
//else
//{
m_lowererMD.LowerCallI(instr, (ushort)flags);
//}
}
else
{
m_lowererMD.LowerCallI(instr, (ushort)flags);
}
break;
}
case Js::OpCode::AsmJsCallI:
instrPrev = m_lowererMD.LowerAsmJsCallI(instr);
break;
case Js::OpCode::AsmJsCallE:
instrPrev = m_lowererMD.LowerAsmJsCallE(instr);
break;
case Js::OpCode::CallIEval:
{
Js::CallFlags flags = (Js::CallFlags)(Js::CallFlags_ExtraArg | (instr->GetDst() ? Js::CallFlags_Value : Js::CallFlags_NotUsed));
if (IsSpreadCall(instr))
{
instrPrev = LowerSpreadCall(instr, flags);
}
else
{
m_lowererMD.LowerCallI(instr, (ushort)flags);
}
#ifdef PERF_HINT
if (PHASE_TRACE1(Js::PerfHintPhase))
{
WritePerfHint(PerfHints::CallsEval, this->m_func, instr->GetByteCodeOffset());
}
#endif
break;
}
case Js::OpCode::CallHelper:
instrPrev = m_lowererMD.LowerCallHelper(instr);
break;
case Js::OpCode::Ret:
if (instr->m_next->m_opcode != Js::OpCode::FunctionExit)
{
// If this RET isn't at the end of the function, insert a branch to
// the epilog.
IR::Instr *exitPrev = m_func->m_exitInstr->m_prev;
if (!exitPrev->IsLabelInstr())
{
exitPrev = IR::LabelInstr::New(Js::OpCode::Label, m_func);
m_func->m_exitInstr->InsertBefore(exitPrev);
}
IR::BranchInstr *exitBr = IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode,
exitPrev->AsLabelInstr(), m_func);
instr->InsertAfter(exitBr);
}
m_lowererMD.LowerRet(instr);
break;
case Js::OpCode::LdArgumentsFromFrame:
this->LoadArgumentsFromFrame(instr);
break;
case Js::OpCode::LdC_A_I4:
{
IR::Opnd *src1 = instr->UnlinkSrc1();
AssertMsg(src1->IsIntConstOpnd(), "Source of LdC_A_I4 should be an IntConst...");
instrPrev = this->LowerLoadVar(instr,
IR::AddrOpnd::NewFromNumber(static_cast<int32>(src1->AsIntConstOpnd()->GetValue()), this->m_func));
src1->Free(this->m_func);
break;
}
case Js::OpCode::LdC_A_R8:
{
IR::Opnd *src1 = instr->UnlinkSrc1();
AssertMsg(src1->IsFloatConstOpnd(), "Source of LdC_A_R8 should be a FloatConst...");
instrPrev = this->LowerLoadVar(instr, src1->AsFloatConstOpnd()->GetAddrOpnd(this->m_func));
src1->Free(this->m_func);
break;
}
case Js::OpCode::LdC_F8_R8:
{
IR::Opnd *src1 = instr->UnlinkSrc1();
AssertMsg(src1->IsFloatConstOpnd() || src1->IsFloat32ConstOpnd(), "Source of LdC_F8_R8 should be a FloatConst...");
if (src1->IsFloatConstOpnd())
{
instrPrev = m_lowererMD.LoadFloatValue(instr->UnlinkDst()->AsRegOpnd(), src1->AsFloatConstOpnd()->m_value, instr);
}
else
{
instrPrev = m_lowererMD.LoadFloatValue(instr->UnlinkDst()->AsRegOpnd(), src1->AsFloat32ConstOpnd()->m_value, instr);
}
src1->Free(this->m_func);
instr->Remove();
break;
}
case Js::OpCode::NewRegEx:
instrPrev = this->LowerNewRegEx(instr);
break;
case Js::OpCode::Conv_Obj:
this->LowerUnaryHelperMem(instr, IR::HelperOp_ConvObject);
break;
case Js::OpCode::NewUnscopablesWrapperObject:
this->LowerUnaryHelperMem(instr, IR::HelperOp_NewUnscopablesWrapperObject);
break;
case Js::OpCode::LdCustomSpreadIteratorList:
this->LowerUnaryHelperMem(instr, IR::HelperOp_ToSpreadedFunctionArgument);
break;
case Js::OpCode::Conv_Numeric:
case Js::OpCode::Conv_Num:
this->LowerConvNum(instr, noMathFastPath);
break;
case Js::OpCode::Incr_Num_A:
case Js::OpCode::Incr_A:
if (PHASE_OFF(Js::MathFastPathPhase, this->m_func) || noMathFastPath)
{
this->LowerUnaryHelperMem(instr, IR::HelperOp_Increment);
}
else
{
instr->SetSrc2(IR::AddrOpnd::New(Js::TaggedInt::ToVarUnchecked(1), IR::AddrOpndKindConstantVar, this->m_func));
m_lowererMD.GenerateFastAdd(instr);
instr->FreeSrc2();
this->LowerUnaryHelperMemWithTemp2(instr, IR_HELPER_OP_FULL_OR_INPLACE(Increment));
}
break;
case Js::OpCode::Decr_Num_A:
case Js::OpCode::Decr_A:
if (PHASE_OFF(Js::MathFastPathPhase, this->m_func) || noMathFastPath)
{
this->LowerUnaryHelperMem(instr, IR::HelperOp_Decrement);
}
else
{
instr->SetSrc2(IR::AddrOpnd::New(Js::TaggedInt::ToVarUnchecked(1), IR::AddrOpndKindConstantVar, this->m_func));
m_lowererMD.GenerateFastSub(instr);
instr->FreeSrc2();
this->LowerUnaryHelperMemWithTemp2(instr, IR_HELPER_OP_FULL_OR_INPLACE(Decrement));
}
break;
case Js::OpCode::Neg_A:
if (instr->GetDst()->IsFloat())
{
Assert(instr->GetSrc1()->IsFloat());
m_lowererMD.LowerToFloat(instr);
}
else if (PHASE_OFF(Js::MathFastPathPhase, this->m_func) || noMathFastPath)
{
this->LowerUnaryHelperMem(instr, IR::HelperOp_Negate);
}
else if (m_lowererMD.GenerateFastNeg(instr))
{
this->LowerUnaryHelperMemWithTemp2(instr, IR_HELPER_OP_FULL_OR_INPLACE(Negate));
}
break;
case Js::OpCode::Not_A:
if (PHASE_OFF(Js::BitopsFastPathPhase, this->m_func) || noMathFastPath)
{
this->LowerUnaryHelperMem(instr, IR::HelperOp_Not);
}
else if (m_lowererMD.GenerateFastNot(instr))
{
this->LowerUnaryHelperMemWithTemp2(instr, IR_HELPER_OP_FULL_OR_INPLACE(Not));
}
break;
case Js::OpCode::BrEq_I4:
case Js::OpCode::BrNeq_I4:
case Js::OpCode::BrGt_I4:
case Js::OpCode::BrGe_I4:
case Js::OpCode::BrLt_I4:
case Js::OpCode::BrLe_I4:
case Js::OpCode::BrUnGt_I4:
case Js::OpCode::BrUnGe_I4:
case Js::OpCode::BrUnLt_I4:
case Js::OpCode::BrUnLe_I4:
{
// See calls to MarkOneFltTmpSym under BrSrEq. This is to handle the case
// where a branch is type-specialized and uses the result of a float pref op,
// which must then be saved to var at the def.
StackSym *sym = instr->GetSrc1()->GetStackSym();
if (sym)
{
sym = sym->GetVarEquivSym(nullptr);
}
sym = instr->GetSrc2()->GetStackSym();
if (sym)
{
sym = sym->GetVarEquivSym(nullptr);
}
}
// FALLTHROUGH
case Js::OpCode::Neg_I4:
case Js::OpCode::Not_I4:
case Js::OpCode::Add_I4:
case Js::OpCode::Sub_I4:
case Js::OpCode::Mul_I4:
case Js::OpCode::RemU_I4:
case Js::OpCode::Rem_I4:
case Js::OpCode::Or_I4:
case Js::OpCode::Xor_I4:
case Js::OpCode::And_I4:
case Js::OpCode::Shl_I4:
case Js::OpCode::Shr_I4:
case Js::OpCode::ShrU_I4:
case Js::OpCode::Rol_I4:
case Js::OpCode::Ror_I4:
case Js::OpCode::BrTrue_I4:
case Js::OpCode::BrFalse_I4:
#ifdef _M_IX86
if (
instr->GetDst() && instr->GetDst()->IsInt64() ||
instr->GetSrc1() && instr->GetSrc1()->IsInt64() ||
instr->GetSrc2() && instr->GetSrc2()->IsInt64()
)
{
m_lowererMD.EmitInt64Instr(instr);
break;
}
#endif
if (instr->HasBailOutInfo())
{
const auto bailOutKind = instr->GetBailOutKind();
if (bailOutKind & IR::BailOutOnResultConditions ||
bailOutKind == IR::BailOutOnFailedHoistedLoopCountBasedBoundCheck)
{
const auto nonBailOutInstr = SplitBailOnResultCondition(instr);
IR::LabelInstr *bailOutLabel, *skipBailOutLabel;
LowerBailOnResultCondition(instr, &bailOutLabel, &skipBailOutLabel);
LowerInstrWithBailOnResultCondition(nonBailOutInstr, bailOutKind, bailOutLabel, skipBailOutLabel);
}
else if (bailOutKind == IR::BailOnModByPowerOf2)
{
Assert(instr->m_opcode == Js::OpCode::Rem_I4);
bool fastPath = GenerateSimplifiedInt4Rem(instr);
Assert(fastPath);
instr->FreeSrc1();
instr->FreeSrc2();
this->GenerateBailOut(instr);
}
}
else
{
if (instr->m_opcode == Js::OpCode::Rem_I4 || instr->m_opcode == Js::OpCode::RemU_I4)
{
// fast path
this->GenerateSimplifiedInt4Rem(instr);
// slow path
this->LowerRemI4(instr);
}
#if defined(_M_IX86) || defined(_M_X64)
else if (instr->m_opcode == Js::OpCode::Mul_I4)
{
if (!LowererMD::GenerateSimplifiedInt4Mul(instr))
{
m_lowererMD.EmitInt4Instr(instr);
}
}
#endif
else
{
m_lowererMD.EmitInt4Instr(instr);
}
}
break;
case Js::OpCode::TrapIfMinIntOverNegOne:
LowerTrapIfMinIntOverNegOne(instr);
break;
case Js::OpCode::TrapIfTruncOverflow:
LowererMD::ChangeToAssign(instr);
break;
case Js::OpCode::TrapIfZero:
LowerTrapIfZero(instr);
break;
case Js::OpCode::TrapIfUnalignedAccess:
instrPrev = LowerTrapIfUnalignedAccess(instr);
break;
case Js::OpCode::DivU_I4:
case Js::OpCode::Div_I4:
this->LowerDivI4(instr);
break;
case Js::OpCode::Typeof:
m_lowererMD.LowerTypeof(instr);
break;
case Js::OpCode::TypeofElem:
this->LowerLdElemI(instr, IR::HelperOp_TypeofElem, false);
break;
case Js::OpCode::LdLen_A:
{
bool fastPath = !noMathFastPath;
if (!fastPath && instr->HasBailOutInfo())
{
// Some bailouts are generated around the helper call, and will work even if the fast path is disabled. Other
// bailouts require the fast path.
const IR::BailOutKind bailOutKind = instr->GetBailOutKind();
if (bailOutKind & IR::BailOutKindBits)
{
fastPath = true;
}
else
{
const IR::BailOutKind bailOutKindMinusBits = bailOutKind & ~IR::BailOutKindBits;
fastPath =
bailOutKindMinusBits &&
bailOutKindMinusBits != IR::BailOutOnImplicitCalls &&
bailOutKindMinusBits != IR::BailOutOnImplicitCallsPreOp;
}
}
bool instrIsInHelperBlock = false;
if (!fastPath)
{
LowerLdLen(instr, false);
}
else if (GenerateFastLdLen(instr, &instrIsInHelperBlock))
{
Assert(
!instr->HasBailOutInfo() ||
(instr->GetBailOutKind() & ~IR::BailOutKindBits) != IR::BailOutOnIrregularLength);
LowerLdLen(instr, instrIsInHelperBlock);
}
break;
}
case Js::OpCode::LdThis:
{
if (noFieldFastPath || !GenerateLdThisCheck(instr))
{
IR::JnHelperMethod meth;
if (instr->IsJitProfilingInstr())
{
Assert(instr->AsJitProfilingInstr()->profileId == Js::Constants::NoProfileId);
m_lowererMD.LoadHelperArgument(instr, CreateFunctionBodyOpnd(instr->m_func));
meth = IR::HelperSimpleProfiledLdThis;
this->LowerBinaryHelper(instr, meth);
}
else
{
meth = IR::HelperLdThisNoFastPath;
this->LowerBinaryHelperMem(instr, meth);
}
}
else
{
this->LowerBinaryHelperMem(instr, IR::HelperLdThis);
}
break;
}
case Js::OpCode::LdNativeCodeData:
Assert(m_func->IsOOPJIT());
instrPrev = LowerLdNativeCodeData(instr);
break;
case Js::OpCode::StrictLdThis:
if (noFieldFastPath)
{
IR::JnHelperMethod meth;
if (instr->IsJitProfilingInstr())
{
Assert(instr->AsJitProfilingInstr()->profileId == Js::Constants::NoProfileId);
m_lowererMD.LoadHelperArgument(instr, CreateFunctionBodyOpnd(instr->m_func));
meth = IR::HelperSimpleProfiledStrictLdThis;
this->LowerUnaryHelper(instr, meth);
}
else
{
meth = IR::HelperStrictLdThis;
this->LowerUnaryHelperMem(instr, meth);
}
}
else
{
this->GenerateLdThisStrict(instr);
instr->Remove();
}
break;
case Js::OpCode::CheckThis:
GenerateLdThisCheck(instr);
instr->FreeSrc1();
this->GenerateBailOut(instr);
break;
case Js::OpCode::StrictCheckThis:
this->GenerateLdThisStrict(instr);
instr->FreeSrc1();
this->GenerateBailOut(instr);
break;
case Js::OpCode::NewScArray:
instrPrev = this->LowerNewScArray(instr);
break;
case Js::OpCode::NewScArrayWithMissingValues:
this->LowerUnaryHelperMem(instr, IR::HelperScrArr_OP_NewScArrayWithMissingValues);
break;
case Js::OpCode::NewScIntArray:
instrPrev = this->LowerNewScIntArray(instr);
break;
case Js::OpCode::NewScFltArray:
instrPrev = this->LowerNewScFltArray(instr);
break;
case Js::OpCode::InitForInEnumerator:
this->LowerInitForInEnumerator(instr);
break;
case Js::OpCode::Add_A:
if (instr->GetDst()->IsFloat())
{
Assert(instr->GetSrc1()->IsFloat());
Assert(instr->GetSrc2()->IsFloat());
// we don't want to mix float32 and float64
Assert(instr->GetDst()->GetType() == instr->GetSrc1()->GetType());
Assert(instr->GetDst()->GetType() == instr->GetSrc2()->GetType());
m_lowererMD.LowerToFloat(instr);
}
else if (PHASE_OFF(Js::MathFastPathPhase, this->m_func) || noMathFastPath)
{
this->LowerBinaryHelperMem(instr, IR::HelperOp_Add);
}
else if (m_lowererMD.TryGenerateFastMulAdd(instr, &instrPrev))
{
}
else
{
m_lowererMD.GenerateFastAdd(instr);
this->LowerBinaryHelperMemWithTemp3(instr, IR_HELPER_OP_FULL_OR_INPLACE(Add), IR::HelperOp_AddLeftDead);
}
break;
case Js::OpCode::Div_A:
{
if (instr->IsJitProfilingInstr()) {
LowerProfiledBinaryOp(instr->AsJitProfilingInstr(), IR::HelperSimpleProfiledDivide);
}
else if (instr->GetDst()->IsFloat())
{
Assert(instr->GetSrc1()->IsFloat());
Assert(instr->GetSrc2()->IsFloat());
Assert(instr->GetDst()->GetType() == instr->GetSrc1()->GetType());
Assert(instr->GetDst()->GetType() == instr->GetSrc2()->GetType());
m_lowererMD.LowerToFloat(instr);
}
else
{
if (!PHASE_OFF(Js::MathFastPathPhase, this->m_func) && !noMathFastPath)
{
IR::AddrOpnd *src2 = instr->GetSrc2()->IsAddrOpnd() ? instr->GetSrc2()->AsAddrOpnd() : nullptr;
if (src2 && src2->IsVar() && Js::TaggedInt::Is(src2->m_address))
{
int32 value = Js::TaggedInt::ToInt32(src2->m_address);
if (Math::IsPow2(value))
{
m_lowererMD.GenerateFastDivByPow2(instr);
}
}
}
this->LowerBinaryHelperMemWithTemp2(instr, IR_HELPER_OP_FULL_OR_INPLACE(Divide));
}
break;
}
case Js::OpCode::Expo_A:
{
if (instr->GetDst()->IsFloat())
{
Assert(instr->GetSrc1()->IsFloat());
Assert(instr->GetSrc2()->IsFloat());
Assert(instr->GetDst()->GetType() == instr->GetSrc1()->GetType());
Assert(instr->GetDst()->GetType() == instr->GetSrc2()->GetType());
m_lowererMD.GenerateFastInlineBuiltInCall(instr, IR::HelperDirectMath_Pow);
}
else
{
this->LowerBinaryHelperMemWithTemp2(instr, IR_HELPER_OP_FULL_OR_INPLACE(Exponentiation));
}
break;
}
case Js::OpCode::Mul_A:
if (instr->GetDst()->IsFloat())
{
Assert(instr->GetSrc1()->IsFloat());
Assert(instr->GetSrc2()->IsFloat());
Assert(instr->GetDst()->GetType() == instr->GetSrc1()->GetType());
Assert(instr->GetDst()->GetType() == instr->GetSrc2()->GetType());
m_lowererMD.LowerToFloat(instr);
}
else if (PHASE_OFF(Js::MathFastPathPhase, this->m_func) || noMathFastPath)
{
this->LowerBinaryHelperMem(instr, IR::HelperOp_Multiply);
}
else if (m_lowererMD.GenerateFastMul(instr))
{
this->LowerBinaryHelperMemWithTemp2(instr, IR_HELPER_OP_FULL_OR_INPLACE(Multiply));
}
break;
case Js::OpCode::Rem_A:
if (instr->GetDst()->IsFloat64())
{
this->LowerRemR8(instr);
}
else if (instr->IsJitProfilingInstr())
{
this->LowerProfiledBinaryOp(instr->AsJitProfilingInstr(), IR::HelperSimpleProfiledRemainder);
}
else
{
this->LowerBinaryHelperMemWithTemp2(instr, IR_HELPER_OP_FULL_OR_INPLACE(Modulus));
}
break;
case Js::OpCode::Sub_A:
if (instr->GetDst()->IsFloat())
{
Assert(instr->GetSrc1()->IsFloat());
Assert(instr->GetSrc2()->IsFloat());
Assert(instr->GetDst()->GetType() == instr->GetSrc1()->GetType());
Assert(instr->GetDst()->GetType() == instr->GetSrc2()->GetType());
m_lowererMD.LowerToFloat(instr);
}
else if (PHASE_OFF(Js::MathFastPathPhase, this->m_func) || noMathFastPath)
{
this->LowerBinaryHelperMem(instr, IR::HelperOp_Subtract);
}
else if (m_lowererMD.TryGenerateFastMulAdd(instr, &instrPrev))
{
}
else
{
m_lowererMD.GenerateFastSub(instr);
this->LowerBinaryHelperMemWithTemp2(instr, IR_HELPER_OP_FULL_OR_INPLACE(Subtract));
}
break;
case Js::OpCode::And_A:
if (PHASE_OFF(Js::BitopsFastPathPhase, this->m_func) || noMathFastPath)
{
this->LowerBinaryHelperMem(instr, IR::HelperOp_And);
}
else if (m_lowererMD.GenerateFastAnd(instr))
{
this->LowerBinaryHelperMemWithTemp2(instr, IR_HELPER_OP_FULL_OR_INPLACE(And));
}
break;
case Js::OpCode::Or_A:
if (PHASE_OFF(Js::BitopsFastPathPhase, this->m_func) || noMathFastPath)
{
this->LowerBinaryHelperMem(instr, IR::HelperOp_Or);
}
else if (m_lowererMD.GenerateFastOr(instr))
{
this->LowerBinaryHelperMemWithTemp2(instr, IR_HELPER_OP_FULL_OR_INPLACE(Or));
}
break;
case Js::OpCode::Xor_A:
if (PHASE_OFF(Js::BitopsFastPathPhase, this->m_func) || noMathFastPath || m_lowererMD.GenerateFastXor(instr))
{
this->LowerBinaryHelperMemWithTemp2(instr, IR_HELPER_OP_FULL_OR_INPLACE(Xor));
}
break;
case Js::OpCode::Shl_A:
if (PHASE_OFF(Js::BitopsFastPathPhase, this->m_func) || noMathFastPath || m_lowererMD.GenerateFastShiftLeft(instr))
{
this->LowerBinaryHelperMem(instr, IR::HelperOp_ShiftLeft);
}
break;
case Js::OpCode::Shr_A:
if (PHASE_OFF(Js::BitopsFastPathPhase, this->m_func) || noMathFastPath || m_lowererMD.GenerateFastShiftRight(instr))
{
this->LowerBinaryHelperMem(instr, IR::HelperOp_ShiftRight);
}
break;
case Js::OpCode::ShrU_A:
if (PHASE_OFF(Js::BitopsFastPathPhase, this->m_func) || noMathFastPath || m_lowererMD.GenerateFastShiftRight(instr))
{
this->LowerBinaryHelperMem(instr, IR::HelperOp_ShiftRightU);
}
break;
case Js::OpCode::CmEq_A:
{
instrPrev = LowerEqualityCompare(instr, IR::HelperOP_CmEq_A);
break;
}
case Js::OpCode::CmNeq_A:
{
instrPrev = LowerEqualityCompare(instr, IR::HelperOP_CmNeq_A);
break;
}
case Js::OpCode::CmSrEq_A:
instrPrev = LowerEqualityCompare(instr, IR::HelperOP_CmSrEq_A);
break;
case Js::OpCode::CmSrNeq_A:
instrPrev = LowerEqualityCompare(instr, IR::HelperOP_CmSrNeq_A);
break;
case Js::OpCode::CmGt_A:
if (instr->GetSrc1()->IsFloat())
{
Assert(instr->GetSrc1()->GetType() == instr->GetSrc2()->GetType());
this->m_lowererMD.GenerateFastCmXxR8(instr);
}
else if (PHASE_OFF(Js::BranchFastPathPhase, this->m_func) || noMathFastPath || !m_lowererMD.GenerateFastCmXxTaggedInt(instr))
{
this->LowerBinaryHelperMem(instr, IR::HelperOP_CmGt_A);
}
break;
case Js::OpCode::CmGe_A:
if (instr->GetSrc1()->IsFloat())
{
Assert(instr->GetSrc1()->GetType() == instr->GetSrc2()->GetType());
this->m_lowererMD.GenerateFastCmXxR8(instr);
}
else if (PHASE_OFF(Js::BranchFastPathPhase, this->m_func) || noMathFastPath || !m_lowererMD.GenerateFastCmXxTaggedInt(instr))
{
this->LowerBinaryHelperMem(instr, IR::HelperOP_CmGe_A);
}
break;
case Js::OpCode::CmLt_A:
if (instr->GetSrc1()->IsFloat())
{
Assert(instr->GetSrc1()->GetType() == instr->GetSrc2()->GetType());
this->m_lowererMD.GenerateFastCmXxR8(instr);
}
else if (PHASE_OFF(Js::BranchFastPathPhase, this->m_func) || noMathFastPath || !m_lowererMD.GenerateFastCmXxTaggedInt(instr))
{
this->LowerBinaryHelperMem(instr, IR::HelperOP_CmLt_A);
}
break;
case Js::OpCode::CmLe_A:
if (instr->GetSrc1()->IsFloat())
{
Assert(instr->GetSrc1()->GetType() == instr->GetSrc2()->GetType());
this->m_lowererMD.GenerateFastCmXxR8(instr);
}
else if (PHASE_OFF(Js::BranchFastPathPhase, this->m_func) || noMathFastPath || !m_lowererMD.GenerateFastCmXxTaggedInt(instr))
{
this->LowerBinaryHelperMem(instr, IR::HelperOP_CmLe_A);
}
break;
case Js::OpCode::CmEq_I4:
case Js::OpCode::CmNeq_I4:
case Js::OpCode::CmGe_I4:
case Js::OpCode::CmGt_I4:
case Js::OpCode::CmLe_I4:
case Js::OpCode::CmLt_I4:
case Js::OpCode::CmUnGe_I4:
case Js::OpCode::CmUnGt_I4:
case Js::OpCode::CmUnLe_I4:
case Js::OpCode::CmUnLt_I4:
this->m_lowererMD.GenerateFastCmXxI4(instr);
break;
case Js::OpCode::Conv_Bool:
instrPrev = this->m_lowererMD.GenerateConvBool(instr);
break;
case Js::OpCode::IsInst:
this->GenerateFastIsInst(instr);
instrPrev = this->LowerIsInst(instr, IR::HelperScrObj_OP_IsInst);
break;
case Js::OpCode::IsIn:
this->GenerateFastArrayIsIn(instr);
this->GenerateFastObjectIsIn(instr);
this->LowerBinaryHelperMem(instr, IR::HelperOp_IsIn);
break;
case Js::OpCode::LdArrViewElem:
instrPrev = LowerLdArrViewElem(instr);
break;
case Js::OpCode::StAtomicWasm:
instrPrev = LowerStAtomicsWasm(instr);
break;
case Js::OpCode::StArrViewElem:
instrPrev = LowerStArrViewElem(instr);
break;
case Js::OpCode::LdAtomicWasm:
instrPrev = LowerLdAtomicsWasm(instr);
break;
case Js::OpCode::LdArrViewElemWasm:
instrPrev = LowerLdArrViewElemWasm(instr);
break;
case Js::OpCode::Memset:
case Js::OpCode::Memcopy:
{
instrPrev = LowerMemOp(instr);
break;
}
case Js::OpCode::ArrayDetachedCheck:
instrPrev = LowerArrayDetachedCheck(instr);
break;
case Js::OpCode::StElemI_A:
case Js::OpCode::StElemI_A_Strict:
{
// Note: under debugger (Fast F12) don't let GenerateFastStElemI which calls into ToNumber_Helper
// which takes double, and currently our helper wrapper doesn't support double.
bool fastPath = !noMathFastPath && !m_func->IsJitInDebugMode();
if (!fastPath && instr->HasBailOutInfo())
{
// Some bailouts are generated around the helper call, and will work even if the fast path is disabled. Other
// bailouts require the fast path.
const IR::BailOutKind bailOutKind = instr->GetBailOutKind();
const IR::BailOutKind bailOutKindBits = bailOutKind & IR::BailOutKindBits;
if (bailOutKindBits & ~(IR::BailOutOnMissingValue | IR::BailOutConvertedNativeArray))
{
fastPath = true;
}
else
{
const IR::BailOutKind bailOutKindMinusBits = bailOutKind & ~IR::BailOutKindBits;
fastPath =
bailOutKindMinusBits &&
bailOutKindMinusBits != IR::BailOutOnImplicitCalls &&
bailOutKindMinusBits != IR::BailOutOnImplicitCallsPreOp;
}
}
IR::Opnd * opnd = instr->GetDst();
IR::Opnd * baseOpnd = opnd->AsIndirOpnd()->GetBaseOpnd();
ValueType profiledBaseValueType = baseOpnd->AsRegOpnd()->GetValueType();
if (profiledBaseValueType.IsUninitialized() && baseOpnd->AsRegOpnd()->m_sym->IsSingleDef())
{
baseOpnd->SetValueType(baseOpnd->FindProfiledValueType());
}
bool instrIsInHelperBlock = false;
if (!fastPath)
{
this->LowerStElemI(
instr,
instr->m_opcode == Js::OpCode::StElemI_A ? Js::PropertyOperation_None : Js::PropertyOperation_StrictMode,
false);
}
else if (GenerateFastStElemI(instr, &instrIsInHelperBlock))
{
#if DBG
if (instr->HasBailOutInfo())
{
const IR::BailOutKind bailOutKind = instr->GetBailOutKind();
Assert(
(bailOutKind & ~IR::BailOutKindBits) != IR::BailOutConventionalTypedArrayAccessOnly &&
!(
bailOutKind &
(IR::BailOutConventionalNativeArrayAccessOnly | IR::BailOutOnArrayAccessHelperCall)
));
}
#endif
this->LowerStElemI(
instr,
instr->m_opcode == Js::OpCode::StElemI_A ? Js::PropertyOperation_None : Js::PropertyOperation_StrictMode,
instrIsInHelperBlock);
}
break;
}
case Js::OpCode::LdElemI_A:
case Js::OpCode::LdMethodElem:
{
bool fastPath =
!noMathFastPath &&
(
instr->m_opcode != Js::OpCode::LdMethodElem ||
instr->GetSrc1()->AsIndirOpnd()->GetBaseOpnd()->GetValueType().IsLikelyObject()
);
if (!fastPath && instr->HasBailOutInfo())
{
// Some bailouts are generated around the helper call, and will work even if the fast path is disabled. Other
// bailouts require the fast path.
const IR::BailOutKind bailOutKind = instr->GetBailOutKind();
if (bailOutKind & IR::BailOutKindBits)
{
fastPath = true;
}
else
{
const IR::BailOutKind bailOutKindMinusBits = bailOutKind & ~IR::BailOutKindBits;
fastPath =
bailOutKindMinusBits &&
bailOutKindMinusBits != IR::BailOutOnImplicitCalls &&
bailOutKindMinusBits != IR::BailOutOnImplicitCallsPreOp;
}
}
IR::Opnd * opnd = instr->GetSrc1();
IR::Opnd * baseOpnd = opnd->AsIndirOpnd()->GetBaseOpnd();
ValueType profiledBaseValueType = baseOpnd->AsRegOpnd()->GetValueType();
if (profiledBaseValueType.IsUninitialized() && baseOpnd->AsRegOpnd()->m_sym->IsSingleDef())
{
baseOpnd->SetValueType(baseOpnd->FindProfiledValueType());
}
bool instrIsInHelperBlock = false;
if (!fastPath)
{
this->LowerLdElemI(
instr,
instr->m_opcode == Js::OpCode::LdElemI_A ? IR::HelperOp_GetElementI : IR::HelperOp_GetMethodElement,
false);
}
else if (GenerateFastLdElemI(instr, &instrIsInHelperBlock))
{
#if DBG
if (instr->HasBailOutInfo())
{
const IR::BailOutKind bailOutKind = instr->GetBailOutKind();
Assert(
(bailOutKind & ~IR::BailOutKindBits) != IR::BailOutConventionalTypedArrayAccessOnly &&
!(
bailOutKind &
(IR::BailOutConventionalNativeArrayAccessOnly | IR::BailOutOnArrayAccessHelperCall)
));
}
#endif
this->LowerLdElemI(
instr,
instr->m_opcode == Js::OpCode::LdElemI_A ? IR::HelperOp_GetElementI : IR::HelperOp_GetMethodElement,
instrIsInHelperBlock);
}
break;
}
case Js::OpCode::InitSetElemI:
instrPrev = this->LowerStElemI(instr, Js::PropertyOperation_None, false, IR::HelperOP_InitElemSetter);
break;
case Js::OpCode::InitGetElemI:
instrPrev = this->LowerStElemI(instr, Js::PropertyOperation_None, false, IR::HelperOP_InitElemGetter);
break;
case Js::OpCode::InitComputedProperty:
instrPrev = this->LowerStElemI(instr, Js::PropertyOperation_None, false, IR::HelperOP_InitComputedProperty);
break;
case Js::OpCode::Delete_A:
this->LowerUnaryHelperMem(instr, IR::HelperOp_Delete);
break;
case Js::OpCode::DeleteElemI_A:
this->LowerDeleteElemI(instr, false);
break;
case Js::OpCode::DeleteElemIStrict_A:
this->LowerDeleteElemI(instr, true);
break;
case Js::OpCode::BytecodeArgOutCapture:
m_lowererMD.ChangeToAssign(instr);
break;
case Js::OpCode::UnwrapWithObj:
this->LowerUnaryHelper(instr, IR::HelperOp_UnwrapWithObj);
break;
#ifdef ENABLE_WASM
case Js::OpCode::CheckWasmSignature:
this->LowerCheckWasmSignature(instr);
break;
case Js::OpCode::LdWasmFunc:
instrPrev = this->LowerLdWasmFunc(instr);
break;
case Js::OpCode::GrowWasmMemory:
instrPrev = this->LowerGrowWasmMemory(instr);
break;
#endif
case Js::OpCode::Ld_I4:
LowererMD::ChangeToAssign(instr);
break;
case Js::OpCode::LdAsmJsFunc:
if (instr->GetSrc1()->IsIndirOpnd())
{
IR::IndirOpnd* indir = instr->GetSrc1()->AsIndirOpnd();
byte scale = m_lowererMD.GetDefaultIndirScale();
if (!indir->GetIndexOpnd())
{
// If we have a constant offset, we need to apply the scale now
int32 offset;
if (Int32Math::Shl(1, scale, &offset) || Int32Math::Mul(offset, indir->GetOffset(), &offset))
{
// The constant is too big to offset this array. Throw out of range.
// Todo:: throw a better error message for this scenario
GenerateRuntimeError(instr, JSERR_ArgumentOutOfRange, IR::HelperOp_RuntimeRangeError);
}
indir->SetOffset(offset);
}
else
{
indir->SetScale(scale);
}
}
//fallthrough
case Js::OpCode::Ld_A:
case Js::OpCode::InitConst:
if (instr->IsJitProfilingInstr() && instr->AsJitProfilingInstr()->isBeginSwitch) {
LowerProfiledBeginSwitch(instr->AsJitProfilingInstr());
break;
}
m_lowererMD.ChangeToAssign(instr);
if (instr->HasBailOutInfo())
{
IR::BailOutKind bailOutKind = instr->GetBailOutKind();
if (bailOutKind == IR::BailOutExpectingString)
{
this->LowerBailOnNotString(instr);
}
else
{
// Should not reach here as there are only 1 BailOutKind (BailOutExpectingString) currently associated with the Load Instr
Assert(false);
}
}
break;
case Js::OpCode::LdIndir:
Assert(instr->GetDst());
Assert(instr->GetDst()->IsRegOpnd());
Assert(instr->GetSrc1());
Assert(instr->GetSrc1()->IsIndirOpnd());
Assert(!instr->GetSrc2());
m_lowererMD.ChangeToAssign(instr);
break;
case Js::OpCode::FromVar:
Assert(instr->GetSrc1()->GetType() == TyVar);
if (instr->GetDst()->GetType() == TyInt32)
{
if (m_lowererMD.EmitLoadInt32(instr, !(instr->HasBailOutInfo() && (instr->GetBailOutKind() == IR::BailOutOnNotPrimitive))))
{
// Bail out instead of calling a helper
Assert(instr->GetBailOutKind() == IR::BailOutIntOnly || instr->GetBailOutKind() == IR::BailOutExpectingInteger);
Assert(!instr->GetSrc1()->GetValueType().IsInt()); // when we know it's an int, it should not have bailout info, to avoid generating a bailout path that will never be taken
instr->UnlinkSrc1();
instr->UnlinkDst();
GenerateBailOut(instr);
}
}
else if (instr->GetDst()->IsFloat())
{
if (m_func->GetJITFunctionBody()->IsAsmJsMode())
{
m_lowererMD.EmitLoadFloat(instr->GetDst(), instr->GetSrc1(), instr);
instr->Remove();
}
else
{
m_lowererMD.EmitLoadFloatFromNumber(instr->GetDst(), instr->GetSrc1(), instr);
}
}
else if (instr->GetDst()->IsInt64())
{
Assert(m_func->GetJITFunctionBody()->IsWasmFunction());
GenerateRuntimeError(instr, WASMERR_InvalidTypeConversion);
instr->ReplaceSrc1(IR::Int64ConstOpnd::New(0, TyInt64, m_func));
LowererMD::ChangeToAssign(instr);
}
#ifdef ENABLE_WASM_SIMD
else if (instr->GetDst()->IsSimd128())
{
Assert(m_func->GetJITFunctionBody()->IsWasmFunction());
GenerateRuntimeError(instr, WASMERR_InvalidTypeConversion);
instr->ReplaceSrc1(IR::Simd128ConstOpnd::New({ 0,0,0,0 }, instr->GetDst()->GetType(), m_func));
LowererMD::ChangeToAssign(instr);
}
#endif
else
{
Assert(UNREACHED);
}
break;
case Js::OpCode::ArgOut_A:
// I don't know if this can happen in asm.js mode, but if it can, we might want to handle differently
Assert(!m_func->GetJITFunctionBody()->IsAsmJsMode());
// fall-through
case Js::OpCode::ArgOut_A_Inline:
case Js::OpCode::ArgOut_A_Dynamic:
{
// ArgOut/StartCall are normally lowered by the lowering of the associated call instr.
// If the call becomes unreachable, we could end up with an orphan ArgOut or StartCall.
// Change the ArgOut into a store to the stack for bailouts
instr->FreeSrc2();
StackSym *argSym = instr->GetDst()->AsSymOpnd()->m_sym->AsStackSym();
argSym->m_offset = this->m_func->StackAllocate(sizeof(Js::Var));
argSym->m_allocated = true;
argSym->m_isOrphanedArg = true;
this->m_lowererMD.ChangeToAssign(instr);
}
break;
case Js::OpCode::LoweredStartCall:
case Js::OpCode::StartCall:
// ArgOut/StartCall are normally lowered by the lowering of the associated call instr.
// If the call becomes unreachable, we could end up with an orphan ArgOut or StartCall.
// We'll just delete these StartCalls during peeps.
break;
case Js::OpCode::ToVar:
Assert(instr->GetDst()->GetType() == TyVar);
if (instr->GetSrc1()->GetType() == TyInt32)
{
m_lowererMD.EmitLoadVar(instr);
}
else if (instr->GetSrc1()->IsFloat())
{
Assert(instr->GetSrc1()->IsRegOpnd());
IR::RegOpnd* float64Opnd = instr->GetSrc1()->AsRegOpnd();
if (float64Opnd->IsFloat32())
{
IR::RegOpnd* float64ConvOpnd = IR::RegOpnd::New(TyFloat64, m_func);
m_lowererMD.EmitFloat32ToFloat64(float64ConvOpnd, float64Opnd, instr);
float64Opnd = float64ConvOpnd;
}
m_lowererMD.SaveDoubleToVar(
instr->GetDst()->AsRegOpnd(),
float64Opnd, instr, instr);
instr->Remove();
}
else if (instr->GetSrc1()->IsInt64() || instr->GetSrc1()->IsSimd128())
{
Assert(m_func->GetJITFunctionBody()->IsWasmFunction());
GenerateRuntimeError(instr, WASMERR_InvalidTypeConversion);
instr->ReplaceSrc1(IR::IntConstOpnd::New(0, TyMachReg, m_func));
LowererMD::ChangeToAssign(instr);
}
else
{
Assert(UNREACHED);
}
break;
case Js::OpCode::Conv_Prim_Sat:
{
GenerateTruncWithCheck<true /* Saturate */>(instr);
break;
}
case Js::OpCode::Conv_Prim:
{
if (IR::Instr::FindSingleDefInstr(Js::OpCode::TrapIfTruncOverflow, instr->GetSrc1()))
{
GenerateTruncWithCheck<false /* Saturate */>(instr);
break;
}
if (instr->GetDst()->IsFloat())
{
if (instr->GetSrc1()->IsIntConstOpnd())
{
LoadFloatFromNonReg(instr->UnlinkSrc1(), instr->UnlinkDst(), instr);
}
else if (instr->GetSrc1()->IsInt32())
{
m_lowererMD.EmitIntToFloat(instr->GetDst(), instr->GetSrc1(), instr);
}
else if (instr->GetSrc1()->IsUInt32())
{
m_lowererMD.EmitUIntToFloat(instr->GetDst(), instr->GetSrc1(), instr);
}
else if (instr->GetSrc1()->IsInt64())
{
m_lowererMD.EmitInt64toFloat(instr->GetDst(), instr->GetSrc1(), instr);
}
else
{
Assert(instr->GetDst()->IsFloat64());
Assert(instr->GetSrc1()->IsFloat32());
m_lowererMD.EmitFloat32ToFloat64(instr->GetDst(), instr->GetSrc1(), instr);
}
}
else if (instr->GetDst()->IsInt64())
{
if (instr->GetSrc1()->IsInt32())
{
m_lowererMD.EmitIntToLong(instr->GetDst(), instr->GetSrc1(), instr);
}
else if (instr->GetSrc1()->IsUInt32())
{
m_lowererMD.EmitUIntToLong(instr->GetDst(), instr->GetSrc1(), instr);
}
else if (instr->GetSrc1()->IsInt64() && instr->GetSrc2())
{
m_lowererMD.EmitSignExtend(instr);
}
else
{
Assert(0);
}
}
else
{
Assert(instr->GetDst()->IsInt32());
if (instr->GetSrc1()->IsInt64())
{
m_lowererMD.EmitLongToInt(instr->GetDst(), instr->GetSrc1(), instr);
}
else if ((instr->GetSrc1()->IsInt32() || instr->GetSrc1()->IsUInt32()) && instr->GetSrc2())
{
m_lowererMD.EmitSignExtend(instr);
}
else
{
Assert(instr->GetSrc1()->IsFloat());
m_lowererMD.EmitFloatToInt(instr->GetDst(), instr->GetSrc1(), instr);
}
}
instr->Remove();
break;
}
case Js::OpCode::FunctionExit:
LowerFunctionExit(instr);
// The rest of Epilog generation happens after reg allocation
break;
case Js::OpCode::FunctionEntry:
LowerFunctionEntry(instr);
// The rest of Prolog generation happens after reg allocation
break;
case Js::OpCode::ArgIn_Rest:
case Js::OpCode::ArgIn_A:
if (m_func->GetJITFunctionBody()->IsAsmJsMode() && !m_func->IsLoopBody())
{
instrPrev = LowerArgInAsmJs(instr);
}
else
{
instrPrev = LowerArgIn(instr);
}
break;
case Js::OpCode::Label:
if (instr->AsLabelInstr()->m_isLoopTop)
{
if (this->outerMostLoopLabel == instr)
{
noFieldFastPath = !defaultDoFastPath;
noMathFastPath = !defaultDoFastPath;
this->outerMostLoopLabel = nullptr;
instr->AsLabelInstr()->GetLoop()->isProcessed = true;
}
this->m_func->MarkConstantAddressSyms(instr->AsLabelInstr()->GetLoop()->regAlloc.liveOnBackEdgeSyms);
instr->AsLabelInstr()->GetLoop()->regAlloc.liveOnBackEdgeSyms->Or(this->addToLiveOnBackEdgeSyms);
}
break;
case Js::OpCode::Br:
instr->m_opcode = LowererMD::MDUncondBranchOpcode;
break;
case Js::OpCode::BrFncEqApply:
LowerBrFncApply(instr, IR::HelperOp_OP_BrFncEqApply);
break;
case Js::OpCode::BrFncNeqApply:
LowerBrFncApply(instr, IR::HelperOp_OP_BrFncNeqApply);
break;
case Js::OpCode::BrHasSideEffects:
case Js::OpCode::BrNotHasSideEffects:
m_lowererMD.GenerateFastBrS(instr->AsBranchInstr());
break;
case Js::OpCode::BrFalse_A:
case Js::OpCode::BrTrue_A:
if (instr->GetSrc1()->IsFloat())
{
GenerateFastBrBool(instr->AsBranchInstr());
}
else if (PHASE_OFF(Js::BranchFastPathPhase, this->m_func) ||
noMathFastPath ||
GenerateFastBrBool(instr->AsBranchInstr()))
{
this->LowerBrBMem(instr, IR::HelperConv_ToBoolean);
}
break;
case Js::OpCode::BrOnObject_A:
if (PHASE_OFF(Js::BranchFastPathPhase, this->m_func) || noMathFastPath)
{
this->LowerBrOnObject(instr, IR::HelperOp_IsObject);
}
else
{
GenerateFastBrOnObject(instr);
}
break;
case Js::OpCode::BrOnBaseConstructorKind:
this->LowerBrOnClassConstructor(instr, IR::HelperOp_IsBaseConstructorKind);
break;
case Js::OpCode::BrOnClassConstructor:
this->LowerBrOnClassConstructor(instr, IR::HelperOp_IsClassConstructor);
break;
case Js::OpCode::BrAddr_A:
case Js::OpCode::BrNotAddr_A:
case Js::OpCode::BrNotNull_A:
m_lowererMD.LowerCondBranch(instr);
break;
case Js::OpCode::BrEq_A:
case Js::OpCode::BrNotNeq_A:
instrPrev = LowerEqualityBranch(instr, IR::HelperOp_Equal);
break;
case Js::OpCode::BrGe_A:
case Js::OpCode::BrNotGe_A:
if (instr->GetSrc1()->IsFloat())
{
Assert(instr->GetSrc1()->GetType() == instr->GetSrc2()->GetType());
m_lowererMD.LowerToFloat(instr);
}
else if (!PHASE_OFF(Js::BranchFastPathPhase, this->m_func) && !noMathFastPath)
{
this->LowerBrCMem(instr, IR::HelperOp_GreaterEqual, false, false /*isHelper*/);
}
else
{
this->LowerBrCMem(instr, IR::HelperOp_GreaterEqual, true, false /*isHelper*/);
}
break;
case Js::OpCode::BrGt_A:
case Js::OpCode::BrNotGt_A:
if (instr->GetSrc1()->IsFloat())
{
Assert(instr->GetSrc1()->GetType() == instr->GetSrc2()->GetType());
m_lowererMD.LowerToFloat(instr);
}
else if (!PHASE_OFF(Js::BranchFastPathPhase, this->m_func) && !noMathFastPath)
{
this->LowerBrCMem(instr, IR::HelperOp_Greater, false, false /*isHelper*/);
}
else
{
this->LowerBrCMem(instr, IR::HelperOp_Greater, true, false /*isHelper*/);
}
break;
case Js::OpCode::BrLt_A:
case Js::OpCode::BrNotLt_A:
if (instr->GetSrc1()->IsFloat())
{
Assert(instr->GetSrc1()->GetType() == instr->GetSrc2()->GetType());
m_lowererMD.LowerToFloat(instr);
}
else if (!PHASE_OFF(Js::BranchFastPathPhase, this->m_func) && !noMathFastPath)
{
this->LowerBrCMem(instr, IR::HelperOp_Less, false, false /*isHelper*/);
}
else
{
this->LowerBrCMem(instr, IR::HelperOp_Less, true, false /*isHelper*/);
}
break;
case Js::OpCode::BrLe_A:
case Js::OpCode::BrNotLe_A:
if (instr->GetSrc1()->IsFloat())
{
Assert(instr->GetSrc1()->GetType() == instr->GetSrc2()->GetType());
m_lowererMD.LowerToFloat(instr);
}
else if (!PHASE_OFF(Js::BranchFastPathPhase, this->m_func) && !noMathFastPath)
{
this->LowerBrCMem(instr, IR::HelperOp_LessEqual, false, false /*isHelper*/);
}
else
{
this->LowerBrCMem(instr, IR::HelperOp_LessEqual, true, false /*isHelper*/);
}
break;
case Js::OpCode::BrNeq_A:
case Js::OpCode::BrNotEq_A:
instrPrev = LowerEqualityBranch(instr, IR::HelperOp_NotEqual);
break;
case Js::OpCode::MultiBr:
{
IR::MultiBranchInstr * multiBranchInstr = instr->AsBranchInstr()->AsMultiBrInstr();
switch (multiBranchInstr->m_kind)
{
case IR::MultiBranchInstr::StrDictionary:
this->GenerateSwitchStringLookup(instr);
break;
case IR::MultiBranchInstr::SingleCharStrJumpTable:
this->GenerateSingleCharStrJumpTableLookup(instr);
m_func->m_totalJumpTableSizeInBytesForSwitchStatements += (multiBranchInstr->GetBranchJumpTable()->tableSize * sizeof(void*));
break;
case IR::MultiBranchInstr::IntJumpTable:
this->LowerMultiBr(instr);
m_func->m_totalJumpTableSizeInBytesForSwitchStatements += (multiBranchInstr->GetBranchJumpTable()->tableSize * sizeof(void*));
break;
default:
Assert(false);
}
break;
}
case Js::OpCode::BrSrEq_A:
case Js::OpCode::BrSrNotNeq_A:
instrPrev = LowerEqualityBranch(instr, IR::HelperOp_StrictEqual);
break;
case Js::OpCode::BrSrNeq_A:
case Js::OpCode::BrSrNotEq_A:
instrPrev = LowerEqualityBranch(instr, IR::HelperOp_NotStrictEqual);
break;
case Js::OpCode::BrOnEmpty:
case Js::OpCode::BrOnNotEmpty:
if (!PHASE_OFF(Js::BranchFastPathPhase, this->m_func))
{
this->GenerateFastBrBReturn(instr);
this->LowerBrBReturn(instr, IR::HelperOp_OP_BrOnEmpty, true);
}
else
{
this->LowerBrBReturn(instr, IR::HelperOp_OP_BrOnEmpty, false);
}
break;
case Js::OpCode::BrOnHasProperty:
case Js::OpCode::BrOnNoProperty:
this->LowerBrProperty(instr, IR::HelperOp_HasProperty);
break;
case Js::OpCode::BrOnException:
Assert(!this->m_func->DoGlobOpt());
instr->Remove();
break;
case Js::OpCode::BrOnNoException:
instr->m_opcode = LowererMD::MDUncondBranchOpcode;
break;
case Js::OpCode::StSlot:
{
PropertySym *propertySym = instr->GetDst()->AsSymOpnd()->m_sym->AsPropertySym();
instrPrev = AddSlotArrayCheck(propertySym, instr);
this->LowerStSlot(instr);
break;
}
case Js::OpCode::StSlotChkUndecl:
{
PropertySym *propertySym = instr->GetDst()->AsSymOpnd()->m_sym->AsPropertySym();
instrPrev = AddSlotArrayCheck(propertySym, instr);
this->LowerStSlotChkUndecl(instr);
break;
}
case Js::OpCode::ProfiledLoopStart:
{
Assert(m_func->DoSimpleJitDynamicProfile());
Assert(instr->IsJitProfilingInstr());
// Check for the helper instr from IRBuilding (it won't be there if there are no LoopEnds due to an infinite loop)
auto prev = instr->m_prev;
if (prev->IsJitProfilingInstr() && prev->AsJitProfilingInstr()->isLoopHelper)
{
auto saveOpnd = prev->UnlinkDst();
instrPrev = prev->m_prev;
prev->Remove();
const auto starFlag = GetImplicitCallFlagsOpnd();
IR::AutoReuseOpnd a(starFlag, m_func);
this->InsertMove(saveOpnd, starFlag, instr);
this->InsertMove(starFlag, CreateClearImplicitCallFlagsOpnd(), instr);
}
else
{
#if DBG
// Double check that we indeed do not have a LoopEnd that is part of the same loop for the rest of the function
auto cur = instr;
auto loopNumber = instr->AsJitProfilingInstr()->loopNumber;
while (cur)
{
Assert(cur->m_opcode != Js::OpCode::ProfiledLoopEnd || cur->IsJitProfilingInstr() && cur->AsJitProfilingInstr()->loopNumber != loopNumber);
cur = cur->m_next;
}
#endif
}
// If we turned off fulljit, there's no reason to do this.
if (PHASE_OFF(Js::FullJitPhase, m_func))
{
instr->Remove();
}
else
{
Assert(instr->GetDst());
instr->SetSrc1(IR::HelperCallOpnd::New(IR::HelperSimpleGetScheduledEntryPoint, m_func));
m_lowererMD.LoadHelperArgument(instr, IR::Opnd::CreateUint32Opnd(instr->AsJitProfilingInstr()->loopNumber, m_func));
m_lowererMD.LoadHelperArgument(instr, IR::Opnd::CreateFramePointerOpnd(m_func));
this->m_lowererMD.LowerCall(instr, 0);
}
break;
}
case Js::OpCode::ProfiledLoopBodyStart:
{
Assert(m_func->DoSimpleJitDynamicProfile());
const auto loopNum = instr->AsJitProfilingInstr()->loopNumber;
Assert(loopNum < m_func->GetJITFunctionBody()->GetLoopCount());
auto entryPointOpnd = instr->UnlinkSrc1();
auto dobailout = instr->UnlinkDst();
const auto dobailoutType = TyUint8;
Assert(dobailout->GetType() == TyUint8 && sizeof(decltype(Js::SimpleJitHelpers::IsLoopCodeGenDone(nullptr))) == 1);
m_lowererMD.LoadHelperArgument(instr, IR::IntConstOpnd::New(0, TyUint32, m_func)); // zero indicates that we do not want to add flags back in
m_lowererMD.LoadHelperArgument(instr, IR::IntConstOpnd::New(loopNum, TyUint32, m_func));
m_lowererMD.LoadHelperArgument(instr, IR::Opnd::CreateFramePointerOpnd(m_func));
instr->SetSrc1(IR::HelperCallOpnd::New(IR::HelperSimpleRecordLoopImplicitCallFlags, m_func));
m_lowererMD.LowerCall(instr, 0);
// Outline of JITed code:
//
// LoopStart:
// entryPoint = GetScheduledEntryPoint(framePtr, loopNum)
// LoopBodyStart:
// uint8 dobailout;
// if (entryPoint) {
// dobailout = IsLoopCodeGenDone(entryPoint)
// } else {
// dobailout = ++interpretCount >= threshold
// }
// // already exists from IRBuilding:
// if (dobailout) {
// Bailout
// }
if (PHASE_OFF(Js::FullJitPhase, m_func) || !m_func->GetJITFunctionBody()->DoJITLoopBody())
{
// If we're not doing fulljit, we've turned off JitLoopBodies, or if we don't have loop headers allocated (the function has a Try, etc)
// just move false to dobailout
this->InsertMove(dobailout, IR::IntConstOpnd::New(0, dobailoutType, m_func, true), instr->m_next);
}
else if (m_func->GetWorkItem()->GetJITTimeInfo()->ForceJITLoopBody())
{
// If we're forcing jit loop bodies, move true to dobailout
this->InsertMove(dobailout, IR::IntConstOpnd::New(1, dobailoutType, m_func, true), instr->m_next);
}
else
{
// Put in the labels
auto entryPointIsNull = IR::LabelInstr::New(Js::OpCode::Label, m_func);
auto checkDoBailout = IR::LabelInstr::New(Js::OpCode::Label, m_func);
instr->InsertAfter(checkDoBailout);
instr->InsertAfter(entryPointIsNull);
this->InsertCompareBranch(entryPointOpnd, IR::AddrOpnd::New(nullptr, IR::AddrOpndKindDynamicMisc, m_func), Js::OpCode::BrEq_A, false, entryPointIsNull, instr->m_next);
// If the entry point is not null
auto isCodeGenDone = IR::Instr::New(Js::OpCode::Call, dobailout, IR::HelperCallOpnd::New(IR::HelperSimpleIsLoopCodeGenDone, m_func), m_func);
entryPointIsNull->InsertBefore(isCodeGenDone);
m_lowererMD.LoadHelperArgument(isCodeGenDone, entryPointOpnd);
m_lowererMD.LowerCall(isCodeGenDone, 0);
this->InsertBranch(LowererMD::MDUncondBranchOpcode, true, checkDoBailout, entryPointIsNull);
const auto type = TyUint32;
auto countReg = IR::RegOpnd::New(type, m_func);
auto countAddr = IR::MemRefOpnd::New(m_func->GetJITFunctionBody()->GetLoopHeaderAddr(loopNum) + Js::LoopHeader::GetOffsetOfInterpretCount(), type, m_func);
IR::AutoReuseOpnd a(countReg, m_func), b(countAddr, m_func);
this->InsertAdd(false, countReg, countAddr, IR::IntConstOpnd::New(1, type, m_func, true), checkDoBailout);
this->InsertMove(countAddr, countReg, checkDoBailout);
this->InsertMove(dobailout, IR::IntConstOpnd::New(0, dobailoutType, m_func, true), checkDoBailout);
this->InsertCompareBranch(countReg, IR::IntConstOpnd::New(m_func->GetJITFunctionBody()->GetLoopHeaderData(loopNum)->interpretCount, type, m_func), Js::OpCode::BrLt_A, checkDoBailout, checkDoBailout);
this->InsertMove(dobailout, IR::IntConstOpnd::New(1, dobailoutType, m_func, true), checkDoBailout);
// fallthrough
// Label checkDoBailout (inserted above)
}
}
break;
case Js::OpCode::ProfiledLoopEnd:
{
Assert(m_func->DoSimpleJitDynamicProfile());
// This is set up in IRBuilding
Assert(instr->GetSrc1());
IR::Opnd* savedFlags = instr->UnlinkSrc1();
m_lowererMD.LoadHelperArgument(instr, savedFlags);
m_lowererMD.LoadHelperArgument(instr, IR::Opnd::CreateUint32Opnd(instr->AsJitProfilingInstr()->loopNumber, m_func));
m_lowererMD.LoadHelperArgument(instr, IR::Opnd::CreateFramePointerOpnd(m_func));
instr->SetSrc1(IR::HelperCallOpnd::New(IR::HelperSimpleRecordLoopImplicitCallFlags, m_func));
m_lowererMD.LowerCall(instr, 0);
}
break;
case Js::OpCode::InitLoopBodyCount:
Assert(this->m_func->IsLoopBody());
instr->SetSrc1(IR::IntConstOpnd::New(0, TyUint32, this->m_func));
this->m_lowererMD.ChangeToAssign(instr);
break;
case Js::OpCode::StLoopBodyCount:
Assert(this->m_func->IsLoopBody());
this->LowerStLoopBodyCount(instr);
break;
case Js::OpCode::IncrLoopBodyCount:
{
Assert(this->m_func->IsLoopBody());
instr->m_opcode = Js::OpCode::Add_I4;
instr->SetSrc2(IR::IntConstOpnd::New(1, TyUint32, this->m_func));
this->m_lowererMD.EmitInt4Instr(instr);
// Update the jittedLoopIterations field on the entryPointInfo
IR::MemRefOpnd *iterationsAddressOpnd = IR::MemRefOpnd::New(this->m_func->GetJittedLoopIterationsSinceLastBailoutAddress(), TyUint32, this->m_func);
InsertMove(iterationsAddressOpnd, instr->GetDst(), instr);
break;
}
#if !FLOATVAR
case Js::OpCode::StSlotBoxTemp:
this->LowerStSlotBoxTemp(instr);
break;
#endif
case Js::OpCode::LdSlot:
{
PropertySym *propertySym = instr->GetSrc1()->AsSymOpnd()->m_sym->AsPropertySym();
instrPrev = AddSlotArrayCheck(propertySym, instr);
}
case Js::OpCode::LdSlotArr:
{
Js::ProfileId profileId;
IR::Instr *profileBeforeInstr;
if (instr->IsJitProfilingInstr())
{
profileId = instr->AsJitProfilingInstr()->profileId;
Assert(profileId != Js::Constants::NoProfileId);
profileBeforeInstr = instr->m_next;
}
else
{
profileId = Js::Constants::NoProfileId;
profileBeforeInstr = nullptr;
}
this->LowerLdSlot(instr);
if (profileId != Js::Constants::NoProfileId)
{
LowerProfileLdSlot(instr->GetDst(), instr->m_func, profileId, profileBeforeInstr);
}
break;
}
case Js::OpCode::ChkUndecl:
instrPrev = this->LowerChkUndecl(instr);
break;
case Js::OpCode::LdArrHead:
this->LowerLdArrHead(instr);
break;
case Js::OpCode::StElemC:
case Js::OpCode::StArrSegElemC:
this->LowerStElemC(instr);
break;
case Js::OpCode::LdEnv:
instrPrev = this->LowerLdEnv(instr);
break;
case Js::OpCode::LdAsmJsEnv:
instrPrev = this->LowerLdAsmJsEnv(instr);
break;
case Js::OpCode::LdElemUndef:
this->LowerLdElemUndef(instr);
break;
case Js::OpCode::LdElemUndefScoped:
this->LowerElementUndefinedScopedMem(instr, IR::HelperOp_LdElemUndefScoped);
break;
case Js::OpCode::EnsureNoRootFld:
this->LowerElementUndefined(instr, IR::HelperOp_EnsureNoRootProperty);
break;
case Js::OpCode::EnsureNoRootRedeclFld:
this->LowerElementUndefined(instr, IR::HelperOp_EnsureNoRootRedeclProperty);
break;
case Js::OpCode::EnsureCanDeclGloFunc:
this->LowerElementUndefined(instr, IR::HelperOp_EnsureCanDeclGloFunc);
break;
case Js::OpCode::ScopedEnsureNoRedeclFld:
this->LowerElementUndefinedScoped(instr, IR::HelperOp_EnsureNoRedeclPropertyScoped);
break;
case Js::OpCode::LdFuncExpr:
// src = function Expression
LoadFuncExpression(instr);
this->GenerateGetCurrentFunctionObject(instr);
break;
case Js::OpCode::LdNewTarget:
this->GenerateLoadNewTarget(instr);
break;
case Js::OpCode::ChkNewCallFlag:
this->GenerateCheckForCallFlagNew(instr);
break;
case Js::OpCode::StFuncExpr:
// object.propid = src
LowerStFld(instr, IR::HelperOp_StFunctionExpression, IR::HelperOp_StFunctionExpression, false);
break;
case Js::OpCode::InitLetFld:
case Js::OpCode::InitRootLetFld:
LowerStFld(instr, IR::HelperOp_InitLetFld, IR::HelperOp_InitLetFld, false);
break;
case Js::OpCode::InitConstFld:
case Js::OpCode::InitRootConstFld:
LowerStFld(instr, IR::HelperOp_InitConstFld, IR::HelperOp_InitConstFld, false);
break;
case Js::OpCode::InitUndeclRootLetFld:
LowerElementUndefined(instr, IR::HelperOp_InitUndeclRootLetFld);
break;
case Js::OpCode::InitUndeclRootConstFld:
LowerElementUndefined(instr, IR::HelperOp_InitUndeclRootConstFld);
break;
case Js::OpCode::InitUndeclConsoleLetFld:
LowerElementUndefined(instr, IR::HelperOp_InitUndeclConsoleLetFld);
break;
case Js::OpCode::InitUndeclConsoleConstFld:
LowerElementUndefined(instr, IR::HelperOp_InitUndeclConsoleConstFld);
break;
case Js::OpCode::InitClassMember:
LowerStFld(instr, IR::HelperOp_InitClassMember, IR::HelperOp_InitClassMember, false);
break;
case Js::OpCode::InitClassMemberComputedName:
instrPrev = this->LowerStElemI(instr, Js::PropertyOperation_None, false, IR::HelperOp_InitClassMemberComputedName);
break;
case Js::OpCode::InitClassMemberGetComputedName:
instrPrev = this->LowerStElemI(instr, Js::PropertyOperation_None, false, IR::HelperOp_InitClassMemberGetComputedName);
break;
case Js::OpCode::InitClassMemberSetComputedName:
instrPrev = this->LowerStElemI(instr, Js::PropertyOperation_None, false, IR::HelperOp_InitClassMemberSetComputedName);
break;
case Js::OpCode::InitClassMemberGet:
instrPrev = this->LowerStFld(instr, IR::HelperOp_InitClassMemberGet, IR::HelperOp_InitClassMemberGet, false);
break;
case Js::OpCode::InitClassMemberSet:
instrPrev = this->LowerStFld(instr, IR::HelperOp_InitClassMemberSet, IR::HelperOp_InitClassMemberSet, false);
break;
case Js::OpCode::NewStackFrameDisplay:
this->LowerLdFrameDisplay(instr, m_func->DoStackFrameDisplay());
break;
case Js::OpCode::LdFrameDisplay:
this->LowerLdFrameDisplay(instr, false);
break;
case Js::OpCode::LdInnerFrameDisplay:
this->LowerLdInnerFrameDisplay(instr);
break;
case Js::OpCode::Throw:
case Js::OpCode::InlineThrow:
case Js::OpCode::EHThrow:
this->LowerUnaryHelperMem(instr, IR::HelperOp_Throw);
break;
case Js::OpCode::TryCatch:
instrPrev = this->LowerTry(instr, true /*try-catch*/);
break;
case Js::OpCode::TryFinally:
instrPrev = this->LowerTry(instr, false /*try-finally*/);
break;
case Js::OpCode::Catch:
instrPrev = this->LowerCatch(instr);
break;
case Js::OpCode::Finally:
instr->Remove();
break;
case Js::OpCode::LeaveNull:
if (this->m_func->DoOptimizeTry() || (this->m_func->IsSimpleJit() && this->m_func->hasBailout))
{
instr->Remove();
}
else
{
instrPrev = m_lowererMD.LowerLeaveNull(instr);
}
break;
case Js::OpCode::Leave:
if (this->m_func->HasTry() && this->m_func->DoOptimizeTry())
{
// Required in Register Allocator to mark region boundaries
break;
}
instrPrev = this->LowerLeave(instr, instr->AsBranchInstr()->GetTarget(), false /*fromFinalLower*/, instr->AsBranchInstr()->m_isOrphanedLeave);
break;
case Js::OpCode::BailOnException:
instrPrev = this->LowerBailOnException(instr);
break;
case Js::OpCode::BailOnEarlyExit:
instrPrev = this->LowerBailOnEarlyExit(instr);
break;
case Js::OpCode::RuntimeTypeError:
case Js::OpCode::InlineRuntimeTypeError:
this->LowerUnaryHelperMem(instr, IR::HelperOp_RuntimeTypeError);
break;
case Js::OpCode::RuntimeReferenceError:
case Js::OpCode::InlineRuntimeReferenceError:
this->LowerUnaryHelperMem(instr, IR::HelperOp_RuntimeReferenceError);
break;
case Js::OpCode::Break:
// Inline breakpoint: for now do nothing.
break;
case Js::OpCode::Nop:
// This may need support for debugging the JIT, but for now just remove the instruction.
instr->Remove();
break;
case Js::OpCode::Unused:
// Currently Unused is used with ScopedLdInst to keep the second dst alive, but we don't need to lower it.
instr->Remove();
break;
case Js::OpCode::StatementBoundary:
// This instruction is merely to help convey source info through the IR
// and eventually generate the nativeOffset maps.
#if DBG_DUMP && DBG
// If we have a JITStatementBreakpoint, then we should break on this statement
{
uint32 statementIndex = instr->AsPragmaInstr()->m_statementIndex;
if (Js::Configuration::Global.flags.StatementDebugBreak.Contains(instr->m_func->GetSourceContextId(), instr->m_func->GetLocalFunctionId(), statementIndex))
{
IR::Instr* tempinstr = instr;
Assert(tempinstr != nullptr);
// go past any labels, and then add a debug breakpoint
while (tempinstr->m_next != nullptr && tempinstr->m_next->m_opcode == Js::OpCode::Label)
{
tempinstr = tempinstr->m_next;
}
this->m_lowererMD.GenerateDebugBreak(tempinstr);
}
}
#endif
break;
case Js::OpCode::BailOnNotPolymorphicInlinee:
instrPrev = LowerBailOnNotPolymorphicInlinee(instr);
break;
case Js::OpCode::BailOnNoSimdTypeSpec:
case Js::OpCode::BailOnNoProfile:
this->GenerateBailOut(instr, nullptr, nullptr);
break;
case Js::OpCode::BailOnNotSpreadable:
instrPrev = this->LowerBailOnNotSpreadable(instr);
break;
case Js::OpCode::BailOnNotStackArgs:
instrPrev = this->LowerBailOnNotStackArgs(instr);
break;
case Js::OpCode::BailOnEqual:
case Js::OpCode::BailOnNotEqual:
instrPrev = this->LowerBailOnEqualOrNotEqual(instr);
break;
case Js::OpCode::BailOnNegative:
LowerBailOnNegative(instr);
break;
#ifdef ENABLE_SCRIPT_DEBUGGING
case Js::OpCode::BailForDebugger:
instrPrev = this->LowerBailForDebugger(instr);
break;
#endif
case Js::OpCode::BailOnNotObject:
instrPrev = this->LowerBailOnNotObject(instr);
break;
case Js::OpCode::CheckIsFuncObj:
instrPrev = this->LowerCheckIsFuncObj(instr);
break;
case Js::OpCode::CheckFuncInfo:
instrPrev = this->LowerCheckIsFuncObj(instr, true);
break;
case Js::OpCode::BailOnNotBuiltIn:
instrPrev = this->LowerBailOnNotBuiltIn(instr);
break;
case Js::OpCode::BailOnNotArray:
{
IR::Instr *bailOnNotArray = nullptr, *bailOnMissingValue = nullptr;
SplitBailOnNotArray(instr, &bailOnNotArray, &bailOnMissingValue);
IR::RegOpnd *const arrayOpnd = LowerBailOnNotArray(bailOnNotArray);
if (bailOnMissingValue)
{
LowerBailOnMissingValue(bailOnMissingValue, arrayOpnd);
}
break;
}
case Js::OpCode::BoundCheck:
case Js::OpCode::UnsignedBoundCheck:
LowerBoundCheck(instr);
break;
case Js::OpCode::BailTarget:
instrPrev = this->LowerBailTarget(instr);
break;
case Js::OpCode::InlineeStart:
this->LowerInlineeStart(instr);
break;
case Js::OpCode::EndCallForPolymorphicInlinee:
instr->Remove();
break;
case Js::OpCode::InlineeEnd:
this->LowerInlineeEnd(instr);
break;
case Js::OpCode::InlineBuiltInEnd:
case Js::OpCode::InlineNonTrackingBuiltInEnd:
this->LowerInlineBuiltIn(instr);
break;
case Js::OpCode::ExtendArg_A:
if (instr->GetSrc1()->IsRegOpnd())
{
IR::RegOpnd *src1 = instr->GetSrc1()->AsRegOpnd();
this->addToLiveOnBackEdgeSyms->Clear(src1->m_sym->m_id);
}
instr->Remove();
break;
case Js::OpCode::InlineBuiltInStart:
case Js::OpCode::BytecodeArgOutUse:
case Js::OpCode::ArgOut_A_InlineBuiltIn:
instr->Remove();
break;
case Js::OpCode::DeadBrEqual:
this->LowerBinaryHelperMem(instr, IR::HelperOp_Equal);
break;
case Js::OpCode::DeadBrSrEqual:
this->LowerBinaryHelperMem(instr, IR::HelperOp_StrictEqual);
break;
case Js::OpCode::DeadBrRelational:
this->LowerBinaryHelperMem(instr, IR::HelperOp_Greater);
break;
case Js::OpCode::DeadBrOnHasProperty:
this->LowerUnaryHelperMem(instr, IR::HelperOp_HasProperty);
break;
case Js::OpCode::DeletedNonHelperBranch:
break;
case Js::OpCode::InitClass:
instrPrev = this->LowerInitClass(instr);
break;
case Js::OpCode::NewConcatStrMulti:
this->LowerNewConcatStrMulti(instr);
break;
case Js::OpCode::NewConcatStrMultiBE:
this->LowerNewConcatStrMultiBE(instr);
break;
case Js::OpCode::SetConcatStrMultiItem:
this->LowerSetConcatStrMultiItem(instr);
break;
case Js::OpCode::SetConcatStrMultiItemBE:
Assert(instr->GetSrc1()->IsRegOpnd());
this->addToLiveOnBackEdgeSyms->Clear(instr->GetSrc1()->GetStackSym()->m_id);
// code corresponding to it should already have been generated while lowering NewConcatStrMultiBE
instr->Remove();
break;
case Js::OpCode::Conv_Str:
this->LowerConvStr(instr);
break;
case Js::OpCode::Coerce_Str:
this->LowerCoerseStr(instr);
break;
case Js::OpCode::Coerce_StrOrRegex:
this->LowerCoerseStrOrRegex(instr);
break;
case Js::OpCode::Coerce_Regex:
this->LowerCoerseRegex(instr);
break;
case Js::OpCode::Conv_PrimStr:
this->LowerConvPrimStr(instr);
break;
case Js::OpCode::ClearAttributes:
this->LowerBinaryHelper(instr, IR::HelperOP_ClearAttributes);
break;
case Js::OpCode::SpreadArrayLiteral:
this->LowerSpreadArrayLiteral(instr);
break;
case Js::OpCode::CallIExtended:
{
// Currently, the only use for CallIExtended is a call that uses spread.
Assert(IsSpreadCall(instr));
instrPrev = this->LowerSpreadCall(instr, Js::CallFlags_None);
break;
}
case Js::OpCode::CallIExtendedNew:
{
// Currently, the only use for CallIExtended is a call that uses spread.
Assert(IsSpreadCall(instr));
instrPrev = this->LowerSpreadCall(instr, Js::CallFlags_New);
break;
}
case Js::OpCode::CallIExtendedNewTargetNew:
{
// Currently, the only use for CallIExtended is a call that uses spread.
Assert(IsSpreadCall(instr));
instrPrev = this->LowerSpreadCall(instr, (Js::CallFlags)(Js::CallFlags_New | Js::CallFlags_ExtraArg | Js::CallFlags_NewTarget));
break;
}
case Js::OpCode::LdSpreadIndices:
instr->Remove();
break;
case Js::OpCode::LdHomeObj:
this->GenerateLdHomeObj(instr);
break;
case Js::OpCode::LdHomeObjProto:
this->GenerateLdHomeObjProto(instr);
break;
case Js::OpCode::LdFuncObj:
this->GenerateLdFuncObj(instr);
break;
case Js::OpCode::LdFuncObjProto:
this->GenerateLdFuncObjProto(instr);
break;
case Js::OpCode::ImportCall:
{
IR::Opnd *src1Opnd = instr->UnlinkSrc1();
IR::Opnd *functionObjOpnd = nullptr;
m_lowererMD.LoadFunctionObjectOpnd(instr, functionObjOpnd);
LoadScriptContext(instr);
m_lowererMD.LoadHelperArgument(instr, src1Opnd);
m_lowererMD.LoadHelperArgument(instr, functionObjOpnd);
m_lowererMD.ChangeToHelperCall(instr, IR::HelperImportCall);
break;
}
case Js::OpCode::SetComputedNameVar:
{
IR::Opnd *src2Opnd = instr->UnlinkSrc2();
IR::Opnd *src1Opnd = instr->UnlinkSrc1();
m_lowererMD.LoadHelperArgument(instr, src2Opnd);
m_lowererMD.LoadHelperArgument(instr, src1Opnd);
m_lowererMD.ChangeToHelperCall(instr, IR::HelperSetComputedNameVar);
break;
}
case Js::OpCode::InlineeMetaArg:
{
m_lowererMD.ChangeToAssign(instr);
break;
}
case Js::OpCode::Yield:
{
instr->FreeSrc1(); // Source is not actually used by the backend other than to calculate lifetime
IR::Opnd* dstOpnd = instr->UnlinkDst();
// prm2 is the ResumeYieldData pointer per calling convention established in JavascriptGenerator::CallGenerator
// This is the value the bytecode expects to be in the dst register of the Yield opcode after resumption.
// Load it here after the bail-in.
StackSym *resumeYieldDataSym = StackSym::NewImplicitParamSym(4, m_func);
m_func->SetArgOffset(resumeYieldDataSym, (LowererMD::GetFormalParamOffset() + 1) * MachPtr);
IR::SymOpnd * resumeYieldDataOpnd = IR::SymOpnd::New(resumeYieldDataSym, TyMachPtr, m_func);
AssertMsg(instr->m_next->IsLabelInstr(), "Expect the resume label to immediately follow Yield instruction");
InsertMove(dstOpnd, resumeYieldDataOpnd, instr->m_next->m_next);
GenerateBailOut(instr);
break;
}
case Js::OpCode::ResumeYield:
case Js::OpCode::ResumeYieldStar:
{
IR::Opnd *srcOpnd1 = instr->UnlinkSrc1();
IR::Opnd *srcOpnd2 = instr->m_opcode == Js::OpCode::ResumeYieldStar ? instr->UnlinkSrc2() : IR::AddrOpnd::NewNull(m_func);
m_lowererMD.LoadHelperArgument(instr, srcOpnd2);
m_lowererMD.LoadHelperArgument(instr, srcOpnd1);
m_lowererMD.ChangeToHelperCall(instr, IR::HelperResumeYield);
break;
}
case Js::OpCode::GeneratorResumeJumpTable:
{
// Lowered in LowerPrologEpilog so that the jumps introduced are not considered to be part of the flow for the RegAlloc phase.
// Introduce a BailOutNoSave label if there were yield points that were elided due to optimizations. They could still be hit
// if an active generator object had been paused at such a yield point when the function body was JITed. So safe guard such a
// case by having the native code simply jump back to the interpreter for such yield points.
IR::LabelInstr *bailOutNoSaveLabel = nullptr;
m_func->MapUntilYieldOffsetResumeLabels([this, &bailOutNoSaveLabel](int, const YieldOffsetResumeLabel& yorl)
{
if (yorl.Second() == nullptr)
{
if (bailOutNoSaveLabel == nullptr)
{
bailOutNoSaveLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func);
}
return true;
}
return false;
});
// Insert the bailoutnosave label somewhere along with a call to BailOutNoSave helper
if (bailOutNoSaveLabel != nullptr)
{
IR::Instr * exitPrevInstr = this->m_func->m_exitInstr->m_prev;
IR::LabelInstr * exitTargetInstr;
if (exitPrevInstr->IsLabelInstr())
{
exitTargetInstr = exitPrevInstr->AsLabelInstr();
exitPrevInstr = exitPrevInstr->m_prev;
}
else
{
exitTargetInstr = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, false);
exitPrevInstr->InsertAfter(exitTargetInstr);
}
bailOutNoSaveLabel->m_hasNonBranchRef = true;
bailOutNoSaveLabel->isOpHelper = true;
IR::Instr* bailOutCall = IR::Instr::New(Js::OpCode::Call, m_func);
exitPrevInstr->InsertAfter(bailOutCall);
exitPrevInstr->InsertAfter(bailOutNoSaveLabel);
exitPrevInstr->InsertAfter(IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, exitTargetInstr, m_func));
IR::RegOpnd * frameRegOpnd = IR::RegOpnd::New(nullptr, LowererMD::GetRegFramePointer(), TyMachPtr, m_func);
m_lowererMD.LoadHelperArgument(bailOutCall, frameRegOpnd);
m_lowererMD.ChangeToHelperCall(bailOutCall, IR::HelperNoSaveRegistersBailOutForElidedYield);
m_func->m_bailOutNoSaveLabel = bailOutNoSaveLabel;
}
break;
}
case Js::OpCode::FrameDisplayCheck:
instrPrev = this->LowerFrameDisplayCheck(instr);
break;
case Js::OpCode::SlotArrayCheck:
instrPrev = this->LowerSlotArrayCheck(instr);
break;
#if DBG
case Js::OpCode::CheckLowerIntBound:
instrPrev = this->LowerCheckLowerIntBound(instr);
break;
case Js::OpCode::CheckUpperIntBound:
instrPrev = this->LowerCheckUpperIntBound(instr);
break;
#endif
#ifdef ENABLE_WASM
case Js::OpCode::Copysign_A:
m_lowererMD.GenerateCopysign(instr);
break;
case Js::OpCode::Trunc_A:
if (!AutoSystemInfo::Data.SSE4_1Available())
{
m_lowererMD.HelperCallForAsmMathBuiltin(instr, IR::HelperDirectMath_TruncFlt, IR::HelperDirectMath_TruncDb);
break;
}
m_lowererMD.GenerateFastInlineBuiltInCall(instr, (IR::JnHelperMethod)0);
break;
case Js::OpCode::Nearest_A:
if (!AutoSystemInfo::Data.SSE4_1Available())
{
m_lowererMD.HelperCallForAsmMathBuiltin(instr, IR::HelperDirectMath_NearestFlt, IR::HelperDirectMath_NearestDb);
break;
}
m_lowererMD.GenerateFastInlineBuiltInCall(instr, (IR::JnHelperMethod)0);
break;
case Js::OpCode::ThrowRuntimeError:
GenerateThrow(instr->UnlinkSrc1(), instr);
instr->Remove();
break;
#endif //ENABLE_WASM
case Js::OpCode::SpeculatedLoadFence:
{
AssertOrFailFast(instr->m_kind == IR::InstrKindByteCodeUses);
#ifdef _M_ARM
AssertOrFailFastMsg(false, "We shouldn't perform this hoisting on ARM");
#else
IR::ByteCodeUsesInstr* bcuInstr = static_cast<IR::ByteCodeUsesInstr*>(instr);
// Most of the time we're not going to be able to remove any masking in a loop, and
// this instruction can be removed.
if (bcuInstr->GetByteCodeUpwardExposedUsed() != nullptr && !bcuInstr->GetByteCodeUpwardExposedUsed()->IsEmpty())
{
// The generated code is:
//
// cmp rax, rax
// for each symbol to mask:
// reg(sym) = cmovne reg(sym), reg(sym)
IR::RegOpnd* temp = IR::RegOpnd::New(TyUint8, instr->m_func);
InsertMove(temp, IR::IntConstOpnd::New(0, TyUint8, instr->m_func), instr);
IR::Instr * cmp = IR::Instr::New(Js::OpCode::CMP, instr->m_func);
cmp->SetSrc1(temp);
cmp->SetSrc2(temp);
instr->InsertBefore(cmp);
m_lowererMD.Legalize(cmp);
FOREACH_BITSET_IN_SPARSEBV(symid, bcuInstr->GetByteCodeUpwardExposedUsed())
{
StackSym* thisSym = instr->m_func->m_symTable->Find(symid)->AsStackSym();
IR::RegOpnd* thisSymReg = IR::RegOpnd::New(thisSym, thisSym->GetType(), instr->m_func);
Js::OpCode specBlockOp = thisSymReg->IsFloat() ? LowererMD::MDSpecBlockFNEOpcode : LowererMD::MDSpecBlockNEOpcode;
IR::Instr* cmov = IR::Instr::New(specBlockOp, thisSymReg, thisSymReg, thisSymReg, instr->m_func);
instr->InsertBefore(cmov);
m_lowererMD.Legalize(cmov);
} NEXT_BITSET_IN_SPARSEBV;
}
#endif
instr->Remove();
break;
}
case Js::OpCode::SpreadObjectLiteral:
this->LowerBinaryHelperMem(instr, IR::HelperSpreadObjectLiteral);
break;
case Js::OpCode::Restify:
instrPrev = this->LowerRestify(instr);
break;
case Js::OpCode::NewPropIdArrForCompProps:
this->LowerUnaryHelperMem(instr, IR::HelperNewPropIdArrForCompProps);
break;
case Js::OpCode::StPropIdArrFromVar:
instrPrev = this->LowerStPropIdArrFromVar(instr);
break;
default:
#ifdef ENABLE_WASM_SIMD
if (IsSimd128Opcode(instr->m_opcode))
{
instrPrev = m_lowererMD.Simd128Instruction(instr);
break;
}
#endif
AssertMsg(instr->IsLowered(), "Unknown opcode");
if(!instr->IsLowered())
{
Fatal();
}
break;
}
#if DBG
LegalizeVerifyRange(instrPrev ? instrPrev->m_next : instrStart,
verifyLegalizeInstrNext ? verifyLegalizeInstrNext->m_prev : nullptr);
this->helperCallCheckState = HelperCallCheckState_None;
#endif
} NEXT_INSTR_BACKWARD_EDITING_IN_RANGE;
Assert(this->outerMostLoopLabel == nullptr);
}
IR::Opnd *
Lowerer::LoadFunctionInfoOpnd(IR::Instr * instr)
{
return IR::AddrOpnd::New(instr->m_func->GetWorkItem()->GetJITTimeInfo()->GetFunctionInfoAddr(), IR::AddrOpndKindDynamicFunctionInfo, instr->m_func);
}
IR::Instr *
Lowerer::LoadFunctionBody(IR::Instr * instr)
{
return m_lowererMD.LoadHelperArgument(instr, LoadFunctionBodyOpnd(instr));
}
IR::Instr *
Lowerer::LoadScriptContext(IR::Instr * instr)
{
return m_lowererMD.LoadHelperArgument(instr, LoadScriptContextOpnd(instr));
}
IR::Opnd *
Lowerer::LoadFunctionBodyOpnd(IR::Instr * instr)
{
return IR::AddrOpnd::New(instr->m_func->GetJITFunctionBody()->GetAddr(), IR::AddrOpndKindDynamicFunctionBody, instr->m_func);
}
IR::Opnd *
Lowerer::LoadScriptContextOpnd(IR::Instr * instr)
{
return IR::AddrOpnd::New(m_func->GetScriptContextInfo()->GetAddr(), IR::AddrOpndKindDynamicScriptContext, this->m_func);
}
IR::Opnd *
Lowerer::LoadScriptContextValueOpnd(IR::Instr * instr, ScriptContextValue valueType)
{
ScriptContextInfo *scriptContextInfo = instr->m_func->GetScriptContextInfo();
switch (valueType)
{
case ScriptContextValue::ScriptContextNumberAllocator:
return IR::AddrOpnd::New(scriptContextInfo->GetNumberAllocatorAddr(), IR::AddrOpndKindDynamicMisc, instr->m_func);
case ScriptContextValue::ScriptContextRecycler:
return IR::AddrOpnd::New(scriptContextInfo->GetRecyclerAddr(), IR::AddrOpndKindDynamicMisc, instr->m_func);
default:
Assert(false);
return nullptr;
}
}
IR::Opnd *
Lowerer::LoadLibraryValueOpnd(IR::Instr * instr, LibraryValue valueType)
{
ScriptContextInfo *scriptContextInfo = instr->m_func->GetScriptContextInfo();
switch (valueType)
{
case LibraryValue::ValueEmptyString:
return IR::AddrOpnd::New(scriptContextInfo->GetEmptyStringAddr(), IR::AddrOpndKindDynamicVar, instr->m_func, true);
case LibraryValue::ValueUndeclBlockVar:
return IR::AddrOpnd::New(scriptContextInfo->GetUndeclBlockVarAddr(), IR::AddrOpndKindDynamicVar, instr->m_func, true);
case LibraryValue::ValueUndefined:
return IR::AddrOpnd::New(scriptContextInfo->GetUndefinedAddr(), IR::AddrOpndKindDynamicVar, instr->m_func, true);
case LibraryValue::ValueNull:
return IR::AddrOpnd::New(scriptContextInfo->GetNullAddr(), IR::AddrOpndKindDynamicVar, instr->m_func, true);
case LibraryValue::ValueTrue:
return IR::AddrOpnd::New(scriptContextInfo->GetTrueAddr(), IR::AddrOpndKindDynamicVar, instr->m_func, true);
case LibraryValue::ValueFalse:
return IR::AddrOpnd::New(scriptContextInfo->GetFalseAddr(), IR::AddrOpndKindDynamicVar, instr->m_func, true);
case LibraryValue::ValueNegativeZero:
return IR::AddrOpnd::New(scriptContextInfo->GetNegativeZeroAddr(), IR::AddrOpndKindDynamicVar, instr->m_func, true);
case LibraryValue::ValueNumberTypeStatic:
return IR::AddrOpnd::New(scriptContextInfo->GetNumberTypeStaticAddr(), IR::AddrOpndKindDynamicType, instr->m_func, true);
case LibraryValue::ValueStringTypeStatic:
return IR::AddrOpnd::New(scriptContextInfo->GetStringTypeStaticAddr(), IR::AddrOpndKindDynamicType, instr->m_func, true);
case LibraryValue::ValueSymbolTypeStatic:
return IR::AddrOpnd::New(scriptContextInfo->GetSymbolTypeStaticAddr(), IR::AddrOpndKindDynamicType, instr->m_func, true);
case LibraryValue::ValueObjectType:
return IR::AddrOpnd::New(scriptContextInfo->GetObjectTypeAddr(), IR::AddrOpndKindDynamicType, instr->m_func);
case LibraryValue::ValueObjectHeaderInlinedType:
return IR::AddrOpnd::New(scriptContextInfo->GetObjectHeaderInlinedTypeAddr(), IR::AddrOpndKindDynamicType, instr->m_func);
case LibraryValue::ValueRegexType:
return IR::AddrOpnd::New(scriptContextInfo->GetRegexTypeAddr(), IR::AddrOpndKindDynamicType, instr->m_func);
case LibraryValue::ValueArrayConstructor:
return IR::AddrOpnd::New(scriptContextInfo->GetArrayConstructorAddr(), IR::AddrOpndKindDynamicVar, instr->m_func);
case LibraryValue::ValueJavascriptArrayType:
return IR::AddrOpnd::New(scriptContextInfo->GetArrayTypeAddr(), IR::AddrOpndKindDynamicType, instr->m_func);
case LibraryValue::ValueNativeIntArrayType:
return IR::AddrOpnd::New(scriptContextInfo->GetNativeIntArrayTypeAddr(), IR::AddrOpndKindDynamicType, instr->m_func);
case LibraryValue::ValueNativeFloatArrayType:
return IR::AddrOpnd::New(scriptContextInfo->GetNativeFloatArrayTypeAddr(), IR::AddrOpndKindDynamicType, instr->m_func);
case LibraryValue::ValueConstructorCacheDefaultInstance:
return IR::AddrOpnd::New(m_func->GetThreadContextInfo()->GetConstructorCacheDefaultInstanceAddr(), IR::AddrOpndKindDynamicMisc, instr->m_func);
case LibraryValue::ValueAbsDoubleCst:
return IR::MemRefOpnd::New(m_func->GetThreadContextInfo()->GetAbsDoubleCstAddr(), TyMachDouble, instr->m_func, IR::AddrOpndKindDynamicDoubleRef);
case LibraryValue::ValueCharStringCache:
return IR::AddrOpnd::New(scriptContextInfo->GetCharStringCacheAddr(), IR::AddrOpndKindDynamicCharStringCache, instr->m_func);
default:
Assert(UNREACHED);
return nullptr;
}
}
IR::Opnd *
Lowerer::LoadVTableValueOpnd(IR::Instr * instr, VTableValue vtableType)
{
return IR::AddrOpnd::New((Js::Var)instr->m_func->GetScriptContextInfo()->GetVTableAddress(vtableType), IR::AddrOpndKindDynamicVtable, this->m_func);
}
IR::Opnd *
Lowerer::LoadOptimizationOverridesValueOpnd(IR::Instr *instr, OptimizationOverridesValue valueType)
{
switch (valueType)
{
case OptimizationOverridesValue::OptimizationOverridesSideEffects:
return IR::MemRefOpnd::New(m_func->GetScriptContextInfo()->GetSideEffectsAddr(), TyInt32, instr->m_func);
case OptimizationOverridesValue::OptimizationOverridesArraySetElementFastPathVtable:
return IR::MemRefOpnd::New(m_func->GetScriptContextInfo()->GetArraySetElementFastPathVtableAddr(), TyMachPtr, instr->m_func);
case OptimizationOverridesValue::OptimizationOverridesIntArraySetElementFastPathVtable:
return IR::MemRefOpnd::New(m_func->GetScriptContextInfo()->GetIntArraySetElementFastPathVtableAddr(), TyMachPtr, instr->m_func);
case OptimizationOverridesValue::OptimizationOverridesFloatArraySetElementFastPathVtable:
return IR::MemRefOpnd::New(m_func->GetScriptContextInfo()->GetFloatArraySetElementFastPathVtableAddr(), TyMachPtr, instr->m_func);
default:
Assert(UNREACHED);
return nullptr;
}
}
IR::Opnd *
Lowerer::LoadNumberAllocatorValueOpnd(IR::Instr *instr, NumberAllocatorValue valueType)
{
ScriptContextInfo *scriptContext = instr->m_func->GetScriptContextInfo();
bool allowNativeCodeBumpAllocation = scriptContext->GetRecyclerAllowNativeCodeBumpAllocation();
switch (valueType)
{
case NumberAllocatorValue::NumberAllocatorEndAddress:
return IR::MemRefOpnd::New(((char *)scriptContext->GetNumberAllocatorAddr()) + Js::RecyclerJavascriptNumberAllocator::GetEndAddressOffset(), TyMachPtr, instr->m_func);
case NumberAllocatorValue::NumberAllocatorFreeObjectList:
return IR::MemRefOpnd::New(
((char *)scriptContext->GetNumberAllocatorAddr()) +
(allowNativeCodeBumpAllocation ? Js::RecyclerJavascriptNumberAllocator::GetFreeObjectListOffset() : Js::RecyclerJavascriptNumberAllocator::GetEndAddressOffset()),
TyMachPtr, instr->m_func);
default:
Assert(false);
return nullptr;
}
}
IR::Opnd *
Lowerer::LoadIsInstInlineCacheOpnd(IR::Instr * instr, uint inlineCacheIndex)
{
intptr_t inlineCache = instr->m_func->GetJITFunctionBody()->GetIsInstInlineCache(inlineCacheIndex);
return IR::AddrOpnd::New(inlineCache, IR::AddrOpndKindDynamicInlineCache, this->m_func);
}
IR::Opnd *
Lowerer::LoadRuntimeInlineCacheOpnd(IR::Instr * instr, IR::PropertySymOpnd * propertySymOpnd, bool isHelper)
{
Assert(propertySymOpnd->m_runtimeInlineCache != 0);
IR::Opnd * inlineCacheOpnd = nullptr;
if (instr->m_func->GetJITFunctionBody()->HasInlineCachesOnFunctionObject() && !instr->m_func->IsInlinee())
{
inlineCacheOpnd = this->GetInlineCacheFromFuncObjectForRuntimeUse(instr, propertySymOpnd, isHelper);
}
else
{
intptr_t inlineCache = propertySymOpnd->m_runtimeInlineCache;
inlineCacheOpnd = IR::AddrOpnd::New(inlineCache, IR::AddrOpndKindDynamicInlineCache, this->m_func, /* dontEncode */ true);
}
return inlineCacheOpnd;
}
bool
Lowerer::TryGenerateFastCmSrXx(IR::Instr * instr)
{
IR::RegOpnd *srcReg1 = instr->GetSrc1()->IsRegOpnd() ? instr->GetSrc1()->AsRegOpnd() : nullptr;
IR::RegOpnd *srcReg2 = instr->GetSrc2()->IsRegOpnd() ? instr->GetSrc2()->AsRegOpnd() : nullptr;
if (srcReg2 && IsConstRegOpnd(srcReg2))
{
return m_lowererMD.GenerateFastCmSrXxConst(instr);
}
else if (srcReg1 && IsConstRegOpnd(srcReg1))
{
instr->SwapOpnds();
return m_lowererMD.GenerateFastCmSrXxConst(instr);
}
return false;
}
// Generate fast path for StrictEquals when one of the sources are undefined, null, boolean
bool
Lowerer::TryGenerateFastBrSrXx(IR::Instr * instr, IR::RegOpnd * srcReg1, IR::RegOpnd * srcReg2, IR::Instr ** pInstrPrev, bool noMathFastPath)
{
bool isEqual = !instr->IsNeq();
if (srcReg2 && IsConstRegOpnd(srcReg2))
{
this->GenerateFastBrConst(instr->AsBranchInstr(), GetConstRegOpnd(srcReg2, instr), isEqual);
instr->Remove();
return true;
}
else if (srcReg1 && IsConstRegOpnd(srcReg1))
{
instr->SwapOpnds();
this->GenerateFastBrConst(instr->AsBranchInstr(), GetConstRegOpnd(srcReg1, instr), isEqual);
instr->Remove();
return true;
}
return false;
}
///----------------------------------------------------------------------------
///
/// Lowerer::GenerateFastBrConst
///
///----------------------------------------------------------------------------
IR::BranchInstr *
Lowerer::GenerateFastBrConst(IR::BranchInstr *branchInstr, IR::Opnd * constOpnd, bool isEqual)
{
Assert(constOpnd->IsAddrOpnd() || constOpnd->IsIntConstOpnd());
//
// Given:
// BrSrXx_A $L1, s1, s2
// where s2 is either 'null', 'undefined', 'true' or 'false'
//
// Generate:
//
// CMP s1, s2
// JEQ/JNE $L1
//
Assert(IsConstRegOpnd(branchInstr->GetSrc2()->AsRegOpnd()));
IR::RegOpnd *opnd = GetRegOpnd(branchInstr->GetSrc1(), branchInstr, m_func, TyVar);
IR::BranchInstr *newBranch;
newBranch = InsertCompareBranch(opnd, constOpnd, isEqual ? Js::OpCode::BrEq_A : Js::OpCode::BrNeq_A, branchInstr->GetTarget(), branchInstr);
return newBranch;
}
bool
Lowerer::TryGenerateFastBrEq(IR::Instr * instr)
{
IR::RegOpnd *srcReg1 = instr->GetSrc1()->IsRegOpnd() ? instr->GetSrc1()->AsRegOpnd() : nullptr;
IR::RegOpnd *srcReg2 = instr->GetSrc2()->IsRegOpnd() ? instr->GetSrc2()->AsRegOpnd() : nullptr;
bool isConst = false;
if (srcReg1 && this->IsNullOrUndefRegOpnd(srcReg1))
{
instr->SwapOpnds();
isConst = true;
}
// Fast path for == null or == undefined
// if (src == null || src == undefined)
if (isConst || (srcReg2 && this->IsNullOrUndefRegOpnd(srcReg2)))
{
IR::BranchInstr *newBranch;
newBranch = this->GenerateFastBrConst(instr->AsBranchInstr(),
this->LoadLibraryValueOpnd(instr, LibraryValue::ValueNull),
true);
this->GenerateFastBrConst(instr->AsBranchInstr(),
this->LoadLibraryValueOpnd(instr, LibraryValue::ValueUndefined),
true);
instr->Remove();
return true;
}
return false;
}
bool
Lowerer::TryGenerateFastBrNeq(IR::Instr * instr)
{
IR::RegOpnd *srcReg1 = instr->GetSrc1()->IsRegOpnd() ? instr->GetSrc1()->AsRegOpnd() : nullptr;
IR::RegOpnd *srcReg2 = instr->GetSrc2()->IsRegOpnd() ? instr->GetSrc2()->AsRegOpnd() : nullptr;
bool isConst = false;
if (srcReg1 && this->IsNullOrUndefRegOpnd(srcReg1))
{
instr->SwapOpnds();
isConst = true;
}
// Fast path for != null or != undefined
// if (src != null && src != undefined)
//
// That is:
// if (src == NULL) goto labelEq
// if (src != undef) goto target
// labelEq:
if (isConst || (srcReg2 && this->IsNullOrUndefRegOpnd(srcReg2)))
{
IR::LabelInstr *labelEq = instr->GetOrCreateContinueLabel();
IR::BranchInstr *newBranch;
newBranch = this->GenerateFastBrConst(instr->AsBranchInstr(),
this->LoadLibraryValueOpnd(instr, LibraryValue::ValueNull),
true);
newBranch->AsBranchInstr()->SetTarget(labelEq);
this->GenerateFastBrConst(instr->AsBranchInstr(),
this->LoadLibraryValueOpnd(instr, LibraryValue::ValueUndefined),
false);
instr->Remove();
return true;
}
return false;
}
void
Lowerer::GenerateDynamicObjectAlloc(IR::Instr * newObjInstr, uint inlineSlotCount, uint slotCount, IR::RegOpnd * newObjDst, IR::Opnd * typeSrc)
{
size_t headerAllocSize = sizeof(Js::DynamicObject) + inlineSlotCount * sizeof(Js::Var);
IR::SymOpnd * tempObjectSymOpnd;
bool isZeroed = GenerateRecyclerOrMarkTempAlloc(newObjInstr, newObjDst, IR::HelperAllocMemForScObject, headerAllocSize, &tempObjectSymOpnd);
if (tempObjectSymOpnd && !PHASE_OFF(Js::HoistMarkTempInitPhase, this->m_func) && this->outerMostLoopLabel)
{
// Hoist the vtable init to the outer most loop top as it never changes
InsertMove(tempObjectSymOpnd,
LoadVTableValueOpnd(this->outerMostLoopLabel, VTableValue::VtableDynamicObject), this->outerMostLoopLabel, false);
}
else
{
// MOV [newObjDst + offset(vtable)], DynamicObject::vtable
GenerateMemInit(newObjDst, 0, LoadVTableValueOpnd(newObjInstr, VTableValue::VtableDynamicObject), newObjInstr, isZeroed);
}
// MOV [newObjDst + offset(type)], newObjectType
GenerateMemInit(newObjDst, Js::DynamicObject::GetOffsetOfType(), typeSrc, newObjInstr, isZeroed);
// CALL JavascriptOperators::AllocMemForVarArray((slotCount - inlineSlotCount) * sizeof(Js::Var))
if (slotCount > inlineSlotCount)
{
size_t auxSlotsAllocSize = (slotCount - inlineSlotCount) * sizeof(Js::Var);
IR::RegOpnd* auxSlots = IR::RegOpnd::New(TyMachPtr, m_func);
GenerateRecyclerAllocAligned(IR::HelperAllocMemForVarArray, auxSlotsAllocSize, auxSlots, newObjInstr);
GenerateMemInit(newObjDst, Js::DynamicObject::GetOffsetOfAuxSlots(), auxSlots, newObjInstr, isZeroed);
IR::IndirOpnd* newObjAuxSlots = IR::IndirOpnd::New(newObjDst, Js::DynamicObject::GetOffsetOfAuxSlots(), TyMachPtr, m_func);
this->InsertMove(newObjAuxSlots, auxSlots, newObjInstr);
}
else
{
GenerateMemInitNull(newObjDst, Js::DynamicObject::GetOffsetOfAuxSlots(), newObjInstr, isZeroed);
}
GenerateMemInitNull(newObjDst, Js::DynamicObject::GetOffsetOfObjectArray(), newObjInstr, isZeroed);
}
void
Lowerer::LowerNewScObjectSimple(IR::Instr * instr)
{
GenerateDynamicObjectAlloc(
instr,
0,
0,
instr->UnlinkDst()->AsRegOpnd(),
LoadLibraryValueOpnd(
instr,
Js::FunctionBody::DoObjectHeaderInliningForEmptyObjects()
? LibraryValue::ValueObjectHeaderInlinedType
: LibraryValue::ValueObjectType));
instr->Remove();
}
void
Lowerer::LowerNewScObjectLiteral(IR::Instr *newObjInstr)
{
Func * func = m_func;
IR::IntConstOpnd * literalObjectIdOpnd = newObjInstr->UnlinkSrc2()->AsIntConstOpnd();
intptr_t literalTypeRef = newObjInstr->m_func->GetJITFunctionBody()->GetObjectLiteralTypeRef(literalObjectIdOpnd->AsUint32());
IR::LabelInstr * helperLabel = nullptr;
IR::LabelInstr * allocLabel = nullptr;
IR::Opnd * literalTypeRefOpnd;
IR::Opnd * literalTypeOpnd;
IR::Opnd * propertyArrayOpnd;
IR::IntConstOpnd * propertyArrayIdOpnd = newObjInstr->UnlinkSrc1()->AsIntConstOpnd();
const Js::PropertyIdArray * propIds = newObjInstr->m_func->GetJITFunctionBody()->ReadPropertyIdArrayFromAuxData(propertyArrayIdOpnd->AsUint32());
intptr_t propArrayAddr = newObjInstr->m_func->GetJITFunctionBody()->GetAuxDataAddr(propertyArrayIdOpnd->AsUint32());
uint inlineSlotCapacity = Js::JavascriptOperators::GetLiteralInlineSlotCapacity(propIds);
uint slotCapacity = Js::JavascriptOperators::GetLiteralSlotCapacity(propIds);
IR::RegOpnd * dstOpnd;
literalTypeRefOpnd = IR::AddrOpnd::New(literalTypeRef, IR::AddrOpndKindDynamicMisc, this->m_func);
propertyArrayOpnd = IR::AddrOpnd::New(propArrayAddr, IR::AddrOpndKindDynamicMisc, this->m_func);
//#if 0 TODO: OOP JIT, obj literal types
// should pass in isShared bit through RPC, enable for in-proc jit to see perf impact
Js::DynamicType * literalType = func->IsOOPJIT() || !CONFIG_FLAG(OOPJITMissingOpts) ? nullptr : *(Js::DynamicType **)literalTypeRef;
if (literalType == nullptr || !literalType->GetIsShared())
{
helperLabel = IR::LabelInstr::New(Js::OpCode::Label, func, true);
allocLabel = IR::LabelInstr::New(Js::OpCode::Label, func);
literalTypeOpnd = IR::RegOpnd::New(TyMachPtr, func);
InsertMove(literalTypeOpnd, IR::MemRefOpnd::New(literalTypeRef, TyMachPtr, func), newObjInstr);
InsertTestBranch(literalTypeOpnd, literalTypeOpnd,
Js::OpCode::BrEq_A, helperLabel, newObjInstr);
InsertTestBranch(IR::IndirOpnd::New(literalTypeOpnd->AsRegOpnd(), Js::DynamicType::GetOffsetOfIsShared(), TyInt8, func),
IR::IntConstOpnd::New(1, TyInt8, func, true), Js::OpCode::BrEq_A, helperLabel, newObjInstr);
dstOpnd = newObjInstr->GetDst()->AsRegOpnd();
}
else
{
literalTypeOpnd = IR::AddrOpnd::New(literalType, IR::AddrOpndKindDynamicType, func);
dstOpnd = newObjInstr->UnlinkDst()->AsRegOpnd();
Assert(inlineSlotCapacity == literalType->GetTypeHandler()->GetInlineSlotCapacity());
Assert(slotCapacity == (uint)literalType->GetTypeHandler()->GetSlotCapacity());
}
if (helperLabel)
{
InsertBranch(Js::OpCode::Br, allocLabel, newObjInstr);
// Slow path to ensure the type is there
newObjInstr->InsertBefore(helperLabel);
IR::HelperCallOpnd * opndHelper = IR::HelperCallOpnd::New(IR::HelperEnsureObjectLiteralType, func);
m_lowererMD.LoadHelperArgument(newObjInstr, literalTypeRefOpnd);
m_lowererMD.LoadHelperArgument(newObjInstr, propertyArrayOpnd);
LoadScriptContext(newObjInstr);
IR::Instr * ensureTypeInstr = IR::Instr::New(Js::OpCode::Call, literalTypeOpnd, opndHelper, func);
newObjInstr->InsertBefore(ensureTypeInstr);
m_lowererMD.LowerCall(ensureTypeInstr, 0);
newObjInstr->InsertBefore(allocLabel);
}
else
{
Assert(allocLabel == nullptr);
}
// For the next call:
// inlineSlotCapacity == Number of slots to allocate beyond the DynamicObject header
// slotCapacity - inlineSlotCapacity == Number of aux slots to allocate
if(Js::FunctionBody::DoObjectHeaderInliningForObjectLiteral(propIds))
{
Assert(inlineSlotCapacity >= Js::DynamicTypeHandler::GetObjectHeaderInlinableSlotCapacity());
Assert(inlineSlotCapacity == slotCapacity);
slotCapacity = inlineSlotCapacity -= Js::DynamicTypeHandler::GetObjectHeaderInlinableSlotCapacity();
}
GenerateDynamicObjectAlloc(
newObjInstr,
inlineSlotCapacity,
slotCapacity,
dstOpnd,
literalTypeOpnd);
newObjInstr->Remove();
}
IR::Instr*
Lowerer::LowerProfiledNewScArray(IR::JitProfilingInstr* arrInstr)
{
IR::Instr *instrPrev = arrInstr->m_prev;
/*
JavascriptArray *ProfilingHelpers::ProfiledNewScArray(
const uint length,
FunctionBody *const functionBody,
const ProfileId profileId)
*/
m_lowererMD.LoadHelperArgument(arrInstr, IR::Opnd::CreateProfileIdOpnd(arrInstr->profileId, m_func));
m_lowererMD.LoadHelperArgument(arrInstr, CreateFunctionBodyOpnd(arrInstr->m_func));
m_lowererMD.LoadHelperArgument(arrInstr, arrInstr->UnlinkSrc1());
arrInstr->SetSrc1(IR::HelperCallOpnd::New(IR::HelperProfiledNewScArray, m_func));
m_lowererMD.LowerCall(arrInstr, 0);
return instrPrev;
}
IR::Instr *
Lowerer::LowerNewScArray(IR::Instr *arrInstr)
{
if (arrInstr->IsJitProfilingInstr())
{
return LowerProfiledNewScArray(arrInstr->AsJitProfilingInstr());
}
IR::Instr *instrPrev = arrInstr->m_prev;
IR::JnHelperMethod helperMethod = IR::HelperScrArr_OP_NewScArray;
if (arrInstr->IsProfiledInstr() && arrInstr->m_func->HasProfileInfo())
{
intptr_t weakFuncRef = arrInstr->m_func->GetWeakFuncRef();
Assert(weakFuncRef);
Js::ProfileId profileId = static_cast<Js::ProfileId>(arrInstr->AsProfiledInstr()->u.profileId);
Js::ArrayCallSiteInfo *arrayInfo = arrInstr->m_func->GetReadOnlyProfileInfo()->GetArrayCallSiteInfo(profileId);
intptr_t arrayInfoAddr = arrInstr->m_func->GetReadOnlyProfileInfo()->GetArrayCallSiteInfoAddr(profileId);
Assert(arrInstr->GetSrc1()->IsConstOpnd());
GenerateProfiledNewScArrayFastPath(arrInstr, arrayInfo, arrayInfoAddr, weakFuncRef, arrInstr->GetSrc1()->AsIntConstOpnd()->AsUint32());
if (arrInstr->GetDst() && arrInstr->GetDst()->GetValueType().IsLikelyNativeArray())
{
m_lowererMD.LoadHelperArgument(arrInstr, IR::AddrOpnd::New(weakFuncRef, IR::AddrOpndKindDynamicFunctionBodyWeakRef, m_func));
m_lowererMD.LoadHelperArgument(arrInstr, IR::AddrOpnd::New(arrayInfoAddr, IR::AddrOpndKindDynamicArrayCallSiteInfo, m_func));
helperMethod = IR::HelperScrArr_ProfiledNewScArray;
}
}
LoadScriptContext(arrInstr);
IR::Opnd *src1Opnd = arrInstr->UnlinkSrc1();
m_lowererMD.LoadHelperArgument(arrInstr, src1Opnd);
m_lowererMD.ChangeToHelperCall(arrInstr, helperMethod);
return instrPrev;
}
template <typename ArrayType>
BOOL Lowerer::IsSmallObject(uint32 length)
{
if (ArrayType::HasInlineHeadSegment(length))
return true;
uint32 alignedHeadSegmentSize = Js::SparseArraySegment<typename ArrayType::TElement>::GetAlignedSize(length);
size_t allocSize = sizeof(Js::SparseArraySegment<typename ArrayType::TElement>) + alignedHeadSegmentSize * sizeof(typename ArrayType::TElement);
return HeapInfo::IsSmallObject(HeapInfo::GetAlignedSizeNoCheck(allocSize));
}
bool
Lowerer::GenerateProfiledNewScArrayFastPath(IR::Instr *instr, Js::ArrayCallSiteInfo * arrayInfo, intptr_t arrayInfoAddr, intptr_t weakFuncRef, uint32 length)
{
if (PHASE_OFF(Js::ArrayCtorFastPathPhase, m_func) || CONFIG_FLAG(ForceES5Array))
{
return false;
}
Func * func = this->m_func;
IR::LabelInstr * helperLabel = IR::LabelInstr::New(Js::OpCode::Label, func, true);
uint32 size = length;
bool isZeroed;
IR::RegOpnd *dstOpnd = instr->GetDst()->AsRegOpnd();
IR::RegOpnd *headOpnd;
uint32 i = length;
if (instr->GetDst() && instr->GetDst()->GetValueType().IsLikelyNativeIntArray())
{
if (!IsSmallObject<Js::JavascriptNativeIntArray>(length))
{
return false;
}
GenerateArrayInfoIsNativeIntArrayTest(instr, arrayInfo, arrayInfoAddr, helperLabel);
Assert(Js::JavascriptNativeIntArray::GetOffsetOfArrayFlags() + sizeof(uint16) == Js::JavascriptNativeIntArray::GetOffsetOfArrayCallSiteIndex());
headOpnd = GenerateArrayLiteralsAlloc<Js::JavascriptNativeIntArray>(instr, &size, arrayInfo, &isZeroed);
const IR::AutoReuseOpnd autoReuseHeadOpnd(headOpnd, func);
GenerateMemInit(dstOpnd, Js::JavascriptNativeIntArray::GetOffsetOfWeakFuncRef(), IR::AddrOpnd::New(weakFuncRef, IR::AddrOpndKindDynamicFunctionBodyWeakRef, m_func), instr, isZeroed);
for (; i < size; i++)
{
GenerateMemInit(headOpnd, sizeof(Js::SparseArraySegmentBase) + i * sizeof(int32),
Js::JavascriptNativeIntArray::MissingItem, instr, isZeroed);
}
}
else if (instr->GetDst() && instr->GetDst()->GetValueType().IsLikelyNativeFloatArray())
{
if (!IsSmallObject<Js::JavascriptNativeFloatArray>(length))
{
return false;
}
GenerateArrayInfoIsNativeFloatAndNotIntArrayTest(instr, arrayInfo, arrayInfoAddr, helperLabel);
Assert(Js::JavascriptNativeFloatArray::GetOffsetOfArrayFlags() + sizeof(uint16) == Js::JavascriptNativeFloatArray::GetOffsetOfArrayCallSiteIndex());
headOpnd = GenerateArrayLiteralsAlloc<Js::JavascriptNativeFloatArray>(instr, &size, arrayInfo, &isZeroed);
const IR::AutoReuseOpnd autoReuseHeadOpnd(headOpnd, func);
GenerateMemInit(dstOpnd, Js::JavascriptNativeFloatArray::GetOffsetOfWeakFuncRef(), IR::AddrOpnd::New(weakFuncRef, IR::AddrOpndKindDynamicFunctionBodyWeakRef, m_func), instr, isZeroed);
// Js::JavascriptArray::MissingItem is a Var, so it may be 32-bit or 64 bit.
uint const offsetStart = sizeof(Js::SparseArraySegmentBase);
for (; i < size; i++)
{
GenerateMemInit(
headOpnd, offsetStart + i * sizeof(double),
GetMissingItemOpndForAssignment(TyFloat64, m_func),
instr, isZeroed);
}
}
else
{
if (!IsSmallObject<Js::JavascriptArray>(length))
{
return false;
}
uint const offsetStart = sizeof(Js::SparseArraySegmentBase);
headOpnd = GenerateArrayLiteralsAlloc<Js::JavascriptArray>(instr, &size, arrayInfo, &isZeroed);
const IR::AutoReuseOpnd autoReuseHeadOpnd(headOpnd, func);
for (; i < size; i++)
{
GenerateMemInit(
headOpnd, offsetStart + i * sizeof(Js::Var),
GetMissingItemOpndForAssignment(TyVar, m_func),
instr, isZeroed);
}
}
// Skip pass the helper call
IR::LabelInstr * doneLabel = IR::LabelInstr::New(Js::OpCode::Label, func);
InsertBranch(Js::OpCode::Br, doneLabel, instr);
instr->InsertBefore(helperLabel);
instr->InsertAfter(doneLabel);
return true;
}
void
Lowerer::GenerateArrayInfoIsNativeIntArrayTest(IR::Instr *instr, Js::ArrayCallSiteInfo * arrayInfo, intptr_t arrayInfoAddr, IR::LabelInstr * helperLabel)
{
Func * func = this->m_func;
InsertTestBranch(IR::MemRefOpnd::New(((char *)arrayInfoAddr) + Js::ArrayCallSiteInfo::GetOffsetOfBits(), TyUint8, func),
IR::IntConstOpnd::New(Js::ArrayCallSiteInfo::NotNativeIntBit, TyUint8, func), Js::OpCode::BrNeq_A, helperLabel, instr);
}
void
Lowerer::GenerateArrayInfoIsNativeFloatAndNotIntArrayTest(IR::Instr *instr, Js::ArrayCallSiteInfo * arrayInfo, intptr_t arrayInfoAddr, IR::LabelInstr * helperLabel)
{
Func * func = this->m_func;
InsertCompareBranch(IR::MemRefOpnd::New(((char *)arrayInfoAddr) + Js::ArrayCallSiteInfo::GetOffsetOfBits(), TyUint8, func),
IR::IntConstOpnd::New(Js::ArrayCallSiteInfo::NotNativeIntBit, TyUint8, func), Js::OpCode::BrNeq_A, helperLabel, instr);
}
template <typename ArrayType>
static IR::JnHelperMethod GetArrayAllocMemHelper();
template <>
IR::JnHelperMethod GetArrayAllocMemHelper<Js::JavascriptArray>()
{
return IR::HelperAllocMemForJavascriptArray;
}
template <>
IR::JnHelperMethod GetArrayAllocMemHelper<Js::JavascriptNativeIntArray>()
{
return IR::HelperAllocMemForJavascriptNativeIntArray;
}
template <>
IR::JnHelperMethod GetArrayAllocMemHelper<Js::JavascriptNativeFloatArray>()
{
return IR::HelperAllocMemForJavascriptNativeFloatArray;
}
template <typename ArrayType>
IR::RegOpnd *
Lowerer::GenerateArrayLiteralsAlloc(IR::Instr *instr, uint32 * psize, Js::ArrayCallSiteInfo * arrayInfo, bool * pIsHeadSegmentZeroed)
{
return GenerateArrayAllocHelper<ArrayType>(instr, psize, arrayInfo, pIsHeadSegmentZeroed, false /* isArrayObjCtor */, false /* isNoArgs */);
}
template <typename ArrayType>
IR::RegOpnd *
Lowerer::GenerateArrayObjectsAlloc(IR::Instr *instr, uint32 * psize, Js::ArrayCallSiteInfo * arrayInfo, bool * pIsHeadSegmentZeroed, bool isNoArgs)
{
return GenerateArrayAllocHelper<ArrayType>(instr, psize, arrayInfo, pIsHeadSegmentZeroed, true /* isArrayObjCtor */, isNoArgs);
}
template <typename ArrayType>
IR::RegOpnd *
Lowerer::GenerateArrayAllocHelper(IR::Instr *instr, uint32 * psize, Js::ArrayCallSiteInfo * arrayInfo, bool * pIsHeadSegmentZeroed, bool isArrayObjCtor, bool isNoArgs)
{
Func * func = this->m_func;
IR::RegOpnd * dstOpnd = instr->GetDst()->AsRegOpnd();
// Generate code as in JavascriptArray::NewLiteral
uint32 count = *psize;
uint alignedHeadSegmentSize;
size_t arrayAllocSize;
IR::RegOpnd * headOpnd = IR::RegOpnd::New(TyMachPtr, func);
const IR::AutoReuseOpnd autoReuseHeadOpnd(headOpnd, func, false);
IR::Instr * leaHeadInstr = nullptr;
bool isHeadSegmentZeroed = false;
if (ArrayType::HasInlineHeadSegment(count))
{
if (isArrayObjCtor)
{
uint32 allocCount = isNoArgs ? Js::SparseArraySegmentBase::SMALL_CHUNK_SIZE : count;
arrayAllocSize = Js::JavascriptArray::DetermineAllocationSizeForArrayObjects<ArrayType, 0>(allocCount, nullptr, &alignedHeadSegmentSize);
}
else
{
uint32 allocCount = count == 0 ? Js::SparseArraySegmentBase::SMALL_CHUNK_SIZE : count;
arrayAllocSize = Js::JavascriptArray::DetermineAllocationSize<ArrayType, 0>(allocCount, nullptr, &alignedHeadSegmentSize);
}
// Note that it is possible for the returned alignedHeadSegmentSize to be greater than INLINE_CHUNK_SIZE because
// of rounding the *entire* object, including the head segment, to the nearest aligned size. In that case, ensure
// that this size is still not larger than INLINE_CHUNK_SIZE size because the head segment is still inlined. This
// keeps consistency with the definition of HasInlineHeadSegment and maintained in the assert below.
uint inlineChunkSize = Js::SparseArraySegmentBase::INLINE_CHUNK_SIZE;
alignedHeadSegmentSize = min(alignedHeadSegmentSize, inlineChunkSize);
Assert(ArrayType::HasInlineHeadSegment(alignedHeadSegmentSize));
leaHeadInstr = IR::Instr::New(Js::OpCode::LEA, headOpnd,
IR::IndirOpnd::New(dstOpnd, sizeof(ArrayType), TyMachPtr, func), func);
isHeadSegmentZeroed = true;
}
else
{
// Need to allocate the head segment first so that if it throws,
// we doesn't have the memory assigned to dstOpnd yet
// Even if the instruction is marked as dstIsTempObject, we still should not allocate
// that big of a chunk on the stack.
alignedHeadSegmentSize = Js::SparseArraySegment<typename ArrayType::TElement>::GetAlignedSize(count);
GenerateRecyclerAlloc(
IR::HelperAllocMemForSparseArraySegmentBase,
sizeof(Js::SparseArraySegment<typename ArrayType::TElement>) +
alignedHeadSegmentSize * sizeof(typename ArrayType::TElement),
headOpnd,
instr);
arrayAllocSize = sizeof(ArrayType);
}
*psize = alignedHeadSegmentSize;
IR::SymOpnd * tempObjectSymOpnd;
bool isZeroed = GenerateRecyclerOrMarkTempAlloc(instr, dstOpnd,
GetArrayAllocMemHelper<ArrayType>(), arrayAllocSize, &tempObjectSymOpnd);
isHeadSegmentZeroed = isHeadSegmentZeroed & isZeroed;
if (tempObjectSymOpnd && !PHASE_OFF(Js::HoistMarkTempInitPhase, this->m_func) && this->outerMostLoopLabel)
{
// Hoist the vtable init to the outer most loop top as it never changes
InsertMove(tempObjectSymOpnd,
this->LoadVTableValueOpnd(this->outerMostLoopLabel, ArrayType::VtableHelper()),
this->outerMostLoopLabel, false);
}
else
{
GenerateMemInit(dstOpnd, 0, this->LoadVTableValueOpnd(instr, ArrayType::VtableHelper()), instr, isZeroed);
}
GenerateMemInit(dstOpnd, ArrayType::GetOffsetOfType(), this->LoadLibraryValueOpnd(instr, ArrayType::InitialTypeHelper()), instr, isZeroed);
GenerateMemInitNull(dstOpnd, ArrayType::GetOffsetOfAuxSlots(), instr, isZeroed);
// Emit the flags and call site index together
Js::ProfileId arrayCallSiteIndex = (Js::ProfileId)instr->AsProfiledInstr()->u.profileId;
#if DBG
if (instr->AsProfiledInstr()->u.profileId < Js::Constants::NoProfileId)
{
Assert((uint32)(arrayInfo - instr->m_func->GetReadOnlyProfileInfo()->GetArrayCallSiteInfo(0)) == arrayCallSiteIndex);
}
else
{
Assert(arrayInfo == nullptr);
}
#endif
// The same at this:
// GenerateMemInit(dstOpnd, ArrayType::GetOffsetOfArrayFlags(), (uint16)Js::DynamicObjectFlags::InitialArrayValue, instr, isZeroed);
// GenerateMemInit(dstOpnd, ArrayType::GetOffsetOfArrayCallSiteIndex(), arrayCallSiteIndex, instr, isZeroed);
GenerateMemInit(dstOpnd, ArrayType::GetOffsetOfArrayFlags(), (uint)Js::DynamicObjectFlags::InitialArrayValue | ((uint)arrayCallSiteIndex << 16), instr, isZeroed);
GenerateMemInit(dstOpnd, ArrayType::GetOffsetOfLength(), count, instr, isZeroed);
if (leaHeadInstr != nullptr)
{
instr->InsertBefore(leaHeadInstr);
ChangeToLea(leaHeadInstr);
}
GenerateMemInit(dstOpnd, ArrayType::GetOffsetOfHead(), headOpnd, instr, isZeroed);
GenerateMemInit(dstOpnd, ArrayType::GetOffsetOfLastUsedSegmentOrSegmentMap(), headOpnd, instr, isZeroed);
// Initialize segment head
GenerateMemInit(headOpnd, Js::SparseArraySegmentBase::GetOffsetOfLeft(), 0, instr, isHeadSegmentZeroed);
GenerateMemInit(headOpnd, Js::SparseArraySegmentBase::GetOffsetOfLength(), isArrayObjCtor ? 0 : count, instr, isHeadSegmentZeroed);
GenerateMemInit(headOpnd, Js::SparseArraySegmentBase::GetOffsetOfSize(), alignedHeadSegmentSize, instr, isHeadSegmentZeroed);
GenerateMemInitNull(headOpnd, Js::SparseArraySegmentBase::GetOffsetOfNext(), instr, isHeadSegmentZeroed);
*pIsHeadSegmentZeroed = isHeadSegmentZeroed;
return headOpnd;
}
template <typename ArrayType>
IR::RegOpnd *
Lowerer::GenerateArrayAlloc(IR::Instr *instr, IR::Opnd * arrayLenOpnd, Js::ArrayCallSiteInfo * arrayInfo)
{
Func * func = this->m_func;
IR::RegOpnd * dstOpnd = instr->GetDst()->AsRegOpnd();
IR::RegOpnd * headOpnd = IR::RegOpnd::New(TyMachPtr, func);
const IR::AutoReuseOpnd autoReuseHeadOpnd(headOpnd, func, false);
IR::Instr * leaHeadInstr = nullptr;
IR::Opnd * arraySizeOpnd = IR::RegOpnd::New(TyUint32, func);
IR::Opnd * alignedArrayAllocSizeOpnd = IR::RegOpnd::New(TyUint32, func);
IR::LabelInstr * doneCalculatingAllocSize = IR::LabelInstr::New(Js::OpCode::Label, func);
IR::LabelInstr * skipToNextBucket = nullptr;
uint8 bucketsCount = ArrayType::AllocationBucketsCount;
Js::JavascriptArray::EnsureCalculationOfAllocationBuckets<ArrayType>();
for (uint8 i = 0;i < bucketsCount;i++)
{
uint elementsCountToInitialize = ArrayType::allocationBuckets[i][Js::JavascriptArray::MissingElementsCountIndex];
uint allocationSize = ArrayType::allocationBuckets[i][Js::JavascriptArray::AllocationSizeIndex];
// Ensure we already have allocation size calculated and within range
Assert(elementsCountToInitialize > 0 && elementsCountToInitialize <= ArrayType::allocationBuckets[bucketsCount - 1][Js::JavascriptArray::MissingElementsCountIndex]);
Assert(allocationSize > 0 && allocationSize <= ArrayType::allocationBuckets[bucketsCount - 1][Js::JavascriptArray::AllocationSizeIndex]);
// CMP arrayLen, currentBucket
// JG $checkNextBucket
if (i != (bucketsCount - 1))
{
Lowerer::InsertCompare(arrayLenOpnd, IR::IntConstOpnd::New((uint16)ArrayType::allocationBuckets[i][Js::JavascriptArray::AllocationBucketIndex], TyUint32, func), instr);
skipToNextBucket = IR::LabelInstr::New(Js::OpCode::Label, func);
Lowerer::InsertBranch(Js::OpCode::BrGt_A, skipToNextBucket, instr);
}
// MOV $arrayAlignedSize, <const1>
// MOV $arrayAllocSize, <const2>
Lowerer::InsertMove(arraySizeOpnd, IR::IntConstOpnd::New((uint16)elementsCountToInitialize, TyUint32, func), instr);
Lowerer::InsertMove(alignedArrayAllocSizeOpnd, IR::IntConstOpnd::New((uint16)allocationSize, TyUint32, func), instr);
// JMP $doneCalculatingAllocSize
if (i != (bucketsCount - 1))
{
Lowerer::InsertBranch(Js::OpCode::Br, doneCalculatingAllocSize, instr);
instr->InsertBefore(skipToNextBucket);
}
}
instr->InsertBefore(doneCalculatingAllocSize);
// ***** Call to allocation helper *****
this->m_lowererMD.LoadHelperArgument(instr, this->LoadScriptContextValueOpnd(instr, ScriptContextValue::ScriptContextRecycler));
this->m_lowererMD.LoadHelperArgument(instr, alignedArrayAllocSizeOpnd);
IR::Instr *newObjCall = IR::Instr::New(Js::OpCode::Call, dstOpnd, IR::HelperCallOpnd::New(GetArrayAllocMemHelper<ArrayType>(), func), func);
instr->InsertBefore(newObjCall);
this->m_lowererMD.LowerCall(newObjCall, 0);
// ***** Load headSeg/initialize it *****
leaHeadInstr = IR::Instr::New(Js::OpCode::LEA, headOpnd,
IR::IndirOpnd::New(dstOpnd, sizeof(ArrayType), TyMachPtr, func), func);
GenerateMemInit(dstOpnd, 0, this->LoadVTableValueOpnd(instr, ArrayType::VtableHelper()), instr, true);
GenerateMemInit(dstOpnd, ArrayType::GetOffsetOfType(), this->LoadLibraryValueOpnd(instr, ArrayType::InitialTypeHelper()), instr, true);
GenerateMemInitNull(dstOpnd, ArrayType::GetOffsetOfAuxSlots(), instr, true);
Js::ProfileId arrayCallSiteIndex = (Js::ProfileId)instr->AsProfiledInstr()->u.profileId;
#if DBG
if (instr->AsProfiledInstr()->u.profileId < Js::Constants::NoProfileId)
{
Assert((uint32)(arrayInfo - instr->m_func->GetReadOnlyProfileInfo()->GetArrayCallSiteInfo(0)) == arrayCallSiteIndex);
}
else
{
Assert(arrayInfo == nullptr);
}
#endif
// ***** Array object initialization *****
GenerateMemInit(dstOpnd, ArrayType::GetOffsetOfArrayFlags(), IR::IntConstOpnd::New((uint16)Js::DynamicObjectFlags::InitialArrayValue, TyUint16, func), instr, true);
GenerateMemInit(dstOpnd, ArrayType::GetOffsetOfLength(), arrayLenOpnd, instr, true);
if (leaHeadInstr != nullptr)
{
instr->InsertBefore(leaHeadInstr);
ChangeToLea(leaHeadInstr);
}
GenerateMemInit(dstOpnd, ArrayType::GetOffsetOfHead(), headOpnd, instr, true);
GenerateMemInit(dstOpnd, ArrayType::GetOffsetOfLastUsedSegmentOrSegmentMap(), headOpnd, instr, true);
GenerateMemInit(headOpnd, Js::SparseArraySegmentBase::GetOffsetOfLeft(), 0, instr, true);
GenerateMemInit(headOpnd, Js::SparseArraySegmentBase::GetOffsetOfLength(), 0, instr, true); // Set head segment length to 0
GenerateMemInit(headOpnd, Js::SparseArraySegmentBase::GetOffsetOfSize(), arraySizeOpnd, instr, true);
GenerateMemInitNull(headOpnd, Js::SparseArraySegmentBase::GetOffsetOfNext(), instr, true);
return headOpnd;
}
bool
Lowerer::GenerateProfiledNewScObjArrayFastPath(IR::Instr *instr, Js::ArrayCallSiteInfo * arrayInfo, intptr_t arrayInfoAddr, intptr_t weakFuncRef, uint32 length, IR::LabelInstr* labelDone, bool isNoArgs)
{
if (PHASE_OFF(Js::ArrayCtorFastPathPhase, m_func))
{
return false;
}
Func * func = this->m_func;
IR::LabelInstr * helperLabel = IR::LabelInstr::New(Js::OpCode::Label, func, true);
uint32 size = length;
bool isZeroed = false;
IR::RegOpnd *dstOpnd = instr->GetDst()->AsRegOpnd();
IR::RegOpnd *headOpnd;
Js::ProfileId profileId = static_cast<Js::ProfileId>(instr->AsProfiledInstr()->u.profileId);
if (arrayInfo && arrayInfo->IsNativeIntArray())
{
GenerateArrayInfoIsNativeIntArrayTest(instr, arrayInfo, arrayInfoAddr, helperLabel);
Assert(Js::JavascriptNativeIntArray::GetOffsetOfArrayFlags() + sizeof(uint16) == Js::JavascriptNativeIntArray::GetOffsetOfArrayCallSiteIndex());
headOpnd = GenerateArrayObjectsAlloc<Js::JavascriptNativeIntArray>(instr, &size, arrayInfo, &isZeroed, isNoArgs);
GenerateMemInit(dstOpnd, Js::JavascriptNativeIntArray::GetOffsetOfArrayCallSiteIndex(), IR::IntConstOpnd::New(profileId, TyUint16, func, true), instr, isZeroed);
GenerateMemInit(dstOpnd, Js::JavascriptNativeIntArray::GetOffsetOfWeakFuncRef(), IR::AddrOpnd::New(weakFuncRef, IR::AddrOpndKindDynamicFunctionBodyWeakRef, m_func), instr, isZeroed);
for (uint i = 0; i < size; i++)
{
GenerateMemInit(headOpnd, sizeof(Js::SparseArraySegmentBase) + i * sizeof(int32),
Js::JavascriptNativeIntArray::MissingItem, instr, isZeroed);
}
}
else if (arrayInfo && arrayInfo->IsNativeFloatArray())
{
GenerateArrayInfoIsNativeFloatAndNotIntArrayTest(instr, arrayInfo, arrayInfoAddr, helperLabel);
Assert(Js::JavascriptNativeFloatArray::GetOffsetOfArrayFlags() + sizeof(uint16) == Js::JavascriptNativeFloatArray::GetOffsetOfArrayCallSiteIndex());
headOpnd = GenerateArrayObjectsAlloc<Js::JavascriptNativeFloatArray>(instr, &size, arrayInfo, &isZeroed, isNoArgs);
GenerateMemInit(dstOpnd, Js::JavascriptNativeFloatArray::GetOffsetOfArrayCallSiteIndex(), IR::IntConstOpnd::New(profileId, TyUint16, func, true), instr, isZeroed);
GenerateMemInit(dstOpnd, Js::JavascriptNativeFloatArray::GetOffsetOfWeakFuncRef(), IR::AddrOpnd::New(weakFuncRef, IR::AddrOpndKindDynamicFunctionBodyWeakRef, m_func), instr, isZeroed);
// Js::JavascriptArray::MissingItem is a Var, so it may be 32-bit or 64 bit.
uint const offsetStart = sizeof(Js::SparseArraySegmentBase);
for (uint i = 0; i < size; i++)
{
GenerateMemInit(
headOpnd, offsetStart + i * sizeof(double),
GetMissingItemOpndForAssignment(TyFloat64, m_func),
instr, isZeroed);
}
}
else
{
uint const offsetStart = sizeof(Js::SparseArraySegmentBase);
headOpnd = GenerateArrayObjectsAlloc<Js::JavascriptArray>(instr, &size, arrayInfo, &isZeroed, isNoArgs);
for (uint i = 0; i < size; i++)
{
GenerateMemInit(
headOpnd, offsetStart + i * sizeof(Js::Var),
GetMissingItemOpndForAssignment(TyVar, m_func),
instr, isZeroed);
}
}
// Skip pass the helper call
InsertBranch(Js::OpCode::Br, labelDone, instr);
instr->InsertBefore(helperLabel);
return true;
}
template <typename ArrayType>
bool
Lowerer::GenerateProfiledNewScObjArrayFastPath(IR::Instr *instr, Js::ArrayCallSiteInfo * arrayInfo, intptr_t arrayInfoAddr, intptr_t weakFuncRef, IR::LabelInstr* helperLabel,
IR::LabelInstr* labelDone, IR::Opnd* lengthOpnd, uint32 offsetOfCallSiteIndex, uint32 offsetOfWeakFuncRef)
{
if (PHASE_OFF(Js::ArrayCtorFastPathPhase, m_func))
{
return false;
}
Func * func = this->m_func;
IR::RegOpnd *dstOpnd = instr->GetDst()->AsRegOpnd();
IR::RegOpnd *headOpnd;
Js::ProfileId profileId = static_cast<Js::ProfileId>(instr->AsProfiledInstr()->u.profileId);
uint sizeOfElement = 0;
uint allocationBucketsCount = ArrayType::AllocationBucketsCount;
uint(*allocationBuckets)[Js::JavascriptArray::AllocationBucketsInfoSize];
allocationBuckets = ArrayType::allocationBuckets;
IRType missingItemType = (arrayInfo ? arrayInfo->IsNativeIntArray() ? IRType::TyInt32 : arrayInfo->IsNativeFloatArray() ? IRType::TyFloat64 : IRType::TyVar : IRType::TyVar);
IR::LabelInstr * arrayInitDone = IR::LabelInstr::New(Js::OpCode::Label, func);
bool isNativeArray = arrayInfo && (arrayInfo->IsNativeIntArray() || arrayInfo->IsNativeFloatArray());
if (arrayInfo && arrayInfo->IsNativeIntArray())
{
sizeOfElement = sizeof(int32);
GenerateArrayInfoIsNativeIntArrayTest(instr, arrayInfo, arrayInfoAddr, helperLabel);
}
else if (arrayInfo && arrayInfo->IsNativeFloatArray())
{
sizeOfElement = sizeof(double);
GenerateArrayInfoIsNativeFloatAndNotIntArrayTest(instr, arrayInfo, arrayInfoAddr, helperLabel);
}
else
{
sizeOfElement = sizeof(Js::Var);
}
lengthOpnd = GenerateUntagVar(lengthOpnd->AsRegOpnd(), helperLabel, instr);
IR::Opnd* upperBound = IR::IntConstOpnd::New(8, TyUint8, func, true);
InsertCompare(lengthOpnd, upperBound, instr);
InsertBranch(Js::OpCode::BrGt_A, true /* isUnsigned */, helperLabel, instr);
headOpnd = GenerateArrayAlloc<ArrayType>(instr, lengthOpnd, arrayInfo);
if (isNativeArray)
{
Assert(ArrayType::GetOffsetOfArrayFlags() + sizeof(uint16) == offsetOfCallSiteIndex);
Assert(offsetOfWeakFuncRef > 0);
GenerateMemInit(dstOpnd, offsetOfCallSiteIndex, IR::IntConstOpnd::New(profileId, TyUint16, func, true), instr, true /* isZeroed */);
GenerateMemInit(dstOpnd, offsetOfWeakFuncRef, IR::AddrOpnd::New(weakFuncRef, IR::AddrOpndKindDynamicFunctionBodyWeakRef, m_func), instr, true /* isZeroed */);
}
uint const offsetStart = sizeof(Js::SparseArraySegmentBase);
uint missingItemCount = 0;
uint missingItemInitializedSoFar = 0;
uint missingItemIndex = 0;
uint maxAllocationSize = allocationBuckets[allocationBucketsCount - 1][Js::JavascriptArray::AllocationSizeIndex];
for (uint8 i = 0;i < allocationBucketsCount;i++)
{
missingItemCount = allocationBuckets[i][Js::JavascriptArray::MissingElementsCountIndex];
if (i > 0)
{
// Reduce missingItemCount we have already set so far
missingItemCount -= missingItemInitializedSoFar;
}
// Generate array initialization with MissingItem
for (uint j = 0;j < missingItemCount;j++)
{
// Ensure we don't write missingItems past allocation size
Assert(offsetStart + missingItemIndex * sizeOfElement <= maxAllocationSize);
GenerateMemInit(headOpnd, offsetStart + missingItemIndex * sizeOfElement, GetMissingItemOpndForAssignment(missingItemType, func), instr, true /*isZeroed*/);
missingItemIndex++;
}
// CMP arrayLen, currentBucket
// JG $checkNextBucket
if (i != (allocationBucketsCount - 1))
{
Lowerer::InsertCompare(lengthOpnd, IR::IntConstOpnd::New(allocationBuckets[i][Js::JavascriptArray::AllocationBucketIndex], TyUint32, func), instr);
Lowerer::InsertBranch(Js::OpCode::BrLe_A, arrayInitDone, instr);
}
missingItemInitializedSoFar += missingItemCount;
}
// Ensure no. of missingItems written are same
Assert(missingItemIndex == missingItemInitializedSoFar);
// Ensure no. of missingItems match what present in allocationBuckets
Assert(missingItemIndex == allocationBuckets[allocationBucketsCount - 1][Js::JavascriptArray::MissingElementsCountIndex]);
instr->InsertBefore(arrayInitDone);
Lowerer::InsertBranch(Js::OpCode::Br, labelDone, instr);
instr->InsertBefore(helperLabel);
return true;
}
void
Lowerer::GenerateProfiledNewScIntArrayFastPath(IR::Instr *instr, Js::ArrayCallSiteInfo * arrayInfo, intptr_t arrayInfoAddr, intptr_t weakFuncRef)
{
// Helper will deal with ForceES5ARray
if (PHASE_OFF(Js::ArrayLiteralFastPathPhase, m_func) || CONFIG_FLAG(ForceES5Array))
{
return;
}
if (!arrayInfo->IsNativeIntArray())
{
return;
}
if (instr->GetSrc1()->AsAddrOpnd()->GetAddrOpndKind() != IR::AddrOpndKindDynamicAuxBufferRef)
{
return;
}
Func * func = this->m_func;
IR::LabelInstr * helperLabel = IR::LabelInstr::New(Js::OpCode::Label, func, true);
GenerateArrayInfoIsNativeIntArrayTest(instr, arrayInfo, arrayInfoAddr, helperLabel);
IR::AddrOpnd * elementsOpnd = instr->GetSrc1()->AsAddrOpnd();
Js::AuxArray<int32> * ints = (Js::AuxArray<int32> *)elementsOpnd->m_metadata;
uint32 size = ints->count;
// Generate code as in JavascriptArray::NewLiteral
bool isHeadSegmentZeroed;
IR::RegOpnd * dstOpnd = instr->GetDst()->AsRegOpnd();
Assert(Js::JavascriptNativeIntArray::GetOffsetOfArrayFlags() + sizeof(uint16) == Js::JavascriptNativeIntArray::GetOffsetOfArrayCallSiteIndex());
IR::RegOpnd * headOpnd = GenerateArrayLiteralsAlloc<Js::JavascriptNativeIntArray>(instr, &size, arrayInfo, &isHeadSegmentZeroed);
const IR::AutoReuseOpnd autoReuseHeadOpnd(headOpnd, func);
GenerateMemInit(dstOpnd, Js::JavascriptNativeIntArray::GetOffsetOfWeakFuncRef(), IR::AddrOpnd::New(weakFuncRef, IR::AddrOpndKindDynamicMisc, m_func), instr, isHeadSegmentZeroed);
// Initialize the elements
uint i = 0;
if (ints->count > 16)
{
// Do memcpy if > 16
IR::RegOpnd * dstElementsOpnd = IR::RegOpnd::New(TyMachPtr, func);
const IR::AutoReuseOpnd autoReuseDstElementsOpnd(dstElementsOpnd, func);
IR::Opnd * srcOpnd = IR::AddrOpnd::New((intptr_t)elementsOpnd->m_address + Js::AuxArray<int32>::OffsetOfElements(), IR::AddrOpndKindDynamicMisc, func);
InsertLea(dstElementsOpnd, IR::IndirOpnd::New(headOpnd, sizeof(Js::SparseArraySegmentBase), TyMachPtr, func), instr);
GenerateMemCopy(dstElementsOpnd, srcOpnd, ints->count * sizeof(int32), instr);
i = ints->count;
}
else
{
for (; i < ints->count; i++)
{
GenerateMemInit(headOpnd, sizeof(Js::SparseArraySegmentBase) + i * sizeof(int32),
ints->elements[i], instr, isHeadSegmentZeroed);
}
}
Assert(i == ints->count);
for (; i < size; i++)
{
GenerateMemInit(headOpnd, sizeof(Js::SparseArraySegmentBase) + i * sizeof(int32),
Js::JavascriptNativeIntArray::MissingItem, instr, isHeadSegmentZeroed);
}
// Skip pass the helper call
IR::LabelInstr * doneLabel = IR::LabelInstr::New(Js::OpCode::Label, func);
InsertBranch(Js::OpCode::Br, doneLabel, instr);
instr->InsertBefore(helperLabel);
instr->InsertAfter(doneLabel);
}
void
Lowerer::GenerateProfiledNewScFloatArrayFastPath(IR::Instr *instr, Js::ArrayCallSiteInfo * arrayInfo, intptr_t arrayInfoAddr, intptr_t weakFuncRef)
{
if (PHASE_OFF(Js::ArrayLiteralFastPathPhase, m_func) || CONFIG_FLAG(ForceES5Array))
{
return;
}
if (!arrayInfo->IsNativeFloatArray())
{
return;
}
if (instr->GetSrc1()->AsAddrOpnd()->GetAddrOpndKind() != IR::AddrOpndKindDynamicAuxBufferRef)
{
return;
}
Func * func = this->m_func;
IR::LabelInstr * helperLabel = IR::LabelInstr::New(Js::OpCode::Label, func, true);
// If the array info hasn't mark as not int array yet, go to the helper and mark it.
// It really is just for assert purpose in JavascriptNativeFloatArray::ToVarArray
GenerateArrayInfoIsNativeFloatAndNotIntArrayTest(instr, arrayInfo, arrayInfoAddr, helperLabel);
IR::AddrOpnd * elementsOpnd = instr->GetSrc1()->AsAddrOpnd();
Js::AuxArray<double> * doubles = (Js::AuxArray<double> *)elementsOpnd->m_metadata;
uint32 size = doubles->count;
// Generate code as in JavascriptArray::NewLiteral
bool isHeadSegmentZeroed;
IR::RegOpnd * dstOpnd = instr->GetDst()->AsRegOpnd();
Assert(Js::JavascriptNativeFloatArray::GetOffsetOfArrayFlags() + sizeof(uint16) == Js::JavascriptNativeFloatArray::GetOffsetOfArrayCallSiteIndex());
IR::RegOpnd * headOpnd = GenerateArrayLiteralsAlloc<Js::JavascriptNativeFloatArray>(instr, &size, arrayInfo, &isHeadSegmentZeroed);
const IR::AutoReuseOpnd autoReuseHeadOpnd(headOpnd, func);
GenerateMemInit(dstOpnd, Js::JavascriptNativeFloatArray::GetOffsetOfWeakFuncRef(), IR::AddrOpnd::New(weakFuncRef, IR::AddrOpndKindDynamicFunctionBodyWeakRef, m_func), instr, isHeadSegmentZeroed);
// Initialize the elements
IR::RegOpnd * dstElementsOpnd = IR::RegOpnd::New(TyMachPtr, func);
const IR::AutoReuseOpnd autoReuseDstElementsOpnd(dstElementsOpnd, func);
IR::Opnd * srcOpnd = IR::AddrOpnd::New((intptr_t)elementsOpnd->m_address + Js::AuxArray<double>::OffsetOfElements(), IR::AddrOpndKindDynamicMisc, func);
InsertLea(dstElementsOpnd, IR::IndirOpnd::New(headOpnd, sizeof(Js::SparseArraySegmentBase), TyMachPtr, func), instr);
GenerateMemCopy(dstElementsOpnd, srcOpnd, doubles->count * sizeof(double), instr);
// Js::JavascriptArray::MissingItem is a Var, so it may be 32-bit or 64 bit.
uint const offsetStart = sizeof(Js::SparseArraySegmentBase) + doubles->count * sizeof(double);
uint const missingItem = (size - doubles->count);
for (uint i = 0; i < missingItem; i++)
{
GenerateMemInit(headOpnd, offsetStart + i * sizeof(double),
GetMissingItemOpndForAssignment(TyFloat64, m_func), instr, isHeadSegmentZeroed);
}
// Skip pass the helper call
IR::LabelInstr * doneLabel = IR::LabelInstr::New(Js::OpCode::Label, func);
InsertBranch(Js::OpCode::Br, doneLabel, instr);
instr->InsertBefore(helperLabel);
instr->InsertAfter(doneLabel);
}
IR::Instr *
Lowerer::LowerNewScIntArray(IR::Instr *arrInstr)
{
IR::Instr *instrPrev = arrInstr->m_prev;
IR::JnHelperMethod helperMethod = IR::HelperScrArr_OP_NewScIntArray;
if ((arrInstr->IsJitProfilingInstr() || arrInstr->IsProfiledInstr()) && arrInstr->m_func->HasProfileInfo())
{
intptr_t weakFuncRef = arrInstr->m_func->GetWeakFuncRef();
if (weakFuncRef)
{
// Technically a load of the same memory address either way.
Js::ProfileId profileId =
arrInstr->IsJitProfilingInstr()
? arrInstr->AsJitProfilingInstr()->profileId
: static_cast<Js::ProfileId>(arrInstr->AsProfiledInstr()->u.profileId);
Js::ArrayCallSiteInfo *arrayInfo = arrInstr->m_func->GetReadOnlyProfileInfo()->GetArrayCallSiteInfo(profileId);
intptr_t arrayInfoAddr = arrInstr->m_func->GetReadOnlyProfileInfo()->GetArrayCallSiteInfoAddr(profileId);
// Only do fast-path if it isn't a JitProfiling instr and not copy-on-access array
if (arrInstr->IsProfiledInstr()
#if ENABLE_COPYONACCESS_ARRAY
&& (PHASE_OFF1(Js::Phase::CopyOnAccessArrayPhase) || arrayInfo->isNotCopyOnAccessArray) && !PHASE_FORCE1(Js::Phase::CopyOnAccessArrayPhase)
#endif
)
{
GenerateProfiledNewScIntArrayFastPath(arrInstr, arrayInfo, arrayInfoAddr, weakFuncRef);
}
m_lowererMD.LoadHelperArgument(arrInstr, IR::AddrOpnd::New(weakFuncRef, IR::AddrOpndKindDynamicFunctionBodyWeakRef, m_func));
m_lowererMD.LoadHelperArgument(arrInstr, IR::AddrOpnd::New(arrayInfoAddr, IR::AddrOpndKindDynamicArrayCallSiteInfo, m_func));
helperMethod = IR::HelperScrArr_ProfiledNewScIntArray;
}
}
LoadScriptContext(arrInstr);
IR::Opnd *elementsOpnd = arrInstr->UnlinkSrc1();
m_lowererMD.LoadHelperArgument(arrInstr, elementsOpnd);
m_lowererMD.ChangeToHelperCall(arrInstr, helperMethod);
return instrPrev;
}
IR::Instr *
Lowerer::LowerNewScFltArray(IR::Instr *arrInstr)
{
IR::Instr *instrPrev = arrInstr->m_prev;
IR::JnHelperMethod helperMethod = IR::HelperScrArr_OP_NewScFltArray;
if ((arrInstr->IsJitProfilingInstr() || arrInstr->IsProfiledInstr()) && arrInstr->m_func->HasProfileInfo())
{
intptr_t weakFuncRef = arrInstr->m_func->GetWeakFuncRef();
if (weakFuncRef)
{
Js::ProfileId profileId =
arrInstr->IsJitProfilingInstr()
? arrInstr->AsJitProfilingInstr()->profileId
: static_cast<Js::ProfileId>(arrInstr->AsProfiledInstr()->u.profileId);
Js::ArrayCallSiteInfo *arrayInfo = arrInstr->m_func->GetReadOnlyProfileInfo()->GetArrayCallSiteInfo(profileId);
intptr_t arrayInfoAddr = arrInstr->m_func->GetReadOnlyProfileInfo()->GetArrayCallSiteInfoAddr(profileId);
// Only do fast-path if it isn't a JitProfiling instr
if (arrInstr->IsProfiledInstr()) {
GenerateProfiledNewScFloatArrayFastPath(arrInstr, arrayInfo, arrayInfoAddr, weakFuncRef);
}
m_lowererMD.LoadHelperArgument(arrInstr, IR::AddrOpnd::New(weakFuncRef, IR::AddrOpndKindDynamicFunctionBodyWeakRef, m_func));
m_lowererMD.LoadHelperArgument(arrInstr, IR::AddrOpnd::New(arrayInfoAddr, IR::AddrOpndKindDynamicArrayCallSiteInfo, m_func));
helperMethod = IR::HelperScrArr_ProfiledNewScFltArray;
}
}
LoadScriptContext(arrInstr);
IR::Opnd *elementsOpnd = arrInstr->UnlinkSrc1();
m_lowererMD.LoadHelperArgument(arrInstr, elementsOpnd);
m_lowererMD.ChangeToHelperCall(arrInstr, helperMethod);
return instrPrev;
}
IR::Instr *
Lowerer::LowerArraySegmentVars(IR::Instr *arrayInstr)
{
IR::Instr * instrPrev;
IR::HelperCallOpnd * opndHelper = IR::HelperCallOpnd::New(IR::HelperArraySegmentVars, m_func);
instrPrev = m_lowererMD.LoadHelperArgument(arrayInstr, arrayInstr->UnlinkSrc2());
m_lowererMD.LoadHelperArgument(arrayInstr, arrayInstr->UnlinkSrc1());
arrayInstr->m_opcode = Js::OpCode::Call;
arrayInstr->SetSrc1(opndHelper);
m_lowererMD.LowerCall(arrayInstr, 0);
return instrPrev;
}
IR::Instr* Lowerer::LowerProfiledNewArray(IR::JitProfilingInstr* instr, bool hasArgs)
{
// Use the special helper which checks whether Array has been overwritten by the user and if
// it hasn't, possibly allocates a native array
// Insert a temporary label before the instruction we're about to lower, so that we can return
// the first instruction above that needs to be lowered after we're done - regardless of argument
// list, StartCall, etc.
IR::Instr* startMarkerInstr = InsertLoweredRegionStartMarker(instr);
Assert(instr->isNewArray);
Assert(instr->arrayProfileId != Js::Constants::NoProfileId);
Assert(instr->profileId != Js::Constants::NoProfileId);
bool isSpreadCall = instr->m_opcode == Js::OpCode::NewScObjectSpread || instr->m_opcode == Js::OpCode::NewScObjArraySpread;
m_lowererMD.LoadNewScObjFirstArg(instr, IR::AddrOpnd::New(nullptr, IR::AddrOpndKindConstantVar, m_func, true), isSpreadCall ? 1 : 0);
if (isSpreadCall)
{
this->LowerSpreadCall(instr, Js::CallFlags_New, true);
}
else
{
const int32 argCount = m_lowererMD.LowerCallArgs(instr, Js::CallFlags_New, 4);
m_lowererMD.LoadHelperArgument(instr, IR::Opnd::CreateProfileIdOpnd(instr->arrayProfileId, m_func));
m_lowererMD.LoadHelperArgument(instr, IR::Opnd::CreateProfileIdOpnd(instr->profileId, m_func));
m_lowererMD.LoadHelperArgument(instr, IR::Opnd::CreateFramePointerOpnd(m_func));
m_lowererMD.LoadHelperArgument(instr, instr->UnlinkSrc1());
instr->SetSrc1(IR::HelperCallOpnd::New(IR::HelperProfiledNewScObjArray, m_func));
m_lowererMD.LowerCall(instr, static_cast<Js::ArgSlot>(argCount));
}
return RemoveLoweredRegionStartMarker(startMarkerInstr);
}
///----------------------------------------------------------------------------
///
/// Lowerer::LowerNewScObject
///
/// Machine independent lowering of a CallI instr.
///
///----------------------------------------------------------------------------
IR::Instr *
Lowerer::LowerNewScObject(IR::Instr *newObjInstr, bool callCtor, bool hasArgs, bool isBaseClassConstructorNewScObject)
{
if (newObjInstr->IsJitProfilingInstr() && newObjInstr->AsJitProfilingInstr()->isNewArray)
{
Assert(callCtor);
return LowerProfiledNewArray(newObjInstr->AsJitProfilingInstr(), hasArgs);
}
bool isSpreadCall = newObjInstr->m_opcode == Js::OpCode::NewScObjectSpread ||
newObjInstr->m_opcode == Js::OpCode::NewScObjArraySpread;
Func* func = newObjInstr->m_func;
// Insert a temporary label before the instruction we're about to lower, so that we can return
// the first instruction above that needs to be lowered after we're done - regardless of argument
// list, StartCall, etc.
IR::Instr* startMarkerInstr = InsertLoweredRegionStartMarker(newObjInstr);
IR::Opnd *ctorOpnd = newObjInstr->GetSrc1();
IR::RegOpnd *newObjDst = newObjInstr->GetDst()->AsRegOpnd();
Assert(!callCtor || !hasArgs || (newObjInstr->GetSrc2() != nullptr /*&& newObjInstr->GetSrc2()->IsSymOpnd()*/));
bool skipNewScObj = false;
bool returnNewScObj = false;
bool emitBailOut = false;
// If we haven't yet split NewScObject into NewScObjectNoCtor and CallI, we will need a temporary register
// to hold the result of the object allocation.
IR::RegOpnd* createObjDst = callCtor ? IR::RegOpnd::New(TyVar, func) : newObjDst;
IR::LabelInstr* helperOrBailoutLabel = IR::LabelInstr::New(Js::OpCode::Label, func, /* isOpHelper = */ true);
IR::LabelInstr* callCtorLabel = IR::LabelInstr::New(Js::OpCode::Label, func, /* isOpHelper = */ false);
// Try to emit the fast allocation and construction path.
bool usedFixedCtorCache = TryLowerNewScObjectWithFixedCtorCache(newObjInstr, createObjDst, helperOrBailoutLabel, callCtorLabel, skipNewScObj, returnNewScObj, emitBailOut);
AssertMsg(!skipNewScObj || callCtor, "What will we return if we skip the default new object and don't call the ctor?");
Assert(!skipNewScObj || !returnNewScObj);
Assert(usedFixedCtorCache || !skipNewScObj);
Assert(!usedFixedCtorCache || newObjInstr->HasFixedFunctionAddressTarget());
Assert(!skipNewScObj || !emitBailOut);
#if DBG && 0 // TODO: OOP JIT, enable assert
if (usedFixedCtorCache)
{
Js::JavascriptFunction* ctor = newObjInstr->GetFixedFunction();
Js::FunctionInfo* ctorInfo = ctor->GetFunctionInfo();
Assert((ctorInfo->GetAttributes() & Js::FunctionInfo::Attributes::ErrorOnNew) == 0);
Assert(!!(ctorInfo->GetAttributes() & Js::FunctionInfo::Attributes::SkipDefaultNewObject) == skipNewScObj);
}
#endif
IR::Instr* startCallInstr = nullptr;
if (callCtor && hasArgs)
{
hasArgs = !newObjInstr->HasEmptyArgOutChain(&startCallInstr);
}
// If we're not skipping the default new object, let's emit bailout or a call to NewScObject* helper
IR::JnHelperMethod newScHelper = IR::HelperInvalid;
IR::Instr *newScObjCall = nullptr;
if (!skipNewScObj)
{
// If we emitted the fast path, this block is a helper block.
if (usedFixedCtorCache)
{
newObjInstr->InsertBefore(helperOrBailoutLabel);
}
if (emitBailOut)
{
IR::Instr* bailOutInstr = newObjInstr;
newObjInstr = IR::Instr::New(newObjInstr->m_opcode, func);
bailOutInstr->TransferTo(newObjInstr);
bailOutInstr->m_opcode = Js::OpCode::BailOut;
bailOutInstr->InsertAfter(newObjInstr);
GenerateBailOut(bailOutInstr);
}
else
{
Assert(!newObjDst->CanStoreTemp());
// createObjDst = NewScObject...(ctorOpnd)
newScHelper = !callCtor ?
(isBaseClassConstructorNewScObject ?
(hasArgs ? IR::HelperNewScObjectNoCtorFull : IR::HelperNewScObjectNoArgNoCtorFull) :
(hasArgs ? IR::HelperNewScObjectNoCtor : IR::HelperNewScObjectNoArgNoCtor)) :
(hasArgs || usedFixedCtorCache ? IR::HelperNewScObjectNoCtor : IR::HelperNewScObjectNoArg);
LoadScriptContext(newObjInstr);
m_lowererMD.LoadHelperArgument(newObjInstr, newObjInstr->GetSrc1());
newScObjCall = IR::Instr::New(Js::OpCode::Call, createObjDst, IR::HelperCallOpnd::New(newScHelper, func), func);
newObjInstr->InsertBefore(newScObjCall);
m_lowererMD.LowerCall(newScObjCall, 0);
}
}
// If we call HelperNewScObjectNoArg directly, we won't be calling the constructor from here, because the helper will do it.
// We could probably avoid this complexity by converting NewScObjectNoArg to NewScObject in the IRBuilder, once we have dedicated
// code paths for new Object() and new Array().
callCtor &= hasArgs || usedFixedCtorCache;
AssertMsg(!skipNewScObj || callCtor, "What will we return if we skip the default new object and don't call the ctor?");
newObjInstr->InsertBefore(callCtorLabel);
if (callCtor && usedFixedCtorCache)
{
IR::JnHelperMethod ctorHelper = IR::JnHelperMethodCount;
// If we have no arguments (i.e. the argument chain is empty), we can recognize a couple of common special cases, such
// as new Object() or new Array(), for which we have optimized helpers.
FixedFieldInfo* ctor = newObjInstr->GetFixedFunction();
intptr_t ctorInfo = ctor->GetFuncInfoAddr();
if (!hasArgs && (ctorInfo == m_func->GetThreadContextInfo()->GetJavascriptObjectNewInstanceAddr() || ctorInfo == m_func->GetThreadContextInfo()->GetJavascriptArrayNewInstanceAddr()))
{
if (ctorInfo == m_func->GetThreadContextInfo()->GetJavascriptObjectNewInstanceAddr())
{
Assert(skipNewScObj);
ctorHelper = IR::HelperNewJavascriptObjectNoArg;
callCtor = false;
}
else if (ctorInfo == m_func->GetThreadContextInfo()->GetJavascriptArrayNewInstanceAddr())
{
Assert(skipNewScObj);
ctorHelper = IR::HelperNewJavascriptArrayNoArg;
callCtor = false;
}
if (!callCtor)
{
LoadScriptContext(newObjInstr);
IR::Instr *ctorCall = IR::Instr::New(Js::OpCode::Call, newObjDst, IR::HelperCallOpnd::New(ctorHelper, func), func);
newObjInstr->InsertBefore(ctorCall);
m_lowererMD.LowerCall(ctorCall, 0);
}
}
}
IR::AutoReuseOpnd autoReuseSavedCtorOpnd;
if (callCtor)
{
// Load the first argument, which is either the object just created or null. Spread has an extra argument.
IR::Instr * argInstr = this->m_lowererMD.LoadNewScObjFirstArg(newObjInstr, createObjDst, isSpreadCall ? 1 : 0);
IR::Instr * insertAfterCtorInstr = newObjInstr->m_next;
if (skipNewScObj)
{
// Since we skipped the default new object, we must be returning whatever the constructor returns
// (which better be an Object), so let's just use newObjDst directly.
// newObjDst = newObjInstr->m_src1(createObjDst, ...)
Assert(newObjInstr->GetDst() == newObjDst);
if (isSpreadCall)
{
newObjInstr = this->LowerSpreadCall(newObjInstr, Js::CallFlags_New);
}
else
{
newObjInstr = this->m_lowererMD.LowerCallI(newObjInstr, Js::CallFlags_New, false, argInstr);
}
}
else
{
// We may need to return the default new object or whatever the constructor returns. Let's stash
// away the constructor's return in a temporary operand, and do the right check, if necessary.
// ctorResultObjOpnd = newObjInstr->m_src1(createObjDst, ...)
IR::RegOpnd *ctorResultObjOpnd = IR::RegOpnd::New(TyVar, func);
newObjInstr->UnlinkDst();
newObjInstr->SetDst(ctorResultObjOpnd);
if (isSpreadCall)
{
newObjInstr = this->LowerSpreadCall(newObjInstr, Js::CallFlags_New);
}
else
{
newObjInstr = this->m_lowererMD.LowerCallI(newObjInstr, Js::CallFlags_New, false, argInstr);
}
if (returnNewScObj)
{
// MOV newObjDst, createObjDst
this->InsertMove(newObjDst, createObjDst, insertAfterCtorInstr);
}
else
{
LowerGetNewScObjectCommon(ctorResultObjOpnd, ctorResultObjOpnd, createObjDst, insertAfterCtorInstr);
this->InsertMove(newObjDst, ctorResultObjOpnd, insertAfterCtorInstr);
}
}
// We don't ever need to update the constructor cache, if we hard coded it. Caches requiring update after constructor
// don't get cloned, and those that don't require update will never need one anymore.
if (!usedFixedCtorCache)
{
LowerUpdateNewScObjectCache(insertAfterCtorInstr, newObjDst, ctorOpnd, false /* isCtorFunction */);
}
}
else
{
if (newObjInstr->IsJitProfilingInstr())
{
Assert(m_func->IsSimpleJit());
Assert(!CONFIG_FLAG(NewSimpleJit));
// This path skipped calling the Ctor, which skips calling LowerCallI with newObjInstr, meaning that the call will not be profiled.
// So we insert it manually here.
if(newScHelper == IR::HelperNewScObjectNoArg &&
newObjDst &&
ctorOpnd->IsRegOpnd() &&
newObjDst->AsRegOpnd()->m_sym == ctorOpnd->AsRegOpnd()->m_sym)
{
Assert(newObjInstr->m_func->IsSimpleJit());
Assert(createObjDst != newObjDst);
// The function object sym is going to be overwritten, so save it in a temp for profiling
IR::RegOpnd *const savedCtorOpnd = IR::RegOpnd::New(ctorOpnd->GetType(), newObjInstr->m_func);
autoReuseSavedCtorOpnd.Initialize(savedCtorOpnd, newObjInstr->m_func);
Lowerer::InsertMove(savedCtorOpnd, ctorOpnd, newObjInstr);
ctorOpnd = savedCtorOpnd;
}
// It is a constructor (CallFlags_New) and therefore a single argument (this) would have been given.
const auto info = Lowerer::MakeCallInfoConst(Js::CallFlags_New, 1, func);
Assert(newScObjCall);
IR::JitProfilingInstr *const newObjJitProfilingInstr = newObjInstr->AsJitProfilingInstr();
GenerateCallProfiling(
newObjJitProfilingInstr->profileId,
newObjJitProfilingInstr->inlineCacheIndex,
createObjDst,
ctorOpnd,
info,
false,
newScObjCall,
newObjInstr);
}
// MOV newObjDst, createObjDst
if (!skipNewScObj && createObjDst != newObjDst)
{
this->InsertMove(newObjDst, createObjDst, newObjInstr);
}
newObjInstr->Remove();
}
// Return the first instruction above the region we've just lowered.
return RemoveLoweredRegionStartMarker(startMarkerInstr);
}
IR::Instr*
Lowerer::GenerateCallProfiling(Js::ProfileId profileId, Js::InlineCacheIndex inlineCacheIndex, IR::Opnd* retval, IR::Opnd*calleeFunctionObjOpnd, IR::Opnd* callInfo, bool returnTypeOnly, IR::Instr*callInstr,IR::Instr*insertAfter)
{
// This should only ever happen in profiling simplejit
Assert(m_func->DoSimpleJitDynamicProfile());
// Make sure they gave us the correct call instruction
#if defined(_M_IX86) || defined(_M_X64)
Assert(callInstr->m_opcode == Js::OpCode::CALL);
#elif defined(_M_ARM)
Assert(callInstr->m_opcode == Js::OpCode::BLX);
#elif defined(_M_ARM64)
Assert(callInstr->m_opcode == Js::OpCode::BLR);
#endif
Func*const func = insertAfter->m_func;
{
// First, we should save the implicit call flags
const auto starFlag = GetImplicitCallFlagsOpnd();
const auto saveOpnd = IR::RegOpnd::New(starFlag->GetType(), func);
IR::AutoReuseOpnd a(starFlag, func), b(saveOpnd, func);
//Save the flags (before call) and restore them (after the call)
this->InsertMove(saveOpnd, starFlag, callInstr);
// Note: On arm this is slightly inefficient because it forces a reload of the memory location to a reg (whereas x86 can load straight from hard-coded memory into a reg)
// But it works and making it not reload the memory location would force more refactoring.
this->InsertMove(starFlag, saveOpnd, insertAfter->m_next);
}
// Profile a call that just happened: push some extra info on the stack and call the helper
if (!retval)
{
if (returnTypeOnly)
{
// If we are only supposed to profile the return type but don't use the return value, we might
// as well do nothing!
return insertAfter;
}
retval = IR::AddrOpnd::NewNull(func);
}
IR::Instr* profileCall = IR::Instr::New(Js::OpCode::Call, func);
bool needInlineCacheIndex;
IR::JnHelperMethod helperMethod;
if (returnTypeOnly)
{
needInlineCacheIndex = false;
helperMethod = IR::HelperSimpleProfileReturnTypeCall;
}
else if(inlineCacheIndex == Js::Constants::NoInlineCacheIndex)
{
needInlineCacheIndex = false;
helperMethod = IR::HelperSimpleProfileCall_DefaultInlineCacheIndex;
}
else
{
needInlineCacheIndex = true;
helperMethod = IR::HelperSimpleProfileCall;
}
profileCall->SetSrc1(IR::HelperCallOpnd::New(helperMethod, func));
insertAfter->InsertAfter(profileCall);
m_lowererMD.LoadHelperArgument(profileCall, callInfo);
m_lowererMD.LoadHelperArgument(profileCall, calleeFunctionObjOpnd);
m_lowererMD.LoadHelperArgument(profileCall, retval);
if(needInlineCacheIndex)
{
m_lowererMD.LoadHelperArgument(profileCall, IR::Opnd::CreateInlineCacheIndexOpnd(inlineCacheIndex, func));
}
m_lowererMD.LoadHelperArgument(profileCall, IR::Opnd::CreateProfileIdOpnd(profileId, func));
// Push the frame pointer so that the profiling call can grab the stack layout
m_lowererMD.LoadHelperArgument(profileCall, IR::Opnd::CreateFramePointerOpnd(func));
// No args: the helper is stdcall
return m_lowererMD.LowerCall(profileCall, 0);
}
bool Lowerer::TryLowerNewScObjectWithFixedCtorCache(IR::Instr* newObjInstr, IR::RegOpnd* newObjDst,
IR::LabelInstr* helperOrBailoutLabel, IR::LabelInstr* callCtorLabel, bool& skipNewScObj, bool& returnNewScObj, bool& emitBailOut)
{
skipNewScObj = false;
returnNewScObj = false;
AssertMsg(!PHASE_OFF(Js::ObjTypeSpecNewObjPhase, this->m_func) || !newObjInstr->HasBailOutInfo(),
"Why do we have bailout on NewScObject when ObjTypeSpecNewObj is off?");
if (PHASE_OFF(Js::FixedNewObjPhase, newObjInstr->m_func) && PHASE_OFF(Js::ObjTypeSpecNewObjPhase, this->m_func))
{
return false;
}
JITTimeConstructorCache * ctorCache;
if (newObjInstr->HasBailOutInfo() && !newObjInstr->HasLazyBailOut())
{
Assert(newObjInstr->IsNewScObjectInstr());
Assert(newObjInstr->IsProfiledInstr());
Assert(newObjInstr->GetBailOutKind() == IR::BailOutFailedCtorGuardCheck || newObjInstr->HasLazyBailOut());
emitBailOut = true;
ctorCache = newObjInstr->m_func->GetConstructorCache(static_cast<Js::ProfileId>(newObjInstr->AsProfiledInstr()->u.profileId));
Assert(ctorCache != nullptr);
Assert(!ctorCache->SkipNewScObject());
Assert(!ctorCache->IsTypeFinal() || ctorCache->CtorHasNoExplicitReturnValue());
LinkCtorCacheToGuardedProperties(ctorCache);
}
else
{
if (newObjInstr->m_opcode == Js::OpCode::NewScObjArray || newObjInstr->m_opcode == Js::OpCode::NewScObjArraySpread)
{
// These instr's carry a profile that indexes the array call site info, not the ctor cache.
return false;
}
ctorCache = newObjInstr->IsProfiledInstr() ? newObjInstr->m_func->GetConstructorCache(static_cast<Js::ProfileId>(newObjInstr->AsProfiledInstr()->u.profileId)) : nullptr;
if (ctorCache == nullptr)
{
if (PHASE_TRACE(Js::FixedNewObjPhase, newObjInstr->m_func) || PHASE_TESTTRACE(Js::FixedNewObjPhase, newObjInstr->m_func))
{
char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
Output::Print(_u("FixedNewObj: function %s (%s): lowering non-fixed new script object for %s, because %s.\n"),
newObjInstr->m_func->GetJITFunctionBody()->GetDisplayName(), newObjInstr->m_func->GetDebugNumberSet(debugStringBuffer), Js::OpCodeUtil::GetOpCodeName(newObjInstr->m_opcode),
newObjInstr->IsProfiledInstr() ? _u("constructor cache hasn't been cloned") : _u("instruction is not profiled"));
Output::Flush();
}
return false;
}
}
Assert(ctorCache != nullptr);
// We should only have cloned if the script contexts match.
// TODO: oop jit, add ctorCache->scriptContext for tracing assert
// Assert(newObjInstr->m_func->GetScriptContextInfo()->GetAddr() == ctorCache->scriptContext);
// Built-in constructors don't need a default new object. Since we know which constructor we're calling, we can skip creating a default
// object and call a specialized helper (or even constructor, directly) avoiding the checks in generic NewScObjectCommon.
if (ctorCache->SkipNewScObject())
{
#if 0 // TODO: oop jit, add constructor info for tracing
if (PHASE_TRACE(Js::FixedNewObjPhase, newObjInstr->m_func) || PHASE_TESTTRACE(Js::FixedNewObjPhase, newObjInstr->m_func))
{
const Js::JavascriptFunction* ctor = ctorCache->constructor;
Js::FunctionBody* ctorBody = ctor->GetFunctionInfo()->HasBody() ? ctor->GetFunctionInfo()->GetFunctionBody() : nullptr;
const char16* ctorName = ctorBody != nullptr ? ctorBody->GetDisplayName() : _u("<unknown>");
char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
char16 debugStringBuffer2[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
Output::Print(_u("FixedNewObj: function %s (%s): lowering skipped new script object for %s with %s ctor <unknown> (%s %s).\n"),
newObjInstr->m_func->GetJITFunctionBody()->GetDisplayName(), newObjInstr->m_func->GetDebugNumberSet(debugStringBuffer2), Js::OpCodeUtil::GetOpCodeName(newObjInstr->m_opcode),
newObjInstr->m_opcode == Js::OpCode::NewScObjectNoCtor ? _u("inlined") : _u("called"),
ctorName, ctorBody ? ctorBody->GetDebugNumberSet(debugStringBuffer) : _u("(null)"));
Output::Flush();
}
#endif
// All built-in constructors share a special singleton cache that is never checked and never invalidated. It cannot be used
// as a guard to protect any property operations downstream from the constructor. If this ever becomes a performance issue,
// we could have a dedicated cache for each built-in constructor, populate it and invalidate it as any other constructor cache.
AssertMsg(!emitBailOut, "Can't bail out on constructor cache guard for built-in constructors.");
skipNewScObj = true;
IR::AddrOpnd* zeroOpnd = IR::AddrOpnd::NewNull(this->m_func);
this->InsertMove(newObjDst, zeroOpnd, newObjInstr);
return true;
}
AssertMsg(ctorCache->GetType() != nullptr, "Why did we hard-code a mismatched, invalidated or polymorphic constructor cache?");
#if 0 // TODO: oop jit, add constructor info for tracing
if (PHASE_TRACE(Js::FixedNewObjPhase, newObjInstr->m_func) || PHASE_TESTTRACE(Js::FixedNewObjPhase, newObjInstr->m_func))
{
const Js::JavascriptFunction* constructor = ctorCache->constructor;
Js::FunctionBody* constructorBody = constructor->GetFunctionInfo()->HasBody() ? constructor->GetFunctionInfo()->GetFunctionBody() : nullptr;
const char16* constructorName = constructorBody != nullptr ? constructorBody->GetDisplayName() : _u("<unknown>");
char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
char16 debugStringBuffer2[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
if (PHASE_TRACE(Js::FixedNewObjPhase, newObjInstr->m_func))
{
Output::Print(_u("FixedNewObj: function %s (%s): lowering fixed new script object for %s with %s ctor <unknown> (%s %s): type = %p, slots = %d, inlined slots = %d.\n"),
newObjInstr->m_func->GetJITFunctionBody()->GetDisplayName(), newObjInstr->m_func->GetDebugNumberSet(debugStringBuffer2), Js::OpCodeUtil::GetOpCodeName(newObjInstr->m_opcode),
newObjInstr->m_opcode == Js::OpCode::NewScObjectNoCtor ? _u("inlined") : _u("called"),
constructorName, constructorBody ? constructorBody->GetDebugNumberSet(debugStringBuffer) : _u("(null)"),
ctorCache->type, ctorCache->slotCount, ctorCache->inlineSlotCount);
}
else
{
Output::Print(_u("FixedNewObj: function %s (%s): lowering fixed new script object for %s with %s ctor <unknown> (%s %s): slots = %d, inlined slots = %d.\n"),
newObjInstr->m_func->GetJITFunctionBody()->GetDisplayName(), newObjInstr->m_func->GetDebugNumberSet(debugStringBuffer2), Js::OpCodeUtil::GetOpCodeName(newObjInstr->m_opcode),
newObjInstr->m_opcode == Js::OpCode::NewScObjectNoCtor ? _u("inlined") : _u("called"),
constructorName, debugStringBuffer, ctorCache->slotCount, ctorCache->inlineSlotCount);
}
Output::Flush();
}
#endif
// If the constructor has no return statements, we can safely return the object that was created here.
// No need to check what the constructor returned - it must be undefined.
returnNewScObj = ctorCache->CtorHasNoExplicitReturnValue();
Assert(Js::ConstructorCache::GetSizeOfGuardValue() == static_cast<size_t>(TySize[TyMachPtr]));
IR::MemRefOpnd* guardOpnd = IR::MemRefOpnd::New(ctorCache->GetRuntimeCacheGuardAddr(), TyMachReg, this->m_func,
IR::AddrOpndKindDynamicGuardValueRef);
IR::AddrOpnd* zeroOpnd = IR::AddrOpnd::NewNull(this->m_func);
InsertCompareBranch(guardOpnd, zeroOpnd, Js::OpCode::BrEq_A, helperOrBailoutLabel, newObjInstr);
// If we are calling new on a class constructor, the contract is that we pass new.target as the 'this' argument.
// function is the constructor on which we called new - which is new.target.
FixedFieldInfo* ctor = newObjInstr->GetFixedFunction();
if (ctor->IsClassCtor())
{
// MOV newObjDst, function
this->InsertMove(newObjDst, newObjInstr->GetSrc1(), newObjInstr);
}
else
{
JITTypeHolder newObjectType(ctorCache->GetType());
Assert(newObjectType->IsShared());
IR::AddrOpnd* typeSrc = IR::AddrOpnd::New(newObjectType->GetAddr(), IR::AddrOpndKindDynamicType, m_func);
// For the next call:
// inlineSlotSize == Number of slots to allocate beyond the DynamicObject header
// slotSize - inlineSlotSize == Number of aux slots to allocate
int inlineSlotSize = ctorCache->GetInlineSlotCount();
int slotSize = ctorCache->GetSlotCount();
if (newObjectType->GetTypeHandler()->IsObjectHeaderInlinedTypeHandler())
{
Assert(inlineSlotSize >= Js::DynamicTypeHandler::GetObjectHeaderInlinableSlotCapacity());
Assert(inlineSlotSize == slotSize);
slotSize = inlineSlotSize -= Js::DynamicTypeHandler::GetObjectHeaderInlinableSlotCapacity();
}
GenerateDynamicObjectAlloc(newObjInstr, inlineSlotSize, slotSize, newObjDst, typeSrc);
}
// JMP $callCtor
IR::BranchInstr *callCtorBranch = IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, callCtorLabel, m_func);
newObjInstr->InsertBefore(callCtorBranch);
return true;
}
void
Lowerer::GenerateRecyclerAllocAligned(IR::JnHelperMethod allocHelper, size_t allocSize, IR::RegOpnd* newObjDst, IR::Instr* insertionPointInstr, bool inOpHelper)
{
IR::LabelInstr * allocDoneLabel = nullptr;
if (!PHASE_OFF(Js::JitAllocNewObjPhase, insertionPointInstr->m_func) && HeapInfo::IsSmallObject(allocSize))
{
IR::LabelInstr * allocHelperLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true);
allocDoneLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, inOpHelper);
this->m_lowererMD.GenerateFastRecyclerAlloc(allocSize, newObjDst, insertionPointInstr, allocHelperLabel, allocDoneLabel);
// $allocHelper:
insertionPointInstr->InsertBefore(allocHelperLabel);
}
// call JavascriptOperators::AllocMemForScObject(allocSize, scriptContext->GetRecycler())
this->m_lowererMD.LoadHelperArgument(insertionPointInstr, this->LoadScriptContextValueOpnd(insertionPointInstr, ScriptContextValue::ScriptContextRecycler));
this->m_lowererMD.LoadHelperArgument(insertionPointInstr, IR::IntConstOpnd::New((int32)allocSize, TyUint32, m_func, true));
IR::Instr *newObjCall = IR::Instr::New(Js::OpCode::Call, newObjDst, IR::HelperCallOpnd::New(allocHelper, m_func), m_func);
insertionPointInstr->InsertBefore(newObjCall);
this->m_lowererMD.LowerCall(newObjCall, 0);
if (allocDoneLabel != nullptr)
{
// $allocDone:
insertionPointInstr->InsertBefore(allocDoneLabel);
}
}
IR::Instr *
Lowerer::LowerGetNewScObject(IR::Instr *instr)
{
Assert(instr);
Assert(instr->m_opcode == Js::OpCode::GetNewScObject);
Assert(instr->GetDst());
Assert(instr->GetSrc1());
Assert(instr->GetSrc2());
const auto instrPrev = instr->m_prev;
Assert(instrPrev);
LowerGetNewScObjectCommon(
instr->GetDst()->AsRegOpnd(),
instr->GetSrc1()->AsRegOpnd(),
instr->GetSrc2()->AsRegOpnd(),
instr);
instr->Remove();
return instrPrev;
}
void
Lowerer::LowerGetNewScObjectCommon(
IR::RegOpnd *const resultObjOpnd,
IR::RegOpnd *const constructorReturnOpnd,
IR::RegOpnd *const newObjOpnd,
IR::Instr *insertBeforeInstr)
{
Assert(resultObjOpnd);
Assert(constructorReturnOpnd);
Assert(newObjOpnd);
Assert(insertBeforeInstr);
// (newObjOpnd == 'this' value passed to constructor)
//
// if (!IsJsObject(constructorReturnOpnd))
// goto notObjectLabel
// newObjOpnd = constructorReturnOpnd
// notObjectLabel:
// resultObjOpnd = newObjOpnd
if(!constructorReturnOpnd->IsEqual(newObjOpnd))
{
// Need to check whether the constructor returned an object
IR::LabelInstr *notObjectLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func);
Assert(insertBeforeInstr->m_prev);
IR::LabelInstr *const doneLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func);
insertBeforeInstr->InsertBefore(doneLabel);
insertBeforeInstr = doneLabel;
#if defined(_M_ARM32_OR_ARM64)
m_lowererMD.LoadHelperArgument(insertBeforeInstr, constructorReturnOpnd);
IR::Opnd * targetOpnd = IR::RegOpnd::New(StackSym::New(TyInt32,m_func), TyInt32, m_func);
IR::Instr * callIsObjectInstr = IR::Instr::New(Js::OpCode::Call, targetOpnd, m_func);
insertBeforeInstr->InsertBefore(callIsObjectInstr);
this->m_lowererMD.ChangeToHelperCall(callIsObjectInstr, IR::HelperOp_IsObject);
InsertTestBranch( targetOpnd, targetOpnd, Js::OpCode::BrEq_A, notObjectLabel,insertBeforeInstr);
#else
m_lowererMD.GenerateIsJsObjectTest(constructorReturnOpnd, insertBeforeInstr, notObjectLabel);
#endif
// Value returned by constructor is an object (use constructorReturnOpnd)
if(!resultObjOpnd->IsEqual(constructorReturnOpnd))
{
this->InsertMove(resultObjOpnd, constructorReturnOpnd, insertBeforeInstr);
}
insertBeforeInstr->InsertBefore(IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, doneLabel, m_func));
// Value returned by constructor is not an object (use newObjOpnd)
insertBeforeInstr->InsertBefore(notObjectLabel);
}
if(!resultObjOpnd->IsEqual(newObjOpnd))
{
this->InsertMove(resultObjOpnd, newObjOpnd, insertBeforeInstr);
}
// fall through to insertBeforeInstr or doneLabel
}
///----------------------------------------------------------------------------
///
/// Lowerer::LowerUpdateNewScObjectCache
///
///----------------------------------------------------------------------------
IR::Instr *
Lowerer::LowerUpdateNewScObjectCache(IR::Instr * insertInstr, IR::Opnd *dst, IR::Opnd *src1, const bool isCtorFunction)
{
// if (!isCtorFunction)
// {
// MOV r1, [src1 + offset(type)] -- check base TypeIds_Function
// CMP [r1 + offset(typeId)], TypeIds_Function
// }
// JNE $fallThru
// MOV r2, [src1 + offset(constructorCache)]
// MOV r3, [r2 + offset(updateAfterCtor)]
// TEST r3, r3 -- check if updateAfterCtor is 0
// JEQ $fallThru
// CALL UpdateNewScObjectCache(src1, dst, scriptContext)
// $fallThru:
IR::LabelInstr *labelFallThru = IR::LabelInstr::New(Js::OpCode::Label, m_func);
src1 = GetRegOpnd(src1, insertInstr, m_func, TyMachReg);
// Check if constructor is a function if we don't already know it.
if (!isCtorFunction)
{
IR::RegOpnd* src1RegOpnd = src1->AsRegOpnd();
// MOV r1, [src1 + offset(type)] -- check base TypeIds_Function
IR::RegOpnd *r1 = IR::RegOpnd::New(TyMachReg, this->m_func);
IR::IndirOpnd *indirOpnd = IR::IndirOpnd::New(src1RegOpnd, Js::RecyclableObject::GetOffsetOfType(), TyMachReg, this->m_func);
Lowerer::InsertMove(r1, indirOpnd, insertInstr);
// CMP [r1 + offset(typeId)], TypeIds_Function
// JNE $fallThru
indirOpnd = IR::IndirOpnd::New(r1, Js::Type::GetOffsetOfTypeId(), TyInt32, this->m_func);
IR::IntConstOpnd *intOpnd = IR::IntConstOpnd::New(Js::TypeIds_Function, TyInt32, this->m_func, true);
IR::BranchInstr* branchInstr = InsertCompareBranch(indirOpnd, intOpnd, Js::OpCode::BrNeq_A, labelFallThru, insertInstr);
InsertObjectPoison(src1RegOpnd, branchInstr, insertInstr, false);
}
// Every function has a constructor cache, even if only the default blank one.
// r2 = MOV JavascriptFunction->constructorCache
IR::RegOpnd *r2 = IR::RegOpnd::New(TyVar, this->m_func);
IR::IndirOpnd *opndIndir = IR::IndirOpnd::New(src1->AsRegOpnd(), Js::JavascriptFunction::GetOffsetOfConstructorCache(), TyMachReg, this->m_func);
IR::Instr *instr = Lowerer::InsertMove(r2, opndIndir, insertInstr);
// r3 = constructorCache->updateAfterCtor
IR::RegOpnd *r3 = IR::RegOpnd::New(TyInt8, this->m_func);
IR::IndirOpnd *indirOpnd = IR::IndirOpnd::New(r2, Js::ConstructorCache::GetOffsetOfUpdateAfterCtor(), TyUint8, this->m_func);
instr = Lowerer::InsertMove(r3, indirOpnd, insertInstr);
// TEST r3, r3 -- check if updateAfterCtor is 0
// JEQ $fallThru
InsertTestBranch(r3, r3, Js::OpCode::BrEq_A, labelFallThru, insertInstr);
// r2 = UpdateNewScObjectCache(src1, dst, scriptContext)
insertInstr->InsertBefore(IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true)); // helper label for uncommon path
IR::HelperCallOpnd * opndHelper = IR::HelperCallOpnd::New(IR::HelperUpdateNewScObjectCache, m_func);
LoadScriptContext(insertInstr);
m_lowererMD.LoadHelperArgument(insertInstr, dst);
m_lowererMD.LoadHelperArgument(insertInstr, src1);
instr = IR::Instr::New(Js::OpCode::Call, m_func);
instr->SetSrc1(opndHelper);
insertInstr->InsertBefore(instr);
m_lowererMD.LowerCall(instr, 0);
// $fallThru:
insertInstr->InsertBefore(labelFallThru);
return insertInstr;
}
IR::Instr *
Lowerer::LowerNewScObjArray(IR::Instr *newObjInstr)
{
if (newObjInstr->HasEmptyArgOutChain())
{
newObjInstr->FreeSrc2();
return LowerNewScObjArrayNoArg(newObjInstr);
}
IR::Instr* startMarkerInstr = nullptr;
IR::Opnd *targetOpnd = newObjInstr->GetSrc1();
Func *func = newObjInstr->m_func;
if (!targetOpnd->IsAddrOpnd())
{
if (!newObjInstr->HasBailOutInfo() || newObjInstr->OnlyHasLazyBailOut())
{
return this->LowerNewScObject(newObjInstr, true, true);
}
// Insert a temporary label before the instruction we're about to lower, so that we can return
// the first instruction above that needs to be lowered after we're done - regardless of argument
// list, StartCall, etc.
startMarkerInstr = InsertLoweredRegionStartMarker(newObjInstr);
// For whatever reason, we couldn't do a fixed function check on the call target.
// Generate a runtime check on the target.
Assert(
newObjInstr->GetBailOutKind() == IR::BailOutOnNotNativeArray ||
newObjInstr->GetBailOutKind() == BailOutInfo::WithLazyBailOut(IR::BailOutOnNotNativeArray)
);
IR::LabelInstr *labelSkipBailOut = IR::LabelInstr::New(Js::OpCode::Label, func);
InsertCompareBranch(
targetOpnd,
LoadLibraryValueOpnd(newObjInstr, LibraryValue::ValueArrayConstructor),
Js::OpCode::BrEq_A,
true,
labelSkipBailOut,
newObjInstr);
IR::ProfiledInstr *instrNew = IR::ProfiledInstr::New(newObjInstr->m_opcode, newObjInstr->UnlinkDst(), newObjInstr->UnlinkSrc1(), newObjInstr->UnlinkSrc2(), func);
instrNew->u.profileId = newObjInstr->AsProfiledInstr()->u.profileId;
newObjInstr->InsertAfter(instrNew);
newObjInstr->m_opcode = Js::OpCode::BailOut;
GenerateBailOut(newObjInstr);
instrNew->InsertBefore(labelSkipBailOut);
newObjInstr = instrNew;
}
else
{
// Insert a temporary label before the instruction we're about to lower, so that we can return
// the first instruction above that needs to be lowered after we're done - regardless of argument
// list, StartCall, etc.
startMarkerInstr = InsertLoweredRegionStartMarker(newObjInstr);
}
intptr_t weakFuncRef = 0;
Js::ArrayCallSiteInfo *arrayInfo = nullptr;
intptr_t arrayInfoAddr = 0;
Assert(newObjInstr->IsProfiledInstr());
IR::RegOpnd *resultObjOpnd = newObjInstr->GetDst()->AsRegOpnd();
IR::Instr * insertInstr = newObjInstr->m_next;
Js::ProfileId profileId = static_cast<Js::ProfileId>(newObjInstr->AsProfiledInstr()->u.profileId);
// We may not have profileId if we converted a NewScObject to NewScObjArray
if (profileId != Js::Constants::NoProfileId)
{
arrayInfo = func->GetReadOnlyProfileInfo()->GetArrayCallSiteInfo(profileId);
arrayInfoAddr = func->GetReadOnlyProfileInfo()->GetArrayCallSiteInfoAddr(profileId);
Assert(arrayInfo);
weakFuncRef = func->GetWeakFuncRef();
Assert(weakFuncRef);
}
IR::LabelInstr * helperLabel = IR::LabelInstr::New(Js::OpCode::Label, func, true);
IR::LabelInstr *labelDone = IR::LabelInstr::New(Js::OpCode::Label, func);
IR::Opnd *linkOpnd = newObjInstr->GetSrc2();
Assert(linkOpnd->IsSymOpnd());
StackSym *linkSym = linkOpnd->AsSymOpnd()->m_sym->AsStackSym();
Assert(linkSym->IsSingleDef());
IR::Instr* argInstr = linkSym->GetInstrDef();
IR::Opnd *opndOfArrayCtor = argInstr->GetSrc1();
const uint16 upperBoundValue = 8;
// Generate fast path only if it meets all the conditions:
// 1. It is the only parameter and it is a likely int
// 2a. If 1st parameter is a variable, emit fast path with checks
// 2b. If 1st parameter is a constant, it is in range 0 and upperBoundValue (inclusive)
if (opndOfArrayCtor->GetValueType().IsLikelyInt() && (opndOfArrayCtor->IsAddrOpnd() || opndOfArrayCtor->IsRegOpnd())) // #1
{
if ((linkSym->GetArgSlotNum() == 2)) // 1. It is the only parameter
{
AssertMsg(linkSym->IsArgSlotSym(), "Not an argSlot symbol...");
linkOpnd = argInstr->GetSrc2();
bool emittedFastPath = false;
// 2a. If 1st parameter is a variable, emit fast path with checks
if (opndOfArrayCtor->IsRegOpnd())
{
if (!opndOfArrayCtor->AsRegOpnd()->IsNotInt())
{
// 3. GenerateFastPath
if (arrayInfo && arrayInfo->IsNativeIntArray())
{
emittedFastPath = GenerateProfiledNewScObjArrayFastPath<Js::JavascriptNativeIntArray>(newObjInstr, arrayInfo, arrayInfoAddr, weakFuncRef, helperLabel, labelDone, opndOfArrayCtor,
Js::JavascriptNativeIntArray::GetOffsetOfArrayCallSiteIndex(),
Js::JavascriptNativeIntArray::GetOffsetOfWeakFuncRef());
}
else if (arrayInfo && arrayInfo->IsNativeFloatArray())
{
emittedFastPath = GenerateProfiledNewScObjArrayFastPath<Js::JavascriptNativeFloatArray>(newObjInstr, arrayInfo, arrayInfoAddr, weakFuncRef, helperLabel, labelDone, opndOfArrayCtor,
Js::JavascriptNativeFloatArray::GetOffsetOfArrayCallSiteIndex(),
Js::JavascriptNativeFloatArray::GetOffsetOfWeakFuncRef());
}
else
{
emittedFastPath = GenerateProfiledNewScObjArrayFastPath<Js::JavascriptArray>(newObjInstr, arrayInfo, arrayInfoAddr, weakFuncRef, helperLabel, labelDone, opndOfArrayCtor, 0, 0);
}
}
}
// 2b. If 1st parameter is a constant, it is in range 0 and upperBoundValue (inclusive)
else
{
int32 length = linkSym->GetIntConstValue();
if (length >= 0 && length <= upperBoundValue)
{
emittedFastPath = GenerateProfiledNewScObjArrayFastPath(newObjInstr, arrayInfo, arrayInfoAddr, weakFuncRef, (uint32)length, labelDone, false);
}
}
// Since we emitted fast path above, move the startCall/argOut instruction right before helper
if (emittedFastPath)
{
linkSym = linkOpnd->AsRegOpnd()->m_sym->AsStackSym();
AssertMsg(!linkSym->IsArgSlotSym() && linkSym->m_isSingleDef, "Arg tree not single def...");
IR::Instr* startCallInstr = linkSym->m_instrDef;
AssertMsg(startCallInstr->GetArgOutCount(false) == 2, "Generating ArrayFastPath for more than 1 parameter not allowed.");
// Since we emitted fast path above, move the startCall/argOut instruction right before helper
startCallInstr->Move(newObjInstr);
argInstr->Move(newObjInstr);
}
}
}
newObjInstr->UnlinkSrc1();
IR::Opnd *profileOpnd = IR::AddrOpnd::New(arrayInfoAddr, IR::AddrOpndKindDynamicArrayCallSiteInfo, func);
this->m_lowererMD.LoadNewScObjFirstArg(newObjInstr, profileOpnd);
IR::JnHelperMethod helperMethod = IR::HelperScrArr_ProfiledNewInstance;
newObjInstr->SetSrc1(IR::HelperCallOpnd::New(helperMethod, func));
newObjInstr = GenerateDirectCall(newObjInstr, targetOpnd, Js::CallFlags_New);
IR::BranchInstr* branchInstr = InsertCompareBranch(
IR::IndirOpnd::New(resultObjOpnd, 0, TyMachPtr, func),
LoadVTableValueOpnd(insertInstr, VTableValue::VtableJavascriptArray),
Js::OpCode::BrEq_A,
true,
labelDone,
insertInstr);
InsertObjectPoison(resultObjOpnd, branchInstr, insertInstr, true);
// We know we have a native array, so store the weak ref and call site index.
InsertMove(
IR::IndirOpnd::New(resultObjOpnd, Js::JavascriptNativeArray::GetOffsetOfArrayCallSiteIndex(), TyUint16, func),
IR::Opnd::CreateProfileIdOpnd(profileId, func),
insertInstr);
InsertMove(
IR::IndirOpnd::New(resultObjOpnd, Js::JavascriptNativeArray::GetOffsetOfWeakFuncRef(), TyMachReg, func),
IR::AddrOpnd::New(weakFuncRef, IR::AddrOpndKindDynamicFunctionBodyWeakRef, func),
insertInstr);
insertInstr->InsertBefore(labelDone);
return RemoveLoweredRegionStartMarker(startMarkerInstr);
}
IR::Instr *
Lowerer::LowerNewScObjArrayNoArg(IR::Instr *newObjInstr)
{
IR::Opnd *targetOpnd = newObjInstr->GetSrc1();
Func *func = newObjInstr->m_func;
IR::Instr* startMarkerInstr = nullptr;
if (!targetOpnd->IsAddrOpnd())
{
if (!newObjInstr->HasBailOutInfo() || newObjInstr->OnlyHasLazyBailOut())
{
return this->LowerNewScObject(newObjInstr, true, false);
}
// Insert a temporary label before the instruction we're about to lower, so that we can return
// the first instruction above that needs to be lowered after we're done - regardless of argument
// list, StartCall, etc.
startMarkerInstr = InsertLoweredRegionStartMarker(newObjInstr);
// For whatever reason, we couldn't do a fixed function check on the call target.
// Generate a runtime check on the target.
Assert(
newObjInstr->GetBailOutKind() == IR::BailOutOnNotNativeArray ||
newObjInstr->GetBailOutKind() == BailOutInfo::WithLazyBailOut(IR::BailOutOnNotNativeArray)
);
IR::LabelInstr *labelSkipBailOut = IR::LabelInstr::New(Js::OpCode::Label, func);
InsertCompareBranch(
targetOpnd,
LoadLibraryValueOpnd(newObjInstr, LibraryValue::ValueArrayConstructor),
Js::OpCode::BrEq_A,
true,
labelSkipBailOut,
newObjInstr);
IR::ProfiledInstr *instrNew = IR::ProfiledInstr::New(newObjInstr->m_opcode, newObjInstr->UnlinkDst(), newObjInstr->UnlinkSrc1(), func);
instrNew->u.profileId = newObjInstr->AsProfiledInstr()->u.profileId;
newObjInstr->InsertAfter(instrNew);
newObjInstr->m_opcode = Js::OpCode::BailOut;
GenerateBailOut(newObjInstr);
instrNew->InsertBefore(labelSkipBailOut);
newObjInstr = instrNew;
}
else
{
// Insert a temporary label before the instruction we're about to lower, so that we can return
// the first instruction above that needs to be lowered after we're done - regardless of argument
// list, StartCall, etc.
startMarkerInstr = InsertLoweredRegionStartMarker(newObjInstr);
}
Assert(newObjInstr->IsProfiledInstr());
intptr_t weakFuncRef = 0;
intptr_t arrayInfoAddr = 0;
Js::ArrayCallSiteInfo *arrayInfo = nullptr;
Js::ProfileId profileId = static_cast<Js::ProfileId>(newObjInstr->AsProfiledInstr()->u.profileId);
if (profileId != Js::Constants::NoProfileId)
{
arrayInfo = func->GetReadOnlyProfileInfo()->GetArrayCallSiteInfo(profileId);
arrayInfoAddr = func->GetReadOnlyProfileInfo()->GetArrayCallSiteInfoAddr(profileId);
Assert(arrayInfo);
weakFuncRef = func->GetWeakFuncRef();
Assert(weakFuncRef);
}
IR::LabelInstr *labelDone = IR::LabelInstr::New(Js::OpCode::Label, func);
GenerateProfiledNewScObjArrayFastPath(newObjInstr, arrayInfo, arrayInfoAddr, weakFuncRef, 0, labelDone, true);
newObjInstr->InsertAfter(labelDone);
m_lowererMD.LoadHelperArgument(newObjInstr, IR::AddrOpnd::New(weakFuncRef, IR::AddrOpndKindDynamicFunctionBodyWeakRef, func));
m_lowererMD.LoadHelperArgument(newObjInstr, IR::AddrOpnd::New(arrayInfoAddr, IR::AddrOpndKindDynamicArrayCallSiteInfo, func));
LoadScriptContext(newObjInstr);
m_lowererMD.LoadHelperArgument(newObjInstr, targetOpnd);
newObjInstr->UnlinkSrc1();
newObjInstr->SetSrc1(IR::HelperCallOpnd::New(IR::HelperScrArr_ProfiledNewInstanceNoArg, func));
m_lowererMD.LowerCall(newObjInstr, 0);
return RemoveLoweredRegionStartMarker(startMarkerInstr);
}
///----------------------------------------------------------------------------
///
/// Lowerer::LowerPrologEpilog
///
///----------------------------------------------------------------------------
void
Lowerer::LowerPrologEpilog()
{
if (m_func->GetJITFunctionBody()->IsCoroutine())
{
LowerGeneratorResumeJumpTable();
}
IR::Instr * instr;
instr = m_func->m_headInstr;
AssertMsg(instr->IsEntryInstr(), "First instr isn't an EntryInstr...");
m_lowererMD.LowerEntryInstr(instr->AsEntryInstr());
instr = m_func->m_exitInstr;
AssertMsg(instr->IsExitInstr(), "Last instr isn't an ExitInstr...");
m_lowererMD.LowerExitInstr(instr->AsExitInstr());
}
void
Lowerer::LowerPrologEpilogAsmJs()
{
IR::Instr * instr;
instr = m_func->m_headInstr;
AssertMsg(instr->IsEntryInstr(), "First instr isn't an EntryInstr...");
m_lowererMD.LowerEntryInstr(instr->AsEntryInstr());
instr = m_func->m_exitInstr;
AssertMsg(instr->IsExitInstr(), "Last instr isn't an ExitInstr...");
m_lowererMD.LowerExitInstrAsmJs(instr->AsExitInstr());
}
void
Lowerer::LowerGeneratorResumeJumpTable()
{
Assert(m_func->GetJITFunctionBody()->IsCoroutine());
IR::Instr * jumpTableInstr = m_func->m_headInstr;
AssertMsg(jumpTableInstr->IsEntryInstr(), "First instr isn't an EntryInstr...");
// Hope to do away with this linked list scan by moving this lowering to a post-prolog-epilog/pre-encoder phase that is common to all architectures (currently such phase is only available on amd64/arm)
while (jumpTableInstr->m_opcode != Js::OpCode::GeneratorResumeJumpTable)
{
jumpTableInstr = jumpTableInstr->m_next;
}
IR::Opnd * srcOpnd = jumpTableInstr->UnlinkSrc1();
m_func->MapYieldOffsetResumeLabels([&](int i, const YieldOffsetResumeLabel& yorl)
{
uint32 offset = yorl.First();
IR::LabelInstr * label = yorl.Second();
if (label != nullptr && label->m_hasNonBranchRef)
{
// Also fix up the bailout at the label with the jump to epilog that was not emitted in GenerateBailOut()
Assert(label->m_prev->HasBailOutInfo());
GenerateJumpToEpilogForBailOut(label->m_prev->GetBailOutInfo(), label->m_prev);
}
else if (label == nullptr)
{
label = m_func->m_bailOutNoSaveLabel;
}
// For each offset label pair, insert a compare of the offset and branch if equal to the label
InsertCompareBranch(srcOpnd, IR::IntConstOpnd::New(offset, TyUint32, m_func), Js::OpCode::BrSrEq_A, label, jumpTableInstr);
});
jumpTableInstr->Remove();
}
void
Lowerer::DoInterruptProbes()
{
this->m_func->SetHasInstrNumber(true);
uint instrCount = 1;
FOREACH_INSTR_IN_FUNC(instr, this->m_func)
{
instr->SetNumber(instrCount++);
if (instr->IsLabelInstr())
{
IR::LabelInstr *labelInstr = instr->AsLabelInstr();
if (labelInstr->m_isLoopTop)
{
// For every loop top label, insert the following:
// cmp sp, ThreadContext::stackLimitForCurrentThread
// bgt $continue
// $helper:
// call JavascriptOperators::ScriptAbort
// b $exit
// $continue:
IR::LabelInstr *newLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
labelInstr->InsertAfter(newLabel);
this->InsertOneLoopProbe(newLabel, newLabel);
}
}
}
NEXT_INSTR_IN_FUNC;
}
// Insert an interrupt probe at each loop back branch. (Currently uncalled, since we're inserting
// probes at loop tops instead of back edges, but kept around because it may prove useful.)
uint
Lowerer::DoLoopProbeAndNumber(IR::BranchInstr *branchInstr)
{
IR::LabelInstr *labelInstr = branchInstr->GetTarget();
if (labelInstr == nullptr || labelInstr->GetNumber() == 0)
{
// Forward branch (possibly an indirect jump after try-catch-finally); nothing to do.
return branchInstr->GetNumber() + 1;
}
Assert(labelInstr->m_isLoopTop);
// Insert a stack probe at this branch. Number all the instructions we insert
// and return the next instruction number.
uint number = branchInstr->GetNumber();
IR::Instr *instrPrev = branchInstr->m_prev;
IR::Instr *instrNext = branchInstr->m_next;
if (branchInstr->IsUnconditional())
{
// B $loop ==>
// cmp [], 0
// beq $loop
// $helper:
// call abort
// b $exit
this->InsertOneLoopProbe(branchInstr, labelInstr);
branchInstr->Remove();
}
else
{
// Bcc $loop ==>
// Binv $notloop
// cmp [], 0
// beq $loop
// $helper:
// call abort
// b $exit
// $notloop:
IR::LabelInstr *loopExitLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
branchInstr->SetTarget(loopExitLabel);
LowererMD::InvertBranch(branchInstr);
branchInstr->InsertAfter(loopExitLabel);
this->InsertOneLoopProbe(loopExitLabel, labelInstr);
}
FOREACH_INSTR_IN_RANGE(instr, instrPrev->m_next, instrNext->m_prev)
{
instr->SetNumber(number++);
}
NEXT_INSTR_IN_RANGE;
return number;
}
void
Lowerer::InsertOneLoopProbe(IR::Instr *insertInstr, IR::LabelInstr *loopLabel)
{
// Insert one interrupt probe at the given instruction. Probe the stack and call the abort helper
// directly if the probe fails.
IR::Opnd *memRefOpnd = IR::MemRefOpnd::New(
m_func->GetThreadContextInfo()->GetThreadStackLimitAddr(),
TyMachReg, this->m_func);
IR::RegOpnd *regStackPointer = IR::RegOpnd::New(
NULL, this->m_lowererMD.GetRegStackPointer(), TyMachReg, this->m_func);
InsertCompareBranch(regStackPointer, memRefOpnd, Js::OpCode::BrGt_A, loopLabel, insertInstr);
IR::LabelInstr *helperLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true);
insertInstr->InsertBefore(helperLabel);
IR::HelperCallOpnd *helperOpnd = IR::HelperCallOpnd::New(IR::HelperScriptAbort, this->m_func);
IR::Instr *instr = IR::Instr::New(Js::OpCode::Call, this->m_func);
instr->SetSrc1(helperOpnd);
insertInstr->InsertBefore(instr);
this->m_lowererMD.LowerCall(instr, 0);
// Jump to the exit after the helper call. This instruction will never be reached, but the jump
// indicates that nothing is live after the call (to avoid useless spills in code that will
// be executed).
instr = this->m_func->m_exitInstr->GetPrevRealInstrOrLabel();
if (instr->IsLabelInstr())
{
helperLabel = instr->AsLabelInstr();
}
else
{
helperLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
this->m_func->m_exitInstr->InsertBefore(helperLabel);
}
instr = IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, helperLabel, this->m_func);
insertInstr->InsertBefore(instr);
}
///----------------------------------------------------------------------------
///
/// Lowerer::LoadPropertySymAsArgument
///
/// Generate code to pass a fieldSym as argument to a helper.
///----------------------------------------------------------------------------
IR::Instr *
Lowerer::LoadPropertySymAsArgument(IR::Instr *instr, IR::Opnd *fieldSrc)
{
IR::Instr * instrPrev;
AssertMsg(fieldSrc->IsSymOpnd() && fieldSrc->AsSymOpnd()->m_sym->IsPropertySym(), "Expected fieldSym as src of LdFld");
IR::SymOpnd *symOpnd = fieldSrc->AsSymOpnd();
PropertySym * fieldSym = symOpnd->m_sym->AsPropertySym();
IR::IntConstOpnd * indexOpnd = IR::IntConstOpnd::New(fieldSym->m_propertyId, TyInt32, m_func, /*dontEncode*/true);
instrPrev = m_lowererMD.LoadHelperArgument(instr, indexOpnd);
IR::RegOpnd * instanceOpnd = symOpnd->CreatePropertyOwnerOpnd(m_func);
m_lowererMD.LoadHelperArgument(instr, instanceOpnd);
return instrPrev;
}
///----------------------------------------------------------------------------
///
/// Lowerer::LoadFunctionBodyAsArgument
///
/// Special case: the "property ID" is a key into the ScriptContext's FunctionBody map
///----------------------------------------------------------------------------
IR::Instr *
Lowerer::LoadFunctionBodyAsArgument(IR::Instr *instr, IR::IntConstOpnd * functionBodySlotOpnd, IR::RegOpnd * envOpnd)
{
IR::Instr * instrPrev;
// We need to pass in the function reference, we can't embed the pointer to the function proxy here.
// The function proxy may be deferred parsed/serialize, and may 'progress' to a real function body after it is undeferred
// At which point the deferred function proxy may be collect.
// Just pass it the address where we will find the function proxy/body
Js::FunctionInfoPtrPtr infoRef = instr->m_func->GetJITFunctionBody()->GetNestedFuncRef((uint)functionBodySlotOpnd->GetValue());
AssertMsg(infoRef, "Expected FunctionProxy for index of NewScFunc or NewScGenFunc opnd");
IR::AddrOpnd * indexOpnd = IR::AddrOpnd::New((Js::Var)infoRef, IR::AddrOpndKindDynamicMisc, m_func);
instrPrev = m_lowererMD.LoadHelperArgument(instr, indexOpnd);
m_lowererMD.LoadHelperArgument(instr, envOpnd);
return instrPrev;
}
IR::Instr *
Lowerer::LowerProfiledLdFld(IR::JitProfilingInstr *ldFldInstr)
{
const auto instrPrev = ldFldInstr->m_prev;
auto src = ldFldInstr->UnlinkSrc1();
AssertMsg(src->IsSymOpnd() && src->AsSymOpnd()->m_sym->IsPropertySym(), "Expected property sym as src");
IR::JnHelperMethod helper = IR::HelperInvalid;
switch (ldFldInstr->m_opcode)
{
case Js::OpCode::LdFld:
helper = IR::HelperProfiledLdFld;
goto ldFldCommon;
case Js::OpCode::LdRootFld:
helper = IR::HelperProfiledLdRootFld;
goto ldFldCommon;
case Js::OpCode::LdMethodFld:
helper = IR::HelperProfiledLdMethodFld;
goto ldFldCommon;
case Js::OpCode::LdRootMethodFld:
helper = IR::HelperProfiledLdRootMethodFld;
goto ldFldCommon;
case Js::OpCode::LdFldForCallApplyTarget:
helper = IR::HelperProfiledLdFld_CallApplyTarget;
goto ldFldCommon;
case Js::OpCode::LdFldForTypeOf:
helper = IR::HelperProfiledLdFldForTypeOf;
goto ldFldCommon;
case Js::OpCode::LdRootFldForTypeOf:
helper = IR::HelperProfiledLdRootFldForTypeOf;
goto ldFldCommon;
ldFldCommon:
{
Assert(ldFldInstr->profileId == Js::Constants::NoProfileId);
/*
Var ProfilingHelpers::ProfiledLdFld_Jit(
const Var instance,
const PropertyId propertyId,
const InlineCacheIndex inlineCacheIndex,
void *const framePointer)
*/
m_lowererMD.LoadHelperArgument(ldFldInstr, IR::Opnd::CreateFramePointerOpnd(m_func));
m_lowererMD.LoadHelperArgument(
ldFldInstr,
IR::Opnd::CreateInlineCacheIndexOpnd(src->AsPropertySymOpnd()->m_inlineCacheIndex, m_func));
LoadPropertySymAsArgument(ldFldInstr, src);
break;
}
case Js::OpCode::LdSuperFld:
{
Assert(ldFldInstr->profileId == Js::Constants::NoProfileId);
IR::Opnd * src2 = nullptr;
/*
Var ProfilingHelpers::ProfiledLdSuperFld_Jit(
const Var instance,
const PropertyId propertyId,
const InlineCacheIndex inlineCacheIndex,
void *const framePointer,
const Var thisInstance)
*/
src2 = ldFldInstr->UnlinkSrc2();
m_lowererMD.LoadHelperArgument(ldFldInstr, src2 );
m_lowererMD.LoadHelperArgument(ldFldInstr, IR::Opnd::CreateFramePointerOpnd(m_func));
m_lowererMD.LoadHelperArgument(
ldFldInstr,
IR::Opnd::CreateInlineCacheIndexOpnd(src->AsPropertySymOpnd()->m_inlineCacheIndex, m_func));
LoadPropertySymAsArgument(ldFldInstr, src);
helper = IR::HelperProfiledLdSuperFld;
break;
}
case Js::OpCode::LdLen_A:
Assert(ldFldInstr->profileId != Js::Constants::NoProfileId);
/*
Var ProfilingHelpers::ProfiledLdLen_Jit(
const Var instance,
const PropertyId propertyId,
const InlineCacheIndex inlineCacheIndex,
const ProfileId profileId,
void *const framePointer)
*/
m_lowererMD.LoadHelperArgument(ldFldInstr, IR::Opnd::CreateFramePointerOpnd(m_func));
m_lowererMD.LoadHelperArgument(ldFldInstr, IR::Opnd::CreateProfileIdOpnd(ldFldInstr->profileId, m_func));
m_lowererMD.LoadHelperArgument(ldFldInstr, IR::Opnd::CreateInlineCacheIndexOpnd(src->AsPropertySymOpnd()->m_inlineCacheIndex, m_func));
LoadPropertySymAsArgument(ldFldInstr, src);
helper = IR::HelperProfiledLdLen;
break;
default:
Assert(false);
}
ldFldInstr->SetSrc1(IR::HelperCallOpnd::New(helper, m_func));
m_lowererMD.LowerCall(ldFldInstr, 0);
return instrPrev;
}
void
Lowerer::GenerateProtoLdFldFromFlagInlineCache(
IR::Instr * insertBeforeInstr,
IR::Opnd * opndDst,
IR::RegOpnd * opndInlineCache,
IR::LabelInstr * labelFallThru,
bool isInlineSlot)
{
// Generate:
//
// s1 = MOV [&(inlineCache->u.accessor.object)] -- load the cached prototype object
// s1 = MOV [&s1->slots] -- load the slot array
// s2 = MOVZXW [&(inlineCache->u.accessor.slotIndex)] -- load the cached slot index
// dst = MOV [s1 + s2*4]
// JMP $fallthru
IR::Opnd* inlineCacheObjOpnd;
IR::IndirOpnd * opndIndir;
IR::RegOpnd * opndObjSlots = nullptr;
inlineCacheObjOpnd = IR::IndirOpnd::New(opndInlineCache, (int32)offsetof(Js::InlineCache, u.accessor.object), TyMachReg, this->m_func);
// s1 = MOV [&(inlineCache->u.accessor.object)] -- load the cached prototype object
IR::RegOpnd *opndObject = IR::RegOpnd::New(TyMachReg, this->m_func);
InsertMove(opndObject, inlineCacheObjOpnd, insertBeforeInstr, false);
if (!isInlineSlot)
{
// s1 = MOV [&s1->slots] -- load the slot array
opndObjSlots = IR::RegOpnd::New(TyMachReg, this->m_func);
opndIndir = IR::IndirOpnd::New(opndObject, Js::DynamicObject::GetOffsetOfAuxSlots(), TyMachReg, this->m_func);
InsertMove(opndObjSlots, opndIndir, insertBeforeInstr, false);
}
// s2 = MOVZXW [&(inlineCache->u.accessor.slotIndex)] -- load the cached slot index
IR::RegOpnd *opndSlotIndex = IR::RegOpnd::New(TyMachReg, this->m_func);
IR::Opnd* slotIndexOpnd = IR::IndirOpnd::New(opndInlineCache, (int32)offsetof(Js::InlineCache, u.accessor.slotIndex), TyUint16, this->m_func);
InsertMove(opndSlotIndex, slotIndexOpnd, insertBeforeInstr, false);
if (isInlineSlot)
{
// dst = MOV [s1 + s2*4]
opndIndir = IR::IndirOpnd::New(opndObject, opndSlotIndex, m_lowererMD.GetDefaultIndirScale(), TyMachReg, this->m_func);
}
else
{
// dst = MOV [s1 + s2*4]
opndIndir = IR::IndirOpnd::New(opndObjSlots, opndSlotIndex, m_lowererMD.GetDefaultIndirScale(), TyMachReg, this->m_func);
}
InsertMove(opndDst, opndIndir, insertBeforeInstr, false);
// JMP $fallthru
InsertBranch(Js::OpCode::Br, labelFallThru, insertBeforeInstr);
}
void
Lowerer::GenerateLocalLdFldFromFlagInlineCache(
IR::Instr * insertBeforeInstr,
IR::RegOpnd * opndBase,
IR::Opnd * opndDst,
IR::RegOpnd * opndInlineCache,
IR::LabelInstr * labelFallThru,
bool isInlineSlot)
{
// Generate:
//
// s1 = MOV [&(inlineCache->u.accessor.object)] -- load the cached prototype object
// s1 = MOV [&s1->slots] -- load the slot array
// s2 = MOVZXW [&(inlineCache->u.accessor.slotIndex)] -- load the cached slot index
// dst = MOV [s1 + s2*4]
// JMP $fallthru
IR::IndirOpnd * opndIndir;
IR::RegOpnd * opndObjSlots = nullptr;
if (!isInlineSlot)
{
// s1 = MOV [&s1->slots] -- load the slot array
opndObjSlots = IR::RegOpnd::New(TyMachReg, this->m_func);
opndIndir = IR::IndirOpnd::New(opndBase, Js::DynamicObject::GetOffsetOfAuxSlots(), TyMachReg, this->m_func);
InsertMove(opndObjSlots, opndIndir, insertBeforeInstr, false);
}
// s2 = MOVZXW [&(inlineCache->u.accessor.slotIndex)] -- load the cached slot index
IR::RegOpnd *opndSlotIndex = IR::RegOpnd::New(TyMachReg, this->m_func);
IR::Opnd* slotIndexOpnd = IR::IndirOpnd::New(opndInlineCache, (int32)offsetof(Js::InlineCache, u.accessor.slotIndex), TyUint16, this->m_func);
InsertMove(opndSlotIndex, slotIndexOpnd, insertBeforeInstr, false);
if (isInlineSlot)
{
// dst = MOV [s1 + s2*4]
opndIndir = IR::IndirOpnd::New(opndBase, opndSlotIndex, m_lowererMD.GetDefaultIndirScale(), TyMachReg, this->m_func);
}
else
{
// dst = MOV [s1 + s2*4]
opndIndir = IR::IndirOpnd::New(opndObjSlots, opndSlotIndex, m_lowererMD.GetDefaultIndirScale(), TyMachReg, this->m_func);
}
InsertMove(opndDst, opndIndir, insertBeforeInstr, false);
// JMP $fallthru
InsertBranch(Js::OpCode::Br, labelFallThru, insertBeforeInstr);
}
void
Lowerer::GenerateFlagProtoCheck(
IR::Instr * insertBeforeInstr,
IR::RegOpnd * opndInlineCache,
IR::LabelInstr * labelNotOnProto)
{
// Generate:
//
// TEST [&(inlineCache->u.accessor.isOnProto)], Js::FlagIsOnProto
// JEQ $next
IR::Opnd* flagsOpnd;
flagsOpnd = IR::IndirOpnd::New(opndInlineCache, (int32)offsetof(Js::InlineCache, u.accessor.rawUInt16), TyInt8, insertBeforeInstr->m_func);
uint isOnProtoFlagMask = Js::InlineCache::GetIsOnProtoFlagMask();
InsertTestBranch(flagsOpnd, IR::IntConstOpnd::New(isOnProtoFlagMask, TyInt8, this->m_func), Js::OpCode::BrEq_A, labelNotOnProto, insertBeforeInstr);
}
///----------------------------------------------------------------------------
///
/// Lowerer::GenerateFastLdMethodFromFlags
///
/// Make use of the helper to cache the type and slot index used to do a LdFld
/// and do an inline load from the appropriate slot if the type hasn't changed
/// since the last time this LdFld was executed.
///
///----------------------------------------------------------------------------
bool
Lowerer::GenerateFastLdMethodFromFlags(IR::Instr * instrLdFld)
{
IR::LabelInstr * labelFallThru;
IR::LabelInstr * bailOutLabel;
IR::Opnd * opndSrc;
IR::Opnd * opndDst;
IR::RegOpnd * opndBase;
IR::RegOpnd * opndType;
IR::RegOpnd * opndInlineCache;
opndSrc = instrLdFld->GetSrc1();
AssertMsg(opndSrc->IsSymOpnd() && opndSrc->AsSymOpnd()->IsPropertySymOpnd() && opndSrc->AsSymOpnd()->m_sym->IsPropertySym(),
"Expected property sym operand as src of LdFldFlags");
IR::PropertySymOpnd * propertySymOpnd = opndSrc->AsPropertySymOpnd();
Assert(!instrLdFld->DoStackArgsOpt());
if (propertySymOpnd->IsTypeCheckSeqCandidate())
{
AssertMsg(propertySymOpnd->HasObjectTypeSym(), "Type optimized property sym operand without a type sym?");
StackSym *typeSym = propertySymOpnd->GetObjectTypeSym();
opndType = IR::RegOpnd::New(typeSym, TyMachReg, this->m_func);
}
else
{
opndType = IR::RegOpnd::New(TyMachReg, this->m_func);
}
opndBase = propertySymOpnd->CreatePropertyOwnerOpnd(m_func);
opndDst = instrLdFld->GetDst();
opndInlineCache = IR::RegOpnd::New(TyMachPtr, this->m_func);
labelFallThru = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
// Label to jump to (or fall through to) when bailing out
bailOutLabel = IR::LabelInstr::New(Js::OpCode::Label, instrLdFld->m_func, true /* isOpHelper */);
InsertMove(opndInlineCache, LoadRuntimeInlineCacheOpnd(instrLdFld, propertySymOpnd), instrLdFld);
IR::LabelInstr * labelFlagAux = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
// Check the flag cache with the untagged type
GenerateObjectTestAndTypeLoad(instrLdFld, opndBase, opndType, bailOutLabel);
GenerateFlagInlineCacheCheck(instrLdFld, opndType, opndInlineCache, labelFlagAux);
IR::LabelInstr * labelFlagInlineLocal = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
GenerateFlagProtoCheck(instrLdFld, opndInlineCache, labelFlagInlineLocal);
GenerateProtoLdFldFromFlagInlineCache(instrLdFld, opndDst, opndInlineCache, labelFallThru, true);
instrLdFld->InsertBefore(labelFlagInlineLocal);
GenerateLocalLdFldFromFlagInlineCache(instrLdFld, opndBase, opndDst, opndInlineCache, labelFallThru, true);
// Check the flag cache with the tagged type
instrLdFld->InsertBefore(labelFlagAux);
IR::RegOpnd * opndTaggedType = IR::RegOpnd::New(TyMachReg, this->m_func);
m_lowererMD.GenerateLoadTaggedType(instrLdFld, opndType, opndTaggedType);
GenerateFlagInlineCacheCheck(instrLdFld, opndTaggedType, opndInlineCache, bailOutLabel);
IR::LabelInstr * labelFlagAuxLocal = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
GenerateFlagProtoCheck(instrLdFld, opndInlineCache, labelFlagAuxLocal);
GenerateProtoLdFldFromFlagInlineCache(instrLdFld, opndDst, opndInlineCache, labelFallThru, false);
instrLdFld->InsertBefore(labelFlagAuxLocal);
GenerateLocalLdFldFromFlagInlineCache(instrLdFld, opndBase, opndDst, opndInlineCache, labelFallThru, false);
instrLdFld->InsertBefore(bailOutLabel);
instrLdFld->InsertAfter(labelFallThru);
// Generate the bailout helper call. 'instr' will be changed to the CALL into the bailout function, so it can't be used for
// ordering instructions anymore.
instrLdFld->UnlinkSrc1();
GenerateBailOut(instrLdFld);
return true;
}
///----------------------------------------------------------------------------
///
/// Lowerer::LowerLdFld
///
/// Lower an instruction (LdFld, ScopedLdFld) that takes a property
/// reference as a source and puts a result in a register.
///
///----------------------------------------------------------------------------
IR::Instr *
Lowerer::LowerLdFld(
IR::Instr * ldFldInstr,
IR::JnHelperMethod helperMethod,
IR::JnHelperMethod polymorphicHelperMethod,
bool useInlineCache,
IR::LabelInstr *labelBailOut,
bool isHelper)
{
if (ldFldInstr->IsJitProfilingInstr())
{
// If we want to profile then do something completely different
return this->LowerProfiledLdFld(ldFldInstr->AsJitProfilingInstr());
}
IR::Opnd *src;
IR::Instr *instrPrev = ldFldInstr->m_prev;
src = ldFldInstr->UnlinkSrc1();
if (ldFldInstr->m_opcode == Js::OpCode::LdSuperFld)
{
IR::Opnd * src2 = nullptr;
src2 = ldFldInstr->UnlinkSrc2();
m_lowererMD.LoadHelperArgument(ldFldInstr, src2);
}
AssertMsg(src->IsSymOpnd() && src->AsSymOpnd()->m_sym->IsPropertySym(), "Expected property sym as src");
if (useInlineCache)
{
IR::Opnd * inlineCacheOpnd;
AssertMsg(src->AsSymOpnd()->IsPropertySymOpnd(), "Need property sym operand to find the inline cache");
if (src->AsPropertySymOpnd()->m_runtimePolymorphicInlineCache && polymorphicHelperMethod != helperMethod)
{
JITTimePolymorphicInlineCache * polymorphicInlineCache = src->AsPropertySymOpnd()->m_runtimePolymorphicInlineCache;
helperMethod = polymorphicHelperMethod;
inlineCacheOpnd = IR::AddrOpnd::New(polymorphicInlineCache->GetAddr(), IR::AddrOpndKindDynamicInlineCache, this->m_func);
}
else
{
// Need to load runtime inline cache opnd first before loading any helper argument
// because LoadRuntimeInlineCacheOpnd may create labels marked as helper,
// and cause op helper register push/pop save in x86, messing up with any helper arguments that is already pushed
inlineCacheOpnd = this->LoadRuntimeInlineCacheOpnd(ldFldInstr, src->AsPropertySymOpnd(), isHelper);
}
this->LoadPropertySymAsArgument(ldFldInstr, src);
this-> m_lowererMD.LoadHelperArgument(
ldFldInstr,
IR::Opnd::CreateInlineCacheIndexOpnd(src->AsPropertySymOpnd()->m_inlineCacheIndex, m_func));
this->m_lowererMD.LoadHelperArgument(ldFldInstr, inlineCacheOpnd);
this->m_lowererMD.LoadHelperArgument(ldFldInstr, LoadFunctionBodyOpnd(ldFldInstr));
}
else
{
LoadScriptContext(ldFldInstr);
this->LoadPropertySymAsArgument(ldFldInstr, src);
}
// Do we need to reload the type and slot array after the helper returns?
// (We do if there's a propertySymOpnd downstream that needs it, i.e., the type is not dead.)
IR::RegOpnd *opndBase = src->AsSymOpnd()->CreatePropertyOwnerOpnd(m_func);
m_lowererMD.ChangeToHelperCall(ldFldInstr, helperMethod, labelBailOut, opndBase, src->AsSymOpnd()->IsPropertySymOpnd() ? src->AsSymOpnd()->AsPropertySymOpnd() : nullptr, isHelper);
return instrPrev;
}
bool
Lowerer::GenerateLdFldWithCachedType(IR::Instr * instrLdFld, bool* continueAsHelperOut, IR::LabelInstr** labelHelperOut, IR::RegOpnd** typeOpndOut)
{
IR::Instr *instr;
IR::Opnd *opnd;
IR::LabelInstr *labelObjCheckFailed = nullptr;
IR::LabelInstr *labelTypeCheckFailed = nullptr;
IR::LabelInstr *labelDone = nullptr;
Assert(continueAsHelperOut != nullptr);
*continueAsHelperOut = false;
Assert(labelHelperOut != nullptr);
*labelHelperOut = nullptr;
Assert(typeOpndOut != nullptr);
*typeOpndOut = nullptr;
Assert(instrLdFld->GetSrc1()->IsSymOpnd());
if (!instrLdFld->GetSrc1()->AsSymOpnd()->IsPropertySymOpnd())
{
return false;
}
IR::PropertySymOpnd *propertySymOpnd = instrLdFld->GetSrc1()->AsPropertySymOpnd();
if (!propertySymOpnd->IsTypeCheckSeqCandidate())
{
return false;
}
AssertMsg(propertySymOpnd->TypeCheckSeqBitsSetOnlyIfCandidate(), "Property sym operand optimized despite not being a candidate?");
if (!propertySymOpnd->IsTypeCheckSeqParticipant() && !propertySymOpnd->NeedsLocalTypeCheck())
{
return false;
}
Assert(!propertySymOpnd->NeedsTypeCheckAndBailOut() || (instrLdFld->HasBailOutInfo() && IR::IsTypeCheckBailOutKind(instrLdFld->GetBailOutKind())));
// In the backwards pass we only add guarded property operations to instructions that are not already
// protected by an upstream type check.
Assert(!propertySymOpnd->IsTypeCheckProtected() || propertySymOpnd->GetGuardedPropOps() == nullptr);
PHASE_PRINT_TESTTRACE(
Js::ObjTypeSpecPhase,
this->m_func,
_u("Field load: %s, property ID: %d, func: %s, cache ID: %d, cloned cache: true, layout: %s, redundant check: %s\n"),
Js::OpCodeUtil::GetOpCodeName(instrLdFld->m_opcode),
propertySymOpnd->m_sym->AsPropertySym()->m_propertyId,
this->m_func->GetJITFunctionBody()->GetDisplayName(),
propertySymOpnd->m_inlineCacheIndex,
propertySymOpnd->GetCacheLayoutString(),
propertySymOpnd->IsTypeChecked() ? _u("true") : _u("false"));
if (propertySymOpnd->HasFinalType() && !propertySymOpnd->IsLoadedFromProto())
{
propertySymOpnd->UpdateSlotForFinalType();
}
// TODO (ObjTypeSpec): If ((PropertySym*)propertySymOpnd->m_sym)->m_stackSym->m_isIntConst consider emitting a direct
// jump to helper or bailout. If we have a type check bailout, we could even abort compilation.
bool hasTypeCheckBailout = instrLdFld->HasBailOutInfo() && IR::IsTypeCheckBailOutKind(instrLdFld->GetBailOutKind());
// If the hard-coded type is not available here, do a type check, and branch to the helper if the check fails.
// In the prototype case, we have to check the type even if it was checked upstream, to cover the case where
// the property has been added locally. Note that this is not necessary if the proto chain has been checked,
// because then we know there's been no store of the property since the type was checked.
bool emitPrimaryTypeCheck = propertySymOpnd->NeedsPrimaryTypeCheck();
bool emitLocalTypeCheck = propertySymOpnd->NeedsLocalTypeCheck();
bool emitLoadFromProtoTypeCheck = propertySymOpnd->NeedsLoadFromProtoTypeCheck();
bool emitTypeCheck = emitPrimaryTypeCheck || emitLocalTypeCheck || emitLoadFromProtoTypeCheck;
if (emitTypeCheck)
{
if (emitLoadFromProtoTypeCheck)
{
propertySymOpnd->EnsureGuardedPropOps(this->m_func->m_alloc);
propertySymOpnd->SetGuardedPropOp(propertySymOpnd->GetObjTypeSpecFldId());
}
labelTypeCheckFailed = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true);
labelObjCheckFailed = hasTypeCheckBailout ? labelTypeCheckFailed : IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true);
*typeOpndOut = this->GenerateCachedTypeCheck(instrLdFld, propertySymOpnd, labelObjCheckFailed, labelTypeCheckFailed);
}
IR::Opnd *opndSlotArray;
if (propertySymOpnd->IsLoadedFromProto())
{
opndSlotArray = this->LoadSlotArrayWithCachedProtoType(instrLdFld, propertySymOpnd);
}
else
{
opndSlotArray = this->LoadSlotArrayWithCachedLocalType(instrLdFld, propertySymOpnd);
}
// Load the value from the slot, getting the slot ID from the cache.
uint16 index = propertySymOpnd->GetSlotIndex();
AssertOrFailFast(index != (uint16)-1);
if (opndSlotArray->IsRegOpnd())
{
opnd = IR::IndirOpnd::New(opndSlotArray->AsRegOpnd(), index * sizeof(Js::Var), TyMachReg, this->m_func);
}
else
{
Assert(opndSlotArray->IsMemRefOpnd());
opnd = IR::MemRefOpnd::New((char*)opndSlotArray->AsMemRefOpnd()->GetMemLoc() + (index * sizeof(Js::Var)), TyMachReg, this->m_func, IR::AddrOpndKindDynamicPropertySlotRef);
}
Lowerer::InsertMove(instrLdFld->GetDst(), opnd, instrLdFld);
// We eliminate the helper, or the type check succeeds, or we bail out before the operation.
// Either delete the original instruction or replace it with a bailout.
if (!emitPrimaryTypeCheck && !emitLocalTypeCheck && !emitLoadFromProtoTypeCheck)
{
Assert(labelTypeCheckFailed == nullptr);
AssertMsg(!instrLdFld->HasBailOutInfo() || instrLdFld->HasLazyBailOut(), "Why does a direct field load have bailout that is not lazy?");
instrLdFld->Remove();
return true;
}
// Otherwise, branch around the bailout or helper.
labelDone = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
instr = IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, labelDone, this->m_func);
instrLdFld->InsertBefore(instr);
// Insert the bailout or helper label here.
instrLdFld->InsertBefore(labelTypeCheckFailed);
instrLdFld->InsertAfter(labelDone);
if (hasTypeCheckBailout)
{
AssertMsg(PHASE_ON1(Js::ObjTypeSpecIsolatedFldOpsWithBailOutPhase) || !propertySymOpnd->IsTypeDead(),
"Why does a field load have a type check bailout, if its type is dead?");
// Convert the original instruction to a bailout.
if (instrLdFld->GetBailOutInfo()->bailOutInstr != instrLdFld)
{
// Set the cache index in the bailout info so that the bailout code will write it into the
// bailout record at runtime.
instrLdFld->GetBailOutInfo()->polymorphicCacheIndex = propertySymOpnd->m_inlineCacheIndex;
}
instrLdFld->FreeDst();
instrLdFld->FreeSrc1();
instrLdFld->m_opcode = Js::OpCode::BailOut;
this->GenerateBailOut(instrLdFld);
return true;
}
else
{
*continueAsHelperOut = true;
Assert(labelObjCheckFailed != nullptr && labelObjCheckFailed != labelTypeCheckFailed);
*labelHelperOut = labelObjCheckFailed;
return false;
}
}
template<bool isRoot>
IR::Instr* Lowerer::GenerateCompleteLdFld(IR::Instr* instr, bool emitFastPath, IR::JnHelperMethod monoHelperAfterFastPath, IR::JnHelperMethod polyHelperAfterFastPath,
IR::JnHelperMethod monoHelperWithoutFastPath, IR::JnHelperMethod polyHelperWithoutFastPath)
{
if(instr->CallsAccessor() && instr->HasBailOutInfo())
{
Assert(!BailOutInfo::IsBailOutOnImplicitCalls(instr->GetBailOutKind()));
}
IR::Instr* prevInstr = instr->m_prev;
IR::LabelInstr* labelHelper = nullptr;
IR::LabelInstr* labelBailOut = nullptr;
bool isHelper = false;
IR::RegOpnd* typeOpnd = nullptr;
if (isRoot)
{
// Don't do the fast path here if emitFastPath is false, even if we can.
if (emitFastPath && (this->GenerateLdFldWithCachedType(instr, &isHelper, &labelHelper, &typeOpnd) || this->GenerateNonConfigurableLdRootFld(instr)))
{
Assert(labelHelper == nullptr);
return prevInstr;
}
}
else
{
if (this->GenerateLdFldWithCachedType(instr, &isHelper, &labelHelper, &typeOpnd))
{
Assert(labelHelper == nullptr);
return prevInstr;
}
}
if (emitFastPath)
{
if (!GenerateFastLdFld(instr, monoHelperWithoutFastPath, polyHelperWithoutFastPath, &labelBailOut, typeOpnd, &isHelper, &labelHelper))
{
if (labelHelper != nullptr)
{
labelHelper->isOpHelper = isHelper;
instr->InsertBefore(labelHelper);
}
prevInstr = LowerLdFld(instr, monoHelperAfterFastPath, polyHelperAfterFastPath, true, labelBailOut, isHelper);
}
}
else
{
if (labelHelper != nullptr)
{
labelHelper->isOpHelper = isHelper;
instr->InsertBefore(labelHelper);
}
prevInstr = LowerLdFld(instr, monoHelperWithoutFastPath, polyHelperWithoutFastPath, true, labelBailOut, isHelper);
}
return prevInstr;
}
bool
Lowerer::GenerateCheckFixedFld(IR::Instr * instrChkFld)
{
IR::Instr *instr;
IR::LabelInstr *labelBailOut = nullptr;
IR::LabelInstr *labelDone = nullptr;
AssertMsg(!PHASE_OFF(Js::FixedMethodsPhase, instrChkFld->m_func) ||
!PHASE_OFF(Js::UseFixedDataPropsPhase, instrChkFld->m_func), "Lowering a check fixed field with fixed data/method phase disabled?");
Assert(instrChkFld->GetSrc1()->IsSymOpnd() && instrChkFld->GetSrc1()->AsSymOpnd()->IsPropertySymOpnd());
IR::PropertySymOpnd *propertySymOpnd = instrChkFld->GetSrc1()->AsPropertySymOpnd();
AssertMsg(propertySymOpnd->TypeCheckSeqBitsSetOnlyIfCandidate(), "Property sym operand optimized despite not being a candidate?");
Assert(propertySymOpnd->MayNeedTypeCheckProtection());
// In the backwards pass we only add guarded property operations to instructions that are not already
// protected by an upstream type check.
Assert(!propertySymOpnd->IsTypeCheckProtected() || propertySymOpnd->GetGuardedPropOps() == nullptr);
// For the non-configurable properties on the global object we do not need a type check. Otherwise,
// we need a type check and bailout here unless this operation is part of the type check sequence and
// is protected by a type check upstream.
bool emitPrimaryTypeCheck = propertySymOpnd->NeedsPrimaryTypeCheck();
// In addition, we may also need a local type check in case the property comes from the prototype and
// it may have been overwritten on the instance after the primary type check upstream. If the property
// comes from the instance, we must still protect against its value changing after the type check, but
// for this a cheaper guard check is sufficient (see below).
bool emitFixedFieldTypeCheck = propertySymOpnd->NeedsCheckFixedFieldTypeCheck() &&
(!propertySymOpnd->IsTypeChecked() || propertySymOpnd->IsLoadedFromProto());
PropertySym * propertySym = propertySymOpnd->m_sym->AsPropertySym();
uint inlineCacheIndex = propertySymOpnd->m_inlineCacheIndex;
bool checkFixedDataGenerated = false;
bool checkFixedTypeGenerated = false;
OUTPUT_TRACE_FUNC(
Js::ObjTypeSpecPhase,
this->m_func,
_u("Fixed field check: %s, property ID: %d, cache ID: %u, cloned cache: true, layout: %s, redundant check: %s count of props: %u \n"),
Js::OpCodeUtil::GetOpCodeName(instrChkFld->m_opcode),
propertySym->m_propertyId,
inlineCacheIndex, propertySymOpnd->GetCacheLayoutString(), propertySymOpnd->IsTypeChecked() ? _u("true") : _u("false"),
propertySymOpnd->GetGuardedPropOps() ? propertySymOpnd->GetGuardedPropOps()->Count() : 0);
if (emitPrimaryTypeCheck || emitFixedFieldTypeCheck)
{
labelBailOut = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true);
if(emitFixedFieldTypeCheck && propertySymOpnd->IsRootObjectNonConfigurableFieldLoad())
{
AssertMsg(!propertySymOpnd->GetGuardedPropOps() || propertySymOpnd->GetGuardedPropOps()->IsEmpty(), "This property Guard is used only for one property");
//We need only cheaper Guard check, if the property belongs to the GlobalObject.
checkFixedDataGenerated = this->GenerateFixedFieldGuardCheck(instrChkFld, propertySymOpnd, labelBailOut);
}
else
{
if (emitFixedFieldTypeCheck)
{
propertySymOpnd->EnsureGuardedPropOps(this->m_func->m_alloc);
propertySymOpnd->SetGuardedPropOp(propertySymOpnd->GetObjTypeSpecFldId());
}
this->GenerateCachedTypeCheck(instrChkFld, propertySymOpnd, labelBailOut, labelBailOut);
checkFixedTypeGenerated = true;
}
}
// We may still need this guard if we didn't emit the write protect type check above. This situation arises if we have
// a fixed field from the instance (not proto) and a property of the same name has been written somewhere between the
// primary type check and here. Note that we don't need a type check, because we know the fixed field exists on the
// object even if it has been written since primary type check, but we need to verify the fixed value didn't get overwritten.
if (!emitPrimaryTypeCheck && !emitFixedFieldTypeCheck && !propertySymOpnd->IsWriteGuardChecked())
{
if (!PHASE_OFF(Js::FixedFieldGuardCheckPhase, this->m_func))
{
Assert(labelBailOut == nullptr);
labelBailOut = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true);
checkFixedDataGenerated = this->GenerateFixedFieldGuardCheck(instrChkFld, propertySymOpnd, labelBailOut);
}
}
// Note that a type handler holds only a weak reference to the singleton instance it represents, so
// it is possible that the instance gets collected before the type and handler do. Hence, the upstream
// type check may succeed, even as the original instance no longer exists. However, this would happen
// only if another instance reached the same type (otherwise we wouldn't ever pass the type check
// upstream). In that case we would have invalidated all fixed fields on that type, and so the type
// check (or property guard check, if necessary) above would fail. All in all, we would never attempt
// to access a fixed field from an instance that has been collected.
if (!emitPrimaryTypeCheck && !emitFixedFieldTypeCheck && propertySymOpnd->IsWriteGuardChecked())
{
Assert(labelBailOut == nullptr);
AssertMsg(!instrChkFld->HasBailOutInfo(), "Why does a direct fixed field check have bailout?");
if (propertySymOpnd->ProducesAuxSlotPtr())
{
this->GenerateAuxSlotPtrLoad(propertySymOpnd, instrChkFld);
}
instrChkFld->Remove();
return true;
}
// With lazy bailout, no checks might be generated for CheckFixedFld, so the code in Lowerer is only an
// unconditional jmp to get past the bailout helper block. This is a new case and is unexpected, so layout
// phase will also move the statement boundary preceding CheckFixedFld together with the jmp to after
// function exit. As a result, source mapping is incorrect. Make sure that this doesn't happen by not
// generating helper blocks at all if we don't generate checks.
if (!checkFixedDataGenerated && !checkFixedTypeGenerated)
{
instrChkFld->Remove();
return true;
}
labelDone = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
instr = IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, labelDone, this->m_func);
instrChkFld->InsertBefore(instr);
// Insert the helper label here.
instrChkFld->InsertBefore(labelBailOut);
instrChkFld->InsertAfter(labelDone);
if (propertySymOpnd->ProducesAuxSlotPtr())
{
this->GenerateAuxSlotPtrLoad(propertySymOpnd, labelDone->m_next);
}
// Convert the original instruction to a bailout.
Assert(instrChkFld->HasBailOutInfo());
if (instrChkFld->GetBailOutInfo()->bailOutInstr != instrChkFld)
{
// Set the cache index in the bailout info so that the bailout code will write it into the
// bailout record at runtime.
instrChkFld->GetBailOutInfo()->polymorphicCacheIndex = inlineCacheIndex;
}
instrChkFld->FreeSrc1();
instrChkFld->m_opcode = Js::OpCode::BailOut;
this->GenerateBailOut(instrChkFld);
return true;
}
void
Lowerer::GenerateCheckObjType(IR::Instr * instrChkObjType)
{
Assert(instrChkObjType->GetSrc1()->IsSymOpnd() && instrChkObjType->GetSrc1()->AsSymOpnd()->IsPropertySymOpnd());
IR::PropertySymOpnd *propertySymOpnd = instrChkObjType->GetSrc1()->AsPropertySymOpnd();
// Why do we have an explicit type check if the cached type has been checked upstream? The dead store pass should have
// removed this instruction.
Assert(propertySymOpnd->IsTypeCheckSeqCandidate() && !propertySymOpnd->IsTypeChecked());
// Why do we have an explicit type check on a non-configurable root field load?
Assert(!propertySymOpnd->IsRootObjectNonConfigurableFieldLoad());
PropertySym * propertySym = propertySymOpnd->m_sym->AsPropertySym();
uint inlineCacheIndex = propertySymOpnd->m_inlineCacheIndex;
PHASE_PRINT_TESTTRACE(
Js::ObjTypeSpecPhase,
this->m_func,
_u("Object type check: %s, property ID: %d, func: %s, cache ID: %d, cloned cache: true, layout: %s, redundant check: %s\n"),
Js::OpCodeUtil::GetOpCodeName(instrChkObjType->m_opcode),
propertySym->m_propertyId,
this->m_func->GetJITFunctionBody()->GetDisplayName(),
inlineCacheIndex, propertySymOpnd->GetCacheLayoutString(), _u("false"));
IR::LabelInstr* labelBailOut = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true);
this->GenerateCachedTypeCheck(instrChkObjType, propertySymOpnd, labelBailOut, labelBailOut);
IR::LabelInstr* labelDone = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
IR::Instr* instr = IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, labelDone, this->m_func);
instrChkObjType->InsertBefore(instr);
// Insert the bailout label here.
instrChkObjType->InsertBefore(labelBailOut);
instrChkObjType->InsertAfter(labelDone);
if (propertySymOpnd->ProducesAuxSlotPtr())
{
this->GenerateAuxSlotPtrLoad(propertySymOpnd, labelDone->m_next);
}
// Convert the original instruction to a bailout.
Assert(instrChkObjType->HasBailOutInfo());
if (instrChkObjType->GetBailOutInfo()->bailOutInstr != instrChkObjType)
{
// Set the cache index in the bailout info so that the bailout code will write it into the
// bailout record at runtime.
instrChkObjType->GetBailOutInfo()->polymorphicCacheIndex = inlineCacheIndex;
}
instrChkObjType->FreeSrc1();
instrChkObjType->m_opcode = Js::OpCode::BailOut;
this->GenerateBailOut(instrChkObjType);
}
void
Lowerer::LowerAdjustObjType(IR::Instr * instrAdjustObjType)
{
IR::AddrOpnd *finalTypeOpnd = instrAdjustObjType->UnlinkDst()->AsAddrOpnd();
IR::AddrOpnd *initialTypeOpnd = instrAdjustObjType->UnlinkSrc2()->AsAddrOpnd();
IR::RegOpnd *baseOpnd = instrAdjustObjType->UnlinkSrc1()->AsRegOpnd();
bool adjusted = this->GenerateAdjustBaseSlots(
instrAdjustObjType, baseOpnd, JITTypeHolder((JITType*)initialTypeOpnd->m_metadata), JITTypeHolder((JITType*)finalTypeOpnd->m_metadata));
if (instrAdjustObjType->m_opcode == Js::OpCode::AdjustObjTypeReloadAuxSlotPtr)
{
Assert(adjusted);
// We reallocated the aux slots, so reload them if necessary.
StackSym * auxSlotPtrSym = baseOpnd->m_sym->GetAuxSlotPtrSym();
Assert(auxSlotPtrSym);
IR::Opnd *opndIndir = IR::IndirOpnd::New(baseOpnd, Js::DynamicObject::GetOffsetOfAuxSlots(), TyMachReg, this->m_func);
IR::RegOpnd *regOpnd = IR::RegOpnd::New(auxSlotPtrSym, TyMachReg, this->m_func);
regOpnd->SetIsJITOptimizedReg(true);
Lowerer::InsertMove(regOpnd, opndIndir, instrAdjustObjType);
}
this->m_func->PinTypeRef((JITType*)finalTypeOpnd->m_metadata);
IR::Opnd *opnd = IR::IndirOpnd::New(baseOpnd, Js::RecyclableObject::GetOffsetOfType(), TyMachReg, instrAdjustObjType->m_func);
this->InsertMove(opnd, finalTypeOpnd, instrAdjustObjType);
initialTypeOpnd->Free(instrAdjustObjType->m_func);
instrAdjustObjType->Remove();
}
bool
Lowerer::GenerateNonConfigurableLdRootFld(IR::Instr * instrLdFld)
{
if (!instrLdFld->GetSrc1()->AsSymOpnd()->IsPropertySymOpnd())
{
return false;
}
IR::PropertySymOpnd *propertySymOpnd = instrLdFld->GetSrc1()->AsPropertySymOpnd();
if (!propertySymOpnd->IsRootObjectNonConfigurableFieldLoad())
{
return false;
}
Assert(!PHASE_OFF(Js::RootObjectFldFastPathPhase, this->m_func));
Assert(!instrLdFld->HasBailOutInfo() || instrLdFld->HasLazyBailOut());
if (instrLdFld->HasLazyBailOut())
{
instrLdFld->ClearBailOutInfo();
}
IR::Opnd * srcOpnd;
intptr_t rootObject = this->m_func->GetJITFunctionBody()->GetRootObject();
if (propertySymOpnd->UsesAuxSlot())
{
IR::RegOpnd * auxSlotOpnd = IR::RegOpnd::New(TyMachPtr, this->m_func);
this->InsertMove(auxSlotOpnd, IR::MemRefOpnd::New((byte *)rootObject + Js::DynamicObject::GetOffsetOfAuxSlots(),
TyMachPtr, this->m_func), instrLdFld);
srcOpnd = IR::IndirOpnd::New(auxSlotOpnd, propertySymOpnd->GetSlotIndex() * sizeof(Js::Var *),
TyVar, this->m_func);
}
else
{
srcOpnd = IR::MemRefOpnd::New((Js::Var *)rootObject + propertySymOpnd->GetSlotIndex(),
TyVar, this->m_func);
}
instrLdFld->ReplaceSrc1(srcOpnd);
instrLdFld->m_opcode = Js::OpCode::Ld_A;
LowererMD::ChangeToAssign(instrLdFld);
return true;
}
IR::Instr *
Lowerer::LowerDelFld(IR::Instr *delFldInstr, IR::JnHelperMethod helperMethod, bool useInlineCache, bool strictMode)
{
IR::Instr *instrPrev;
Js::PropertyOperationFlags propertyOperationFlag = Js::PropertyOperation_None;
if (strictMode)
{
propertyOperationFlag = Js::PropertyOperation_StrictMode;
}
instrPrev = m_lowererMD.LoadHelperArgument(delFldInstr, IR::IntConstOpnd::New((IntConstType)propertyOperationFlag, TyInt32, m_func, true));
LowerLdFld(delFldInstr, helperMethod, helperMethod, useInlineCache);
return instrPrev;
}
IR::Instr *
Lowerer::LowerIsInst(IR::Instr * isInstInstr, IR::JnHelperMethod helperMethod)
{
IR::Instr * instrPrev;
IR::Instr * instrArg;
IR::RegOpnd * argOpnd;
// inlineCache
instrPrev = m_lowererMD.LoadHelperArgument(isInstInstr, LoadIsInstInlineCacheOpnd(isInstInstr, isInstInstr->GetSrc1()->AsIntConstOpnd()->AsUint32()));
isInstInstr->FreeSrc1();
argOpnd = isInstInstr->UnlinkSrc2()->AsRegOpnd();
Assert(argOpnd->m_sym->m_isSingleDef);
instrArg = argOpnd->m_sym->m_instrDef;
argOpnd->Free(m_func);
// scriptContext
LoadScriptContext(isInstInstr);
// instance goes last, so remember it now
IR::Opnd * instanceOpnd = instrArg->UnlinkSrc1();
argOpnd = instrArg->UnlinkSrc2()->AsRegOpnd();
Assert(argOpnd->m_sym->m_isSingleDef);
instrArg->Remove();
instrArg = argOpnd->m_sym->m_instrDef;
argOpnd->Free(m_func);
// function
IR::Opnd *opnd = instrArg->UnlinkSrc1();
m_lowererMD.LoadHelperArgument(isInstInstr, opnd);
Assert(instrArg->GetSrc2() == NULL);
instrArg->Remove();
// instance
m_lowererMD.LoadHelperArgument(isInstInstr, instanceOpnd);
m_lowererMD.ChangeToHelperCall(isInstInstr, helperMethod);
return instrPrev;
}
void
Lowerer::GenerateStackScriptFunctionInit(StackSym * stackSym, Js::FunctionInfoPtrPtr nestedInfo)
{
Func * func = this->m_func;
Assert(func->HasAnyStackNestedFunc());
Assert(nextStackFunctionOpnd);
IR::Instr * insertBeforeInstr = func->GetFunctionEntryInsertionPoint();
IR::RegOpnd * addressOpnd = IR::RegOpnd::New(TyMachPtr, func);
const IR::AutoReuseOpnd autoReuseAddressOpnd(addressOpnd, func);
InsertLea(addressOpnd, IR::SymOpnd::New(stackSym, TyMachPtr, func), insertBeforeInstr);
// Currently we don't initialize the environment until we actually allocate the function, we also
// walk the list of stack function when we need to box them. so we should use initialize it to NullFrameDisplay
GenerateStackScriptFunctionInit(addressOpnd, nestedInfo,
IR::AddrOpnd::New(func->GetThreadContextInfo()->GetNullFrameDisplayAddr(), IR::AddrOpndKindDynamicMisc, func), insertBeforeInstr);
// Establish the next link
InsertMove(nextStackFunctionOpnd, addressOpnd, insertBeforeInstr);
this->nextStackFunctionOpnd = IR::SymOpnd::New(stackSym, sizeof(Js::StackScriptFunction), TyMachPtr, func);
}
void
Lowerer::GenerateScriptFunctionInit(IR::RegOpnd * regOpnd, IR::Opnd * vtableAddressOpnd,
Js::FunctionInfoPtrPtr nestedInfo, IR::Opnd * envOpnd, IR::Instr * insertBeforeInstr, bool isZeroed)
{
Func * func = this->m_func;
IR::Opnd * functionInfoOpnd = IR::RegOpnd::New(TyMachPtr, func);
InsertMove(functionInfoOpnd, IR::MemRefOpnd::New(nestedInfo, TyMachPtr, func), insertBeforeInstr);
IR::Opnd * functionProxyOpnd = IR::RegOpnd::New(TyMachPtr, func);
InsertMove(functionProxyOpnd, IR::IndirOpnd::New(functionInfoOpnd->AsRegOpnd(), Js::FunctionInfo::GetOffsetOfFunctionProxy(), TyMachPtr, func), insertBeforeInstr);
IR::Opnd * typeOpnd = IR::RegOpnd::New(TyMachPtr, func);
InsertMove(typeOpnd, IR::IndirOpnd::New(functionProxyOpnd->AsRegOpnd(), Js::FunctionProxy::GetOffsetOfDeferredPrototypeType(),
TyMachPtr, func), insertBeforeInstr);
IR::LabelInstr * labelHelper = IR::LabelInstr::New(Js::OpCode::Label, func, true);
InsertTestBranch(typeOpnd, typeOpnd, Js::OpCode::BrEq_A, labelHelper, insertBeforeInstr);
IR::LabelInstr * labelDone = IR::LabelInstr::New(Js::OpCode::Label, func, false);
InsertBranch(Js::OpCode::Br, labelDone, insertBeforeInstr);
insertBeforeInstr->InsertBefore(labelHelper);
m_lowererMD.LoadHelperArgument(insertBeforeInstr, functionProxyOpnd);
IR::Instr * callHelperInstr = IR::Instr::New(Js::OpCode::Call, typeOpnd,
IR::HelperCallOpnd::New(IR::JnHelperMethod::HelperEnsureFunctionProxyDeferredPrototypeType, func), func);
insertBeforeInstr->InsertBefore(callHelperInstr);
m_lowererMD.LowerCall(callHelperInstr, 0);
insertBeforeInstr->InsertBefore(labelDone);
GenerateMemInit(regOpnd, 0, vtableAddressOpnd, insertBeforeInstr, isZeroed);
GenerateMemInit(regOpnd, Js::ScriptFunction::GetOffsetOfType(), typeOpnd, insertBeforeInstr, isZeroed);
GenerateMemInitNull(regOpnd, Js::ScriptFunction::GetOffsetOfAuxSlots(), insertBeforeInstr, isZeroed);
GenerateMemInitNull(regOpnd, Js::ScriptFunction::GetOffsetOfObjectArray(), insertBeforeInstr, isZeroed);
GenerateMemInit(regOpnd, Js::ScriptFunction::GetOffsetOfConstructorCache(),
LoadLibraryValueOpnd(insertBeforeInstr, LibraryValue::ValueConstructorCacheDefaultInstance),
insertBeforeInstr, isZeroed);
GenerateMemInit(regOpnd, Js::ScriptFunction::GetOffsetOfFunctionInfo(), functionInfoOpnd, insertBeforeInstr, isZeroed);
GenerateMemInit(regOpnd, Js::ScriptFunction::GetOffsetOfEnvironment(), envOpnd, insertBeforeInstr, isZeroed);
GenerateMemInitNull(regOpnd, Js::ScriptFunction::GetOffsetOfCachedScopeObj(), insertBeforeInstr, isZeroed);
GenerateMemInitNull(regOpnd, Js::ScriptFunction::GetOffsetOfHasInlineCaches(), insertBeforeInstr, isZeroed);
}
void
Lowerer::GenerateStackScriptFunctionInit(IR::RegOpnd * regOpnd, Js::FunctionInfoPtrPtr nestedInfo, IR::Opnd * envOpnd, IR::Instr * insertBeforeInstr)
{
Func * func = this->m_func;
GenerateScriptFunctionInit(regOpnd,
LoadVTableValueOpnd(insertBeforeInstr, VTableValue::VtableStackScriptFunction),
nestedInfo, envOpnd, insertBeforeInstr);
InsertMove(IR::IndirOpnd::New(regOpnd, Js::StackScriptFunction::GetOffsetOfBoxedScriptFunction(), TyMachPtr, func),
IR::AddrOpnd::NewNull(func), insertBeforeInstr);
}
void
Lowerer::EnsureStackFunctionListStackSym()
{
Func * func = this->m_func;
Assert(func->HasAnyStackNestedFunc());
#if defined(_M_IX86) || defined(_M_X64)
Assert(func->m_localStackHeight == (func->HasArgumentSlot()? MachArgsSlotOffset : 0));
StackSym * stackFunctionListStackSym = StackSym::New(TyMachPtr, func);
func->StackAllocate(stackFunctionListStackSym, sizeof(Js::ScriptFunction *));
nextStackFunctionOpnd = IR::SymOpnd::New(stackFunctionListStackSym, TyMachPtr, func);
#else
Assert(func->m_localStackHeight == 0);
nextStackFunctionOpnd = IR::IndirOpnd::New(IR::RegOpnd::New(NULL, FRAME_REG, TyMachReg, func),
-(int32)(Js::Constants::StackNestedFuncList * sizeof(Js::Var)), TyMachPtr, func);
#endif
}
void
Lowerer::AllocStackClosure()
{
m_func->StackAllocate(m_func->GetLocalFrameDisplaySym(), sizeof(Js::Var));
m_func->StackAllocate(m_func->GetLocalClosureSym(), sizeof(Js::Var));
}
void
Lowerer::EnsureZeroLastStackFunctionNext()
{
Assert(nextStackFunctionOpnd != nullptr);
Func * func = this->m_func;
IR::Instr * insertBeforeInstr = func->GetFunctionEntryInsertionPoint();
InsertMove(nextStackFunctionOpnd, IR::AddrOpnd::NewNull(func), insertBeforeInstr);
}
IR::Instr *
Lowerer::GenerateNewStackScFunc(IR::Instr * newScFuncInstr, IR::RegOpnd ** ppEnvOpnd)
{
Assert(newScFuncInstr->m_func->DoStackNestedFunc());
Func * func = newScFuncInstr->m_func;
uint index = newScFuncInstr->GetSrc1()->AsIntConstOpnd()->AsUint32();
Assert(index < func->GetJITFunctionBody()->GetNestedCount());
IR::LabelInstr * labelNoStackFunc = IR::LabelInstr::New(Js::OpCode::Label, func, true);
IR::LabelInstr * labelDone = IR::LabelInstr::New(Js::OpCode::Label, func);
InsertTestBranch(IR::MemRefOpnd::New(func->GetJITFunctionBody()->GetFlagsAddr(), TyInt8, func),
IR::IntConstOpnd::New(Js::FunctionBody::Flags_StackNestedFunc, TyInt8, func, true),
Js::OpCode::BrEq_A, labelNoStackFunc, newScFuncInstr);
Js::FunctionInfoPtrPtr nestedInfo = func->GetJITFunctionBody()->GetNestedFuncRef(index);
IR::Instr * instrAssignDst;
IR::RegOpnd * envOpnd = *ppEnvOpnd;
if (!func->IsLoopBody())
{
// the stackAllocate Call below for this sym is passing a size that is not represented by any IRType and hence passing TyMisc for the constructor
StackSym * stackSym = StackSym::New(TyMisc, func);
// ScriptFunction and it's next pointer
this->m_func->StackAllocate(stackSym, sizeof(Js::StackScriptFunction) + sizeof(Js::StackScriptFunction *));
GenerateStackScriptFunctionInit(stackSym, nestedInfo);
InsertMove(IR::SymOpnd::New(stackSym, Js::ScriptFunction::GetOffsetOfEnvironment(), TyMachPtr, func),
envOpnd,
newScFuncInstr);
instrAssignDst =
InsertLea(newScFuncInstr->GetDst()->AsRegOpnd(), IR::SymOpnd::New(stackSym, TyMachPtr, func), newScFuncInstr);
}
else
{
Assert(func->IsTopFunc());
Assert(func->m_loopParamSym);
IR::Instr * envDefInstr = envOpnd->AsRegOpnd()->m_sym->m_instrDef;
Assert(envDefInstr && envDefInstr->m_opcode == Js::OpCode::NewScFuncData);
IR::RegOpnd * opndFuncPtr = envDefInstr->UnlinkSrc2()->AsRegOpnd();
Assert(opndFuncPtr);
envOpnd = envDefInstr->UnlinkSrc1()->AsRegOpnd();
Assert(envOpnd);
*ppEnvOpnd = envOpnd;
envDefInstr->Remove();
if (index != 0)
{
IR::RegOpnd * opnd = IR::RegOpnd::New(TyVar, func);
InsertAdd(false, opnd, opndFuncPtr, IR::IntConstOpnd::New(index * sizeof(Js::StackScriptFunction), TyMachPtr, func), newScFuncInstr);
opndFuncPtr = opnd;
}
InsertMove(IR::IndirOpnd::New(opndFuncPtr, Js::ScriptFunction::GetOffsetOfEnvironment(), TyMachPtr, func),
envOpnd, newScFuncInstr);
instrAssignDst = InsertMove(newScFuncInstr->GetDst(), opndFuncPtr, newScFuncInstr);
}
InsertBranch(Js::OpCode::Br, labelDone, newScFuncInstr);
newScFuncInstr->InsertBefore(labelNoStackFunc);
newScFuncInstr->InsertAfter(labelDone);
return instrAssignDst;
}
IR::Instr *
Lowerer::LowerNewScFunc(IR::Instr * newScFuncInstr)
{
IR::Instr *stackNewScFuncInstr = nullptr;
IR::RegOpnd * envOpnd = newScFuncInstr->UnlinkSrc2()->AsRegOpnd();
if (newScFuncInstr->m_func->DoStackNestedFunc())
{
stackNewScFuncInstr = GenerateNewStackScFunc(newScFuncInstr, &envOpnd);
}
IR::IntConstOpnd * functionBodySlotOpnd = newScFuncInstr->UnlinkSrc1()->AsIntConstOpnd();
IR::Instr * instrPrev = this->LoadFunctionBodyAsArgument(newScFuncInstr, functionBodySlotOpnd, envOpnd);
m_lowererMD.ChangeToHelperCall(newScFuncInstr, IR::HelperScrFunc_OP_NewScFunc );
return stackNewScFuncInstr == nullptr? instrPrev : stackNewScFuncInstr;
}
IR::Instr *
Lowerer::LowerNewScFuncHomeObj(IR::Instr * newScFuncInstr)
{
newScFuncInstr->m_opcode = Js::OpCode::CallHelper;
IR::HelperCallOpnd *helperOpnd = IR::HelperCallOpnd::New(IR::HelperScrFunc_OP_NewScFuncHomeObj, this->m_func);
IR::Opnd * src1 = newScFuncInstr->UnlinkSrc1();
newScFuncInstr->SetSrc1(helperOpnd);
newScFuncInstr->SetSrc2(src1);
return newScFuncInstr;
}
IR::Instr *
Lowerer::LowerNewScGenFunc(IR::Instr * newScFuncInstr)
{
IR::IntConstOpnd * functionBodySlotOpnd = newScFuncInstr->UnlinkSrc1()->AsIntConstOpnd();
IR::RegOpnd * envOpnd = newScFuncInstr->UnlinkSrc2()->AsRegOpnd();
IR::Instr * instrPrev = this->LoadFunctionBodyAsArgument(newScFuncInstr, functionBodySlotOpnd, envOpnd);
m_lowererMD.ChangeToHelperCall(newScFuncInstr, IR::HelperScrFunc_OP_NewScGenFunc );
return instrPrev;
}
IR::Instr *
Lowerer::LowerNewScGenFuncHomeObj(IR::Instr * newScFuncInstr)
{
newScFuncInstr->m_opcode = Js::OpCode::CallHelper;
IR::HelperCallOpnd *helperOpnd = IR::HelperCallOpnd::New(IR::HelperScrFunc_OP_NewScGenFuncHomeObj, this->m_func);
IR::Opnd * src1 = newScFuncInstr->UnlinkSrc1();
newScFuncInstr->SetSrc1(helperOpnd);
newScFuncInstr->SetSrc2(src1);
return newScFuncInstr;
}
IR::Instr *
Lowerer::LowerStPropIdArrFromVar(IR::Instr * stPropIdInstr)
{
IR::HelperCallOpnd *helperOpnd = IR::HelperCallOpnd::New(IR::HelperStPropIdArrFromVar, this->m_func);
IR::Opnd * src1 = stPropIdInstr->UnlinkSrc1();
stPropIdInstr->SetSrc1(helperOpnd);
stPropIdInstr->SetSrc2(src1);
return m_lowererMD.LowerCallHelper(stPropIdInstr);
}
IR::Instr *
Lowerer::LowerRestify(IR::Instr * newRestInstr)
{
IR::HelperCallOpnd *helperOpnd = IR::HelperCallOpnd::New(IR::HelperRestify, this->m_func);
IR::Opnd * src1 = newRestInstr->UnlinkSrc1();
newRestInstr->SetSrc1(helperOpnd);
newRestInstr->SetSrc2(src1);
return m_lowererMD.LowerCallHelper(newRestInstr);
}
///----------------------------------------------------------------------------
///
/// Lowerer::LowerScopedLdFld
///
/// Lower a load instruction that takes an additional instance to use as a
/// a default if the scope chain provided doesn't contain the property.
///
///----------------------------------------------------------------------------
IR::Instr *
Lowerer::LowerScopedLdFld(IR::Instr * ldFldInstr, IR::JnHelperMethod helperMethod, bool withInlineCache)
{
IR::Opnd *src;
IR::Instr *instrPrev = ldFldInstr->m_prev;
if(!withInlineCache)
{
LoadScriptContext(ldFldInstr);
}
intptr_t rootObject = m_func->GetJITFunctionBody()->GetRootObject();
src = IR::AddrOpnd::New(rootObject, IR::AddrOpndKindDynamicVar, this->m_func, true);
instrPrev = m_lowererMD.LoadHelperArgument(ldFldInstr, src);
src = ldFldInstr->UnlinkSrc1();
AssertMsg(src->IsSymOpnd() && src->AsSymOpnd()->m_sym->IsPropertySym(), "Expected property sym as src");
this->LoadPropertySymAsArgument(ldFldInstr, src);
if (withInlineCache)
{
AssertMsg(src->AsSymOpnd()->IsPropertySymOpnd(), "Need property sym operand to find the inline cache");
m_lowererMD.LoadHelperArgument(
ldFldInstr,
IR::Opnd::CreateInlineCacheIndexOpnd(src->AsPropertySymOpnd()->m_inlineCacheIndex, m_func));
// Not using the polymorphic inline cache because the fast path only uses the monomorphic inline cache
this->m_lowererMD.LoadHelperArgument(ldFldInstr, this->LoadRuntimeInlineCacheOpnd(ldFldInstr, src->AsPropertySymOpnd()));
m_lowererMD.LoadHelperArgument(ldFldInstr, LoadFunctionBodyOpnd(ldFldInstr));
}
m_lowererMD.ChangeToHelperCall(ldFldInstr, helperMethod);
return instrPrev;
}
///----------------------------------------------------------------------------
///
/// Lowerer::LowerScopedLdInst
///
/// Lower a load instruction that takes an additional instance to use as a
/// a default if the scope chain provided doesn't contain the property.
///
///----------------------------------------------------------------------------
IR::Instr *
Lowerer::LowerScopedLdInst(IR::Instr *instr, IR::JnHelperMethod helperMethod)
{
IR::Opnd *src;
IR::Instr *instrPrev;
// last argument is the scriptContext
instrPrev = LoadScriptContext(instr);
src = instr->UnlinkSrc2();
AssertMsg(src->IsRegOpnd(), "Expected Reg opnd as src2");
// __out Var*. The StackSym is allocated in irbuilder, and here we need to insert a lea
StackSym* dstSym = src->GetStackSym();
IR::Instr *load = InsertLoadStackAddress(dstSym, instr);
IR::Opnd* tempOpnd = load->GetDst();
m_lowererMD.LoadHelperArgument(instr, tempOpnd);
// now 3rd last argument is the rootObject of the function. Need to add addrOpnd to
// pass in the address of the roobObject.
IR::Opnd * srcOpnd;
intptr_t rootObject = m_func->GetJITFunctionBody()->GetRootObject();
srcOpnd = IR::AddrOpnd::New(rootObject, IR::AddrOpndKindDynamicVar, instr->m_func, true);
instrPrev = m_lowererMD.LoadHelperArgument(instr, srcOpnd);
// no change, the property field built from irbuilder.
src = instr->UnlinkSrc1();
AssertMsg(src->IsSymOpnd() && src->AsSymOpnd()->m_sym->IsPropertySym(), "Expected property sym as src");
this->LoadPropertySymAsArgument(instr, src);
instrPrev = m_lowererMD.ChangeToHelperCall(instr, helperMethod);
IR::RegOpnd* regOpnd = IR::RegOpnd::New(dstSym, TyVar, m_func);
IR::SymOpnd*symOpnd = IR::SymOpnd::New(dstSym, TyVar, m_func);
this->InsertMove(regOpnd, symOpnd, instrPrev);
return instrPrev;
}
IR::Instr *
Lowerer::LowerScopedDelFld(IR::Instr * delFldInstr, IR::JnHelperMethod helperMethod, bool withInlineCache, bool strictMode)
{
IR::Instr *instrPrev;
Js::PropertyOperationFlags propertyOperationFlag = Js::PropertyOperation_None;
if (strictMode)
{
propertyOperationFlag = Js::PropertyOperation_StrictMode;
}
instrPrev = m_lowererMD.LoadHelperArgument(delFldInstr, IR::IntConstOpnd::New((IntConstType)propertyOperationFlag, TyInt32, m_func, true));
LowerScopedLdFld(delFldInstr, helperMethod, withInlineCache);
return instrPrev;
}
IR::Instr *
Lowerer::LowerProfiledStFld(IR::JitProfilingInstr *stFldInstr, Js::PropertyOperationFlags flags)
{
Assert(stFldInstr->profileId == Js::Constants::NoProfileId);
IR::Instr *const instrPrev = stFldInstr->m_prev;
/*
void ProfilingHelpers::ProfiledInitFld_Jit(
const Var instance,
const PropertyId propertyId,
const InlineCacheIndex inlineCacheIndex,
const Var value,
void *const framePointer)
void ProfilingHelpers::ProfiledStFld_Jit(
const Var instance,
const PropertyId propertyId,
const InlineCacheIndex inlineCacheIndex,
const Var value,
void *const framePointer)
void ProfilingHelpers::ProfiledStSuperFld_Jit(
const Var instance,
const PropertyId propertyId,
const InlineCacheIndex inlineCacheIndex,
const Var value,
void *const framePointer,
const Var thisInstance)
{
*/
m_lowererMD.LoadHelperArgument(stFldInstr, IR::Opnd::CreateFramePointerOpnd(m_func));
if (stFldInstr->m_opcode == Js::OpCode::StSuperFld)
{
m_lowererMD.LoadHelperArgument(stFldInstr, stFldInstr->UnlinkSrc2());
}
m_lowererMD.LoadHelperArgument(stFldInstr, stFldInstr->UnlinkSrc1());
IR::Opnd *dst = stFldInstr->UnlinkDst();
AssertMsg(dst->IsSymOpnd() && dst->AsSymOpnd()->m_sym->IsPropertySym(), "Expected property sym as dst of field store");
m_lowererMD.LoadHelperArgument(
stFldInstr,
IR::Opnd::CreateInlineCacheIndexOpnd(dst->AsPropertySymOpnd()->m_inlineCacheIndex, m_func));
LoadPropertySymAsArgument(stFldInstr, dst);
IR::JnHelperMethod helper;
switch (stFldInstr->m_opcode)
{
case Js::OpCode::InitFld:
case Js::OpCode::InitRootFld:
helper = IR::HelperProfiledInitFld;
break;
case Js::OpCode::StSuperFld:
helper = IR::HelperProfiledStSuperFld;
break;
default:
helper =
flags & Js::PropertyOperation_Root
? flags & Js::PropertyOperation_StrictMode ? IR::HelperProfiledStRootFld_Strict : IR::HelperProfiledStRootFld
: flags & Js::PropertyOperation_StrictMode ? IR::HelperProfiledStFld_Strict : IR::HelperProfiledStFld;
break;
}
stFldInstr->SetSrc1(IR::HelperCallOpnd::New(helper, m_func));
m_lowererMD.LowerCall(stFldInstr, 0);
return instrPrev;
}
///----------------------------------------------------------------------------
///
/// Lowerer::LowerStFld
///
///----------------------------------------------------------------------------
IR::Instr *
Lowerer::LowerStFld(
IR::Instr * stFldInstr,
IR::JnHelperMethod helperMethod,
IR::JnHelperMethod polymorphicHelperMethod,
bool withInlineCache,
IR::LabelInstr *labelBailOut,
bool isHelper,
bool withPutFlags,
Js::PropertyOperationFlags flags)
{
if (stFldInstr->IsJitProfilingInstr())
{
// If we want to profile then do something completely different
return this->LowerProfiledStFld(stFldInstr->AsJitProfilingInstr(), flags);
}
IR::Instr *instrPrev = stFldInstr->m_prev;
IR::Opnd *dst = stFldInstr->UnlinkDst();
AssertMsg(dst->IsSymOpnd() && dst->AsSymOpnd()->m_sym->IsPropertySym(), "Expected property sym as dst of field store");
IR::Opnd * inlineCacheOpnd = nullptr;
if (withInlineCache)
{
AssertMsg(dst->AsSymOpnd()->IsPropertySymOpnd(), "Need property sym operand to find the inline cache");
if (dst->AsPropertySymOpnd()->m_runtimePolymorphicInlineCache && polymorphicHelperMethod != helperMethod)
{
JITTimePolymorphicInlineCache * polymorphicInlineCache = dst->AsPropertySymOpnd()->m_runtimePolymorphicInlineCache;
helperMethod = polymorphicHelperMethod;
inlineCacheOpnd = IR::AddrOpnd::New(polymorphicInlineCache->GetAddr(), IR::AddrOpndKindDynamicInlineCache, this->m_func);
}
else
{
// Need to load runtime inline cache opnd first before loading any helper argument
// because LoadRuntimeInlineCacheOpnd may create labels marked as helper
// and cause op helper register push/pop save in x86, messing up with any helper arguments that is already pushed
inlineCacheOpnd = this->LoadRuntimeInlineCacheOpnd(stFldInstr, dst->AsPropertySymOpnd(), isHelper);
}
}
if (withPutFlags)
{
m_lowererMD.LoadHelperArgument(stFldInstr,
IR::IntConstOpnd::New(static_cast<IntConstType>(flags), IRType::TyInt32, m_func, true));
}
IR::Opnd *src = stFldInstr->UnlinkSrc1();
if (stFldInstr->m_opcode == Js::OpCode::StSuperFld)
{
m_lowererMD.LoadHelperArgument(stFldInstr, stFldInstr->UnlinkSrc2());
}
m_lowererMD.LoadHelperArgument(stFldInstr, src);
this->LoadPropertySymAsArgument(stFldInstr, dst);
if (withInlineCache)
{
Assert(inlineCacheOpnd != nullptr);
this->m_lowererMD.LoadHelperArgument(
stFldInstr,
IR::Opnd::CreateInlineCacheIndexOpnd(dst->AsPropertySymOpnd()->m_inlineCacheIndex, m_func));
this->m_lowererMD.LoadHelperArgument(stFldInstr, inlineCacheOpnd);
this->m_lowererMD.LoadHelperArgument(stFldInstr, LoadFunctionBodyOpnd(stFldInstr));
}
IR::RegOpnd *opndBase = dst->AsSymOpnd()->CreatePropertyOwnerOpnd(m_func);
m_lowererMD.ChangeToHelperCall(stFldInstr, helperMethod, labelBailOut, opndBase, dst->AsSymOpnd()->IsPropertySymOpnd() ? dst->AsSymOpnd()->AsPropertySymOpnd() : nullptr, isHelper);
return instrPrev;
}
IR::Instr* Lowerer::GenerateCompleteStFld(IR::Instr* instr, bool emitFastPath, IR::JnHelperMethod monoHelperAfterFastPath, IR::JnHelperMethod polyHelperAfterFastPath,
IR::JnHelperMethod monoHelperWithoutFastPath, IR::JnHelperMethod polyHelperWithoutFastPath, bool withPutFlags, Js::PropertyOperationFlags flags)
{
if(instr->CallsAccessor() && instr->HasBailOutInfo())
{
IR::BailOutKind kindMinusBits = instr->GetBailOutKind() & ~IR::BailOutKindBits;
Assert(kindMinusBits != IR::BailOutOnImplicitCalls && kindMinusBits != IR::BailOutOnImplicitCallsPreOp);
}
IR::Instr* prevInstr = instr->m_prev;
IR::LabelInstr* labelBailOut = nullptr;
IR::LabelInstr* labelHelper = nullptr;
bool isHelper = false;
IR::RegOpnd* typeOpnd = nullptr;
if(emitFastPath && GenerateFastStFldForCustomProperty(instr, &labelHelper))
{
if(labelHelper)
{
Assert(labelHelper->isOpHelper);
instr->InsertBefore(labelHelper);
prevInstr = this->LowerStFld(instr, monoHelperWithoutFastPath, polyHelperWithoutFastPath, true, labelBailOut, isHelper, withPutFlags, flags);
}
else
{
instr->Remove();
return prevInstr;
}
}
else if (this->GenerateStFldWithCachedType(instr, &isHelper, &labelHelper, &typeOpnd))
{
Assert(labelHelper == nullptr);
return prevInstr;
}
else if (emitFastPath)
{
if (!GenerateFastStFld(instr, monoHelperWithoutFastPath, polyHelperWithoutFastPath, &labelBailOut, typeOpnd, &isHelper, &labelHelper, withPutFlags, flags))
{
if (labelHelper != nullptr)
{
labelHelper->isOpHelper = isHelper;
instr->InsertBefore(labelHelper);
}
prevInstr = this->LowerStFld(instr, monoHelperAfterFastPath, polyHelperAfterFastPath, true, labelBailOut, isHelper, withPutFlags, flags);
}
}
else
{
if (labelHelper != nullptr)
{
labelHelper->isOpHelper = isHelper;
instr->InsertBefore(labelHelper);
}
prevInstr = this->LowerStFld(instr, monoHelperWithoutFastPath, monoHelperWithoutFastPath, true, labelBailOut, isHelper, withPutFlags, flags);
}
return prevInstr;
}
void
Lowerer::GenerateDirectFieldStore(IR::Instr* instrStFld, IR::PropertySymOpnd* propertySymOpnd)
{
Func* func = instrStFld->m_func;
IR::Opnd *opndSlotArray = this->LoadSlotArrayWithCachedLocalType(instrStFld, propertySymOpnd);
// Store the value to the slot, getting the slot index from the cache.
uint16 index = propertySymOpnd->GetSlotIndex();
AssertOrFailFast(index != (uint16)-1);
#if defined(RECYCLER_WRITE_BARRIER_JIT) && (defined(_M_IX86) || defined(_M_AMD64))
if (opndSlotArray->IsRegOpnd())
{
IR::IndirOpnd * opndDst = IR::IndirOpnd::New(opndSlotArray->AsRegOpnd(), index * sizeof(Js::Var), TyMachReg, func);
this->GetLowererMD()->GenerateWriteBarrierAssign(opndDst, instrStFld->GetSrc1(), instrStFld);
}
else
{
Assert(opndSlotArray->IsMemRefOpnd());
IR::MemRefOpnd * opndDst = IR::MemRefOpnd::New((char*)opndSlotArray->AsMemRefOpnd()->GetMemLoc() + (index * sizeof(Js::Var)), TyMachReg, func);
this->GetLowererMD()->GenerateWriteBarrierAssign(opndDst, instrStFld->GetSrc1(), instrStFld);
}
#else
IR::Opnd *opnd;
if (opndSlotArray->IsRegOpnd())
{
opnd = IR::IndirOpnd::New(opndSlotArray->AsRegOpnd(), index * sizeof(Js::Var), TyMachReg, func);
}
else
{
opnd = IR::MemRefOpnd::New((char*)opndSlotArray->AsMemRefOpnd()->GetMemLoc() + (index * sizeof(Js::Var)), TyMachReg, func);
}
this->InsertMove(opnd, instrStFld->GetSrc1(), instrStFld);
#endif
}
bool
Lowerer::GenerateStFldWithCachedType(IR::Instr *instrStFld, bool* continueAsHelperOut, IR::LabelInstr** labelHelperOut, IR::RegOpnd** typeOpndOut)
{
IR::Instr *instr;
IR::RegOpnd *typeOpnd = nullptr;
IR::LabelInstr* labelObjCheckFailed = nullptr;
IR::LabelInstr *labelTypeCheckFailed = nullptr;
IR::LabelInstr *labelBothTypeChecksFailed = nullptr;
IR::LabelInstr *labelDone = nullptr;
Assert(continueAsHelperOut != nullptr);
*continueAsHelperOut = false;
Assert(labelHelperOut != nullptr);
*labelHelperOut = nullptr;
Assert(typeOpndOut != nullptr);
*typeOpndOut = nullptr;
Assert(instrStFld->GetDst()->IsSymOpnd());
if (!instrStFld->GetDst()->AsSymOpnd()->IsPropertySymOpnd() || !instrStFld->GetDst()->AsPropertySymOpnd()->IsTypeCheckSeqCandidate())
{
return false;
}
IR::PropertySymOpnd *propertySymOpnd = instrStFld->GetDst()->AsPropertySymOpnd();
// If we have any object type spec info, we better not believe this is a load from prototype, since this is a store
// and we never share inline caches between loads and stores.
Assert(!propertySymOpnd->HasObjTypeSpecFldInfo() || !propertySymOpnd->IsLoadedFromProto());
AssertMsg(propertySymOpnd->TypeCheckSeqBitsSetOnlyIfCandidate(), "Property sym operand optimized despite not being a candidate?");
if (!propertySymOpnd->IsTypeCheckSeqCandidate())
{
return false;
}
if (!propertySymOpnd->IsTypeCheckSeqParticipant() && !propertySymOpnd->NeedsLocalTypeCheck())
{
return false;
}
Assert(!propertySymOpnd->NeedsTypeCheckAndBailOut() || (instrStFld->HasBailOutInfo() && IR::IsTypeCheckBailOutKind(instrStFld->GetBailOutKind())));
// In the backwards pass we only add guarded property operations to instructions that are not already
// protected by an upstream type check.
Assert(!propertySymOpnd->IsTypeCheckProtected() || propertySymOpnd->GetGuardedPropOps() == nullptr);
PHASE_PRINT_TESTTRACE(
Js::ObjTypeSpecPhase,
this->m_func,
_u("Field store: %s, property ID: %d, func: %s, cache ID: %d, cloned cache: true, layout: %s, redundant check: %s\n"),
Js::OpCodeUtil::GetOpCodeName(instrStFld->m_opcode),
propertySymOpnd->m_sym->AsPropertySym()->m_propertyId,
this->m_func->GetJITFunctionBody()->GetDisplayName(),
propertySymOpnd->m_inlineCacheIndex, propertySymOpnd->GetCacheLayoutString(),
propertySymOpnd->IsTypeChecked() ? _u("true") : _u("false"));
if (propertySymOpnd->HasFinalType() && !propertySymOpnd->IsLoadedFromProto())
{
propertySymOpnd->UpdateSlotForFinalType();
}
Func* func = instrStFld->m_func;
// TODO (ObjTypeSpec): If ((PropertySym*)propertySymOpnd->m_sym)->m_stackSym->m_isIntConst consider emitting a direct
// jump to helper or bailout. If we have a type check bailout, we could even abort compilation.
bool hasTypeCheckBailout = instrStFld->HasBailOutInfo() && IR::IsTypeCheckBailOutKind(instrStFld->GetBailOutKind());
// If the type hasn't been checked upstream, see if it makes sense to check it here.
bool isTypeChecked = propertySymOpnd->IsTypeChecked();
if (!isTypeChecked)
{
// If the initial type has been checked, we can do a hard coded type transition without any type checks
// (see GenerateStFldWithCachedFinalType), which is always worth doing, even if the type is not needed
// downstream. We're not introducing any additional bailouts.
if (propertySymOpnd->HasFinalType() && propertySymOpnd->HasInitialType() && !propertySymOpnd->IsTypeDead())
{
// We have a final type in hand, so we can JIT (most of) the type transition work.
return this->GenerateStFldWithCachedFinalType(instrStFld, propertySymOpnd);
}
if (propertySymOpnd->HasTypeMismatch())
{
// So we have a type mismatch, which happens when the type (and the type without property if ObjTypeSpecStore
// is on) on this instruction didn't match the live type value according to the flow. We must have hit some
// stale inline cache (perhaps inlined from a different function, or on a code path not taken for a while).
// Either way, we know exactly what type the object must have at this point (fully determined by flow), but
// we don't know whether that type already has the property we're storing here. All in all, we know exactly
// what shape the object will have after this operation, but we're not sure what label (type) to give this
// shape. Thus we can simply let the fast path do its thing based on the live inline cache. The downstream
// instructions relying only on this shape (loads and stores) are safe, and those that need the next type
// (i.e. adds) will do the same thing as this instruction.
return false;
}
// If we're still here then we must need a primary type check on this instruction to protect
// a sequence of field operations downstream, or a local type check for an isolated field store.
Assert(propertySymOpnd->NeedsPrimaryTypeCheck() || propertySymOpnd->NeedsLocalTypeCheck());
labelTypeCheckFailed = IR::LabelInstr::New(Js::OpCode::Label, func, true);
labelBothTypeChecksFailed = IR::LabelInstr::New(Js::OpCode::Label, func, true);
labelObjCheckFailed = hasTypeCheckBailout ? labelBothTypeChecksFailed : IR::LabelInstr::New(Js::OpCode::Label, func, true);
typeOpnd = this->GenerateCachedTypeCheck(instrStFld, propertySymOpnd, labelObjCheckFailed, labelBothTypeChecksFailed, labelTypeCheckFailed);
*typeOpndOut = typeOpnd;
}
// Either we are protected by a type check upstream or we just emitted a type check above,
// now it's time to store the field value.
GenerateDirectFieldStore(instrStFld, propertySymOpnd);
// If we are protected by a type check upstream, we don't need a bailout or helper here, delete the instruction
// and return "true" to indicate that we succeeded in eliminating it.
if (isTypeChecked)
{
Assert(labelTypeCheckFailed == nullptr && labelBothTypeChecksFailed == nullptr);
AssertMsg(
!instrStFld->HasBailOutInfo() || instrStFld->OnlyHasLazyBailOut(),
"Why does a direct field store have bailout that is not lazy?"
);
if (propertySymOpnd->HasInitialType() && propertySymOpnd->HasFinalType())
{
bool isPrototypeTypeHandler = propertySymOpnd->GetInitialType()->GetTypeHandler()->IsPrototype();
if (isPrototypeTypeHandler)
{
LoadScriptContext(instrStFld);
m_lowererMD.LoadHelperArgument(instrStFld, IR::IntConstOpnd::New(propertySymOpnd->GetPropertyId(), TyInt32, m_func, true));
IR::Instr * invalidateCallInstr = IR::Instr::New(Js::OpCode::Call, m_func);
instrStFld->InsertBefore(invalidateCallInstr);
m_lowererMD.ChangeToHelperCall(invalidateCallInstr, IR::HelperInvalidateProtoCaches);
}
}
instrStFld->Remove();
return true;
}
// Otherwise, branch around the helper on successful type check.
labelDone = IR::LabelInstr::New(Js::OpCode::Label, func);
instr = IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, labelDone, func);
instrStFld->InsertBefore(instr);
// On failed type check, try the type without property if we've got one.
instrStFld->InsertBefore(labelTypeCheckFailed);
// Caution, this is one of the dusty corners of the JIT. We only get here if this is an isolated StFld which adds a property, or
// ObjTypeSpecStore is off. In the former case no downstream operations depend on the final type produced here, and we can fall
// back on live cache and helper if the type doesn't match. In the latter we may have a cache with type transition, which must
// produce a value for the type after transition, because that type is consumed downstream. Thus, if the object's type doesn't
// match either the type with or the type without the property we're storing, we must bail out here.
bool emitAddProperty = propertySymOpnd->IsMono() && propertySymOpnd->HasInitialType();
if (emitAddProperty)
{
GenerateCachedTypeWithoutPropertyCheck(instrStFld, propertySymOpnd, typeOpnd, labelBothTypeChecksFailed);
GenerateFieldStoreWithTypeChange(instrStFld, propertySymOpnd, propertySymOpnd->GetInitialType(), propertySymOpnd->GetType());
instr = IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, labelDone, func);
instrStFld->InsertBefore(instr);
}
instrStFld->InsertBefore(labelBothTypeChecksFailed);
instrStFld->InsertAfter(labelDone);
if (hasTypeCheckBailout)
{
AssertMsg(PHASE_ON1(Js::ObjTypeSpecIsolatedFldOpsWithBailOutPhase) || !PHASE_ON(Js::DeadStoreTypeChecksOnStoresPhase, this->m_func) || !propertySymOpnd->IsTypeDead() || propertySymOpnd->TypeCheckRequired(),
"Why does a field store have a type check bailout, if its type is dead?");
if (instrStFld->GetBailOutInfo()->bailOutInstr != instrStFld)
{
// Set the cache index in the bailout info so that the generated code will write it into the
// bailout record at runtime.
instrStFld->GetBailOutInfo()->polymorphicCacheIndex = propertySymOpnd->m_inlineCacheIndex;
}
else
{
Assert(instrStFld->GetBailOutInfo()->polymorphicCacheIndex == propertySymOpnd->m_inlineCacheIndex);
}
instrStFld->m_opcode = Js::OpCode::BailOut;
instrStFld->FreeSrc1();
instrStFld->FreeDst();
this->GenerateBailOut(instrStFld);
return true;
}
else
{
*continueAsHelperOut = true;
Assert(labelObjCheckFailed != nullptr && labelObjCheckFailed != labelBothTypeChecksFailed);
*labelHelperOut = labelObjCheckFailed;
return false;
}
}
IR::RegOpnd *
Lowerer::GenerateCachedTypeCheck(IR::Instr *instrChk, IR::PropertySymOpnd *propertySymOpnd, IR::LabelInstr* labelObjCheckFailed, IR::LabelInstr *labelTypeCheckFailed, IR::LabelInstr *labelSecondChance)
{
Assert(propertySymOpnd->MayNeedTypeCheckProtection());
Func* func = instrChk->m_func;
IR::RegOpnd *regOpnd = propertySymOpnd->CreatePropertyOwnerOpnd(func);
regOpnd->SetValueType(propertySymOpnd->GetPropertyOwnerValueType());
if (!regOpnd->IsNotTaggedValue())
{
m_lowererMD.GenerateObjectTest(regOpnd, instrChk, labelObjCheckFailed);
}
// Load the current object type into typeOpnd
IR::RegOpnd* typeOpnd = IR::RegOpnd::New(TyMachReg, func);
IR::Opnd *sourceType;
if (regOpnd->m_sym->IsConst() && !regOpnd->m_sym->IsIntConst() && !regOpnd->m_sym->IsFloatConst())
{
sourceType = IR::MemRefOpnd::New((BYTE*)regOpnd->m_sym->GetConstAddress() +
Js::RecyclableObject::GetOffsetOfType(), TyMachReg, func, IR::AddrOpndKindDynamicObjectTypeRef);
}
else
{
sourceType = IR::IndirOpnd::New(regOpnd, Js::RecyclableObject::GetOffsetOfType(), TyMachReg, func);
}
InsertMove(typeOpnd, sourceType, instrChk);
// Note: don't attempt equivalent type check if we're doing a final type optimization or if we have a monomorphic
// cache and no type check bailout. In the latter case, we can wind up doing expensive failed equivalence checks
// repeatedly and never rejit.
bool doEquivTypeCheck =
instrChk->HasEquivalentTypeCheckBailOut() ||
(propertySymOpnd->HasEquivalentTypeSet() &&
!(propertySymOpnd->HasFinalType() && propertySymOpnd->HasInitialType()) &&
!propertySymOpnd->MustDoMonoCheck() &&
(propertySymOpnd->IsPoly() || instrChk->HasTypeCheckBailOut()));
Assert(doEquivTypeCheck || !instrChk->HasEquivalentTypeCheckBailOut());
// Create and initialize the property guard if required. Note that for non-shared monomorphic checks we can refer
// directly to the (pinned) type and not use a guard.
Js::PropertyGuard * typeCheckGuard;
IR::RegOpnd * polyIndexOpnd = nullptr;
JITTypeHolder monoType = nullptr;
if (doEquivTypeCheck)
{
typeCheckGuard = CreateEquivalentTypeGuardAndLinkToGuardedProperties(propertySymOpnd);
if (typeCheckGuard->IsPoly())
{
Assert(propertySymOpnd->ShouldUsePolyEquivTypeGuard(this->m_func));
polyIndexOpnd = this->GeneratePolymorphicTypeIndex(typeOpnd, typeCheckGuard, instrChk);
}
}
else
{
monoType = propertySymOpnd->MustDoMonoCheck() ? propertySymOpnd->GetMonoGuardType() : propertySymOpnd->GetType();
typeCheckGuard = this->CreateTypePropertyGuardForGuardedProperties(monoType, propertySymOpnd);
}
// Create the opnd we will check against the current type.
IR::Opnd *expectedTypeOpnd;
JITTypeHolder directCheckType = nullptr;
if (typeCheckGuard == nullptr)
{
Assert(monoType != nullptr);
expectedTypeOpnd = IR::AddrOpnd::New(monoType->GetAddr(), IR::AddrOpndKindDynamicType, func, true);
directCheckType = monoType;
}
else
{
Assert(Js::PropertyGuard::GetSizeOfValue() == static_cast<size_t>(TySize[TyMachPtr]));
if (this->m_func->IsOOPJIT())
{
if (polyIndexOpnd != nullptr)
{
IR::RegOpnd * baseOpnd = IR::RegOpnd::New(TyMachPtr, func);
this->GenerateLeaOfOOPData(baseOpnd, typeCheckGuard, Js::JitPolyEquivalentTypeGuard::GetOffsetOfPolyValues(), instrChk);
expectedTypeOpnd = IR::IndirOpnd::New(baseOpnd, polyIndexOpnd, m_lowererMD.GetDefaultIndirScale(), TyMachPtr, func);
}
else
{
expectedTypeOpnd = this->GenerateIndirOfOOPData(typeCheckGuard, 0, instrChk);
}
this->addToLiveOnBackEdgeSyms->Set(func->GetTopFunc()->GetNativeCodeDataSym()->m_id);
}
else
{
if (polyIndexOpnd != nullptr)
{
IR::RegOpnd * baseOpnd = IR::RegOpnd::New(TyMachPtr, func);
InsertMove(baseOpnd, IR::AddrOpnd::New((Js::Var)typeCheckGuard->AsPolyTypeCheckGuard()->GetAddressOfPolyValues(), IR::AddrOpndKindDynamicTypeCheckGuard, func, true), instrChk);
expectedTypeOpnd = IR::IndirOpnd::New(baseOpnd, polyIndexOpnd, m_lowererMD.GetDefaultIndirScale(), TyMachPtr, func);
}
else
{
expectedTypeOpnd = IR::MemRefOpnd::New((void*)(typeCheckGuard->GetAddressOfValue()), TyMachPtr, func, IR::AddrOpndKindDynamicGuardValueRef);
}
}
}
if (PHASE_VERBOSE_TRACE(Js::ObjTypeSpecPhase, this->m_func))
{
OUTPUT_VERBOSE_TRACE_FUNC(Js::ObjTypeSpecPhase, this->m_func, _u("Emitted %s type check "),
directCheckType != nullptr ? _u("direct") : propertySymOpnd->IsPoly() ? _u("equivalent") : _u("indirect"));
#if DBG
if (propertySymOpnd->GetGuardedPropOps() != nullptr)
{
Output::Print(_u(" guarding operations:\n "));
propertySymOpnd->GetGuardedPropOps()->Dump();
}
else
{
Output::Print(_u("\n"));
}
#else
Output::Print(_u("\n"));
#endif
Output::Flush();
}
if (doEquivTypeCheck)
{
// TODO (ObjTypeSpec): For isolated equivalent type checks it would be good to emit a check if the cache is still valid, and
// if not go straight to live polymorphic cache. This way we wouldn't have to bail out and re-JIT, and also wouldn't continue
// to try the equivalent type cache, miss it and do the slow comparison. This may be as easy as sticking a null on the main
// type in the equivalent type cache.
IR::LabelInstr* labelCheckEquivalentType = IR::LabelInstr::New(Js::OpCode::Label, func, true);
IR::BranchInstr* branchInstr = InsertCompareBranch(typeOpnd, expectedTypeOpnd, Js::OpCode::BrNeq_A, labelCheckEquivalentType, instrChk);
InsertObjectPoison(regOpnd, branchInstr, instrChk, false);
IR::LabelInstr *labelTypeCheckSucceeded = IR::LabelInstr::New(Js::OpCode::Label, func, false);
InsertBranch(Js::OpCode::Br, labelTypeCheckSucceeded, instrChk);
instrChk->InsertBefore(labelCheckEquivalentType);
IR::Opnd* typeCheckGuardOpnd = nullptr;
if (this->m_func->IsOOPJIT())
{
typeCheckGuardOpnd = IR::RegOpnd::New(TyMachPtr, func);
this->GenerateLeaOfOOPData(typeCheckGuardOpnd->AsRegOpnd(), typeCheckGuard, 0, instrChk);
this->addToLiveOnBackEdgeSyms->Set(func->GetTopFunc()->GetNativeCodeDataSym()->m_id);
}
else
{
typeCheckGuardOpnd = IR::AddrOpnd::New((Js::Var)typeCheckGuard, IR::AddrOpndKindDynamicTypeCheckGuard, func, true);
}
IR::JnHelperMethod helperMethod;
if (polyIndexOpnd != nullptr)
{
helperMethod = propertySymOpnd->HasFixedValue() ? IR::HelperCheckIfPolyTypeIsEquivalentForFixedField : IR::HelperCheckIfPolyTypeIsEquivalent;
this->m_lowererMD.LoadHelperArgument(instrChk, polyIndexOpnd);
}
else
{
helperMethod = propertySymOpnd->HasFixedValue() ? IR::HelperCheckIfTypeIsEquivalentForFixedField : IR::HelperCheckIfTypeIsEquivalent;
}
this->m_lowererMD.LoadHelperArgument(instrChk, typeCheckGuardOpnd);
this->m_lowererMD.LoadHelperArgument(instrChk, typeOpnd);
IR::RegOpnd* equivalentTypeCheckResultOpnd = IR::RegOpnd::New(TyUint8, func);
IR::HelperCallOpnd* equivalentTypeCheckHelperCallOpnd = IR::HelperCallOpnd::New(helperMethod, func);
IR::Instr* equivalentTypeCheckCallInstr = IR::Instr::New(Js::OpCode::Call, equivalentTypeCheckResultOpnd, equivalentTypeCheckHelperCallOpnd, func);
instrChk->InsertBefore(equivalentTypeCheckCallInstr);
this->m_lowererMD.LowerCall(equivalentTypeCheckCallInstr, 0);
InsertTestBranch(equivalentTypeCheckResultOpnd, equivalentTypeCheckResultOpnd, Js::OpCode::BrEq_A, labelTypeCheckFailed, instrChk);
// TODO (ObjTypeSpec): Consider emitting a shared bailout to which a specific bailout kind is written at runtime. This would allow us to distinguish
// between non-equivalent type and other cases, such as invalidated guard (due to fixed field overwrite, perhaps) or too much thrashing on the
// equivalent type cache. We could determine bailout kind based on the value returned by the helper. In the case of cache thrashing we could just
// turn off the whole optimization for a given function.
instrChk->InsertBefore(labelTypeCheckSucceeded);
}
else
{
IR::BranchInstr* branchInstr = InsertCompareBranch(typeOpnd, expectedTypeOpnd, Js::OpCode::BrNeq_A, labelSecondChance != nullptr ? labelSecondChance : labelTypeCheckFailed, instrChk);
InsertObjectPoison(regOpnd, branchInstr, instrChk, false);
}
// Don't pin the type for polymorphic operations. The code can successfully execute even if this type is no longer referenced by any objects,
// as long as there are other objects with types equivalent on the properties referenced by this code. The type is kept alive until entry point
// installation by the JIT transfer data, and after that by the equivalent type cache, so it will stay alive unless or until it gets evicted
// from the cache.
if (!doEquivTypeCheck)
{
Assert(monoType != nullptr);
PinTypeRef(monoType, monoType.t, instrChk, propertySymOpnd->m_sym->AsPropertySym()->m_propertyId);
}
return typeOpnd;
}
IR::RegOpnd *
Lowerer::GeneratePolymorphicTypeIndex(IR::RegOpnd * typeOpnd, Js::PropertyGuard * typeCheckGuard, IR::Instr * instrInsert)
{
IR::RegOpnd * resultOpnd = IR::RegOpnd::New(TyMachReg, this->m_func);
InsertMove(resultOpnd, typeOpnd, instrInsert);
InsertShift(Js::OpCode::ShrU_A, false, resultOpnd, resultOpnd, IR::IntConstOpnd::New(PolymorphicInlineCacheShift, TyInt8, this->m_func, true), instrInsert);
InsertAnd(resultOpnd, resultOpnd, IR::IntConstOpnd::New(typeCheckGuard->AsPolyTypeCheckGuard()->GetSize() - 1, TyMachReg, this->m_func, true), instrInsert);
return resultOpnd;
}
void
Lowerer::GenerateLeaOfOOPData(IR::RegOpnd * regOpnd, void * address, int32 offset, IR::Instr * instrInsert)
{
Func * func = instrInsert->m_func;
int32 dataOffset;
Int32Math::Add(NativeCodeData::GetDataTotalOffset(address), offset, &dataOffset);
InsertLea(regOpnd,
IR::IndirOpnd::New(IR::RegOpnd::New(func->GetTopFunc()->GetNativeCodeDataSym(), TyVar, m_func), dataOffset, TyMachPtr,
#if DBG
NativeCodeData::GetDataDescription(address, func->m_alloc),
#endif
func, true),
instrInsert);
}
IR::Opnd *
Lowerer::GenerateIndirOfOOPData(void * address, int32 offset, IR::Instr * instrInsert)
{
Func * func = instrInsert->m_func;
int32 dataOffset;
Int32Math::Add(NativeCodeData::GetDataTotalOffset(address), offset, &dataOffset);
IR::Opnd * opnd = IR::IndirOpnd::New(IR::RegOpnd::New(func->GetTopFunc()->GetNativeCodeDataSym(), TyVar, m_func), dataOffset, TyMachPtr,
#if DBG
NativeCodeData::GetDataDescription(address, func->m_alloc),
#endif
func, true);
return opnd;
}
void
Lowerer::InsertObjectPoison(IR::Opnd* poisonedOpnd, IR::BranchInstr* branchInstr, IR::Instr* insertInstr, bool isForStore)
{
#ifndef _M_ARM
LowererMD::InsertObjectPoison(poisonedOpnd, branchInstr, insertInstr, isForStore);
#endif
}
void
Lowerer::PinTypeRef(JITTypeHolder type, void* typeRef, IR::Instr* instr, Js::PropertyId propertyId)
{
this->m_func->PinTypeRef(typeRef);
if (PHASE_TRACE(Js::TracePinnedTypesPhase, this->m_func))
{
char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
Output::Print(_u("PinnedTypes: function %s(%s) instr %s property ID %u pinned %s reference 0x%p to type 0x%p.\n"),
this->m_func->GetJITFunctionBody()->GetDisplayName(), this->m_func->GetDebugNumberSet(debugStringBuffer),
Js::OpCodeUtil::GetOpCodeName(instr->m_opcode), propertyId,
typeRef == type.t ? _u("strong") : _u("weak"), typeRef, type.t);
Output::Flush();
}
}
void
Lowerer::GenerateCachedTypeWithoutPropertyCheck(IR::Instr *instrInsert, IR::PropertySymOpnd *propertySymOpnd, IR::Opnd *typeOpnd, IR::LabelInstr *labelTypeCheckFailed)
{
Assert(propertySymOpnd->IsMonoObjTypeSpecCandidate());
Assert(propertySymOpnd->HasInitialType());
JITTypeHolder typeWithoutProperty = propertySymOpnd->GetInitialType();
// We should never add properties to objects of static types.
Assert(Js::DynamicType::Is(typeWithoutProperty->GetTypeId()));
if (typeOpnd == nullptr)
{
// No opnd holding the type was passed in, so we have to load the type here.
IR::RegOpnd *baseOpnd = propertySymOpnd->CreatePropertyOwnerOpnd(m_func);
if (!baseOpnd->IsNotTaggedValue())
{
m_lowererMD.GenerateObjectTest(baseOpnd, instrInsert, labelTypeCheckFailed);
}
IR::Opnd *opnd = IR::IndirOpnd::New(baseOpnd, Js::RecyclableObject::GetOffsetOfType(), TyMachReg, this->m_func);
typeOpnd = IR::RegOpnd::New(TyMachReg, this->m_func);
InsertMove(typeOpnd, opnd, instrInsert);
}
Js::JitTypePropertyGuard* typePropertyGuard = CreateTypePropertyGuardForGuardedProperties(typeWithoutProperty, propertySymOpnd);
IR::Opnd *expectedTypeOpnd;
if (typePropertyGuard)
{
bool emitDirectCheck = true;
Assert(typePropertyGuard != nullptr);
Assert(Js::PropertyGuard::GetSizeOfValue() == static_cast<size_t>(TySize[TyMachPtr]));
if (this->m_func->IsOOPJIT())
{
int typeCheckGuardOffset = NativeCodeData::GetDataTotalOffset(typePropertyGuard);
expectedTypeOpnd = IR::IndirOpnd::New(IR::RegOpnd::New(m_func->GetTopFunc()->GetNativeCodeDataSym(), TyVar, m_func), typeCheckGuardOffset, TyMachPtr,
#if DBG
NativeCodeData::GetDataDescription(typePropertyGuard, this->m_func->m_alloc),
#endif
this->m_func, true);
this->addToLiveOnBackEdgeSyms->Set(m_func->GetTopFunc()->GetNativeCodeDataSym()->m_id);
}
else
{
expectedTypeOpnd = IR::MemRefOpnd::New((void*)(typePropertyGuard->GetAddressOfValue()), TyMachPtr, this->m_func, IR::AddrOpndKindDynamicGuardValueRef);
}
emitDirectCheck = false;
OUTPUT_VERBOSE_TRACE_FUNC(Js::ObjTypeSpecPhase, this->m_func, _u("Emitted %s type check for type 0x%p.\n"),
emitDirectCheck ? _u("direct") : _u("indirect"), typeWithoutProperty->GetAddr());
}
else
{
expectedTypeOpnd = IR::AddrOpnd::New(typeWithoutProperty->GetAddr(), IR::AddrOpndKindDynamicType, m_func, true);
}
InsertCompareBranch(typeOpnd, expectedTypeOpnd, Js::OpCode::BrNeq_A, labelTypeCheckFailed, instrInsert);
// Technically, it should be enough to pin the final type, because it should keep all of its predecessors alive, but
// just to be extra cautious, let's pin the initial type as well.
PinTypeRef(typeWithoutProperty, typeWithoutProperty.t, instrInsert, propertySymOpnd->m_sym->AsPropertySym()->m_propertyId);
}
bool
Lowerer::GenerateFixedFieldGuardCheck(IR::Instr *insertPointInstr, IR::PropertySymOpnd *propertySymOpnd, IR::LabelInstr *labelBailOut)
{
return this->GeneratePropertyGuardCheck(insertPointInstr, propertySymOpnd, labelBailOut);
}
Js::JitTypePropertyGuard*
Lowerer::CreateTypePropertyGuardForGuardedProperties(JITTypeHolder type, IR::PropertySymOpnd* propertySymOpnd)
{
// We should always have a list of guarded properties.
Assert(propertySymOpnd->GetGuardedPropOps() != nullptr);
Js::JitTypePropertyGuard* guard = nullptr;
if (m_func->GetWorkItem()->GetJITTimeInfo()->HasSharedPropertyGuards())
{
// Consider (ObjTypeSpec): Because we allocate these guards from the JIT thread we can't share guards for the same type across multiple functions.
// This leads to proliferation of property guards on the thread context. The alternative would be to pre-allocate shared (by value) guards
// from the thread context during work item creation. We would create too many of them (because some types aren't actually used as guards),
// but we could share a guard for a given type between functions. This may ultimately be better.
LinkGuardToGuardedProperties(propertySymOpnd->GetGuardedPropOps(), [this, type, &guard](Js::PropertyId propertyId)
{
if (ShouldDoLazyFixedTypeBailout(this->m_func))
{
this->m_func->lazyBailoutProperties.Item(propertyId);
}
else
{
if (guard == nullptr)
{
guard = this->m_func->GetOrCreateSingleTypeGuard(type->GetAddr());
}
if (PHASE_TRACE(Js::ObjTypeSpecPhase, this->m_func) || PHASE_TRACE(Js::TracePropertyGuardsPhase, this->m_func))
{
char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
Output::Print(_u("ObjTypeSpec: function %s(%s) registered guard 0x%p with value 0x%p for property ID %u.\n"),
m_func->GetJITFunctionBody()->GetDisplayName(), this->m_func->GetDebugNumberSet(debugStringBuffer),
guard, guard->GetValue(), propertyId);
Output::Flush();
}
this->m_func->EnsurePropertyGuardsByPropertyId();
this->m_func->LinkGuardToPropertyId(propertyId, guard);
}
});
}
return guard;
}
Js::JitEquivalentTypeGuard*
Lowerer::CreateEquivalentTypeGuardAndLinkToGuardedProperties(IR::PropertySymOpnd* propertySymOpnd)
{
// We should always have a list of guarded properties.
Assert(propertySymOpnd->HasObjTypeSpecFldInfo() && propertySymOpnd->HasEquivalentTypeSet() && propertySymOpnd->GetGuardedPropOps());
Js::JitEquivalentTypeGuard* guard;
if (propertySymOpnd->ShouldUsePolyEquivTypeGuard(this->m_func))
{
Js::JitPolyEquivalentTypeGuard *polyGuard = this->m_func->CreatePolyEquivalentTypeGuard(propertySymOpnd->GetObjTypeSpecFldId());
// Copy types from the type set to the guard's value locations
Js::EquivalentTypeSet* typeSet = propertySymOpnd->GetEquivalentTypeSet();
for (uint16 ti = 0; ti < typeSet->GetCount(); ti++)
{
intptr_t typeToCache = typeSet->GetType(ti)->GetAddr();
polyGuard->SetPolyValue(typeToCache, polyGuard->GetIndexForValue(typeToCache));
}
guard = polyGuard;
}
else
{
guard = this->m_func->CreateEquivalentTypeGuard(propertySymOpnd->GetFirstEquivalentType(), propertySymOpnd->GetObjTypeSpecFldId());
}
if (m_func->GetWorkItem()->GetJITTimeInfo()->HasSharedPropertyGuards())
{
LinkGuardToGuardedProperties(propertySymOpnd->GetGuardedPropOps(), [=](Js::PropertyId propertyId)
{
if (PHASE_TRACE(Js::ObjTypeSpecPhase, this->m_func) || PHASE_TRACE(Js::TracePropertyGuardsPhase, this->m_func))
{
char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
Output::Print(_u("ObjTypeSpec: function %s(%s) registered equivalent type spec guard 0x%p with value 0x%p for property ID %u.\n"),
this->m_func->GetJITFunctionBody()->GetDisplayName(), this->m_func->GetDebugNumberSet(debugStringBuffer),
guard, guard->GetValue(), propertyId);
Output::Flush();
}
this->m_func->EnsurePropertyGuardsByPropertyId();
this->m_func->LinkGuardToPropertyId(propertyId, guard);
});
}
Assert(guard->GetCache() != nullptr);
Js::EquivalentTypeCache* cache = guard->GetCache();
// TODO (ObjTypeSpec): If we delayed populating the types until encoder, we could bulk allocate all equivalent type caches
// in one block from the heap. This would allow us to not allocate them from the native code data allocator and free them
// when no longer needed. However, we would need to store the global property operation ID in the guard, so we can look up
// the info in the encoder. Perhaps we could overload the cache pointer to be the ID until encoder.
// Copy types from the type set to the guard's cache
Js::EquivalentTypeSet* typeSet = propertySymOpnd->GetEquivalentTypeSet();
uint16 cachedTypeCount = typeSet->GetCount() < EQUIVALENT_TYPE_CACHE_SIZE ? typeSet->GetCount() : EQUIVALENT_TYPE_CACHE_SIZE;
for (uint16 ti = 0; ti < cachedTypeCount; ti++)
{
cache->types[ti] = (Js::Type*)typeSet->GetType(ti)->GetAddr();
}
#ifdef DEBUG
bool there_was_a_null_type = false;
for (uint16 ti = 0; ti < cachedTypeCount; ti++)
{
if (cache->types[ti] == nullptr)
{
there_was_a_null_type = true;
}
else if (there_was_a_null_type)
{
AssertMsg(false, "there_was_a_null_type ? something is wrong here.");
}
}
#endif
// Populate property ID and slot index arrays on the guard's cache. We iterate over the
// bit vector of property operations protected by this guard, but some property operations
// may be referring to the same property ID (but not share the same cache). We skip
// redundant entries by maintaining a hash set of property IDs we've already encountered.
auto propOps = propertySymOpnd->GetGuardedPropOps();
uint propOpCount = propOps->Count();
bool isTypeStatic = Js::StaticType::Is(propertySymOpnd->GetFirstEquivalentType()->GetTypeId());
JsUtil::BaseDictionary<Js::PropertyId, Js::EquivalentPropertyEntry*, JitArenaAllocator> propIds(this->m_alloc, propOpCount);
Js::EquivalentPropertyEntry* properties = AnewArray(this->m_alloc, Js::EquivalentPropertyEntry, propOpCount);
uint propIdCount = 0;
FOREACH_BITSET_IN_SPARSEBV(propOpId, propOps)
{
ObjTypeSpecFldInfo* propOpInfo = this->m_func->GetGlobalObjTypeSpecFldInfo(propOpId);
Js::PropertyId propertyId = propOpInfo->GetPropertyId();
Js::PropertyIndex propOpIndex = Js::Constants::NoSlot;
bool hasFixedValue = propOpInfo->HasFixedValue();
if (hasFixedValue)
{
cache->SetHasFixedValue();
}
bool isLoadedFromProto = propOpInfo->IsLoadedFromProto();
if (isLoadedFromProto)
{
cache->SetIsLoadedFromProto();
}
else
{
propOpIndex = propOpInfo->GetSlotIndex();
}
bool propOpUsesAuxSlot = propOpInfo->UsesAuxSlot();
AssertMsg(!isTypeStatic || !propOpInfo->IsBeingStored(), "Why are we storing a field to an object of static type?");
Js::EquivalentPropertyEntry* entry = nullptr;
if (propIds.TryGetValue(propertyId, &entry))
{
if (propOpIndex == entry->slotIndex && propOpUsesAuxSlot == entry->isAuxSlot)
{
entry->mustBeWritable |= propOpInfo->IsBeingStored();
}
else
{
// Due to inline cache sharing we have the same property accessed using different caches
// with inconsistent info. This means a guaranteed bailout on the equivalent type check.
// We'll just let it happen and turn off the optimization for this function. We could avoid
// this problem by tracking property information on the value type in glob opt.
if (PHASE_TRACE(Js::EquivObjTypeSpecPhase, this->m_func))
{
char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
Output::Print(_u("EquivObjTypeSpec: top function %s (%s): duplicate property clash on %d \n"),
m_func->GetJITFunctionBody()->GetDisplayName(), m_func->GetDebugNumberSet(debugStringBuffer), propertyId);
Output::Flush();
}
Assert(propIdCount < propOpCount);
__analysis_assume(propIdCount < propOpCount);
entry = &properties[propIdCount++];
entry->propertyId = propertyId;
entry->slotIndex = propOpIndex;
entry->isAuxSlot = propOpUsesAuxSlot;
entry->mustBeWritable = propOpInfo->IsBeingStored();
}
}
else
{
Assert(propIdCount < propOpCount);
__analysis_assume(propIdCount < propOpCount);
entry = &properties[propIdCount++];
entry->propertyId = propertyId;
entry->slotIndex = propOpIndex;
entry->isAuxSlot = propOpUsesAuxSlot;
entry->mustBeWritable = propOpInfo->IsBeingStored();
propIds.AddNew(propertyId, entry);
}
}
NEXT_BITSET_IN_SPARSEBV;
cache->record.propertyCount = propIdCount;
// Js::EquivalentPropertyEntry does not contain pointer, no need to fixup
cache->record.properties = NativeCodeDataNewArrayNoFixup(this->m_func->GetNativeCodeDataAllocator(), Js::EquivalentPropertyEntry, propIdCount);
memcpy(cache->record.properties, properties, propIdCount * sizeof(Js::EquivalentPropertyEntry));
return guard;
}
bool
Lowerer::LinkCtorCacheToGuardedProperties(JITTimeConstructorCache* ctorCache)
{
// We do not always have guarded properties. If the constructor is empty and the subsequent code doesn't load or store any of
// the constructed object's properties, or if all inline caches are empty then this ctor cache doesn't guard any properties.
if (ctorCache->GetGuardedPropOps() == nullptr)
{
return false;
}
bool linked = false;
if (this->m_func->GetWorkItem()->GetJITTimeInfo()->HasSharedPropertyGuards())
{
linked = LinkGuardToGuardedProperties(ctorCache->GetGuardedPropOps(), [=](Js::PropertyId propertyId)
{
if (PHASE_TRACE(Js::ObjTypeSpecPhase, this->m_func) || PHASE_TRACE(Js::TracePropertyGuardsPhase, this->m_func))
{
char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
Output::Print(_u("ObjTypeSpec: function %s(%s) registered ctor cache 0x%p with value 0x%p for property %u.\n"),
this->m_func->GetJITFunctionBody()->GetDisplayName(), this->m_func->GetDebugNumberSet(debugStringBuffer),
ctorCache->GetRuntimeCacheAddr(), ctorCache->GetType()->GetAddr(), propertyId);
Output::Flush();
}
this->m_func->EnsureCtorCachesByPropertyId();
this->m_func->LinkCtorCacheToPropertyId(propertyId, ctorCache);
});
}
return linked;
}
template<typename LinkFunc>
bool
Lowerer::LinkGuardToGuardedProperties(const BVSparse<JitArenaAllocator>* guardedPropOps, LinkFunc link)
{
Assert(this->m_func->GetWorkItem()->GetJITTimeInfo()->HasSharedPropertyGuards());
Assert(guardedPropOps != nullptr);
bool linked = false;
// For every entry in the bit vector, register the guard for the corresponding property ID.
FOREACH_BITSET_IN_SPARSEBV(propertyOpId, guardedPropOps)
{
ObjTypeSpecFldInfo* propertyOpInfo = this->m_func->GetGlobalObjTypeSpecFldInfo(propertyOpId);
Js::PropertyId propertyId = propertyOpInfo->GetPropertyId();
// It's okay for an equivalent type check to be registered as a guard against a property becoming read-only. This transpires if, there is
// a different monomorphic type check upstream, which guarantees the actual type of the object needed for the hard-coded type transition,
// but it is later followed by a sequence of polymorphic inline caches, which do not have that type in the type set. At the beginning of
// that sequence we'll emit an equivalent type check to verify that the actual type has relevant properties on appropriate slots. Then in
// the dead store pass we'll walk upwards and encounter this check first, thus we'll drop the guarded properties accumulated thus far
// (including the one being added) on that check.
// AssertMsg(!propertyOpInfo->IsBeingAdded() || !isEquivalentTypeGuard, "Why do we have an equivalent type check protecting a property add?");
if (propertyOpInfo->IsBeingAdded() || propertyOpInfo->IsLoadedFromProto() || propertyOpInfo->HasFixedValue())
{
// Equivalent object type spec only supports fixed fields on prototypes. This is to simplify the slow type equivalence check.
// See JavascriptOperators::CheckIfTypeIsEquivalent.
Assert(!propertyOpInfo->IsPoly() || (!propertyOpInfo->HasFixedValue() || propertyOpInfo->IsLoadedFromProto() || propertyOpInfo->UsesAccessor()));
if (this->m_func->GetWorkItem()->GetJITTimeInfo()->HasSharedPropertyGuard(propertyId))
{
link(propertyId);
linked = true;
}
else
{
AssertMsg(false, "Did we fail to create a shared property guard for a guarded property?");
}
}
}
NEXT_BITSET_IN_SPARSEBV;
return linked;
}
bool
Lowerer::GeneratePropertyGuardCheck(IR::Instr *insertPointInstr, IR::PropertySymOpnd *propertySymOpnd, IR::LabelInstr *labelBailOut)
{
intptr_t guard = propertySymOpnd->GetPropertyGuardValueAddr();
Assert(guard != 0);
if (ShouldDoLazyFixedDataBailout(this->m_func))
{
this->m_func->lazyBailoutProperties.Item(propertySymOpnd->GetPropertyId());
return false;
}
else
{
Assert(Js::PropertyGuard::GetSizeOfValue() == static_cast<size_t>(TySize[TyMachPtr]));
IR::AddrOpnd* zeroOpnd = IR::AddrOpnd::NewNull(this->m_func);
IR::MemRefOpnd* guardOpnd = IR::MemRefOpnd::New(guard, TyMachPtr, this->m_func, IR::AddrOpndKindDynamicGuardValueRef);
IR::BranchInstr *branchInstr = InsertCompareBranch(guardOpnd, zeroOpnd, Js::OpCode::BrEq_A, labelBailOut, insertPointInstr);
IR::RegOpnd *objPtrReg = IR::RegOpnd::New(propertySymOpnd->GetObjectSym(), TyMachPtr, m_func);
InsertObjectPoison(objPtrReg, branchInstr, insertPointInstr, false);
return true;
}
}
IR::Instr*
Lowerer::GeneratePropertyGuardCheckBailoutAndLoadType(IR::Instr *insertInstr)
{
IR::Instr* instrPrev = insertInstr->m_prev;
IR::Opnd* numberTypeOpnd = IR::AddrOpnd::New(insertInstr->m_func->GetScriptContextInfo()->GetNumberTypeStaticAddr(), IR::AddrOpndKindDynamicType, insertInstr->m_func);
IR::PropertySymOpnd* propertySymOpnd = insertInstr->GetSrc1()->AsPropertySymOpnd();
IR::LabelInstr* labelBailout = IR::LabelInstr::New(Js::OpCode::Label, insertInstr->m_func, true);
IR::LabelInstr* labelContinue = IR::LabelInstr::New(Js::OpCode::Label, insertInstr->m_func);
IR::LabelInstr* loadNumberTypeLabel = IR::LabelInstr::New(Js::OpCode::Label, insertInstr->m_func, true);
GeneratePropertyGuardCheck(insertInstr, propertySymOpnd, labelBailout);
IR::RegOpnd *baseOpnd = propertySymOpnd->CreatePropertyOwnerOpnd(m_func);
GenerateObjectTestAndTypeLoad(insertInstr, baseOpnd, insertInstr->GetDst()->AsRegOpnd(), loadNumberTypeLabel);
insertInstr->InsertBefore(IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, labelContinue, this->m_func));
insertInstr->InsertBefore(loadNumberTypeLabel);
this->InsertMove(insertInstr->GetDst(), numberTypeOpnd, insertInstr);
insertInstr->InsertBefore(IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, labelContinue, this->m_func));
insertInstr->InsertBefore(labelBailout);
insertInstr->InsertAfter(labelContinue);
insertInstr->FreeSrc1();
insertInstr->m_opcode = Js::OpCode::BailOut;
this->GenerateBailOut(insertInstr);
return instrPrev;
}
void
Lowerer::GenerateAdjustSlots(IR::Instr *instrInsert, IR::PropertySymOpnd *propertySymOpnd, JITTypeHolder initialType, JITTypeHolder finalType)
{
IR::RegOpnd *baseOpnd = propertySymOpnd->CreatePropertyOwnerOpnd(m_func);
bool adjusted = this->GenerateAdjustBaseSlots(instrInsert, baseOpnd, initialType, finalType);
if (!adjusted)
{
baseOpnd->Free(m_func);
}
}
bool
Lowerer::GenerateAdjustBaseSlots(IR::Instr *instrInsert, IR::RegOpnd *baseOpnd, JITTypeHolder initialType, JITTypeHolder finalType)
{
// Possibly allocate new slot capacity to accommodate a type transition.
AssertMsg(JITTypeHandler::IsTypeHandlerCompatibleForObjectHeaderInlining(initialType->GetTypeHandler(), finalType->GetTypeHandler()),
"Incompatible typeHandler transition?");
int oldCount = 0;
int newCount = 0;
Js::PropertyIndex inlineSlotCapacity = 0;
Js::PropertyIndex newInlineSlotCapacity = 0;
bool needSlotAdjustment =
JITTypeHandler::NeedSlotAdjustment(initialType->GetTypeHandler(), finalType->GetTypeHandler(), &oldCount, &newCount, &inlineSlotCapacity, &newInlineSlotCapacity);
if (!needSlotAdjustment)
{
return false;
}
// Call AdjustSlots using the new counts. Because AdjustSlots uses the "no dispose" flavor of alloc,
// no implicit calls are possible, and we don't need an implicit call check and bailout.
// CALL AdjustSlots, instance, newInlineSlotCapacity, newAuxSlotCapacity
//3rd Param
Assert(newCount > newInlineSlotCapacity);
const int newAuxSlotCapacity = newCount - newInlineSlotCapacity;
m_lowererMD.LoadHelperArgument(instrInsert, IR::IntConstOpnd::New(newAuxSlotCapacity, TyInt32, this->m_func));
//2nd Param
m_lowererMD.LoadHelperArgument(instrInsert, IR::IntConstOpnd::New(newInlineSlotCapacity, TyUint16, this->m_func));
//1st Param (instance)
m_lowererMD.LoadHelperArgument(instrInsert, baseOpnd);
//CALL HelperAdjustSlots
IR::Opnd *opnd = IR::HelperCallOpnd::New(IR::HelperAdjustSlots, this->m_func);
IR::Instr *instr = IR::Instr::New(Js::OpCode::Call, this->m_func);
instr->SetSrc1(opnd);
instrInsert->InsertBefore(instr);
m_lowererMD.LowerCall(instr, 0);
return true;
}
void
Lowerer::GenerateFieldStoreWithTypeChange(IR::Instr * instrStFld, IR::PropertySymOpnd *propertySymOpnd, JITTypeHolder initialType, JITTypeHolder finalType)
{
// Adjust instance slots, if necessary.
this->GenerateAdjustSlots(instrStFld, propertySymOpnd, initialType, finalType);
// We should never add properties to objects of static types.
Assert(Js::DynamicType::Is(finalType->GetTypeId()));
// Let's pin the final type to be sure its alive when we try to do the type transition.
PinTypeRef(finalType, finalType.t, instrStFld, propertySymOpnd->m_sym->AsPropertySym()->m_propertyId);
IR::Opnd *finalTypeOpnd = IR::AddrOpnd::New(finalType->GetAddr(), IR::AddrOpndKindDynamicType, instrStFld->m_func, true);
// Set the new type.
IR::RegOpnd *baseOpnd = propertySymOpnd->CreatePropertyOwnerOpnd(instrStFld->m_func);
IR::Opnd *opnd = IR::IndirOpnd::New(baseOpnd, Js::RecyclableObject::GetOffsetOfType(), TyMachReg, instrStFld->m_func);
this->InsertMove(opnd, finalTypeOpnd, instrStFld);
// Now do the store.
GenerateDirectFieldStore(instrStFld, propertySymOpnd);
bool isPrototypeTypeHandler = initialType->GetTypeHandler()->IsPrototype();
if (isPrototypeTypeHandler)
{
LoadScriptContext(instrStFld);
m_lowererMD.LoadHelperArgument(instrStFld, IR::IntConstOpnd::New(propertySymOpnd->GetPropertyId(), TyInt32, m_func, true));
IR::Instr * invalidateCallInstr = IR::Instr::New(Js::OpCode::Call, m_func);
instrStFld->InsertBefore(invalidateCallInstr);
m_lowererMD.ChangeToHelperCall(invalidateCallInstr, IR::HelperInvalidateProtoCaches);
}
}
bool
Lowerer::GenerateStFldWithCachedFinalType(IR::Instr * instrStFld, IR::PropertySymOpnd *propertySymOpnd)
{
// This function tries to treat a sequence of add-property stores as a single type transition.
Assert(propertySymOpnd == instrStFld->GetDst()->AsPropertySymOpnd());
Assert(propertySymOpnd->IsMonoObjTypeSpecCandidate());
Assert(propertySymOpnd->HasFinalType());
Assert(propertySymOpnd->HasInitialType());
IR::Instr *instr;
IR::LabelInstr *labelBailOut = nullptr;
AssertMsg(!propertySymOpnd->IsTypeChecked(), "Why are we doing a type transition when we have the type we want?");
// If the initial type must be checked here, do it.
Assert(instrStFld->HasBailOutInfo());
labelBailOut = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true);
GenerateCachedTypeWithoutPropertyCheck(instrStFld, propertySymOpnd, nullptr/*typeOpnd*/, labelBailOut);
// Do the type transition.
GenerateFieldStoreWithTypeChange(instrStFld, propertySymOpnd, propertySymOpnd->GetInitialType(), propertySymOpnd->GetFinalType());
instrStFld->FreeSrc1();
instrStFld->FreeDst();
// Insert the bailout and let the main path branch around it.
IR::LabelInstr *labelDone = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
instr = IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, labelDone, this->m_func);
instrStFld->InsertBefore(instr);
if (instrStFld->HasBailOutInfo())
{
Assert(labelBailOut != nullptr);
instrStFld->InsertBefore(labelBailOut);
instrStFld->InsertAfter(labelDone);
instrStFld->m_opcode = Js::OpCode::BailOut;
this->GenerateBailOut(instrStFld);
}
else
{
instrStFld->InsertAfter(labelDone);
instrStFld->Remove();
}
return true;
}
///----------------------------------------------------------------------------
///
/// Lowerer::LowerScopedStFld
///
///----------------------------------------------------------------------------
IR::Instr *
Lowerer::LowerScopedStFld(IR::Instr * stFldInstr, IR::JnHelperMethod helperMethod, bool withInlineCache,
bool withPropertyOperationFlags, Js::PropertyOperationFlags flags)
{
IR::Instr *instrPrev = stFldInstr->m_prev;
if (withPropertyOperationFlags)
{
m_lowererMD.LoadHelperArgument(stFldInstr,
IR::IntConstOpnd::New(static_cast<IntConstType>(flags), IRType::TyInt32, m_func, true));
}
if(!withInlineCache)
{
LoadScriptContext(stFldInstr);
}
// Pass the default instance
IR::Opnd *src = stFldInstr->UnlinkSrc2();
m_lowererMD.LoadHelperArgument(stFldInstr, src);
// Pass the value to store
src = stFldInstr->UnlinkSrc1();
m_lowererMD.LoadHelperArgument(stFldInstr, src);
// Pass the property sym to store to
IR::Opnd *dst = stFldInstr->UnlinkDst();
AssertMsg(dst->IsSymOpnd() && dst->AsSymOpnd()->m_sym->IsPropertySym(), "Expected property sym as dst of field store");
this->LoadPropertySymAsArgument(stFldInstr, dst);
if (withInlineCache)
{
AssertMsg(dst->AsSymOpnd()->IsPropertySymOpnd(), "Need property sym operand to find the inline cache");
m_lowererMD.LoadHelperArgument(
stFldInstr,
IR::Opnd::CreateInlineCacheIndexOpnd(dst->AsPropertySymOpnd()->m_inlineCacheIndex, m_func));
// Not using the polymorphic inline cache because the fast path only uses the monomorphic inline cache
this->m_lowererMD.LoadHelperArgument(stFldInstr, this->LoadRuntimeInlineCacheOpnd(stFldInstr, dst->AsPropertySymOpnd()));
m_lowererMD.LoadHelperArgument(stFldInstr, LoadFunctionBodyOpnd(stFldInstr));
}
m_lowererMD.ChangeToHelperCall(stFldInstr, helperMethod);
return instrPrev;
}
///----------------------------------------------------------------------------
///
/// Lowerer::LowerLoadVar
///
///----------------------------------------------------------------------------
IR::Instr *
Lowerer::LowerLoadVar(IR::Instr *instr, IR::Opnd *opnd)
{
instr->SetSrc1(opnd);
return m_lowererMD.ChangeToAssign(instr);
}
IR::Instr *
Lowerer::LoadHelperTemp(IR::Instr * instr, IR::Instr * instrInsert)
{
IR::Opnd *tempOpnd;
IR::Opnd *dst = instr->GetDst();
AssertMsg(dst != nullptr, "Always expect a dst for these.");
AssertMsg(instr->dstIsTempNumber, "Should only be loading temps here");
Assert(dst->IsRegOpnd());
StackSym * tempNumberSym = this->GetTempNumberSym(dst, instr->dstIsTempNumberTransferred);
IR::Instr *load = InsertLoadStackAddress(tempNumberSym, instrInsert);
tempOpnd = load->GetDst();
m_lowererMD.LoadHelperArgument(instrInsert, tempOpnd);
return load;
}
void
Lowerer::LoadArgumentCount(IR::Instr *const instr)
{
Assert(instr);
Assert(instr->GetDst());
Assert(!instr->GetSrc1());
Assert(!instr->GetSrc2());
if(instr->m_func->IsInlinee())
{
// Argument count including 'this'
instr->SetSrc1(IR::IntConstOpnd::New(instr->m_func->actualCount, TyUint32, instr->m_func, true));
LowererMD::ChangeToAssign(instr);
}
else if (instr->m_func->GetJITFunctionBody()->IsCoroutine())
{
IR::SymOpnd* symOpnd = LoadCallInfo(instr);
instr->SetSrc1(symOpnd);
LowererMD::ChangeToAssign(instr);
}
else
{
m_lowererMD.LoadArgumentCount(instr);
}
}
void
Lowerer::LoadStackArgPtr(IR::Instr *const instr)
{
Assert(instr);
Assert(instr->GetDst());
Assert(!instr->GetSrc1());
Assert(!instr->GetSrc2());
if(instr->m_func->IsInlinee())
{
// Address of argument after 'this'
const auto firstRealArgStackSym = instr->m_func->GetInlineeArgvSlotOpnd()->m_sym->AsStackSym();
this->m_func->SetArgOffset(firstRealArgStackSym, firstRealArgStackSym->m_offset + MachPtr);
instr->SetSrc1(IR::SymOpnd::New(firstRealArgStackSym, TyMachPtr, instr->m_func));
ChangeToLea(instr);
}
else
{
m_lowererMD.LoadStackArgPtr(instr);
}
}
IR::Instr *
Lowerer::InsertLoadStackAddress(StackSym *sym, IR::Instr * instrInsert, IR::RegOpnd *optionalDstOpnd /* = nullptr */)
{
IR::RegOpnd * regDst = optionalDstOpnd != nullptr ? optionalDstOpnd : IR::RegOpnd::New(TyMachReg, this->m_func);
IR::SymOpnd * symSrc = IR::SymOpnd::New(sym, TyMachPtr, this->m_func);
return InsertLea(regDst, symSrc, instrInsert);
}
void
Lowerer::LoadArgumentsFromFrame(IR::Instr *const instr)
{
Assert(instr);
Assert(instr->GetDst());
Assert(!instr->GetSrc1());
Assert(!instr->GetSrc2());
if(instr->m_func->IsInlinee())
{
// Use the inline object meta arg slot for the arguments object
instr->SetSrc1(instr->m_func->GetInlineeArgumentsObjectSlotOpnd());
LowererMD::ChangeToAssign(instr);
}
else
{
m_lowererMD.LoadArgumentsFromFrame(instr);
}
}
#ifdef ENABLE_WASM
IR::Instr *
Lowerer::LowerCheckWasmSignature(IR::Instr * instr)
{
Assert(m_func->GetJITFunctionBody()->IsWasmFunction());
Assert(instr->GetSrc1());
Assert(instr->GetSrc2()->IsIntConstOpnd());
int sigId = instr->UnlinkSrc2()->AsIntConstOpnd()->AsInt32();
IR::Instr *instrPrev = instr->m_prev;
IR::IndirOpnd * actualSig = IR::IndirOpnd::New(instr->UnlinkSrc1()->AsRegOpnd(), Js::WasmScriptFunction::GetOffsetOfSignature(), TyMachReg, m_func);
Wasm::WasmSignature * expectedSig = m_func->GetJITFunctionBody()->GetAsmJsInfo()->GetWasmSignature(sigId);
if (expectedSig->GetShortSig() == Js::Constants::InvalidSignature)
{
intptr_t sigAddr = m_func->GetJITFunctionBody()->GetAsmJsInfo()->GetWasmSignatureAddr(sigId);
IR::AddrOpnd * expectedOpnd = IR::AddrOpnd::New(sigAddr, IR::AddrOpndKindConstantAddress, m_func);
m_lowererMD.LoadHelperArgument(instr, expectedOpnd);
m_lowererMD.LoadHelperArgument(instr, actualSig);
LoadScriptContext(instr);
m_lowererMD.ChangeToHelperCall(instr, IR::HelperOp_CheckWasmSignature);
}
else
{
IR::LabelInstr * trapLabel = InsertLabel(true, instr);
IR::LabelInstr * labelFallThrough = InsertLabel(false, instr->m_next);
IR::RegOpnd * actualRegOpnd = IR::RegOpnd::New(TyMachReg, m_func);
InsertMove(actualRegOpnd, actualSig, trapLabel);
IR::IndirOpnd * shortSigIndir = IR::IndirOpnd::New(actualRegOpnd, Wasm::WasmSignature::GetOffsetOfShortSig(), TyMachReg, m_func);
InsertCompareBranch(shortSigIndir, IR::IntConstOpnd::New(expectedSig->GetShortSig(), TyMachReg, m_func), Js::OpCode::BrNeq_A, trapLabel, trapLabel);
InsertBranch(Js::OpCode::Br, labelFallThrough, trapLabel);
GenerateThrow(IR::IntConstOpnd::NewFromType(SCODE_CODE(WASMERR_SignatureMismatch), TyInt32, m_func), instr);
instr->Remove();
}
return instrPrev;
}
IR::Instr *
Lowerer::LowerLdWasmFunc(IR::Instr* instr)
{
IR::Instr * prev = instr->m_prev;
IR::RegOpnd * tableReg = instr->UnlinkSrc1()->AsRegOpnd();
IR::Opnd * indexOpnd = instr->UnlinkSrc2();
IR::Opnd * dst = instr->UnlinkDst();
IR::IndirOpnd * lengthOpnd = IR::IndirOpnd::New(tableReg, Js::WebAssemblyTable::GetOffsetOfCurrentLength(), TyUint32, m_func);
IR::IndirOpnd * valuesIndirOpnd = IR::IndirOpnd::New(tableReg, Js::WebAssemblyTable::GetOffsetOfValues(), TyMachPtr, m_func);
IR::RegOpnd * valuesRegOpnd = IR::RegOpnd::New(TyMachPtr, m_func);
byte scale = m_lowererMD.GetDefaultIndirScale();
IR::IndirOpnd * funcIndirOpnd;
if (indexOpnd->IsIntConstOpnd())
{
funcIndirOpnd = IR::IndirOpnd::New(valuesRegOpnd, indexOpnd->AsIntConstOpnd()->AsInt32() << scale, TyMachPtr, m_func);
}
else
{
Assert(indexOpnd->IsRegOpnd());
funcIndirOpnd = IR::IndirOpnd::New(valuesRegOpnd, indexOpnd->AsRegOpnd(), TyMachPtr, m_func);
funcIndirOpnd->SetScale(scale);
}
IR::LabelInstr * trapOutOfBoundsLabel = InsertLabel(true, instr);
IR::LabelInstr * trapLabel = InsertLabel(true, trapOutOfBoundsLabel);
IR::LabelInstr * doneLabel = InsertLabel(false, instr->m_next);
InsertCompareBranch(indexOpnd, lengthOpnd, Js::OpCode::BrGe_A, true, trapOutOfBoundsLabel, trapLabel);
InsertMove(valuesRegOpnd, valuesIndirOpnd, trapLabel);
InsertMove(dst, funcIndirOpnd, trapLabel);
InsertCompareBranch(dst, IR::IntConstOpnd::New(0, TyMachPtr, m_func), Js::OpCode::BrEq_A, trapLabel, trapLabel);
InsertBranch(Js::OpCode::Br, doneLabel, trapLabel);
GenerateThrow(IR::IntConstOpnd::NewFromType(SCODE_CODE(WASMERR_NeedWebAssemblyFunc), TyInt32, m_func), trapOutOfBoundsLabel);
GenerateThrow(IR::IntConstOpnd::NewFromType(SCODE_CODE(WASMERR_TableIndexOutOfRange), TyInt32, m_func), instr);
instr->Remove();
return prev;
}
IR::Instr *
Lowerer::LowerGrowWasmMemory(IR::Instr* instr)
{
IR::Instr * instrPrev = m_lowererMD.LoadHelperArgument(instr, instr->UnlinkSrc2());
m_lowererMD.LoadHelperArgument(instr, instr->UnlinkSrc1());
m_lowererMD.ChangeToHelperCall(instr, IR::HelperOp_GrowWasmMemory);
return instrPrev;
}
#endif
IR::Instr *
Lowerer::LowerUnaryHelper(IR::Instr *instr, IR::JnHelperMethod helperMethod, IR::Opnd* opndBailoutArg)
{
IR::Instr *instrPrev;
IR::Opnd *src1 = instr->UnlinkSrc1();
instrPrev = m_lowererMD.LoadHelperArgument(instr, src1);
m_lowererMD.ChangeToHelperCall(instr, helperMethod, nullptr, opndBailoutArg);
return instrPrev;
}
// helper takes memory context as second argument
IR::Instr *
Lowerer::LowerUnaryHelperMem(IR::Instr *instr, IR::JnHelperMethod helperMethod, IR::Opnd* opndBailoutArg)
{
IR::Instr *instrPrev;
instrPrev = LoadScriptContext(instr);
return this->LowerUnaryHelper(instr, helperMethod, opndBailoutArg);
}
IR::Instr *
Lowerer::LowerUnaryHelperMemWithFunctionInfo(IR::Instr *instr, IR::JnHelperMethod helperMethod)
{
m_lowererMD.LoadHelperArgument(instr, this->LoadFunctionInfoOpnd(instr));
return this->LowerUnaryHelperMem(instr, helperMethod);
}
IR::Instr *
Lowerer::LowerUnaryHelperMemWithFuncBody(IR::Instr *instr, IR::JnHelperMethod helperMethod)
{
m_lowererMD.LoadHelperArgument(instr, this->LoadFunctionBodyOpnd(instr));
return this->LowerUnaryHelperMem(instr, helperMethod);
}
IR::Instr *
Lowerer::LowerBinaryHelperMemWithFuncBody(IR::Instr *instr, IR::JnHelperMethod helperMethod)
{
AssertMsg(Js::OpCodeUtil::GetOpCodeLayout(instr->m_opcode) == Js::OpLayoutType::Reg3, "Expected a binary instruction...");
m_lowererMD.LoadHelperArgument(instr, this->LoadFunctionBodyOpnd(instr));
return this->LowerBinaryHelperMem(instr, helperMethod);
}
IR::Instr *
Lowerer::LowerUnaryHelperMemWithTemp(IR::Instr *instr, IR::JnHelperMethod helperMethod)
{
AssertMsg(Js::OpCodeUtil::GetOpCodeLayout(instr->m_opcode) == Js::OpLayoutType::Reg2, "Expected a unary instruction...");
IR::Instr * instrFirst;
IR::Opnd * tempOpnd;
if (instr->dstIsTempNumber)
{
instrFirst = this->LoadHelperTemp(instr, instr);
}
else
{
tempOpnd = IR::IntConstOpnd::New(0, TyInt32, this->m_func);
instrFirst = m_lowererMD.LoadHelperArgument(instr, tempOpnd);
}
this->LowerUnaryHelperMem(instr, helperMethod);
return instrFirst;
}
IR::Instr *
Lowerer::LowerUnaryHelperMemWithTemp2(IR::Instr *instr, IR::JnHelperMethod helperMethod, IR::JnHelperMethod helperMethodWithTemp)
{
AssertMsg(Js::OpCodeUtil::GetOpCodeLayout(instr->m_opcode) == Js::OpLayoutType::Reg2, "Expected a unary instruction...");
if (instr->dstIsTempNumber)
{
IR::Instr * instrFirst = this->LoadHelperTemp(instr, instr);
this->LowerUnaryHelperMem(instr, helperMethodWithTemp);
return instrFirst;
}
return this->LowerUnaryHelperMem(instr, helperMethod);
}
IR::Instr *
Lowerer::LowerUnaryHelperMemWithBoolReference(IR::Instr *instr, IR::JnHelperMethod helperMethod, bool useBoolForBailout)
{
if (!this->m_func->tempSymBool)
{
this->m_func->tempSymBool = StackSym::New(TyUint8, this->m_func);
this->m_func->StackAllocate(this->m_func->tempSymBool, TySize[TyUint8]);
}
IR::SymOpnd * boolOpnd = IR::SymOpnd::New(this->m_func->tempSymBool, TyUint8, this->m_func);
IR::RegOpnd * boolRefOpnd = IR::RegOpnd::New(TyMachReg, this->m_func);
InsertLea(boolRefOpnd, boolOpnd, instr);
m_lowererMD.LoadHelperArgument(instr, boolRefOpnd);
return this->LowerUnaryHelperMem(instr, helperMethod, useBoolForBailout ? boolOpnd : nullptr);
}
IR::Instr *
Lowerer::LowerInitCachedScope(IR::Instr* instr)
{
instr->m_opcode = Js::OpCode::CallHelper;
IR::HelperCallOpnd *helperOpnd = IR::HelperCallOpnd::New(IR::HelperOP_InitCachedScope, this->m_func);
IR::Opnd * src1 = instr->UnlinkSrc1();
instr->SetSrc1(helperOpnd);
instr->SetSrc2(src1);
return instr;
}
///----------------------------------------------------------------------------
///
/// Lowerer::LowerBinaryHelper
///
///----------------------------------------------------------------------------
IR::Instr *
Lowerer::LowerBinaryHelper(IR::Instr *instr, IR::JnHelperMethod helperMethod)
{
// The only case where this would still be null when we return is when
// helperMethod == HelperOP_CmSrEq_EmptyString; in which case we ignore
// instrPrev.
IR::Instr *instrPrev = nullptr;
AssertMsg((Js::OpCodeUtil::GetOpCodeLayout(instr->m_opcode) == Js::OpLayoutType::Reg1Unsigned1) ||
Js::OpCodeUtil::GetOpCodeLayout(instr->m_opcode) == Js::OpLayoutType::Reg3 ||
Js::OpCodeUtil::GetOpCodeLayout(instr->m_opcode) == Js::OpLayoutType::Reg2 ||
Js::OpCodeUtil::GetOpCodeLayout(instr->m_opcode) == Js::OpLayoutType::Reg2Int1 ||
Js::OpCodeUtil::GetOpCodeLayout(instr->m_opcode) == Js::OpLayoutType::ElementU ||
instr->m_opcode == Js::OpCode::InvalCachedScope, "Expected a binary instruction...");
IR::Opnd *src2 = instr->UnlinkSrc2();
if (helperMethod != IR::HelperOP_CmSrEq_EmptyString)
instrPrev = m_lowererMD.LoadHelperArgument(instr, src2);
IR::Opnd *src1 = instr->UnlinkSrc1();
m_lowererMD.LoadHelperArgument(instr, src1);
m_lowererMD.ChangeToHelperCall(instr, helperMethod);
return instrPrev;
}
// helper takes memory context as third argument
IR::Instr *
Lowerer::LowerBinaryHelperMem(IR::Instr *instr, IR::JnHelperMethod helperMethod)
{
IR::Instr *instrPrev;
AssertMsg(Js::OpCodeUtil::GetOpCodeLayout(instr->m_opcode) == Js::OpLayoutType::Reg3 ||
Js::OpCodeUtil::GetOpCodeLayout(instr->m_opcode) == Js::OpLayoutType::Reg2 ||
Js::OpCodeUtil::GetOpCodeLayout(instr->m_opcode) == Js::OpLayoutType::Reg2Int1 ||
Js::OpCodeUtil::GetOpCodeLayout(instr->m_opcode) == Js::OpLayoutType::Reg1Unsigned1, "Expected a binary instruction...");
instrPrev = LoadScriptContext(instr);
return this->LowerBinaryHelper(instr, helperMethod);
}
IR::Instr *
Lowerer::LowerBinaryHelperMemWithTemp(IR::Instr *instr, IR::JnHelperMethod helperMethod)
{
AssertMsg(Js::OpCodeUtil::GetOpCodeLayout(instr->m_opcode) == Js::OpLayoutType::Reg3, "Expected a binary instruction...");
IR::Instr * instrFirst;
IR::Opnd * tempOpnd;
if (instr->dstIsTempNumber)
{
instrFirst = this->LoadHelperTemp(instr, instr);
}
else
{
tempOpnd = IR::IntConstOpnd::New(0, TyInt32, this->m_func);
instrFirst = m_lowererMD.LoadHelperArgument(instr, tempOpnd);
}
this->LowerBinaryHelperMem(instr, helperMethod);
return instrFirst;
}
IR::Instr *
Lowerer::LowerBinaryHelperMemWithTemp2(
IR::Instr *instr,
IR::JnHelperMethod helperMethod,
IR::JnHelperMethod helperMethodWithTemp
)
{
AssertMsg(Js::OpCodeUtil::GetOpCodeLayout(instr->m_opcode) == Js::OpLayoutType::Reg3, "Expected a binary instruction...");
if (instr->dstIsTempNumber && instr->GetDst() && instr->GetDst()->GetValueType().HasBeenNumber())
{
IR::Instr * instrFirst = this->LoadHelperTemp(instr, instr);
this->LowerBinaryHelperMem(instr, helperMethodWithTemp);
return instrFirst;
}
return this->LowerBinaryHelperMem(instr, helperMethod);
}
IR::Instr *
Lowerer::LowerAddLeftDeadForString(IR::Instr *instr)
{
IR::Opnd * opndLeft;
IR::Opnd * opndRight;
opndLeft = instr->GetSrc1();
opndRight = instr->GetSrc2();
Assert(opndLeft && opndRight);
bool generateFastPath = this->m_func->DoFastPaths();
if (!generateFastPath
|| !opndLeft->IsRegOpnd()
|| !opndRight->IsRegOpnd()
|| !instr->GetDst()->IsRegOpnd()
|| !opndLeft->GetValueType().IsLikelyString()
|| !opndRight->GetValueType().IsLikelyString()
|| !opndLeft->IsEqual(instr->GetDst()->AsRegOpnd())
|| opndLeft->IsEqual(opndRight))
{
return this->LowerBinaryHelperMemWithTemp(instr, IR::HelperOp_AddLeftDead);
}
IR::LabelInstr * labelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true);
IR::LabelInstr * labelFallThrough = instr->GetOrCreateContinueLabel(false);
IR::LabelInstr *insertBeforeInstr = labelHelper;
instr->InsertBefore(labelHelper);
if (!opndLeft->IsNotTaggedValue())
{
this->m_lowererMD.GenerateObjectTest(opndLeft->AsRegOpnd(), insertBeforeInstr, labelHelper);
}
IR::BranchInstr* branchInstr = InsertCompareBranch(
IR::IndirOpnd::New(opndLeft->AsRegOpnd(), 0, TyMachPtr, m_func),
this->LoadVTableValueOpnd(insertBeforeInstr, VTableValue::VtableCompoundString),
Js::OpCode::BrNeq_A,
labelHelper,
insertBeforeInstr);
InsertObjectPoison(opndLeft->AsRegOpnd(), branchInstr, insertBeforeInstr, false);
GenerateStringTest(opndRight->AsRegOpnd(), insertBeforeInstr, labelHelper);
// left->m_charLength <= JavascriptArray::MaxCharLength
IR::IndirOpnd *indirLeftCharLengthOpnd = IR::IndirOpnd::New(opndLeft->AsRegOpnd(), Js::JavascriptString::GetOffsetOfcharLength(), TyUint32, m_func);
IR::RegOpnd *regLeftCharLengthOpnd = IR::RegOpnd::New(TyUint32, m_func);
InsertMove(regLeftCharLengthOpnd, indirLeftCharLengthOpnd, insertBeforeInstr);
InsertCompareBranch(
regLeftCharLengthOpnd,
IR::IntConstOpnd::New(Js::JavascriptString::MaxCharLength, TyUint32, m_func),
Js::OpCode::BrGe_A,
labelHelper,
insertBeforeInstr);
// left->m_pszValue == NULL (!left->IsFinalized())
InsertCompareBranch(
IR::IndirOpnd::New(opndLeft->AsRegOpnd(), offsetof(Js::JavascriptString, m_pszValue), TyMachPtr, this->m_func),
IR::AddrOpnd::NewNull(m_func),
Js::OpCode::BrNeq_A,
labelHelper,
insertBeforeInstr);
// right->m_pszValue != NULL (right->IsFinalized())
InsertCompareBranch(
IR::IndirOpnd::New(opndRight->AsRegOpnd(), offsetof(Js::JavascriptString, m_pszValue), TyMachPtr, this->m_func),
IR::AddrOpnd::NewNull(m_func),
Js::OpCode::BrEq_A,
labelHelper,
insertBeforeInstr);
// if ownsLastBlock != 0
InsertCompareBranch(
IR::IndirOpnd::New(opndLeft->AsRegOpnd(), (int32)Js::CompoundString::GetOffsetOfOwnsLastBlock(), TyUint8, m_func),
IR::IntConstOpnd::New(0, TyUint8, m_func),
Js::OpCode::BrEq_A,
labelHelper,
insertBeforeInstr);
// if right->m_charLength == 1
InsertCompareBranch(IR::IndirOpnd::New(opndRight->AsRegOpnd(), offsetof(Js::JavascriptString, m_charLength), TyUint32, m_func),
IR::IntConstOpnd::New(1, TyUint32, m_func),
Js::OpCode::BrNeq_A, labelHelper, insertBeforeInstr);
// if left->m_directCharLength == -1
InsertCompareBranch(IR::IndirOpnd::New(opndLeft->AsRegOpnd(), (int32)Js::CompoundString::GetOffsetOfDirectCharLength(), TyUint32, m_func),
IR::IntConstOpnd::New(UINT32_MAX, TyUint32, m_func),
Js::OpCode::BrNeq_A, labelHelper, insertBeforeInstr);
// if lastBlockInfo.charLength < lastBlockInfo.charCapacity
IR::IndirOpnd *indirCharLength = IR::IndirOpnd::New(opndLeft->AsRegOpnd(), (int32)Js::CompoundString::GetOffsetOfLastBlockInfo() + (int32)Js::CompoundString::GetOffsetOfLastBlockInfoCharLength(), TyUint32, m_func);
IR::RegOpnd *charLengthOpnd = IR::RegOpnd::New(TyUint32, this->m_func);
InsertMove(charLengthOpnd, indirCharLength, insertBeforeInstr);
InsertCompareBranch(charLengthOpnd, IR::IndirOpnd::New(opndLeft->AsRegOpnd(), (int32)Js::CompoundString::GetOffsetOfLastBlockInfo() + (int32)Js::CompoundString::GetOffsetOfLastBlockInfoCharCapacity(), TyUint32, m_func), Js::OpCode::BrGe_A, labelHelper, insertBeforeInstr);
// load c = right->m_pszValue[0]
IR::RegOpnd *pszValue0Opnd = IR::RegOpnd::New(TyMachPtr, this->m_func);
IR::IndirOpnd *indirRightPszOpnd = IR::IndirOpnd::New(opndRight->AsRegOpnd(), offsetof(Js::JavascriptString, m_pszValue), TyMachPtr, this->m_func);
InsertMove(pszValue0Opnd, indirRightPszOpnd, insertBeforeInstr);
IR::RegOpnd *charResultOpnd = IR::RegOpnd::New(TyUint16, this->m_func);
InsertMove(charResultOpnd, IR::IndirOpnd::New(pszValue0Opnd, 0, TyUint16, this->m_func), insertBeforeInstr);
// lastBlockInfo.buffer[blockCharLength] = c;
IR::RegOpnd *baseOpnd = IR::RegOpnd::New(TyMachPtr, this->m_func);
InsertMove(baseOpnd, IR::IndirOpnd::New(opndLeft->AsRegOpnd(), (int32)Js::CompoundString::GetOffsetOfLastBlockInfo() + (int32)Js::CompoundString::GetOffsetOfLastBlockInfoBuffer(), TyMachPtr, m_func), insertBeforeInstr);
IR::IndirOpnd *indirBufferToStore = IR::IndirOpnd::New(baseOpnd, charLengthOpnd, (byte)Math::Log2(sizeof(char16)), TyUint16, m_func);
InsertMove(indirBufferToStore, charResultOpnd, insertBeforeInstr);
// left->m_charLength++
InsertAdd(false, indirLeftCharLengthOpnd, regLeftCharLengthOpnd, IR::IntConstOpnd::New(1, TyUint32, this->m_func), insertBeforeInstr);
// lastBlockInfo.charLength++
InsertAdd(false, indirCharLength, indirCharLength, IR::IntConstOpnd::New(1, TyUint32, this->m_func), insertBeforeInstr);
InsertBranch(Js::OpCode::Br, labelFallThrough, insertBeforeInstr);
return this->LowerBinaryHelperMemWithTemp(instr, IR::HelperOp_AddLeftDead);
}
IR::Instr *
Lowerer::LowerBinaryHelperMemWithTemp3(IR::Instr *instr, IR::JnHelperMethod helperMethod, IR::JnHelperMethod helperMethodWithTemp, IR::JnHelperMethod helperMethodLeftDead)
{
IR::Opnd *src1 = instr->GetSrc1();
if (src1->IsRegOpnd() && src1->AsRegOpnd()->m_isTempLastUse && !src1->GetValueType().IsNotString())
{
Assert(helperMethodLeftDead == IR::HelperOp_AddLeftDead);
return LowerAddLeftDeadForString(instr);
}
else
{
return this->LowerBinaryHelperMemWithTemp2(instr, helperMethod, helperMethodWithTemp);
}
}
StackSym *
Lowerer::GetTempNumberSym(IR::Opnd * opnd, bool isTempTransferred)
{
AssertMsg(opnd->IsRegOpnd(), "Expected regOpnd");
if (isTempTransferred)
{
StackSym * tempNumberSym = StackSym::New(TyMisc, m_func);
this->m_func->StackAllocate(tempNumberSym, sizeof(Js::JavascriptNumber));
return tempNumberSym;
}
StackSym * stackSym = opnd->AsRegOpnd()->m_sym;
StackSym * tempNumberSym = stackSym->m_tempNumberSym;
if (tempNumberSym == nullptr)
{
tempNumberSym = StackSym::New(TyMisc, m_func);
this->m_func->StackAllocate(tempNumberSym, sizeof(Js::JavascriptNumber));
stackSym->m_tempNumberSym = tempNumberSym;
}
return tempNumberSym;
}
void Lowerer::LowerProfiledLdElemI(IR::JitProfilingInstr *const instr)
{
Assert(instr);
/*
Var ProfilingHelpers::ProfiledLdElem(
const Var base,
const Var varIndex,
FunctionBody *const functionBody,
const ProfileId profileId,
bool didArrayAccessHelperCall,
bool bailedOutOnArraySpecialization)
*/
Func *const func = instr->m_func;
m_lowererMD.LoadHelperArgument(instr, IR::IntConstOpnd::New(false, TyInt8, func));
m_lowererMD.LoadHelperArgument(instr, IR::IntConstOpnd::New(false, TyInt8, func));
m_lowererMD.LoadHelperArgument(instr, IR::Opnd::CreateProfileIdOpnd(instr->profileId, func));
m_lowererMD.LoadHelperArgument(instr, CreateFunctionBodyOpnd(func));
IR::IndirOpnd *const indir = instr->UnlinkSrc1()->AsIndirOpnd();
IR::Opnd *const indexOpnd = indir->UnlinkIndexOpnd();
Assert(indexOpnd || indir->GetOffset() >= 0 && !Js::TaggedInt::IsOverflow(indir->GetOffset()));
m_lowererMD.LoadHelperArgument(
instr,
indexOpnd
? static_cast<IR::Opnd *>(indexOpnd)
: IR::AddrOpnd::New(Js::TaggedInt::ToVarUnchecked(indir->GetOffset()), IR::AddrOpndKindDynamicVar, func));
m_lowererMD.LoadHelperArgument(instr, indir->UnlinkBaseOpnd());
indir->Free(func);
instr->SetSrc1(IR::HelperCallOpnd::New(IR::HelperProfiledLdElem, func));
m_lowererMD.LowerCall(instr, 0);
}
void Lowerer::LowerProfiledStElemI(IR::JitProfilingInstr *const instr, const Js::PropertyOperationFlags flags)
{
Assert(instr);
/*
void ProfilingHelpers::ProfiledStElem(
const Var base,
const Var varIndex,
const Var value,
FunctionBody *const functionBody,
const ProfileId profileId,
const PropertyOperationFlags flags,
bool didArrayAccessHelperCall,
bool bailedOutOnArraySpecialization)
*/
Func *const func = instr->m_func;
IR::JnHelperMethod helper;
if(flags == Js::PropertyOperation_None)
{
helper = IR::HelperProfiledStElem_DefaultFlags;
}
else
{
helper = IR::HelperProfiledStElem;
m_lowererMD.LoadHelperArgument(instr, IR::IntConstOpnd::New(false, TyInt8, func));
m_lowererMD.LoadHelperArgument(instr, IR::IntConstOpnd::New(false, TyInt8, func));
m_lowererMD.LoadHelperArgument(instr, IR::IntConstOpnd::New(flags, TyInt32, func, true));
}
m_lowererMD.LoadHelperArgument(instr, IR::Opnd::CreateProfileIdOpnd(instr->profileId, func));
m_lowererMD.LoadHelperArgument(instr, CreateFunctionBodyOpnd(func));
m_lowererMD.LoadHelperArgument(instr, instr->UnlinkSrc1());
IR::IndirOpnd *const indir = instr->UnlinkDst()->AsIndirOpnd();
IR::Opnd *const indexOpnd = indir->UnlinkIndexOpnd();
Assert(indexOpnd || indir->GetOffset() >= 0 && !Js::TaggedInt::IsOverflow(indir->GetOffset()));
m_lowererMD.LoadHelperArgument(
instr,
indexOpnd
? static_cast<IR::Opnd *>(indexOpnd)
: IR::AddrOpnd::New(Js::TaggedInt::ToVarUnchecked(indir->GetOffset()), IR::AddrOpndKindDynamicVar, func));
m_lowererMD.LoadHelperArgument(instr, indir->UnlinkBaseOpnd());
indir->Free(func);
instr->SetSrc1(IR::HelperCallOpnd::New(helper, func));
m_lowererMD.LowerCall(instr, 0);
}
///----------------------------------------------------------------------------
///
/// Lowerer::LowerStElemI
///
///----------------------------------------------------------------------------
IR::Instr *
Lowerer::LowerStElemI(IR::Instr * instr, Js::PropertyOperationFlags flags, bool isHelper, IR::JnHelperMethod helperMethod)
{
IR::Instr *instrPrev = instr->m_prev;
if (instr->IsJitProfilingInstr())
{
Assert(!isHelper);
LowerProfiledStElemI(instr->AsJitProfilingInstr(), flags);
return instrPrev;
}
IR::Opnd *src1 = instr->GetSrc1();
IR::Opnd *dst = instr->GetDst();
IR::Opnd *newDst = nullptr;
IRType srcType = src1->GetType();
AssertMsg(dst->IsIndirOpnd(), "Expected indirOpnd on StElementI");
#if !FLOATVAR
if (dst->AsIndirOpnd()->GetBaseOpnd()->GetValueType().IsLikelyOptimizedTypedArray() && src1->IsRegOpnd())
{
// We allow the source of typedArray StElem to be marked as temp, since we just need the value,
// however if the array turns out to be a non-typed array, or the index isn't valid (the value is then stored as a property)
// the temp needs to be boxed if it is a float. The BoxStackNumber helper will box JavascriptNumbers
// which are on the stack.
// regVar = BoxStackNumber(src1, scriptContext)
IR::Instr *newInstr = IR::Instr::New(Js::OpCode::Call, this->m_func);
IR::RegOpnd *regVar = IR::RegOpnd::New(TyVar, this->m_func);
newInstr->SetDst(regVar);
newInstr->SetSrc1(src1);
instr->InsertBefore(newInstr);
LowerUnaryHelperMem(newInstr, IR::HelperBoxStackNumber);
// MOV src1, regVar
newInstr = IR::Instr::New(Js::OpCode::Ld_A, src1, regVar, this->m_func);
instr->InsertBefore(m_lowererMD.ChangeToAssign(newInstr));
}
#endif
if(instr->HasBailOutInfo())
{
IR::BailOutKind bailOutKind = instr->GetBailOutKind();
if(bailOutKind & IR::BailOutOnInvalidatedArrayHeadSegment)
{
Assert(!(bailOutKind & IR::BailOutOnMissingValue));
LowerBailOnInvalidatedArrayHeadSegment(instr, isHelper);
bailOutKind ^= IR::BailOutOnInvalidatedArrayHeadSegment;
Assert(!bailOutKind || instr->GetBailOutKind() == bailOutKind);
}
else if(bailOutKind & IR::BailOutOnMissingValue)
{
LowerBailOnCreatedMissingValue(instr, isHelper);
bailOutKind ^= IR::BailOutOnMissingValue;
Assert(!bailOutKind || instr->GetBailOutKind() == bailOutKind);
}
if(bailOutKind & IR::BailOutOnInvalidatedArrayLength)
{
LowerBailOnInvalidatedArrayLength(instr, isHelper);
bailOutKind ^= IR::BailOutOnInvalidatedArrayLength;
Assert(!bailOutKind || instr->GetBailOutKind() == bailOutKind);
}
if(bailOutKind & IR::BailOutConvertedNativeArray)
{
IR::LabelInstr *labelSkipBailOut = IR::LabelInstr::New(Js::OpCode::Label, m_func, isHelper);
instr->InsertAfter(labelSkipBailOut);
LowerOneBailOutKind(instr, IR::BailOutConvertedNativeArray, isHelper);
newDst = IR::RegOpnd::New(TyMachReg, m_func);
InsertTestBranch(newDst, newDst, Js::OpCode::BrEq_A, labelSkipBailOut, instr->m_next);
}
}
instr->UnlinkDst();
instr->UnlinkSrc1();
Assert(
helperMethod == IR::HelperOP_InitElemGetter ||
helperMethod == IR::HelperOP_InitElemSetter ||
helperMethod == IR::HelperOP_InitComputedProperty ||
helperMethod == IR::HelperOp_SetElementI ||
helperMethod == IR::HelperOp_InitClassMemberComputedName ||
helperMethod == IR::HelperOp_InitClassMemberGetComputedName ||
helperMethod == IR::HelperOp_InitClassMemberSetComputedName
);
IR::IndirOpnd* dstIndirOpnd = dst->AsIndirOpnd();
IR::Opnd *indexOpnd = dstIndirOpnd->UnlinkIndexOpnd();
if (indexOpnd && indexOpnd->GetType() != TyVar)
{
Assert(
helperMethod != IR::HelperOP_InitElemGetter &&
helperMethod != IR::HelperOP_InitElemSetter &&
helperMethod != IR::HelperOp_InitClassMemberGetComputedName &&
helperMethod != IR::HelperOp_InitClassMemberSetComputedName
);
if (indexOpnd->GetType() == TyInt32)
{
helperMethod =
srcType == TyVar ? IR::HelperOp_SetElementI_Int32 :
srcType == TyInt32 ? IR::HelperOp_SetNativeIntElementI_Int32 :
IR::HelperOp_SetNativeFloatElementI_Int32;
}
else if (indexOpnd->GetType() == TyUint32)
{
helperMethod =
srcType == TyVar ? IR::HelperOp_SetElementI_UInt32 :
srcType == TyInt32 ? IR::HelperOp_SetNativeIntElementI_UInt32 :
IR::HelperOp_SetNativeFloatElementI_UInt32;
}
else
{
Assert(FALSE);
}
}
else
{
if (indexOpnd == nullptr)
{
// No index; the offset identifies the element.
IntConstType offset = (IntConstType)dst->AsIndirOpnd()->GetOffset();
indexOpnd = IR::AddrOpnd::NewFromNumber(offset, m_func);
}
if (srcType != TyVar)
{
helperMethod =
srcType == TyInt32 ? IR::HelperOp_SetNativeIntElementI : IR::HelperOp_SetNativeFloatElementI;
}
}
if (srcType == TyFloat64)
{
m_lowererMD.LoadDoubleHelperArgument(instr, src1);
}
m_lowererMD.LoadHelperArgument(instr,
IR::IntConstOpnd::New(static_cast<IntConstType>(flags), IRType::TyInt32, m_func, true));
LoadScriptContext(instr);
if (srcType != TyFloat64)
{
m_lowererMD.LoadHelperArgument(instr, src1);
}
m_lowererMD.LoadHelperArgument(instr, indexOpnd);
IR::Opnd *baseOpnd = dst->AsIndirOpnd()->UnlinkBaseOpnd();
m_lowererMD.LoadHelperArgument(instr, baseOpnd);
dst->Free(this->m_func);
if (newDst)
{
instr->SetDst(newDst);
}
m_lowererMD.ChangeToHelperCall(instr, helperMethod, nullptr, nullptr, nullptr, isHelper);
return instrPrev;
}
///----------------------------------------------------------------------------
///
/// Lowerer::LowerLdElemI
///
///----------------------------------------------------------------------------
IR::Instr *
Lowerer::LowerLdElemI(IR::Instr * instr, IR::JnHelperMethod helperMethod, bool isHelper)
{
IR::Instr *instrPrev = instr->m_prev;
if(instr->IsJitProfilingInstr())
{
Assert(helperMethod == IR::HelperOp_GetElementI);
Assert(!isHelper);
LowerProfiledLdElemI(instr->AsJitProfilingInstr());
return instrPrev;
}
if (!isHelper && instr->DoStackArgsOpt())
{
IR::LabelInstr * labelLdElem = IR::LabelInstr::New(Js::OpCode::Label, instr->m_func);
// Pass in null for labelFallThru to only generate the LdHeapArgument call
GenerateFastArgumentsLdElemI(instr, nullptr);
instr->InsertBefore(labelLdElem);
instr->UnlinkSrc1();
instr->UnlinkDst();
Assert(instr->HasBailOutInfo() && instr->GetBailOutKind() == IR::BailOutKind::BailOnStackArgsOutOfActualsRange);
instr = GenerateBailOut(instr, nullptr, nullptr);
return instrPrev;
}
IR::Opnd *src1 = instr->UnlinkSrc1();
AssertMsg(src1->IsIndirOpnd(), "Expected indirOpnd");
IR::IndirOpnd *indirOpnd = src1->AsIndirOpnd();
bool loadScriptContext = true;
IRType dstType = instr->GetDst()->GetType();
IR::Opnd *indexOpnd = indirOpnd->UnlinkIndexOpnd();
if (indexOpnd && indexOpnd->GetType() != TyVar)
{
Assert(indexOpnd->GetType() == TyUint32 || indexOpnd->GetType() == TyInt32);
switch (helperMethod)
{
case IR::HelperOp_GetElementI:
if (indexOpnd->GetType() == TyUint32)
{
helperMethod =
dstType == TyVar ? IR::HelperOp_GetElementI_UInt32 :
dstType == TyInt32 ? IR::HelperOp_GetNativeIntElementI_UInt32 :
IR::HelperOp_GetNativeFloatElementI_UInt32;
}
else
{
helperMethod =
dstType == TyVar ? IR::HelperOp_GetElementI_Int32 :
dstType == TyInt32 ? IR::HelperOp_GetNativeIntElementI_Int32 :
IR::HelperOp_GetNativeFloatElementI_Int32;
}
break;
case IR::HelperOp_GetMethodElement:
Assert(dstType == TyVar);
helperMethod = indexOpnd->GetType() == TyUint32?
IR::HelperOp_GetMethodElement_UInt32 : IR::HelperOp_GetMethodElement_Int32;
break;
case IR::HelperOp_TypeofElem:
Assert(dstType == TyVar);
helperMethod = indexOpnd->GetType() == TyUint32?
IR::HelperOp_TypeofElem_UInt32 : IR::HelperOp_TypeofElem_Int32;
break;
default:
Assert(false);
}
}
else
{
if (indexOpnd == nullptr)
{
// No index; the offset identifies the element.
IntConstType offset = (IntConstType)src1->AsIndirOpnd()->GetOffset();
indexOpnd = IR::AddrOpnd::NewFromNumber(offset, m_func);
}
if (dstType != TyVar)
{
loadScriptContext = false;
helperMethod =
dstType == TyInt32 ? IR::HelperOp_GetNativeIntElementI : IR::HelperOp_GetNativeFloatElementI;
}
}
// Jitted loop bodies have volatile information about values created outside the loop, so don't update array creation site
// profile data from jitted loop bodies
if(!m_func->IsLoopBody())
{
const ValueType baseValueType(indirOpnd->GetBaseOpnd()->GetValueType());
if( baseValueType.IsLikelyObject() &&
baseValueType.GetObjectType() == ObjectType::Array &&
!baseValueType.HasIntElements())
{
switch(helperMethod)
{
case IR::HelperOp_GetElementI:
helperMethod =
baseValueType.HasFloatElements()
? IR::HelperOp_GetElementI_ExpectingNativeFloatArray
: IR::HelperOp_GetElementI_ExpectingVarArray;
break;
case IR::HelperOp_GetElementI_UInt32:
helperMethod =
baseValueType.HasFloatElements()
? IR::HelperOp_GetElementI_UInt32_ExpectingNativeFloatArray
: IR::HelperOp_GetElementI_UInt32_ExpectingVarArray;
break;
case IR::HelperOp_GetElementI_Int32:
helperMethod =
baseValueType.HasFloatElements()
? IR::HelperOp_GetElementI_Int32_ExpectingNativeFloatArray
: IR::HelperOp_GetElementI_Int32_ExpectingVarArray;
break;
}
}
}
if (loadScriptContext)
{
LoadScriptContext(instr);
}
m_lowererMD.LoadHelperArgument(instr, indexOpnd);
IR::Opnd *baseOpnd = indirOpnd->UnlinkBaseOpnd();
m_lowererMD.LoadHelperArgument(instr, baseOpnd);
src1->Free(this->m_func);
m_lowererMD.ChangeToHelperCall(instr, helperMethod, nullptr, nullptr, nullptr, isHelper);
return instrPrev;
}
void Lowerer::LowerLdLen(IR::Instr *const instr, const bool isHelper)
{
Assert(instr);
Assert(instr->m_opcode == Js::OpCode::LdLen_A);
// LdLen has persisted to this point for the sake of pre-lower opts.
// Turn it into a LdFld of the "length" property.
// This is normally a load of the internal "length" of an Array, so it probably doesn't benefit
// from inline caching.
if (instr->GetSrc1()->IsRegOpnd())
{
IR::RegOpnd * baseOpnd = instr->GetSrc1()->AsRegOpnd();
PropertySym* fieldSym = PropertySym::FindOrCreate(baseOpnd->m_sym->m_id, Js::PropertyIds::length, (uint32)-1, (uint)-1, PropertyKindData, m_func);
instr->ReplaceSrc1(IR::SymOpnd::New(fieldSym, TyVar, m_func));
}
LowerLdFld(instr, IR::HelperOp_GetProperty, IR::HelperOp_GetProperty, false, nullptr, isHelper);
}
IR::Instr* InsertMaskableMove(bool isStore, bool generateWriteBarrier, IR::Opnd* dst, IR::Opnd* src1, IR::Opnd* src2, IR::Opnd* indexOpnd, IR::Instr* insertBeforeInstr, Lowerer* lowerer)
{
Assert(insertBeforeInstr->m_func->GetJITFunctionBody()->IsAsmJsMode());
// Mask with the bounds check operand to avoid speculation issues
const bool usesFastArray = insertBeforeInstr->m_func->GetJITFunctionBody()->UsesWAsmJsFastVirtualBuffer();
IR::RegOpnd* mask = nullptr;
bool shouldMaskResult = false;
if (!usesFastArray)
{
bool shouldMask = isStore ? CONFIG_FLAG_RELEASE(PoisonTypedArrayStore) : CONFIG_FLAG_RELEASE(PoisonTypedArrayLoad);
if (shouldMask && indexOpnd != nullptr)
{
// indices in asmjs fit in 32 bits, but we need a mask
IR::RegOpnd* temp = IR::RegOpnd::New(indexOpnd->GetType(), insertBeforeInstr->m_func);
lowerer->InsertMove(temp, indexOpnd, insertBeforeInstr, false);
lowerer->InsertAdd(false, temp, temp, IR::IntConstOpnd::New((uint32)src1->GetSize() - 1, temp->GetType(), insertBeforeInstr->m_func, true), insertBeforeInstr);
// For native ints and vars, we do the masking after the load; we don't do this for
// floats and doubles because the conversion to and from fp regs is slow.
shouldMaskResult = (!isStore) && IRType_IsNativeIntOrVar(src1->GetType()) && TySize[dst->GetType()] <= TySize[TyMachReg];
// When we do post-load masking, we AND the mask with dst, so they need to have the
// same type, as otherwise we'll hit asserts later on. When we do pre-load masking,
// we AND the mask with the index component of the indir opnd for the move from the
// array, so we need to align with that type instead.
mask = IR::RegOpnd::New((shouldMaskResult ? dst : indexOpnd)->GetType(), insertBeforeInstr->m_func);
if (temp->GetSize() != mask->GetSize())
{
Assert(mask->GetSize() == MachPtr);
Assert(src2->GetType() == TyUint32);
temp = temp->UseWithNewType(TyMachPtr, insertBeforeInstr->m_func)->AsRegOpnd();
src2 = src2->UseWithNewType(TyMachPtr, insertBeforeInstr->m_func)->AsRegOpnd();
}
lowerer->InsertSub(false, mask, temp, src2, insertBeforeInstr);
lowerer->InsertShift(Js::OpCode::Shr_A, false, mask, mask, IR::IntConstOpnd::New(TySize[mask->GetType()] * 8 - 1, TyInt8, insertBeforeInstr->m_func), insertBeforeInstr);
// If we're not masking the result, we're masking the index
if (!shouldMaskResult)
{
lowerer->InsertAnd(indexOpnd, indexOpnd, mask, insertBeforeInstr);
}
}
}
IR::Instr* ret = lowerer->InsertMove(dst, src1, insertBeforeInstr, generateWriteBarrier);
if(!usesFastArray && shouldMaskResult)
{
// Mask the result if we didn't use the mask earlier to mask the index
lowerer->InsertAnd(dst, dst, mask, insertBeforeInstr);
}
return ret;
}
IR::Instr *
Lowerer::LowerLdArrViewElem(IR::Instr * instr)
{
#ifdef ASMJS_PLAT
Assert(m_func->GetJITFunctionBody()->IsAsmJsMode());
Assert(instr);
Assert(instr->m_opcode == Js::OpCode::LdArrViewElem);
IR::Instr * instrPrev = instr->m_prev;
IR::RegOpnd * indexOpnd = instr->GetSrc1()->AsIndirOpnd()->GetIndexOpnd();
int32 offset = instr->GetSrc1()->AsIndirOpnd()->GetOffset();
IR::Opnd * dst = instr->GetDst();
IR::Opnd * src1 = instr->GetSrc1();
IR::Opnd * src2 = instr->GetSrc2();
IR::Instr * done;
if (offset < 0)
{
IR::Opnd * oobValue = nullptr;
if(dst->IsFloat32())
{
oobValue = IR::MemRefOpnd::New(m_func->GetThreadContextInfo()->GetFloatNaNAddr(), TyFloat32, m_func);
}
else if(dst->IsFloat64())
{
oobValue = IR::MemRefOpnd::New(m_func->GetThreadContextInfo()->GetDoubleNaNAddr(), TyFloat64, m_func);
}
else
{
oobValue = IR::IntConstOpnd::New(0, dst->GetType(), m_func);
}
instr->ReplaceSrc1(oobValue);
if (src2)
{
instr->FreeSrc2();
}
return m_lowererMD.ChangeToAssign(instr);
}
if (indexOpnd || m_func->GetJITFunctionBody()->GetAsmJsInfo()->AccessNeedsBoundCheck((uint32)offset))
{
// CMP indexOpnd, src2(arrSize)
// JA $helper
// JMP $load
// $helper:
// MOV dst, 0
// JMP $done
// $load:
// MOV dst, src1([arrayBuffer + indexOpnd])
// $done:
Assert(!dst->IsFloat32() || src1->IsFloat32());
Assert(!dst->IsFloat64() || src1->IsFloat64());
done = m_lowererMD.LowerAsmJsLdElemHelper(instr);
}
else
{
// any access below 0x10000 is safe
instr->UnlinkDst();
instr->UnlinkSrc1();
if (src2)
{
instr->FreeSrc2();
}
done = instr;
}
InsertMaskableMove(false, true, dst, src1, src2, indexOpnd, done, this);
instr->Remove();
return instrPrev;
#else
Assert(UNREACHED);
return instr;
#endif
}
IR::Instr *
Lowerer::LowerWasmArrayBoundsCheck(IR::Instr * instr, IR::Opnd *addrOpnd)
{
uint32 offset = addrOpnd->AsIndirOpnd()->GetOffset();
// don't encode offset for wasm memory reads/writes
addrOpnd->AsIndirOpnd()->m_dontEncode = true;
// if offset/size overflow the max length, throw (this also saves us from having to do int64 math)
int64 constOffset = (int64)addrOpnd->GetSize() + (int64)offset;
if (constOffset >= Js::ArrayBuffer::MaxArrayBufferLength)
{
GenerateRuntimeError(instr, WASMERR_ArrayIndexOutOfRange, IR::HelperOp_WebAssemblyRuntimeError);
return instr;
}
else
{
return m_lowererMD.LowerWasmArrayBoundsCheck(instr, addrOpnd);
}
}
IR::Instr *
Lowerer::LowerLdArrViewElemWasm(IR::Instr * instr)
{
#ifdef ENABLE_WASM
Assert(m_func->GetJITFunctionBody()->IsWasmFunction());
Assert(instr);
Assert(instr->m_opcode == Js::OpCode::LdArrViewElemWasm);
IR::Instr * instrPrev = instr->m_prev;
IR::Opnd * dst = instr->GetDst();
IR::Opnd * src1 = instr->GetSrc1();
Assert(!dst->IsFloat32() || src1->IsFloat32());
Assert(!dst->IsFloat64() || src1->IsFloat64());
IR::Instr * done = LowerWasmArrayBoundsCheck(instr, src1);
IR::Instr* newMove = InsertMaskableMove(false, true, dst, src1, instr->GetSrc2(), src1->AsIndirOpnd()->GetIndexOpnd(), done, this);
if (m_func->GetJITFunctionBody()->UsesWAsmJsFastVirtualBuffer())
{
// We need to have an AV when accessing out of bounds memory even if the dst is not used
// Make sure LinearScan doesn't dead store this instruction
newMove->hasSideEffects = true;
}
instr->Remove();
return instrPrev;
#else
Assert(UNREACHED);
return instr;
#endif
}
IR::Instr *
Lowerer::LowerMemset(IR::Instr * instr, IR::RegOpnd * helperRet)
{
IR::Opnd * dst = instr->UnlinkDst();
IR::Opnd * src1 = instr->UnlinkSrc1();
Assert(dst->IsIndirOpnd());
IR::Opnd *baseOpnd = dst->AsIndirOpnd()->UnlinkBaseOpnd();
IR::Opnd *indexOpnd = dst->AsIndirOpnd()->UnlinkIndexOpnd();
IR::Opnd *sizeOpnd = instr->UnlinkSrc2();
Assert(baseOpnd);
Assert(sizeOpnd);
Assert(indexOpnd);
IR::JnHelperMethod helperMethod = IR::HelperOp_Memset;
IR::Instr *instrPrev = nullptr;
if (src1->IsRegOpnd() && !src1->IsVar())
{
IR::RegOpnd* varOpnd = IR::RegOpnd::New(TyVar, instr->m_func);
instrPrev = IR::Instr::New(Js::OpCode::ToVar, varOpnd, src1, instr->m_func);
instr->InsertBefore(instrPrev);
src1 = varOpnd;
}
instr->SetDst(helperRet);
LoadScriptContext(instr);
m_lowererMD.LoadHelperArgument(instr, sizeOpnd);
m_lowererMD.LoadHelperArgument(instr, src1);
m_lowererMD.LoadHelperArgument(instr, indexOpnd);
m_lowererMD.LoadHelperArgument(instr, baseOpnd);
m_lowererMD.ChangeToHelperCall(instr, helperMethod);
dst->Free(m_func);
return instrPrev;
}
IR::Instr *
Lowerer::LowerMemcopy(IR::Instr * instr, IR::RegOpnd * helperRet)
{
IR::Opnd * dst = instr->UnlinkDst();
IR::Opnd * src = instr->UnlinkSrc1();
Assert(dst->IsIndirOpnd());
Assert(src->IsIndirOpnd());
IR::Opnd *dstBaseOpnd = dst->AsIndirOpnd()->UnlinkBaseOpnd();
IR::Opnd *dstIndexOpnd = dst->AsIndirOpnd()->UnlinkIndexOpnd();
IR::Opnd *srcBaseOpnd = src->AsIndirOpnd()->UnlinkBaseOpnd();
IR::Opnd *srcIndexOpnd = src->AsIndirOpnd()->UnlinkIndexOpnd();
IR::Opnd *sizeOpnd = instr->UnlinkSrc2();
Assert(sizeOpnd);
Assert(dstBaseOpnd);
Assert(dstIndexOpnd);
Assert(srcBaseOpnd);
Assert(srcIndexOpnd);
IR::JnHelperMethod helperMethod = IR::HelperOp_Memcopy;
instr->SetDst(helperRet);
LoadScriptContext(instr);
m_lowererMD.LoadHelperArgument(instr, sizeOpnd);
m_lowererMD.LoadHelperArgument(instr, srcIndexOpnd);
m_lowererMD.LoadHelperArgument(instr, srcBaseOpnd);
m_lowererMD.LoadHelperArgument(instr, dstIndexOpnd);
m_lowererMD.LoadHelperArgument(instr, dstBaseOpnd);
m_lowererMD.ChangeToHelperCall(instr, helperMethod);
dst->Free(m_func);
src->Free(m_func);
return nullptr;
}
IR::Instr *
Lowerer::LowerMemOp(IR::Instr * instr)
{
Assert(instr->m_opcode == Js::OpCode::Memset || instr->m_opcode == Js::OpCode::Memcopy);
IR::Instr *instrPrev = instr->m_prev;
IR::RegOpnd* helperRet = IR::RegOpnd::New(TyInt8, instr->m_func);
const bool isHelper = false;
AssertMsg(instr->HasBailOutInfo(), "Expected bailOut on MemOp instruction");
if (instr->HasBailOutInfo())
{
IR::BailOutKind bailOutKind = instr->GetBailOutKind();
if (bailOutKind & IR::BailOutOnInvalidatedArrayHeadSegment)
{
Assert(!(bailOutKind & IR::BailOutOnMissingValue));
LowerBailOnInvalidatedArrayHeadSegment(instr, isHelper);
bailOutKind ^= IR::BailOutOnInvalidatedArrayHeadSegment;
Assert(!bailOutKind || instr->GetBailOutKind() == bailOutKind);
}
else if (bailOutKind & IR::BailOutOnMissingValue)
{
LowerBailOnCreatedMissingValue(instr, isHelper);
bailOutKind ^= IR::BailOutOnMissingValue;
Assert(!bailOutKind || instr->GetBailOutKind() == bailOutKind);
}
if (bailOutKind & IR::BailOutOnInvalidatedArrayLength)
{
LowerBailOnInvalidatedArrayLength(instr, isHelper);
bailOutKind ^= IR::BailOutOnInvalidatedArrayLength;
Assert(!bailOutKind || instr->GetBailOutKind() == bailOutKind);
}
AssertMsg(bailOutKind & IR::BailOutOnMemOpError, "Expected BailOutOnMemOpError on MemOp instruction");
if (bailOutKind & IR::BailOutOnMemOpError)
{
// Insert or get continue label
IR::LabelInstr *const skipBailOutLabel = instr->GetOrCreateContinueLabel(isHelper);
Func *const func = instr->m_func;
LowerOneBailOutKind(instr, IR::BailOutOnMemOpError, isHelper);
IR::Instr *const insertBeforeInstr = instr->m_next;
// test helperRet, helperRet
// jz $skipBailOut
InsertCompareBranch(
helperRet,
IR::IntConstOpnd::New(0, TyInt8, func),
Js::OpCode::BrNeq_A,
skipBailOutLabel,
insertBeforeInstr);
// (Bail out with IR::BailOutOnMemOpError)
// $skipBailOut:
bailOutKind ^= IR::BailOutOnMemOpError;
Assert(!bailOutKind || instr->GetBailOutKind() == bailOutKind);
}
instr->ClearBailOutInfo();
}
IR::Instr* newInstrPrev = nullptr;
if (instr->m_opcode == Js::OpCode::Memset)
{
newInstrPrev = LowerMemset(instr, helperRet);
}
else if (instr->m_opcode == Js::OpCode::Memcopy)
{
newInstrPrev = LowerMemcopy(instr, helperRet);
}
if (newInstrPrev != nullptr)
{
instrPrev = newInstrPrev;
}
return instrPrev;
}
IR::Instr*
Lowerer::LowerStAtomicsWasm(IR::Instr* instr)
{
#ifdef ENABLE_WASM
Assert(m_func->GetJITFunctionBody()->IsWasmFunction());
Assert(instr);
Assert(instr->m_opcode == Js::OpCode::StAtomicWasm);
IR::Instr * instrPrev = instr->m_prev;
IR::Opnd * dst = instr->GetDst();
IR::Opnd * src1 = instr->GetSrc1();
Assert(IRType_IsNativeInt(dst->GetType()));
IR::Instr * done = LowerWasmArrayBoundsCheck(instr, dst);
m_lowererMD.LowerAtomicStore(dst, src1, done);
instr->Remove();
return instrPrev;
#else
Assert(UNREACHED);
return instr;
#endif
}
IR::Instr * Lowerer::LowerLdAtomicsWasm(IR::Instr * instr)
{
#ifdef ENABLE_WASM
Assert(m_func->GetJITFunctionBody()->IsWasmFunction());
Assert(instr);
Assert(instr->m_opcode == Js::OpCode::LdAtomicWasm);
IR::Instr * instrPrev = instr->m_prev;
IR::Opnd * dst = instr->GetDst();
IR::Opnd * src1 = instr->GetSrc1();
Assert(IRType_IsNativeInt(dst->GetType()));
IR::Instr * done = LowerWasmArrayBoundsCheck(instr, src1);
m_lowererMD.LowerAtomicLoad(dst, src1, done);
instr->Remove();
return instrPrev;
#else
Assert(UNREACHED);
return instr;
#endif
}
IR::Instr *
Lowerer::LowerStArrViewElem(IR::Instr * instr)
{
#ifdef ASMJS_PLAT
Assert(m_func->GetJITFunctionBody()->IsAsmJsMode());
Assert(instr);
Assert(instr->m_opcode == Js::OpCode::StArrViewElem);
IR::Instr * instrPrev = instr->m_prev;
IR::Opnd * dst = instr->GetDst();
IR::Opnd * src1 = instr->GetSrc1();
IR::Opnd * src2 = instr->GetSrc2();
// type of dst is the type of array
IR::RegOpnd * indexOpnd = dst->AsIndirOpnd()->GetIndexOpnd();
int32 offset = dst->AsIndirOpnd()->GetOffset();
Assert(!dst->IsFloat32() || src1->IsFloat32());
Assert(!dst->IsFloat64() || src1->IsFloat64());
Assert(!dst->IsInt64() || src1->IsInt64());
IR::Instr * done;
if (m_func->GetJITFunctionBody()->IsWasmFunction())
{
done = LowerWasmArrayBoundsCheck(instr, dst);
}
else if (offset < 0)
{
instr->Remove();
return instrPrev;
}
else if (indexOpnd || m_func->GetJITFunctionBody()->GetAsmJsInfo()->AccessNeedsBoundCheck((uint32)offset))
{
// CMP indexOpnd, src2(arrSize)
// JA $helper
// JMP $store
// $helper:
// JMP $done
// $store:
// MOV dst([arrayBuffer + indexOpnd]), src1
// $done:
done = m_lowererMD.LowerAsmJsStElemHelper(instr);
}
else
{
// any constant access below 0x10000 is safe, as that is the min heap size
instr->UnlinkDst();
instr->UnlinkSrc1();
done = instr;
if (src2)
{
instr->FreeSrc2();
}
}
// wasm memory buffer is not recycler allocated, so we shouldn't generate write barrier
InsertMaskableMove(true, false, dst, src1, src2, indexOpnd, done, this);
instr->Remove();
return instrPrev;
#else
Assert(UNREACHED);
return instr;
#endif
}
IR::Instr *
Lowerer::LowerArrayDetachedCheck(IR::Instr * instr)
{
// TEST isDetached, isDetached
// JE Done
// Helper:
// CALL Js::Throw::OutOfMemory
// Done:
Assert(m_func->GetJITFunctionBody()->IsAsmJsMode());
IR::Instr * instrPrev = instr->m_prev;
IR::Opnd * isDetachedOpnd = instr->UnlinkSrc1();
Assert(isDetachedOpnd->IsIndirOpnd() || isDetachedOpnd->IsMemRefOpnd());
IR::LabelInstr * doneLabel = InsertLabel(false, instr->m_next);
IR::LabelInstr * helperLabel = InsertLabel(true, instr);
InsertTestBranch(isDetachedOpnd, isDetachedOpnd, Js::OpCode::BrNotNeq_A, doneLabel, helperLabel);
m_lowererMD.ChangeToHelperCall(instr, IR::HelperOp_OutOfMemoryError);
return instrPrev;
}
///----------------------------------------------------------------------------
///
/// Lowerer::LowerDeleteElemI
///
///----------------------------------------------------------------------------
IR::Instr *
Lowerer::LowerDeleteElemI(IR::Instr * instr, bool strictMode)
{
IR::Instr *instrPrev;
IR::Opnd *src1 = instr->UnlinkSrc1();
AssertMsg(src1->IsIndirOpnd(), "Expected indirOpnd on DeleteElementI");
Js::PropertyOperationFlags propertyOperationFlag = Js::PropertyOperation_None;
if (strictMode)
{
propertyOperationFlag = Js::PropertyOperation_StrictMode;
}
instrPrev = instr->m_prev;
IR::JnHelperMethod helperMethod = IR::HelperOp_DeleteElementI;
IR::Opnd *indexOpnd = src1->AsIndirOpnd()->UnlinkIndexOpnd();
if (indexOpnd)
{
if (indexOpnd->GetType() == TyInt32)
{
helperMethod = IR::HelperOp_DeleteElementI_Int32;
}
else if (indexOpnd->GetType() == TyUint32)
{
helperMethod = IR::HelperOp_DeleteElementI_UInt32;
}
else
{
Assert(indexOpnd->GetType() == TyVar);
}
}
else
{
// No index; the offset identifies the element.
IntConstType offset = (IntConstType)src1->AsIndirOpnd()->GetOffset();
indexOpnd = IR::AddrOpnd::NewFromNumber(offset, m_func);
}
m_lowererMD.LoadHelperArgument(instr, IR::IntConstOpnd::New((IntConstType)propertyOperationFlag, TyInt32, m_func, true));
LoadScriptContext(instr);
m_lowererMD.LoadHelperArgument(instr, indexOpnd);
IR::Opnd *baseOpnd = src1->AsIndirOpnd()->UnlinkBaseOpnd();
m_lowererMD.LoadHelperArgument(instr, baseOpnd);
src1->Free(this->m_func);
m_lowererMD.ChangeToHelperCall(instr, helperMethod);
return instrPrev;
}
IR::Opnd *
Lowerer::GetForInEnumeratorFieldOpnd(IR::Opnd * forInEnumeratorOpnd, uint fieldOffset, IRType type)
{
if (forInEnumeratorOpnd->IsSymOpnd())
{
IR::SymOpnd * symOpnd = forInEnumeratorOpnd->AsSymOpnd();
return IR::SymOpnd::New(symOpnd->GetStackSym(), symOpnd->m_offset + fieldOffset, type, this->m_func);
}
Assert(forInEnumeratorOpnd->IsIndirOpnd());
IR::IndirOpnd * indirOpnd = forInEnumeratorOpnd->AsIndirOpnd();
return IR::IndirOpnd::New(indirOpnd->GetBaseOpnd(), indirOpnd->GetOffset() + fieldOffset, type, this->m_func);
}
void
Lowerer::GenerateFastBrBReturn(IR::Instr * instr)
{
Assert(instr->m_opcode == Js::OpCode::BrOnEmpty || instr->m_opcode == Js::OpCode::BrOnNotEmpty);
AssertMsg(instr->GetSrc1() != nullptr && instr->GetSrc2() == nullptr, "Expected 1 src opnds on BrB");
IR::Opnd * forInEnumeratorOpnd = instr->GetSrc1();
IR::LabelInstr * labelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true);
IR::LabelInstr * loopBody = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
// CMP forInEnumerator->canUseJitFastPath, 0
// JEQ $helper
IR::Opnd * canUseJitFastPathOpnd = GetForInEnumeratorFieldOpnd(forInEnumeratorOpnd, Js::ForInObjectEnumerator::GetOffsetOfCanUseJitFastPath(), TyInt8);
InsertCompareBranch(canUseJitFastPathOpnd, IR::IntConstOpnd::New(0, TyInt8, this->m_func), Js::OpCode::BrEq_A, labelHelper, instr);
// MOV objectOpnd, forInEnumerator->enumerator.object
// MOV cachedDataTypeOpnd, forInEnumerator->enumerator.cachedDataType
// CMP cachedDataTypeOpnd, objectOpnd->type
// JNE $helper
IR::RegOpnd * objectOpnd = IR::RegOpnd::New(TyMachPtr, this->m_func);
InsertMove(objectOpnd,
GetForInEnumeratorFieldOpnd(forInEnumeratorOpnd, Js::ForInObjectEnumerator::GetOffsetOfEnumeratorObject(), TyMachPtr), instr);
IR::RegOpnd * cachedDataTypeOpnd = IR::RegOpnd::New(TyMachPtr, this->m_func);
InsertMove(cachedDataTypeOpnd,
GetForInEnumeratorFieldOpnd(forInEnumeratorOpnd, Js::ForInObjectEnumerator::GetOffsetOfEnumeratorInitialType(), TyMachPtr), instr);
InsertCompareBranch(cachedDataTypeOpnd, IR::IndirOpnd::New(objectOpnd, Js::DynamicObject::GetOffsetOfType(), TyMachPtr, this->m_func),
Js::OpCode::BrNeq_A, labelHelper, instr);
// MOV cachedDataOpnd, forInEnumeratorOpnd->enumerator.cachedData
// MOV enumeratedCountOpnd, forInEnumeratorOpnd->enumerator.enumeratedCount
// CMP enumeratedCountOpnd, cachedDataOpnd->cachedCount
// JLT $loopBody
IR::RegOpnd * cachedDataOpnd = IR::RegOpnd::New(TyMachPtr, m_func);
InsertMove(cachedDataOpnd,
GetForInEnumeratorFieldOpnd(forInEnumeratorOpnd, Js::ForInObjectEnumerator::GetOffsetOfEnumeratorCachedData(), TyMachPtr), instr);
IR::RegOpnd * enumeratedCountOpnd = IR::RegOpnd::New(TyUint32, m_func);
InsertMove(enumeratedCountOpnd,
GetForInEnumeratorFieldOpnd(forInEnumeratorOpnd, Js::ForInObjectEnumerator::GetOffsetOfEnumeratorEnumeratedCount(), TyUint32), instr);
InsertCompareBranch(enumeratedCountOpnd,
IR::IndirOpnd::New(cachedDataOpnd, Js::DynamicObjectPropertyEnumerator::GetOffsetOfCachedDataCachedCount(), TyUint32, this->m_func),
Js::OpCode::BrLt_A, loopBody, instr);
// CMP cacheData.completed, 0
// JNE $loopEnd
// JMP $helper
IR::LabelInstr * labelAfter = instr->GetOrCreateContinueLabel();
InsertCompareBranch(
IR::IndirOpnd::New(cachedDataOpnd, Js::DynamicObjectPropertyEnumerator::GetOffsetOfCachedDataCompleted(), TyInt8, this->m_func),
IR::IntConstOpnd::New(0, TyInt8, this->m_func),
Js::OpCode::BrNeq_A, instr->m_opcode == Js::OpCode::BrOnNotEmpty ? labelAfter : instr->AsBranchInstr()->GetTarget(), instr);
InsertBranch(Js::OpCode::Br, labelHelper, instr);
// $loopBody:
instr->InsertBefore(loopBody);
IR::Opnd * opndDst = instr->GetDst(); // ForIn result propertyString
Assert(opndDst->IsRegOpnd());
// MOV stringsOpnd, cachedData->strings
// MOV opndDst, stringsOpnd[enumeratedCount]
IR::RegOpnd * stringsOpnd = IR::RegOpnd::New(TyMachPtr, m_func);
InsertMove(stringsOpnd,
IR::IndirOpnd::New(cachedDataOpnd, Js::DynamicObjectPropertyEnumerator::GetOffsetOfCachedDataStrings(), TyMachPtr, this->m_func), instr);
InsertMove(opndDst,
IR::IndirOpnd::New(stringsOpnd, enumeratedCountOpnd, m_lowererMD.GetDefaultIndirScale(), TyVar, this->m_func), instr);
// MOV indexesOpnd, cachedData->indexes
// MOV objectIndexOpnd, indexesOpnd[enumeratedCount]
// MOV forInEnumeratorOpnd->enumerator.objectIndex, objectIndexOpnd
IR::RegOpnd * indexesOpnd = IR::RegOpnd::New(TyMachPtr, m_func);
InsertMove(indexesOpnd,
IR::IndirOpnd::New(cachedDataOpnd, Js::DynamicObjectPropertyEnumerator::GetOffsetOfCachedDataIndexes(), TyMachPtr, this->m_func), instr);
IR::RegOpnd * objectIndexOpnd = IR::RegOpnd::New(TyUint32, m_func);
InsertMove(objectIndexOpnd,
IR::IndirOpnd::New(indexesOpnd, enumeratedCountOpnd, IndirScale4, TyUint32, this->m_func), instr);
InsertMove(GetForInEnumeratorFieldOpnd(forInEnumeratorOpnd, Js::ForInObjectEnumerator::GetOffsetOfEnumeratorObjectIndex(), TyUint32),
objectIndexOpnd, instr);
// INC enumeratedCountOpnd
// MOV forInEnumeratorOpnd->enumerator.enumeratedCount, enumeratedCountOpnd
InsertAdd(false, enumeratedCountOpnd, enumeratedCountOpnd, IR::IntConstOpnd::New(1, TyUint32, this->m_func), instr);
InsertMove(GetForInEnumeratorFieldOpnd(forInEnumeratorOpnd, Js::ForInObjectEnumerator::GetOffsetOfEnumeratorEnumeratedCount(), TyUint32),
enumeratedCountOpnd, instr);
// We know result propertyString (opndDst) != NULL
InsertBranch(Js::OpCode::Br, instr->m_opcode == Js::OpCode::BrOnNotEmpty ? instr->AsBranchInstr()->GetTarget() : labelAfter, instr);
// $helper
instr->InsertBefore(labelHelper);
// $after
}
///----------------------------------------------------------------------------
///
/// Lowerer::LowerBrB - lower 1-operand (boolean) conditional branch
///
///----------------------------------------------------------------------------
IR::Instr *
Lowerer::LowerBrBReturn(IR::Instr * instr, IR::JnHelperMethod helperMethod, bool isHelper)
{
IR::Instr * instrPrev;
IR::Instr * instrCall;
IR::HelperCallOpnd * opndHelper;
IR::Opnd * opndDst;
AssertMsg(instr->GetSrc1() != nullptr && instr->GetSrc2() == nullptr, "Expected 1 src opnds on BrB");
Assert(instr->m_opcode == Js::OpCode::BrOnEmpty || instr->m_opcode == Js::OpCode::BrOnNotEmpty);
IR::RegOpnd * forInEnumeratorRegOpnd = GenerateForInEnumeratorLoad(instr->UnlinkSrc1(), instr);
instrPrev = m_lowererMD.LoadHelperArgument(instr, forInEnumeratorRegOpnd);
// Generate helper call to convert the unknown operand to boolean
opndHelper = IR::HelperCallOpnd::New(helperMethod, this->m_func);
opndDst = instr->UnlinkDst();
instrCall = IR::Instr::New(Js::OpCode::Call, opndDst, opndHelper, this->m_func);
instr->InsertBefore(instrCall);
instrCall = m_lowererMD.LowerCall(instrCall, 0);
// Branch on the result of the call
instr->m_opcode = (instr->m_opcode == Js::OpCode::BrOnNotEmpty? Js::OpCode::BrTrue_A : Js::OpCode::BrFalse_A);
instr->SetSrc1(opndDst);
IR::Instr *loweredInstr;
loweredInstr = this->LowerCondBranchCheckBailOut(instr->AsBranchInstr(), instrCall, isHelper);
#if DBG
if (isHelper)
{
if (!loweredInstr->IsBranchInstr())
{
loweredInstr = loweredInstr->GetNextBranchOrLabel();
}
if (loweredInstr->IsBranchInstr())
{
loweredInstr->AsBranchInstr()->m_isHelperToNonHelperBranch = true;
}
}
#endif
return instrPrev;
}
///----------------------------------------------------------------------------
///
/// Lowerer::LowerMultiBr
/// - Lowers the instruction for dictionary look up(string case arms)
///
///----------------------------------------------------------------------------
IR::Instr* Lowerer::LowerMultiBr(IR::Instr * instr, IR::JnHelperMethod helperMethod)
{
IR::Instr * instrPrev = instr->m_prev;
IR::Instr * instrCall;
IR::HelperCallOpnd * opndHelper;
IR::Opnd * opndSrc;
IR::Opnd * opndDst;
StackSym * symDst;
AssertMsg(instr->GetSrc1() != nullptr && instr->GetSrc2() == nullptr, "Expected 1 src opnd on BrB");
// Push the args in reverse order.
// The end and start labels for the function are used to guarantee
// that the dictionary jump destinations haven't been tampered with, so we
// will always jump to some location within this function
IR::LabelOpnd * endFuncOpnd = IR::LabelOpnd::New(m_func->EnsureFuncEndLabel(), m_func);
m_lowererMD.LoadHelperArgument(instr, endFuncOpnd);
IR::LabelOpnd * startFuncOpnd = IR::LabelOpnd::New(m_func->EnsureFuncStartLabel(), m_func);
m_lowererMD.LoadHelperArgument(instr, startFuncOpnd);
//Load the address of the dictionary pair- Js::StringDictionaryWrapper
auto dictionary = instr->AsBranchInstr()->AsMultiBrInstr()->GetBranchDictionary();
if (this->m_func->IsOOPJIT())
{
auto dictionaryOffset = NativeCodeData::GetDataTotalOffset(dictionary);
auto addressRegOpnd = IR::RegOpnd::New(TyMachPtr, m_func);
Lowerer::InsertLea(addressRegOpnd,
IR::IndirOpnd::New(IR::RegOpnd::New(m_func->GetTopFunc()->GetNativeCodeDataSym(), TyVar, m_func), dictionaryOffset, TyMachPtr,
#if DBG
NativeCodeData::GetDataDescription(dictionary, this->m_func->m_alloc),
#endif
this->m_func, true), instr);
this->addToLiveOnBackEdgeSyms->Set(m_func->GetTopFunc()->GetNativeCodeDataSym()->m_id);
m_lowererMD.LoadHelperArgument(instr, addressRegOpnd);
}
else
{
IR::AddrOpnd* nativestringDictionaryOpnd = IR::AddrOpnd::New(dictionary, IR::AddrOpndKindDynamicMisc, this->m_func);
m_lowererMD.LoadHelperArgument(instr, nativestringDictionaryOpnd);
}
//Load the String passed in the Switch expression for look up - JavascriptString
opndSrc = instr->UnlinkSrc1();
m_lowererMD.LoadHelperArgument(instr, opndSrc);
// Generate helper call for dictionary lookup.
opndHelper = IR::HelperCallOpnd::New(helperMethod, this->m_func);
symDst = StackSym::New(TyMachPtr,this->m_func);
opndDst = IR::RegOpnd::New(symDst, TyMachPtr, this->m_func);
instrCall = IR::Instr::New(Js::OpCode::Call, opndDst, opndHelper, this->m_func);
instr->InsertBefore(instrCall);
instrCall = m_lowererMD.LowerCall(instrCall, 0);
instr->SetSrc1(instrCall->GetDst());
instr->m_opcode = LowererMD::MDMultiBranchOpcode;
return instrPrev;
}
void
Lowerer::LowerJumpTableMultiBranch(IR::MultiBranchInstr * multiBrInstr, IR::RegOpnd * indexOpnd)
{
Func * func = this->m_func;
IR::Opnd * opndDst = IR::RegOpnd::New(TyMachPtr, func);
//Move the native address of the jump table to a register
IR::LabelInstr * nativeJumpTableLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func);
nativeJumpTableLabel->m_isDataLabel = true;
IR::LabelOpnd * nativeJumpTable = IR::LabelOpnd::New(nativeJumpTableLabel, m_func);
IR::RegOpnd * nativeJumpTableReg = IR::RegOpnd::New(TyMachPtr, func);
InsertMove(nativeJumpTableReg, nativeJumpTable, multiBrInstr);
BranchJumpTableWrapper * branchJumpTable = multiBrInstr->GetBranchJumpTable();
AssertMsg(branchJumpTable->labelInstr == nullptr, "Should not be already assigned");
branchJumpTable->labelInstr = nativeJumpTableLabel;
//Indirect addressing @ target location in the jump table.
//MOV eax, [nativeJumpTableReg + (offset * indirScale)]
BYTE indirScale = this->m_lowererMD.GetDefaultIndirScale();
IR::Opnd * opndSrc = IR::IndirOpnd::New(nativeJumpTableReg, indexOpnd, indirScale, TyMachReg, this->m_func);
IR::Instr * indirInstr = InsertMove(opndDst, opndSrc, multiBrInstr);
//MultiBr eax
multiBrInstr->SetSrc1(indirInstr->GetDst());
//Jump to the address at the target location in the jump table
multiBrInstr->m_opcode = LowererMD::MDMultiBranchOpcode;
}
///----------------------------------------------------------------------------
///
/// Lowerer::LowerMultiBr
/// - Lowers the instruction for jump table(consecutive integer case arms)
///
///----------------------------------------------------------------------------
IR::Instr* Lowerer::LowerMultiBr(IR::Instr * instr)
{
IR::Instr * instrPrev = instr->m_prev;
AssertMsg(instr->GetSrc1() != nullptr && instr->GetSrc2() == nullptr, "Expected 1 src opnd on BrB");
AssertMsg(instr->IsBranchInstr() && instr->AsBranchInstr()->IsMultiBranch(), "Bad Instruction Lowering Call to LowerMultiBr()");
IR::MultiBranchInstr * multiBrInstr = instr->AsBranchInstr()->AsMultiBrInstr();
IR::RegOpnd * offset = instr->UnlinkSrc1()->AsRegOpnd();
LowerJumpTableMultiBranch(multiBrInstr, offset);
return instrPrev;
}
IR::Instr* Lowerer::LowerBrBMem(IR::Instr * instr, IR::JnHelperMethod helperMethod)
{
IR::Instr * instrPrev;
IR::Instr * instrCall;
IR::HelperCallOpnd * opndHelper;
IR::Opnd * opndSrc;
IR::Opnd * opndDst;
StackSym * symDst;
AssertMsg(instr->GetSrc1() != nullptr && instr->GetSrc2() == nullptr, "Expected 1 src opnds on BrB");
instrPrev = LoadScriptContext(instr);
opndSrc = instr->UnlinkSrc1();
m_lowererMD.LoadHelperArgument(instr, opndSrc);
// Generate helper call to convert the unknown operand to boolean
opndHelper = IR::HelperCallOpnd::New(helperMethod, this->m_func);
symDst = StackSym::New(TyVar, this->m_func);
opndDst = IR::RegOpnd::New(symDst, TyVar, this->m_func);
instrCall = IR::Instr::New(Js::OpCode::Call, opndDst, opndHelper, this->m_func);
instr->InsertBefore(instrCall);
instrCall = m_lowererMD.LowerCall(instrCall, 0);
// Branch on the result of the call
instr->SetSrc1(opndDst);
m_lowererMD.LowerCondBranch(instr);
return instrPrev;
}
IR::Instr* Lowerer::LowerBrOnObject(IR::Instr * instr, IR::JnHelperMethod helperMethod)
{
IR::Instr * instrPrev;
IR::Instr * instrCall;
IR::HelperCallOpnd * opndHelper;
IR::Opnd * opndSrc;
IR::Opnd * opndDst;
StackSym * symDst;
AssertMsg(instr->GetSrc1() != nullptr && instr->GetSrc2() == nullptr, "Expected 1 src opnds on BrB");
opndSrc = instr->UnlinkSrc1();
instrPrev = m_lowererMD.LoadHelperArgument(instr, opndSrc);
// Generate helper call to check if the operand's type is object
opndHelper = IR::HelperCallOpnd::New(helperMethod, this->m_func);
symDst = StackSym::New(TyVar, this->m_func);
opndDst = IR::RegOpnd::New(symDst, TyVar, this->m_func);
instrCall = IR::Instr::New(Js::OpCode::Call, opndDst, opndHelper, this->m_func);
instr->InsertBefore(instrCall);
instrCall = m_lowererMD.LowerCall(instrCall, 0);
// Branch on the result of the call
instr->SetSrc1(opndDst);
m_lowererMD.LowerCondBranch(instr);
return instrPrev;
}
IR::Instr * Lowerer::LowerBrOnClassConstructor(IR::Instr * instr, IR::JnHelperMethod helperMethod)
{
IR::Instr * instrPrev;
IR::Instr * instrCall;
IR::HelperCallOpnd * opndHelper;
IR::Opnd * opndSrc;
IR::Opnd * opndDst;
StackSym * symDst;
AssertMsg(instr->GetSrc1() != nullptr && instr->GetSrc2() == nullptr, "Expected 1 src opnds on BrB");
opndSrc = instr->UnlinkSrc1();
instrPrev = m_lowererMD.LoadHelperArgument(instr, opndSrc);
// Generate helper call to check if the operand's type is object
opndHelper = IR::HelperCallOpnd::New(helperMethod, this->m_func);
symDst = StackSym::New(TyVar, this->m_func);
opndDst = IR::RegOpnd::New(symDst, TyVar, this->m_func);
instrCall = IR::Instr::New(Js::OpCode::Call, opndDst, opndHelper, this->m_func);
instr->InsertBefore(instrCall);
instrCall = m_lowererMD.LowerCall(instrCall, 0);
// Branch on the result of the call
instr->SetSrc1(opndDst);
m_lowererMD.LowerCondBranch(instr);
return instrPrev;
}
IR::Instr *
Lowerer::LowerEqualityCompare(IR::Instr* instr, IR::JnHelperMethod helper)
{
IR::Instr * instrPrev = instr->m_prev;
bool needHelper = true;
bool fNoLower = false;
bool isStrictCompare = instr->m_opcode == Js::OpCode::CmSrEq_A || instr->m_opcode == Js::OpCode::CmSrNeq_A;
if (instr->GetSrc1()->IsFloat())
{
Assert(instr->GetSrc1()->GetType() == instr->GetSrc2()->GetType());
this->m_lowererMD.GenerateFastCmXxR8(instr);
}
else if (PHASE_OFF(Js::BranchFastPathPhase, m_func) || !m_func->DoFastPaths())
{
LowerBinaryHelperMem(instr, helper);
}
else if (TryGenerateFastBrOrCmTypeOf(instr, &instrPrev, instr->IsNeq(), &fNoLower))
{
if (!fNoLower)
{
LowerBinaryHelperMem(instr, helper);
}
}
else if (isStrictCompare && TryGenerateFastCmSrXx(instr))
{
}
else
{
if (GenerateFastBrOrCmString(instr))
{
LowerBinaryHelperMem(instr, helper);
}
else if (isStrictCompare && GenerateFastBrOrCmEqDefinite(instr, helper, &needHelper, false, false))
{
if (needHelper)
{
LowerBinaryHelperMem(instr, helper);
}
}
else if(GenerateFastCmEqLikely(instr, &needHelper, false) || GenerateFastEqBoolInt(instr, &needHelper, false))
{
if (needHelper)
{
if (isStrictCompare)
{
LowerStrictBrOrCm(instr, helper, false, false /* isBranch */, true);
}
else
{
LowerBinaryHelperMem(instr, helper);
}
}
}
else if (!m_lowererMD.GenerateFastCmXxTaggedInt(instr, false))
{
if (isStrictCompare)
{
LowerStrictBrOrCm(instr, helper, false, false /* isBranch */, false);
}
else
{
LowerBinaryHelperMem(instr, helper);
}
}
}
if (!needHelper)
{
instr->Remove();
}
return instrPrev;
}
IR::Instr *
Lowerer::LowerEqualityBranch(IR::Instr* instr, IR::JnHelperMethod helper)
{
IR::RegOpnd *srcReg1 = instr->GetSrc1()->IsRegOpnd() ? instr->GetSrc1()->AsRegOpnd() : nullptr;
IR::RegOpnd *srcReg2 = instr->GetSrc2()->IsRegOpnd() ? instr->GetSrc2()->AsRegOpnd() : nullptr;
IR::Instr * instrPrev = instr->m_prev;
bool fNoLower = false;
const bool noFastPath = PHASE_OFF(Js::BranchFastPathPhase, m_func) || !m_func->DoFastPaths();
if (instr->GetSrc1()->IsFloat())
{
Assert(instr->GetSrc1()->GetType() == instr->GetSrc2()->GetType());
m_lowererMD.LowerToFloat(instr);
return instrPrev;
}
if (instr->GetSrc2()->IsFloat())
{
Assert(instr->GetSrc1()->GetType() == instr->GetSrc2()->GetType());
instr->SwapOpnds();
m_lowererMD.LowerToFloat(instr);
return instrPrev;
}
if (noFastPath)
{
LowerBrCMem(instr, helper, true, false /*isHelper*/);
return instrPrev;
}
if (TryGenerateFastBrOrCmTypeOf(instr, &instrPrev, instr->IsNeq(), &fNoLower))
{
if (!fNoLower)
{
LowerBrCMem(instr, helper, false, false /*isHelper*/);
}
return instrPrev;
}
bool done = false;
bool isStrictCompare = false;
switch(instr->m_opcode)
{
case Js::OpCode::BrNeq_A:
case Js::OpCode::BrNotEq_A:
done = TryGenerateFastBrNeq(instr);
break;
case Js::OpCode::BrEq_A:
case Js::OpCode::BrNotNeq_A:
done = TryGenerateFastBrEq(instr);
break;
case Js::OpCode::BrSrEq_A:
case Js::OpCode::BrSrNotNeq_A:
case Js::OpCode::BrSrNeq_A:
case Js::OpCode::BrSrNotEq_A:
isStrictCompare = true;
done = TryGenerateFastBrSrXx(instr, srcReg1, srcReg2, &instrPrev, noFastPath);
break;
default:
Assume(UNREACHED);
}
if (done)
{
return instrPrev;
}
bool needHelper = true;
bool hasStrFastPath = false;
if (GenerateFastBrOrCmString(instr))
{
hasStrFastPath = true;
LowerBrCMem(instr, helper, false, true);
}
else if (isStrictCompare && GenerateFastBrOrCmEqDefinite(instr, helper, &needHelper, true, hasStrFastPath))
{
if (needHelper)
{
LowerBrCMem(instr, helper, true /*noMathFastPath*/, hasStrFastPath);
}
}
else if (GenerateFastBrEqLikely(instr->AsBranchInstr(), &needHelper, hasStrFastPath) || GenerateFastEqBoolInt(instr, &needHelper, hasStrFastPath))
{
if (needHelper)
{
if (isStrictCompare)
{
LowerStrictBrOrCm(instr, helper, false, true /* isBranch */, true);
}
else
{
LowerBrCMem(instr, helper, false, hasStrFastPath);
}
}
}
else if (needHelper)
{
if (isStrictCompare)
{
LowerStrictBrOrCm(instr, helper, false, true /* isBranch */, false);
}
else
{
LowerBrCMem(instr, helper, false, hasStrFastPath);
}
}
if (!needHelper)
{
if (instr->AsBranchInstr()->GetTarget()->m_isLoopTop)
{
LowerBrCMem(instr, helper, false, hasStrFastPath);
}
else
{
instr->Remove();
}
}
return instrPrev;
}
// Generate fast path for StrictEquals for objects that are not GlobalObject, HostDispatch or External to be pointer comparison
IR::Instr *
Lowerer::LowerStrictBrOrCm(IR::Instr * instr, IR::JnHelperMethod helperMethod, bool noMathFastPath, bool isBranch, bool isHelper)
{
IR::Instr * instrPrev = instr->m_prev;
IR::LabelInstr * labelHelper = nullptr;
IR::LabelInstr * labelFallThrough = nullptr;
IR::LabelInstr * labelBranchSuccess = nullptr;
IR::LabelInstr * labelBranchFailure = nullptr;
LibraryValue successValueType = ValueInvalid;
LibraryValue failureValueType = ValueInvalid;
bool isEqual = !instr->IsNeq();
IR::Opnd * src1 = instr->GetSrc1();
IR::Opnd * src2 = instr->GetSrc2();
AssertMsg(src1 != nullptr && src2 != nullptr, "Expected 2 src opnds on BrC");
labelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true);
if (!noMathFastPath)
{
labelFallThrough = instr->GetOrCreateContinueLabel(isHelper);
if (!isBranch)
{
labelBranchSuccess = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, isHelper);
labelBranchFailure = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, isHelper);
successValueType = isEqual ? LibraryValue::ValueTrue : LibraryValue::ValueFalse;
failureValueType = isEqual ? LibraryValue::ValueFalse : LibraryValue::ValueTrue;
}
else
{
labelBranchSuccess = isEqual ? instr->AsBranchInstr()->GetTarget() : labelFallThrough;
labelBranchFailure = isEqual ? labelFallThrough : instr->AsBranchInstr()->GetTarget();
}
if (src1->IsEqual(src2))
{
if (instr->GetSrc1()->GetValueType().IsNotFloat())
{
if (!isBranch)
{
InsertMove(instr->GetDst(), LoadLibraryValueOpnd(instr, successValueType), instr);
InsertBranch(Js::OpCode::Br, labelFallThrough, instr);
}
else
{
IR::BranchInstr * branch = IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, labelBranchSuccess, this->m_func);
instr->InsertBefore(branch);
}
instr->Remove();
return instrPrev;
}
#if !FLOATVAR
m_lowererMD.GenerateObjectTest(src1->AsRegOpnd(), instr, labelHelper);
IR::RegOpnd *src1TypeReg = IR::RegOpnd::New(TyMachReg, this->m_func);
Lowerer::InsertMove(src1TypeReg, IR::IndirOpnd::New(src1->AsRegOpnd(), Js::RecyclableObject::GetOffsetOfType(), TyMachReg, this->m_func), instr);
// MOV src1TypeIdReg, [src1TypeReg + offset(typeId)]
IR::RegOpnd *src1TypeIdReg = IR::RegOpnd::New(TyInt32, this->m_func);
Lowerer::InsertMove(src1TypeIdReg, IR::IndirOpnd::New(src1TypeReg, Js::Type::GetOffsetOfTypeId(), TyInt32, this->m_func), instr);
// CMP src1TypeIdReg, TypeIds_Number
// JEQ $helper
IR::IntConstOpnd *numberTypeId = IR::IntConstOpnd::New(Js::TypeIds_Number, TyInt32, this->m_func, true);
InsertCompareBranch(src1TypeIdReg, numberTypeId, Js::OpCode::BrEq_A, labelHelper, instr);
#else
m_lowererMD.GenerateObjectTest(src1->AsRegOpnd(), instr, labelHelper);
#endif
IR::BranchInstr * branch = IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, labelBranchSuccess, this->m_func);
instr->InsertBefore(branch);
}
else
{
m_lowererMD.GenerateObjectTest(src1->AsRegOpnd(), instr, labelHelper);
#if !FLOATVAR
IR::RegOpnd *src1TypeReg = IR::RegOpnd::New(TyMachReg, this->m_func);
Lowerer::InsertMove(src1TypeReg, IR::IndirOpnd::New(src1->AsRegOpnd(), Js::RecyclableObject::GetOffsetOfType(), TyMachReg, this->m_func), instr);
// MOV src1TypeIdReg, [src1TypeReg + offset(typeId)]
IR::RegOpnd *src1TypeIdReg = IR::RegOpnd::New(TyInt32, this->m_func);
Lowerer::InsertMove(src1TypeIdReg, IR::IndirOpnd::New(src1TypeReg, Js::Type::GetOffsetOfTypeId(), TyInt32, this->m_func), instr);
// CMP src1TypeIdReg, TypeIds_Number
// JEQ $helper
IR::IntConstOpnd *numberTypeId = IR::IntConstOpnd::New(Js::TypeIds_Number, TyInt32, this->m_func, true);
InsertCompareBranch(src1TypeIdReg, numberTypeId, Js::OpCode::BrEq_A, labelHelper, instr);
#endif
// CMP src1, src2 - Ptr comparison
// JEQ $branchSuccess
InsertCompareBranch(src1, src2, Js::OpCode::BrEq_A, labelBranchSuccess, instr);
#if FLOATVAR
IR::RegOpnd *src1TypeReg = IR::RegOpnd::New(TyMachReg, this->m_func);
Lowerer::InsertMove(src1TypeReg, IR::IndirOpnd::New(src1->AsRegOpnd(), Js::RecyclableObject::GetOffsetOfType(), TyMachReg, this->m_func), instr);
// MOV src1TypeIdReg, [src1TypeReg + offset(typeId)]
IR::RegOpnd *src1TypeIdReg = IR::RegOpnd::New(TyInt32, this->m_func);
Lowerer::InsertMove(src1TypeIdReg, IR::IndirOpnd::New(src1TypeReg, Js::Type::GetOffsetOfTypeId(), TyInt32, this->m_func), instr);
#endif
// CMP src1TypeIdReg, TypeIds_HostDispatch
// JLE $helper (le condition covers string, int64, uint64, hostdispatch, as well as undefined, null, boolean)
IR::IntConstOpnd *hostDispatchTypeId = IR::IntConstOpnd::New(Js::TypeIds_HostDispatch, TyInt32, this->m_func, true);
InsertCompareBranch(src1TypeIdReg, hostDispatchTypeId, Js::OpCode::BrLe_A, labelHelper, instr);
// CMP src1TypeIdReg, TypeIds_GlobalObject
// JE $helper
IR::IntConstOpnd *globalObjectTypeId = IR::IntConstOpnd::New(Js::TypeIds_GlobalObject, TyInt32, this->m_func, true);
InsertCompareBranch(src1TypeIdReg, globalObjectTypeId, Js::OpCode::BrEq_A, labelHelper, instr);
// TEST src1TypeReg->flags, TypeFlagMask_EngineExternal
// JE $helper
IR::Opnd *flags = IR::IndirOpnd::New(src1TypeReg, Js::Type::GetOffsetOfFlags(), TyInt8, this->m_func);
InsertTestBranch(flags, IR::IntConstOpnd::New(TypeFlagMask_EngineExternal, TyInt8, this->m_func), Js::OpCode::BrNeq_A, labelHelper, instr);
if (src2->IsRegOpnd())
{
m_lowererMD.GenerateObjectTest(src2->AsRegOpnd(), instr, labelHelper);
// MOV src2TypeReg, [src2 + offset(type)]
// TEST [src2TypeReg + offset(flags)], TypeFlagMask_EngineExternal
// JE $helper
IR::RegOpnd *src2TypeReg = IR::RegOpnd::New(TyMachReg, this->m_func);
IR::IndirOpnd *src2Type = IR::IndirOpnd::New(src2->AsRegOpnd(), Js::RecyclableObject::GetOffsetOfType(), TyMachReg, this->m_func);
Lowerer::InsertMove(src2TypeReg, src2Type, instr);
IR::Opnd *src2Flags = IR::IndirOpnd::New(src2TypeReg, Js::Type::GetOffsetOfFlags(), TyInt8, this->m_func);
InsertTestBranch(src2Flags, IR::IntConstOpnd::New(TypeFlagMask_EngineExternal, TyInt8, this->m_func), Js::OpCode::BrNeq_A, labelHelper, instr);
}
// JMP $done
IR::BranchInstr * branch = IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, labelBranchFailure, this->m_func);
instr->InsertBefore(branch);
}
if (!isBranch)
{
instr->InsertBefore(labelBranchSuccess);
InsertMove(instr->GetDst(), LoadLibraryValueOpnd(instr, successValueType), instr);
InsertBranch(Js::OpCode::Br, labelFallThrough, instr);
instr->InsertBefore(labelBranchFailure);
InsertMove(instr->GetDst(), LoadLibraryValueOpnd(instr, failureValueType), instr);
InsertBranch(Js::OpCode::Br, labelFallThrough, instr);
}
}
instr->InsertBefore(labelHelper);
if (isBranch)
{
LowerBrCMem(instr, helperMethod, true, true);
}
else
{
LowerBinaryHelperMem(instr, helperMethod);
}
return instrPrev;
}
IR::Instr *
Lowerer::LowerBrCMem(IR::Instr * instr, IR::JnHelperMethod helperMethod, bool noMathFastPath, bool isHelper)
{
IR::Instr * instrPrev = instr->m_prev;
IR::Instr * instrCall;
IR::HelperCallOpnd * opndHelper;
IR::Opnd * opndSrc;
IR::Opnd * opndDst;
StackSym * symDst;
bool inverted = false;
AssertMsg(instr->GetSrc1() != nullptr && instr->GetSrc2() != nullptr, "Expected 2 src opnds on BrC");
if (!noMathFastPath && !this->GenerateFastCondBranch(instr->AsBranchInstr(), &isHelper))
{
return instrPrev;
}
// Push the args in reverse order.
const bool loadScriptContext = !(helperMethod == IR::HelperOp_StrictEqualString || helperMethod == IR::HelperOp_StrictEqualEmptyString);
const bool loadArg2 = !(helperMethod == IR::HelperOp_StrictEqualEmptyString);
if (helperMethod == IR::HelperOp_NotEqual)
{
// Op_NotEqual() returns !Op_Equal(). It is faster to call Op_Equal() directly.
helperMethod = IR::HelperOp_Equal;
instr->AsBranchInstr()->Invert();
inverted = true;
}
else if(helperMethod == IR::HelperOp_NotStrictEqual)
{
// Op_NotStrictEqual() returns !Op_StrictEqual(). It is faster to call Op_StrictEqual() directly.
helperMethod = IR::HelperOp_StrictEqual;
instr->AsBranchInstr()->Invert();
inverted = true;
}
if (loadScriptContext)
LoadScriptContext(instr);
opndSrc = instr->UnlinkSrc2();
if (loadArg2)
m_lowererMD.LoadHelperArgument(instr, opndSrc);
opndSrc = instr->UnlinkSrc1();
m_lowererMD.LoadHelperArgument(instr, opndSrc);
// Generate helper call to compare the source operands.
opndHelper = IR::HelperCallOpnd::New(helperMethod, this->m_func);
symDst = StackSym::New(TyMachReg, this->m_func);
opndDst = IR::RegOpnd::New(symDst, TyMachReg, this->m_func);
instrCall = IR::Instr::New(Js::OpCode::Call, opndDst, opndHelper, this->m_func);
instr->InsertBefore(instrCall);
instrCall = m_lowererMD.LowerCall(instrCall, 0);
switch (instr->m_opcode)
{
case Js::OpCode::BrNotEq_A:
case Js::OpCode::BrNotNeq_A:
case Js::OpCode::BrSrNotEq_A:
case Js::OpCode::BrSrNotNeq_A:
if (instr->HasBailOutInfo())
{
instr->GetBailOutInfo()->isInvertedBranch = true;
}
break;
case Js::OpCode::BrNotGe_A:
case Js::OpCode::BrNotGt_A:
case Js::OpCode::BrNotLe_A:
case Js::OpCode::BrNotLt_A:
inverted = true;
break;
}
// Branch if the result is "true".
instr->SetSrc1(opndDst);
instr->m_opcode = (inverted ? Js::OpCode::BrFalse_A : Js::OpCode::BrTrue_A);
this->LowerCondBranchCheckBailOut(instr->AsBranchInstr(), instrCall, !noMathFastPath && isHelper);
return instrPrev;
}
IR::Instr *
Lowerer::LowerBrFncApply(IR::Instr * instr, IR::JnHelperMethod helperMethod) {
IR::Instr * instrPrev = instr->m_prev;
IR::Instr * instrCall;
IR::HelperCallOpnd * opndHelper;
IR::Opnd * opndSrc;
IR::Opnd * opndDst;
StackSym * symDst;
AssertMsg(instr->GetSrc1() != nullptr, "Expected 1 src opnd on BrFncApply");
LoadScriptContext(instr);
opndSrc = instr->UnlinkSrc1();
m_lowererMD.LoadHelperArgument(instr, opndSrc);
// Generate helper call to compare the source operands.
opndHelper = IR::HelperCallOpnd::New(helperMethod, this->m_func);
symDst = StackSym::New(TyMachReg, this->m_func);
opndDst = IR::RegOpnd::New(symDst, TyMachReg, this->m_func);
instrCall = IR::Instr::New(Js::OpCode::Call, opndDst, opndHelper, this->m_func);
instr->InsertBefore(instrCall);
instrCall = m_lowererMD.LowerCall(instrCall, 0);
// Branch if the result is "true".
instr->SetSrc1(opndDst);
instr->m_opcode = Js::OpCode::BrTrue_A;
m_lowererMD.LowerCondBranch(instr);
return instrPrev;
}
///----------------------------------------------------------------------------
///
/// Lowerer::LowerBrProperty - lower branch-on-has/no-property
///
///----------------------------------------------------------------------------
IR::Instr *
Lowerer::LowerBrProperty(IR::Instr * instr, IR::JnHelperMethod helper)
{
IR::Instr * instrPrev;
IR::Instr * instrCall;
IR::HelperCallOpnd * opndHelper;
IR::Opnd * opndSrc;
IR::Opnd * opndDst;
opndSrc = instr->UnlinkSrc1();
AssertMsg(opndSrc->IsSymOpnd() && opndSrc->AsSymOpnd()->m_sym->IsPropertySym(),
"Expected propertySym as src of BrProperty");
instrPrev = LoadScriptContext(instr);
this->LoadPropertySymAsArgument(instr, opndSrc);
opndHelper = IR::HelperCallOpnd::New(helper, this->m_func);
opndDst = IR::RegOpnd::New(StackSym::New(TyMachReg, this->m_func), TyMachReg, this->m_func);
instrCall = IR::Instr::New(Js::OpCode::Call, opndDst, opndHelper, this->m_func);
instr->InsertBefore(instrCall);
instrCall = m_lowererMD.LowerCall(instrCall, 0);
// Branch on the result of the call
instr->SetSrc1(opndDst);
switch (instr->m_opcode)
{
case Js::OpCode::BrOnHasProperty:
instr->m_opcode = Js::OpCode::BrTrue_A;
break;
case Js::OpCode::BrOnNoProperty:
instr->m_opcode = Js::OpCode::BrFalse_A;
break;
default:
AssertMsg(0, "Unknown opcode on BrProperty branch");
break;
}
this->LowerCondBranchCheckBailOut(instr->AsBranchInstr(), instrCall, false);
return instrPrev;
}
///----------------------------------------------------------------------------
///
/// Lowerer::LowerElementUndefined
///
///----------------------------------------------------------------------------
IR::Instr *
Lowerer::LowerElementUndefined(IR::Instr * instr, IR::JnHelperMethod helper)
{
IR::Opnd *dst = instr->UnlinkDst();
AssertMsg(dst->IsSymOpnd() && dst->AsSymOpnd()->m_sym->IsPropertySym(), "Expected fieldSym as dst of Ld Undefined");
// Pass the property sym to store to
this->LoadPropertySymAsArgument(instr, dst);
m_lowererMD.ChangeToHelperCall(instr, helper);
return instr;
}
IR::Instr *
Lowerer::LowerElementUndefinedMem(IR::Instr * instr, IR::JnHelperMethod helper)
{
// Pass script context
IR::Instr * instrPrev = LoadScriptContext(instr);
this->LowerElementUndefined(instr, helper);
return instrPrev;
}
IR::Instr *
Lowerer::LowerLdElemUndef(IR::Instr * instr)
{
if (this->m_func->GetJITFunctionBody()->IsEval())
{
return LowerElementUndefinedMem(instr, IR::HelperOp_LdElemUndefDynamic);
}
else
{
return LowerElementUndefined(instr, IR::HelperOp_LdElemUndef);
}
}
///----------------------------------------------------------------------------
///
/// Lowerer::LowerElementUndefinedScoped
///
///----------------------------------------------------------------------------
IR::Instr *
Lowerer::LowerElementUndefinedScoped(IR::Instr * instr, IR::JnHelperMethod helper)
{
IR::Instr * instrPrev = instr->m_prev;
// Pass the default instance
IR::Opnd *src = instr->UnlinkSrc1();
m_lowererMD.LoadHelperArgument(instr, src);
// Pass the property sym to store to
IR::Opnd * dst = instr->UnlinkDst();
AssertMsg(dst->IsSymOpnd() && dst->AsSymOpnd()->m_sym->IsPropertySym(), "Expected fieldSym as dst of Ld Undefined Scoped");
this->LoadPropertySymAsArgument(instr, dst);
m_lowererMD.ChangeToHelperCall(instr, helper);
return instrPrev;
}
IR::Instr *
Lowerer::LowerElementUndefinedScopedMem(IR::Instr * instr, IR::JnHelperMethod helper)
{
// Pass script context
IR::Instr * instrPrev = LoadScriptContext(instr);
this->LowerElementUndefinedScoped(instr, helper);
return instrPrev;
}
void
Lowerer::LowerStLoopBodyCount(IR::Instr* instr)
{
intptr_t header = m_func->m_workItem->GetLoopHeaderAddr();
IR::MemRefOpnd *loopBodyCounterOpnd = IR::MemRefOpnd::New((BYTE*)(header) + Js::LoopHeader::GetOffsetOfProfiledLoopCounter(), TyUint32, this->m_func);
instr->SetDst(loopBodyCounterOpnd);
instr->ReplaceSrc1(instr->GetSrc1()->AsRegOpnd()->UseWithNewType(TyUint32, this->m_func));
IR::AutoReuseOpnd autoReuse(loopBodyCounterOpnd, this->m_func);
m_lowererMD.ChangeToAssign(instr);
return;
}
#if !FLOATVAR
IR::Instr *
Lowerer::LowerStSlotBoxTemp(IR::Instr *stSlot)
{
// regVar = BoxStackNumber(src, scriptContext)
IR::RegOpnd * regSrc = stSlot->UnlinkSrc1()->AsRegOpnd();
IR::Instr * instr = IR::Instr::New(Js::OpCode::Call, this->m_func);
IR::RegOpnd *regVar = IR::RegOpnd::New(TyVar, this->m_func);
instr->SetDst(regVar);
instr->SetSrc1(regSrc);
stSlot->InsertBefore(instr);
this->LowerUnaryHelperMem(instr, IR::HelperBoxStackNumber);
stSlot->SetSrc1(regVar);
return this->LowerStSlot(stSlot);
}
#endif
IR::Opnd *
Lowerer::CreateOpndForSlotAccess(IR::Opnd * opnd)
{
IR::SymOpnd * symOpnd = opnd->AsSymOpnd();
PropertySym * dstSym = symOpnd->m_sym->AsPropertySym();
if (!m_func->IsLoopBody() &&
m_func->DoStackFrameDisplay() &&
(dstSym->m_stackSym == m_func->GetLocalClosureSym() || dstSym->m_stackSym == m_func->GetLocalFrameDisplaySym()))
{
// Stack closure syms are made to look like slot accesses for the benefit of GlobOpt, so that it can do proper
// copy prop and implicit call bailout. But what we really want is local stack load/store.
// Don't do this for loop body, though, since we don't have the value saved on the stack.
IR::SymOpnd * closureSym = IR::SymOpnd::New(dstSym->m_stackSym, 0, TyMachReg, this->m_func);
closureSym->GetStackSym()->m_isClosureSym = true;
return closureSym;
}
int32 offset = dstSym->m_propertyId;
if (!m_func->GetJITFunctionBody()->IsAsmJsMode())
{
offset = offset * TySize[opnd->GetType()];
}
#ifdef ASMJS_PLAT
if (m_func->IsTJLoopBody())
{
offset = offset - m_func->GetJITFunctionBody()->GetAsmJsInfo()->GetTotalSizeInBytes();
}
#endif
IR::IndirOpnd *indirOpnd = IR::IndirOpnd::New(symOpnd->CreatePropertyOwnerOpnd(m_func),
offset , opnd->GetType(), this->m_func);
return indirOpnd;
}
IR::Instr* Lowerer::AddSlotArrayCheck(PropertySym *propertySym, IR::Instr* instr)
{
if (propertySym->m_stackSym != m_func->GetLocalClosureSym() || PHASE_OFF(Js::ClosureRangeCheckPhase, m_func))
{
return instr->m_prev;
}
IR::Instr *instrDef = propertySym->m_stackSym->m_instrDef;
bool doDynamicCheck = this->m_func->IsLoopBody();
bool insertSlotArrayCheck = false;
uint32 slotId = (uint32)propertySym->m_propertyId;
if (instrDef)
{
switch (instrDef->m_opcode)
{
case Js::OpCode::NewScopeSlots:
case Js::OpCode::NewStackScopeSlots:
case Js::OpCode::NewScopeSlotsWithoutPropIds:
{
IR::Opnd *allocOpnd = allocOpnd = instrDef->GetSrc1();
uint32 allocCount = allocOpnd->AsIntConstOpnd()->AsUint32();
if (slotId >= allocCount)
{
Js::Throw::FatalInternalError();
}
break;
}
case Js::OpCode::ArgIn_A:
break;
case Js::OpCode::LdSlot:
case Js::OpCode::LdSlotArr:
{
if (doDynamicCheck && slotId > Js::ScopeSlots::FirstSlotIndex)
{
insertSlotArrayCheck = true;
}
break;
}
case Js::OpCode::SlotArrayCheck:
{
uint32 currentSlotId = instrDef->GetSrc2()->AsIntConstOpnd()->AsInt32();
if (slotId > currentSlotId)
{
instrDef->ReplaceSrc2(IR::IntConstOpnd::New(slotId, TyUint32, m_func));
}
break;
}
default:
Js::Throw::FatalInternalError();
}
}
if (insertSlotArrayCheck)
{
IR::Instr *insertInstr = instrDef->m_next;
IR::RegOpnd *dstOpnd = instrDef->UnlinkDst()->AsRegOpnd();
IR::Instr *checkInstr = IR::Instr::New(Js::OpCode::SlotArrayCheck, dstOpnd, m_func);
dstOpnd = IR::RegOpnd::New(TyVar, m_func);
instrDef->SetDst(dstOpnd);
checkInstr->SetSrc1(dstOpnd);
// Attach the slot ID to the check instruction.
IR::IntConstOpnd *slotIdOpnd = IR::IntConstOpnd::New(slotId, TyUint32, m_func);
checkInstr->SetSrc2(slotIdOpnd);
insertInstr->InsertBefore(checkInstr);
}
return instr->m_prev;
}
IR::Instr *
Lowerer::LowerStSlot(IR::Instr *instr)
{
// StSlot stores the nth Var in the buffer pointed to by the property sym's stack sym.
IR::Opnd * dstOpnd = instr->UnlinkDst();
AssertMsg(dstOpnd, "Expected dst opnd on StSlot");
IR::Opnd * dstNew = this->CreateOpndForSlotAccess(dstOpnd);
dstOpnd->Free(this->m_func);
instr->SetDst(dstNew);
instr = m_lowererMD.ChangeToWriteBarrierAssign(instr, this->m_func);
return instr;
}
IR::Instr *
Lowerer::LowerStSlotChkUndecl(IR::Instr *instrStSlot)
{
Assert(instrStSlot->GetSrc2() != nullptr);
// Src2 is required only to avoid dead store false positives during GlobOpt.
instrStSlot->FreeSrc2();
IR::Opnd *dstOpnd = this->CreateOpndForSlotAccess(instrStSlot->GetDst());
IR::Instr *instr = this->LowerStSlot(instrStSlot);
this->GenUndeclChk(instr, dstOpnd);
return instr;
}
void Lowerer::LowerProfileLdSlot(IR::Opnd *const valueOpnd, Func *const ldSlotFunc, const Js::ProfileId profileId, IR::Instr *const insertBeforeInstr)
{
Assert(valueOpnd);
Assert(profileId != Js::Constants::NoProfileId);
Assert(insertBeforeInstr);
Func *const irFunc = insertBeforeInstr->m_func;
m_lowererMD.LoadHelperArgument(insertBeforeInstr, IR::Opnd::CreateProfileIdOpnd(profileId, irFunc));
m_lowererMD.LoadHelperArgument(insertBeforeInstr, CreateFunctionBodyOpnd(ldSlotFunc));
m_lowererMD.LoadHelperArgument(insertBeforeInstr, valueOpnd);
IR::Instr *const callInstr = IR::Instr::New(Js::OpCode::Call, irFunc);
callInstr->SetSrc1(IR::HelperCallOpnd::New(IR::HelperProfileLdSlot, irFunc));
insertBeforeInstr->InsertBefore(callInstr);
m_lowererMD.LowerCall(callInstr, 0);
}
void
Lowerer::LowerLdSlot(IR::Instr *instr)
{
IR::Opnd * srcOpnd = instr->UnlinkSrc1();
AssertMsg(srcOpnd, "Expected src opnd on LdSlot");
IR::Opnd * srcNew = this->CreateOpndForSlotAccess(srcOpnd);
srcOpnd->Free(this->m_func);
instr->SetSrc1(srcNew);
m_lowererMD.ChangeToAssign(instr);
}
IR::Instr *
Lowerer::LowerChkUndecl(IR::Instr *instr)
{
IR::Instr *instrPrev = instr->m_prev;
this->GenUndeclChk(instr, instr->GetSrc1());
instr->Remove();
return instrPrev;
}
void
Lowerer::GenUndeclChk(IR::Instr *instrInsert, IR::Opnd *opnd)
{
IR::LabelInstr *labelContinue = IR::LabelInstr::New(Js::OpCode::Label, m_func);
InsertCompareBranch(
opnd,
LoadLibraryValueOpnd(instrInsert, LibraryValue::ValueUndeclBlockVar),
Js::OpCode::BrNeq_A, labelContinue, instrInsert);
IR::LabelInstr *labelThrow = IR::LabelInstr::New(Js::OpCode::Label, m_func, true);
instrInsert->InsertBefore(labelThrow);
IR::Instr *instr = IR::Instr::New(
Js::OpCode::RuntimeReferenceError,
IR::RegOpnd::New(TyMachReg, m_func),
IR::IntConstOpnd::New(SCODE_CODE(JSERR_UseBeforeDeclaration), TyInt32, m_func),
m_func);
instrInsert->InsertBefore(instr);
this->LowerUnaryHelperMem(instr, IR::HelperOp_RuntimeReferenceError);
instrInsert->InsertBefore(labelContinue);
}
///----------------------------------------------------------------------------
///
/// Lowerer::LowerStElemC
///
///----------------------------------------------------------------------------
IR::Instr *
Lowerer::LowerStElemC(IR::Instr * stElem)
{
IR::Instr *instrPrev = stElem->m_prev;
IR::IndirOpnd * indirOpnd = stElem->GetDst()->AsIndirOpnd();
IR::RegOpnd *indexOpnd = indirOpnd->UnlinkIndexOpnd();
Assert(!indexOpnd || indexOpnd->m_sym->IsIntConst());
IntConstType value;
if (indexOpnd)
{
value = indexOpnd->AsRegOpnd()->m_sym->GetIntConstValue();
indexOpnd->Free(this->m_func);
}
else
{
value = (IntConstType)indirOpnd->GetOffset();
}
if (stElem->IsJitProfilingInstr())
{
Assert(stElem->AsJitProfilingInstr()->profileId == Js::Constants::NoProfileId);
m_lowererMD.LoadHelperArgument(stElem, stElem->UnlinkSrc1());
const auto meth = stElem->m_opcode == Js::OpCode::StElemC ? IR::HelperSimpleStoreArrayHelper : IR::HelperSimpleStoreArraySegHelper;
stElem->SetSrc1(IR::HelperCallOpnd::New(meth, m_func));
m_lowererMD.LoadHelperArgument(stElem, IR::IntConstOpnd::New(value, TyUint32, m_func));
m_lowererMD.LoadHelperArgument(stElem, indirOpnd->UnlinkBaseOpnd());
stElem->UnlinkDst()->Free(m_func);
m_lowererMD.LowerCall(stElem, 0);
return instrPrev;
}
IntConstType base;
IR::RegOpnd *baseOpnd = indirOpnd->GetBaseOpnd();
const ValueType baseValueType(baseOpnd->GetValueType());
if(baseValueType.IsLikelyNativeArray())
{
Assert(stElem->m_opcode == Js::OpCode::StElemC);
IR::LabelInstr *labelBailOut = nullptr;
IR::Instr *instrBailOut = nullptr;
if (stElem->HasBailOutInfo())
{
labelBailOut = IR::LabelInstr::New(Js::OpCode::Label, m_func, true);
instrBailOut = stElem;
stElem = IR::Instr::New(instrBailOut->m_opcode, m_func);
instrBailOut->TransferTo(stElem);
instrBailOut->InsertBefore(stElem);
IR::LabelInstr *labelDone = IR::LabelInstr::New(Js::OpCode::Label, m_func);
InsertBranch(Js::OpCode::Br, labelDone, instrBailOut);
instrBailOut->InsertBefore(labelBailOut);
instrBailOut->InsertAfter(labelDone);
instrBailOut->m_opcode = Js::OpCode::BailOut;
GenerateBailOut(instrBailOut);
}
if (!baseValueType.IsObject())
{
// Likely native array: do a vtable check and bail if it fails.
Assert(labelBailOut);
GenerateArrayTest(baseOpnd, labelBailOut, labelBailOut, stElem, true);
}
if (stElem->GetSrc1()->GetType() == TyVar)
{
// Storing a non-specialized value. This may cause array conversion, which invalidates all the code
// that depends on the array check we've already done.
// Call a helper that returns the type ID of the resulting array, check it here against the one we
// expect, and bail if it fails.
Assert(labelBailOut);
// Call a helper to (try and) unbox the var and store it.
// If we had to convert the array to do the store, we'll bail.
LoadScriptContext(stElem);
m_lowererMD.LoadHelperArgument(stElem, stElem->UnlinkSrc1());
IR::IntConstOpnd * intConstIndexOpnd = IR::IntConstOpnd::New(value, TyUint32, m_func);
m_lowererMD.LoadHelperArgument(stElem, intConstIndexOpnd);
m_lowererMD.LoadHelperArgument(stElem, indirOpnd->UnlinkBaseOpnd());
IR::JnHelperMethod helperMethod;
if (baseValueType.HasIntElements())
{
helperMethod = IR::HelperScrArr_SetNativeIntElementC;
}
else
{
helperMethod = IR::HelperScrArr_SetNativeFloatElementC;
}
IR::Instr *instrInsertBranch = stElem->m_next;
IR::RegOpnd *typeIdOpnd = IR::RegOpnd::New(TyUint32, m_func);
stElem->ReplaceDst(typeIdOpnd);
m_lowererMD.ChangeToHelperCall(stElem, helperMethod);
InsertCompareBranch(
typeIdOpnd,
IR::IntConstOpnd::New(
baseValueType.HasIntElements() ?
Js::TypeIds_NativeIntArray : Js::TypeIds_NativeFloatArray, TyUint32, m_func),
Js::OpCode::BrNeq_A,
labelBailOut,
instrInsertBranch);
return instrPrev;
}
else if (baseValueType.HasIntElements() && labelBailOut)
{
Assert(stElem->GetSrc1()->GetType() == GetArrayIndirType(baseValueType));
IR::Opnd* missingElementOpnd = GetMissingItemOpnd(stElem->GetSrc1()->GetType(), m_func);
if (!stElem->GetSrc1()->IsEqual(missingElementOpnd))
{
InsertMissingItemCompareBranch(stElem->GetSrc1(), Js::OpCode::BrEq_A, labelBailOut, stElem);
}
else
{
//Its a missing value store and data flow proves that src1 is always missing value. Array cannot be an int array at the first place
//if this code was ever hit. Just bailout, this code path would be updated with the profile information next time around.
InsertBranch(Js::OpCode::Br, labelBailOut, stElem);
#if DBG
labelBailOut->m_noHelperAssert = true;
#endif
stElem->Remove();
return instrPrev;
}
}
else
{
Assert(stElem->GetSrc1()->GetType() == GetArrayIndirType(baseValueType));
}
stElem->GetDst()->SetType(stElem->GetSrc1()->GetType());
Assert(value <= Js::SparseArraySegmentBase::INLINE_CHUNK_SIZE);
if(baseValueType.HasIntElements())
{
base = sizeof(Js::JavascriptNativeIntArray) + offsetof(Js::SparseArraySegment<int32>, elements);
}
else
{
base = sizeof(Js::JavascriptNativeFloatArray) + offsetof(Js::SparseArraySegment<double>, elements);
}
}
else if(baseValueType.IsLikelyObject() && baseValueType.GetObjectType() == ObjectType::Array)
{
Assert(stElem->m_opcode == Js::OpCode::StElemC);
Assert(value <= Js::SparseArraySegmentBase::INLINE_CHUNK_SIZE);
base = sizeof(Js::JavascriptArray) + offsetof(Js::SparseArraySegment<Js::Var>, elements);
}
else
{
Assert(stElem->m_opcode == Js::OpCode::StElemC || stElem->m_opcode == Js::OpCode::StArrSegElemC);
Assert(indirOpnd->GetBaseOpnd()->GetType() == TyVar);
base = offsetof(Js::SparseArraySegment<Js::Var>, elements);
}
Assert(value >= 0);
// MOV [r3 + offset(element) + index], src
const BYTE indirScale =
baseValueType.IsLikelyAnyOptimizedArray() ? GetArrayIndirScale(baseValueType) : m_lowererMD.GetDefaultIndirScale();
IntConstType offset = base + (value << indirScale);
Assert(Math::FitsInDWord(offset));
indirOpnd->SetOffset((int32)offset);
m_lowererMD.ChangeToWriteBarrierAssign(stElem, this->m_func);
return instrPrev;
}
void Lowerer::LowerLdArrHead(IR::Instr *const instr)
{
IR::RegOpnd *array = instr->UnlinkSrc1()->AsRegOpnd();
const ValueType arrayValueType(array->GetValueType());
Assert(arrayValueType.IsAnyOptimizedArray());
if(arrayValueType.GetObjectType() == ObjectType::ObjectWithArray)
{
array = LoadObjectArray(array, instr);
}
// mov arrayHeadSegment, [array + offset(headSegment)]
instr->GetDst()->SetType(TyMachPtr);
instr->SetSrc1(
IR::IndirOpnd::New(
array,
GetArrayOffsetOfHeadSegment(arrayValueType),
TyMachPtr,
instr->m_func));
LowererMD::ChangeToAssign(instr);
}
// Creates the rest parameter array.
// Var JavascriptArray::OP_NewScArrayWithElements(
// uint32 elementCount,
// Var *elements,
// ScriptContext* scriptContext)
IR::Instr *Lowerer::LowerRestParameter(IR::Opnd *formalsOpnd, IR::Opnd *dstOpnd, IR::Opnd *excessOpnd, IR::Instr *instr, IR::RegOpnd *generatorArgsPtrOpnd)
{
IR::Instr * helperCallInstr = IR::Instr::New(LowererMD::MDCallOpcode, dstOpnd, instr->m_func);
instr->InsertAfter(helperCallInstr);
// Var JavascriptArray::OP_NewScArrayWithElements(
// int32 elementCount,
// Var *elements,
// ScriptContext* scriptContext)
IR::JnHelperMethod helperMethod = IR::HelperScrArr_OP_NewScArrayWithElements;
LoadScriptContext(helperCallInstr);
BOOL isGenerator = this->m_func->GetJITFunctionBody()->IsCoroutine();
// Elements pointer = ebp + (formals count + formals offset + 1)*sizeof(Var)
IR::RegOpnd *srcOpnd = isGenerator ? generatorArgsPtrOpnd : IR::Opnd::CreateFramePointerOpnd(this->m_func);
uint16 actualOffset = isGenerator ? 0 : GetFormalParamOffset(); //4
IR::RegOpnd *argPtrOpnd = IR::RegOpnd::New(TyMachPtr, this->m_func);
InsertAdd(false, argPtrOpnd, srcOpnd, IR::IntConstOpnd::New((formalsOpnd->AsIntConstOpnd()->GetValue() + actualOffset) * MachPtr, TyMachPtr, this->m_func), helperCallInstr);
m_lowererMD.LoadHelperArgument(helperCallInstr, argPtrOpnd);
m_lowererMD.LoadHelperArgument(helperCallInstr, excessOpnd);
m_lowererMD.ChangeToHelperCall(helperCallInstr, helperMethod);
return helperCallInstr;
}
///----------------------------------------------------------------------------
///
/// Lowerer::LowerArgIn
///
/// This function checks the passed-in argument count against the index of this
/// argument and uses null for a param value if the caller didn't explicitly
/// pass anything.
///
///----------------------------------------------------------------------------
IR::Instr *
Lowerer::LowerArgIn(IR::Instr *instrArgIn)
{
IR::LabelInstr * labelDone;
IR::LabelInstr * labelUndef;
IR::LabelInstr * labelNormal;
IR::LabelInstr * labelInit;
IR::LabelInstr * labelInitNext;
IR::BranchInstr * instrBranch;
IR::Instr * instrArgInNext;
IR::Instr * instrInsert;
IR::Instr * instrPrev;
IR::Instr * instrResume = nullptr;
IR::Opnd * dstOpnd;
IR::Opnd * srcOpnd;
IR::Opnd * opndUndef;
Js::ArgSlot argIndex;
StackSym * symParam;
BOOLEAN isDuplicate;
IR::RegOpnd * generatorArgsPtrOpnd = nullptr;
// We start with:
// s1 = ArgIn_A param1
// s2 = ArgIn_A param2
// ...
// sn = ArgIn_A paramn
//
// We want to end up with:
//
// s1 = ArgIn_A param1 -- Note that this is unconditional
// count = (load from param area)
// BrLt_A $start, count, n -- Forward cbranch to the uncommon case
// Br $Ln
// $start:
// sn = assign undef
// BrGe_A $Ln-1, count, n-1
// sn-1 = assign undef
// ...
// s2 = assign undef
// Br $done
// $Ln:
// sn = assign paramn
// $Ln-1:
// sn-1 = assign paramn-1
// ...
// s2 = assign param2
// $done:
AnalysisAssert(instrArgIn);
IR::Opnd *restDst = nullptr;
bool hasRest = instrArgIn->m_opcode == Js::OpCode::ArgIn_Rest;
if (hasRest)
{
IR::Instr *restInstr = instrArgIn;
restDst = restInstr->UnlinkDst();
if (m_func->GetJITFunctionBody()->HasImplicitArgIns() && m_func->argInsCount > 0)
{
while (instrArgIn->m_opcode != Js::OpCode::ArgIn_A)
{
instrArgIn = instrArgIn->m_prev;
if (instrResume == nullptr)
{
instrResume = instrArgIn;
}
}
restInstr->Remove();
}
else
{
Assert(instrArgIn->m_func == this->m_func);
IR::Instr * instrCount = m_lowererMD.LoadInputParamCount(instrArgIn, -this->m_func->GetInParamsCount());
IR::Opnd * excessOpnd = instrCount->GetDst();
IR::LabelInstr *createRestArrayLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
// BrGe $createRestArray, excess, 0
InsertCompareBranch(excessOpnd, IR::IntConstOpnd::New(0, TyUint8, this->m_func), Js::OpCode::BrGe_A, createRestArrayLabel, instrArgIn);
// MOV excess, 0
InsertMove(excessOpnd, IR::IntConstOpnd::New(0, TyUint8, this->m_func), instrArgIn);
// $createRestArray
instrArgIn->InsertBefore(createRestArrayLabel);
if (m_func->GetJITFunctionBody()->IsCoroutine())
{
generatorArgsPtrOpnd = LoadGeneratorArgsPtr(instrArgIn);
}
IR::IntConstOpnd * formalsOpnd = IR::IntConstOpnd::New(this->m_func->GetInParamsCount(), TyUint32, this->m_func);
IR::Instr *prev = LowerRestParameter(formalsOpnd, restDst, excessOpnd, instrArgIn, generatorArgsPtrOpnd);
instrArgIn->Remove();
return prev;
}
}
srcOpnd = instrArgIn->GetSrc1();
symParam = srcOpnd->AsSymOpnd()->m_sym->AsStackSym();
argIndex = symParam->GetParamSlotNum();
if (argIndex == 1)
{
// The "this" argument is not source-dependent and doesn't need to be checked.
if (m_func->GetJITFunctionBody()->IsCoroutine())
{
generatorArgsPtrOpnd = LoadGeneratorArgsPtr(instrArgIn);
ConvertArgOpndIfGeneratorFunction(instrArgIn, generatorArgsPtrOpnd);
}
m_lowererMD.ChangeToAssign(instrArgIn);
return instrResume == nullptr ? instrArgIn->m_prev : instrResume;
}
Js::ArgSlot formalsCount = this->m_func->GetInParamsCount();
AssertMsg(argIndex <= formalsCount, "Expect to see the ArgIn's within the range of the formals");
// Because there may be instructions between the ArgIn's, such as saves to the frame object,
// we find the top of the sequence of ArgIn's and insert everything there. This assumes that
// ArgIn's use param symbols as src's and not the results of previous instructions.
instrPrev = instrArgIn;
Js::ArgSlot currArgInCount = 0;
Assert(this->m_func->argInsCount > 0);
while (currArgInCount < this->m_func->argInsCount - 1)
{
instrPrev = instrPrev->m_prev;
if (instrPrev->m_opcode == Js::OpCode::ArgIn_A)
{
srcOpnd = instrPrev->GetSrc1();
symParam = srcOpnd->AsSymOpnd()->m_sym->AsStackSym();
AssertMsg(symParam->GetParamSlotNum() < argIndex, "ArgIn's not in numerical order");
argIndex = symParam->GetParamSlotNum();
currArgInCount++;
}
else
{
// Make sure that this instruction gets lowered.
if (instrResume == nullptr)
{
instrResume = instrPrev;
}
}
}
// The loading of parameters will be inserted above this instruction.
instrInsert = instrPrev;
AnalysisAssert(instrInsert);
if (instrResume == nullptr)
{
// We found no intervening non-ArgIn's, so lowering can resume at the previous instruction.
instrResume = instrInsert->m_prev;
}
// Now insert all the checks and undef-assigns.
if (m_func->GetJITFunctionBody()->IsCoroutine())
{
generatorArgsPtrOpnd = LoadGeneratorArgsPtr(instrInsert);
}
// excessOpnd = (load from param area) - formalCounts
IR::Instr * instrCount = this->m_lowererMD.LoadInputParamCount(instrInsert, -formalsCount, true);
IR::Opnd * excessOpnd = instrCount->GetDst();
labelUndef = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, /*helperLabel*/ true);
Lowerer::InsertBranch(Js::OpCode::BrLt_A, labelUndef, instrInsert);
// Br $Ln
labelNormal = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
labelInit = labelNormal;
instrBranch = IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, labelNormal, this->m_func);
instrInsert->InsertBefore(instrBranch);
// Insert the labels
instrInsert->InsertBefore(labelUndef);
instrInsert->InsertBefore(labelNormal);
//Adjustment for deadstore of ArgIn_A
Js::ArgSlot highestSlotNum = instrArgIn->GetSrc1()->AsSymOpnd()->m_sym->AsStackSym()->GetParamSlotNum();
Js::ArgSlot missingSlotNums = this->m_func->GetInParamsCount() - highestSlotNum;
Assert(missingSlotNums >= 0);
while (missingSlotNums > 0)
{
InsertAdd(true, excessOpnd, excessOpnd, IR::IntConstOpnd::New(1, TyMachReg, this->m_func), labelNormal);
Lowerer::InsertBranch(Js::OpCode::BrEq_A, labelNormal, labelNormal);
missingSlotNums--;
}
// MOV undefReg, undefAddress
IR::Opnd* opndUndefAddress = this->LoadLibraryValueOpnd(labelNormal, LibraryValue::ValueUndefined);
opndUndef = IR::RegOpnd::New(TyMachPtr, this->m_func);
Lowerer::InsertMove(opndUndef, opndUndefAddress, labelNormal);
BVSparse<JitArenaAllocator> *formalsBv = JitAnew(this->m_alloc, BVSparse<JitArenaAllocator>, this->m_alloc);
while (currArgInCount > 0)
{
dstOpnd = instrArgIn->GetDst();
Assert(dstOpnd->IsRegOpnd());
isDuplicate = formalsBv->TestAndSet(dstOpnd->AsRegOpnd()->m_sym->AsStackSym()->m_id);
// Now insert the undef initialization before the "normal" label
// sn = assign undef
Lowerer::InsertMove(dstOpnd, opndUndef, labelNormal);
// INC excessOpnd
// BrEq_A $Ln-1
currArgInCount--;
labelInitNext = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
// And insert the "normal" initialization before the "done" label
// sn = assign paramn
// $Ln-1:
labelInit->InsertAfter(labelInitNext);
labelInit = labelInitNext;
instrArgInNext = instrArgIn->m_prev;
instrArgIn->Unlink();
Js::ArgSlot prevParamSlotNum = instrArgIn->GetSrc1()->AsSymOpnd()->m_sym->AsStackSym()->GetParamSlotNum();
// function foo(x, x) { use(x); }
// This should refer to the second 'x'. Since we reverse the order here however, we need to skip
// the initialization of the first 'x' to not override the one for the second. WOOB:1105504
if (isDuplicate)
{
instrArgIn->Free();
}
else
{
ConvertArgOpndIfGeneratorFunction(instrArgIn, generatorArgsPtrOpnd);
labelInit->InsertBefore(instrArgIn);
this->m_lowererMD.ChangeToAssign(instrArgIn);
}
instrArgIn = instrArgInNext;
while (instrArgIn->m_opcode != Js::OpCode::ArgIn_A)
{
instrArgIn = instrArgIn->m_prev;
AssertMsg(instrArgIn, "???");
}
//Adjustment for deadstore of ArgIn_A
Js::ArgSlot currParamSlotNum = instrArgIn->GetSrc1()->AsSymOpnd()->m_sym->AsStackSym()->GetParamSlotNum();
Js::ArgSlot diffSlotsNum = prevParamSlotNum - currParamSlotNum;
AssertMsg(diffSlotsNum > 0, "Argins are not in order?");
while (diffSlotsNum > 0)
{
InsertAdd(true, excessOpnd, excessOpnd, IR::IntConstOpnd::New(1, TyMachReg, this->m_func), labelNormal);
InsertBranch(Js::OpCode::BrEq_A, labelInitNext, labelNormal);
diffSlotsNum--;
}
AssertMsg(instrArgIn->GetSrc1()->AsSymOpnd()->m_sym->AsStackSym()->GetParamSlotNum() <= formalsCount,
"Expect all ArgIn's to be in numerical order by param slot");
}
// Insert final undef and normal initializations, jumping unconditionally to the end
// rather than checking against the decremented formals count as we did inside the loop above.
// s2 = assign undef
dstOpnd = instrArgIn->GetDst();
Assert(dstOpnd->IsRegOpnd());
isDuplicate = formalsBv->TestAndSet(dstOpnd->AsRegOpnd()->m_sym->AsStackSym()->m_id);
Lowerer::InsertMove(dstOpnd, opndUndef, labelNormal);
if (hasRest)
{
InsertMove(excessOpnd, IR::IntConstOpnd::New(0, TyUint8, this->m_func), labelNormal);
}
// Br $done
labelDone = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
instrBranch = IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, labelDone, this->m_func);
labelNormal->InsertBefore(instrBranch);
// s2 = assign param2
// $done:
labelInit->InsertAfter(labelDone);
if (hasRest)
{
// The formals count has been tainted, so restore it before lowering rest
IR::IntConstOpnd * formalsOpnd = IR::IntConstOpnd::New(this->m_func->GetInParamsCount(), TyUint32, this->m_func);
LowerRestParameter(formalsOpnd, restDst, excessOpnd, labelDone, generatorArgsPtrOpnd);
}
instrArgIn->Unlink();
if (isDuplicate)
{
instrArgIn->Free();
}
else
{
ConvertArgOpndIfGeneratorFunction(instrArgIn, generatorArgsPtrOpnd);
labelDone->InsertBefore(instrArgIn);
this->m_lowererMD.ChangeToAssign(instrArgIn);
}
JitAdelete(this->m_alloc, formalsBv);
return instrResume;
}
void
Lowerer::ConvertArgOpndIfGeneratorFunction(IR::Instr *instrArgIn, IR::RegOpnd *generatorArgsPtrOpnd)
{
if (this->m_func->GetJITFunctionBody()->IsCoroutine())
{
// Replace stack param operand with offset into arguments array held by
// the generator object.
IR::Opnd * srcOpnd = instrArgIn->UnlinkSrc1();
StackSym * symParam = srcOpnd->AsSymOpnd()->m_sym->AsStackSym();
Js::ArgSlot argIndex = symParam->GetParamSlotNum();
IR::IndirOpnd * indirOpnd = IR::IndirOpnd::New(generatorArgsPtrOpnd, (argIndex - 1) * MachPtr, TyMachPtr, this->m_func);
srcOpnd->Free(this->m_func);
instrArgIn->SetSrc1(indirOpnd);
}
}
IR::RegOpnd *
Lowerer::LoadGeneratorArgsPtr(IR::Instr *instrInsert)
{
IR::Instr * instr = LoadGeneratorObject(instrInsert);
IR::RegOpnd * generatorRegOpnd = instr->GetDst()->AsRegOpnd();
IR::IndirOpnd * indirOpnd = IR::IndirOpnd::New(generatorRegOpnd, Js::JavascriptGenerator::GetArgsPtrOffset(), TyMachPtr, instrInsert->m_func);
IR::RegOpnd * argsPtrOpnd = IR::RegOpnd::New(TyMachReg, instrInsert->m_func);
Lowerer::InsertMove(argsPtrOpnd, indirOpnd, instrInsert);
return argsPtrOpnd;
}
IR::Instr *
Lowerer::LoadGeneratorObject(IR::Instr * instrInsert)
{
StackSym * generatorSym = StackSym::NewImplicitParamSym(3, instrInsert->m_func);
instrInsert->m_func->SetArgOffset(generatorSym, LowererMD::GetFormalParamOffset() * MachPtr);
IR::SymOpnd * generatorSymOpnd = IR::SymOpnd::New(generatorSym, TyMachPtr, instrInsert->m_func);
IR::RegOpnd * generatorRegOpnd = IR::RegOpnd::New(TyMachPtr, instrInsert->m_func);
instrInsert->m_func->SetHasImplicitParamLoad();
return Lowerer::InsertMove(generatorRegOpnd, generatorSymOpnd, instrInsert);
}
IR::Instr *
Lowerer::LowerArgInAsmJs(IR::Instr * instr)
{
Assert(m_func->GetJITFunctionBody()->IsAsmJsMode());
Assert(instr && instr->m_opcode == Js::OpCode::ArgIn_A);
IR::Instr* instrPrev = instr->m_prev;
m_lowererMD.ChangeToAssign(instr);
return instrPrev;
}
bool
Lowerer::InlineBuiltInLibraryCall(IR::Instr *callInstr)
{
IR::Opnd *src1 = callInstr->GetSrc1();
IR::Opnd *src2 = callInstr->GetSrc2();
// Get the arg count by looking at the slot number of the last arg symbol.
if (!src2->IsSymOpnd())
{
// No args? Not sure this is possible, but handle it.
return false;
}
StackSym *argLinkSym = src2->AsSymOpnd()->m_sym->AsStackSym();
// Subtract "this" from the arg count.
IntConstType argCount = argLinkSym->GetArgSlotNum() - 1;
// Find the callee's built-in index (if any).
Js::BuiltinFunction index = Func::GetBuiltInIndex(src1);
// Warning!
// Don't add new built-in to following switch. Built-ins needs to be inlined in call direct way.
// Following is only for prejit scenarios where we don't get inlining always and generate fast path in lowerer.
// Generating fastpath here misses fixed functions and globopt optimizations.
switch(index)
{
case Js::BuiltinFunction::JavascriptString_CharAt:
case Js::BuiltinFunction::JavascriptString_CharCodeAt:
if (argCount != 1)
{
return false;
}
if (!callInstr->GetDst())
{
// Optimization of Char[Code]At assumes result is used.
return false;
}
break;
case Js::BuiltinFunction::Math_Abs:
#ifdef _M_IX86
if (!AutoSystemInfo::Data.SSE2Available())
{
return false;
}
#endif
if (argCount != 1)
{
return false;
}
if (!callInstr->GetDst())
{
// Optimization of Abs assumes result is used.
return false;
}
break;
case Js::BuiltinFunction::JavascriptArray_Push:
{
if (argCount != 1)
{
return false;
}
if (callInstr->GetDst())
{
// Optimization of push assumes result is unused.
return false;
}
StackSym *linkSym = callInstr->GetSrc2()->AsSymOpnd()->m_sym->AsStackSym();
Assert(linkSym->IsSingleDef());
linkSym = linkSym->m_instrDef->GetSrc2()->AsSymOpnd()->m_sym->AsStackSym();
Assert(linkSym->IsSingleDef());
IR::Opnd *const arrayOpnd = linkSym->m_instrDef->GetSrc1();
if(!arrayOpnd->IsRegOpnd())
{
// This should be rare, but needs to be handled.
// By now, we've already started some of the inlining. Simply jmp to the helper.
// The branch will get peeped later.
return false;
}
if(!ShouldGenerateArrayFastPath(arrayOpnd, false, false, false) ||
arrayOpnd->GetValueType().IsLikelyNativeArray())
{
// Rejecting native array for now, since we have to do a FromVar at the call site and bail out.
return false;
}
break;
}
case Js::BuiltinFunction::JavascriptString_Replace:
{
if(argCount != 2)
{
return false;
}
if(!ShouldGenerateStringReplaceFastPath(callInstr, argCount))
{
return false;
}
break;
}
default:
return false;
}
Assert(Func::IsBuiltInInlinedInLowerer(callInstr->GetSrc1()));
IR::Opnd *callTargetOpnd = callInstr->GetSrc1();
IR::LabelInstr *labelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true);
IR::Opnd *objRefOpnd = IR::MemRefOpnd::New((void*)this->GetObjRefForBuiltInTarget(callTargetOpnd->AsRegOpnd()), TyMachReg, this->m_func);
InsertCompareBranch(callTargetOpnd, objRefOpnd, Js::OpCode::BrNeq_A, labelHelper, callInstr);
callInstr->InsertBefore(labelHelper);
Assert(argCount <= 2);
IR::Opnd *argsOpnd[3];
IR::Opnd *linkOpnd = callInstr->GetSrc2();
while(linkOpnd->IsSymOpnd())
{
IR::SymOpnd * symOpnd = linkOpnd->AsSymOpnd();
StackSym *sym = symOpnd->m_sym->AsStackSym();
Assert(sym->m_isSingleDef);
IR::Instr *argInstr = sym->m_instrDef;
Assert(argCount >= 0);
argsOpnd[argCount] = argInstr->GetSrc1();
argCount--;
argInstr->Unlink();
labelHelper->InsertAfter(argInstr);
linkOpnd = argInstr->GetSrc2();
}
AnalysisAssert(argCount == -1);
// Move startcall
Assert(linkOpnd->IsRegOpnd());
StackSym *sym = linkOpnd->AsRegOpnd()->m_sym;
Assert(sym->m_isSingleDef);
IR::Instr *startCall = sym->m_instrDef;
Assert(startCall->m_opcode == Js::OpCode::StartCall);
startCall->Unlink();
labelHelper->InsertAfter(startCall);
// $doneLabel:
IR::LabelInstr *doneLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
callInstr->InsertAfter(doneLabel);
bool success = true;
switch(index)
{
case Js::BuiltinFunction::Math_Abs:
this->m_lowererMD.GenerateFastAbs(callInstr->GetDst(), argsOpnd[1], callInstr, labelHelper, labelHelper, doneLabel);
break;
case Js::BuiltinFunction::JavascriptString_CharCodeAt:
case Js::BuiltinFunction::JavascriptString_CharAt:
success = GenerateFastCharAt(index, callInstr->GetDst(), argsOpnd[0], argsOpnd[1],
callInstr, labelHelper, labelHelper, doneLabel);
break;
case Js::BuiltinFunction::JavascriptArray_Push:
success = GenerateFastPush(argsOpnd[0], argsOpnd[1], callInstr, labelHelper, labelHelper, nullptr, doneLabel);
break;
case Js::BuiltinFunction::JavascriptString_Replace:
success = GenerateFastReplace(argsOpnd[0], argsOpnd[1], argsOpnd[2], callInstr, labelHelper, labelHelper, doneLabel);
break;
default:
Assert(UNREACHED);
}
IR::Instr *instr = IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, doneLabel, this->m_func);
labelHelper->InsertBefore(instr);
return success;
}
// Perform lowerer part of inlining built-in function.
// For details, see inline.cpp.
//
// Description of changes here (note that taking care of Argouts are similar to InlineeStart):
// - Move ArgOut_A_InlineBuiltIn next to the call instr -- used by bailout processing in register allocator.
// - Remove StartCall and InlineBuiltInStart for this call.
// Before:
// StartCall fn
// d1 = BIA s1, link1
// ...
// InlineBuiltInStart fn, link0
// After:
// ...
// d1 = BIA s1, NULL
void Lowerer::LowerInlineBuiltIn(IR::Instr* builtInEndInstr)
{
Assert(builtInEndInstr->m_opcode == Js::OpCode::InlineBuiltInEnd || builtInEndInstr->m_opcode == Js::OpCode::InlineNonTrackingBuiltInEnd);
IR::Instr* startCallInstr = nullptr;
builtInEndInstr->IterateArgInstrs([&](IR::Instr* argInstr) {
startCallInstr = argInstr->GetSrc2()->GetStackSym()->m_instrDef;
return false;
});
// Keep the startCall around as bailout refers to it. Just unlink it for now - do not delete it.
startCallInstr->Unlink();
builtInEndInstr->Remove();
}
intptr_t
Lowerer::GetObjRefForBuiltInTarget(IR::RegOpnd * regOpnd)
{
intptr_t mathFns = m_func->GetScriptContextInfo()->GetBuiltinFunctionsBaseAddr();
Js::BuiltinFunction index = regOpnd->m_sym->m_builtInIndex;
AssertMsg(index < Js::BuiltinFunction::Count, "Invalid built-in index on a call target marked as built-in");
return mathFns + index;
}
IR::Instr *
Lowerer::LowerNewRegEx(IR::Instr * instr)
{
IR::Opnd *src1 = instr->UnlinkSrc1();
Assert(src1->IsAddrOpnd());
#if ENABLE_REGEX_CONFIG_OPTIONS
if (REGEX_CONFIG_FLAG(RegexTracing))
{
Assert(!instr->GetDst()->CanStoreTemp());
IR::Instr * instrPrev = LoadScriptContext(instr);
instrPrev = m_lowererMD.LoadHelperArgument(instr, src1);
m_lowererMD.ChangeToHelperCall(instr, IR::HelperScrRegEx_OP_NewRegEx);
return instrPrev;
}
#endif
IR::Instr * instrPrev = instr->m_prev;
IR::RegOpnd * dstOpnd = instr->UnlinkDst()->AsRegOpnd();
IR::SymOpnd * tempObjectSymOpnd;
bool isZeroed = GenerateRecyclerOrMarkTempAlloc(instr, dstOpnd, IR::HelperAllocMemForJavascriptRegExp, sizeof(Js::JavascriptRegExp), &tempObjectSymOpnd);
if (tempObjectSymOpnd && !PHASE_OFF(Js::HoistMarkTempInitPhase, this->m_func) && this->outerMostLoopLabel)
{
// Hoist the vtable and pattern init to the outer most loop top as it never changes
InsertMove(tempObjectSymOpnd,
LoadVTableValueOpnd(this->outerMostLoopLabel, VTableValue::VtableJavascriptRegExp),
this->outerMostLoopLabel, false);
}
else
{
GenerateMemInit(dstOpnd, 0, LoadVTableValueOpnd(instr, VTableValue::VtableJavascriptRegExp), instr, isZeroed);
}
GenerateMemInit(dstOpnd, Js::JavascriptRegExp::GetOffsetOfType(),
this->LoadLibraryValueOpnd(instr, LibraryValue::ValueRegexType), instr, isZeroed);
GenerateMemInitNull(dstOpnd, Js::JavascriptRegExp::GetOffsetOfAuxSlots(), instr, isZeroed);
GenerateMemInitNull(dstOpnd, Js::JavascriptRegExp::GetOffsetOfObjectArray(), instr, isZeroed);
if (tempObjectSymOpnd && !PHASE_OFF(Js::HoistMarkTempInitPhase, this->m_func) && this->outerMostLoopLabel)
{
InsertMove(IR::SymOpnd::New(tempObjectSymOpnd->m_sym,
tempObjectSymOpnd->m_offset + Js::JavascriptRegExp::GetOffsetOfPattern(), TyMachPtr, this->m_func),
src1, this->outerMostLoopLabel, false);
}
else
{
GenerateMemInit(dstOpnd, Js::JavascriptRegExp::GetOffsetOfPattern(), src1, instr, isZeroed);
}
GenerateMemInitNull(dstOpnd, Js::JavascriptRegExp::GetOffsetOfSplitPattern(), instr, isZeroed);
GenerateMemInitNull(dstOpnd, Js::JavascriptRegExp::GetOffsetOfLastIndexVar(), instr, isZeroed);
GenerateMemInit(dstOpnd, Js::JavascriptRegExp::GetOffsetOfLastIndexOrFlag(), 0, instr, isZeroed);
instr->Remove();
return instrPrev;
}
IR::Instr *
Lowerer::GenerateRuntimeError(IR::Instr * insertBeforeInstr, Js::MessageId errorCode, IR::JnHelperMethod helper /*= IR::JnHelperMethod::HelperOp_RuntimeTypeError*/)
{
IR::Instr * runtimeErrorInstr = IR::Instr::New(Js::OpCode::RuntimeTypeError, this->m_func);
runtimeErrorInstr->SetSrc1(IR::IntConstOpnd::New(errorCode, TyInt32, this->m_func, true));
insertBeforeInstr->InsertBefore(runtimeErrorInstr);
return this->LowerUnaryHelperMem(runtimeErrorInstr, helper);
}
bool Lowerer::IsNullOrUndefRegOpnd(IR::RegOpnd *opnd) const
{
StackSym *sym = opnd->m_sym;
if (sym->IsIntConst() || sym->IsFloatConst())
{
return false;
}
return opnd->GetValueType().IsUndefined() || opnd->GetValueType().IsNull();
}
bool Lowerer::IsConstRegOpnd(IR::RegOpnd *opnd) const
{
StackSym *sym = opnd->m_sym;
if (sym->IsIntConst() || sym->IsFloatConst())
{
return false;
}
const auto& vt = opnd->GetValueType();
return vt.IsUndefined() || vt.IsNull() || (sym->m_isConst && vt.IsBoolean());
}
IR::Opnd * Lowerer::GetConstRegOpnd(IR::RegOpnd *opnd, IR::Instr * instr)
{
if (opnd->GetValueType().IsUndefined())
{
return this->LoadLibraryValueOpnd(instr, LibraryValue::ValueUndefined);
}
if (opnd->GetValueType().IsNull())
{
return this->LoadLibraryValueOpnd(instr, LibraryValue::ValueNull);
}
Assert(opnd->GetValueType().IsBoolean());
return opnd->GetStackSym()->GetInstrDef()->GetSrc1()->AsAddrOpnd();
}
bool
Lowerer::HasSideEffects(IR::Instr *instr)
{
if (LowererMD::IsCall(instr))
{
#ifdef _M_IX86
IR::Opnd *src1 = instr->GetSrc1();
if (src1->IsHelperCallOpnd())
{
IR::HelperCallOpnd * helper = src1->AsHelperCallOpnd();
switch(helper->m_fnHelper)
{
case IR::HelperOp_Int32ToAtomInPlace:
case IR::HelperOp_Int32ToAtom:
case IR::HelperOp_UInt32ToAtom:
return false;
}
}
#endif
return true;
}
return instr->HasAnySideEffects();
}
bool Lowerer::IsArgSaveRequired(Func *func) {
return (!func->IsTrueLeaf() || func->IsJitInDebugMode() ||
// GetHasImplicitParamLoad covers generators, asmjs,
// and other javascript functions that implicitly read from the arg stack slots
func->GetHasThrow() || func->GetHasImplicitParamLoad() || func->HasThis() || func->argInsCount > 0);
}
IR::Instr*
Lowerer::GenerateFastInlineBuiltInMathRandom(IR::Instr* instr)
{
AssertMsg(instr->GetDst()->IsFloat(), "dst must be float.");
IR::Instr* retInstr = instr->m_prev;
IR::Opnd* dst = instr->GetDst();
#if defined(_M_X64)
if (m_func->GetScriptContextInfo()->IsPRNGSeeded())
{
const uint64 mExp = 0x3FF0000000000000;
const uint64 mMant = 0x000FFFFFFFFFFFFF;
IR::RegOpnd* r0 = IR::RegOpnd::New(TyUint64, m_func); // s0
IR::RegOpnd* r1 = IR::RegOpnd::New(TyUint64, m_func); // s1
IR::RegOpnd* r3 = IR::RegOpnd::New(TyUint64, m_func); // helper uint64 reg
IR::RegOpnd* r4 = IR::RegOpnd::New(TyFloat64, m_func); // helper float64 reg
// ===========================================================
// s0 = scriptContext->GetLibrary()->GetRandSeed1();
// s1 = scriptContext->GetLibrary()->GetRandSeed0();
// ===========================================================
this->InsertMove(r0,
IR::MemRefOpnd::New((BYTE*)m_func->GetScriptContextInfo()->GetLibraryAddr() + Js::JavascriptLibrary::GetRandSeed1Offset(), TyUint64, instr->m_func), instr);
this->InsertMove(r1,
IR::MemRefOpnd::New((BYTE*)m_func->GetScriptContextInfo()->GetLibraryAddr() + Js::JavascriptLibrary::GetRandSeed0Offset(), TyUint64, instr->m_func), instr);
// ===========================================================
// s1 ^= s1 << 23;
// ===========================================================
this->InsertMove(r3, r1, instr);
this->InsertShift(Js::OpCode::Shl_A, false, r3, r3, IR::IntConstOpnd::New(23, TyInt8, m_func), instr);
this->InsertXor(r1, r1, r3, instr);
// ===========================================================
// s1 ^= s1 >> 17;
// ===========================================================
this->InsertMove(r3, r1, instr);
this->InsertShift(Js::OpCode::ShrU_A, false, r3, r3, IR::IntConstOpnd::New(17, TyInt8, m_func), instr);
this->InsertXor(r1, r1, r3, instr);
// ===========================================================
// s1 ^= s0;
// ===========================================================
this->InsertXor(r1, r1, r0, instr);
// ===========================================================
// s1 ^= s0 >> 26;
// ===========================================================
this->InsertMove(r3, r0, instr);
this->InsertShift(Js::OpCode::ShrU_A, false, r3, r3, IR::IntConstOpnd::New(26, TyInt8, m_func), instr);
this->InsertXor(r1, r1, r3, instr);
// ===========================================================
// scriptContext->GetLibrary()->SetRandSeed0(s0);
// scriptContext->GetLibrary()->SetRandSeed1(s1);
// ===========================================================
this->InsertMove(
IR::MemRefOpnd::New((BYTE*)m_func->GetScriptContextInfo()->GetLibraryAddr() + Js::JavascriptLibrary::GetRandSeed0Offset(), TyUint64, m_func), r0, instr);
this->InsertMove(
IR::MemRefOpnd::New((BYTE*)m_func->GetScriptContextInfo()->GetLibraryAddr() + Js::JavascriptLibrary::GetRandSeed1Offset(), TyUint64, m_func), r1, instr);
// ===========================================================
// dst = bit_cast<float64>(((s0 + s1) & mMant) | mExp);
// ===========================================================
this->InsertAdd(false, r1, r1, r0, instr);
this->InsertMove(r3, IR::IntConstOpnd::New(mMant, TyInt64, m_func, true), instr);
this->InsertAnd(r1, r1, r3, instr);
this->InsertMove(r3, IR::IntConstOpnd::New(mExp, TyInt64, m_func, true), instr);
this->InsertOr(r1, r1, r3, instr);
this->InsertMoveBitCast(dst, r1, instr);
// ===================================================================
// dst -= 1.0;
// ===================================================================
this->InsertMove(r4, IR::MemRefOpnd::New(m_func->GetThreadContextInfo()->GetDoubleOnePointZeroAddr(), TyFloat64, m_func, IR::AddrOpndKindDynamicDoubleRef), instr);
this->InsertSub(false, dst, dst, r4, instr);
}
else
#endif
{
IR::Opnd* tmpdst = dst;
if (!dst->IsRegOpnd())
{
tmpdst = IR::RegOpnd::New(dst->GetType(), instr->m_func);
}
LoadScriptContext(instr);
IR::Instr * helperCallInstr = IR::Instr::New(LowererMD::MDCallOpcode, tmpdst, instr->m_func);
instr->InsertBefore(helperCallInstr);
m_lowererMD.ChangeToHelperCall(helperCallInstr, IR::JnHelperMethod::HelperDirectMath_Random);
if (tmpdst != dst)
{
InsertMove(dst, tmpdst, instr);
}
}
instr->Remove();
return retInstr;
}
IR::Instr *
Lowerer::LowerCallDirect(IR::Instr * instr)
{
IR::Opnd* linkOpnd = instr->UnlinkSrc2();
StackSym *linkSym = linkOpnd->AsSymOpnd()->m_sym->AsStackSym();
IR::Instr* argInstr = linkSym->m_instrDef;
Assert(argInstr->m_opcode == Js::OpCode::ArgOut_A_InlineSpecialized);
IR::Opnd* funcObj = argInstr->UnlinkSrc1();
instr->SetSrc2(argInstr->UnlinkSrc2());
argInstr->Remove();
if (instr->HasBailOutInfo() && !instr->HasLazyBailOut())
{
IR::Instr * bailOutInstr = this->SplitBailOnImplicitCall(instr, instr->m_next, instr->m_next);
this->LowerBailOnEqualOrNotEqual(bailOutInstr);
}
Js::CallFlags flags = instr->GetDst() ? Js::CallFlags_Value : Js::CallFlags_NotUsed;
return this->GenerateDirectCall(instr, funcObj, (ushort)flags);
}
IR::Instr *
Lowerer::GenerateDirectCall(IR::Instr* inlineInstr, IR::Opnd* funcObj, ushort callflags)
{
int32 argCount = m_lowererMD.LowerCallArgs(inlineInstr, callflags);
m_lowererMD.LoadHelperArgument(inlineInstr, funcObj);
m_lowererMD.LowerCall(inlineInstr, (Js::ArgSlot)argCount); //to account for function object and callinfo
return inlineInstr->m_prev;
}
/*
* GenerateHelperToArrayPushFastPath
* Generates Helper Call and pushes arguments to the Push HelperCall
*/
IR::Instr *
Lowerer::GenerateHelperToArrayPushFastPath(IR::Instr * instr, IR::LabelInstr * bailOutLabelHelper)
{
IR::Opnd * arrayHelperOpnd = instr->UnlinkSrc1();
IR::Opnd * elementHelperOpnd = instr->UnlinkSrc2();
IR::JnHelperMethod helperMethod;
if(elementHelperOpnd->IsInt32())
{
Assert(arrayHelperOpnd->GetValueType().IsLikelyNativeIntArray());
helperMethod = IR::HelperArray_NativeIntPush;
m_lowererMD.LoadHelperArgument(instr, elementHelperOpnd);
}
else if(elementHelperOpnd->IsFloat())
{
Assert(arrayHelperOpnd->GetValueType().IsLikelyNativeFloatArray());
helperMethod = IR::HelperArray_NativeFloatPush;
m_lowererMD.LoadDoubleHelperArgument(instr, elementHelperOpnd);
}
else
{
helperMethod = IR::HelperArray_VarPush;
m_lowererMD.LoadHelperArgument(instr, elementHelperOpnd);
}
m_lowererMD.LoadHelperArgument(instr, arrayHelperOpnd);
LoadScriptContext(instr);
return m_lowererMD.ChangeToHelperCall(instr, helperMethod);
}
/*
* GenerateHelperToArrayPopFastPath
* Generates Helper Call and pushes arguments to the Pop HelperCall
*/
IR::Instr *
Lowerer::GenerateHelperToArrayPopFastPath(IR::Instr * instr, IR::LabelInstr * doneLabel, IR::LabelInstr * bailOutLabelHelper)
{
IR::Opnd * arrayHelperOpnd = instr->UnlinkSrc1();
ValueType arrayValueType = arrayHelperOpnd->GetValueType();
IR::JnHelperMethod helperMethod;
//Decide the helperMethod based on dst availability and nativity of the array.
if(arrayValueType.IsLikelyNativeArray() && !instr->GetDst())
{
helperMethod = IR::HelperArray_NativePopWithNoDst;
}
else if(arrayValueType.IsLikelyNativeIntArray())
{
helperMethod = IR::HelperArray_NativeIntPop;
}
else if(arrayValueType.IsLikelyNativeFloatArray())
{
helperMethod = IR::HelperArray_NativeFloatPop;
}
else
{
helperMethod = IR::HelperArray_VarPop;
}
m_lowererMD.LoadHelperArgument(instr, arrayHelperOpnd);
//We do not need scriptContext for HelperArray_NativePopWithNoDst call.
if(helperMethod != IR::HelperArray_NativePopWithNoDst)
{
LoadScriptContext(instr);
}
IR::Instr * retInstr = m_lowererMD.ChangeToHelperCall(instr, helperMethod, bailOutLabelHelper);
//We don't need missing item check for var arrays, as there it is taken care by the helper.
if(arrayValueType.IsLikelyNativeArray())
{
if(retInstr->GetDst())
{
//Do this check only for native arrays with Dst. For Var arrays, this is taken care in the Runtime helper itself.
InsertMissingItemCompareBranch(retInstr->GetDst(), Js::OpCode::BrNeq_A, doneLabel, bailOutLabelHelper);
}
else
{
//We need unconditional jump to doneLabel, if there is no dst in Pop instr.
InsertBranch(Js::OpCode::Br, true, doneLabel, bailOutLabelHelper);
}
}
return retInstr;
}
IR::Instr *
Lowerer::LowerCondBranchCheckBailOut(IR::BranchInstr * branchInstr, IR::Instr * helperCall, bool isHelper)
{
Assert(branchInstr->m_opcode == Js::OpCode::BrTrue_A || branchInstr->m_opcode == Js::OpCode::BrFalse_A);
if (branchInstr->HasBailOutInfo())
{
#ifdef ENABLE_SCRIPT_DEBUGGING
IR::BailOutKind debuggerBailOutKind = IR::BailOutInvalid;
if (branchInstr->HasAuxBailOut())
{
// We have shared debugger bailout. For branches we lower it here, not in SplitBailForDebugger.
// See SplitBailForDebugger for details.
AssertMsg(!(branchInstr->GetBailOutKind() & IR::BailOutForDebuggerBits), "There should be no debugger bits in main bailout kind.");
debuggerBailOutKind = branchInstr->GetAuxBailOutKind() & IR::BailOutForDebuggerBits;
AssertMsg((debuggerBailOutKind & ~(IR::BailOutIgnoreException | IR::BailOutForceByFlag)) == 0, "Only IR::BailOutIgnoreException|ForceByFlag supported here.");
}
#endif
IR::Instr * bailOutInstr = this->SplitBailOnImplicitCall(branchInstr, helperCall, branchInstr);
IR::Instr* prevInstr = this->LowerBailOnEqualOrNotEqual(bailOutInstr, branchInstr, nullptr, nullptr, isHelper);
#ifdef ENABLE_SCRIPT_DEBUGGING
if (debuggerBailOutKind != IR::BailOutInvalid)
{
// Note that by this time implicit calls bailout is already lowered.
// What we do here is use same bailout info and lower debugger bailout which would be shared bailout.
BailOutInfo* bailOutInfo = bailOutInstr->GetBailOutInfo();
IR::BailOutInstr* debuggerBailoutInstr = IR::BailOutInstr::New(
Js::OpCode::BailForDebugger, debuggerBailOutKind, bailOutInfo, bailOutInfo->bailOutFunc);
prevInstr->InsertAfter(debuggerBailoutInstr);
// The result of that is:
// original helper op_* instr, then debugger bailout, then implicit calls bailout/etc with the branch instr.
// Example:
// s35(eax).i32 = CALL Op_GreaterEqual.u32 # -- original op_* helper
// s34.i32 = MOV s35(eax).i32 #
// BailForDebugger # Bailout: #0042 (BailOutIgnoreException) -- the debugger bailout
// CMP [0x0003BDE0].i8, 1 (0x1).i8 # -- implicit calls check
// JEQ $L10 #
//$L11: [helper] #
// CALL SaveAllRegistersAndBranchBailOut.u32 # Bailout: #0042 (BailOutOnImplicitCalls)
// JMP $L5 #
//$L10: [helper] #
// BrFalse_A $L3, s34.i32 #0034 -- The BrTrue/BrFalse branch (branch instr)
//$L6: [helper] #0042
this->LowerBailForDebugger(debuggerBailoutInstr, isHelper);
// After lowering this we will have a check which on bailout condition will JMP to $L11.
}
#else
(prevInstr);
#endif
}
return m_lowererMD.LowerCondBranch(branchInstr);
}
IR::SymOpnd *
Lowerer::LoadCallInfo(IR::Instr * instrInsert)
{
IR::SymOpnd * srcOpnd;
Func * func = instrInsert->m_func;
if (func->GetJITFunctionBody()->IsCoroutine())
{
// Generator function arguments and ArgumentsInfo are not on the stack. Instead they
// are accessed off the generator object (which is prm1).
IR::Instr *genLoadInstr = LoadGeneratorObject(instrInsert);
IR::RegOpnd * generatorRegOpnd = genLoadInstr->GetDst()->AsRegOpnd();
IR::IndirOpnd * indirOpnd = IR::IndirOpnd::New(generatorRegOpnd, Js::JavascriptGenerator::GetCallInfoOffset(), TyMachPtr, func);
IR::Instr * instr = Lowerer::InsertMove(IR::RegOpnd::New(TyMachPtr, func), indirOpnd, instrInsert);
StackSym * callInfoSym = StackSym::New(TyMachReg, func);
IR::SymOpnd * callInfoSymOpnd = IR::SymOpnd::New(callInfoSym, TyMachReg, func);
Lowerer::InsertMove(callInfoSymOpnd, instr->GetDst(), instrInsert);
srcOpnd = IR::SymOpnd::New(callInfoSym, TyMachReg, func);
}
else
{
// Otherwise callInfo is always the "second" argument.
// The stack looks like this:
//
// script param N
// ...
// script param 1
// callinfo
// function object
// return addr
// FP -> FP chain
StackSym * srcSym = LowererMD::GetImplicitParamSlotSym(1, func);
srcOpnd = IR::SymOpnd::New(srcSym, TyMachReg, func);
}
return srcOpnd;
}
IR::Instr *
Lowerer::LowerBailOnNotStackArgs(IR::Instr * instr)
{
if (!this->m_func->GetHasStackArgs())
{
throw Js::RejitException(RejitReason::InlineApplyDisabled);
}
IR::Instr * prevInstr = instr->m_prev;
// Bail out test
// Label to skip Bailout and continue
IR::LabelInstr * continueLabelInstr;
IR::Instr *instrNext = instr->m_next;
if (instrNext->IsLabelInstr())
{
continueLabelInstr = instrNext->AsLabelInstr();
}
else
{
continueLabelInstr = IR::LabelInstr::New(Js::OpCode::Label, m_func, false);
instr->InsertAfter(continueLabelInstr);
}
if (!instr->m_func->IsInlinee())
{
//BailOut if the number of actuals (except "this" argument) is greater than or equal to 15.
IR::RegOpnd* ldLenDstOpnd = IR::RegOpnd::New(TyUint32, instr->m_func);
const IR::AutoReuseOpnd autoReuseldLenDstOpnd(ldLenDstOpnd, instr->m_func);
IR::Instr* ldLen = IR::Instr::New(Js::OpCode::LdLen_A, ldLenDstOpnd, instr->m_func);
ldLenDstOpnd->SetValueType(ValueType::GetTaggedInt()); //LdLen_A works only on stack arguments
instr->InsertBefore(ldLen);
this->GenerateFastRealStackArgumentsLdLen(ldLen);
this->InsertCompareBranch(ldLenDstOpnd, IR::IntConstOpnd::New(Js::InlineeCallInfo::MaxInlineeArgoutCount, TyUint32, m_func, true), Js::OpCode::BrLt_A, true, continueLabelInstr, instr);
this->GenerateBailOut(instr, nullptr, nullptr);
}
else
{
//For Inlined functions, we are sure actuals can't exceed Js::InlineeCallInfo::MaxInlineeArgoutCount (15).
//No need to bail out.
instr->Remove();
}
return prevInstr;
}
IR::Instr *
Lowerer::LowerBailOnNotSpreadable(IR::Instr *instr)
{
// We only avoid bailing out / throwing a rejit exception when the array operand is a simple, non-optimized, non-object array.
IR::Instr * prevInstr = instr->m_prev;
Func *func = instr->m_func;
IR::Opnd *arraySrcOpnd = instr->UnlinkSrc1();
IR::RegOpnd *arrayOpnd = GetRegOpnd(arraySrcOpnd, instr, func, TyMachPtr);
const ValueType baseValueType(arrayOpnd->GetValueType());
// Check if we can just throw a rejit exception based on valuetype alone instead of bailing out.
if (!baseValueType.IsLikelyArray()
|| baseValueType.IsLikelyAnyOptimizedArray()
|| (baseValueType.IsLikelyObject() && (baseValueType.GetObjectType() == ObjectType::ObjectWithArray))
// Validate that GenerateArrayTest will not fail.
|| !(baseValueType.IsUninitialized() || baseValueType.HasBeenObject())
|| m_func->IsInlinee())
{
throw Js::RejitException(RejitReason::InlineSpreadDisabled);
}
// Past this point, we will need to use a bailout.
IR::LabelInstr *bailOutLabel = IR::LabelInstr::New(Js::OpCode::Label, func, true /* isOpHelper */);
// See if we can skip various array checks on value type alone
if (!baseValueType.IsArray())
{
GenerateArrayTest(arrayOpnd, bailOutLabel, bailOutLabel, instr, false);
}
if (!(baseValueType.IsArray() && baseValueType.HasNoMissingValues()))
{
InsertTestBranch(
IR::IndirOpnd::New(arrayOpnd, Js::JavascriptArray::GetOffsetOfArrayFlags(), TyUint8, func),
IR::IntConstOpnd::New(static_cast<uint8>(Js::DynamicObjectFlags::HasNoMissingValues), TyUint8, func, true),
Js::OpCode::BrEq_A,
bailOutLabel,
instr);
}
IR::IndirOpnd *arrayLenPtrOpnd = IR::IndirOpnd::New(arrayOpnd, Js::JavascriptArray::GetOffsetOfLength(), TyUint32, func);
InsertCompareBranch(arrayLenPtrOpnd, IR::IntConstOpnd::New(Js::InlineeCallInfo::MaxInlineeArgoutCount - 1, TyUint8, func), Js::OpCode::BrGt_A, true, bailOutLabel, instr);
IR::LabelInstr *skipBailOutLabel = IR::LabelInstr::New(Js::OpCode::Label, func);
InsertBranch(Js::OpCode::Br, skipBailOutLabel, instr);
instr->InsertBefore(bailOutLabel);
instr->InsertAfter(skipBailOutLabel);
GenerateBailOut(instr);
return prevInstr;
}
IR::Instr *
Lowerer::LowerBailOnNotPolymorphicInlinee(IR::Instr * instr)
{
Assert(instr->HasBailOutInfo() && (instr->GetBailOutKind() == IR::BailOutOnFailedPolymorphicInlineTypeCheck || instr->GetBailOutKind() == IR::BailOutOnPolymorphicInlineFunction));
IR::Instr* instrPrev = instr->m_prev;
this->GenerateBailOut(instr, nullptr, nullptr);
return instrPrev;
}
void
Lowerer::LowerBailoutCheckAndLabel(IR::Instr *instr, bool onEqual, bool isHelper)
{
// Label to skip Bailout and continue
IR::LabelInstr * continueLabelInstr;
IR::Instr *instrNext = instr->m_next;
if (instrNext->IsLabelInstr())
{
continueLabelInstr = instrNext->AsLabelInstr();
}
else
{
continueLabelInstr = IR::LabelInstr::New(Js::OpCode::Label, m_func, isHelper);
instr->InsertAfter(continueLabelInstr);
}
if(instr->GetBailOutKind() == IR::BailOutInjected)
{
// BailOnEqual 0, 0
Assert(onEqual);
Assert(instr->GetSrc1()->IsEqual(instr->GetSrc2()));
Assert(instr->GetSrc1()->AsIntConstOpnd()->GetValue() == 0);
// The operands cannot be equal when generating a compare (assert) but since this is for testing purposes, hoist a src.
// Ideally, we would just create a BailOut instruction that generates a guaranteed bailout, but there seem to be issues
// with doing this in a non-helper path. So finally, it would generate:
// xor s0, s0
// test s0, s0
// jnz $continue
// $bailout:
// // bailout
// $continue:
instr->HoistSrc1(LowererMD::GetLoadOp(instr->GetSrc1()->GetType()));
}
InsertCompareBranch(instr->UnlinkSrc1(), instr->UnlinkSrc2(),
onEqual ? Js::OpCode::BrNeq_A : Js::OpCode::BrEq_A, continueLabelInstr, instr);
if (!isHelper)
{
IR::LabelInstr * helperLabelInstr = IR::LabelInstr::New(Js::OpCode::Label, m_func, true);
instr->InsertBefore(helperLabelInstr);
#if DBG
helperLabelInstr->m_noLazyHelperAssert = true;
#endif
}
}
IR::Instr *
Lowerer::LowerBailOnEqualOrNotEqual(IR::Instr * instr,
IR::BranchInstr *branchInstr, // = nullptr
IR::LabelInstr *labelBailOut, // = nullptr
IR::PropertySymOpnd * propSymOpnd, // = nullptr
bool isHelper) // = false
{
IR::Instr * prevInstr = instr->m_prev;
// Bail out test
bool onEqual = instr->m_opcode == Js::OpCode::BailOnEqual;
LowerBailoutCheckAndLabel(instr, onEqual, isHelper);
// BailOutOnImplicitCalls is a post-op bailout. Since we look at the profile info for LdFld/StFld to decide whether the instruction may or may not call an accessor,
// we need to update this profile information on the bailout path for BailOutOnImplicitCalls if the implicit call was an accessor call.
if(propSymOpnd && ((instr->GetBailOutKind() & ~IR::BailOutKindBits) == IR::BailOutOnImplicitCalls) && (propSymOpnd->m_inlineCacheIndex != -1) &&
instr->m_func->HasProfileInfo())
{
// result = AND implCallFlags, ~ImplicitCall_None
// TST result, ImplicitCall_Accessor
// JEQ $bail
// OR profiledFlags, ( FldInfo_FromAccessor | FldInfo_Polymorphic )
// $bail
IR::Opnd * implicitCallFlags = GetImplicitCallFlagsOpnd();
IR::Opnd * accessorImplicitCall = IR::IntConstOpnd::New(Js::ImplicitCall_Accessor & ~Js::ImplicitCall_None, GetImplicitCallFlagsType(), instr->m_func, true);
IR::Opnd * maskNoImplicitCall = IR::IntConstOpnd::New((Js::ImplicitCallFlags)~Js::ImplicitCall_None, GetImplicitCallFlagsType(), instr->m_func, true);
IR::Opnd * fldInfoAccessor = IR::IntConstOpnd::New(Js::FldInfo_FromAccessor | Js::FldInfo_Polymorphic, GetFldInfoFlagsType(), instr->m_func, true);
IR::LabelInstr * label = IR::LabelInstr::New(Js::OpCode::Label, instr->m_func, true);
IR::Instr * andInstr = InsertAnd(IR::RegOpnd::New(GetImplicitCallFlagsType(), instr->m_func), implicitCallFlags, maskNoImplicitCall, instr);
InsertTestBranch(andInstr->GetDst(), accessorImplicitCall, Js::OpCode::BrEq_A, label, instr);
intptr_t infoAddr = instr->m_func->GetReadOnlyProfileInfo()->GetFldInfoAddr(propSymOpnd->m_inlineCacheIndex);
IR::Opnd * profiledFlags = IR::MemRefOpnd::New(infoAddr + Js::FldInfo::GetOffsetOfFlags(), TyInt8, instr->m_func);
InsertOr(profiledFlags, profiledFlags, fldInfoAccessor, instr);
instr->InsertBefore(label);
}
this->GenerateBailOut(instr, branchInstr, labelBailOut);
return prevInstr;
}
void Lowerer::LowerBailOnNegative(IR::Instr *const instr)
{
Assert(instr);
Assert(instr->m_opcode == Js::OpCode::BailOnNegative);
Assert(instr->HasBailOutInfo());
Assert(!instr->GetDst());
Assert(instr->GetSrc1());
Assert(instr->GetSrc1()->GetType() == TyInt32 || instr->GetSrc1()->GetType() == TyUint32);
Assert(!instr->GetSrc2());
IR::LabelInstr *const skipBailOutLabel = instr->GetOrCreateContinueLabel(false);
LowerOneBailOutKind(instr, instr->GetBailOutKind(), false);
Assert(!instr->HasBailOutInfo());
IR::Instr *insertBeforeInstr = instr->m_next;
Func *const func = instr->m_func;
// test src, src
// jns $skipBailOut
InsertCompareBranch(
instr->UnlinkSrc1(),
IR::IntConstOpnd::New(0, TyInt32, func, true),
Js::OpCode::BrGe_A,
skipBailOutLabel,
insertBeforeInstr);
instr->Remove();
}
IR::Instr *
Lowerer::LowerBailOnNotObject(IR::Instr *instr,
IR::BranchInstr *branchInstr /* = nullptr */,
IR::LabelInstr *labelBailOut /* = nullptr */)
{
IR::Instr *prevInstr = instr->m_prev;
IR::LabelInstr *continueLabelInstr = IR::LabelInstr::New(Js::OpCode::Label,
m_func);
instr->InsertAfter(continueLabelInstr);
this->m_lowererMD.GenerateObjectTest(instr->UnlinkSrc1(),
instr,
continueLabelInstr,
/* fContinueLabel = */ true);
this->GenerateBailOut(instr, branchInstr, labelBailOut);
return prevInstr;
}
IR::Instr*
Lowerer::LowerCheckIsFuncObj(IR::Instr *instr, bool checkFuncInfo)
{
// The CheckIsFuncObj instr and CheckFuncInfo instr (checkFuncInfo = true) are used to
// generate bailout instrs that type check a function (and can also check the func info).
// Rather than creating these bailout instrs in Inline, they are created in Lower because
// CheckIsFuncObj and CheckFuncInfo instrs can be hoisted outside of loops and thus the
// bailout instrs created can exist outside of loops.
IR::RegOpnd *funcOpnd = instr->GetSrc1()->AsRegOpnd();
IR::BailOutKind bailOutKind = instr->GetBailOutKind();
BailOutInfo *bailOutInfo = instr->GetBailOutInfo();
// Check that the property is an object.
InsertObjectCheck(funcOpnd, instr, bailOutKind, bailOutInfo);
// Check that the object is a function with the correct type ID.
IR::Instr *lastInstr = InsertFunctionTypeIdCheck(funcOpnd, instr, bailOutKind, bailOutInfo);
if (checkFuncInfo)
{
// Check that the function body matches the func info.
lastInstr = InsertFunctionInfoCheck(
funcOpnd, instr, instr->GetSrc2()->AsAddrOpnd(), bailOutKind, bailOutInfo);
lastInstr->SetByteCodeOffset(instr);
}
if (bailOutInfo->bailOutInstr == instr)
{
// bailOutInstr is currently instr. By changing bailOutInstr to point to lastInstr, the next
// instruction to be lowered (lastInstr) will create the bailout target. This is necessary in
// cases where instr does not have a shared bailout (ex: instr was not hoisted outside of a loop).
bailOutInfo->bailOutInstr = lastInstr;
}
// the CheckFunctionEntryPoint instr exists in order to create the instrs above. It does not have
// any other purpose and thus it is removed. The instr's BailOutInfo continues to be used and thus
// must not be deleted. Flags are turned off to stop Remove() from deleting instr's BailOutInfo.
instr->hasBailOutInfo = false;
instr->hasAuxBailOut = false;
instr->Remove();
return lastInstr;
}
IR::Instr*
Lowerer::LowerBailOnTrue(IR::Instr* instr, IR::LabelInstr* labelBailOut /*nullptr*/)
{
IR::Instr* instrPrev = instr->m_prev;
IR::LabelInstr* continueLabel = instr->GetOrCreateContinueLabel();
IR::RegOpnd * regSrc1 = IR::RegOpnd::New(instr->GetSrc1()->GetType(), this->m_func);
InsertMove(regSrc1, instr->UnlinkSrc1(), instr);
InsertTestBranch(regSrc1, regSrc1, Js::OpCode::BrEq_A, continueLabel, instr);
GenerateBailOut(instr, nullptr, labelBailOut);
return instrPrev;
}
IR::Instr *
Lowerer::LowerBailOnNotBuiltIn(IR::Instr *instr,
IR::BranchInstr *branchInstr /* = nullptr */,
IR::LabelInstr *labelBailOut /* = nullptr */)
{
Assert(instr->GetSrc2()->IsIntConstOpnd());
IR::Instr *prevInstr = instr->m_prev;
intptr_t builtInFuncs = m_func->GetScriptContextInfo()->GetBuiltinFunctionsBaseAddr();
Js::BuiltinFunction builtInIndex = instr->UnlinkSrc2()->AsIntConstOpnd()->AsInt32();
IR::Opnd *builtIn = IR::MemRefOpnd::New((void*)(builtInFuncs + builtInIndex * MachPtr), TyMachReg, instr->m_func);
#if TESTBUILTINFORNULL
IR::LabelInstr * continueAfterTestLabel = IR::LabelInstr::New(Js::OpCode::Label, instr->m_func);
InsertTestBranch(builtIn, builtIn, Js::OpCode::BrNeq_A, continueAfterTestLabel, instr);
this->m_lowererMD.GenerateDebugBreak(instr);
instr->InsertBefore(continueAfterTestLabel);
#endif
IR::LabelInstr * continueLabel = IR::LabelInstr::New(Js::OpCode::Label, instr->m_func);
instr->InsertAfter(continueLabel);
InsertCompareBranch(instr->UnlinkSrc1(), builtIn, Js::OpCode::BrEq_A, continueLabel, instr);
GenerateBailOut(instr, branchInstr, labelBailOut);
return prevInstr;
}
#ifdef ENABLE_SCRIPT_DEBUGGING
IR::Instr *
Lowerer::LowerBailForDebugger(IR::Instr* instr, bool isInsideHelper /* = false */)
{
IR::Instr * prevInstr = instr->m_prev;
IR::BailOutKind bailOutKind = instr->GetBailOutKind();
AssertMsg(bailOutKind, "bailOutKind should not be zero at this time.");
AssertMsg(!(bailOutKind & IR::BailOutExplicit) || bailOutKind == IR::BailOutExplicit,
"BailOutExplicit cannot be combined with any other bailout flags.");
IR::LabelInstr* explicitBailOutLabel = nullptr;
if (!(bailOutKind & IR::BailOutExplicit))
{
intptr_t flags = m_func->GetScriptContextInfo()->GetDebuggingFlagsAddr();
// Check 1 (do we need to bail out?)
// JXX bailoutLabel
// Check 2 (do we need to bail out?)
// JXX bailoutLabel
// ...
// JMP continueLabel
// bailoutDocumentLabel:
// (determine if document boundary reached - if not, JMP to continueLabel)
// NOTE: THIS BLOCK IS CONDITIONALLY GENERATED BASED ON doGenerateBailOutDocumentBlock
// bailoutLabel:
// bail out
// continueLabel:
// ...
IR::LabelInstr* bailOutDocumentLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func, /*isOpHelper*/ true);
instr->InsertBefore(bailOutDocumentLabel);
IR::LabelInstr* bailOutLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func, /*isOpHelper*/ true);
instr->InsertBefore(bailOutLabel);
IR::LabelInstr* continueLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func, /*isOpHelper*/ isInsideHelper);
instr->InsertAfter(continueLabel);
IR::BranchInstr* continueBranchInstr = this->InsertBranch(Js::OpCode::Br, continueLabel, bailOutDocumentLabel); // JMP continueLabel.
bool doGenerateBailOutDocumentBlock = false;
const IR::BailOutKind c_forceAndIgnoreEx = IR::BailOutForceByFlag | IR::BailOutIgnoreException;
if ((bailOutKind & c_forceAndIgnoreEx) == c_forceAndIgnoreEx)
{
// It's faster to check these together in 1 check rather than 2 separate checks at run time.
// CMP [&(flags->m_forceInterpreter, flags->m_isIgnoreException)], 0
// BNE bailout
IR::Opnd* opnd1 = IR::MemRefOpnd::New((BYTE*)flags + DebuggingFlags::GetForceInterpreterOffset(), TyInt16, m_func);
IR::Opnd* opnd2 = IR::IntConstOpnd::New(0, TyInt16, m_func, /*dontEncode*/ true);
InsertCompareBranch(opnd1, opnd2, Js::OpCode::BrNeq_A, bailOutLabel, continueBranchInstr);
bailOutKind ^= c_forceAndIgnoreEx;
}
else
{
if (bailOutKind & IR::BailOutForceByFlag)
{
// CMP [&flags->m_forceInterpreter], 0
// BNE bailout
IR::Opnd* opnd1 = IR::MemRefOpnd::New((BYTE*)flags + DebuggingFlags::GetForceInterpreterOffset(), TyInt8, m_func);
IR::Opnd* opnd2 = IR::IntConstOpnd::New(0, TyInt8, m_func, /*dontEncode*/ true);
InsertCompareBranch(opnd1, opnd2, Js::OpCode::BrNeq_A, bailOutLabel, continueBranchInstr);
bailOutKind ^= IR::BailOutForceByFlag;
}
if (bailOutKind & IR::BailOutIgnoreException)
{
// CMP [&flags->m_byteCodeOffsetAfterIgnoreException], DebuggingFlags::InvalidByteCodeOffset
// BNE bailout
IR::Opnd* opnd1 = IR::MemRefOpnd::New((BYTE*)flags + DebuggingFlags::GetByteCodeOffsetAfterIgnoreExceptionOffset(), TyInt32, m_func);
IR::Opnd* opnd2 = IR::IntConstOpnd::New(DebuggingFlags::InvalidByteCodeOffset, TyInt32, m_func, /*dontEncode*/ true);
InsertCompareBranch(opnd1, opnd2, Js::OpCode::BrNeq_A, bailOutLabel, continueBranchInstr);
bailOutKind ^= IR::BailOutIgnoreException;
}
}
if (bailOutKind & IR::BailOutBreakPointInFunction)
{
// CMP [&functionBody->m_sourceInfo.m_probeCount], 0
// BNE bailout
IR::Opnd* opnd1 = IR::MemRefOpnd::New(m_func->GetJITFunctionBody()->GetProbeCountAddr(), TyInt32, m_func);
IR::Opnd* opnd2 = IR::IntConstOpnd::New(0, TyInt32, m_func, /*dontEncode*/ true);
InsertCompareBranch(opnd1, opnd2, Js::OpCode::BrNeq_A, bailOutLabel, continueBranchInstr);
bailOutKind ^= IR::BailOutBreakPointInFunction;
}
// on method entry
if(bailOutKind & IR::BailOutStep)
{
// TEST STEP_BAILOUT, [&stepController->StepType]
// BNE BailoutLabel
IR::Opnd* opnd1 = IR::MemRefOpnd::New(m_func->GetScriptContextInfo()->GetDebugStepTypeAddr(), TyInt8, m_func);
IR::Opnd* opnd2 = IR::IntConstOpnd::New(Js::STEP_BAILOUT, TyInt8, this->m_func, /*dontEncode*/ true);
InsertTestBranch(opnd1, opnd2, Js::OpCode::BrNeq_A, bailOutLabel, continueBranchInstr);
// CMP STEP_DOCUMENT, [&stepController->StepType]
// BEQ BailoutDocumentLabel
opnd1 = IR::MemRefOpnd::New(m_func->GetScriptContextInfo()->GetDebugStepTypeAddr(), TyInt8, m_func);
opnd2 = IR::IntConstOpnd::New(Js::STEP_DOCUMENT, TyInt8, this->m_func, /*dontEncode*/ true);
InsertCompareBranch(opnd1, opnd2, Js::OpCode::BrEq_A, /*isUnsigned*/ true, bailOutDocumentLabel, continueBranchInstr);
doGenerateBailOutDocumentBlock = true;
bailOutKind ^= IR::BailOutStep;
}
// on method exit
if (bailOutKind & IR::BailOutStackFrameBase)
{
// CMP EffectiveFrameBase, [&stepController->frameAddrWhenSet]
// BA bailoutLabel
RegNum effectiveFrameBaseReg;
#ifdef _M_X64
effectiveFrameBaseReg = m_lowererMD.GetRegStackPointer();
#else
effectiveFrameBaseReg = m_lowererMD.GetRegFramePointer();
#endif
IR::Opnd* opnd1 = IR::RegOpnd::New(nullptr, effectiveFrameBaseReg, TyMachReg, m_func);
IR::Opnd* opnd2 = IR::MemRefOpnd::New(m_func->GetScriptContextInfo()->GetDebugFrameAddressAddr(), TyMachReg, m_func);
this->InsertCompareBranch(opnd1, opnd2, Js::OpCode::BrGt_A, /*isUnsigned*/ true, bailOutLabel, continueBranchInstr);
// CMP STEP_DOCUMENT, [&stepController->StepType]
// BEQ BailoutDocumentLabel
opnd1 = IR::MemRefOpnd::New(m_func->GetScriptContextInfo()->GetDebugStepTypeAddr(), TyInt8, m_func);
opnd2 = IR::IntConstOpnd::New(Js::STEP_DOCUMENT, TyInt8, this->m_func, /*dontEncode*/ true);
InsertCompareBranch(opnd1, opnd2, Js::OpCode::BrEq_A, /*isUnsigned*/ true, bailOutDocumentLabel, continueBranchInstr);
doGenerateBailOutDocumentBlock = true;
bailOutKind ^= IR::BailOutStackFrameBase;
}
if (bailOutKind & IR::BailOutLocalValueChanged)
{
int32 hasLocalVarChangedOffset = m_func->GetHasLocalVarChangedOffset();
if (hasLocalVarChangedOffset != Js::Constants::InvalidOffset)
{
// CMP [EBP + hasLocalVarChangedStackOffset], 0
// BNE bailout
StackSym* sym = StackSym::New(TyInt8, m_func);
sym->m_offset = hasLocalVarChangedOffset;
sym->m_allocated = true;
IR::Opnd* opnd1 = IR::SymOpnd::New(sym, TyInt8, m_func);
IR::Opnd* opnd2 = IR::IntConstOpnd::New(0, TyInt8, m_func);
InsertCompareBranch(opnd1, opnd2, Js::OpCode::BrNeq_A, bailOutLabel, continueBranchInstr);
}
bailOutKind ^= IR::BailOutLocalValueChanged;
}
if (doGenerateBailOutDocumentBlock)
{
// GENERATE the BailoutDocumentLabel
// bailOutDocumentLabel:
// CMP CurrentScriptId, [&stepController->ScriptIdWhenSet]
// BEQ ContinueLabel
// bailOutLabel: // (fallthrough bailOutLabel)
IR::Opnd* opnd1 = IR::MemRefOpnd::New(m_func->GetJITFunctionBody()->GetScriptIdAddr(), TyInt32, m_func);
IR::Opnd* opnd2 = IR::MemRefOpnd::New(m_func->GetScriptContextInfo()->GetDebugScriptIdWhenSetAddr(), TyInt32, m_func);
IR::RegOpnd* reg1 = IR::RegOpnd::New(TyInt32, m_func);
InsertMove(reg1, opnd2, bailOutLabel);
InsertCompareBranch(opnd1, reg1, Js::OpCode::BrEq_A, /*isUnsigned*/ true, continueLabel, bailOutLabel);
}
AssertMsg(bailOutKind == (IR::BailOutKind)0, "Some of the bits in BailOutKind were not processed!");
// Note: at this time the 'instr' is in between bailoutLabel and continueLabel.
}
else
{
// For explicit/unconditional bailout use label which is not a helper, otherwise we would get a helper in main code path
// which breaks helper label consistency (you can only get to helper from a conditional branch in main code), see DbCheckPostLower.
explicitBailOutLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, false);
}
this->GenerateBailOut(instr, nullptr, explicitBailOutLabel);
return prevInstr;
}
#endif
IR::Instr*
Lowerer::LowerBailOnException(IR::Instr * instr)
{
Assert(instr->HasBailOutInfo());
IR::Instr * instrPrev = instr->m_prev;
this->GenerateBailOut(instr, nullptr, nullptr);
return instrPrev;
}
IR::Instr*
Lowerer::LowerBailOnEarlyExit(IR::Instr * instr)
{
Assert(instr->HasBailOutInfo());
IR::Instr * instrPrev = instr->m_prev;
this->GenerateBailOut(instr, nullptr, nullptr);
return instrPrev;
}
// Generate BailOut Lowerer Instruction if the value is INT_MIN.
// It it's not INT_MIN, we continue without bailout.
IR::Instr *
Lowerer::LowerBailOnIntMin(IR::Instr *instr, IR::BranchInstr *branchInstr /* = nullptr */, IR::LabelInstr *labelBailOut /* = nullptr */)
{
Assert(instr);
Assert(instr->GetSrc1());
IR::LabelInstr *continueLabelInstr = IR::LabelInstr::New(Js::OpCode::Label, m_func);
instr->InsertAfter(continueLabelInstr);
if(!instr->HasBailOutInfo())
{
instr->Remove();
}
else
{
Assert(instr->GetBailOutKind() == IR::BailOnIntMin);
// Note: src1 must be int32 at this point.
if (instr->GetSrc1()->IsIntConstOpnd())
{
// For consts we can check the value at JIT time. Note: without this check we'll have to legalize the CMP instr.
IR::IntConstOpnd* intConst = instr->UnlinkSrc1()->AsIntConstOpnd();
if (intConst->GetValue() == INT_MIN)
{
this->GenerateBailOut(instr, branchInstr, labelBailOut);
intConst->Free(instr->m_func);
}
else
{
instr->Remove();
}
}
else
{
InsertCompareBranch(instr->UnlinkSrc1(), IR::IntConstOpnd::New(INT_MIN, TyInt32, this->m_func), Js::OpCode::BrNeq_A, continueLabelInstr, instr);
this->GenerateBailOut(instr, branchInstr, labelBailOut);
}
}
return continueLabelInstr;
}
///----------------------------------------------------------------------------
///
/// Lowerer::LowerBailOnNotString
/// Generate BailOut Lowerer Instruction if not a String
///
///----------------------------------------------------------------------------
void Lowerer::LowerBailOnNotString(IR::Instr *instr)
{
if (!instr->GetSrc1()->GetValueType().IsString())
{
/*Creating a MOV instruction*/
IR::Instr * movInstr = IR::Instr::New(instr->m_opcode, instr->UnlinkDst(), instr->UnlinkSrc1(), instr->m_func);
instr->InsertBefore(movInstr);
IR::LabelInstr *continueLabelInstr = IR::LabelInstr::New(Js::OpCode::Label, m_func);
IR::LabelInstr *helperLabelInstr = IR::LabelInstr::New(Js::OpCode::Label, m_func, true);
instr->InsertAfter(continueLabelInstr);
IR::RegOpnd *srcReg = movInstr->GetSrc1()->IsRegOpnd() ? movInstr->GetSrc1()->AsRegOpnd() : nullptr;
this->GenerateStringTest(srcReg, instr, helperLabelInstr, continueLabelInstr);
this->GenerateBailOut(instr, nullptr, helperLabelInstr);
}
else
{
instr->ClearBailOutInfo();
}
}
void Lowerer::LowerOneBailOutKind(
IR::Instr *const instr,
const IR::BailOutKind bailOutKindToLower,
const bool isInHelperBlock,
const bool preserveBailOutKindInInstr)
{
Assert(instr);
Assert(bailOutKindToLower);
Assert(!(bailOutKindToLower & IR::BailOutKindBits) || !(bailOutKindToLower & bailOutKindToLower - 1u));
Func *const func = instr->m_func;
// Split bailouts other than the one being lowered here
BailOutInfo *const bailOutInfo = instr->GetBailOutInfo();
IR::BailOutKind bailOutKind = instr->GetBailOutKind();
Assert(
bailOutKindToLower & IR::BailOutKindBits
? bailOutKind & bailOutKindToLower
: (bailOutKind & ~IR::BailOutKindBits) == bailOutKindToLower);
if(!preserveBailOutKindInInstr)
{
bailOutKind -= bailOutKindToLower;
}
if(bailOutKind)
{
if(bailOutInfo->bailOutInstr == instr)
{
// Create a shared bailout point for the split bailout checks
IR::Instr *const sharedBail = instr->ShareBailOut();
Assert(sharedBail->GetBailOutInfo() == bailOutInfo);
GenerateBailOut(sharedBail);
}
instr->SetBailOutKind(bailOutKind);
}
else
{
instr->UnlinkBailOutInfo();
if(bailOutInfo->bailOutInstr == instr)
{
bailOutInfo->bailOutInstr = nullptr;
}
}
IR::Instr *const insertBeforeInstr = instr->m_next;
// (Bail out with the requested bail out kind)
IR::BailOutInstr *const bailOutInstr = IR::BailOutInstr::New(Js::OpCode::BailOut, bailOutKindToLower, bailOutInfo, func);
bailOutInstr->SetByteCodeOffset(instr);
insertBeforeInstr->InsertBefore(bailOutInstr);
GenerateBailOut(bailOutInstr);
// The caller is expected to generate code to decide whether to bail out
}
void Lowerer::SplitBailOnNotArray(
IR::Instr *const instr,
IR::Instr * *const bailOnNotArrayRef,
IR::Instr * *const bailOnMissingValueRef)
{
Assert(instr);
Assert(!instr->GetDst());
Assert(instr->GetSrc1());
Assert(instr->GetSrc1()->IsRegOpnd());
Assert(!instr->GetSrc2());
Assert(bailOnNotArrayRef);
Assert(bailOnMissingValueRef);
IR::Instr *&bailOnNotArray = *bailOnNotArrayRef;
IR::Instr *&bailOnMissingValue = *bailOnMissingValueRef;
bailOnNotArray = instr;
bailOnMissingValue = nullptr;
IR::BailOutKind bailOutKind = instr->GetBailOutKind();
if(bailOutKind == IR::BailOutOnNotArray ||
bailOutKind == IR::BailOutOnNotNativeArray)
{
return;
}
// Split array checks
BailOutInfo *const bailOutInfo = instr->GetBailOutInfo();
if(bailOutInfo->bailOutInstr == instr)
{
// Create a shared bailout point for the split bailout checks
IR::Instr *const sharedBail = instr->ShareBailOut();
Assert(sharedBail->GetBailOutInfo() == bailOutInfo);
LowerBailTarget(sharedBail);
}
bailOutKind -= IR::BailOutOnMissingValue;
Assert(bailOutKind == IR::BailOutOnNotArray ||
bailOutKind == IR::BailOutOnNotNativeArray);
instr->SetBailOutKind(bailOutKind);
Func *const func = bailOutInfo->bailOutFunc;
IR::Instr *const insertBeforeInstr = instr->m_next;
// Split missing value checks
bailOnMissingValue = IR::BailOutInstr::New(Js::OpCode::BailOnNotArray, IR::BailOutOnMissingValue, bailOutInfo, func);
bailOnMissingValue->SetByteCodeOffset(instr);
insertBeforeInstr->InsertBefore(bailOnMissingValue);
}
IR::RegOpnd *Lowerer::LowerBailOnNotArray(IR::Instr *const instr)
{
Assert(instr);
Assert(!instr->GetDst());
Assert(instr->GetSrc1());
Assert(instr->GetSrc1()->IsRegOpnd());
Assert(!instr->GetSrc2());
Func *const func = instr->m_func;
// Label to jump to (or fall through to) when bailing out
const auto bailOutLabel = IR::LabelInstr::New(Js::OpCode::Label, func, true /* isOpHelper */);
instr->InsertBefore(bailOutLabel);
// Label to jump to when not bailing out
const auto skipBailOutLabel = IR::LabelInstr::New(Js::OpCode::Label, func);
instr->InsertAfter(skipBailOutLabel);
// Do the array tests and jump to bailOutLabel if it's not an array. Fall through if it is an array.
IR::RegOpnd *const arrayOpnd =
GenerateArrayTest(instr->UnlinkSrc1()->AsRegOpnd(), bailOutLabel, bailOutLabel, bailOutLabel, true);
// Skip bail-out when it is an array
InsertBranch(Js::OpCode::Br, skipBailOutLabel, bailOutLabel);
// Generate the bailout helper call. 'instr' will be changed to the CALL into the bailout function, so it can't be used for
// ordering instructions anymore.
GenerateBailOut(instr);
return arrayOpnd;
}
void Lowerer::LowerBailOnMissingValue(IR::Instr *const instr, IR::RegOpnd *const arrayOpnd)
{
Assert(instr);
Assert(!instr->GetDst());
Assert(!instr->GetSrc1());
Assert(!instr->GetSrc2());
Assert(arrayOpnd);
Assert(arrayOpnd->GetValueType().IsArrayOrObjectWithArray());
Func *const func = instr->m_func;
// Label to jump to when not bailing out
const auto skipBailOutLabel = IR::LabelInstr::New(Js::OpCode::Label, func);
instr->InsertAfter(skipBailOutLabel);
// Skip bail-out when the array has no missing values
//
// test [array + offsetOf(objectArrayOrFlags)], Js::DynamicObjectFlags::HasNoMissingValues
// jnz $skipBailOut
const IR::AutoReuseOpnd autoReuseArrayOpnd(arrayOpnd, func);
CompileAssert(
static_cast<Js::DynamicObjectFlags>(static_cast<uint8>(Js::DynamicObjectFlags::HasNoMissingValues)) ==
Js::DynamicObjectFlags::HasNoMissingValues);
InsertTestBranch(
IR::IndirOpnd::New(arrayOpnd, Js::JavascriptArray::GetOffsetOfArrayFlags(), TyUint8, func),
IR::IntConstOpnd::New(static_cast<uint8>(Js::DynamicObjectFlags::HasNoMissingValues), TyUint8, func, true),
Js::OpCode::BrNeq_A,
skipBailOutLabel,
instr);
// Generate the bailout helper call. 'instr' will be changed to the CALL into the bailout function, so it can't be used for
// ordering instructions anymore.
GenerateBailOut(instr);
}
void Lowerer::LowerBailOnInvalidatedArrayHeadSegment(IR::Instr *const instr, const bool isInHelperBlock)
{
/*
// Generate checks for whether the head segment or the head segment length changed during the helper call
if(!(baseValueType.IsArrayOrObjectWithArray() && arrayOpnd && arrayOpnd.HeadSegmentSym()))
{
// Record the array head segment before the helper call
headSegmentBeforeHelperCall = Js::JavascriptArray::Jit_GetArrayHeadSegmentForArrayOrObjectWithArray(base)
}
if(!(baseValueType.IsArrayOrObjectWithArray() && arrayOpnd && arrayOpnd.HeadSegmentLengthSym()))
{
// Record the array head segment length before the helper call
if(baseValueType.IsArrayOrObjectWithArray() && arrayOpnd && arrayOpnd.HeadSegmentSym())
{
mov headSegmentLengthBeforeHelperCall, [headSegmentBeforeHelperCall + offsetOf(length)]
}
else
{
headSegmentLengthBeforeHelperCall =
Js::JavascriptArray::Jit_GetArrayHeadSegmentLength(headSegmentBeforeHelperCall)
}
}
helperCall:
(Helper call and other bailout checks)
// If the array has a different head segment or head segment length after the helper call, then this store needs to bail
// out
invalidatedHeadSegment =
JavascriptArray::Jit_OperationInvalidatedArrayHeadSegment(
headSegmentBeforeHelperCall,
headSegmentLengthBeforeHelperCall,
base)
test invalidatedHeadSegment, invalidatedHeadSegment
jz $skipBailOut
(Bail out with IR::BailOutOnInvalidatedArrayHeadSegment)
$skipBailOut:
*/
Assert(instr);
Assert(instr->m_opcode == Js::OpCode::StElemI_A || instr->m_opcode == Js::OpCode::StElemI_A_Strict || instr->m_opcode == Js::OpCode::Memset || instr->m_opcode == Js::OpCode::Memcopy);
Assert(instr->GetDst());
Assert(instr->GetDst()->IsIndirOpnd());
Func *const func = instr->m_func;
IR::RegOpnd *const baseOpnd = instr->GetDst()->AsIndirOpnd()->GetBaseOpnd();
const ValueType baseValueType(baseOpnd->GetValueType());
Assert(!baseValueType.IsNotArrayOrObjectWithArray());
const bool isArrayOrObjectWithArray = baseValueType.IsArrayOrObjectWithArray();
IR::ArrayRegOpnd *const arrayOpnd = baseOpnd->IsArrayRegOpnd() ? baseOpnd->AsArrayRegOpnd() : nullptr;
IR::RegOpnd *headSegmentBeforeHelperCallOpnd;
IR::AutoReuseOpnd autoReuseHeadSegmentBeforeHelperCallOpnd;
if(isArrayOrObjectWithArray && arrayOpnd && arrayOpnd->HeadSegmentSym())
{
headSegmentBeforeHelperCallOpnd = IR::RegOpnd::New(arrayOpnd->HeadSegmentSym(), TyMachPtr, func);
autoReuseHeadSegmentBeforeHelperCallOpnd.Initialize(headSegmentBeforeHelperCallOpnd, func);
}
else
{
// Record the array head segment before the helper call
// headSegmentBeforeHelperCall = Js::JavascriptArray::Jit_GetArrayHeadSegmentForArrayOrObjectWithArray(base)
m_lowererMD.LoadHelperArgument(instr, baseOpnd);
IR::Instr *const callInstr = IR::Instr::New(Js::OpCode::Call, func);
headSegmentBeforeHelperCallOpnd = IR::RegOpnd::New(StackSym::New(TyMachPtr, func), TyMachPtr, func);
autoReuseHeadSegmentBeforeHelperCallOpnd.Initialize(headSegmentBeforeHelperCallOpnd, func);
callInstr->SetDst(headSegmentBeforeHelperCallOpnd);
instr->InsertBefore(callInstr);
m_lowererMD.ChangeToHelperCall(callInstr, IR::HelperArray_Jit_GetArrayHeadSegmentForArrayOrObjectWithArray);
}
IR::RegOpnd *headSegmentLengthBeforeHelperCallOpnd;
IR::AutoReuseOpnd autoReuseHeadSegmentLengthBeforeHelperCallOpnd;
if(isArrayOrObjectWithArray && arrayOpnd && arrayOpnd->HeadSegmentLengthSym())
{
headSegmentLengthBeforeHelperCallOpnd = IR::RegOpnd::New(arrayOpnd->HeadSegmentLengthSym(), TyUint32, func);
autoReuseHeadSegmentLengthBeforeHelperCallOpnd.Initialize(headSegmentLengthBeforeHelperCallOpnd, func);
}
else
{
headSegmentLengthBeforeHelperCallOpnd = IR::RegOpnd::New(StackSym::New(TyUint32, func), TyUint32, func);
autoReuseHeadSegmentLengthBeforeHelperCallOpnd.Initialize(headSegmentLengthBeforeHelperCallOpnd, func);
if(isArrayOrObjectWithArray && arrayOpnd && arrayOpnd->HeadSegmentSym())
{
// Record the array head segment length before the helper call
// mov headSegmentLengthBeforeHelperCall, [headSegmentBeforeHelperCall + offsetOf(length)]
InsertMove(
headSegmentLengthBeforeHelperCallOpnd,
IR::IndirOpnd::New(
headSegmentBeforeHelperCallOpnd,
Js::SparseArraySegmentBase::GetOffsetOfLength(),
TyUint32,
func),
instr);
}
else
{
// Record the array head segment length before the helper call
// headSegmentLengthBeforeHelperCall =
// Js::JavascriptArray::Jit_GetArrayHeadSegmentLength(headSegmentBeforeHelperCall)
m_lowererMD.LoadHelperArgument(instr, headSegmentBeforeHelperCallOpnd);
IR::Instr *const callInstr = IR::Instr::New(Js::OpCode::Call, func);
callInstr->SetDst(headSegmentLengthBeforeHelperCallOpnd);
instr->InsertBefore(callInstr);
m_lowererMD.ChangeToHelperCall(callInstr, IR::HelperArray_Jit_GetArrayHeadSegmentLength);
}
}
IR::LabelInstr *const skipBailOutLabel = instr->GetOrCreateContinueLabel(isInHelperBlock);
LowerOneBailOutKind(instr, IR::BailOutOnInvalidatedArrayHeadSegment, isInHelperBlock);
IR::Instr *const insertBeforeInstr = instr->m_next;
// If the array has a different head segment or head segment length after the helper call, then this store needs to bail out
// invalidatedHeadSegment =
// JavascriptArray::Jit_OperationInvalidatedArrayHeadSegment(
// headSegmentBeforeHelperCall,
// headSegmentLengthBeforeHelperCall,
// base)
m_lowererMD.LoadHelperArgument(insertBeforeInstr, baseOpnd);
m_lowererMD.LoadHelperArgument(insertBeforeInstr, headSegmentLengthBeforeHelperCallOpnd);
m_lowererMD.LoadHelperArgument(insertBeforeInstr, headSegmentBeforeHelperCallOpnd);
IR::Instr *const callInstr = IR::Instr::New(Js::OpCode::Call, func);
IR::RegOpnd *const invalidatedHeadSegmentOpnd = IR::RegOpnd::New(TyUint8, func);
const IR::AutoReuseOpnd autoReuseInvalidatedHeadSegmentOpnd(invalidatedHeadSegmentOpnd, func);
callInstr->SetDst(invalidatedHeadSegmentOpnd);
insertBeforeInstr->InsertBefore(callInstr);
m_lowererMD.ChangeToHelperCall(callInstr, IR::HelperArray_Jit_OperationInvalidatedArrayHeadSegment);
// test invalidatedHeadSegment, invalidatedHeadSegment
// jz $skipBailOut
InsertTestBranch(
invalidatedHeadSegmentOpnd,
invalidatedHeadSegmentOpnd,
Js::OpCode::BrEq_A,
skipBailOutLabel,
insertBeforeInstr);
// (Bail out with IR::BailOutOnInvalidatedArrayHeadSegment)
// $skipBailOut:
}
void Lowerer::LowerBailOnInvalidatedArrayLength(IR::Instr *const instr, const bool isInHelperBlock)
{
/*
// Generate checks for whether the length changed during the helper call
if(!(arrayOpnd && arrayOpnd.LengthSym() && arrayOpnd.LengthSym() != arrayOpnd.HeadSegmentLengthSym()))
{
// Record the array length before the helper call
lengthBeforeHelperCall = Js::JavascriptArray::Jit_GetArrayLength(base)
}
helperCall:
(Helper call and other bailout checks)
// If the array has a different length after the helper call, then this store needs to bail out
invalidatedLength = JavascriptArray::Jit_OperationInvalidatedArrayLength(lengthBeforeHelperCall, base)
test invalidatedLength, invalidatedLength
jz $skipBailOut
(Bail out with IR::BailOutOnInvalidatedArrayLength)
$skipBailOut:
*/
Assert(instr);
Assert(instr->m_opcode == Js::OpCode::StElemI_A || instr->m_opcode == Js::OpCode::StElemI_A_Strict || instr->m_opcode == Js::OpCode::Memset || instr->m_opcode == Js::OpCode::Memcopy);
Assert(instr->GetDst());
Assert(instr->GetDst()->IsIndirOpnd());
Func *const func = instr->m_func;
IR::RegOpnd *const baseOpnd = instr->GetDst()->AsIndirOpnd()->GetBaseOpnd();
const ValueType baseValueType(baseOpnd->GetValueType());
Assert(!baseValueType.IsNotArray());
IR::ArrayRegOpnd *const arrayOpnd = baseOpnd->IsArrayRegOpnd() ? baseOpnd->AsArrayRegOpnd() : nullptr;
IR::RegOpnd *lengthBeforeHelperCallOpnd;
IR::AutoReuseOpnd autoReuseLengthBeforeHelperCallOpnd;
if(arrayOpnd && arrayOpnd->LengthSym() && arrayOpnd->LengthSym() != arrayOpnd->HeadSegmentLengthSym())
{
lengthBeforeHelperCallOpnd = IR::RegOpnd::New(arrayOpnd->LengthSym(), arrayOpnd->LengthSym()->GetType(), func);
autoReuseLengthBeforeHelperCallOpnd.Initialize(lengthBeforeHelperCallOpnd, func);
}
else
{
// Record the array length before the helper call
// lengthBeforeHelperCall = Js::JavascriptArray::Jit_GetArrayLength(base)
m_lowererMD.LoadHelperArgument(instr, baseOpnd);
IR::Instr *const callInstr = IR::Instr::New(Js::OpCode::Call, func);
lengthBeforeHelperCallOpnd = IR::RegOpnd::New(TyUint32, func);
autoReuseLengthBeforeHelperCallOpnd.Initialize(lengthBeforeHelperCallOpnd, func);
callInstr->SetDst(lengthBeforeHelperCallOpnd);
instr->InsertBefore(callInstr);
m_lowererMD.ChangeToHelperCall(callInstr, IR::HelperArray_Jit_GetArrayLength);
}
IR::LabelInstr *const skipBailOutLabel = instr->GetOrCreateContinueLabel(isInHelperBlock);
LowerOneBailOutKind(instr, IR::BailOutOnInvalidatedArrayLength, isInHelperBlock);
IR::Instr *const insertBeforeInstr = instr->m_next;
// If the array has a different length after the helper call, then this store needs to bail out
// invalidatedLength = JavascriptArray::Jit_OperationInvalidatedArrayLength(lengthBeforeHelperCall, base)
m_lowererMD.LoadHelperArgument(insertBeforeInstr, baseOpnd);
m_lowererMD.LoadHelperArgument(insertBeforeInstr, lengthBeforeHelperCallOpnd);
IR::Instr *const callInstr = IR::Instr::New(Js::OpCode::Call, func);
IR::RegOpnd *const invalidatedLengthOpnd = IR::RegOpnd::New(TyUint8, func);
const IR::AutoReuseOpnd autoReuseInvalidatedLengthOpnd(invalidatedLengthOpnd, func);
callInstr->SetDst(invalidatedLengthOpnd);
insertBeforeInstr->InsertBefore(callInstr);
m_lowererMD.ChangeToHelperCall(callInstr, IR::HelperArray_Jit_OperationInvalidatedArrayLength);
// test invalidatedLength, invalidatedLength
// jz $skipBailOut
InsertTestBranch(
invalidatedLengthOpnd,
invalidatedLengthOpnd,
Js::OpCode::BrEq_A,
skipBailOutLabel,
insertBeforeInstr);
// (Bail out with IR::BailOutOnInvalidatedArrayLength)
// $skipBailOut:
}
void Lowerer::LowerBailOnCreatedMissingValue(IR::Instr *const instr, const bool isInHelperBlock)
{
/*
// Generate checks for whether the first missing value was created during the helper call
if(!(baseValueType.IsArrayOrObjectWithArray() && baseValueType.HasNoMissingValues()))
{
// Record whether the array has missing values before the helper call
arrayFlagsBeforeHelperCall = Js::JavascriptArray::Jit_GetArrayFlagsForArrayOrObjectWithArray(base)
}
helperCall:
(Helper call and other bailout checks)
// If the array had no missing values before the helper call, and the array has missing values after the helper
// call, then this store created the first missing value in the array and needs to bail out
if(baseValueType.IsArrayOrObjectWithArray() && baseValueType.HasNoMissingValues())
(arrayFlagsBeforeHelperCall = Js::DynamicObjectFlags::HasNoMissingValues)
createdFirstMissingValue = JavascriptArray::Jit_OperationCreatedFirstMissingValue(arrayFlagsBeforeHelperCall, base)
test createdFirstMissingValue, createdFirstMissingValue
jz $skipBailOut
(Bail out with IR::BailOutOnMissingValue)
$skipBailOut:
*/
Assert(instr);
Assert(instr->m_opcode == Js::OpCode::StElemI_A || instr->m_opcode == Js::OpCode::StElemI_A_Strict || instr->m_opcode == Js::OpCode::Memset || instr->m_opcode == Js::OpCode::Memcopy);
Assert(instr->GetDst());
Assert(instr->GetDst()->IsIndirOpnd());
Func *const func = instr->m_func;
IR::RegOpnd *const baseOpnd = instr->GetDst()->AsIndirOpnd()->GetBaseOpnd();
const ValueType baseValueType(baseOpnd->GetValueType());
Assert(!baseValueType.IsNotArrayOrObjectWithArray());
IR::Opnd *arrayFlagsBeforeHelperCallOpnd = nullptr;
IR::AutoReuseOpnd autoReuseArrayFlagsBeforeHelperCallOpnd;
const IRType arrayFlagsType = sizeof(uintptr_t) == sizeof(uint32) ? TyUint32 : TyUint64;
if(!(baseValueType.IsArrayOrObjectWithArray() && baseValueType.HasNoMissingValues()))
{
// Record whether the array has missing values before the helper call
// arrayFlagsBeforeHelperCall = Js::JavascriptArray::Jit_GetArrayFlagsForArrayOrObjectWithArray(base)
m_lowererMD.LoadHelperArgument(instr, baseOpnd);
IR::Instr *const callInstr = IR::Instr::New(Js::OpCode::Call, func);
arrayFlagsBeforeHelperCallOpnd = IR::RegOpnd::New(arrayFlagsType, func);
autoReuseArrayFlagsBeforeHelperCallOpnd.Initialize(arrayFlagsBeforeHelperCallOpnd, func);
callInstr->SetDst(arrayFlagsBeforeHelperCallOpnd);
instr->InsertBefore(callInstr);
m_lowererMD.ChangeToHelperCall(callInstr, IR::HelperArray_Jit_GetArrayFlagsForArrayOrObjectWithArray);
}
IR::LabelInstr *const skipBailOutLabel = instr->GetOrCreateContinueLabel(isInHelperBlock);
LowerOneBailOutKind(instr, IR::BailOutOnMissingValue, isInHelperBlock);
IR::Instr *const insertBeforeInstr = instr->m_next;
// If the array had no missing values before the helper call, and the array has missing values after the helper
// call, then this store created the first missing value in the array and needs to bail out
if(baseValueType.IsArrayOrObjectWithArray() && baseValueType.HasNoMissingValues())
{
// (arrayFlagsBeforeHelperCall = Js::DynamicObjectFlags::HasNoMissingValues)
Assert(!arrayFlagsBeforeHelperCallOpnd);
arrayFlagsBeforeHelperCallOpnd =
arrayFlagsType == TyUint32
? static_cast<IR::Opnd *>(
IR::IntConstOpnd::New(
static_cast<uintptr_t>(Js::DynamicObjectFlags::HasNoMissingValues),
arrayFlagsType,
func,
true))
: IR::AddrOpnd::New(
reinterpret_cast<void *>(Js::DynamicObjectFlags::HasNoMissingValues),
IR::AddrOpndKindConstantVar,
func,
true);
autoReuseArrayFlagsBeforeHelperCallOpnd.Initialize(arrayFlagsBeforeHelperCallOpnd, func);
}
else
{
Assert(arrayFlagsBeforeHelperCallOpnd);
}
// createdFirstMissingValue = JavascriptArray::Jit_OperationCreatedFirstMissingValue(arrayFlagsBeforeHelperCall, base)
m_lowererMD.LoadHelperArgument(insertBeforeInstr, baseOpnd);
m_lowererMD.LoadHelperArgument(insertBeforeInstr, arrayFlagsBeforeHelperCallOpnd);
IR::Instr *const callInstr = IR::Instr::New(Js::OpCode::Call, func);
IR::RegOpnd *const createdFirstMissingValueOpnd = IR::RegOpnd::New(TyUint8, func);
IR::AutoReuseOpnd autoReuseCreatedFirstMissingValueOpnd(createdFirstMissingValueOpnd, func);
callInstr->SetDst(createdFirstMissingValueOpnd);
insertBeforeInstr->InsertBefore(callInstr);
m_lowererMD.ChangeToHelperCall(callInstr, IR::HelperArray_Jit_OperationCreatedFirstMissingValue);
// test createdFirstMissingValue, createdFirstMissingValue
// jz $skipBailOut
InsertCompareBranch(
createdFirstMissingValueOpnd,
IR::IntConstOpnd::New(0, createdFirstMissingValueOpnd->GetType(), func, true),
Js::OpCode::BrEq_A,
skipBailOutLabel,
insertBeforeInstr);
// (Bail out with IR::BailOutOnMissingValue)
// $skipBailOut:
}
IR::Opnd*
Lowerer::GetFuncObjectOpnd(IR::Instr* insertBeforeInstr)
{
Func * func = insertBeforeInstr->m_func;
IR::Opnd *paramOpnd = nullptr;
if (func->IsInlinee())
{
paramOpnd = func->GetInlineeFunctionObjectSlotOpnd();
}
else
{
#if defined(_M_ARM32_OR_ARM64)
StackSym * paramSym = this->m_lowererMD.GetImplicitParamSlotSym(0);
#else
StackSym *paramSym = StackSym::New(TyMachReg, this->m_func);
this->m_func->SetArgOffset(paramSym, 2 * MachPtr);
this->m_func->SetHasImplicitParamLoad();
#endif
paramOpnd = IR::SymOpnd::New(paramSym, TyMachReg, this->m_func);
}
if (func->GetJITFunctionBody()->IsCoroutine())
{
// the function object for generator calls is a GeneratorVirtualScriptFunction object
// and we need to return the real JavascriptGeneratorFunction object so grab it before
// assigning to the dst
Assert(!func->IsInlinee());
IR::RegOpnd *tmpOpnd = IR::RegOpnd::New(TyMachReg, func);
Lowerer::InsertMove(tmpOpnd, paramOpnd, insertBeforeInstr);
paramOpnd = IR::IndirOpnd::New(tmpOpnd, Js::GeneratorVirtualScriptFunction::GetRealFunctionOffset(), TyMachPtr, func);
}
return paramOpnd;
}
///----------------------------------------------------------------------------
///
/// Lowerer::LoadFuncExpression
///
/// Load the function expression to src1 from [ebp + 8]
///
///----------------------------------------------------------------------------
IR::Instr *
Lowerer::LoadFuncExpression(IR::Instr *instrFuncExpr)
{
ASSERT_INLINEE_FUNC(instrFuncExpr);
IR::Opnd *paramOpnd = GetFuncObjectOpnd(instrFuncExpr);
// mov dst, param
instrFuncExpr->SetSrc1(paramOpnd);
LowererMD::ChangeToAssign(instrFuncExpr);
return instrFuncExpr;
}
void Lowerer::LowerBoundCheck(IR::Instr *const instr)
{
Assert(instr);
Assert(instr->m_opcode == Js::OpCode::BoundCheck || instr->m_opcode == Js::OpCode::UnsignedBoundCheck);
#if DBG
if(instr->m_opcode == Js::OpCode::UnsignedBoundCheck)
{
// UnsignedBoundCheck is currently only supported for the pattern:
// UnsignedBoundCheck s1 <= s2 + c, where c == 0 || c == -1
Assert(instr->GetSrc1()->IsRegOpnd());
Assert(instr->GetSrc1()->IsInt32());
Assert(instr->GetSrc2());
Assert(!instr->GetSrc2()->IsIntConstOpnd());
if(instr->GetDst())
{
const int32 c = instr->GetDst()->AsIntConstOpnd()->AsInt32();
Assert(c == 0 || c == -1);
}
}
#endif
const IR::BailOutKind bailOutKind = instr->GetBailOutKind();
Assert(
bailOutKind == IR::BailOutOnArrayAccessHelperCall ||
bailOutKind == IR::BailOutOnInvalidatedArrayHeadSegment ||
bailOutKind == IR::BailOutOnFailedHoistedBoundCheck ||
bailOutKind == IR::BailOutOnFailedHoistedLoopCountBasedBoundCheck);
IR::LabelInstr *const skipBailOutLabel = instr->GetOrCreateContinueLabel(false);
LowerOneBailOutKind(instr, bailOutKind, false);
Assert(!instr->HasBailOutInfo());
IR::Instr *insertBeforeInstr = instr->m_next;
#if DBG
const auto VerifyLeftOrRightOpnd = [&](IR::Opnd *const opnd, const bool isRightOpnd)
{
if(!opnd)
{
Assert(isRightOpnd);
return;
}
if(opnd->IsIntConstOpnd())
{
Assert(!isRightOpnd || opnd->AsIntConstOpnd()->GetValue() != 0);
return;
}
Assert(opnd->GetType() == TyInt32 || opnd->GetType() == TyUint32);
};
#endif
// left <= right + offset (src1 <= src2 + dst)
IR::Opnd *leftOpnd = instr->UnlinkSrc1();
DebugOnly(VerifyLeftOrRightOpnd(leftOpnd, false));
IR::Opnd *rightOpnd = instr->UnlinkSrc2();
DebugOnly(VerifyLeftOrRightOpnd(rightOpnd, true));
Assert(!leftOpnd->IsIntConstOpnd() || rightOpnd && !rightOpnd->IsIntConstOpnd());
IR::IntConstOpnd *offsetOpnd = instr->GetDst() ? instr->UnlinkDst()->AsIntConstOpnd() : nullptr;
Assert(!offsetOpnd || offsetOpnd->GetValue() != 0);
const bool doUnsignedCompare = instr->m_opcode == Js::OpCode::UnsignedBoundCheck;
instr->Remove();
Func *const func = insertBeforeInstr->m_func;
IntConstType offset = offsetOpnd ? offsetOpnd->GetValue() : 0;
Js::OpCode compareOpCode = Js::OpCode::BrLe_A;
if(leftOpnd->IsIntConstOpnd() && rightOpnd->IsRegOpnd() && offset != IntConstMin)
{
// Put the constants together: swap the operands, negate the offset, and invert the branch
IR::Opnd *const tempOpnd = leftOpnd;
leftOpnd = rightOpnd;
rightOpnd = tempOpnd;
offset = -offset;
compareOpCode = Js::OpCode::BrGe_A;
}
if(rightOpnd->IsIntConstOpnd())
{
// Try to aggregate right + offset into a constant offset
IntConstType newOffset;
if(!IntConstMath::Add(offset, rightOpnd->AsIntConstOpnd()->GetValue(), TyInt32, &newOffset))
{
offset = newOffset;
rightOpnd = nullptr;
offsetOpnd = nullptr;
}
}
// Determine if the Add for (right + offset) is necessary, and the op code that will be used for the comparison
IR::AutoReuseOpnd autoReuseAddResultOpnd;
if(offset == -1 && compareOpCode == Js::OpCode::BrLe_A)
{
offset = 0;
compareOpCode = Js::OpCode::BrLt_A;
}
else if(offset == 1 && compareOpCode == Js::OpCode::BrGe_A)
{
offset = 0;
compareOpCode = Js::OpCode::BrGt_A;
}
else if(offset != 0 && rightOpnd)
{
// Need to Add (right + offset). If it overflows, bail out.
IR::LabelInstr *const bailOutLabel = insertBeforeInstr->m_prev->GetOrCreateContinueLabel(true);
insertBeforeInstr = bailOutLabel;
// mov temp, right
// add temp, offset
// jo $bailOut
// $bailOut: (insertBeforeInstr)
Assert(!offsetOpnd || offsetOpnd->GetValue() == offset);
IR::RegOpnd *const addResultOpnd = IR::RegOpnd::New(TyInt32, func);
autoReuseAddResultOpnd.Initialize(addResultOpnd, func);
InsertAdd(
true,
addResultOpnd,
rightOpnd,
offsetOpnd ? offsetOpnd->UseWithNewType(TyInt32, func) : IR::IntConstOpnd::New(offset, TyInt32, func),
insertBeforeInstr);
InsertBranch(LowererMD::MDOverflowBranchOpcode, bailOutLabel, insertBeforeInstr);
rightOpnd = addResultOpnd;
}
// cmp left, right
// jl[e] $skipBailOut
// $bailOut:
if(!rightOpnd)
{
rightOpnd = IR::IntConstOpnd::New(offset, TyInt32, func);
}
InsertCompareBranch(leftOpnd, rightOpnd, compareOpCode, doUnsignedCompare, skipBailOutLabel, insertBeforeInstr);
}
IR::Instr *
Lowerer::LowerBailTarget(IR::Instr * instr)
{
// this is just a bailout target, just skip over it and generate a label before so other bailout can jump here.
IR::Instr * prevInstr = instr->m_prev;
IR::LabelInstr * continueLabelInstr = IR::LabelInstr::New(Js::OpCode::Label, m_func);
instr->InsertAfter(continueLabelInstr);
IR::BranchInstr * skipInstr = IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, continueLabelInstr, this->m_func);
instr->InsertBefore(skipInstr);
this->GenerateBailOut(instr);
return prevInstr;
}
IR::Instr *
Lowerer::SplitBailOnImplicitCall(IR::Instr *& instr)
{
Assert(instr->IsPlainInstr() || instr->IsProfiledInstr());
const auto bailOutKind = instr->GetBailOutKind();
Assert(BailOutInfo::IsBailOutOnImplicitCalls(bailOutKind));
IR::Opnd * implicitCallFlags = this->GetImplicitCallFlagsOpnd();
const IR::AutoReuseOpnd autoReuseImplicitCallFlags(implicitCallFlags, instr->m_func);
IR::IntConstOpnd * noImplicitCall = IR::IntConstOpnd::New(Js::ImplicitCall_None, TyInt8, this->m_func, true);
const IR::AutoReuseOpnd autoReuseNoImplicitCall(noImplicitCall, instr->m_func);
// Reset the implicit call flag on every helper call
Lowerer::InsertMove(implicitCallFlags, noImplicitCall, instr);
IR::Instr *disableImplicitCallsInstr = nullptr, *enableImplicitCallsInstr = nullptr;
if(BailOutInfo::WithoutLazyBailOut(bailOutKind) == IR::BailOutOnImplicitCallsPreOp)
{
const auto disableImplicitCallAddress =
m_lowererMD.GenerateMemRef(
instr->m_func->GetThreadContextInfo()->GetDisableImplicitFlagsAddr(),
TyInt8,
instr);
// Disable implicit calls since they will be called after bailing out
disableImplicitCallsInstr =
IR::Instr::New(
Js::OpCode::Ld_A,
disableImplicitCallAddress,
IR::IntConstOpnd::New(DisableImplicitCallFlag, TyInt8, instr->m_func, true),
instr->m_func);
instr->InsertBefore(disableImplicitCallsInstr);
// Create instruction for re-enabling implicit calls
enableImplicitCallsInstr =
IR::Instr::New(
Js::OpCode::Ld_A,
disableImplicitCallAddress,
IR::IntConstOpnd::New(DisableImplicitNoFlag, TyInt8, instr->m_func, true),
instr->m_func);
#if DBG
enableImplicitCallsInstr->m_noLazyHelperAssert = true;
#endif
}
IR::Instr * bailOutInstr = instr;
instr = IR::Instr::New(instr->m_opcode, instr->m_func);
bailOutInstr->TransferTo(instr);
bailOutInstr->InsertBefore(instr);
if(disableImplicitCallsInstr)
{
// Re-enable implicit calls
Assert(enableImplicitCallsInstr);
bailOutInstr->InsertBefore(enableImplicitCallsInstr);
// Lower both instructions. Lowering an instruction may free the instruction's original operands, so do that last.
LowererMD::ChangeToAssign(disableImplicitCallsInstr);
LowererMD::ChangeToAssign(enableImplicitCallsInstr);
}
bailOutInstr->m_opcode = Js::OpCode::BailOnNotEqual;
bailOutInstr->SetSrc1(implicitCallFlags);
bailOutInstr->SetSrc2(noImplicitCall);
return bailOutInstr;
}
IR::Instr *
Lowerer::SplitBailOnImplicitCall(IR::Instr * instr, IR::Instr * helperCall, IR::Instr * insertBeforeInstr)
{
IR::Opnd * implicitCallFlags = this->GetImplicitCallFlagsOpnd();
const IR::AutoReuseOpnd autoReuseImplicitCallFlags(implicitCallFlags, instr->m_func);
IR::IntConstOpnd * noImplicitCall = IR::IntConstOpnd::New(Js::ImplicitCall_None, TyInt8, this->m_func, true);
const IR::AutoReuseOpnd autoReuseNoImplicitCall(noImplicitCall, instr->m_func);
// Reset the implicit call flag on every helper call
Lowerer::InsertMove(implicitCallFlags, noImplicitCall, helperCall->m_prev);
BailOutInfo * bailOutInfo = instr->GetBailOutInfo();
if (bailOutInfo->bailOutInstr == instr)
{
bailOutInfo->bailOutInstr = nullptr;
}
IR::Instr * bailOutInstr = IR::BailOutInstr::New(Js::OpCode::BailOnNotEqual, IR::BailOutOnImplicitCalls, bailOutInfo, bailOutInfo->bailOutFunc);
bailOutInstr->SetSrc1(implicitCallFlags);
bailOutInstr->SetSrc2(noImplicitCall);
insertBeforeInstr->InsertBefore(bailOutInstr);
instr->ClearBailOutInfo();
return bailOutInstr;
}
// Split out bailout for debugger into separate bailout instr out of real instr which has bailout for debugger.
// Returns the instr which needs to lower next, which would normally be last of splitted instr.
// IR on input:
// - Real instr with BailOutInfo but it's opcode is not BailForDebugger.
// - debugger bailout is not shared. In this case we'll have debugger bailout in instr->GetBailOutKind().
// - debugger bailout is shared. In this case we'll have debugger bailout in instr->GetAuxBailOutKind().
// IR on output:
// - Either of:
// - real instr, then debuggerBailout -- in case we only had debugger bailout.
// - real instr with BailOutInfo w/o debugger bailout, then debuggerBailout, then sharedBailout -- in case bailout for debugger was shared w/some other b.o.
IR::Instr* Lowerer::SplitBailForDebugger(IR::Instr* instr)
{
Assert(m_func->IsJitInDebugMode() && instr->m_opcode != Js::OpCode::BailForDebugger);
IR::BailOutKind debuggerBailOutKind; // Used for splitted instr.
BailOutInfo* bailOutInfo = instr->GetBailOutInfo();
IR::Instr* sharedBailoutInstr = nullptr;
if (instr->GetBailOutKind() & IR::BailOutForDebuggerBits)
{
// debugger bailout is not shared.
Assert(!instr->HasAuxBailOut());
AssertMsg(!(instr->GetBailOutKind() & ~IR::BailOutForDebuggerBits), "There should only be debugger bailout bits in the instr.");
debuggerBailOutKind = instr->GetBailOutKind() & IR::BailOutForDebuggerBits;
// There is no non-debugger bailout in the instr, still can't clear bailout info, as we use it for the splitted instr,
// but we need to mark the bailout as hasn't been generated yet.
if (bailOutInfo->bailOutInstr == instr)
{
// null will be picked up by following BailOutInstr::New which will change it to new bailout instr.
bailOutInfo->bailOutInstr = nullptr;
}
// Remove bailout info from the original instr which from now on becomes just regular instr, w/o deallocating bailout info.
instr->ClearBailOutInfo();
}
else if (instr->IsBranchInstr() && instr->HasBailOutInfo() && instr->HasAuxBailOut())
{
// Branches with shared bailout are lowered in LowerCondBranchCheckBailOut,
// can't do here because we need to use BranchBailOutRecord but don't know which BrTrue/BrFalse to use for it.
debuggerBailOutKind = IR::BailOutInvalid;
}
else if (instr->HasAuxBailOut() && instr->GetAuxBailOutKind() & IR::BailOutForDebuggerBits)
{
// debugger bailout is shared.
AssertMsg(!(instr->GetBailOutKind() & IR::BailOutForDebuggerBits), "There should be no debugger bits in main bailout kind.");
debuggerBailOutKind = instr->GetAuxBailOutKind() & IR::BailOutForDebuggerBits;
// This will insert SharedBail instr after current instr and set bailOutInfo->bailOutInstr to the shared one.
sharedBailoutInstr = instr->ShareBailOut();
// As we extracted aux bail out, invalidate all tracks of it in the instr.
instr->ResetAuxBailOut();
}
else
{
AssertMsg(FALSE, "shouldn't get here");
debuggerBailOutKind = IR::BailOutInvalid;
}
if (debuggerBailOutKind != IR::BailOutInvalid)
{
IR::BailOutInstr* debuggerBailoutInstr = IR::BailOutInstr::New(
Js::OpCode::BailForDebugger, debuggerBailOutKind, bailOutInfo, bailOutInfo->bailOutFunc);
instr->InsertAfter(debuggerBailoutInstr);
// Since we go backwards, we need to process extracted out bailout for debugger first.
instr = sharedBailoutInstr ? sharedBailoutInstr : debuggerBailoutInstr;
}
return instr;
}
IR::Instr *
Lowerer::SplitBailOnResultCondition(IR::Instr *const instr) const
{
Assert(instr);
Assert(!instr->IsLowered());
Assert(
instr->GetBailOutKind() & IR::BailOutOnResultConditions ||
instr->GetBailOutKind() == IR::BailOutOnFailedHoistedLoopCountBasedBoundCheck);
const auto nonBailOutInstr = IR::Instr::New(instr->m_opcode, instr->m_func);
instr->TransferTo(nonBailOutInstr);
instr->InsertBefore(nonBailOutInstr);
return nonBailOutInstr;
}
void
Lowerer::LowerBailOnResultCondition(
IR::Instr *const instr,
IR::LabelInstr * *const bailOutLabel,
IR::LabelInstr * *const skipBailOutLabel)
{
Assert(instr);
Assert(
instr->GetBailOutKind() & IR::BailOutOnResultConditions ||
instr->GetBailOutKind() == IR::BailOutOnFailedHoistedLoopCountBasedBoundCheck);
Assert(bailOutLabel);
Assert(skipBailOutLabel);
// Label to jump to (or fall through to) when bailing out. The actual bailout label
// (bailOutInfo->bailOutInstr->AsLabelInstr()) may be shared, and code may be added to restore values before the jump to the
// actual bailout label in the cloned bailout case, so always create a new bailout label for this particular path.
*bailOutLabel = IR::LabelInstr::New(Js::OpCode::Label, instr->m_func, true /* isOpHelper */);
instr->InsertBefore(*bailOutLabel);
// Label to jump to when not bailing out
*skipBailOutLabel = IR::LabelInstr::New(Js::OpCode::Label, instr->m_func);
instr->InsertAfter(*skipBailOutLabel);
// Generate the bailout helper call. 'instr' will be changed to the CALL into the bailout function, so it can't be used for
// ordering instructions anymore.
GenerateBailOut(instr);
}
void
Lowerer::PreserveSourcesForBailOnResultCondition(IR::Instr *const instr, IR::LabelInstr *const skipBailOutLabel) const
{
Assert(instr);
Assert(!instr->IsLowered());
Assert(!instr->HasBailOutInfo());
// Since this instruction may bail out, writing to the destination cannot overwrite one of the sources, or we may lose one
// of the sources needed to redo the equivalent byte code instruction. Determine if the sources need to be preserved.
const auto dst = instr->GetDst();
Assert(dst);
const auto dstStackSym = dst->GetStackSym();
if(!dstStackSym || !dstStackSym->HasByteCodeRegSlot())
{
// We only need to ensure that a byte-code source is not being overwritten
return;
}
switch(instr->m_opcode)
{
// The sources of these instructions don't need restoring, or will be restored in the bailout path
case Js::OpCode::Neg_I4:
// In case of overflow or zero, the result is the same as the operand
case Js::OpCode::Add_I4:
case Js::OpCode::Sub_I4:
// In case of overflow, there is always enough information to restore the operands
return;
}
Assert(instr->GetSrc1());
if(!dst->IsEqual(instr->GetSrc1()) && !(instr->GetSrc2() && dst->IsEqual(instr->GetSrc2())))
{
// The destination is different from the sources
return;
}
// The destination is the same as one of the sources and the original sources cannot be restored after the instruction, so
// use a temporary destination for the result and move it back to the original destination after deciding not to bail out
LowererMD::ChangeToAssign(instr->SinkDst(Js::OpCode::Ld_I4, RegNOREG, skipBailOutLabel));
}
void
Lowerer::LowerInstrWithBailOnResultCondition(
IR::Instr *const instr,
const IR::BailOutKind bailOutKind,
IR::LabelInstr *const bailOutLabel,
IR::LabelInstr *const skipBailOutLabel) const
{
Assert(instr);
Assert(!instr->IsLowered());
Assert(!instr->HasBailOutInfo());
Assert(bailOutKind & IR::BailOutOnResultConditions || bailOutKind == IR::BailOutOnFailedHoistedLoopCountBasedBoundCheck);
Assert(bailOutLabel);
Assert(instr->m_next == bailOutLabel);
Assert(skipBailOutLabel);
// Preserve sources that are overwritten by the instruction if needed
PreserveSourcesForBailOnResultCondition(instr, skipBailOutLabel);
// Lower the instruction
switch(instr->m_opcode)
{
case Js::OpCode::Neg_I4:
LowererMD::LowerInt4NegWithBailOut(instr, bailOutKind, bailOutLabel, skipBailOutLabel);
break;
case Js::OpCode::Add_I4:
LowererMD::LowerInt4AddWithBailOut(instr, bailOutKind, bailOutLabel, skipBailOutLabel);
break;
case Js::OpCode::Sub_I4:
LowererMD::LowerInt4SubWithBailOut(instr, bailOutKind, bailOutLabel, skipBailOutLabel);
break;
case Js::OpCode::Mul_I4:
LowererMD::LowerInt4MulWithBailOut(instr, bailOutKind, bailOutLabel, skipBailOutLabel);
break;
case Js::OpCode::Rem_I4:
m_lowererMD.LowerInt4RemWithBailOut(instr, bailOutKind, bailOutLabel, skipBailOutLabel);
break;
default:
Assert(false); // not implemented
__assume(false);
}
}
void
Lowerer::GenerateObjectTestAndTypeLoad(IR::Instr *instrLdSt, IR::RegOpnd *opndBase, IR::RegOpnd *opndType, IR::LabelInstr *labelHelper)
{
IR::IndirOpnd *opndIndir;
if (!opndBase->IsNotTaggedValue())
{
m_lowererMD.GenerateObjectTest(opndBase, instrLdSt, labelHelper);
}
opndIndir = IR::IndirOpnd::New(opndBase, Js::RecyclableObject::GetOffsetOfType(), TyMachReg, this->m_func);
InsertMove(opndType, opndIndir, instrLdSt);
}
IR::LabelInstr *
Lowerer::GenerateBailOut(IR::Instr * instr, IR::BranchInstr * branchInstr, IR::LabelInstr *bailOutLabel, IR::LabelInstr * collectRuntimeStatsLabel)
{
BailOutInfo * bailOutInfo = instr->GetBailOutInfo();
IR::Instr * bailOutInstr = bailOutInfo->bailOutInstr;
if (instr->IsCloned())
{
Assert(bailOutInstr != instr);
// Jump to the cloned bail out label
IR::LabelInstr * bailOutLabelInstr = bailOutInstr->AsLabelInstr();
IR::BranchInstr * bailOutBranch = IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, bailOutLabelInstr, this->m_func);
instr->InsertBefore(bailOutBranch);
instr->Remove();
return bailOutLabel;
}
// Add helper label to trigger layout.
if (!collectRuntimeStatsLabel)
{
collectRuntimeStatsLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true);
}
Assert(!collectRuntimeStatsLabel->IsLinked());
instr->InsertBefore(collectRuntimeStatsLabel);
if (bailOutInstr != instr)
{
// this bailOutInfo is shared, just jump to the bailout target
IR::Opnd * indexOpndForBailOutKind = nullptr;
int bailOutRecordOffset = 0;
if (this->m_func->IsOOPJIT())
{
bailOutRecordOffset = NativeCodeData::GetDataTotalOffset(bailOutInfo->bailOutRecord);
indexOpndForBailOutKind = IR::IndirOpnd::New(IR::RegOpnd::New(m_func->GetTopFunc()->GetNativeCodeDataSym(), TyVar, m_func), (int)(bailOutRecordOffset + BailOutRecord::GetOffsetOfBailOutKind()), TyUint32,
#if DBG
NativeCodeData::GetDataDescription(bailOutInfo->bailOutRecord, this->m_func->m_alloc),
#endif
m_func, true);
this->addToLiveOnBackEdgeSyms->Set(m_func->GetTopFunc()->GetNativeCodeDataSym()->m_id);
}
else
{
indexOpndForBailOutKind =
IR::MemRefOpnd::New((BYTE*)bailOutInfo->bailOutRecord + BailOutRecord::GetOffsetOfBailOutKind(), TyUint32, this->m_func, IR::AddrOpndKindDynamicBailOutKindRef);
}
InsertMove(
indexOpndForBailOutKind, IR::IntConstOpnd::New(instr->GetBailOutKind(), indexOpndForBailOutKind->GetType(), this->m_func), instr, false);
// No point in doing this for BailOutFailedEquivalentTypeCheck or BailOutFailedEquivalentFixedFieldTypeCheck,
// because the respective inline cache is already polymorphic, anyway.
if (instr->GetBailOutKind() == IR::BailOutFailedTypeCheck || instr->GetBailOutKind() == IR::BailOutFailedFixedFieldTypeCheck)
{
// We have a type check bailout that shares a bailout record with other instructions.
// Generate code to write the cache index into the bailout record before we jump to the call site.
Assert(bailOutInfo->polymorphicCacheIndex != (uint)-1);
Assert(bailOutInfo->bailOutRecord);
IR::Opnd * indexOpnd = nullptr;
if (this->m_func->IsOOPJIT())
{
indexOpnd = IR::IndirOpnd::New(IR::RegOpnd::New(m_func->GetTopFunc()->GetNativeCodeDataSym(), TyVar, m_func), (int)(bailOutRecordOffset + BailOutRecord::GetOffsetOfPolymorphicCacheIndex()), TyUint32, m_func);
}
else
{
indexOpnd = IR::MemRefOpnd::New((BYTE*)bailOutInfo->bailOutRecord + BailOutRecord::GetOffsetOfPolymorphicCacheIndex(), TyUint32, this->m_func);
}
InsertMove(
indexOpnd, IR::IntConstOpnd::New(bailOutInfo->polymorphicCacheIndex, TyUint32, this->m_func), instr, false);
}
if (bailOutInfo->bailOutRecord->IsShared())
{
IR::Opnd *functionBodyOpnd;
if (this->m_func->IsOOPJIT())
{
functionBodyOpnd = IR::IndirOpnd::New(IR::RegOpnd::New(m_func->GetTopFunc()->GetNativeCodeDataSym(), TyVar, m_func), (int)(bailOutRecordOffset + SharedBailOutRecord::GetOffsetOfFunctionBody()), TyMachPtr, m_func);
}
else
{
functionBodyOpnd = IR::MemRefOpnd::New((BYTE*)bailOutInfo->bailOutRecord + SharedBailOutRecord::GetOffsetOfFunctionBody(), TyMachPtr, this->m_func);
}
InsertMove(
functionBodyOpnd, CreateFunctionBodyOpnd(instr->m_func), instr, false);
}
// GenerateBailOut should have replaced this as a label as we should have already lowered
// the main bailOutInstr.
IR::LabelInstr * bailOutTargetLabel = bailOutInstr->AsLabelInstr();
#if DBG
if (bailOutTargetLabel->m_noHelperAssert)
{
collectRuntimeStatsLabel->m_noHelperAssert = true;
}
#endif
Assert(bailOutLabel == nullptr || bailOutLabel == bailOutTargetLabel);
IR::BranchInstr * newBranchInstr = IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, bailOutTargetLabel, this->m_func);
instr->InsertAfter(newBranchInstr);
instr->Remove();
return collectRuntimeStatsLabel ? collectRuntimeStatsLabel : bailOutLabel;
}
// The bailout hasn't been generated yet.
Assert(!bailOutInstr->IsLabelInstr());
// Capture the condition for this bailout
if (bailOutLabel == nullptr)
{
// Create a label and place it in the bailout info so that shared bailout point can jump to this one
if (instr->m_prev->IsLabelInstr())
{
bailOutLabel = instr->m_prev->AsLabelInstr();
Assert(bailOutLabel->isOpHelper);
}
else
{
bailOutLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true);
instr->InsertBefore(bailOutLabel);
}
}
else
{
instr->InsertBefore(bailOutLabel);
}
#if DBG
bailOutLabel->m_noLazyHelperAssert = true;
#endif
#if DBG
const IR::BailOutKind bailOutKind = bailOutInstr->GetBailOutKind();
if (bailOutInstr->m_opcode == Js::OpCode::BailOnNoSimdTypeSpec ||
bailOutInstr->m_opcode == Js::OpCode::BailOnNoProfile ||
bailOutInstr->m_opcode == Js::OpCode::BailOnException ||
bailOutInstr->m_opcode == Js::OpCode::Yield ||
bailOutKind & (IR::BailOutConventionalTypedArrayAccessOnly |
IR::BailOutConventionalNativeArrayAccessOnly |
IR::BailOutOnArrayAccessHelperCall))
{
bailOutLabel->m_noHelperAssert = true;
}
#endif
bailOutInfo->bailOutInstr = bailOutLabel;
bailOutLabel->m_hasNonBranchRef = true;
// Create the bail out record
Assert(bailOutInfo->bailOutRecord == nullptr);
BailOutRecord * bailOutRecord;
IR::JnHelperMethod helperMethod;
if (branchInstr != nullptr)
{
Assert(branchInstr->GetSrc2() == nullptr);
Assert(branchInstr->GetDst() == nullptr);
IR::LabelInstr * targetLabel = branchInstr->GetTarget();
Assert(targetLabel->GetByteCodeOffset() != Js::Constants::NoByteCodeOffset);
uint32 trueOffset;
uint32 falseOffset;
IR::Opnd *condOpnd = branchInstr->GetSrc1();
bool invertTarget = (branchInstr->m_opcode == Js::OpCode::BrFalse_A);
if (bailOutInfo->isInvertedBranch)
{
// Flip the condition
IR::Instr *subInstr = IR::Instr::New(Js::OpCode::Sub_I4, condOpnd, condOpnd, IR::IntConstOpnd::New(1, TyMachReg, instr->m_func), instr->m_func);
instr->InsertBefore(subInstr);
this->m_lowererMD.EmitInt4Instr(subInstr);
// We should really do a DEC/NEG for a full 2's complement flip from 0/1 to 1/0,
// but DEC is sufficient to flip from 0/1 to -1/0, which is false/true to true/false...
// instr->InsertBefore(IR::Instr::New(Js::OpCode::Neg_I4, condOpnd, condOpnd, instr->m_func));
invertTarget = invertTarget ? false : true;
}
if (!invertTarget)
{
trueOffset = targetLabel->GetByteCodeOffset();
falseOffset = bailOutInfo->bailOutOffset;
}
else
{
falseOffset = targetLabel->GetByteCodeOffset();
trueOffset = bailOutInfo->bailOutOffset;
}
bailOutRecord = NativeCodeDataNewZ(this->m_func->GetNativeCodeDataAllocator(),
BranchBailOutRecord, trueOffset, falseOffset, branchInstr->GetByteCodeReg(), instr->GetBailOutKind(), bailOutInfo->bailOutFunc);
helperMethod = IR::HelperSaveAllRegistersAndBranchBailOut;
#ifdef _M_IX86
if(!AutoSystemInfo::Data.SSE2Available())
{
helperMethod = IR::HelperSaveAllRegistersNoSse2AndBranchBailOut;
}
#endif
// Save the condition. The register allocator will generate arguments.
bailOutInfo->branchConditionOpnd = branchInstr->GetSrc1()->Copy(branchInstr->m_func);
}
else
{
if (bailOutInstr->GetBailOutKind() == IR::BailOutShared)
{
bailOutRecord = NativeCodeDataNewZ(this->m_func->GetNativeCodeDataAllocator(),
SharedBailOutRecord, bailOutInfo->bailOutOffset, bailOutInfo->polymorphicCacheIndex, instr->GetBailOutKind(), bailOutInfo->bailOutFunc);
if (bailOutInfo->isLoopTopBailOutInfo)
{
bailOutRecord->SetType(BailOutRecord::BailoutRecordType::SharedForLoopTop);
}
}
else
{
bailOutRecord = NativeCodeDataNewZ(this->m_func->GetNativeCodeDataAllocator(),
BailOutRecord, bailOutInfo->bailOutOffset, bailOutInfo->polymorphicCacheIndex, instr->GetBailOutKind(), bailOutInfo->bailOutFunc);
}
helperMethod = IR::HelperSaveAllRegistersAndBailOut;
#ifdef _M_IX86
if(!AutoSystemInfo::Data.SSE2Available())
{
helperMethod = IR::HelperSaveAllRegistersNoSse2AndBailOut;
}
#endif
}
// Save the bailout record. The register allocator will generate arguments.
bailOutInfo->bailOutRecord = bailOutRecord;
#if ENABLE_DEBUG_CONFIG_OPTIONS
bailOutRecord->bailOutOpcode = bailOutInfo->bailOutOpcode;
#endif
if (instr->m_opcode == Js::OpCode::BailOnNotStackArgs && instr->GetSrc1())
{
// src1 on BailOnNotStackArgs is helping CSE
instr->FreeSrc1();
}
if (instr->GetSrc2() != nullptr)
{
// Ideally we should never be in this situation but incase we reached a
// condition where we didn't free src2, free it here.
instr->FreeSrc2();
}
// We do not need lazybailout bit on SaveAllRegistersAndBailOut
if (instr->HasLazyBailOut())
{
instr->ClearLazyBailOut();
Assert(instr->HasBailOutInfo());
}
// Call the bail out wrapper
instr->m_opcode = Js::OpCode::Call;
if(instr->GetDst())
{
// To facilitate register allocation, don't assign a destination. The result will anyway go into the return register,
// but the register allocator does not need to kill that register for the call.
instr->FreeDst();
}
instr->SetSrc1(IR::HelperCallOpnd::New(helperMethod, this->m_func));
m_lowererMD.LowerCall(instr, 0);
if (bailOutInstr->GetBailOutKind() != IR::BailOutForGeneratorYield)
{
// Defer introducing the JMP to epilog until LowerPrologEpilog phase for Yield bailouts so
// that Yield does not appear to have flow out of its containing block for the RegAlloc phase.
// Yield is an unconditional bailout but we want to simulate the flow as if the Yield were
// just like a call.
GenerateJumpToEpilogForBailOut(bailOutInfo, instr);
}
return collectRuntimeStatsLabel ? collectRuntimeStatsLabel : bailOutLabel;
}
void
Lowerer::GenerateJumpToEpilogForBailOut(BailOutInfo * bailOutInfo, IR::Instr *instr)
{
IR::Instr * exitPrevInstr = this->m_func->m_exitInstr->m_prev;
// JMP to the epilog
IR::LabelInstr * exitTargetInstr;
if (exitPrevInstr->IsLabelInstr())
{
exitTargetInstr = exitPrevInstr->AsLabelInstr();
}
else
{
exitTargetInstr = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, false);
exitPrevInstr->InsertAfter(exitTargetInstr);
}
exitTargetInstr = m_lowererMD.GetBailOutStackRestoreLabel(bailOutInfo, exitTargetInstr);
IR::Instr * instrAfter = instr->m_next;
IR::BranchInstr * exitInstr = IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, exitTargetInstr, this->m_func);
instrAfter->InsertBefore(exitInstr);
}
///----------------------------------------------------------------------------
///
/// Lowerer::GenerateFastCondBranch
///
///----------------------------------------------------------------------------
bool
Lowerer::GenerateFastCondBranch(IR::BranchInstr * instrBranch, bool *pIsHelper)
{
// The idea is to do an inline compare if we can prove that both sources
// are tagged ints
//
// Given:
//
// Brxx_A $L, src1, src2
//
// Generate:
//
// (If not Int31's, goto $helper)
// Jxx $L, src1, src2
// JMP $fallthru
// $helper:
// (caller will generate normal helper call sequence)
// $fallthru:
IR::LabelInstr * labelHelper = nullptr;
IR::LabelInstr * labelFallThru;
IR::BranchInstr * instr;
IR::Opnd * opndSrc1;
IR::Opnd * opndSrc2;
opndSrc1 = instrBranch->GetSrc1();
opndSrc2 = instrBranch->GetSrc2();
AssertMsg(opndSrc1 && opndSrc2, "BrC expects 2 src operands");
// Not tagged ints?
if (opndSrc1->IsRegOpnd() && opndSrc1->AsRegOpnd()->IsNotInt())
{
return true;
}
if (opndSrc2->IsRegOpnd() && opndSrc2->AsRegOpnd()->IsNotInt())
{
return true;
}
// Tagged ints?
bool isTaggedInts = false;
if (opndSrc1->IsTaggedInt())
{
if (opndSrc2->IsTaggedInt())
{
isTaggedInts = true;
}
}
if (!isTaggedInts)
{
labelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true);
this->m_lowererMD.GenerateSmIntPairTest(instrBranch, opndSrc1, opndSrc2, labelHelper);
}
// Jxx $L, src1, src2
opndSrc1 = opndSrc1->UseWithNewType(TyInt32, this->m_func);
opndSrc2 = opndSrc2->UseWithNewType(TyInt32, this->m_func);
instr = IR::BranchInstr::New(instrBranch->m_opcode, instrBranch->GetTarget(), opndSrc1, opndSrc2, this->m_func);
instrBranch->InsertBefore(instr);
this->m_lowererMD.LowerCondBranch(instr);
if (isTaggedInts)
{
instrBranch->Remove();
// Skip lowering call to helper
return false;
}
// JMP $fallthru
IR::Instr *instrNext = instrBranch->GetNextRealInstrOrLabel();
if (instrNext->IsLabelInstr())
{
labelFallThru = instrNext->AsLabelInstr();
}
else
{
labelFallThru = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, /**pIsHelper*/FALSE);
instrBranch->InsertAfter(labelFallThru);
}
instr = IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, labelFallThru, this->m_func);
instrBranch->InsertBefore(instr);
// $helper:
// (caller will generate normal helper call sequence)
// $fallthru:
AssertMsg(labelHelper, "Should not be NULL");
instrBranch->InsertBefore(labelHelper);
*pIsHelper = true;
return true;
}
IR::Instr *
Lowerer::LowerInlineeStart(IR::Instr * inlineeStartInstr)
{
IR::Opnd *linkOpnd = inlineeStartInstr->GetSrc2();
if (!linkOpnd)
{
Assert(inlineeStartInstr->m_func->m_hasInlineArgsOpt);
return inlineeStartInstr->m_prev;
}
AssertMsg(inlineeStartInstr->m_func->firstActualStackOffset != -1, "This should have been already done in backward pass");
IR::Instr *startCall;
// Free the argOut links and lower them to MOVs
inlineeStartInstr->IterateArgInstrs([&](IR::Instr* argInstr){
Assert(argInstr->m_opcode == Js::OpCode::ArgOut_A || argInstr->m_opcode == Js::OpCode::ArgOut_A_Inline);
startCall = argInstr->GetSrc2()->GetStackSym()->m_instrDef;
argInstr->FreeSrc2();
#pragma prefast(suppress:6235, "Non-Zero Constant in Condition")
if (!PHASE_ON(Js::EliminateArgoutForInlineePhase, this->m_func) || inlineeStartInstr->m_func->GetJITFunctionBody()->HasOrParentHasArguments())
{
m_lowererMD.ChangeToAssign(argInstr);
}
else
{
argInstr->m_opcode = Js::OpCode::ArgOut_A_InlineBuiltIn;
}
return false;
});
IR::Instr *argInsertInstr = inlineeStartInstr;
uint i = 0;
inlineeStartInstr->IterateMetaArgs( [&] (IR::Instr* metaArg)
{
if(i == 0)
{
Lowerer::InsertMove(metaArg->m_func->GetNextInlineeFrameArgCountSlotOpnd(),
IR::AddrOpnd::NewNull(metaArg->m_func),
argInsertInstr);
}
if (i == Js::Constants::InlineeMetaArgIndex_FunctionObject)
{
metaArg->SetSrc1(inlineeStartInstr->GetSrc1());
}
metaArg->Unlink();
argInsertInstr->InsertBefore(metaArg);
IR::Instr* prev = metaArg->m_prev;
m_lowererMD.ChangeToAssign(metaArg);
if (i == Js::Constants::InlineeMetaArgIndex_Argc)
{
#if defined(_M_IX86) || defined(_M_X64)
Assert(metaArg == prev->m_next);
#else //defined(_M_ARM)
Assert(prev->m_next->m_opcode == Js::OpCode::LDIMM);
#endif
metaArg = prev->m_next;
Assert(metaArg->GetSrc1()->AsIntConstOpnd()->m_dontEncode == true);
metaArg->isInlineeEntryInstr = true;
LowererMD::Legalize(metaArg);
}
argInsertInstr = metaArg;
i++;
return false;
});
IR::Instr* prev = inlineeStartInstr->m_prev;
if (inlineeStartInstr->m_func->m_hasInlineArgsOpt)
{
inlineeStartInstr->FreeSrc1();
inlineeStartInstr->FreeSrc2();
inlineeStartInstr->FreeDst();
}
else
{
inlineeStartInstr->Remove();
}
return prev;
}
void
Lowerer::LowerInlineeEnd(IR::Instr *instr)
{
Assert(instr->m_func->IsInlinee());
Assert(m_func->IsTopFunc());
// No need to emit code if the function wasn't marked as having implicit calls or bailout. Dead-Store should have removed inline overhead.
if (instr->m_func->GetHasImplicitCalls() || PHASE_OFF(Js::DeadStorePhase, this->m_func))
{
Lowerer::InsertMove(instr->m_func->GetInlineeArgCountSlotOpnd(),
IR::IntConstOpnd::New(0, TyMachReg, instr->m_func),
instr);
}
// Keep InlineeEnd around as it is used by register allocator, if we have optimized the arguments stack
if (instr->m_func->m_hasInlineArgsOpt)
{
instr->FreeSrc1();
}
else
{
instr->Remove();
}
}
IR::Instr *
Lowerer::LoadFloatFromNonReg(IR::Opnd * opndSrc, IR::Opnd * opndDst, IR::Instr * instrInsert)
{
double value;
if (opndSrc->IsAddrOpnd())
{
Js::Var var = opndSrc->AsAddrOpnd()->m_address;
if (Js::TaggedInt::Is(var))
{
value = Js::TaggedInt::ToDouble(var);
}
else
{
value = Js::JavascriptNumber::GetValue(var);
}
}
else if (opndSrc->IsIntConstOpnd())
{
if (opndSrc->IsUInt32())
{
value = (double)(uint32)opndSrc->AsIntConstOpnd()->GetValue();
}
else
{
value = (double)opndSrc->AsIntConstOpnd()->GetValue();
}
}
else if (opndSrc->IsFloatConstOpnd())
{
value = (double)opndSrc->AsFloatConstOpnd()->m_value;
}
else if (opndSrc->IsFloat32ConstOpnd())
{
float floatValue = opndSrc->AsFloat32ConstOpnd()->m_value;
return LowererMD::LoadFloatValue(opndDst, floatValue, instrInsert);
}
else
{
AssertMsg(0, "Unexpected opnd type");
value = 0;
}
return LowererMD::LoadFloatValue(opndDst, value, instrInsert);
}
void
Lowerer::LoadInt32FromUntaggedVar(IR::Instr *const instrLoad)
{
Assert(instrLoad);
Assert(instrLoad->GetDst());
Assert(instrLoad->GetDst()->IsRegOpnd());
Assert(instrLoad->GetDst()->IsInt32());
Assert(instrLoad->GetSrc1());
Assert(instrLoad->GetSrc1()->IsRegOpnd());
Assert(instrLoad->GetSrc1()->IsVar());
Assert(!instrLoad->GetSrc2());
// push src
// int32Value = call JavascriptNumber::GetNonzeroInt32Value_NoChecks
// test int32Value, int32Value
// jne $done
// (fall through to 'instrLoad'; caller will generate code here)
// $done:
// (rest of program)
Func *const func = instrLoad->m_func;
IR::LabelInstr *const doneLabel = instrLoad->GetOrCreateContinueLabel();
// push src
// int32Value = call JavascriptNumber::GetNonzeroInt32Value_NoChecks
StackSym *const int32ValueSym = instrLoad->GetDst()->AsRegOpnd()->m_sym;
IR::Instr *const instr =
IR::Instr::New(
Js::OpCode::Call,
IR::RegOpnd::New(int32ValueSym, TyInt32, func),
instrLoad->GetSrc1()->AsRegOpnd(),
func);
instrLoad->InsertBefore(instr);
LowerUnaryHelper(instr, IR::HelperGetNonzeroInt32Value_NoTaggedIntCheck);
// test int32Value, int32Value
// jne $done
InsertCompareBranch(
IR::RegOpnd::New(int32ValueSym, TyInt32, func),
IR::IntConstOpnd::New(0, TyInt32, func, true),
Js::OpCode::BrNeq_A,
doneLabel,
instrLoad);
}
bool
Lowerer::GetValueFromIndirOpnd(IR::IndirOpnd *indirOpnd, IR::Opnd **pValueOpnd, IntConstType *pValue)
{
IR::RegOpnd *indexOpnd = indirOpnd->GetIndexOpnd();
IR::Opnd* valueOpnd = nullptr;
IntConstType value = 0;
if (!indexOpnd)
{
value = (IntConstType)indirOpnd->GetOffset();
if (value < 0)
{
// Can't do fast path for negative index
return false;
}
valueOpnd = IR::IntConstOpnd::New(value, TyInt32, this->m_func);
}
else if (indexOpnd->m_sym->IsIntConst())
{
value = indexOpnd->AsRegOpnd()->m_sym->GetIntConstValue();
if (value < 0)
{
// Can't do fast path for negative index
return false;
}
valueOpnd = IR::IntConstOpnd::New(value, TyInt32, this->m_func);
}
*pValueOpnd = valueOpnd;
*pValue = value;
return true;
}
void
Lowerer::GenerateFastBrOnObject(IR::Instr *instr)
{
Assert(instr->m_opcode == Js::OpCode::BrOnObject_A);
IR::RegOpnd *object = instr->GetSrc1()->IsRegOpnd() ? instr->GetSrc1()->AsRegOpnd() : nullptr;
IR::LabelInstr *done = instr->GetOrCreateContinueLabel();
IR::LabelInstr *target = instr->AsBranchInstr()->GetTarget();
IR::RegOpnd *typeRegOpnd = IR::RegOpnd::New(TyMachReg, m_func);
IR::IntConstOpnd *typeIdOpnd = IR::IntConstOpnd::New(Js::TypeIds_LastJavascriptPrimitiveType, TyInt32, instr->m_func);
if (!object)
{
object = IR::RegOpnd::New(TyVar, m_func);
Lowerer::InsertMove(object, instr->GetSrc1(), instr);
}
// TEST object, 1
// JNE $done
// MOV typeRegOpnd, [object + offset(Type)]
// CMP [typeRegOpnd + offset(TypeId)], TypeIds_LastJavascriptPrimitiveType
// JGT $target
// $done:
m_lowererMD.GenerateObjectTest(object, instr, done);
InsertMove(typeRegOpnd,
IR::IndirOpnd::New(object, Js::RecyclableObject::GetOffsetOfType(), TyMachReg, m_func),
instr);
InsertCompareBranch(
IR::IndirOpnd::New(typeRegOpnd, Js::Type::GetOffsetOfTypeId(), TyInt32, m_func),
typeIdOpnd, Js::OpCode::BrGt_A, target, instr);
instr->Remove();
}
void Lowerer::GenerateObjectHeaderInliningTest(IR::RegOpnd *baseOpnd, IR::LabelInstr * target,IR::Instr *insertBeforeInstr)
{
Assert(baseOpnd);
Assert(target);
AssertMsg(
baseOpnd->GetValueType().IsLikelyObject() &&
baseOpnd->GetValueType().GetObjectType() == ObjectType::ObjectWithArray,
"Why are we here, when the object is already known not to have an ObjArray");
Assert(insertBeforeInstr);
Func *const func = insertBeforeInstr->m_func;
// mov type, [base + offsetOf(type)]
IR::RegOpnd *const opnd = IR::RegOpnd::New(TyMachPtr, func);
InsertMove(
opnd,
IR::IndirOpnd::New(
baseOpnd,
Js::DynamicObject::GetOffsetOfType(),
opnd->GetType(),
func),
insertBeforeInstr);
// mov typeHandler, [type + offsetOf(typeHandler)]
InsertMove(
opnd,
IR::IndirOpnd::New(
opnd,
Js::DynamicType::GetOffsetOfTypeHandler(),
opnd->GetType(),
func),
insertBeforeInstr);
IR::IndirOpnd * offsetOfInlineSlotOpnd = IR::IndirOpnd::New(opnd,Js::DynamicTypeHandler::GetOffsetOfOffsetOfInlineSlots(), TyInt16, func);
IR::IntConstOpnd * objHeaderInlinedSlotOffset = IR::IntConstOpnd::New(Js::DynamicTypeHandler::GetOffsetOfObjectHeaderInlineSlots(), TyInt16, func);
// CMP [typeHandler + offsetOf(offsetOfInlineSlots)], objHeaderInlinedSlotOffset
InsertCompareBranch(
offsetOfInlineSlotOpnd,
objHeaderInlinedSlotOffset,
Js::OpCode::BrEq_A,
target,
insertBeforeInstr);
}
void Lowerer::GenerateObjectTypeTest(IR::RegOpnd *srcReg, IR::Instr *instrInsert, IR::LabelInstr *labelHelper)
{
Assert(srcReg);
if (!srcReg->IsNotTaggedValue())
{
m_lowererMD.GenerateObjectTest(srcReg, instrInsert, labelHelper);
}
// CMP [srcReg], Js::DynamicObject::`vtable'
// JNE $helper
IR::BranchInstr *branchInstr = InsertCompareBranch(
IR::IndirOpnd::New(srcReg, 0, TyMachPtr, m_func),
LoadVTableValueOpnd(instrInsert, VTableValue::VtableDynamicObject),
Js::OpCode::BrNeq_A,
labelHelper,
instrInsert);
InsertObjectPoison(srcReg, branchInstr, instrInsert, false);
}
const VTableValue Lowerer::VtableAddresses[static_cast<ValueType::TSize>(ObjectType::Count)] =
{
/* ObjectType::UninitializedObject */ VTableValue::VtableInvalid,
/* ObjectType::Object */ VTableValue::VtableInvalid,
/* ObjectType::RegExp */ VTableValue::VtableInvalid,
/* ObjectType::ObjectWithArray */ VTableValue::VtableJavascriptArray,
/* ObjectType::Array */ VTableValue::VtableJavascriptArray,
/* ObjectType::Int8Array */ VTableValue::VtableInt8Array,
/* ObjectType::Uint8Array */ VTableValue::VtableUint8Array,
/* ObjectType::Uint8ClampedArray */ VTableValue::VtableUint8ClampedArray,
/* ObjectType::Int16Array */ VTableValue::VtableInt16Array,
/* ObjectType::Uint16Array */ VTableValue::VtableUint16Array,
/* ObjectType::Int32Array */ VTableValue::VtableInt32Array,
/* ObjectType::Uint32Array */ VTableValue::VtableUint32Array,
/* ObjectType::Float32Array */ VTableValue::VtableFloat32Array,
/* ObjectType::Float64Array */ VTableValue::VtableFloat64Array,
/* ObjectType::Int8VirtualArray */ VTableValue::VtableInt8VirtualArray,
/* ObjectType::Uint8VirtualArray */ VTableValue::VtableUint8VirtualArray,
/* ObjectType::Uint8ClampedVirtualArray */ VTableValue::VtableUint8ClampedVirtualArray,
/* ObjectType::Int16VirtualArray */ VTableValue::VtableInt16VirtualArray,
/* ObjectType::Uint16VirtualArray */ VTableValue::VtableUint16VirtualArray,
/* ObjectType::Int32VirtualArray */ VTableValue::VtableInt32VirtualArray,
/* ObjectType::Uint32VirtualArray */ VTableValue::VtableUint32VirtualArray,
/* ObjectType::Float32VirtualArray */ VTableValue::VtableFloat32VirtualArray,
/* ObjectType::Float64VirtualArray */ VTableValue::VtableFloat64VirtualArray,
/* ObjectType::Int8MixedArray */ VTableValue::VtableInt8Array,
/* ObjectType::Uint8MixedArray */ VTableValue::VtableUint8Array,
/* ObjectType::Uint8ClampedMixedArray */ VTableValue::VtableUint8ClampedArray,
/* ObjectType::Int16MixedArray */ VTableValue::VtableInt16Array,
/* ObjectType::Uint16MixedArray */ VTableValue::VtableUint16Array,
/* ObjectType::Int32MixedArray */ VTableValue::VtableInt32Array,
/* ObjectType::Uint32MixedArray */ VTableValue::VtableUint32Array,
/* ObjectType::Float32MixedArray */ VTableValue::VtableFloat32Array,
/* ObjectType::Float64MixedArray */ VTableValue::VtableFloat64Array,
/* ObjectType::Int64Array */ VTableValue::VtableInt64Array,
/* ObjectType::Uint64Array */ VTableValue::VtableUint64Array,
/* ObjectType::BoolArray */ VTableValue::VtableBoolArray,
/* ObjectType::CharArray */ VTableValue::VtableCharArray
};
const uint32 Lowerer::OffsetsOfHeadSegment[static_cast<ValueType::TSize>(ObjectType::Count)] =
{
/* ObjectType::UninitializedObject */ static_cast<uint32>(-1),
/* ObjectType::Object */ static_cast<uint32>(-1),
/* ObjectType::RegExp */ static_cast<uint32>(-1),
/* ObjectType::ObjectWithArray */ Js::JavascriptArray::GetOffsetOfHead(),
/* ObjectType::Array */ Js::JavascriptArray::GetOffsetOfHead(),
/* ObjectType::Int8Array */ Js::Int8Array::GetOffsetOfBuffer(),
/* ObjectType::Uint8Array */ Js::Uint8Array::GetOffsetOfBuffer(),
/* ObjectType::Uint8ClampedArray */ Js::Uint8ClampedArray::GetOffsetOfBuffer(),
/* ObjectType::Int16Array */ Js::Int16Array::GetOffsetOfBuffer(),
/* ObjectType::Uint16Array */ Js::Uint16Array::GetOffsetOfBuffer(),
/* ObjectType::Int32Array */ Js::Int32Array::GetOffsetOfBuffer(),
/* ObjectType::Uint32Array */ Js::Uint32Array::GetOffsetOfBuffer(),
/* ObjectType::Float32Array */ Js::Float32Array::GetOffsetOfBuffer(),
/* ObjectType::Float64Array */ Js::Float64Array::GetOffsetOfBuffer(),
/* ObjectType::Int8VirtualArray */ Js::Int8VirtualArray::GetOffsetOfBuffer(),
/* ObjectType::Uint8VirtualArray */ Js::Uint8VirtualArray::GetOffsetOfBuffer(),
/* ObjectType::Uint8ClampedVirtualArray */ Js::Uint8ClampedVirtualArray::GetOffsetOfBuffer(),
/* ObjectType::Int16VirtualArray */ Js::Int16VirtualArray::GetOffsetOfBuffer(),
/* ObjectType::Uint16VirtualArray */ Js::Uint16VirtualArray::GetOffsetOfBuffer(),
/* ObjectType::Int32VirtualArray */ Js::Int32VirtualArray::GetOffsetOfBuffer(),
/* ObjectType::Uint32VirtualArray */ Js::Uint32VirtualArray::GetOffsetOfBuffer(),
/* ObjectType::Float32VirtualArray */ Js::Float32VirtualArray::GetOffsetOfBuffer(),
/* ObjectType::Float64VirtualArray */ Js::Float64VirtualArray::GetOffsetOfBuffer(),
/* ObjectType::Int8MixedArray */ Js::Int8Array::GetOffsetOfBuffer(),
/* ObjectType::Uint8MixedArray */ Js::Uint8Array::GetOffsetOfBuffer(),
/* ObjectType::Uint8ClampedMixedArray */ Js::Uint8ClampedArray::GetOffsetOfBuffer(),
/* ObjectType::Int16MixedArray */ Js::Int16Array::GetOffsetOfBuffer(),
/* ObjectType::Uint16MixedArray */ Js::Uint16Array::GetOffsetOfBuffer(),
/* ObjectType::Int32MixedArray */ Js::Int32Array::GetOffsetOfBuffer(),
/* ObjectType::Uint32MixedArray */ Js::Uint32Array::GetOffsetOfBuffer(),
/* ObjectType::Float32MixedArray */ Js::Float32Array::GetOffsetOfBuffer(),
/* ObjectType::Float64MixedArray */ Js::Float64Array::GetOffsetOfBuffer(),
/* ObjectType::Int64Array */ Js::Int64Array::GetOffsetOfBuffer(),
/* ObjectType::Uint64Array */ Js::Uint64Array::GetOffsetOfBuffer(),
/* ObjectType::BoolArray */ Js::BoolArray::GetOffsetOfBuffer(),
/* ObjectType::CharArray */ Js::CharArray::GetOffsetOfBuffer()
};
const uint32 Lowerer::OffsetsOfLength[static_cast<ValueType::TSize>(ObjectType::Count)] =
{
/* ObjectType::UninitializedObject */ static_cast<uint32>(-1),
/* ObjectType::Object */ static_cast<uint32>(-1),
/* ObjectType::RegExp */ static_cast<uint32>(-1),
/* ObjectType::ObjectWithArray */ Js::JavascriptArray::GetOffsetOfLength(),
/* ObjectType::Array */ Js::JavascriptArray::GetOffsetOfLength(),
/* ObjectType::Int8Array */ Js::Int8Array::GetOffsetOfLength(),
/* ObjectType::Uint8Array */ Js::Uint8Array::GetOffsetOfLength(),
/* ObjectType::Uint8ClampedArray */ Js::Uint8ClampedArray::GetOffsetOfLength(),
/* ObjectType::Int16Array */ Js::Int16Array::GetOffsetOfLength(),
/* ObjectType::Uint16Array */ Js::Uint16Array::GetOffsetOfLength(),
/* ObjectType::Int32Array */ Js::Int32Array::GetOffsetOfLength(),
/* ObjectType::Uint32Array */ Js::Uint32Array::GetOffsetOfLength(),
/* ObjectType::Float32Array */ Js::Float32Array::GetOffsetOfLength(),
/* ObjectType::Float64Array */ Js::Float64Array::GetOffsetOfLength(),
/* ObjectType::Int8VirtualArray */ Js::Int8VirtualArray::GetOffsetOfLength(),
/* ObjectType::Uint8VirtualArray */ Js::Uint8VirtualArray::GetOffsetOfLength(),
/* ObjectType::Uint8ClampedVirtualArray */ Js::Uint8ClampedVirtualArray::GetOffsetOfLength(),
/* ObjectType::Int16VirtualArray */ Js::Int16VirtualArray::GetOffsetOfLength(),
/* ObjectType::Uint16VirtualArray */ Js::Uint16VirtualArray::GetOffsetOfLength(),
/* ObjectType::Int32VirtualArray */ Js::Int32VirtualArray::GetOffsetOfLength(),
/* ObjectType::Uint32VirtualArray */ Js::Uint32VirtualArray::GetOffsetOfLength(),
/* ObjectType::Float32VirtualArray */ Js::Float32VirtualArray::GetOffsetOfLength(),
/* ObjectType::Float64VirtualArray */ Js::Float64VirtualArray::GetOffsetOfLength(),
/* ObjectType::Int8MixedArray */ Js::Int8Array::GetOffsetOfLength(),
/* ObjectType::Uint8MixedArray */ Js::Uint8Array::GetOffsetOfLength(),
/* ObjectType::Uint8ClampedMixedArray */ Js::Uint8ClampedArray::GetOffsetOfLength(),
/* ObjectType::Int16MixedArray */ Js::Int16Array::GetOffsetOfLength(),
/* ObjectType::Uint16MixedArray */ Js::Uint16Array::GetOffsetOfLength(),
/* ObjectType::Int32MixedArray */ Js::Int32Array::GetOffsetOfLength(),
/* ObjectType::Uint32MixedArray */ Js::Uint32Array::GetOffsetOfLength(),
/* ObjectType::Float32MixedArray */ Js::Float32Array::GetOffsetOfLength(),
/* ObjectType::Float64MixedArray */ Js::Float64Array::GetOffsetOfLength(),
/* ObjectType::Int64Array */ Js::Int64Array::GetOffsetOfLength(),
/* ObjectType::Uint64Array */ Js::Uint64Array::GetOffsetOfLength(),
/* ObjectType::BoolArray */ Js::BoolArray::GetOffsetOfLength(),
/* ObjectType::CharArray */ Js::CharArray::GetOffsetOfLength()
};
const IRType Lowerer::IndirTypes[static_cast<ValueType::TSize>(ObjectType::Count)] =
{
/* ObjectType::UninitializedObject */ TyIllegal,
/* ObjectType::Object */ TyIllegal,
/* ObjectType::RegExp */ TyIllegal,
/* ObjectType::ObjectWithArray */ TyVar,
/* ObjectType::Array */ TyVar,
/* ObjectType::Int8Array */ TyInt8,
/* ObjectType::Uint8Array */ TyUint8,
/* ObjectType::Uint8ClampedArray */ TyUint8,
/* ObjectType::Int16Array */ TyInt16,
/* ObjectType::Uint16Array */ TyUint16,
/* ObjectType::Int32Array */ TyInt32,
/* ObjectType::Uint32Array */ TyUint32,
/* ObjectType::Float32Array */ TyFloat32,
/* ObjectType::Float64Array */ TyFloat64,
/* ObjectType::Int8VirtualArray */ TyInt8,
/* ObjectType::Uint8VirtualArray */ TyUint8,
/* ObjectType::Uint8ClampedVirtualArray */ TyUint8,
/* ObjectType::Int16VirtualArray */ TyInt16,
/* ObjectType::Uint16vArray */ TyUint16,
/* ObjectType::Int32VirtualArray */ TyInt32,
/* ObjectType::Uint32VirtualArray */ TyUint32,
/* ObjectType::Float32VirtualArray */ TyFloat32,
/* ObjectType::Float64VirtualArray */ TyFloat64,
/* ObjectType::Int8MixedArray */ TyInt8,
/* ObjectType::Uint8MixedArray */ TyUint8,
/* ObjectType::Uint8ClampedMixedArray */ TyUint8,
/* ObjectType::Int16MixedArray */ TyInt16,
/* ObjectType::Uint16MixedArray */ TyUint16,
/* ObjectType::Int32MixedArray */ TyInt32,
/* ObjectType::Uint32MixedArray */ TyUint32,
/* ObjectType::Float32MixedArray */ TyFloat32,
/* ObjectType::Float64MixedArray */ TyFloat64,
/* ObjectType::Int64Array */ TyInt64,
/* ObjectType::Uint64Array */ TyUint64,
/* ObjectType::BoolArray */ TyUint8,
/* ObjectType::CharArray */ TyUint16
};
const BYTE Lowerer::IndirScales[static_cast<ValueType::TSize>(ObjectType::Count)] =
{
/* ObjectType::UninitializedObject */ static_cast<BYTE>(-1),
/* ObjectType::Object */ static_cast<BYTE>(-1),
/* ObjectType::RegExp */ static_cast<BYTE>(-1),
/* ObjectType::ObjectWithArray */ LowererMD::GetDefaultIndirScale(),
/* ObjectType::Array */ LowererMD::GetDefaultIndirScale(),
/* ObjectType::Int8Array */ 0, // log2(sizeof(int8))
/* ObjectType::Uint8Array */ 0, // log2(sizeof(uint8))
/* ObjectType::Uint8ClampedArray */ 0, // log2(sizeof(uint8))
/* ObjectType::Int16Array */ 1, // log2(sizeof(int16))
/* ObjectType::Uint16Array */ 1, // log2(sizeof(uint16))
/* ObjectType::Int32Array */ 2, // log2(sizeof(int32))
/* ObjectType::Uint32Array */ 2, // log2(sizeof(uint32))
/* ObjectType::Float32Array */ 2, // log2(sizeof(float))
/* ObjectType::Float64Array */ 3, // log2(sizeof(double))
/* ObjectType::Int8VirtualArray */ 0, // log2(sizeof(int8))
/* ObjectType::Uint8VirtualArray */ 0, // log2(sizeof(uint8))
/* ObjectType::Uint8ClampedVirtualArray */ 0, // log2(sizeof(uint8))
/* ObjectType::Int16VirtualArray */ 1, // log2(sizeof(int16))
/* ObjectType::Uint16VirtualArray */ 1, // log2(sizeof(uint16))
/* ObjectType::Int32VirtualArray */ 2, // log2(sizeof(int32))
/* ObjectType::Uint32VirtualArray */ 2, // log2(sizeof(uint32))
/* ObjectType::Float32VirtualArray */ 2, // log2(sizeof(float))
/* ObjectType::Float64VirtualArray */ 3, // log2(sizeof(double))
/* ObjectType::Int8MixedArray */ 0, // log2(sizeof(int8))
/* ObjectType::Uint8MixedArray */ 0, // log2(sizeof(uint8))
/* ObjectType::Uint8ClampedMixedArray */ 0, // log2(sizeof(uint8))
/* ObjectType::Int16MixedArray */ 1, // log2(sizeof(int16))
/* ObjectType::Uint16MixedArray */ 1, // log2(sizeof(uint16))
/* ObjectType::Int32MixedArray */ 2, // log2(sizeof(int32))
/* ObjectType::Uint32MixedArray */ 2, // log2(sizeof(uint32))
/* ObjectType::Float32MixedArray */ 2, // log2(sizeof(float))
/* ObjectType::Float64MixedArray */ 3, // log2(sizeof(double))
/* ObjectType::Int64Array */ 3, // log2(sizeof(int64))
/* ObjectType::Uint64Array */ 3, // log2(sizeof(uint64))
/* ObjectType::BoolArray */ 0, // log2(sizeof(bool))
/* ObjectType::CharArray */ 1 // log2(sizeof(char16))
};
VTableValue Lowerer::GetArrayVtableAddress(const ValueType valueType, bool getVirtual)
{
Assert(valueType.IsLikelyAnyOptimizedArray());
if(valueType.IsLikelyArrayOrObjectWithArray())
{
if(valueType.HasIntElements())
{
return VTableValue::VtableNativeIntArray;
}
else if(valueType.HasFloatElements())
{
return VTableValue::VtableNativeFloatArray;
}
}
if (getVirtual && valueType.IsLikelyMixedTypedArrayType())
{
return VtableAddresses[static_cast<ValueType::TSize>(valueType.GetMixedToVirtualTypedArrayObjectType())];
}
return VtableAddresses[static_cast<ValueType::TSize>(valueType.GetObjectType())];
}
uint32 Lowerer::GetArrayOffsetOfHeadSegment(const ValueType valueType)
{
Assert(valueType.IsLikelyAnyOptimizedArray());
return OffsetsOfHeadSegment[static_cast<ValueType::TSize>(valueType.GetObjectType())];
}
uint32 Lowerer::GetArrayOffsetOfLength(const ValueType valueType)
{
Assert(valueType.IsLikelyAnyOptimizedArray());
return OffsetsOfLength[static_cast<ValueType::TSize>(valueType.GetObjectType())];
}
IRType Lowerer::GetArrayIndirType(const ValueType valueType)
{
Assert(valueType.IsLikelyAnyOptimizedArray());
if(valueType.IsLikelyArrayOrObjectWithArray())
{
if(valueType.HasIntElements())
{
return TyInt32;
}
else if(valueType.HasFloatElements())
{
return TyFloat64;
}
}
return IndirTypes[static_cast<ValueType::TSize>(valueType.GetObjectType())];
}
BYTE Lowerer::GetArrayIndirScale(const ValueType valueType)
{
Assert(valueType.IsLikelyAnyOptimizedArray());
if(valueType.IsLikelyArrayOrObjectWithArray())
{
if(valueType.HasIntElements())
{
return 2; // log2(sizeof(int32))
}
else if(valueType.HasFloatElements())
{
return 3; // log2(sizeof(double))
}
}
return IndirScales[static_cast<ValueType::TSize>(valueType.GetObjectType())];
}
int Lowerer::SimdGetElementCountFromBytes(ValueType arrValueType, uint8 dataWidth)
{
Assert(dataWidth == 4 || dataWidth == 8 || dataWidth == 12 || dataWidth == 16);
Assert(arrValueType.IsTypedArray());
BYTE bpe = 1 << Lowerer::GetArrayIndirScale(arrValueType);
// round up
return (int)::ceil(((float)dataWidth) / bpe);
}
bool Lowerer::ShouldGenerateArrayFastPath(
const IR::Opnd *const arrayOpnd,
const bool supportsObjectsWithArrays,
const bool supportsTypedArrays,
const bool requiresSse2ForFloatArrays) const
{
Assert(arrayOpnd);
const ValueType arrayValueType(arrayOpnd->GetValueType());
if(arrayValueType.IsUninitialized())
{
// Don't have info about the value type, better to generate the fast path anyway
return true;
}
if (!arrayValueType.IsLikelyObject())
{
if (!arrayValueType.HasBeenObject() || arrayValueType.IsLikelyString())
{
return false;
}
//We have seen at least once there is an object in the code path. Generate fastpath hoping it to be array.
//Its nice if we can get all the attributes set but valueType is only 16 bits. Consider expanding the same.
return true;
}
if( (!supportsObjectsWithArrays && arrayValueType.GetObjectType() == ObjectType::ObjectWithArray) ||
(!supportsTypedArrays && arrayValueType.IsLikelyTypedArray()) )
{
// The fast path likely would not hit
return false;
}
if(arrayValueType.GetObjectType() == ObjectType::UninitializedObject)
{
// Don't have info about the object type, better to generate the fast path anyway
return true;
}
#ifdef _M_IX86
if(requiresSse2ForFloatArrays &&
(
arrayValueType.GetObjectType() == ObjectType::Float32Array ||
arrayValueType.GetObjectType() == ObjectType::Float64Array
) &&
!AutoSystemInfo::Data.SSE2Available())
{
// Fast paths for float arrays rely on SSE2
return false;
}
#endif
return !arrayValueType.IsLikelyAnyUnOptimizedArray();
}
IR::RegOpnd *Lowerer::LoadObjectArray(IR::RegOpnd *const baseOpnd, IR::Instr *const insertBeforeInstr)
{
Assert(baseOpnd);
Assert(
baseOpnd->GetValueType().IsLikelyObject() &&
baseOpnd->GetValueType().GetObjectType() == ObjectType::ObjectWithArray);
Assert(insertBeforeInstr);
Func *const func = insertBeforeInstr->m_func;
// mov array, [base + offsetOf(objectArrayOrFlags)]
IR::RegOpnd *const arrayOpnd =
baseOpnd->IsArrayRegOpnd() ? baseOpnd->AsArrayRegOpnd()->CopyAsRegOpnd(func) : baseOpnd->Copy(func)->AsRegOpnd();
arrayOpnd->m_sym = StackSym::New(TyVar, func);
arrayOpnd->SetValueType(arrayOpnd->GetValueType().ToArray());
const IR::AutoReuseOpnd autoReuseArrayOpnd(arrayOpnd, func, false /* autoDelete */);
InsertMove(
arrayOpnd,
IR::IndirOpnd::New(
baseOpnd,
Js::DynamicObject::GetOffsetOfObjectArray(),
arrayOpnd->GetType(),
func),
insertBeforeInstr);
return arrayOpnd;
}
void
Lowerer::GenerateIsEnabledArraySetElementFastPathCheck(
IR::LabelInstr * isDisabledLabel,
IR::Instr * const insertBeforeInstr)
{
InsertCompareBranch(
this->LoadOptimizationOverridesValueOpnd(insertBeforeInstr, OptimizationOverridesValue::OptimizationOverridesArraySetElementFastPathVtable),
LoadVTableValueOpnd(insertBeforeInstr, VTableValue::VtableInvalid),
Js::OpCode::BrEq_A,
isDisabledLabel,
insertBeforeInstr);
}
IR::RegOpnd *Lowerer::GenerateArrayTest(
IR::RegOpnd *const baseOpnd,
IR::LabelInstr *const isNotObjectLabel,
IR::LabelInstr *const isNotArrayLabel,
IR::Instr *const insertBeforeInstr,
const bool forceFloat,
const bool isStore,
const bool allowDefiniteArray)
{
Assert(baseOpnd);
const ValueType baseValueType(baseOpnd->GetValueType());
// Shouldn't request to do an array test when it's already known to be an array, or if it's unlikely to be an array
Assert(!baseValueType.IsAnyOptimizedArray() || allowDefiniteArray || baseValueType.IsNativeArray());
Assert(baseValueType.IsUninitialized() || baseValueType.HasBeenObject());
Assert(isNotObjectLabel);
Assert(isNotArrayLabel);
Assert(insertBeforeInstr);
Func *const func = insertBeforeInstr->m_func;
IR::RegOpnd *arrayOpnd;
IR::AutoReuseOpnd autoReuseArrayOpnd;
if(baseValueType.IsLikelyObject() && baseValueType.GetObjectType() == ObjectType::ObjectWithArray)
{
// Only DynamicObject is allowed (DynamicObject vtable is ensured) because some object types have special handling for
// index properties - arguments object, string object, external object, etc.
// JavascriptArray::Jit_TryGetArrayForObjectWithArray as well.
GenerateObjectTypeTest(baseOpnd, insertBeforeInstr, isNotObjectLabel);
GenerateObjectHeaderInliningTest(baseOpnd, isNotArrayLabel, insertBeforeInstr);
arrayOpnd = LoadObjectArray(baseOpnd, insertBeforeInstr);
autoReuseArrayOpnd.Initialize(arrayOpnd, func, false /* autoDelete */);
// test array, array
// je $isNotArrayLabel
// test array, 1
// jne $isNotArrayLabel
InsertTestBranch(
arrayOpnd,
arrayOpnd,
Js::OpCode::BrEq_A,
isNotArrayLabel,
insertBeforeInstr);
InsertTestBranch(
arrayOpnd,
IR::IntConstOpnd::New(1, TyUint8, func, true),
Js::OpCode::BrNeq_A,
isNotArrayLabel,
insertBeforeInstr);
}
else
{
if(!baseOpnd->IsNotTaggedValue())
{
m_lowererMD.GenerateObjectTest(baseOpnd, insertBeforeInstr, isNotObjectLabel);
}
arrayOpnd = baseOpnd->Copy(func)->AsRegOpnd();
if(!baseValueType.IsLikelyAnyOptimizedArray())
{
arrayOpnd->SetValueType(
ValueType::GetObject(ObjectType::Array)
.ToLikely()
.SetHasNoMissingValues(false)
.SetArrayTypeId(Js::TypeIds_Array));
}
autoReuseArrayOpnd.Initialize(arrayOpnd, func, false /* autoDelete */);
}
VTableValue vtableAddress = baseValueType.IsLikelyAnyOptimizedArray()
? GetArrayVtableAddress(baseValueType)
: VTableValue::VtableJavascriptArray;
VTableValue virtualVtableAddress = VTableValue::VtableInvalid;
if (baseValueType.IsLikelyMixedTypedArrayType())
{
virtualVtableAddress = GetArrayVtableAddress(baseValueType, true);
}
IR::Opnd * vtableOpnd;
IR::Opnd * vtableVirtualOpnd = nullptr;
if (isStore &&
(vtableAddress == VTableValue::VtableJavascriptArray ||
baseValueType.IsLikelyNativeArray()))
{
vtableOpnd = IR::RegOpnd::New(TyMachPtr, func);
if (baseValueType.IsLikelyNativeArray())
{
if (baseValueType.HasIntElements())
{
InsertMove(vtableOpnd, this->LoadOptimizationOverridesValueOpnd(insertBeforeInstr, OptimizationOverridesValue::OptimizationOverridesIntArraySetElementFastPathVtable), insertBeforeInstr);
}
else
{
Assert(baseValueType.HasFloatElements());
InsertMove(vtableOpnd, this->LoadOptimizationOverridesValueOpnd(insertBeforeInstr, OptimizationOverridesValue::OptimizationOverridesFloatArraySetElementFastPathVtable), insertBeforeInstr);
}
}
else
{
InsertMove(vtableOpnd, this->LoadOptimizationOverridesValueOpnd(insertBeforeInstr, OptimizationOverridesValue::OptimizationOverridesArraySetElementFastPathVtable), insertBeforeInstr);
}
}
else
{
vtableOpnd = LoadVTableValueOpnd(insertBeforeInstr, vtableAddress);
}
// cmp [array], vtableAddress
// jne $isNotArrayLabel
if (forceFloat && baseValueType.IsLikelyNativeFloatArray())
{
// We expect a native float array. If we get native int instead, convert it on the spot and bail out afterward.
const auto goodArrayLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func);
IR::BranchInstr* branchInstr = InsertCompareBranch(
IR::IndirOpnd::New(arrayOpnd, 0, TyMachPtr, func),
vtableOpnd,
Js::OpCode::BrEq_A,
goodArrayLabel,
insertBeforeInstr);
InsertObjectPoison(arrayOpnd, branchInstr, insertBeforeInstr, isStore);
IR::LabelInstr *notFloatArrayLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func, true);
insertBeforeInstr->InsertBefore(notFloatArrayLabel);
if (isStore)
{
vtableOpnd = IR::RegOpnd::New(TyMachPtr, func);
InsertMove(vtableOpnd, IR::MemRefOpnd::New(
func->GetScriptContextInfo()->GetIntArraySetElementFastPathVtableAddr(),
TyMachPtr, func), insertBeforeInstr);
}
else
{
vtableOpnd = LoadVTableValueOpnd(insertBeforeInstr, VTableValue::VtableJavascriptNativeIntArray);
}
branchInstr = InsertCompareBranch(
IR::IndirOpnd::New(arrayOpnd, 0, TyMachPtr, func),
vtableOpnd,
Js::OpCode::BrNeq_A,
isNotArrayLabel,
insertBeforeInstr);
InsertObjectPoison(arrayOpnd, branchInstr, insertBeforeInstr, isStore);
m_lowererMD.LoadHelperArgument(insertBeforeInstr, arrayOpnd);
IR::Instr *helperInstr = IR::Instr::New(Js::OpCode::Call, m_func);
insertBeforeInstr->InsertBefore(helperInstr);
m_lowererMD.ChangeToHelperCall(helperInstr, IR::HelperIntArr_ToNativeFloatArray);
// Branch to the (bailout) label, because converting the array may have made our array checks unsafe.
InsertBranch(Js::OpCode::Br, isNotArrayLabel, insertBeforeInstr);
insertBeforeInstr->InsertBefore(goodArrayLabel);
}
else
{
IR::LabelInstr* goodArrayLabel = nullptr;
if (baseValueType.IsLikelyMixedTypedArrayType())
{
goodArrayLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func);
InsertCompareBranch(
IR::IndirOpnd::New(arrayOpnd, 0, TyMachPtr, func),
vtableOpnd,
Js::OpCode::BrEq_A,
goodArrayLabel,
insertBeforeInstr);
Assert(virtualVtableAddress);
vtableVirtualOpnd = LoadVTableValueOpnd(insertBeforeInstr, virtualVtableAddress);
Assert(vtableVirtualOpnd);
IR::BranchInstr* branchInstr = InsertCompareBranch(
IR::IndirOpnd::New(arrayOpnd, 0, TyMachPtr, func),
vtableVirtualOpnd,
Js::OpCode::BrNeq_A,
isNotArrayLabel,
insertBeforeInstr);
InsertObjectPoison(arrayOpnd, branchInstr, insertBeforeInstr, isStore);
insertBeforeInstr->InsertBefore(goodArrayLabel);
}
else
{
IR::BranchInstr *branchInstr = InsertCompareBranch(
IR::IndirOpnd::New(arrayOpnd, 0, TyMachPtr, func),
vtableOpnd,
Js::OpCode::BrNeq_A,
isNotArrayLabel,
insertBeforeInstr);
InsertObjectPoison(arrayOpnd, branchInstr, insertBeforeInstr, isStore);
}
}
ValueType arrayValueType(arrayOpnd->GetValueType());
if(arrayValueType.IsLikelyArrayOrObjectWithArray() && !arrayValueType.IsObject())
{
arrayValueType = arrayValueType.SetHasNoMissingValues(false);
}
arrayValueType = arrayValueType.ToDefiniteObject();
arrayOpnd->SetValueType(arrayValueType);
return arrayOpnd;
}
///----------------------------------------------------------------------------
///
/// Lowerer::HoistIndirOffset
///
/// Replace the offset of the given indir with a new symbol, which becomes the indir index.
/// Assign the new symbol by creating an assignment from the constant offset.
///
///----------------------------------------------------------------------------
IR::Instr *Lowerer::HoistIndirOffset(IR::Instr* instr, IR::IndirOpnd *indirOpnd, RegNum regNum)
{
int32 offset = indirOpnd->GetOffset();
if (indirOpnd->GetIndexOpnd())
{
Assert(indirOpnd->GetBaseOpnd());
return Lowerer::HoistIndirOffsetAsAdd(instr, indirOpnd, indirOpnd->GetBaseOpnd(), offset, regNum);
}
IR::IntConstOpnd *offsetOpnd = IR::IntConstOpnd::New(offset, TyInt32, instr->m_func);
IR::RegOpnd *indexOpnd = IR::RegOpnd::New(StackSym::New(TyMachReg, instr->m_func), regNum, TyMachReg, instr->m_func);
#if defined(DBG) && defined(_M_ARM)
if (regNum == SCRATCH_REG)
{
AssertMsg(indirOpnd->GetBaseOpnd()->GetReg()!= SCRATCH_REG, "Why both are SCRATCH_REG");
if (instr->GetSrc1() && instr->GetSrc1()->IsRegOpnd())
{
Assert(instr->GetSrc1()->AsRegOpnd()->GetReg() != SCRATCH_REG);
}
if (instr->GetSrc2() && instr->GetSrc2()->IsRegOpnd())
{
Assert(instr->GetSrc2()->AsRegOpnd()->GetReg() != SCRATCH_REG);
}
if (instr->GetDst() && instr->GetDst()->IsRegOpnd())
{
Assert(instr->GetDst()->AsRegOpnd()->GetReg() != SCRATCH_REG);
}
}
#endif
// Clear the offset and add a new reg as the index.
indirOpnd->SetOffset(0);
indirOpnd->SetIndexOpnd(indexOpnd);
IR::Instr *instrAssign = Lowerer::InsertMove(indexOpnd, offsetOpnd, instr);
indexOpnd->m_sym->SetIsIntConst(offset);
return instrAssign;
}
IR::Instr *Lowerer::HoistIndirOffsetAsAdd(IR::Instr* instr, IR::IndirOpnd *orgOpnd, IR::Opnd *baseOpnd, int offset, RegNum regNum)
{
IR::RegOpnd *newBaseOpnd = IR::RegOpnd::New(StackSym::New(TyMachPtr, instr->m_func), regNum, TyMachPtr, instr->m_func);
IR::IntConstOpnd *src2 = IR::IntConstOpnd::New(offset, TyInt32, instr->m_func);
IR::Instr * instrAdd = IR::Instr::New(Js::OpCode::Add_A, newBaseOpnd, baseOpnd, src2, instr->m_func);
LowererMD::ChangeToAdd(instrAdd, false);
instr->InsertBefore(instrAdd);
orgOpnd->ReplaceBaseOpnd(newBaseOpnd);
orgOpnd->SetOffset(0);
return instrAdd;
}
IR::Instr *Lowerer::HoistIndirIndexOpndAsAdd(IR::Instr* instr, IR::IndirOpnd *orgOpnd, IR::Opnd *baseOpnd, IR::Opnd *indexOpnd, RegNum regNum)
{
IR::RegOpnd *newBaseOpnd = IR::RegOpnd::New(StackSym::New(TyMachPtr, instr->m_func), regNum, TyMachPtr, instr->m_func);
IR::Instr * instrAdd = IR::Instr::New(Js::OpCode::Add_A, newBaseOpnd, baseOpnd, indexOpnd->UseWithNewType(TyMachPtr, instr->m_func), instr->m_func);
LowererMD::ChangeToAdd(instrAdd, false);
instr->InsertBefore(instrAdd);
orgOpnd->ReplaceBaseOpnd(newBaseOpnd);
orgOpnd->SetIndexOpnd(nullptr);
return instrAdd;
}
///----------------------------------------------------------------------------
///
/// Lowerer::HoistSymOffset
///
/// Replace the given sym with an indir using the given base and offset.
/// (This is used, for instance, to hoist a sym offset that is too large to encode.)
///
///----------------------------------------------------------------------------
IR::Instr *Lowerer::HoistSymOffset(IR::Instr *instr, IR::SymOpnd *symOpnd, RegNum baseReg, uint32 offset, RegNum regNum)
{
IR::RegOpnd *baseOpnd = IR::RegOpnd::New(nullptr, baseReg, TyMachPtr, instr->m_func);
IR::IndirOpnd *indirOpnd = IR::IndirOpnd::New(baseOpnd, offset, symOpnd->GetType(), instr->m_func);
if (symOpnd == instr->GetDst())
{
instr->ReplaceDst(indirOpnd);
}
else
{
instr->ReplaceSrc(symOpnd, indirOpnd);
}
return Lowerer::HoistIndirOffset(instr, indirOpnd, regNum);
}
IR::Instr *Lowerer::HoistSymOffsetAsAdd(IR::Instr* instr, IR::SymOpnd *orgOpnd, IR::Opnd *baseOpnd, int offset, RegNum regNum)
{
IR::IndirOpnd *newIndirOpnd = IR::IndirOpnd::New(baseOpnd->AsRegOpnd(), 0, TyMachPtr, instr->m_func);
instr->Replace(orgOpnd, newIndirOpnd); // Replace SymOpnd with IndirOpnd
return Lowerer::HoistIndirOffsetAsAdd(instr, newIndirOpnd, baseOpnd, offset, regNum);
}
IR::LabelInstr *Lowerer::InsertLabel(const bool isHelper, IR::Instr *const insertBeforeInstr)
{
Assert(insertBeforeInstr);
Func *const func = insertBeforeInstr->m_func;
IR::LabelInstr *const instr = IR::LabelInstr::New(Js::OpCode::Label, func, isHelper);
insertBeforeInstr->InsertBefore(instr);
return instr;
}
IR::Instr *Lowerer::InsertMoveWithBarrier(IR::Opnd *dst, IR::Opnd *src, IR::Instr *const insertBeforeInstr)
{
return Lowerer::InsertMove(dst, src, insertBeforeInstr, true);
}
IR::Instr *Lowerer::InsertMove(IR::Opnd *dst, IR::Opnd *src, IR::Instr *const insertBeforeInstr, bool generateWriteBarrier)
{
Assert(dst);
Assert(src);
Assert(insertBeforeInstr);
Func *const func = insertBeforeInstr->m_func;
if(dst->IsFloat() && src->IsConstOpnd())
{
return LoadFloatFromNonReg(src, dst, insertBeforeInstr);
}
if(TySize[dst->GetType()] < TySize[src->GetType()])
{
#if _M_IX86
if (IRType_IsInt64(src->GetType()))
{
// On x86, if we are trying to move an int64 to a smaller type
// Insert a move of the low bits into dst
return InsertMove(dst, func->FindOrCreateInt64Pair(src).low, insertBeforeInstr, generateWriteBarrier);
}
else
#endif
{
src = src->UseWithNewType(dst->GetType(), func);
}
}
IR::Instr * instr = IR::Instr::New(Js::OpCode::Ld_A, dst, src, func);
insertBeforeInstr->InsertBefore(instr);
if (generateWriteBarrier)
{
instr = LowererMD::ChangeToWriteBarrierAssign(instr, func);
}
else
{
LowererMD::ChangeToAssignNoBarrierCheck(instr);
}
return instr;
}
IR::BranchInstr *Lowerer::InsertBranch(
const Js::OpCode opCode,
IR::LabelInstr *const target,
IR::Instr *const insertBeforeInstr)
{
return InsertBranch(opCode, false /* isUnsigned */, target, insertBeforeInstr);
}
IR::BranchInstr *Lowerer::InsertBranch(
const Js::OpCode opCode,
const bool isUnsigned,
IR::LabelInstr *const target,
IR::Instr *const insertBeforeInstr)
{
Assert(target);
Assert(insertBeforeInstr);
Func *const func = insertBeforeInstr->m_func;
IR::BranchInstr *const instr = IR::BranchInstr::New(opCode, target, func);
if(!instr->IsLowered())
{
if(opCode == Js::OpCode::Br)
{
instr->m_opcode = LowererMD::MDUncondBranchOpcode;
}
else if(isUnsigned)
{
instr->m_opcode = LowererMD::MDUnsignedBranchOpcode(opCode);
}
else
{
instr->m_opcode = LowererMD::MDBranchOpcode(opCode);
}
}
insertBeforeInstr->InsertBefore(instr);
return instr;
}
IR::Instr *Lowerer::InsertCompare(IR::Opnd *const src1, IR::Opnd *const src2, IR::Instr *const insertBeforeInstr)
{
Assert(src1);
Assert(!src1->IsFloat64()); // not implemented
Assert(src2);
Assert(!src2->IsFloat64()); // not implemented
Assert(!src1->IsEqual(src2));
Assert(insertBeforeInstr);
Func *const func = insertBeforeInstr->m_func;
IR::Instr *const instr = IR::Instr::New(Js::OpCode::CMP, func);
instr->SetSrc1(src1);
instr->SetSrc2(src2);
insertBeforeInstr->InsertBefore(instr);
LowererMD::Legalize(instr);
return instr;
}
IR::BranchInstr *Lowerer::InsertCompareBranch(
IR::Opnd *const compareSrc1,
IR::Opnd *const compareSrc2,
Js::OpCode branchOpCode,
IR::LabelInstr *const target,
IR::Instr *const insertBeforeInstr,
const bool ignoreNaN)
{
return InsertCompareBranch(compareSrc1, compareSrc2, branchOpCode, false /* isUnsigned */, target, insertBeforeInstr, ignoreNaN);
}
IR::BranchInstr *Lowerer::InsertCompareBranch(
IR::Opnd *compareSrc1,
IR::Opnd *compareSrc2,
Js::OpCode branchOpCode,
const bool isUnsigned,
IR::LabelInstr *const target,
IR::Instr *const insertBeforeInstr,
const bool ignoreNaN)
{
Assert(compareSrc1);
Assert(compareSrc2);
Func *const func = insertBeforeInstr->m_func;
if(compareSrc1->IsFloat())
{
Assert(compareSrc2->IsFloat());
Assert(!isUnsigned);
IR::BranchInstr *const instr = IR::BranchInstr::New(branchOpCode, target, compareSrc1, compareSrc2, func);
insertBeforeInstr->InsertBefore(instr);
return LowererMD::LowerFloatCondBranch(instr, ignoreNaN);
}
#ifdef _M_IX86
else if (compareSrc1->IsInt64())
{
Assert(compareSrc2->IsInt64());
IR::BranchInstr *const instr = IR::BranchInstr::New(branchOpCode, target, compareSrc1, compareSrc2, func);
insertBeforeInstr->InsertBefore(instr);
m_lowererMD.EmitInt64Instr(instr);
return instr;
}
#endif
Js::OpCode swapSrcsBranchOpCode;
switch(branchOpCode)
{
case Js::OpCode::BrEq_A:
case Js::OpCode::BrNeq_A:
swapSrcsBranchOpCode = branchOpCode;
goto Common_BrEqNeqGeGtLeLt;
case Js::OpCode::BrGe_A:
swapSrcsBranchOpCode = Js::OpCode::BrLe_A;
goto Common_BrEqNeqGeGtLeLt;
case Js::OpCode::BrGt_A:
swapSrcsBranchOpCode = Js::OpCode::BrLt_A;
goto Common_BrEqNeqGeGtLeLt;
case Js::OpCode::BrLe_A:
swapSrcsBranchOpCode = Js::OpCode::BrGe_A;
goto Common_BrEqNeqGeGtLeLt;
case Js::OpCode::BrLt_A:
swapSrcsBranchOpCode = Js::OpCode::BrGt_A;
// fall through
Common_BrEqNeqGeGtLeLt:
// Check if src1 is a constant and src2 is not, and facilitate folding the constant into the Cmp instruction
if( (
compareSrc1->IsIntConstOpnd() ||
(
compareSrc1->IsAddrOpnd() &&
Math::FitsInDWord(reinterpret_cast<size_t>(compareSrc1->AsAddrOpnd()->m_address))
)
) &&
!compareSrc2->IsIntConstOpnd() &&
!compareSrc2->IsAddrOpnd())
{
// Swap the sources and branch
IR::Opnd *const tempSrc = compareSrc1;
compareSrc1 = compareSrc2;
compareSrc2 = tempSrc;
branchOpCode = swapSrcsBranchOpCode;
}
// Check for compare with zero, to prefer using Test instead of Cmp
if( !compareSrc1->IsRegOpnd() ||
!(
(compareSrc2->IsIntConstOpnd() && compareSrc2->AsIntConstOpnd()->GetValue() == 0) ||
(compareSrc2->IsAddrOpnd() && !compareSrc2->AsAddrOpnd()->m_address)
) ||
branchOpCode == Js::OpCode::BrGt_A || branchOpCode == Js::OpCode::BrLe_A)
{
goto Default;
}
if(branchOpCode == Js::OpCode::BrGe_A || branchOpCode == Js::OpCode::BrLt_A)
{
if(isUnsigned)
{
goto Default;
}
branchOpCode = LowererMD::MDCompareWithZeroBranchOpcode(branchOpCode);
}
if(!compareSrc2->IsInUse())
{
compareSrc2->Free(func);
}
InsertTest(compareSrc1, compareSrc1, insertBeforeInstr);
break;
default:
Default:
InsertCompare(compareSrc1, compareSrc2, insertBeforeInstr);
break;
}
return InsertBranch(branchOpCode, isUnsigned, target, insertBeforeInstr);
}
IR::Instr *Lowerer::InsertTest(IR::Opnd *const src1, IR::Opnd *const src2, IR::Instr *const insertBeforeInstr)
{
Assert(src1);
Assert(!src1->IsFloat64()); // not implemented
Assert(src2);
Assert(!src2->IsFloat64()); // not implemented
#if !TARGET_64
Assert(!src1->IsInt64()); // not implemented
Assert(!src2->IsInt64()); // not implemented
#endif
Assert(insertBeforeInstr);
Func *const func = insertBeforeInstr->m_func;
IR::Instr *const instr = IR::Instr::New(LowererMD::MDTestOpcode, func);
instr->SetSrc1(src1);
instr->SetSrc2(src2);
insertBeforeInstr->InsertBefore(instr);
LowererMD::Legalize(instr);
return instr;
}
IR::BranchInstr *Lowerer::InsertTestBranch(
IR::Opnd *const testSrc1,
IR::Opnd *const testSrc2,
const Js::OpCode branchOpCode,
IR::LabelInstr *const target,
IR::Instr *const insertBeforeInstr)
{
return InsertTestBranch(testSrc1, testSrc2, branchOpCode, false /* isUnsigned */, target, insertBeforeInstr);
}
IR::BranchInstr *Lowerer::InsertTestBranch(
IR::Opnd *const testSrc1,
IR::Opnd *const testSrc2,
const Js::OpCode branchOpCode,
const bool isUnsigned,
IR::LabelInstr *const target,
IR::Instr *const insertBeforeInstr)
{
InsertTest(testSrc1, testSrc2, insertBeforeInstr);
return InsertBranch(branchOpCode, isUnsigned, target, insertBeforeInstr);
}
/* Inserts add with an overflow check, if we overflow throw OOM
* add dst, src
* jno $continueLabel
* overflow code
* $continueLabel : fall through
*/
void Lowerer::InsertAddWithOverflowCheck(
const bool needFlags,
IR::Opnd *const dst,
IR::Opnd *src1,
IR::Opnd *src2,
IR::Instr *const insertBeforeInstr,
IR::Instr **const onOverflowInsertBeforeInstrRef)
{
Func * func = insertBeforeInstr->m_func;
InsertAdd(needFlags, dst, src1, src2, insertBeforeInstr);
IR::LabelInstr *const continueLabel = IR::LabelInstr::New(Js::OpCode::Label, func, false);
InsertBranch(LowererMD::MDNotOverflowBranchOpcode, continueLabel, insertBeforeInstr);
*onOverflowInsertBeforeInstrRef = continueLabel;
}
IR::Instr *Lowerer::InsertAdd(
const bool needFlags,
IR::Opnd *const dst,
IR::Opnd *src1,
IR::Opnd *src2,
IR::Instr *const insertBeforeInstr)
{
Assert(dst);
Assert(src1);
Assert(src2);
Assert(insertBeforeInstr);
Func *const func = insertBeforeInstr->m_func;
if(src2->IsIntConstOpnd())
{
IR::IntConstOpnd *const intConstOpnd = src2->AsIntConstOpnd();
const IntConstType value = intConstOpnd->GetValue();
if(value < 0 && value != IntConstMin)
{
// Change (s1 = s1 + -5) into (s1 = s1 - 5)
IR::IntConstOpnd *const newSrc2 = intConstOpnd->CopyInternal(func);
newSrc2->SetValue(-value);
return InsertSub(needFlags, dst, src1, newSrc2, insertBeforeInstr);
}
}
else if(src1->IsIntConstOpnd())
{
IR::IntConstOpnd *const intConstOpnd = src1->AsIntConstOpnd();
const IntConstType value = intConstOpnd->GetValue();
if(value < 0 && value != IntConstMin)
{
// Change (s1 = -5 + s1) into (s1 = s1 - 5)
IR::Opnd *const newSrc1 = src2;
IR::IntConstOpnd *const newSrc2 = intConstOpnd->CopyInternal(func);
newSrc2->SetValue(-value);
return InsertSub(needFlags, dst, newSrc1, newSrc2, insertBeforeInstr);
}
}
IR::Instr *const instr = IR::Instr::New(Js::OpCode::Add_A, dst, src1, src2, func);
insertBeforeInstr->InsertBefore(instr);
LowererMD::ChangeToAdd(instr, needFlags);
LowererMD::Legalize(instr);
return instr;
}
IR::Instr *Lowerer::InsertSub(
const bool needFlags,
IR::Opnd *const dst,
IR::Opnd *src1,
IR::Opnd *src2,
IR::Instr *const insertBeforeInstr)
{
Assert(dst);
Assert(src1);
Assert(src2);
Assert(insertBeforeInstr);
Func *const func = insertBeforeInstr->m_func;
if(src2->IsIntConstOpnd())
{
IR::IntConstOpnd *const intConstOpnd = src2->AsIntConstOpnd();
const IntConstType value = intConstOpnd->GetValue();
if(value < 0 && value != IntConstMin)
{
// Change (s1 = s1 - -5) into (s1 = s1 + 5)
IR::IntConstOpnd *const newSrc2 = intConstOpnd->CopyInternal(func);
newSrc2->SetValue(-value);
return InsertAdd(needFlags, dst, src1, newSrc2, insertBeforeInstr);
}
}
IR::Instr *const instr = IR::Instr::New(Js::OpCode::Sub_A, dst, src1, src2, func);
insertBeforeInstr->InsertBefore(instr);
LowererMD::ChangeToSub(instr, needFlags);
LowererMD::Legalize(instr);
return instr;
}
IR::Instr *Lowerer::InsertLea(IR::RegOpnd *const dst, IR::Opnd *const src, IR::Instr *const insertBeforeInstr)
{
Assert(dst);
Assert(src);
Assert(src->IsIndirOpnd() || src->IsSymOpnd());
Assert(insertBeforeInstr);
Func *const func = insertBeforeInstr->m_func;
IR::Instr *const instr = IR::Instr::New(LowererMD::MDLea, dst, src, func);
insertBeforeInstr->InsertBefore(instr);
return ChangeToLea(instr);
}
IR::Instr *
Lowerer::ChangeToLea(IR::Instr * instr)
{
Assert(instr);
Assert(instr->GetDst());
Assert(instr->GetDst()->IsRegOpnd());
Assert(instr->GetSrc1());
Assert(instr->GetSrc1()->IsIndirOpnd() || instr->GetSrc1()->IsSymOpnd());
Assert(!instr->GetSrc2());
instr->m_opcode = LowererMD::MDLea;
LowererMD::Legalize(instr);
return instr;
}
#if _M_X64
IR::Instr *Lowerer::InsertMoveBitCast(
IR::Opnd *const dst,
IR::Opnd *const src1,
IR::Instr *const insertBeforeInstr)
{
Assert(dst);
Assert(dst->GetType() == TyFloat64);
Assert(src1);
Assert(src1->GetType() == TyUint64);
Assert(insertBeforeInstr);
Func *const func = insertBeforeInstr->m_func;
IR::Instr *const instr = IR::Instr::New(LowererMD::MDMovUint64ToFloat64Opcode, dst, src1, func);
insertBeforeInstr->InsertBefore(instr);
LowererMD::Legalize(instr);
return instr;
}
#endif
IR::Instr *Lowerer::InsertXor(
IR::Opnd *const dst,
IR::Opnd *const src1,
IR::Opnd *const src2,
IR::Instr *const insertBeforeInstr)
{
Assert(dst);
Assert(src1);
Assert(src2);
Assert(insertBeforeInstr);
Func *const func = insertBeforeInstr->m_func;
IR::Instr *const instr = IR::Instr::New(LowererMD::MDXorOpcode, dst, src1, src2, func);
insertBeforeInstr->InsertBefore(instr);
LowererMD::Legalize(instr);
return instr;
}
IR::Instr *Lowerer::InsertAnd(
IR::Opnd *const dst,
IR::Opnd *const src1,
IR::Opnd *const src2,
IR::Instr *const insertBeforeInstr)
{
Assert(dst);
Assert(src1);
Assert(src2);
Assert(insertBeforeInstr);
Func *const func = insertBeforeInstr->m_func;
IR::Instr *const instr = IR::Instr::New(Js::OpCode::AND, dst, src1, src2, func);
insertBeforeInstr->InsertBefore(instr);
LowererMD::Legalize(instr);
return instr;
}
IR::Instr *Lowerer::InsertOr(
IR::Opnd *const dst,
IR::Opnd *const src1,
IR::Opnd *const src2,
IR::Instr *const insertBeforeInstr)
{
Assert(dst);
Assert(src1);
Assert(src2);
Assert(insertBeforeInstr);
Func *const func = insertBeforeInstr->m_func;
IR::Instr *const instr = IR::Instr::New(LowererMD::MDOrOpcode, dst, src1, src2, func);
insertBeforeInstr->InsertBefore(instr);
LowererMD::Legalize(instr);
return instr;
}
IR::Instr *Lowerer::InsertShift(
const Js::OpCode opCode,
const bool needFlags,
IR::Opnd *const dst,
IR::Opnd *const src1,
IR::Opnd *const src2,
IR::Instr *const insertBeforeInstr)
{
Assert(dst);
Assert(!dst->IsFloat64()); // not implemented
Assert(src1);
Assert(!src1->IsFloat64()); // not implemented
Assert(src2);
Assert(!src2->IsFloat64()); // not implemented
Assert(insertBeforeInstr);
Func *const func = insertBeforeInstr->m_func;
IR::Instr *const instr = IR::Instr::New(opCode, dst, src1, src2, func);
insertBeforeInstr->InsertBefore(instr);
LowererMD::ChangeToShift(instr, needFlags);
LowererMD::Legalize(instr);
return instr;
}
IR::Instr *Lowerer::InsertShiftBranch(
const Js::OpCode shiftOpCode,
IR::Opnd *const dst,
IR::Opnd *const src1,
IR::Opnd *const src2,
const Js::OpCode branchOpCode,
IR::LabelInstr *const target,
IR::Instr *const insertBeforeInstr)
{
return InsertShiftBranch(shiftOpCode, dst, src1, src2, branchOpCode, false /* isUnsigned */, target, insertBeforeInstr);
}
IR::Instr *Lowerer::InsertShiftBranch(
const Js::OpCode shiftOpCode,
IR::Opnd *const dst,
IR::Opnd *const src1,
IR::Opnd *const src2,
const Js::OpCode branchOpCode,
const bool isUnsigned,
IR::LabelInstr *const target,
IR::Instr *const insertBeforeInstr)
{
InsertShift(shiftOpCode, true /* needFlags */, dst, src1, src2, insertBeforeInstr);
return InsertBranch(branchOpCode, isUnsigned, target, insertBeforeInstr);
}
IR::Instr *Lowerer::InsertConvertFloat32ToFloat64(
IR::Opnd *const dst,
IR::Opnd *const src,
IR::Instr *const insertBeforeInstr)
{
Assert(dst);
Assert(dst->IsFloat64());
Assert(src);
Assert(src->IsFloat32());
Assert(insertBeforeInstr);
Func *const func = insertBeforeInstr->m_func;
IR::Instr *const instr = IR::Instr::New(LowererMD::MDConvertFloat32ToFloat64Opcode, dst, src, func);
insertBeforeInstr->InsertBefore(instr);
LowererMD::Legalize(instr);
return instr;
}
IR::Instr *Lowerer::InsertConvertFloat64ToFloat32(
IR::Opnd *const dst,
IR::Opnd *const src,
IR::Instr *const insertBeforeInstr)
{
Assert(dst);
Assert(dst->IsFloat32());
Assert(src);
Assert(src->IsFloat64());
Assert(insertBeforeInstr);
Func *const func = insertBeforeInstr->m_func;
IR::Instr *const instr = IR::Instr::New(LowererMD::MDConvertFloat64ToFloat32Opcode, dst, src, func);
insertBeforeInstr->InsertBefore(instr);
LowererMD::Legalize(instr);
return instr;
}
void Lowerer::InsertDecUInt32PreventOverflow(
IR::Opnd *const dst,
IR::Opnd *const src,
IR::Instr *const insertBeforeInstr,
IR::Instr * *const onOverflowInsertBeforeInstrRef)
{
Assert(dst);
Assert(dst->GetType() == TyUint32);
Assert(src);
Assert(src->GetType() == TyUint32);
Assert(insertBeforeInstr);
Func *const func = insertBeforeInstr->m_func;
// Generate:
// subs temp, src, 1
// bcs $overflow
// mov dst, temp
// b $continue
// $overflow:
// mov dst, 0
// $continue:
IR::LabelInstr *const overflowLabel = Lowerer::InsertLabel(false, insertBeforeInstr);
// subs temp, src, 1
IR::RegOpnd *const tempOpnd = IR::RegOpnd::New(StackSym::New(TyUint32, func), TyUint32, func);
const IR::AutoReuseOpnd autoReuseTempOpnd(tempOpnd, func);
Lowerer::InsertSub(true, tempOpnd, src, IR::IntConstOpnd::New(1, TyUint32, func, true), overflowLabel);
// bcs $overflow
Lowerer::InsertBranch(Js::OpCode::BrLt_A, true, overflowLabel, overflowLabel);
// mov dst, temp
Lowerer::InsertMove(dst, tempOpnd, overflowLabel);
const bool dstEqualsSrc = dst->IsEqual(src);
if(!dstEqualsSrc || onOverflowInsertBeforeInstrRef)
{
// b $continue
// $overflow:
// mov dst, 0
// $continue:
IR::LabelInstr *const continueLabel = Lowerer::InsertLabel(false, insertBeforeInstr);
Lowerer::InsertBranch(Js::OpCode::Br, continueLabel, overflowLabel);
if(!dstEqualsSrc)
{
Lowerer::InsertMove(dst, IR::IntConstOpnd::New(0, TyUint32, func, true), continueLabel);
}
if(onOverflowInsertBeforeInstrRef)
{
*onOverflowInsertBeforeInstrRef = continueLabel;
}
}
else
{
// $overflow:
}
}
void Lowerer::InsertFloatCheckForZeroOrNanBranch(
IR::Opnd *const src,
const bool branchOnZeroOrNan,
IR::LabelInstr *const target,
IR::LabelInstr *const fallthroughLabel,
IR::Instr *const insertBeforeInstr)
{
Assert(src);
Assert(src->IsFloat64());
Assert(target);
Assert(!fallthroughLabel || fallthroughLabel != target);
Assert(insertBeforeInstr);
Func *const func = insertBeforeInstr->m_func;
IR::BranchInstr *const branchOnEqualOrNotEqual =
InsertCompareBranch(
src,
IR::MemRefOpnd::New(func->GetThreadContextInfo()->GetDoubleZeroAddr(), TyFloat64, func),
branchOnZeroOrNan ? Js::OpCode::BrEq_A : Js::OpCode::BrNeq_A,
target,
insertBeforeInstr,
true /* ignoreNaN */);
// x86/x64
// When NaN is ignored, on x86 and x64, JE branches when equal or unordered since an unordered result sets the zero
// flag, and JNE branches when not equal and not unordered. By comparing with zero, JE will branch when src is zero or
// NaN, and JNE will branch when src is not zero and not NaN.
//
// ARM
// When NaN is ignored, BEQ branches when equal and not unordered, and BNE branches when not equal or unordered. So,
// when comparing src with zero, an unordered check needs to be added before the BEQ/BNE.
branchOnEqualOrNotEqual; // satisfy the compiler
#ifdef _M_ARM32_OR_ARM64
InsertBranch(
Js::OpCode::BVS,
branchOnZeroOrNan
? target
: fallthroughLabel ? fallthroughLabel : insertBeforeInstr->m_prev->GetOrCreateContinueLabel(),
branchOnEqualOrNotEqual);
#endif
}
IR::IndirOpnd*
Lowerer::GenerateFastElemICommon(
_In_ IR::Instr* elemInstr,
_In_ bool isStore,
_In_ IR::IndirOpnd* indirOpnd,
_In_ IR::LabelInstr* labelHelper,
_In_ IR::LabelInstr* labelCantUseArray,
_In_opt_ IR::LabelInstr* labelFallthrough,
_Out_ bool* pIsTypedArrayElement,
_Out_ bool* pIsStringIndex,
_Out_opt_ bool* emitBailoutRef,
_Outptr_opt_result_maybenull_ IR::Opnd** maskOpnd,
_Outptr_opt_result_maybenull_ IR::LabelInstr** pLabelSegmentLengthIncreased, // = nullptr
_In_ bool checkArrayLengthOverflow, // = true
_In_ bool forceGenerateFastPath, // = false
_In_ bool returnLength, // = false
_In_opt_ IR::LabelInstr* bailOutLabelInstr, // = nullptr
_Out_opt_ bool* indirOpndOverflowed, // = nullptr
_In_ Js::FldInfoFlags flags) // = Js::FldInfo_NoInfo
{
*pIsTypedArrayElement = false;
*pIsStringIndex = false;
if(pLabelSegmentLengthIncreased)
{
*pLabelSegmentLengthIncreased = nullptr;
}
if (maskOpnd)
{
*maskOpnd = nullptr;
}
if (indirOpndOverflowed)
{
*indirOpndOverflowed = false;
}
if (emitBailoutRef)
{
*emitBailoutRef = false;
}
IR::RegOpnd *baseOpnd = indirOpnd->GetBaseOpnd();
AssertMsg(baseOpnd, "This shouldn't be NULL");
// Caution: If making changes to the conditions under which we don't emit the typical array checks, make sure
// the code in GlobOpt::ShouldAssumeIndirOpndHasNonNegativeIntIndex is updated accordingly. We don't want the
// global optimizer to type specialize instructions, for which the lowerer is forced to emit unconditional
// bailouts.
if (baseOpnd->IsTaggedInt())
{
return NULL;
}
IR::RegOpnd *indexOpnd = indirOpnd->GetIndexOpnd();
if (indexOpnd)
{
const bool normalLocation = (flags & (Js::FldInfo_FromLocal | Js::FldInfo_FromProto | Js::FldInfo_FromLocalWithoutProperty)) != 0;
const bool normalSlots = (flags & (Js::FldInfo_FromAuxSlots | Js::FldInfo_FromInlineSlots)) != 0;
const bool generateFastpath = !baseOpnd->GetValueType().IsLikelyOptimizedTypedArray() && normalLocation && normalSlots && flags != Js::FldInfo_NoInfo;
if (indexOpnd->GetValueType().IsLikelyString())
{
if (generateFastpath)
{
// If profile data says that it's a typed array - do not generate the property string fast path as the src. could be a temp and that would cause a bug.
*pIsTypedArrayElement = false;
*pIsStringIndex = true;
return GenerateFastElemIStringIndexCommon(elemInstr, isStore, indirOpnd, labelHelper, flags);
}
else
{
// There's no point in generating the int index fast path if we know the index has a string value.
return nullptr;
}
}
else if (indexOpnd->GetValueType().IsLikelySymbol())
{
if (generateFastpath)
{
// If profile data says that it's a typed array - do not generate the symbol fast path as the src. could be a temp and that would cause a bug.
return GenerateFastElemISymbolIndexCommon(elemInstr, isStore, indirOpnd, labelHelper, flags);
}
else
{
// There's no point in generating the int index fast path if we know the index has a symbol value.
return nullptr;
}
}
}
return
GenerateFastElemIIntIndexCommon(
elemInstr,
isStore,
indirOpnd,
labelHelper,
labelCantUseArray,
labelFallthrough,
pIsTypedArrayElement,
emitBailoutRef,
pLabelSegmentLengthIncreased,
checkArrayLengthOverflow,
maskOpnd,
false,
returnLength,
bailOutLabelInstr,
indirOpndOverflowed);
}
void
Lowerer::GenerateDynamicLoadPolymorphicInlineCacheSlot(IR::Instr * instrInsert, IR::RegOpnd * inlineCacheOpnd, IR::Opnd * objectTypeOpnd)
{
// Generates:
// MOV opndOffset, objectTypeOpnd
// SHR opndOffset, PolymorphicInlineCacheShift
// MOVZX cacheIndexOpnd, inlineCacheOpnd->size
// DEC cacheIndexOpnd
// AND opndOffset, cacheIndexOpnd
// SHL opndOffset, Math::Log2(sizeof(Js::InlineCache))
// MOV inlineCacheOpnd, inlineCacheOpnd->inlineCaches
// LEA inlineCacheOpnd, [inlineCacheOpnd + opndOffset]
IntConstType rightShiftAmount = PolymorphicInlineCacheShift;
IntConstType leftShiftAmount = Math::Log2(sizeof(Js::InlineCache));
Assert(rightShiftAmount > leftShiftAmount);
IR::RegOpnd * opndOffset = IR::RegOpnd::New(TyMachPtr, m_func);
InsertShift(Js::OpCode::ShrU_A, false, opndOffset, objectTypeOpnd, IR::IntConstOpnd::New(rightShiftAmount, TyUint8, m_func, true), instrInsert);
IR::RegOpnd * cacheIndexOpnd = IR::RegOpnd::New(TyMachPtr, m_func);
InsertMove(cacheIndexOpnd, IR::IndirOpnd::New(inlineCacheOpnd, Js::PolymorphicInlineCache::GetOffsetOfSize(), TyUint16, m_func), instrInsert);
InsertSub(false, cacheIndexOpnd, cacheIndexOpnd, IR::IntConstOpnd::New(1, TyMachPtr, m_func), instrInsert);
InsertAnd(opndOffset, opndOffset, cacheIndexOpnd, instrInsert);
InsertShift(Js::OpCode::Shl_A, false, opndOffset, opndOffset, IR::IntConstOpnd::New(leftShiftAmount, TyUint8, m_func), instrInsert);
InsertMove(inlineCacheOpnd, IR::IndirOpnd::New(inlineCacheOpnd, Js::PolymorphicInlineCache::GetOffsetOfInlineCaches(), TyMachPtr, m_func), instrInsert);
InsertLea(inlineCacheOpnd, IR::IndirOpnd::New(inlineCacheOpnd, opndOffset, TyMachPtr, m_func), instrInsert);
}
// Test that the operand is a PropertyString, or bail to helper
void
Lowerer::GeneratePropertyStringTest(IR::RegOpnd *srcReg, IR::Instr *instrInsert, IR::LabelInstr *labelHelper, bool isStore)
{
// Generates:
// StringTest(srcReg, $helper) ; verify index is string type
// CMP srcReg, PropertyString::`vtable' ; verify index is property string
// JNE $helper
GenerateStringTest(srcReg, instrInsert, labelHelper);
IR::LabelInstr * notPropStrLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func, true);
IR::LabelInstr * propStrLoadedLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func);
IR::BranchInstr *branchInstr = InsertCompareBranch(
IR::IndirOpnd::New(srcReg, 0, TyMachPtr, m_func),
LoadVTableValueOpnd(instrInsert, VTableValue::VtablePropertyString),
Js::OpCode::BrNeq_A, notPropStrLabel, instrInsert);
InsertObjectPoison(srcReg, branchInstr, instrInsert, isStore);
InsertBranch(Js::OpCode::Br, propStrLoadedLabel, instrInsert);
InsertBranch(Js::OpCode::Br, propStrLoadedLabel, instrInsert);
instrInsert->InsertBefore(notPropStrLabel);
branchInstr = InsertCompareBranch(
IR::IndirOpnd::New(srcReg, 0, TyMachPtr, m_func),
LoadVTableValueOpnd(instrInsert, VTableValue::VtableLiteralStringWithPropertyStringPtr),
Js::OpCode::BrNeq_A, labelHelper, instrInsert);
InsertObjectPoison(srcReg, branchInstr, instrInsert, isStore);
IR::IndirOpnd * propStrOpnd = IR::IndirOpnd::New(srcReg, Js::LiteralStringWithPropertyStringPtr::GetOffsetOfPropertyString(), TyMachPtr, m_func);
InsertCompareBranch(propStrOpnd, IR::IntConstOpnd::New(NULL, TyMachPtr, m_func), Js::OpCode::BrNeq_A, labelHelper, instrInsert);
// We don't really own srcReg, but it is fine to update it to be the PropertyString, since that is better to have anyway
InsertMove(srcReg, propStrOpnd, instrInsert);
instrInsert->InsertBefore(propStrLoadedLabel);
}
IR::IndirOpnd*
Lowerer::GenerateFastElemIStringIndexCommon(
_In_ IR::Instr* elemInstr,
_In_ bool isStore,
_In_ IR::IndirOpnd* indirOpnd,
_In_ IR::LabelInstr* labelHelper,
_In_ Js::FldInfoFlags flags)
{
IR::RegOpnd *indexOpnd = indirOpnd->GetIndexOpnd();
IR::RegOpnd *baseOpnd = indirOpnd->GetBaseOpnd();
Assert(baseOpnd != nullptr);
Assert(indexOpnd->GetValueType().IsLikelyString());
// Generates:
// PropertyStringTest(indexOpnd, $helper) ; verify index is string type
// FastElemISymbolOrStringIndexCommon(indexOpnd, baseOpnd, $helper) ; shared code with JavascriptSymbol
GeneratePropertyStringTest(indexOpnd, elemInstr, labelHelper, isStore);
const uint32 inlineCacheOffset = isStore ? Js::PropertyString::GetOffsetOfStElemInlineCache() : Js::PropertyString::GetOffsetOfLdElemInlineCache();
const uint32 hitRateOffset = Js::PropertyString::GetOffsetOfHitRate();
return GenerateFastElemISymbolOrStringIndexCommon(elemInstr, indexOpnd, baseOpnd, inlineCacheOffset, hitRateOffset, labelHelper, flags);
}
IR::IndirOpnd*
Lowerer::GenerateFastElemISymbolIndexCommon(
_In_ IR::Instr* elemInstr,
_In_ bool isStore,
_In_ IR::IndirOpnd* indirOpnd,
_In_ IR::LabelInstr* labelHelper,
_In_ Js::FldInfoFlags flags)
{
IR::RegOpnd *indexOpnd = indirOpnd->GetIndexOpnd();
IR::RegOpnd *baseOpnd = indirOpnd->GetBaseOpnd();
Assert(baseOpnd != nullptr);
Assert(indexOpnd->GetValueType().IsLikelySymbol());
// Generates:
// SymbolTest(indexOpnd, $helper) ; verify index is symbol type
// FastElemISymbolOrStringIndexCommon(indexOpnd, baseOpnd, $helper) ; shared code with PropertyString
GenerateSymbolTest(indexOpnd, elemInstr, labelHelper);
const uint32 inlineCacheOffset = isStore ? Js::JavascriptSymbol::GetOffsetOfStElemInlineCache() : Js::JavascriptSymbol::GetOffsetOfLdElemInlineCache();
const uint32 hitRateOffset = Js::JavascriptSymbol::GetOffsetOfHitRate();
return GenerateFastElemISymbolOrStringIndexCommon(elemInstr, indexOpnd, baseOpnd, inlineCacheOffset, hitRateOffset, labelHelper, flags);
}
void
Lowerer::GenerateFastIsInSymbolOrStringIndex(IR::Instr * instrInsert, IR::RegOpnd *indexOpnd, IR::RegOpnd *baseOpnd, IR::Opnd *dest, uint32 inlineCacheOffset, const uint32 hitRateOffset, IR::LabelInstr * labelHelper, IR::LabelInstr * labelDone)
{
// Try to look up the property in the cache, or bail to helper
GenerateLookUpInIndexCache(instrInsert, indexOpnd, baseOpnd, nullptr /*opndSlotArray*/, nullptr /*opndSlotIndex*/, inlineCacheOffset, hitRateOffset, labelHelper);
// MOV dest, true
InsertMove(dest, LoadLibraryValueOpnd(instrInsert, LibraryValue::ValueTrue), instrInsert);
// JMP labelDone
InsertBranch(Js::OpCode::Br, labelDone, instrInsert);
}
IR::IndirOpnd*
Lowerer::GenerateFastElemISymbolOrStringIndexCommon(
_In_ IR::Instr* instrInsert,
_In_ IR::RegOpnd* indexOpnd,
_In_ IR::RegOpnd* baseOpnd,
_In_ const uint32 inlineCacheOffset,
_In_ const uint32 hitRateOffset,
_In_ IR::LabelInstr* labelHelper,
_In_ Js::FldInfoFlags flags)
{
// Try to look up the property in the cache, or bail to helper
IR::RegOpnd * opndSlotArray = IR::RegOpnd::New(TyMachReg, instrInsert->m_func);
IR::RegOpnd * opndSlotIndex = IR::RegOpnd::New(TyMachReg, instrInsert->m_func);
GenerateLookUpInIndexCache(instrInsert, indexOpnd, baseOpnd, opndSlotArray, opndSlotIndex, inlineCacheOffset, hitRateOffset, labelHelper, flags);
// return [opndSlotArray + opndSlotIndex * PtrSize]
return IR::IndirOpnd::New(opndSlotArray, opndSlotIndex, m_lowererMD.GetDefaultIndirScale(), TyMachReg, instrInsert->m_func);
}
// Look up a value from the polymorphic inline cache on a PropertyString or Symbol. Offsets are relative to indexOpnd.
// Checks local and/or proto caches based on profile data. If the property is not found, jump to the helper.
// opndSlotArray is optional; if provided, it will receive the base address of the slot array that contains the property.
// opndSlotIndex is optional; if provided, it will receive the index of the match within the slot array.
void
Lowerer::GenerateLookUpInIndexCache(
_In_ IR::Instr* instrInsert,
_In_ IR::RegOpnd* indexOpnd,
_In_ IR::RegOpnd* baseOpnd,
_In_opt_ IR::RegOpnd* opndSlotArray,
_In_opt_ IR::RegOpnd* opndSlotIndex,
_In_ const uint32 inlineCacheOffset,
_In_ const uint32 hitRateOffset,
_In_ IR::LabelInstr* labelHelper,
_In_ Js::FldInfoFlags flags) // = Js::FldInfo_NoInfo
{
// Generates:
// MOV inlineCacheOpnd, index->inlineCache
// GenerateObjectTest(baseOpnd, $helper) ; verify base is an object
// MOV objectTypeOpnd, baseOpnd->type
// GenerateDynamicLoadPolymorphicInlineCacheSlot(inlineCacheOpnd, objectTypeOpnd) ; loads inline cache for given type
// if (checkLocalInlineSlots)
// GenerateLookUpInIndexCacheHelper<CheckLocal, CheckInlineSlot> // checks local inline slots, goes to next on failure
// if (checkLocalAuxSlots)
// GenerateLookUpInIndexCacheHelper<CheckLocal, CheckAuxSlot> // checks local aux slots, goes to next on failure
// if (fromProto && fromInlineSlots)
// GenerateLookUpInIndexCacheHelper<CheckProto, CheckInlineSlot> // checks proto inline slots, goes to next on failure
// if (fromProto && fromAuxSlots)
// GenerateLookUpInIndexCacheHelper<CheckProto, CheckAuxSlot> // checks proto aux slots, goes to next on failure
// if (doAdd && fromInlineSlots)
// GenerateLookUpInIndexCacheHelper<CheckLocal, CheckInlineSlot, DoAdd> // checks typeWithoutProperty inline slots, goes to next on failure
// if (doAdd && fromAuxSlots)
// GenerateLookUpInIndexCacheHelper<CheckLocal, CheckAuxSlot, DoAdd> // checks typeWithoutProperty aux slots, goes to helper on failure
// $slotIndexLoadedLabel
// INC indexOpnd->hitRate
const bool fromInlineSlots = (flags & Js::FldInfo_FromInlineSlots) == Js::FldInfo_FromInlineSlots;
const bool fromAuxSlots = (flags & Js::FldInfo_FromAuxSlots) == Js::FldInfo_FromAuxSlots;
const bool fromLocal = (flags & Js::FldInfo_FromLocal) == Js::FldInfo_FromLocal;
const bool fromProto = (flags & Js::FldInfo_FromProto) == Js::FldInfo_FromProto;
const bool doAdd = (flags & Js::FldInfo_FromLocalWithoutProperty) == Js::FldInfo_FromLocalWithoutProperty;
const bool checkLocalInlineSlots = flags == Js::FldInfo_NoInfo || (fromInlineSlots && fromLocal);
const bool checkLocalAuxSlots = flags == Js::FldInfo_NoInfo || (fromAuxSlots && fromLocal);
m_lowererMD.GenerateObjectTest(baseOpnd, instrInsert, labelHelper);
IR::RegOpnd * objectTypeOpnd = IR::RegOpnd::New(TyMachPtr, m_func);
InsertMove(objectTypeOpnd, IR::IndirOpnd::New(baseOpnd, Js::RecyclableObject::GetOffsetOfType(), TyMachPtr, m_func), instrInsert);
IR::RegOpnd * inlineCacheOpnd = IR::RegOpnd::New(TyMachPtr, m_func);
InsertMove(inlineCacheOpnd, IR::IndirOpnd::New(indexOpnd, inlineCacheOffset, TyMachPtr, m_func), instrInsert);
GenerateDynamicLoadPolymorphicInlineCacheSlot(instrInsert, inlineCacheOpnd, objectTypeOpnd);
IR::LabelInstr* slotIndexLoadedLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func);
IR::BranchInstr* branchToPatch = nullptr;
IR::LabelInstr* nextLabel = nullptr;
IR::RegOpnd* taggedTypeOpnd = nullptr;
if (checkLocalInlineSlots)
{
GenerateLookUpInIndexCacheHelper<true /* CheckLocal */, true /* CheckInlineSlot */, false /* DoAdd */>(
instrInsert,
baseOpnd,
opndSlotArray,
opndSlotIndex,
objectTypeOpnd,
inlineCacheOpnd,
slotIndexLoadedLabel,
labelHelper,
&nextLabel,
&branchToPatch,
&taggedTypeOpnd);
}
if (checkLocalAuxSlots)
{
GenerateLookUpInIndexCacheHelper<true /* CheckLocal */, false /* CheckInlineSlot */, false /* DoAdd */>(
instrInsert,
baseOpnd,
opndSlotArray,
opndSlotIndex,
objectTypeOpnd,
inlineCacheOpnd,
slotIndexLoadedLabel,
labelHelper,
&nextLabel,
&branchToPatch,
&taggedTypeOpnd);
}
if (fromProto)
{
if (fromInlineSlots)
{
GenerateLookUpInIndexCacheHelper<false /* CheckLocal */, true /* CheckInlineSlot */, false /* DoAdd */>(
instrInsert,
baseOpnd,
opndSlotArray,
opndSlotIndex,
objectTypeOpnd,
inlineCacheOpnd,
slotIndexLoadedLabel,
labelHelper,
&nextLabel,
&branchToPatch,
&taggedTypeOpnd);
}
if (fromAuxSlots)
{
GenerateLookUpInIndexCacheHelper<false /* CheckLocal */, false /* CheckInlineSlot */, false /* DoAdd */>(
instrInsert,
baseOpnd,
opndSlotArray,
opndSlotIndex,
objectTypeOpnd,
inlineCacheOpnd,
slotIndexLoadedLabel,
labelHelper,
&nextLabel,
&branchToPatch,
&taggedTypeOpnd);
}
}
if (doAdd)
{
Assert(opndSlotArray);
if (fromInlineSlots)
{
GenerateLookUpInIndexCacheHelper<true /* CheckLocal */, true /* CheckInlineSlot */, true /* DoAdd */>(
instrInsert,
baseOpnd,
opndSlotArray,
opndSlotIndex,
objectTypeOpnd,
inlineCacheOpnd,
slotIndexLoadedLabel,
labelHelper,
&nextLabel,
&branchToPatch,
&taggedTypeOpnd);
}
if (fromAuxSlots)
{
GenerateLookUpInIndexCacheHelper<true /* CheckLocal */, false /* CheckInlineSlot */, true /* DoAdd */>(
instrInsert,
baseOpnd,
opndSlotArray,
opndSlotIndex,
objectTypeOpnd,
inlineCacheOpnd,
slotIndexLoadedLabel,
labelHelper,
&nextLabel,
&branchToPatch,
&taggedTypeOpnd);
}
}
Assert(branchToPatch);
Assert(nextLabel);
Assert(nextLabel->labelRefs.Count() == 1 && nextLabel->labelRefs.Head() == branchToPatch);
branchToPatch->SetTarget(labelHelper);
nextLabel->Remove();
instrInsert->InsertBefore(slotIndexLoadedLabel);
IR::IndirOpnd * hitRateOpnd = IR::IndirOpnd::New(indexOpnd, hitRateOffset, TyInt32, m_func);
IR::IntConstOpnd * incOpnd = IR::IntConstOpnd::New(1, TyInt32, m_func);
// overflow check: not needed here, we don't allocate anything with hitrate
InsertAdd(false, hitRateOpnd, hitRateOpnd, incOpnd, instrInsert);
}
template <bool CheckLocal, bool CheckInlineSlot, bool DoAdd>
void
Lowerer::GenerateLookUpInIndexCacheHelper(
_In_ IR::Instr* insertInstr,
_In_ IR::RegOpnd* baseOpnd,
_In_opt_ IR::RegOpnd* opndSlotArray,
_In_opt_ IR::RegOpnd* opndSlotIndex,
_In_ IR::RegOpnd* objectTypeOpnd,
_In_ IR::RegOpnd* inlineCacheOpnd,
_In_ IR::LabelInstr* doneLabel,
_In_ IR::LabelInstr* helperLabel,
_Outptr_ IR::LabelInstr** nextLabel,
_Outptr_ IR::BranchInstr** branchToPatch,
_Inout_ IR::RegOpnd** taggedTypeOpnd)
{
CompileAssert(!DoAdd || CheckLocal);
AnalysisAssert(!opndSlotArray || opndSlotIndex);
*nextLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func);
IR::RegOpnd* typeOpnd = nullptr;
if (CheckInlineSlot)
{
typeOpnd = objectTypeOpnd;
}
else
{
if (*taggedTypeOpnd == nullptr)
{
*taggedTypeOpnd = IR::RegOpnd::New(TyMachReg, m_func);
m_lowererMD.GenerateLoadTaggedType(insertInstr, objectTypeOpnd, *taggedTypeOpnd);
}
typeOpnd = *taggedTypeOpnd;
}
IR::RegOpnd* objectOpnd = nullptr;
if (CheckLocal)
{
*branchToPatch = GenerateLocalInlineCacheCheck(insertInstr, typeOpnd, inlineCacheOpnd, *nextLabel, DoAdd);
if (DoAdd)
{
if (!CheckInlineSlot)
{
GenerateAuxSlotAdjustmentRequiredCheck(insertInstr, inlineCacheOpnd, helperLabel);
}
GenerateSetObjectTypeFromInlineCache(insertInstr, baseOpnd, inlineCacheOpnd, !CheckInlineSlot);
}
objectOpnd = baseOpnd;
}
else
{
*branchToPatch = GenerateProtoInlineCacheCheck(insertInstr, typeOpnd, inlineCacheOpnd, *nextLabel);
IR::RegOpnd* protoOpnd = IR::RegOpnd::New(TyMachReg, m_func);
int32 protoObjOffset = (int32)offsetof(Js::InlineCache, u.proto.prototypeObject);
IR::IndirOpnd* protoIndir = IR::IndirOpnd::New(inlineCacheOpnd, protoObjOffset, TyMachReg, m_func);
InsertMove(protoOpnd, protoIndir, insertInstr);
objectOpnd = protoOpnd;
}
if (opndSlotArray)
{
if (CheckInlineSlot)
{
InsertMove(opndSlotArray, objectOpnd, insertInstr);
}
else
{
IR::IndirOpnd* auxIndir = IR::IndirOpnd::New(objectOpnd, Js::DynamicObject::GetOffsetOfAuxSlots(), TyMachReg, m_func);
InsertMove(opndSlotArray, auxIndir, insertInstr);
}
size_t slotIndexOffset = CheckLocal ? offsetof(Js::InlineCache, u.local.slotIndex) : offsetof(Js::InlineCache, u.proto.slotIndex);
IR::IndirOpnd* slotOffsetIndir = IR::IndirOpnd::New(inlineCacheOpnd, (int32)slotIndexOffset, TyUint16, m_func);
// overflow check: not needed here, we don't allocate anything with hitrate
InsertMove(opndSlotIndex, slotOffsetIndir, insertInstr);
}
InsertBranch(Js::OpCode::Br, doneLabel, insertInstr);
insertInstr->InsertBefore(*nextLabel);
}
IR::IndirOpnd *
Lowerer::GenerateFastElemIIntIndexCommon(
IR::Instr * instr,
bool isStore,
IR::IndirOpnd * indirOpnd,
IR::LabelInstr * labelHelper,
IR::LabelInstr * labelCantUseArray,
IR::LabelInstr *labelFallthrough,
bool * pIsTypedArrayElement,
bool *emitBailoutRef,
IR::LabelInstr **pLabelSegmentLengthIncreased,
bool checkArrayLengthOverflow /*= true*/,
IR::Opnd** maskOpnd,
bool forceGenerateFastPath /* = false */,
bool returnLength,
IR::LabelInstr *bailOutLabelInstr /* = nullptr*/,
bool * indirOpndOverflowed /* = nullptr */)
{
IR::RegOpnd *indexOpnd = indirOpnd->GetIndexOpnd();
IR::RegOpnd *baseOpnd = indirOpnd->GetBaseOpnd();
Assert(!baseOpnd->IsTaggedInt() || (indexOpnd && indexOpnd->IsNotInt()));
if (indirOpndOverflowed != nullptr)
{
*indirOpndOverflowed = false;
}
BYTE indirScale = this->m_lowererMD.GetDefaultIndirScale();
IRType indirType = TyVar;
const ValueType baseValueType(baseOpnd->GetValueType());
// TEST base, AtomTag -- check base not tagged int
// JNE $helper
// if (base.GetValueType() != Array) {
// CMP [base], JavascriptArray::`vtable'
// JNE $helper
// }
// TEST index, 1 -- index tagged int
// JEQ $helper
// if (inputIndex is not int const) {
// MOV index, inputIndex
// SAR index, Js::VarTag_Shift -- remote atom tag
// JS $helper -- exclude negative index
// }
// MOV headSegment, [base + offset(head)]
// CMP [headSegment + offset(length)], index -- bounds check
// if (opcode == StElemI_A) {
// JA $done (for typedarray, JA $toNumberHelper)
// CMP [headSegment + offset(size)], index -- chunk has room?
// JBE $helper
// if (index is not int const) {
// LEA newLength, [index + 1]
// } else {
// newLength = index + 1
// }
// if(BailOutOnInvalidatedArrayLength) {
// CMP [base + offset(length)], newlength
// JB $helper
// }
// MOV [headSegment + offset(length)], newLength -- update length on chunk
// CMP [base + offset(length)], newLength
// JAE $done
// MOV [base + offset(length)], newLength -- update length on array
// if(length to be returned){
// SHL newLength, AtomTag
// INC newLength
// MOV dst, newLength
// }
// JMP $done
//
// $toNumberHelper: Call HelperOp_ConvNumber_Full
// JMP $done
// $done
// } else {la
// JBE $helper
// }
// return [headSegment + offset(elements) + index]
// Caution: If making changes to the conditions under which we don't emit the typical array checks, make sure
// the code in GlobOpt::ShouldAssumeIndirOpndHasNonNegativeIntIndex is updated accordingly. We don't want the
// global optimizer to type specialize instructions, for which the lowerer is forced to emit unconditional
// bailouts.
bool isIndexNotInt = false;
IntConstType value = 0;
IR::Opnd * indexValueOpnd = nullptr;
bool invertBoundCheckComparison = false;
bool checkIndexConstOverflowed = false;
if (indirOpnd->TryGetIntConstIndexValue(true, &value, &isIndexNotInt))
{
if (value >= 0)
{
indexValueOpnd = IR::IntConstOpnd::New(value, TyUint32, this->m_func);
invertBoundCheckComparison = true; // facilitate folding the constant index into the compare instruction
checkIndexConstOverflowed = true;
}
else
{
// If the index is a negative int constant we go directly to helper.
Assert(!forceGenerateFastPath);
return nullptr;
}
}
else if (isIndexNotInt)
{
// If we know the index is not an int we go directly to helper.
Assert(!forceGenerateFastPath);
return nullptr;
}
//At this point indexValueOpnd is either NULL or contains the valueOpnd
if(!forceGenerateFastPath && !ShouldGenerateArrayFastPath(baseOpnd, true, true, true))
{
return nullptr;
}
if(baseValueType.IsLikelyAnyOptimizedArray())
{
indirScale = GetArrayIndirScale(baseValueType);
indirType = GetArrayIndirType(baseValueType);
}
if (checkIndexConstOverflowed && (static_cast<uint64>(value) << indirScale) > INT32_MAX &&
indirOpndOverflowed != nullptr)
{
*indirOpndOverflowed = true;
return nullptr;
}
IRType elementType = TyIllegal;
IR::Opnd * element = nullptr;
if(instr->m_opcode == Js::OpCode::InlineArrayPush)
{
element = instr->GetSrc2();
elementType = element->GetType();
}
else if(isStore && instr->GetSrc1())
{
element = instr->GetSrc1();
elementType = element->GetType();
}
Assert(isStore || (element == nullptr && elementType == TyIllegal));
if (isStore && baseValueType.IsLikelyNativeArray() && indirType != elementType)
{
// We're trying to write a value of the wrong type, which should force a conversion of the array.
// Go to the helper for that.
return nullptr;
}
IR::RegOpnd *arrayOpnd = baseOpnd;
IR::RegOpnd *headSegmentOpnd = nullptr;
IR::Opnd *headSegmentLengthOpnd = nullptr;
IR::AutoReuseOpnd autoReuseHeadSegmentOpnd, autoReuseHeadSegmentLengthOpnd;
bool indexIsNonnegative = indexValueOpnd || indexOpnd->GetType() == TyUint32 || !checkArrayLengthOverflow;
bool indexIsLessThanHeadSegmentLength = false;
if(!baseValueType.IsAnyOptimizedArray())
{
arrayOpnd = GenerateArrayTest(baseOpnd, labelCantUseArray, labelCantUseArray, instr, true, isStore);
}
else
{
if(arrayOpnd->IsArrayRegOpnd())
{
IR::ArrayRegOpnd *const arrayRegOpnd = arrayOpnd->AsArrayRegOpnd();
if(arrayRegOpnd->HeadSegmentSym())
{
headSegmentOpnd = IR::RegOpnd::New(arrayRegOpnd->HeadSegmentSym(), TyMachPtr, m_func);
DebugOnly(headSegmentOpnd->FreezeSymValue());
autoReuseHeadSegmentOpnd.Initialize(headSegmentOpnd, m_func);
}
if(arrayRegOpnd->HeadSegmentLengthSym())
{
headSegmentLengthOpnd = IR::RegOpnd::New(arrayRegOpnd->HeadSegmentLengthSym(), TyUint32, m_func);
// This value can change over the course of this function
//DebugOnly(headSegmentLengthOpnd->AsRegOpnd()->FreezeSymValue());
autoReuseHeadSegmentLengthOpnd.Initialize(headSegmentLengthOpnd, m_func);
}
if (arrayRegOpnd->EliminatedLowerBoundCheck())
{
indexIsNonnegative = true;
}
if(arrayRegOpnd->EliminatedUpperBoundCheck())
{
indexIsLessThanHeadSegmentLength = true;
}
}
}
IR::AutoReuseOpnd autoReuseArrayOpnd;
if(arrayOpnd->GetValueType().GetObjectType() != ObjectType::ObjectWithArray)
{
autoReuseArrayOpnd.Initialize(arrayOpnd, m_func);
}
const auto EnsureObjectArrayLoaded = [&]()
{
if(arrayOpnd->GetValueType().GetObjectType() != ObjectType::ObjectWithArray)
{
return;
}
arrayOpnd = LoadObjectArray(arrayOpnd, instr);
autoReuseArrayOpnd.Initialize(arrayOpnd, m_func);
};
const bool doUpperBoundCheck = checkArrayLengthOverflow && !indexIsLessThanHeadSegmentLength;
if(!indexValueOpnd)
{
indexValueOpnd =
m_lowererMD.LoadNonnegativeIndex(
indexOpnd,
(
indexIsNonnegative
#if !INT32VAR
||
// On 32-bit platforms, skip the negative check since for now, the unsigned upper bound check covers it
doUpperBoundCheck
#endif
),
labelCantUseArray,
labelHelper,
instr);
}
const IR::AutoReuseOpnd autoReuseIndexValueOpnd(indexValueOpnd, m_func);
if (baseValueType.IsLikelyTypedArray())
{
*pIsTypedArrayElement = true;
if(doUpperBoundCheck)
{
if(!headSegmentLengthOpnd)
{
// (headSegmentLength = [base + offset(length)])
int lengthOffset;
lengthOffset = Js::Float64Array::GetOffsetOfLength();
headSegmentLengthOpnd = IR::IndirOpnd::New(arrayOpnd, lengthOffset, TyUint32, m_func);
autoReuseHeadSegmentLengthOpnd.Initialize(headSegmentLengthOpnd, m_func);
}
// CMP index, headSegmentLength -- upper bound check
if(!invertBoundCheckComparison)
{
InsertCompare(indexValueOpnd, headSegmentLengthOpnd, instr);
}
else
{
InsertCompare(headSegmentLengthOpnd, indexValueOpnd, instr);
}
}
}
else
{
*pIsTypedArrayElement = false;
if (isStore &&
baseValueType.IsLikelyNativeIntArray() &&
(!element->IsIntConstOpnd() || Js::SparseArraySegment<int32>::GetMissingItem() == element->AsIntConstOpnd()->AsInt32()))
{
Assert(instr->m_opcode != Js::OpCode::InlineArrayPush || bailOutLabelInstr);
// Check for a write of the MissingItem value.
InsertMissingItemCompareBranch(
element,
Js::OpCode::BrEq_A,
instr->m_opcode == Js::OpCode::InlineArrayPush ? bailOutLabelInstr : labelCantUseArray,
instr);
}
if(!headSegmentOpnd)
{
EnsureObjectArrayLoaded();
// MOV headSegment, [base + offset(head)]
indirOpnd = IR::IndirOpnd::New(arrayOpnd, Js::JavascriptArray::GetOffsetOfHead(), TyMachPtr, this->m_func);
headSegmentOpnd = IR::RegOpnd::New(TyMachPtr, this->m_func);
autoReuseHeadSegmentOpnd.Initialize(headSegmentOpnd, m_func);
InsertMove(headSegmentOpnd, indirOpnd, instr);
}
if(doUpperBoundCheck)
{
if(!headSegmentLengthOpnd)
{
// (headSegmentLength = [headSegment + offset(length)])
headSegmentLengthOpnd =
IR::IndirOpnd::New(headSegmentOpnd, Js::SparseArraySegmentBase::GetOffsetOfLength(), TyUint32, m_func);
autoReuseHeadSegmentLengthOpnd.Initialize(headSegmentLengthOpnd, m_func);
}
// CMP index, headSegmentLength -- upper bound check
if(!invertBoundCheckComparison)
{
InsertCompare(indexValueOpnd, headSegmentLengthOpnd, instr);
}
else
{
InsertCompare(headSegmentLengthOpnd, indexValueOpnd, instr);
}
}
}
const IR::BailOutKind bailOutKind = instr->HasBailOutInfo() ? instr->GetBailOutKind() : IR::BailOutInvalid;
const bool needBailOutOnInvalidLength = !!(bailOutKind & (IR::BailOutOnInvalidatedArrayHeadSegment));
const bool needBailOutToHelper = !!(bailOutKind & (IR::BailOutOnArrayAccessHelperCall));
const bool needBailOutOnSegmentLengthCompare = needBailOutToHelper || needBailOutOnInvalidLength;
bool usingSegmentLengthIncreasedLabel = false;
if(indexIsLessThanHeadSegmentLength || needBailOutOnSegmentLengthCompare)
{
if (needBailOutOnSegmentLengthCompare)
{
// The bailout must be pre-op because it will not have completed the operation
Assert(instr->GetBailOutInfo()->bailOutOffset == instr->GetByteCodeOffset());
// TODO: Check this with lazy bailout
// Verify other bailouts these can be combined with
Assert(
!(
bailOutKind &
IR::BailOutKindBits &
~(
IR::LazyBailOut |
IR::BailOutOnArrayAccessHelperCall |
IR::BailOutOnInvalidatedArrayHeadSegment |
IR::BailOutOnInvalidatedArrayLength |
IR::BailOutConventionalNativeArrayAccessOnly |
IR::BailOutOnMissingValue |
(bailOutKind & IR::BailOutOnArrayAccessHelperCall ? IR::BailOutInvalid : IR::BailOutConvertedNativeArray)
)
)
);
if (bailOutKind & IR::BailOutOnArrayAccessHelperCall)
{
// Omit the helper call and generate a bailout instead
Assert(emitBailoutRef);
*emitBailoutRef = true;
}
}
if (indexIsLessThanHeadSegmentLength)
{
Assert(!(bailOutKind & IR::BailOutOnInvalidatedArrayHeadSegment));
}
else
{
IR::LabelInstr *bailOutLabel;
if (needBailOutOnInvalidLength)
{
Assert(isStore);
// Lower a separate (but shared) bailout for this case, and preserve the bailout kind in the instruction if the
// helper call is going to be generated, because the bailout kind needs to be lowered again and differently in the
// helper call path.
//
// Generate:
// (instr)
// jmp $continue
// $bailOut:
// Bail out with IR::BailOutOnInvalidatedArrayHeadSegment
// $continue:
LowerOneBailOutKind(
instr,
IR::BailOutOnInvalidatedArrayHeadSegment,
false,
!(bailOutKind & IR::BailOutOnArrayAccessHelperCall));
bailOutLabel = instr->GetOrCreateContinueLabel(true);
InsertBranch(Js::OpCode::Br, labelFallthrough, bailOutLabel);
}
else
{
Assert(needBailOutToHelper);
bailOutLabel = labelHelper;
}
// Bail out if the index is outside the head segment bounds
// jae $bailOut
Assert(checkArrayLengthOverflow);
InsertBranch(
!invertBoundCheckComparison ? Js::OpCode::BrGe_A : Js::OpCode::BrLe_A,
true /* isUnsigned */,
bailOutLabel,
instr);
}
}
else if (isStore && !baseValueType.IsLikelyTypedArray()) // #if (opcode == StElemI_A)
{
IR::LabelInstr *labelDone = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
LABELNAME(labelDone);
IR::LabelInstr *labelSegmentLengthIncreased = nullptr;
const bool isPush = instr->m_opcode != Js::OpCode::StElemI_A && instr->m_opcode != Js::OpCode::StElemI_A_Strict;
// Put the head segment size check and length updates in a helper block since they're not the common path for StElem.
// For push, that is the common path so keep it in a non-helper block.
const bool isInHelperBlock = !isPush;
if(checkArrayLengthOverflow)
{
if(pLabelSegmentLengthIncreased &&
!(
(baseValueType.IsArrayOrObjectWithArray() && baseValueType.HasNoMissingValues()) ||
((instr->m_opcode == Js::OpCode::StElemI_A || instr->m_opcode == Js::OpCode::StElemI_A_Strict) &&
instr->IsProfiledInstr() && !instr->AsProfiledInstr()->u.stElemInfo->LikelyFillsMissingValue())
))
{
// For arrays that are not guaranteed to have no missing values, before storing to an element where
// (index < length), the element value needs to be checked to see if it's a missing value, and if so, fall back
// to the helper. This is done to keep the missing value tracking precise in arrays. So, create a separate label
// for the case where the length was increased (index >= length), and pass it back to GenerateFastStElemI, which
// will fill in the rest.
labelSegmentLengthIncreased = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, isInHelperBlock);
LABELNAME(labelSegmentLengthIncreased);
*pLabelSegmentLengthIncreased = labelSegmentLengthIncreased;
// Since this is effectively a separate exit point, we need to do the spectre mitigations in this place as well.
usingSegmentLengthIncreasedLabel = true;
}
else
{
labelSegmentLengthIncreased = labelDone;
}
// JB $done
InsertBranch(
!invertBoundCheckComparison ? Js::OpCode::BrLt_A : Js::OpCode::BrGt_A,
true /* isUnsigned */,
labelDone,
instr);
}
if(isInHelperBlock)
{
InsertLabel(true /* isHelper */, instr);
}
EnsureObjectArrayLoaded();
do // while(false);
{
if(checkArrayLengthOverflow)
{
if(instr->HasBailOutInfo() && instr->GetBailOutKind() & IR::BailOutOnMissingValue)
{
// Need to bail out if this store would create a missing value. The store would cause a missing value to be
// created if (index > length && index < size). If (index >= size) we would go to helper anyway, and the bailout
// handling for this is done after the helper call, so just go to helper if (index > length).
//
// jne $helper // branch for (cmp index, headSegmentLength)
InsertBranch(Js::OpCode::BrNeq_A, labelHelper, instr);
}
else
{
// If (index < size) we will not call the helper, so the array flags must be updated to reflect that it no
// longer has no missing values.
//
// jne indexGreaterThanLength // branch for (cmp index, headSegmentLength)
// cmp index, [headSegment + offset(size)]
// jae $helper
// jmp indexLessThanSize
// indexGreaterThanLength:
// cmp index, [headSegment + offset(size)]
// jae $helper
// and [array + offsetOf(objectArrayOrFlags)], ~Js::DynamicObjectFlags::HasNoMissingValues
// indexLessThanSize:
// if(!index->IsConstOpnd()) {
// sub temp, index, [headSegment + offset(size)]
// sar temp, 31
// and index, temp
// }
IR::LabelInstr *const indexGreaterThanLengthLabel = InsertLabel(true /* isHelper */, instr);
LABELNAME(indexGreaterThanLengthLabel);
IR::LabelInstr *const indexLessThanSizeLabel = InsertLabel(isInHelperBlock, instr);
LABELNAME(indexLessThanSizeLabel);
// jne indexGreaterThanLength // branch for (cmp index, headSegmentLength)
InsertBranch(Js::OpCode::BrNeq_A, indexGreaterThanLengthLabel, indexGreaterThanLengthLabel);
// cmp index, [headSegment + offset(size)]
// jae $helper
// jmp indexLessThanSize
// indexGreaterThanLength:
InsertCompareBranch(
indexValueOpnd,
IR::IndirOpnd::New(headSegmentOpnd, offsetof(Js::SparseArraySegmentBase, size), TyUint32, m_func),
Js::OpCode::BrGe_A,
true /* isUnsigned */,
labelHelper,
indexGreaterThanLengthLabel);
InsertBranch(Js::OpCode::Br, indexLessThanSizeLabel, indexGreaterThanLengthLabel);
// indexGreaterThanLength:
// cmp index, [headSegment + offset(size)]
// jae $helper
// and [array + offsetOf(objectArrayOrFlags)], ~Js::DynamicObjectFlags::HasNoMissingValues
// indexLessThanSize:
InsertCompareBranch(
indexValueOpnd,
IR::IndirOpnd::New(headSegmentOpnd, offsetof(Js::SparseArraySegmentBase, size), TyUint32, m_func),
Js::OpCode::BrGe_A,
true /* isUnsigned */,
labelHelper,
indexLessThanSizeLabel);
CompileAssert(
static_cast<Js::DynamicObjectFlags>(static_cast<uint8>(Js::DynamicObjectFlags::HasNoMissingValues)) ==
Js::DynamicObjectFlags::HasNoMissingValues);
InsertAnd(
IR::IndirOpnd::New(arrayOpnd, Js::JavascriptArray::GetOffsetOfArrayFlags(), TyUint8, m_func),
IR::IndirOpnd::New(arrayOpnd, Js::JavascriptArray::GetOffsetOfArrayFlags(), TyUint8, m_func),
IR::IntConstOpnd::New(
static_cast<uint8>(~Js::DynamicObjectFlags::HasNoMissingValues),
TyUint8,
m_func,
true),
indexLessThanSizeLabel);
// In speculative cases, we want to avoid a write to an array setting the length to something huge, which
// would then allow subsequent reads to hit arbitrary memory (in the speculative path). This is done with
// a mask generated from the difference between the index and the size. Since we should have already gone
// to the helper in any case where this would execute, it's a functional no-op.
// indexLessThanSize:
// In speculative cases, we want to avoid a write to an array setting the length to something huge, which
// would then allow subsequent reads to hit arbitrary memory (in the speculative path). This is done with
// a mask generated from the difference between the index and the size. Since we should have already gone
// to the helper in any case where this would execute, it's a functional no-op.
// if(!index->IsConstOpnd()) {
// sub temp, index, [headSegment + offset(size)]
// sar temp, 31
// and index, temp
// }
if (!indexValueOpnd->IsConstOpnd()
&& (baseValueType.IsLikelyTypedArray()
? CONFIG_FLAG_RELEASE(PoisonTypedArrayStore)
: ((indirType == TyVar && CONFIG_FLAG_RELEASE(PoisonVarArrayStore))
|| (IRType_IsNativeInt(indirType) && CONFIG_FLAG_RELEASE(PoisonIntArrayStore))
|| (IRType_IsFloat(indirType) && CONFIG_FLAG_RELEASE(PoisonFloatArrayStore)))
)
)
{
IR::RegOpnd* temp = IR::RegOpnd::New(TyUint32, m_func);
InsertSub(
false,
temp,
indexValueOpnd,
IR::IndirOpnd::New(headSegmentOpnd, offsetof(Js::SparseArraySegmentBase, size), TyUint32, m_func),
instr);
InsertShift(Js::OpCode::Shr_A, false, temp, temp, IR::IntConstOpnd::New(31, TyInt8, m_func), instr);
InsertAnd(indexValueOpnd, indexValueOpnd, temp, instr);
}
break;
}
}
// CMP index, [headSegment + offset(size)]
// JAE $helper
indirOpnd = IR::IndirOpnd::New(headSegmentOpnd, offsetof(Js::SparseArraySegmentBase, size), TyUint32, this->m_func);
InsertCompareBranch(indexValueOpnd, indirOpnd, Js::OpCode::BrGe_A, true /* isUnsigned */, labelHelper, instr);
} while(false);
if(isPush)
{
IR::LabelInstr *const updateLengthLabel = InsertLabel(isInHelperBlock, instr);
LABELNAME(updateLengthLabel);
if(!doUpperBoundCheck && !headSegmentLengthOpnd)
{
// (headSegmentLength = [headSegment + offset(length)])
headSegmentLengthOpnd =
IR::IndirOpnd::New(headSegmentOpnd, Js::SparseArraySegmentBase::GetOffsetOfLength(), TyUint32, m_func);
autoReuseHeadSegmentLengthOpnd.Initialize(headSegmentLengthOpnd, m_func);
}
// For push, it is guaranteed that (index >= length). We already know that (index < size), but we need to check if
// (index > length) because in that case a missing value will be created and the missing value tracking in the array
// needs to be updated.
//
// cmp index, headSegmentLength
// je $updateLength
// and [array + offsetOf(objectArrayOrFlags)], ~Js::DynamicObjectFlags::HasNoMissingValues
// updateLength:
InsertCompareBranch(
indexValueOpnd,
headSegmentLengthOpnd,
Js::OpCode::BrEq_A,
updateLengthLabel,
updateLengthLabel);
CompileAssert(
static_cast<Js::DynamicObjectFlags>(static_cast<uint8>(Js::DynamicObjectFlags::HasNoMissingValues)) ==
Js::DynamicObjectFlags::HasNoMissingValues);
InsertAnd(
IR::IndirOpnd::New(arrayOpnd, Js::JavascriptArray::GetOffsetOfArrayFlags(), TyUint8, m_func),
IR::IndirOpnd::New(arrayOpnd, Js::JavascriptArray::GetOffsetOfArrayFlags(), TyUint8, m_func),
IR::IntConstOpnd::New(
static_cast<uint8>(~Js::DynamicObjectFlags::HasNoMissingValues),
TyUint8,
m_func,
true),
updateLengthLabel);
}
if (baseValueType.IsArrayOrObjectWithArray())
{
// We didn't emit an array check, but if we are going to grow the array
// We need to go to helper if there is an ES5 array/objectarray used as prototype
GenerateIsEnabledArraySetElementFastPathCheck(labelHelper, instr);
}
IR::Opnd *newLengthOpnd;
IR::AutoReuseOpnd autoReuseNewLengthOpnd;
if (indexValueOpnd->IsRegOpnd())
{
// LEA newLength, [index + 1]
newLengthOpnd = IR::RegOpnd::New(TyUint32, this->m_func);
autoReuseNewLengthOpnd.Initialize(newLengthOpnd, m_func);
InsertAdd(false /* needFlags */, newLengthOpnd, indexValueOpnd, IR::IntConstOpnd::New(1, TyUint32, m_func), instr);
}
else
{
newLengthOpnd = IR::IntConstOpnd::New(value + 1, TyUint32, this->m_func);
autoReuseNewLengthOpnd.Initialize(newLengthOpnd, m_func);
}
// This is a common enough case that we want to go through this path instead of the simpler one, since doing it this way is faster for preallocated but un-filled arrays.
if (!!(bailOutKind & IR::BailOutOnInvalidatedArrayLength))
{
// If we'd increase the array length, go to the helper
indirOpnd = IR::IndirOpnd::New(arrayOpnd, Js::JavascriptArray::GetOffsetOfLength(), TyUint32, this->m_func);
InsertCompareBranch(
newLengthOpnd,
indirOpnd,
Js::OpCode::BrGt_A,
true,
labelHelper,
instr);
}
// MOV [headSegment + offset(length)], newLength
indirOpnd = IR::IndirOpnd::New(headSegmentOpnd, offsetof(Js::SparseArraySegmentBase, length), TyUint32, this->m_func);
InsertMove(indirOpnd, newLengthOpnd, instr);
// We've changed the head segment length, so we may need to change the head segment length opnd
if (headSegmentLengthOpnd != nullptr && !headSegmentLengthOpnd->IsIndirOpnd())
{
InsertMove(headSegmentLengthOpnd, newLengthOpnd, instr);
}
if (checkArrayLengthOverflow)
{
// CMP newLength, [base + offset(length)]
// JBE $segmentLengthIncreased
Assert(labelSegmentLengthIncreased);
indirOpnd = IR::IndirOpnd::New(arrayOpnd, Js::JavascriptArray::GetOffsetOfLength(), TyUint32, this->m_func);
InsertCompareBranch(
newLengthOpnd,
indirOpnd,
Js::OpCode::BrLe_A,
true /* isUnsigned */,
labelSegmentLengthIncreased,
instr);
if(!isInHelperBlock)
{
InsertLabel(true /* isHelper */, instr);
}
}
// MOV [base + offset(length)], newLength
indirOpnd = IR::IndirOpnd::New(arrayOpnd, Js::JavascriptArray::GetOffsetOfLength(), TyUint32, this->m_func);
InsertMove(indirOpnd, newLengthOpnd, instr);
if(returnLength)
{
if(newLengthOpnd->GetSize() != MachPtr)
{
newLengthOpnd = newLengthOpnd->UseWithNewType(TyMachPtr, m_func)->AsRegOpnd();
}
// SHL newLength, AtomTag
// INC newLength
this->m_lowererMD.GenerateInt32ToVarConversion(newLengthOpnd, instr);
// MOV dst, newLength
InsertMove(instr->GetDst(), newLengthOpnd, instr);
}
// Calling code assumes that indirOpnd is initialized before labelSegmentLengthIncreased is reached
if(labelSegmentLengthIncreased && labelSegmentLengthIncreased != labelDone)
{
// labelSegmentLengthIncreased:
instr->InsertBefore(labelSegmentLengthIncreased);
}
// $done
instr->InsertBefore(labelDone);
}
else // #else
{
if (checkArrayLengthOverflow)
{
if (*pIsTypedArrayElement && isStore)
{
IR::LabelInstr *labelInlineSet = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
LABELNAME(labelInlineSet);
//For positive index beyond length or negative index its essentially nop for typed array store
InsertBranch(
!invertBoundCheckComparison ? Js::OpCode::BrLt_A : Js::OpCode::BrGt_A,
true /* isUnsigned */,
labelInlineSet,
instr);
// For typed array, call ToNumber before we fallThrough.
if (instr->GetSrc1()->GetType() == TyVar && !instr->GetSrc1()->GetValueType().IsPrimitive())
{
// Enter an ophelper block
IR::LabelInstr * opHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true);
LABELNAME(opHelper);
instr->InsertBefore(opHelper);
IR::Instr *toNumberInstr = IR::Instr::New(Js::OpCode::Call, this->m_func);
toNumberInstr->SetSrc1(instr->GetSrc1());
instr->InsertBefore(toNumberInstr);
if (BailOutInfo::IsBailOutOnImplicitCalls(bailOutKind))
{
// Bail out if this conversion triggers implicit calls.
toNumberInstr = this->AddBailoutToHelperCallInstr(toNumberInstr, instr->GetBailOutInfo(), bailOutKind, instr);
}
LowerUnaryHelperMem(toNumberInstr, IR::HelperOp_ConvNumber_Full);
}
InsertBranch(Js::OpCode::Br, labelFallthrough, instr); //Jump to fallThrough
instr->InsertBefore(labelInlineSet);
}
else
{
// JAE $helper
InsertBranch(
!invertBoundCheckComparison ? Js::OpCode::BrGe_A : Js::OpCode::BrLe_A,
true /* isUnsigned */,
labelHelper,
instr);
}
}
EnsureObjectArrayLoaded();
if (instr->m_opcode == Js::OpCode::InlineArrayPop)
{
Assert(!baseValueType.IsLikelyTypedArray());
Assert(bailOutLabelInstr);
if (indexValueOpnd->IsIntConstOpnd())
{
// indirOpnd = [headSegment + index + offset(elements)]
IntConstType offset = offsetof(Js::SparseArraySegment<Js::Var>, elements) + (value << indirScale);
// TODO: Assert(Math::FitsInDWord(offset));
indirOpnd = IR::IndirOpnd::New(headSegmentOpnd, (int32)offset, indirType, this->m_func);
}
else
{
// indirOpnd = [headSegment + offset(elements) + (index << scale)]
indirOpnd = IR::IndirOpnd::New(headSegmentOpnd, indexValueOpnd->AsRegOpnd(), indirScale, indirType, this->m_func);
indirOpnd->SetOffset(offsetof(Js::SparseArraySegment<Js::Var>, elements));
}
IR::Opnd * tmpDst = nullptr;
IR::Opnd * dst = instr->GetDst();
// Pop might not have a dst, if not don't worry about returning the last element. But we still have to
// worry about gaps, because these force us to access the prototype chain, which may have side-effects.
if (dst || !baseValueType.HasNoMissingValues())
{
if (!dst)
{
dst = IR::RegOpnd::New(indirType, this->m_func);
}
else if (dst->AsRegOpnd()->m_sym == arrayOpnd->m_sym)
{
tmpDst = IR::RegOpnd::New(TyVar, this->m_func);
dst = tmpDst;
}
// Use a mask to prevent arbitrary speculative reads
// If you think this code looks highly similar to the code later in this function,
// you'd be right. Unfortunately, I wasn't able to find a way to reduce duplication
// here without significantly complicating the code structure.
if (!headSegmentLengthOpnd)
{
headSegmentLengthOpnd =
IR::IndirOpnd::New(headSegmentOpnd, Js::SparseArraySegmentBase::GetOffsetOfLength(), TyUint32, m_func);
autoReuseHeadSegmentLengthOpnd.Initialize(headSegmentLengthOpnd, m_func);
}
IR::RegOpnd* localMaskOpnd = nullptr;
#if TARGET_64
IR::Opnd* lengthOpnd = nullptr;
AnalysisAssert(headSegmentLengthOpnd != nullptr);
lengthOpnd = IR::RegOpnd::New(headSegmentLengthOpnd->GetType(), m_func);
{
IR::Instr * instrMov = IR::Instr::New(Js::OpCode::MOV_TRUNC, lengthOpnd, headSegmentLengthOpnd, m_func);
instr->InsertBefore(instrMov);
LowererMD::Legalize(instrMov);
}
if (lengthOpnd->GetSize() != MachPtr)
{
lengthOpnd = lengthOpnd->UseWithNewType(TyMachPtr, this->m_func)->AsRegOpnd();
}
// MOV r1, [opnd + offset(type)]
IR::RegOpnd* indexValueRegOpnd = IR::RegOpnd::New(indexValueOpnd->GetType(), m_func);
{
IR::Instr * instrMov = IR::Instr::New(Js::OpCode::MOV_TRUNC, indexValueRegOpnd, indexValueOpnd, m_func);
instr->InsertBefore(instrMov);
LowererMD::Legalize(instrMov);
}
if (indexValueRegOpnd->GetSize() != MachPtr)
{
indexValueRegOpnd = indexValueRegOpnd->UseWithNewType(TyMachPtr, this->m_func)->AsRegOpnd();
}
localMaskOpnd = IR::RegOpnd::New(TyMachPtr, m_func);
InsertSub(false, localMaskOpnd, indexValueRegOpnd, lengthOpnd, instr);
InsertShift(Js::OpCode::Shr_A, false, localMaskOpnd, localMaskOpnd, IR::IntConstOpnd::New(63, TyInt8, m_func), instr);
#else
localMaskOpnd = IR::RegOpnd::New(TyInt32, m_func);
InsertSub(false, localMaskOpnd, indexValueOpnd, headSegmentLengthOpnd, instr);
InsertShift(Js::OpCode::Shr_A, false, localMaskOpnd, localMaskOpnd, IR::IntConstOpnd::New(31, TyInt8, m_func), instr);
#endif
// for pop we always do the masking before the load in cases where we load a value
IR::RegOpnd* loadAddr = IR::RegOpnd::New(TyMachPtr, m_func);
#if _M_ARM32_OR_ARM64
if (indirOpnd->GetIndexOpnd() != nullptr && indirOpnd->GetScale() > 0)
{
// We don't support encoding for LEA with scale on ARM/ARM64, so do the scale calculation as a separate instruction
IR::RegOpnd* fullIndexOpnd = IR::RegOpnd::New(indirOpnd->GetIndexOpnd()->GetType(), m_func);
InsertShift(Js::OpCode::Shl_A, false, fullIndexOpnd, indirOpnd->GetIndexOpnd(), IR::IntConstOpnd::New(indirOpnd->GetScale(), TyInt8, m_func), instr);
IR::IndirOpnd* newIndir = IR::IndirOpnd::New(indirOpnd->GetBaseOpnd(), fullIndexOpnd, indirType, m_func);
if (indirOpnd->GetOffset() != 0)
{
newIndir->SetOffset(indirOpnd->GetOffset());
}
indirOpnd = newIndir;
}
#endif
IR::AutoReuseOpnd reuseIndir(indirOpnd, m_func);
InsertLea(loadAddr, indirOpnd, instr);
InsertAnd(loadAddr, loadAddr, localMaskOpnd, instr);
indirOpnd = IR::IndirOpnd::New(loadAddr, 0, indirType, m_func);
// MOV dst, [head + offset]
InsertMove(dst, indirOpnd, instr);
//If the array has missing values, check for one
if (!baseValueType.HasNoMissingValues())
{
InsertMissingItemCompareBranch(
dst,
Js::OpCode::BrEq_A,
bailOutLabelInstr,
instr);
}
}
// MOV [head + offset], missing
InsertMove(indirOpnd, GetMissingItemOpndForAssignment(indirType, m_func), instr);
IR::Opnd *newLengthOpnd;
IR::AutoReuseOpnd autoReuseNewLengthOpnd;
if (indexValueOpnd->IsRegOpnd())
{
// LEA newLength, [index]
newLengthOpnd = indexValueOpnd;
autoReuseNewLengthOpnd.Initialize(newLengthOpnd, m_func);
}
else
{
newLengthOpnd = IR::IntConstOpnd::New(value, TyUint32, this->m_func);
autoReuseNewLengthOpnd.Initialize(newLengthOpnd, m_func);
}
//update segment length and array length
// MOV [headSegment + offset(length)], newLength
IR::IndirOpnd *lengthIndirOpnd = IR::IndirOpnd::New(headSegmentOpnd, offsetof(Js::SparseArraySegmentBase, length), TyUint32, this->m_func);
InsertMove(lengthIndirOpnd, newLengthOpnd, instr);
// We've changed the head segment length, so we may need to change the head segment length opnd
if (headSegmentLengthOpnd != nullptr && !headSegmentLengthOpnd->IsIndirOpnd())
{
InsertMove(headSegmentLengthOpnd, newLengthOpnd, instr);
}
// MOV [base + offset(length)], newLength
lengthIndirOpnd = IR::IndirOpnd::New(arrayOpnd, Js::JavascriptArray::GetOffsetOfLength(), TyUint32, this->m_func);
InsertMove(lengthIndirOpnd, newLengthOpnd, instr);
if (tmpDst)
{
// The array opnd and the destination is the same, need to move the value in the tmp dst
// to the actual dst
InsertMove(instr->GetDst(), tmpDst, instr);
}
return indirOpnd;
}
} // #endif
// Should we poison the load of the address to/from which the store/load happens?
bool shouldPoisonLoad = maskOpnd != nullptr
&& (
(!isStore && (!instr->IsSafeToSpeculate()) &&
(baseValueType.IsLikelyTypedArray()
? CONFIG_FLAG_RELEASE(PoisonTypedArrayLoad)
: ((indirType == TyVar && CONFIG_FLAG_RELEASE(PoisonVarArrayLoad))
|| (IRType_IsNativeInt(indirType) && CONFIG_FLAG_RELEASE(PoisonIntArrayLoad))
|| (IRType_IsFloat(indirType) && CONFIG_FLAG_RELEASE(PoisonFloatArrayLoad)))
)
)
||
(isStore &&
(baseValueType.IsLikelyTypedArray()
? CONFIG_FLAG_RELEASE(PoisonTypedArrayStore)
: ((indirType == TyVar && CONFIG_FLAG_RELEASE(PoisonVarArrayStore))
|| (IRType_IsNativeInt(indirType) && CONFIG_FLAG_RELEASE(PoisonIntArrayStore))
|| (IRType_IsFloat(indirType) && CONFIG_FLAG_RELEASE(PoisonFloatArrayStore)))
)
)
)
;
// We have two exit paths for this function in the store case when we might grow the head
// segment, due to tracking for missing elements. This unfortunately means that we need a
// copy of the poisoning code on the other exit path, since the determination of the path
// and the use of the path determination to decide whether we found the missing value are
// things that have to happen on opposite sides of the poisoning.
IR::Instr* insertForSegmentLengthIncreased = nullptr;
if (shouldPoisonLoad && usingSegmentLengthIncreasedLabel)
{
insertForSegmentLengthIncreased = (*pLabelSegmentLengthIncreased)->m_next;
}
#if TARGET_32
if (shouldPoisonLoad)
{
// Prevent index from being negative, which would break the poisoning
if (indexValueOpnd->IsIntConstOpnd())
{
indexValueOpnd = IR::IntConstOpnd::New(value & INT32_MAX, TyUint32, m_func);
}
else
{
IR::RegOpnd* newIndexValueOpnd = IR::RegOpnd::New(TyUint32, m_func);
InsertAnd(newIndexValueOpnd, indexValueOpnd, IR::IntConstOpnd::New(INT32_MAX, TyUint32, m_func), instr);
if(insertForSegmentLengthIncreased != nullptr)
{
InsertAnd(newIndexValueOpnd, indexValueOpnd, IR::IntConstOpnd::New(INT32_MAX, TyUint32, m_func), insertForSegmentLengthIncreased);
}
indexValueOpnd = newIndexValueOpnd;
}
}
#endif
if (baseValueType.IsLikelyTypedArray())
{
if(!headSegmentOpnd)
{
// MOV headSegment, [base + offset(arrayBuffer)]
int bufferOffset;
bufferOffset = Js::Float64Array::GetOffsetOfBuffer();
indirOpnd = IR::IndirOpnd::New(arrayOpnd, bufferOffset, TyMachPtr, this->m_func);
headSegmentOpnd = IR::RegOpnd::New(TyMachPtr, this->m_func);
autoReuseHeadSegmentOpnd.Initialize(headSegmentOpnd, m_func);
IR::AutoReuseOpnd reuseIndir(indirOpnd, m_func);
InsertMove(headSegmentOpnd, indirOpnd, instr);
if(insertForSegmentLengthIncreased != nullptr)
{
InsertMove(headSegmentOpnd, indirOpnd, insertForSegmentLengthIncreased);
}
}
// indirOpnd = [headSegment + index]
if (indexValueOpnd->IsIntConstOpnd())
{
IntConstType offset = (value << indirScale);
// TODO: Assert(Math::FitsInDWord(offset));
indirOpnd = IR::IndirOpnd::New(headSegmentOpnd, (int32)offset, indirType, this->m_func);
}
else
{
indirOpnd = IR::IndirOpnd::New(headSegmentOpnd, indexValueOpnd->AsRegOpnd(), indirScale, indirType, this->m_func);
}
}
else if (indexValueOpnd->IsIntConstOpnd())
{
// indirOpnd = [headSegment + index + offset(elements)]
IntConstType offset = offsetof(Js::SparseArraySegment<Js::Var>, elements) + (value << indirScale);
// TODO: Assert(Math::FitsInDWord(offset));
indirOpnd = IR::IndirOpnd::New(headSegmentOpnd, (int32)offset, indirType, this->m_func);
}
else
{
// indirOpnd = [headSegment + offset(elements) + (index << scale)]
indirOpnd = IR::IndirOpnd::New(headSegmentOpnd, indexValueOpnd->AsRegOpnd(), indirScale, indirType, this->m_func);
indirOpnd->SetOffset(offsetof(Js::SparseArraySegment<Js::Var>, elements));
}
if (shouldPoisonLoad)
{
// Use a mask to prevent arbitrary speculative reads
if (!headSegmentLengthOpnd
#if ENABLE_FAST_ARRAYBUFFER
&& !baseValueType.IsLikelyOptimizedVirtualTypedArray()
#endif
)
{
if (baseValueType.IsLikelyTypedArray())
{
int lengthOffset;
lengthOffset = GetArrayOffsetOfLength(baseValueType);
headSegmentLengthOpnd = IR::IndirOpnd::New(arrayOpnd, lengthOffset, TyUint32, m_func);
autoReuseHeadSegmentLengthOpnd.Initialize(headSegmentLengthOpnd, m_func);
}
else
{
headSegmentLengthOpnd =
IR::IndirOpnd::New(headSegmentOpnd, Js::SparseArraySegmentBase::GetOffsetOfLength(), TyUint32, m_func);
autoReuseHeadSegmentLengthOpnd.Initialize(headSegmentLengthOpnd, m_func);
}
}
IR::RegOpnd* localMaskOpnd = nullptr;
#if TARGET_64
IR::Opnd* lengthOpnd = nullptr;
#if ENABLE_FAST_ARRAYBUFFER
if (baseValueType.IsLikelyOptimizedVirtualTypedArray())
{
lengthOpnd = IR::IntConstOpnd::New(MAX_ASMJS_ARRAYBUFFER_LENGTH >> indirScale, TyMachReg, m_func);
}
else
#endif
{
AnalysisAssert(headSegmentLengthOpnd != nullptr);
lengthOpnd = IR::RegOpnd::New(headSegmentLengthOpnd->GetType(), m_func);
IR::Instr * instrMov = IR::Instr::New(Js::OpCode::MOV_TRUNC, lengthOpnd, headSegmentLengthOpnd, m_func);
instr->InsertBefore(instrMov);
LowererMD::Legalize(instrMov);
if (insertForSegmentLengthIncreased != nullptr)
{
IR::Instr * instrMov2 = IR::Instr::New(Js::OpCode::MOV_TRUNC, lengthOpnd, headSegmentLengthOpnd, m_func);
insertForSegmentLengthIncreased->InsertBefore(instrMov2);
LowererMD::Legalize(instrMov2);
}
if (lengthOpnd->GetSize() != MachPtr)
{
lengthOpnd = lengthOpnd->UseWithNewType(TyMachPtr, this->m_func)->AsRegOpnd();
}
}
// MOV r1, [opnd + offset(type)]
IR::RegOpnd* indexValueRegOpnd = IR::RegOpnd::New(indexValueOpnd->GetType(), m_func);
IR::Instr * instrMov = IR::Instr::New(Js::OpCode::MOV_TRUNC, indexValueRegOpnd, indexValueOpnd, m_func);
instr->InsertBefore(instrMov);
LowererMD::Legalize(instrMov);
if (insertForSegmentLengthIncreased != nullptr)
{
IR::Instr * instrMov2 = IR::Instr::New(Js::OpCode::MOV_TRUNC, indexValueRegOpnd, indexValueOpnd, m_func);
insertForSegmentLengthIncreased->InsertBefore(instrMov2);
LowererMD::Legalize(instrMov2);
}
if (indexValueRegOpnd->GetSize() != MachPtr)
{
indexValueRegOpnd = indexValueRegOpnd->UseWithNewType(TyMachPtr, this->m_func)->AsRegOpnd();
}
localMaskOpnd = IR::RegOpnd::New(TyMachPtr, m_func);
InsertSub(false, localMaskOpnd, indexValueRegOpnd, lengthOpnd, instr);
InsertShift(Js::OpCode::Shr_A, false, localMaskOpnd, localMaskOpnd, IR::IntConstOpnd::New(63, TyInt8, m_func), instr);
if (insertForSegmentLengthIncreased != nullptr)
{
InsertSub(false, localMaskOpnd, indexValueRegOpnd, lengthOpnd, insertForSegmentLengthIncreased);
InsertShift(Js::OpCode::Shr_A, false, localMaskOpnd, localMaskOpnd, IR::IntConstOpnd::New(63, TyInt8, m_func), insertForSegmentLengthIncreased);
}
#else
localMaskOpnd = IR::RegOpnd::New(TyInt32, m_func);
InsertSub(false, localMaskOpnd, indexValueOpnd, headSegmentLengthOpnd, instr);
InsertShift(Js::OpCode::Shr_A, false, localMaskOpnd, localMaskOpnd, IR::IntConstOpnd::New(31, TyInt8, m_func), instr);
if (insertForSegmentLengthIncreased != nullptr)
{
InsertSub(false, localMaskOpnd, indexValueOpnd, headSegmentLengthOpnd, insertForSegmentLengthIncreased);
InsertShift(Js::OpCode::Shr_A, false, localMaskOpnd, localMaskOpnd, IR::IntConstOpnd::New(31, TyInt8, m_func), insertForSegmentLengthIncreased);
}
#endif
if ((IRType_IsNativeInt(indirType) || indirType == TyVar) && !isStore)
{
*maskOpnd = localMaskOpnd;
}
else
{
// for float values, do the poisoning before the load to avoid needing slow floating point conversions
IR::RegOpnd* loadAddr = IR::RegOpnd::New(TyMachPtr, m_func);
#if _M_ARM32_OR_ARM64
if (indirOpnd->GetIndexOpnd() != nullptr && indirOpnd->GetScale() > 0)
{
// We don't support encoding for LEA with scale on ARM/ARM64, so do the scale calculation as a separate instruction
IR::RegOpnd* fullIndexOpnd = IR::RegOpnd::New(indirOpnd->GetIndexOpnd()->GetType(), m_func);
InsertShift(Js::OpCode::Shl_A, false, fullIndexOpnd, indirOpnd->GetIndexOpnd(), IR::IntConstOpnd::New(indirOpnd->GetScale(), TyInt8, m_func), instr);
IR::IndirOpnd* newIndir = IR::IndirOpnd::New(indirOpnd->GetBaseOpnd(), fullIndexOpnd, indirType, m_func);
if (insertForSegmentLengthIncreased != nullptr)
{
InsertShift(Js::OpCode::Shl_A, false, fullIndexOpnd, indirOpnd->GetIndexOpnd(), IR::IntConstOpnd::New(indirOpnd->GetScale(), TyInt8, m_func), insertForSegmentLengthIncreased);
}
if (indirOpnd->GetOffset() != 0)
{
newIndir->SetOffset(indirOpnd->GetOffset());
}
indirOpnd = newIndir;
}
#endif
IR::AutoReuseOpnd reuseIndir(indirOpnd, m_func);
InsertLea(loadAddr, indirOpnd, instr);
InsertAnd(loadAddr, loadAddr, localMaskOpnd, instr);
if (insertForSegmentLengthIncreased != nullptr)
{
InsertLea(loadAddr, indirOpnd, insertForSegmentLengthIncreased);
InsertAnd(loadAddr, loadAddr, localMaskOpnd, insertForSegmentLengthIncreased);
// We want to export a segmentLengthIncreasedLabel to the caller that is after the poisoning
// code, since that's also the code that generates indirOpnd in this case.
IR::LabelInstr* exportedSegmentLengthIncreasedLabel = IR::LabelInstr::New(Js::OpCode::Label, insertForSegmentLengthIncreased->m_func, (*pLabelSegmentLengthIncreased)->isOpHelper);
LABELNAME(exportedSegmentLengthIncreasedLabel);
insertForSegmentLengthIncreased->InsertBefore(exportedSegmentLengthIncreasedLabel);
*pLabelSegmentLengthIncreased = exportedSegmentLengthIncreasedLabel;
}
indirOpnd = IR::IndirOpnd::New(loadAddr, 0, indirType, m_func);
}
}
return indirOpnd;
}
IR::BranchInstr*
Lowerer::InsertMissingItemCompareBranch(IR::Opnd* compareSrc, Js::OpCode opcode, IR::LabelInstr* target, IR::Instr* insertBeforeInstr)
{
IR::Opnd* missingItemOpnd = GetMissingItemOpndForCompare(compareSrc->GetType(), m_func);
if (compareSrc->IsFloat64())
{
Assert(compareSrc->IsRegOpnd() || compareSrc->IsIndirOpnd());
return m_lowererMD.InsertMissingItemCompareBranch(compareSrc, missingItemOpnd, opcode, target, insertBeforeInstr);
}
else
{
Assert(compareSrc->IsInt32() || compareSrc->IsVar());
return InsertCompareBranch(missingItemOpnd, compareSrc, opcode, target, insertBeforeInstr, true);
}
}
IR::RegOpnd *
Lowerer::GenerateUntagVar(IR::RegOpnd * opnd, IR::LabelInstr * labelFail, IR::Instr * insertBeforeInstr, bool generateTagCheck)
{
if (!opnd->IsVar())
{
AssertMsg(opnd->GetSize() == 4, "This should be 32-bit wide");
return opnd;
}
AssertMsg(!opnd->IsNotInt(), "An opnd we know is not an int should not try to untag it as it will always fail");
if (opnd->m_sym->IsIntConst())
{
int32 constValue = opnd->m_sym->GetIntConstValue();
IR::IntConstOpnd* constOpnd = IR::IntConstOpnd::New(constValue, TyInt32, this->m_func);
IR::RegOpnd* regOpnd = IR::RegOpnd::New(TyInt32, this->m_func);
InsertMove(regOpnd, constOpnd, insertBeforeInstr);
return regOpnd;
}
return m_lowererMD.GenerateUntagVar(opnd, labelFail, insertBeforeInstr, generateTagCheck && !opnd->IsTaggedInt());
}
void
Lowerer::GenerateNotZeroTest( IR::Opnd * opndSrc, IR::LabelInstr * isZeroLabel, IR::Instr * insertBeforeInstr)
{
InsertTestBranch(opndSrc, opndSrc, Js::OpCode::BrEq_A, isZeroLabel, insertBeforeInstr);
}
bool
Lowerer::GenerateFastStringLdElem(IR::Instr * ldElem, IR::LabelInstr * labelHelper, IR::LabelInstr * labelFallThru)
{
IR::IndirOpnd * indirOpnd = ldElem->GetSrc1()->AsIndirOpnd();
IR::RegOpnd * baseOpnd = indirOpnd->GetBaseOpnd();
// don't generate the fast path if the instance is not likely string
if (!baseOpnd->GetValueType().IsLikelyString())
{
return false;
}
Assert(!baseOpnd->IsTaggedInt());
IR::RegOpnd * indexOpnd = indirOpnd->GetIndexOpnd();
// Don't generate the fast path if the index operand is not likely int
if (indexOpnd && !indexOpnd->GetValueType().IsLikelyInt())
{
return false;
}
// Make sure the instance is a string
Assert(!indexOpnd || !indexOpnd->IsNotInt());
GenerateStringTest(baseOpnd, ldElem, labelHelper);
IR::Opnd * index32CmpOpnd;
IR::RegOpnd * bufferOpnd = IR::RegOpnd::New(TyMachPtr, this->m_func);
const IR::AutoReuseOpnd autoReuseBufferOpnd(bufferOpnd, m_func);
IR::IndirOpnd * charIndirOpnd;
if (indexOpnd)
{
// Untag the var and generate the indir into the string buffer
IR::RegOpnd * index32Opnd = GenerateUntagVar(indexOpnd, labelHelper, ldElem);
charIndirOpnd = IR::IndirOpnd::New(bufferOpnd, index32Opnd, 1, TyUint16, this->m_func);
index32CmpOpnd = index32Opnd;
}
else
{
// Just use the offset to indirect into the string buffer
charIndirOpnd = IR::IndirOpnd::New(bufferOpnd, indirOpnd->GetOffset() * sizeof(char16), TyUint16, this->m_func);
index32CmpOpnd = IR::IntConstOpnd::New((uint32)indirOpnd->GetOffset(), TyUint32, this->m_func);
}
// Check if the index is in range of the string length
// CMP [baseOpnd + offset(length)], indexOpnd -- string length
// JBE $helper -- unsigned compare, and string length are at most INT_MAX - 1
// -- so that even if we have a negative index, this will fail
IR::RegOpnd* lengthOpnd = IR::RegOpnd::New(TyUint32, m_func);
InsertMove(lengthOpnd, IR::IndirOpnd::New(baseOpnd, offsetof(Js::JavascriptString, m_charLength), TyUint32, this->m_func), ldElem);
InsertCompareBranch(lengthOpnd, index32CmpOpnd, Js::OpCode::BrLe_A, true, labelHelper, ldElem);
// Load the string buffer and make sure it is not null
// MOV bufferOpnd, [baseOpnd + offset(m_pszValue)]
// TEST bufferOpnd, bufferOpnd
// JEQ $labelHelper
indirOpnd = IR::IndirOpnd::New(baseOpnd, offsetof(Js::JavascriptString, m_pszValue), TyMachPtr, this->m_func);
InsertMove(bufferOpnd, indirOpnd, ldElem);
GenerateNotZeroTest(bufferOpnd, labelHelper, ldElem);
IR::RegOpnd* maskOpnd = nullptr;
if (CONFIG_FLAG_RELEASE(PoisonStringLoad))
{
// Mask off the sign before loading so that poisoning will work for negative indices
if (index32CmpOpnd->IsIntConstOpnd())
{
charIndirOpnd->SetOffset((index32CmpOpnd->AsIntConstOpnd()->AsUint32() & INT32_MAX) * sizeof(char16));
}
else
{
InsertAnd(index32CmpOpnd, index32CmpOpnd, IR::IntConstOpnd::New(INT32_MAX, TyInt32, m_func), ldElem);
}
// All bits in mask will be 1 for a valid index or 0 for an OOB index
maskOpnd = IR::RegOpnd::New(TyInt32, m_func);
InsertSub(false, maskOpnd, index32CmpOpnd, lengthOpnd, ldElem);
InsertShift(Js::OpCode::Shr_A, false, maskOpnd, maskOpnd, IR::IntConstOpnd::New(31, TyInt8, m_func), ldElem);
}
// Load the character and check if it is 7bit ASCI (which we have the cache for)
// MOV charOpnd, [bufferOpnd + index32Opnd]
// CMP charOpnd, 0x80
// JAE $helper
IR::RegOpnd * charOpnd = IR::RegOpnd::New(TyUint32, this->m_func);
const IR::AutoReuseOpnd autoReuseCharOpnd(charOpnd, m_func);
InsertMove(charOpnd, charIndirOpnd, ldElem);
if (CONFIG_FLAG_RELEASE(PoisonStringLoad))
{
InsertAnd(charOpnd, charOpnd, maskOpnd, ldElem);
}
InsertCompareBranch(charOpnd, IR::IntConstOpnd::New(Js::CharStringCache::CharStringCacheSize, TyUint16, this->m_func),
Js::OpCode::BrGe_A, true, labelHelper, ldElem);
// Load the string from the cache
// MOV charStringCache, <charStringCache, address>
// MOV stringOpnd, [charStringCache + charOpnd * 4]
IR::RegOpnd * cacheOpnd = IR::RegOpnd::New(TyMachPtr, this->m_func);
const IR::AutoReuseOpnd autoReuseCacheOpnd(cacheOpnd, m_func);
Assert(Js::JavascriptLibrary::GetCharStringCacheAOffset() == Js::JavascriptLibrary::GetCharStringCacheOffset());
InsertMove(cacheOpnd, this->LoadLibraryValueOpnd(ldElem, LibraryValue::ValueCharStringCache), ldElem);
// Check if we have created the string or not
// TEST stringOpnd, stringOpnd
// JE $helper
IR::RegOpnd * stringOpnd = IR::RegOpnd::New(TyMachPtr, this->m_func);
const IR::AutoReuseOpnd autoReuseStringOpnd(stringOpnd, m_func);
InsertMove(stringOpnd, IR::IndirOpnd::New(cacheOpnd, charOpnd, this->m_lowererMD.GetDefaultIndirScale(), TyVar, this->m_func), ldElem);
GenerateNotZeroTest(stringOpnd, labelHelper, ldElem);
InsertMove(ldElem->GetDst(), stringOpnd, ldElem);
InsertBranch(Js::OpCode::Br, labelFallThru, ldElem);
return true;
}
bool
Lowerer::GenerateFastLdElemI(IR::Instr *& ldElem, bool *instrIsInHelperBlockRef)
{
Assert(instrIsInHelperBlockRef);
bool &instrIsInHelperBlock = *instrIsInHelperBlockRef;
instrIsInHelperBlock = false;
IR::LabelInstr * labelHelper;
IR::LabelInstr * labelFallThru;
IR::LabelInstr * labelBailOut = nullptr;
IR::LabelInstr * labelMissingNative = nullptr;
IR::Opnd *src1 = ldElem->GetSrc1();
AssertMsg(src1->IsIndirOpnd(), "Expected indirOpnd on LdElementI");
IR::IndirOpnd * indirOpnd = src1->AsIndirOpnd();
// From FastElemICommon:
// TEST base, AtomTag -- check base not tagged int
// JNE $helper
// MOV r1, [base + offset(type)] -- check base isArray
// CMP [r1 + offset(typeId)], TypeIds_Array
// JNE $helper
// TEST index, 1 -- index tagged int
// JEQ $helper
// MOV r2, index
// SAR r2, Js::VarTag_Shift -- remoe atom tag
// JS $helper -- exclude negative index
// MOV r4, [base + offset(head)]
// CMP r2, [r4 + offset(length)] -- bounds check
// JAE $helper
// MOV r3, [r4 + offset(elements)]
// Generated here:
// MOV dst, [r3 + r2]
// TEST dst, dst
// JNE $fallthrough
if(ldElem->m_opcode == Js::OpCode::LdMethodElem && indirOpnd->GetBaseOpnd()->GetValueType().IsLikelyOptimizedTypedArray())
{
// Typed arrays don't return objects, so it's not worth generating a fast path for LdMethodElem. Calling the helper also
// generates a better error message. Skip the fast path and just generate a helper call.
return true;
}
labelFallThru = ldElem->GetOrCreateContinueLabel();
labelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true);
// If we know for sure (based on flow graph) we're loading from the arguments object, then ignore the (path-based) profile info.
bool isNativeArrayLoad = !ldElem->DoStackArgsOpt() && indirOpnd->GetBaseOpnd()->GetValueType().IsLikelyNativeArray();
bool needMissingValueCheck = true;
bool emittedFastPath = false;
bool emitBailout = false;
if (ldElem->DoStackArgsOpt())
{
emittedFastPath = GenerateFastArgumentsLdElemI(ldElem, labelFallThru);
emitBailout = true;
}
else if (GenerateFastStringLdElem(ldElem, labelHelper, labelFallThru))
{
emittedFastPath = true;
}
else
{
IR::LabelInstr * labelCantUseArray = labelHelper;
if (isNativeArrayLoad)
{
if (ldElem->GetDst()->GetType() == TyVar)
{
// Skip the fast path and just generate a helper call
return true;
}
// Specialized native array lowering for LdElem requires that it is profiled. When not profiled, GlobOpt should not
// have specialized it.
Assert(ldElem->IsProfiledInstr());
labelBailOut = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true);
labelCantUseArray = labelBailOut;
}
Js::FldInfoFlags flags = Js::FldInfo_NoInfo;
if (ldElem->IsProfiledInstr())
{
flags = ldElem->AsProfiledInstr()->u.ldElemInfo->flags;
}
bool isTypedArrayElement, isStringIndex, indirOpndOverflowed = false;
IR::Opnd* maskOpnd = nullptr;
indirOpnd =
GenerateFastElemICommon(
ldElem,
false,
src1->AsIndirOpnd(),
labelHelper,
labelCantUseArray,
labelFallThru,
&isTypedArrayElement,
&isStringIndex,
&emitBailout,
&maskOpnd,
nullptr, /* pLabelSegmentLengthIncreased */
true, /* checkArrayLengthOverflow */
false, /* forceGenerateFastPath */
false, /* returnLength */
nullptr, /* bailOutLabelInstr */
&indirOpndOverflowed,
flags);
IR::Opnd *dst = ldElem->GetDst();
IRType dstType = dst->AsRegOpnd()->GetType();
// The index is negative or not int.
if (indirOpnd == nullptr)
{
// could have bailout kind BailOutOnArrayAccessHelperCall if indirOpnd overflows
Assert(!(ldElem->HasBailOutInfo() && ldElem->GetBailOutKind() & IR::BailOutOnArrayAccessHelperCall) || indirOpndOverflowed);
// don't check fast path without bailout because it might not be TypedArray
if (indirOpndOverflowed && ldElem->HasBailOutInfo())
{
bool bailoutForOpndOverflow = false;
const IR::BailOutKind bailOutKind = ldElem->GetBailOutKind();
// return undefined for typed array if load dest is var, bailout otherwise
if ((bailOutKind & ~IR::BailOutKindBits) == IR::BailOutConventionalTypedArrayAccessOnly)
{
if (dst->IsVar())
{
// returns undefined in case of indirOpnd overflow which is consistent with behavior of interpreter
IR::Opnd * undefinedOpnd = this->LoadLibraryValueOpnd(ldElem, LibraryValue::ValueUndefined);
InsertMove(dst, undefinedOpnd, ldElem);
ldElem->FreeSrc1();
ldElem->FreeDst();
ldElem->Remove();
emittedFastPath = true;
}
else
{
bailoutForOpndOverflow = true;
}
}
if (bailoutForOpndOverflow || (bailOutKind & (IR::BailOutConventionalNativeArrayAccessOnly | IR::BailOutOnArrayAccessHelperCall)))
{
IR::Opnd * constOpnd = nullptr;
if (dst->IsFloat())
{
constOpnd = IR::FloatConstOpnd::New(Js::JavascriptNumber::NaN, TyFloat64, m_func);
}
else
{
constOpnd = IR::IntConstOpnd::New(0, TyInt32, this->m_func, true);
}
InsertMove(dst, constOpnd, ldElem);
ldElem->FreeSrc1();
ldElem->FreeDst();
GenerateBailOut(ldElem, nullptr, nullptr);
emittedFastPath = true;
}
return !emittedFastPath;
}
// The global optimizer should never type specialize a LdElem for which the index is not int or an integer constant
// with a negative value. This would force an unconditional bail out on the main code path.
else if (dst->IsVar())
{
if (PHASE_TRACE(Js::TypedArrayTypeSpecPhase, this->m_func) && PHASE_TRACE(Js::LowererPhase, this->m_func))
{
char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
Output::Print(_u("Typed Array Lowering: function: %s (%s): instr %s, not specialized by glob opt due to negative or not likely int index.\n"),
this->m_func->GetJITFunctionBody()->GetDisplayName(),
this->m_func->GetDebugNumberSet(debugStringBuffer),
Js::OpCodeUtil::GetOpCodeName(ldElem->m_opcode));
Output::Flush();
}
// We must be dealing with some unconventional index value. Don't emit fast path, but go directly to helper.
emittedFastPath = false;
return true;
}
else
{
AssertMsg(false, "Global optimizer shouldn't have specialized this instruction.");
Assert(dst->IsRegOpnd());
// If global optimizer failed to notice the unconventional index and type specialized the dst,
// there is nothing to do but bail out. This could happen if global optimizer's information based
// on value tracking fails to recognize a non-integer index or a constant int index that is negative.
// The bailout below ensures that we behave correctly in retail builds even under
// these (unlikely) conditions. To satisfy the downstream code we must populate the type specialized operand
// with some made up values, even though we will unconditionally bail out here and the values will never be
// used.
IR::IntConstOpnd *constOpnd = IR::IntConstOpnd::New(0, TyInt32, this->m_func, true);
InsertMove(dst, constOpnd, ldElem);
ldElem->FreeSrc1();
ldElem->FreeDst();
GenerateBailOut(ldElem, nullptr, nullptr);
return false;
}
}
const IR::AutoReuseOpnd autoReuseIndirOpnd(indirOpnd, m_func);
const ValueType baseValueType(src1->AsIndirOpnd()->GetBaseOpnd()->GetValueType());
if ((ldElem->HasBailOutInfo() &&
ldElem->GetByteCodeOffset() != Js::Constants::NoByteCodeOffset &&
ldElem->GetBailOutInfo()->bailOutOffset <= ldElem->GetByteCodeOffset() &&
dst->IsEqual(src1->AsIndirOpnd()->GetBaseOpnd())) ||
(src1->AsIndirOpnd()->GetIndexOpnd() && dst->IsEqual(src1->AsIndirOpnd()->GetIndexOpnd())))
{
// This is a pre-op bailout where the dst is the same as one of the srcs. The dst may be trashed before bailing out,
// but since the operation will be processed again in the interpreter, src values need to be kept intact. Use a
// temporary dst until after the operation is complete.
IR::Instr *instrSink = ldElem->SinkDst(Js::OpCode::Ld_A);
// The sink instruction needs to be on the fall-through path
instrSink->Unlink();
labelFallThru->InsertAfter(instrSink);
LowererMD::ChangeToAssign(instrSink);
dst = ldElem->GetDst();
}
if (isTypedArrayElement)
{
// For typedArrays, convert the loaded element to the appropriate type
IR::RegOpnd *reg;
IR::AutoReuseOpnd autoReuseReg;
Assert(dst->IsRegOpnd());
if(indirOpnd->IsFloat())
{
AssertMsg((dstType == TyFloat64) || (dstType == TyVar), "For Float32Array LdElemI's dst should be specialized to TyFloat64 or not at all.");
if(indirOpnd->IsFloat32())
{
// MOVSS reg32.f32, indirOpnd.f32
IR::RegOpnd *reg32 = IR::RegOpnd::New(TyFloat32, this->m_func);
const IR::AutoReuseOpnd autoReuseReg32(reg32, m_func);
InsertMove(reg32, indirOpnd, ldElem);
// CVTPS2PD dst/reg.f64, reg32.f64
reg = dstType == TyFloat64 ? dst->AsRegOpnd() : IR::RegOpnd::New(TyFloat64, this->m_func);
autoReuseReg.Initialize(reg, m_func);
InsertConvertFloat32ToFloat64(reg, reg32, ldElem);
}
else
{
Assert(indirOpnd->IsFloat64());
// MOVSD dst/reg.f64, indirOpnd.f64
reg = dstType == TyFloat64 ? dst->AsRegOpnd() : IR::RegOpnd::New(TyFloat64, this->m_func);
autoReuseReg.Initialize(reg, m_func);
InsertMove(reg, indirOpnd, ldElem);
}
if (dstType != TyFloat64)
{
// Convert reg.f64 to var
m_lowererMD.SaveDoubleToVar(dst->AsRegOpnd(), reg, ldElem, ldElem);
}
#if FLOATVAR
// For NaNs, go to the helper to guarantee we don't have an illegal NaN
// TODO(magardn): move this to MD code.
#if _M_X64
// UCOMISD reg, reg
{
IR::Instr *const instr = IR::Instr::New(Js::OpCode::UCOMISD, this->m_func);
instr->SetSrc1(reg);
instr->SetSrc2(reg);
ldElem->InsertBefore(instr);
}
// JP $helper
{
IR::Instr *const instr = IR::BranchInstr::New(Js::OpCode::JP, labelHelper, this->m_func);
ldElem->InsertBefore(instr);
}
#elif _M_ARM64
// FCMP reg, reg
{
IR::Instr *const instr = IR::Instr::New(Js::OpCode::FCMP, this->m_func);
instr->SetSrc1(reg);
instr->SetSrc2(reg);
ldElem->InsertBefore(instr);
}
// BVS $helper
{
IR::Instr *const instr = IR::BranchInstr::New(Js::OpCode::BVS, labelHelper, this->m_func);
ldElem->InsertBefore(instr);
}
#endif
#endif
if(dstType == TyFloat64)
{
emitBailout = true;
}
}
else
{
AssertMsg((dstType == TyInt32) || (dstType == TyVar), "For Int/UintArray LdElemI's dst should be specialized to TyInt32 or not at all.");
reg = dstType == TyInt32 ? dst->AsRegOpnd() : IR::RegOpnd::New(TyInt32, this->m_func);
autoReuseReg.Initialize(reg, m_func);
// Int32 and Uint32 arrays could overflow an int31, but the others can't
if (indirOpnd->GetType() != TyUint32
#if !INT32VAR
&& indirOpnd->GetType() != TyInt32
#endif
)
{
reg->SetValueType(ValueType::GetTaggedInt()); // Fits as a tagged-int
}
// MOV/MOVZX/MOVSX dst/reg.int32, IndirOpnd.type
IR::Instr* instrMov = InsertMove(reg, indirOpnd, ldElem);
if (maskOpnd)
{
#if TARGET_64
if (maskOpnd->GetSize() != reg->GetType())
{
maskOpnd = maskOpnd->UseWithNewType(reg->GetType(), m_func)->AsRegOpnd();
}
#endif
instrMov = InsertAnd(reg, reg, maskOpnd, ldElem);
}
if (dstType == TyInt32)
{
instrMov->dstIsTempNumber = ldElem->dstIsTempNumber;
instrMov->dstIsTempNumberTransferred = ldElem->dstIsTempNumberTransferred;
if (indirOpnd->GetType() == TyUint32)
{
// TEST dst, dst
// JSB $helper (bailout)
InsertCompareBranch(
reg,
IR::IntConstOpnd::New(0, TyUint32, this->m_func, /* dontEncode = */ true),
Js::OpCode::BrLt_A,
labelHelper,
ldElem);
}
emitBailout = true;
}
else
{
// MOV dst, reg
IR::Instr *const instr = IR::Instr::New(Js::OpCode::ToVar, dst, reg, this->m_func);
instr->dstIsTempNumber = ldElem->dstIsTempNumber;
instr->dstIsTempNumberTransferred = ldElem->dstIsTempNumberTransferred;
ldElem->InsertBefore(instr);
// Convert dst to var
m_lowererMD.EmitLoadVar(instr, /* isFromUint32 = */ (indirOpnd->GetType() == TyUint32));
}
}
// JMP $fallthrough
InsertBranch(Js::OpCode::Br, labelFallThru, ldElem);
emittedFastPath = true;
if (PHASE_TRACE(Js::TypedArrayTypeSpecPhase, this->m_func) && PHASE_TRACE(Js::LowererPhase, this->m_func))
{
char baseValueTypeStr[VALUE_TYPE_MAX_STRING_SIZE];
baseValueType.ToString(baseValueTypeStr);
char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
Output::Print(_u("Typed Array Lowering: function: %s (%s), instr: %s, base value type: %S, %s."),
this->m_func->GetJITFunctionBody()->GetDisplayName(),
this->m_func->GetDebugNumberSet(debugStringBuffer),
Js::OpCodeUtil::GetOpCodeName(ldElem->m_opcode),
baseValueTypeStr,
(!dst->IsVar() ? _u("specialized") : _u("not specialized")));
Output::Print(_u("\n"));
Output::Flush();
}
}
else
{
// MOV dst, indirOpnd
InsertMove(dst, indirOpnd, ldElem);
if (maskOpnd)
{
#if TARGET_64
if (maskOpnd->GetSize() != dst->GetType())
{
maskOpnd = maskOpnd->UseWithNewType(dst->GetType(), m_func)->AsRegOpnd();
}
#endif
InsertAnd(dst, dst, maskOpnd, ldElem);
}
// The string index fast path does not operate on index properties (we don't get a PropertyString in that case), so
// we don't need to do any further checks in that case
// For LdMethodElem, if the loaded value is a tagged number, the error message generated by the helper call is
// better than if we were to just try to call the number. Also, the call arguments need to be evaluated before
// throwing the error, so just test whether it's an object and jump to helper if it's not.
const bool needObjectTest = !isStringIndex && !isNativeArrayLoad && ldElem->m_opcode == Js::OpCode::LdMethodElem;
needMissingValueCheck =
!isStringIndex && !(baseValueType.IsArrayOrObjectWithArray() && baseValueType.HasNoMissingValues());
if(needMissingValueCheck)
{
// TEST dst, dst
// JEQ $helper | JNE $fallthrough
InsertMissingItemCompareBranch(
dst,
needObjectTest ? Js::OpCode::BrEq_A : Js::OpCode::BrNeq_A,
needObjectTest ? labelHelper : labelFallThru,
ldElem);
if (isNativeArrayLoad)
{
Assert(!needObjectTest);
Assert(labelHelper != labelBailOut);
if(ldElem->AsProfiledInstr()->u.ldElemInfo->GetElementType().HasBeenUndefined())
{
// We're going to bail out trying to load "missing value" into a type-spec'd opnd.
// Branch to a point where we'll convert the array so that we don't keep bailing here.
// (Gappy arrays are not well-suited to nativeness.)
labelMissingNative = IR::LabelInstr::New(Js::OpCode::Label, m_func, true);
InsertBranch(Js::OpCode::Br, labelMissingNative, ldElem);
}
else
{
// If the value has not been profiled to be undefined at some point, jump directly to bail out
InsertBranch(Js::OpCode::Br, labelBailOut, ldElem);
}
}
}
if(needObjectTest)
{
// GenerateObjectTest(dst)
// JIsObject $fallthrough
m_lowererMD.GenerateObjectTest(dst, ldElem, labelFallThru, true);
}
else if(!needMissingValueCheck)
{
// JMP $fallthrough
InsertBranch(Js::OpCode::Br, labelFallThru, ldElem);
}
emittedFastPath = true;
}
}
// $helper:
// bailout or caller generated helper call
// $fallthru:
if (!emittedFastPath)
{
labelHelper->isOpHelper = false;
}
ldElem->InsertBefore(labelHelper);
instrIsInHelperBlock = true;
if (isNativeArrayLoad)
{
Assert(ldElem->HasBailOutInfo());
Assert(labelHelper != labelBailOut);
// Transform the original instr:
//
// $helper:
// dst = LdElemI_A src (BailOut)
// $fallthrough:
//
// to:
//
// b $fallthru <--- we get here if we loaded a valid element directly
// $helper:
// dst = LdElemI_A src
// cmp dst, MissingItem
// bne $fallthrough
// $bailout:
// BailOut
// $fallthrough:
LowerOneBailOutKind(ldElem, IR::BailOutConventionalNativeArrayAccessOnly, instrIsInHelperBlock);
IR::Instr *const insertBeforeInstr = ldElem->m_next;
// Do missing value check on value returned from helper so that we don't have to check the index against
// array length. (We already checked it above against the segment length.)
bool hasBeenUndefined = ldElem->AsProfiledInstr()->u.ldElemInfo->GetElementType().HasBeenUndefined();
if (hasBeenUndefined)
{
if(!emitBailout)
{
if (labelMissingNative == nullptr)
{
labelMissingNative = IR::LabelInstr::New(Js::OpCode::Label, m_func, true);
#if DBG
labelMissingNative->m_noLazyHelperAssert = true;
#endif
}
InsertMissingItemCompareBranch(ldElem->GetDst(), Js::OpCode::BrEq_A, labelMissingNative, insertBeforeInstr);
}
InsertBranch(Js::OpCode::Br, labelFallThru, insertBeforeInstr);
if(labelMissingNative)
{
// We're going to bail out on a load from a gap, but convert the array to Var first, so we don't just
// bail here over and over. Gappy arrays are not well suited to nativeness.
// NOTE: only emit this call if the profile tells us that this has happened before ("hasBeenUndefined").
// Emitting this in Navier-Stokes brutalizes the score.
insertBeforeInstr->InsertBefore(labelMissingNative);
IR::JnHelperMethod helperMethod;
indirOpnd = ldElem->GetSrc1()->AsIndirOpnd();
if (indirOpnd->GetBaseOpnd()->GetValueType().HasIntElements())
{
helperMethod = IR::HelperIntArr_ToVarArray;
}
else
{
Assert(indirOpnd->GetBaseOpnd()->GetValueType().HasFloatElements());
helperMethod = IR::HelperFloatArr_ToVarArray;
}
m_lowererMD.LoadHelperArgument(insertBeforeInstr, indirOpnd->GetBaseOpnd());
IR::Instr *instrHelper = IR::Instr::New(Js::OpCode::Call, m_func);
instrHelper->SetSrc1(IR::HelperCallOpnd::New(helperMethod, m_func));
insertBeforeInstr->InsertBefore(instrHelper);
m_lowererMD.LowerCall(instrHelper, 0);
}
}
else
{
if(!emitBailout)
{
InsertMissingItemCompareBranch(ldElem->GetDst(), Js::OpCode::BrEq_A, labelBailOut, insertBeforeInstr);
}
InsertBranch(Js::OpCode::Br, labelFallThru, insertBeforeInstr);
}
insertBeforeInstr->InsertBefore(labelBailOut);
}
if (emitBailout)
{
ldElem->UnlinkSrc1();
ldElem->UnlinkDst();
GenerateBailOut(ldElem, nullptr, nullptr);
}
return !emitBailout;
}
IR::Opnd *
Lowerer::GetMissingItemOpnd(IRType type, Func *func)
{
if (type == TyVar)
{
return IR::AddrOpnd::New(Js::JavascriptArray::MissingItem, IR::AddrOpndKindConstantAddress, func, true);
}
if (type == TyInt32)
{
return IR::IntConstOpnd::New(Js::JavascriptNativeIntArray::MissingItem, TyInt32, func, true);
}
AssertMsg(false, "Only expecting TyVar and TyInt32 in Lowerer::GetMissingItemOpnd");
__assume(false);
}
IR::Opnd*
Lowerer::GetMissingItemOpndForAssignment(IRType type, Func *func)
{
switch (type)
{
case TyVar:
case TyInt32:
return GetMissingItemOpnd(type, func);
case TyFloat64:
return IR::MemRefOpnd::New(func->GetThreadContextInfo()->GetNativeFloatArrayMissingItemAddr(), TyFloat64, func);
default:
AnalysisAssertMsg(false, "Unexpected type in Lowerer::GetMissingItemOpndForAssignment");
__assume(false);
}
}
IR::Opnd *
Lowerer::GetMissingItemOpndForCompare(IRType type, Func *func)
{
switch (type)
{
case TyVar:
case TyInt32:
return GetMissingItemOpnd(type, func);
case TyFloat64:
#if TARGET_64
return IR::MemRefOpnd::New(func->GetThreadContextInfo()->GetNativeFloatArrayMissingItemAddr(), TyUint64, func);
#else
return IR::MemRefOpnd::New(func->GetThreadContextInfo()->GetNativeFloatArrayMissingItemAddr(), TyUint32, func);
#endif
default:
AnalysisAssertMsg(false, "Unexpected type in Lowerer::GetMissingItemOpndForCompare");
__assume(false);
}
}
bool
Lowerer::GenerateFastStElemI(IR::Instr *& stElem, bool *instrIsInHelperBlockRef)
{
Assert(instrIsInHelperBlockRef);
bool &instrIsInHelperBlock = *instrIsInHelperBlockRef;
instrIsInHelperBlock = false;
IR::LabelInstr * labelHelper;
IR::LabelInstr * labelSegmentLengthIncreased;
IR::LabelInstr * labelFallThru;
IR::LabelInstr * labelBailOut = nullptr;
IR::Opnd *dst = stElem->GetDst();
IR::IndirOpnd * indirOpnd = dst->AsIndirOpnd();
AssertMsg(dst->IsIndirOpnd(), "Expected indirOpnd on StElementI");
// From FastElemICommon:
// TEST base, AtomTag -- check base not tagged int
// JNE $helper
// MOV r1, [base + offset(type)] -- check base isArray
// CMP [r1 + offset(typeId)], TypeIds_Array
// JNE $helper
// TEST index, 1 -- index tagged int
// JEQ $helper
// MOV r2, index
// SAR r2, Js::VarTag_Shift -- remove atom tag
// JS $helper -- exclude negative index
// MOV r4, [base + offset(head)]
// CMP r2, [r4 + offset(length)] -- bounds check
// JB $done
// CMP r2, [r4 + offset(size)] -- chunk has room?
// JAE $helper
// LEA r5, [r2 + 1]
// MOV [r4 + offset(length)], r5 -- update length on chunk
// CMP r5, [base + offset(length)]
// JBE $done
// MOV [base + offset(length)], r5 -- update length on array
// $done
// LEA r3, [r4 + offset(elements)]
// Generated here.
// MOV [r3 + r2], src
labelFallThru = stElem->GetOrCreateContinueLabel();
labelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true);
bool emitBailout = false;
bool isNativeArrayStore = indirOpnd->GetBaseOpnd()->GetValueType().IsLikelyNativeArray();
IR::LabelInstr * labelCantUseArray = labelHelper;
if (isNativeArrayStore)
{
if (stElem->GetSrc1()->GetType() != GetArrayIndirType(indirOpnd->GetBaseOpnd()->GetValueType()))
{
// Skip the fast path and just generate a helper call
return true;
}
if(stElem->HasBailOutInfo())
{
const IR::BailOutKind bailOutKind = stElem->GetBailOutKind();
if (bailOutKind & IR::BailOutConventionalNativeArrayAccessOnly)
{
labelBailOut = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true);
labelCantUseArray = labelBailOut;
}
}
}
Js::FldInfoFlags flags = Js::FldInfo_NoInfo;
if (stElem->IsProfiledInstr())
{
flags = stElem->AsProfiledInstr()->u.stElemInfo->flags;
}
bool isTypedArrayElement, isStringIndex, indirOpndOverflowed = false;
IR::Opnd* maskOpnd = nullptr;
indirOpnd =
GenerateFastElemICommon(
stElem,
true,
indirOpnd,
labelHelper,
labelCantUseArray,
labelFallThru,
&isTypedArrayElement,
&isStringIndex,
&emitBailout,
&maskOpnd,
&labelSegmentLengthIncreased,
true, /* checkArrayLengthOverflow */
false, /* forceGenerateFastPath */
false, /* returnLength */
nullptr, /* bailOutLabelInstr */
&indirOpndOverflowed,
flags);
IR::Opnd *src = stElem->GetSrc1();
const IR::AutoReuseOpnd autoReuseSrc(src, m_func);
// The index is negative or not int.
if (indirOpnd == nullptr)
{
Assert(!(stElem->HasBailOutInfo() && stElem->GetBailOutKind() & IR::BailOutOnArrayAccessHelperCall) || indirOpndOverflowed);
if (indirOpndOverflowed && stElem->HasBailOutInfo())
{
bool emittedFastPath = false;
const IR::BailOutKind bailOutKind = stElem->GetBailOutKind();
// ignore StElemI in case of indirOpnd overflow only for typed array which is consistent with behavior of interpreter
if ((bailOutKind & ~IR::BailOutKindBits) == IR::BailOutConventionalTypedArrayAccessOnly)
{
stElem->FreeSrc1();
stElem->FreeDst();
stElem->Remove();
emittedFastPath = true;
}
if (!emittedFastPath && (bailOutKind & (IR::BailOutConventionalNativeArrayAccessOnly | IR::BailOutOnArrayAccessHelperCall)))
{
stElem->FreeSrc1();
stElem->FreeDst();
GenerateBailOut(stElem, nullptr, nullptr);
emittedFastPath = true;
}
return !emittedFastPath;
}
// The global optimizer should never type specialize a StElem for which we know the index is not int or is a negative
// int constant. This would result in an unconditional bailout on the main code path.
else if (src->IsVar())
{
if (PHASE_TRACE(Js::TypedArrayTypeSpecPhase, this->m_func) && PHASE_TRACE(Js::LowererPhase, this->m_func))
{
char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
Output::Print(_u("Typed Array Lowering: function: %s (%s): instr %s, not specialized by glob opt due to negative or not likely int index.\n"),
this->m_func->GetJITFunctionBody()->GetDisplayName(),
this->m_func->GetDebugNumberSet(debugStringBuffer),
Js::OpCodeUtil::GetOpCodeName(stElem->m_opcode));
Output::Flush();
}
// We must be dealing with some atypical index value. Don't emit fast path, but go directly to helper.
return true;
}
else
{
// If global optimizer failed to notice the unconventional index and type specialized the src,
// there is nothing to do but bail out. We should never hit this code path, unless the global optimizer's conditions
// for not specializing the instruction don't match the lowerer's conditions for not emitting the array checks (see above).
// This could happen if global optimizer's information based on value tracking fails to recognize a non-integer index or
// a constant int index that is negative. The bailout below ensures that we behave correctly in retail builds even under
// these (unlikely) conditions.
AssertMsg(false, "Global optimizer shouldn't have specialized this instruction.");
stElem->FreeSrc1();
stElem->FreeDst();
GenerateBailOut(stElem, nullptr, nullptr);
return false;
}
}
const IR::AutoReuseOpnd autoReuseIndirOpnd(indirOpnd, m_func);
const ValueType baseValueType(dst->AsIndirOpnd()->GetBaseOpnd()->GetValueType());
if (isTypedArrayElement)
{
if (PHASE_TRACE(Js::TypedArrayTypeSpecPhase, this->m_func) && PHASE_TRACE(Js::LowererPhase, this->m_func))
{
char baseValueTypeStr[VALUE_TYPE_MAX_STRING_SIZE];
baseValueType.ToString(baseValueTypeStr);
char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
Output::Print(_u("Typed Array Lowering: function: %s (%s), instr: %s, base value type: %S, %s."),
this->m_func->GetJITFunctionBody()->GetDisplayName(),
this->m_func->GetDebugNumberSet(debugStringBuffer),
Js::OpCodeUtil::GetOpCodeName(stElem->m_opcode),
baseValueTypeStr,
(!src->IsVar() ? _u("specialized") : _u("not specialized")));
Output::Print(_u("\n"));
Output::Flush();
}
ObjectType objectType = baseValueType.GetObjectType();
if(indirOpnd->IsFloat())
{
if (src->GetType() == TyFloat64)
{
IR::RegOpnd *const regSrc = src->AsRegOpnd();
if (indirOpnd->IsFloat32())
{
// CVTSD2SS reg.f32, regSrc.f64 -- Convert regSrc from f64 to f32
IR::RegOpnd *const reg = IR::RegOpnd::New(TyFloat32, this->m_func);
const IR::AutoReuseOpnd autoReuseReg(reg, m_func);
InsertConvertFloat64ToFloat32(reg, regSrc, stElem);
// MOVSS indirOpnd, reg
InsertMove(indirOpnd, reg, stElem, false);
}
else
{
// MOVSD indirOpnd, regSrc
InsertMove(indirOpnd, regSrc, stElem, false);
}
emitBailout = true;
}
else
{
Assert(src->GetType() == TyVar);
// MOV reg, src
IR::RegOpnd *const reg = IR::RegOpnd::New(TyVar, this->m_func);
const IR::AutoReuseOpnd autoReuseReg(reg, m_func);
InsertMove(reg, src, stElem);
// Convert to float, and assign to indirOpnd
if (baseValueType.IsLikelyOptimizedVirtualTypedArray())
{
IR::RegOpnd* dstReg = IR::RegOpnd::New(indirOpnd->GetType(), this->m_func);
m_lowererMD.EmitLoadFloat(dstReg, reg, stElem, stElem, labelHelper);
InsertMove(indirOpnd, dstReg, stElem);
}
else
{
m_lowererMD.EmitLoadFloat(indirOpnd, reg, stElem, stElem, labelHelper);
}
}
}
else if (objectType == ObjectType::Uint8ClampedArray || objectType == ObjectType::Uint8ClampedVirtualArray || objectType == ObjectType::Uint8ClampedMixedArray)
{
Assert(indirOpnd->GetType() == TyUint8);
IR::RegOpnd *regSrc;
IR::AutoReuseOpnd autoReuseRegSrc;
if(src->IsRegOpnd())
{
regSrc = src->AsRegOpnd();
}
else
{
regSrc = IR::RegOpnd::New(StackSym::New(src->GetType(), m_func), src->GetType(), m_func);
autoReuseRegSrc.Initialize(regSrc, m_func);
InsertMove(regSrc, src, stElem);
}
IR::Opnd *bitMaskOpnd;
IRType srcType = regSrc->GetType();
if ((srcType == TyFloat64) || (srcType == TyInt32))
{
// if (srcType == TyInt32) {
// TEST regSrc, ~255
// JE $storeValue
// JSB $handleNegative
// MOV indirOpnd, 255
// JMP $fallThru
// $handleNegative [isHelper = false]
// MOV indirOpnd, 0
// JMP $fallThru
// $storeValue
// MOV indirOpnd, regSrc
// }
// else {
// MOVSD regTmp, regSrc
// ADDSD regTmp, 0.5
// CVTTSD2SI regOpnd, regTmp
// TEST regOpnd, ~255
// JE $storeValue
// $handleOutOfBounds [isHelper = true]
// COMISD regSrc, [&FloatZero]
// JB $handleNegative
// MOV regOpnd, 255
// JMP $storeValue
// $handleNegative [isHelper = true]
// MOV regOpnd, 0
// $storeValue
// MOV indirOpnd, regOpnd
// }
// $fallThru
IR::RegOpnd *regOpnd;
IR::AutoReuseOpnd autoReuseRegOpnd;
if (srcType == TyInt32)
{
// When srcType == TyInt32 we will never call the helper and we will never
// modify the regOpnd. Therefore, it's okay to use regSrc directly, and it
// reduces register pressure.
regOpnd = regSrc;
}
else
{
#ifdef _M_IX86
AssertMsg(AutoSystemInfo::Data.SSE2Available(), "GlobOpt shouldn't have specialized Uint8ClampedArray StElem to float64 if SSE2 is unavailable.");
#endif
regOpnd = IR::RegOpnd::New(TyInt32, this->m_func);
autoReuseRegOpnd.Initialize(regOpnd, m_func);
Assert(objectType == ObjectType::Uint8ClampedArray || objectType == ObjectType::Uint8ClampedVirtualArray || objectType == ObjectType::Uint8ClampedMixedArray);
// Uint8ClampedArray follows IEEE 754 rounding rules for ties which round up
// odd integers and round down even integers. Both ties result in the nearest
// even integer value.
//
// CVTSD2SI regOpnd, regSrc
LowererMD::InsertConvertFloat64ToInt32(RoundModeHalfToEven, regOpnd, regSrc, stElem);
}
IR::LabelInstr *labelStoreValue = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, false);
#ifndef _M_ARM
// TEST regOpnd, ~255
// JE $storeValue
bitMaskOpnd = IR::IntConstOpnd::New(~255, TyInt32, this->m_func, true);
InsertTestBranch(regOpnd, bitMaskOpnd, Js::OpCode::BrEq_A, labelStoreValue, stElem);
#else // ARM
// Special case for ARM, a shift may be better
//
// ASRS tempReg, src, 8
// BEQ $inlineSet
InsertShiftBranch(
Js::OpCode::Shr_A,
IR::RegOpnd::New(TyInt32, this->m_func),
regOpnd,
IR::IntConstOpnd::New(8, TyInt8, this->m_func),
Js::OpCode::BrEq_A,
labelStoreValue,
stElem);
#endif
IR::LabelInstr *labelHandleNegative = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, srcType == TyFloat64);
if (srcType == TyInt32)
{
// JSB $handleNegativeOrOverflow
InsertBranch(
LowererMD::MDCompareWithZeroBranchOpcode(Js::OpCode::BrLt_A),
labelHandleNegative,
stElem);
// MOV IndirOpnd.u8, 255
InsertMove(indirOpnd, IR::IntConstOpnd::New(255, TyUint8, this->m_func, true), stElem);
// JMP $fallThru
InsertBranch(Js::OpCode::Br, labelFallThru, stElem);
// $handleNegative [isHelper = false]
stElem->InsertBefore(labelHandleNegative);
// MOV IndirOpnd.u8, 0
InsertMove(indirOpnd, IR::IntConstOpnd::New(0, TyUint8, this->m_func, true), stElem);
// JMP $fallThru
InsertBranch(Js::OpCode::Br, labelFallThru, stElem);
}
else
{
Assert(regOpnd != regSrc);
// This label is just to ensure the following code is moved to the helper block.
// $handleOutOfBounds [isHelper = true]
IR::LabelInstr *labelHandleOutOfBounds = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true);
stElem->InsertBefore(labelHandleOutOfBounds);
// COMISD regSrc, FloatZero
// JB labelHandleNegative
IR::MemRefOpnd * zeroOpnd = IR::MemRefOpnd::New(this->m_func->GetThreadContextInfo()->GetDoubleZeroAddr(), TyMachDouble, this->m_func);
InsertCompareBranch(regSrc, zeroOpnd, Js::OpCode::BrNotGe_A, labelHandleNegative, stElem);
// MOV regOpnd, 255
InsertMove(regOpnd, IR::IntConstOpnd::New(255, TyUint8, this->m_func, true), stElem);
// JMP $storeValue
InsertBranch(Js::OpCode::Br, labelStoreValue, stElem);
// $handleNegative [isHelper = true]
stElem->InsertBefore(labelHandleNegative);
// MOV regOpnd, 0
InsertMove(regOpnd, IR::IntConstOpnd::New(0, TyUint8, this->m_func, true), stElem);
}
// $storeValue
stElem->InsertBefore(labelStoreValue);
// MOV IndirOpnd.u8, regOpnd.u8
InsertMove(indirOpnd, regOpnd, stElem);
emitBailout = true;
}
else
{
Assert(srcType == TyVar);
#if INT32VAR
bitMaskOpnd = IR::AddrOpnd::New((Js::Var)~(INT_PTR)(Js::TaggedInt::ToVarUnchecked(255)), IR::AddrOpndKindConstantVar, this->m_func, true);
#else
bitMaskOpnd = IR::IntConstOpnd::New(~(INT_PTR)(Js::TaggedInt::ToVarUnchecked(255)), TyMachReg, this->m_func, true);
#endif
// Note: We are assuming that if no bits other than ~(TaggedInt(255)) are 1, that we have a tagged
// int value between 0 - 255.
// #if INT32VAR
// This works for pointers because tagged int bit can't be on, and first 64k are not valid addresses
// This works for floats because a valid float would have one of the upper 13 bits on.
// #else
// Any pointer is larger than 512 because first 64k memory is reserved by the OS
// #endif
IR::LabelInstr *labelInlineSet = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
#ifndef _M_ARM
// TEST src, ~(TaggedInt(255)) -- Check for tagged int >= 255 and <= 0
// JEQ $inlineSet
InsertTestBranch(regSrc, bitMaskOpnd, Js::OpCode::BrEq_A, labelInlineSet, stElem);
#else // ARM
// Special case for ARM, a shift may be better
//
// ASRS tempReg, src, 8
// BEQ $inlineSet
InsertShiftBranch(
Js::OpCode::Shr_A,
IR::RegOpnd::New(TyInt32, this->m_func),
regSrc,
IR::IntConstOpnd::New(8, TyInt8, this->m_func),
Js::OpCode::BrEq_A,
labelInlineSet,
stElem);
#endif
// Uint8ClampedArray::DirectSetItem(array, index, value);
// Inserting a helper call. Make sure it observes the main instructions's requirements regarding implicit calls.
if (!instrIsInHelperBlock)
{
stElem->InsertBefore(IR::LabelInstr::New(Js::OpCode::Label, m_func, true));
}
if (stElem->HasBailOutInfo() && (stElem->GetBailOutKind() & IR::BailOutOnArrayAccessHelperCall))
{
// Bail out instead of doing the helper call.
Assert(labelHelper);
this->InsertBranch(Js::OpCode::Br, labelHelper, stElem);
}
else
{
IR::Instr *instr = IR::Instr::New(Js::OpCode::Call, this->m_func);
stElem->InsertBefore(instr);
if (stElem->HasBailOutInfo() && BailOutInfo::IsBailOutOnImplicitCalls(stElem->GetBailOutKind()))
{
// Bail out if this helper triggers implicit calls.
instr = this->AddBailoutToHelperCallInstr(instr, stElem->GetBailOutInfo(), stElem->GetBailOutKind(), stElem);
}
m_lowererMD.LoadHelperArgument(instr, regSrc);
IR::Opnd *indexOpnd = indirOpnd->GetIndexOpnd();
if (indexOpnd == nullptr)
{
if (indirOpnd->GetOffset() == 0)
{
// There are two ways that we can get an indirOpnd with no index and 0 offset.
// The first is that we're storing to element 0 in the array by constant offset.
// The second is that we got a pointer back that has spectre masking, so it's going
// to not have the appropriate index into the array. In that case, we need to regen
// the index.
// The plan is
// 1. get the backing buffer pointer
// 2. subtract that from the indexOpnd to get the numeric index
// This is unfortunately slightly worse perf for constant writes of vars to index 0
// of Uint8ClampedArrays, but that's hopefully uncommon enough that the impact will
// be minimal
// MOV backingBufferOpnd, [base + offset(arrayBuffer)]
// SUB indexOpnd, backingBufferOpnd
int bufferOffset = GetArrayOffsetOfHeadSegment(baseValueType);
IR::IndirOpnd* arrayBufferOpnd = IR::IndirOpnd::New(stElem->GetDst()->AsIndirOpnd()->GetBaseOpnd(), bufferOffset, TyMachPtr, this->m_func);
IR::RegOpnd* backingBufferOpnd = IR::RegOpnd::New(TyMachPtr, this->m_func);
InsertMove(backingBufferOpnd, arrayBufferOpnd, instr);
IR::RegOpnd* tempIndexOpnd = IR::RegOpnd::New(TyMachPtr, this->m_func);
InsertSub(false, tempIndexOpnd, indirOpnd->GetBaseOpnd(), backingBufferOpnd, instr);
indexOpnd = tempIndexOpnd->UseWithNewType(TyInt32, this->m_func);
}
else
{
indexOpnd = IR::IntConstOpnd::New(indirOpnd->GetOffset(), TyInt32, this->m_func);
}
}
else
{
Assert(indirOpnd->GetOffset() == 0);
}
m_lowererMD.LoadHelperArgument(instr, indexOpnd);
m_lowererMD.LoadHelperArgument(instr, stElem->GetDst()->AsIndirOpnd()->GetBaseOpnd());
Assert(objectType == ObjectType::Uint8ClampedArray || objectType == ObjectType::Uint8ClampedMixedArray || objectType == ObjectType::Uint8ClampedVirtualArray);
m_lowererMD.ChangeToHelperCall(instr, IR::JnHelperMethod::HelperUint8ClampedArraySetItem);
// JMP $fallThrough
InsertBranch(Js::OpCode::Br, labelFallThru, stElem);
}
//$inlineSet
stElem->InsertBefore(labelInlineSet);
IR::RegOpnd *regOpnd;
IR::AutoReuseOpnd autoReuseRegOpnd;
#if INT32VAR
regOpnd = regSrc;
#else
// MOV r1, src
// SAR r1, 1
regOpnd = IR::RegOpnd::New(TyInt32, this->m_func);
autoReuseRegOpnd.Initialize(regOpnd, m_func);
InsertShift(
Js::OpCode::Shr_A,
false /* needFlags */,
regOpnd,
regSrc,
IR::IntConstOpnd::New(1, TyInt8, this->m_func),
stElem);
#endif
// MOV IndirOpnd.u8, reg.u8
InsertMove(indirOpnd, regOpnd, stElem);
}
}
else
{
if (src->IsInt32())
{
// MOV indirOpnd, src
InsertMove(indirOpnd, src, stElem);
emitBailout = true;
}
else if (src->IsFloat64())
{
AssertMsg(indirOpnd->GetType() == TyUint32, "Only StElemI to Uint32Array could be specialized to float64.");
#ifdef _M_IX86
AssertMsg(AutoSystemInfo::Data.SSE2Available(), "GloOpt shouldn't have specialized Uint32Array StElemI to float64 if SSE2 is unavailable.");
#endif
bool bailOutOnHelperCall = stElem->HasBailOutInfo() ? !!(stElem->GetBailOutKind() & IR::BailOutOnArrayAccessHelperCall) : false;
if (bailOutOnHelperCall)
{
if(!GlobOpt::DoEliminateArrayAccessHelperCall(this->m_func))
{
// Array access helper call removal is already off for some reason. Prevent trying to rejit again
// because it won't help and the same thing will happen again. Just abort jitting this function.
if(PHASE_TRACE(Js::BailOutPhase, this->m_func))
{
Output::Print(_u(" Aborting JIT because EliminateArrayAccessHelperCall is already off\n"));
Output::Flush();
}
throw Js::OperationAbortedException();
}
throw Js::RejitException(RejitReason::ArrayAccessHelperCallEliminationDisabled);
}
IR::RegOpnd *const reg = IR::RegOpnd::New(TyInt32, this->m_func);
const IR::AutoReuseOpnd autoReuseReg(reg, m_func);
m_lowererMD.EmitFloatToInt(reg, src, stElem, stElem, labelHelper);
// MOV indirOpnd, reg
InsertMove(indirOpnd, reg, stElem);
emitBailout = true;
}
else
{
Assert(src->IsVar());
if(src->IsAddrOpnd())
{
IR::AddrOpnd *const addrSrc = src->AsAddrOpnd();
Assert(addrSrc->IsVar());
Assert(Js::TaggedInt::Is(addrSrc->m_address));
// MOV indirOpnd, intValue
InsertMove(
indirOpnd,
IR::IntConstOpnd::New(Js::TaggedInt::ToInt32(addrSrc->m_address), TyInt32, m_func),
stElem);
}
else
{
IR::RegOpnd *const regSrc = src->AsRegOpnd();
// FromVar reg, Src
IR::RegOpnd *const reg = IR::RegOpnd::New(TyInt32, this->m_func);
const IR::AutoReuseOpnd autoReuseReg(reg, m_func);
IR::Instr * instr = IR::Instr::New(Js::OpCode::FromVar, reg, regSrc, stElem->m_func);
stElem->InsertBefore(instr);
// Convert reg to int32
// Note: ToUint32 is implemented as (uint32)ToInt32()
IR::BailOutKind bailOutKind = stElem->HasBailOutInfo() ? stElem->GetBailOutKind() : IR::BailOutInvalid;
if (BailOutInfo::IsBailOutOnImplicitCalls(bailOutKind))
{
instr = this->AddBailoutToHelperCallInstr(instr, stElem->GetBailOutInfo(), bailOutKind, stElem);
}
bool bailOutOnHelperCall = !!(bailOutKind & IR::BailOutOnArrayAccessHelperCall);
m_lowererMD.EmitLoadInt32(instr, true /*conversionFromObjectAllowed*/, bailOutOnHelperCall, labelHelper);
// MOV indirOpnd, reg
InsertMove(indirOpnd, reg, stElem);
}
}
}
}
else
{
if(labelSegmentLengthIncreased)
{
IR::Instr *const insertBeforeInstr = labelSegmentLengthIncreased->m_next;
// We might be changing the array to have missing values here, or we might be
// changing it to extend it; in either case, we're not going to make it _not_
// have missing values after this operation, so just write and fallthrough.
// labelSegmentLengthIncreased:
// mov [segment + index], src
// jmp $fallThru
InsertMove(indirOpnd, src, insertBeforeInstr);
InsertBranch(Js::OpCode::Br, labelFallThru, insertBeforeInstr);
}
if (!(isStringIndex || (baseValueType.IsArrayOrObjectWithArray() && baseValueType.HasNoMissingValues())))
{
if(!stElem->IsProfiledInstr() || stElem->AsProfiledInstr()->u.stElemInfo->LikelyFillsMissingValue())
{
// Check whether the store is filling a missing value. If so, fall back to the helper so that it can check whether
// this store is filling the last missing value in the array. This is necessary to keep the missing value tracking
// in arrays precise. The check is omitted when profile data says that the store is likely to create missing values.
//
// cmp [segment + index], Js::SparseArraySegment::MissingValue
// je $helper
InsertMissingItemCompareBranch(
indirOpnd,
Js::OpCode::BrEq_A,
labelHelper,
stElem);
}
else
{
GenerateIsEnabledArraySetElementFastPathCheck(labelHelper, stElem);
}
}
// MOV [r3 + r2], src
InsertMoveWithBarrier(indirOpnd, src, stElem);
}
// JMP $fallThru
InsertBranch(Js::OpCode::Br, labelFallThru, stElem);
// $helper:
// bailout or caller generated helper call
// $fallThru:
stElem->InsertBefore(labelHelper);
instrIsInHelperBlock = true;
if (isNativeArrayStore && !isStringIndex)
{
Assert(stElem->HasBailOutInfo());
Assert(labelHelper != labelBailOut);
// Transform the original instr:
//
// $helper:
// dst = LdElemI_A src (BailOut)
// $fallthrough:
//
// to:
//
// $helper:
// dst = LdElemI_A src
// b $fallthrough
// $bailout:
// BailOut
// $fallthrough:
LowerOneBailOutKind(stElem, IR::BailOutConventionalNativeArrayAccessOnly, instrIsInHelperBlock);
IR::Instr *const insertBeforeInstr = stElem->m_next;
InsertBranch(Js::OpCode::Br, labelFallThru, insertBeforeInstr);
insertBeforeInstr->InsertBefore(labelBailOut);
}
if (emitBailout)
{
stElem->FreeSrc1();
stElem->FreeDst();
GenerateBailOut(stElem, nullptr, nullptr);
}
return !emitBailout;
}
bool
Lowerer::GenerateFastLdLen(IR::Instr *ldLen, bool *instrIsInHelperBlockRef)
{
Assert(instrIsInHelperBlockRef);
bool &instrIsInHelperBlock = *instrIsInHelperBlockRef;
instrIsInHelperBlock = false;
// TEST src, AtomTag -- check src not tagged int
// JNE $helper
// CMP [src], JavascriptArray::`vtable' -- check base isArray
// JNE $string
// MOV length, [src + offset(length)] -- Load array length
// JMP $tovar
// $string:
// CMP [src + offset(type)], static_string_type -- check src isString
// JNE $helper
// MOV length, [src + offset(length)] -- Load string length
// $toVar:
// TEST length, 0xC0000000 -- test for overflow of SHL, or negative
// JNE $helper
// SHL length, Js::VarTag_Shift -- restore the var tag on the result
// INC length
// MOV dst, length
// JMP $fallthru
// $helper:
// CALL GetProperty(src, length_property_id, scriptContext)
// $fallthru:
IR::Opnd * opnd = ldLen->GetSrc1();
IR::RegOpnd * dst = ldLen->GetDst()->AsRegOpnd();
const ValueType srcValueType(opnd->GetValueType());
IR::LabelInstr *const labelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true);
if (ldLen->DoStackArgsOpt())
{
GenerateFastArgumentsLdLen(ldLen, ldLen->GetOrCreateContinueLabel());
ldLen->Remove();
return false;
}
else
{
const bool arrayFastPath = ShouldGenerateArrayFastPath(opnd, false, true, false);
// HasBeenString instead of IsLikelyString because it could be a merge between StringObject and String, and this
// information about whether it's a StringObject or some other object is not available in the profile data
const bool stringFastPath = srcValueType.IsUninitialized() || srcValueType.HasBeenString();
if(!(arrayFastPath || stringFastPath))
{
return true;
}
IR::RegOpnd * src;
if (opnd->IsRegOpnd())
{
src = opnd->AsRegOpnd();
}
else
{
// LdLen has a PropertySymOpnd until globopt where the decision whether to convert it to LdFld is made. If globopt is skipped, the opnd will
// still be a PropertySymOpnd here. In that case, do the conversion here.
IR::SymOpnd * symOpnd = opnd->AsSymOpnd();
PropertySym * propertySym = symOpnd->m_sym->AsPropertySym();
src = IR::RegOpnd::New(propertySym->m_stackSym, IRType::TyVar, this->m_func);
ldLen->ReplaceSrc1(src);
opnd = src;
}
const int32 arrayOffsetOfLength =
srcValueType.IsLikelyAnyOptimizedArray()
? GetArrayOffsetOfLength(srcValueType)
: Js::JavascriptArray::GetOffsetOfLength();
IR::LabelInstr *labelString = nullptr;
IR::RegOpnd *arrayOpnd = src;
IR::RegOpnd *arrayLengthOpnd = nullptr;
IR::AutoReuseOpnd autoReuseArrayLengthOpnd;
if(arrayFastPath)
{
if(!srcValueType.IsAnyOptimizedArray())
{
if(stringFastPath)
{
// If we don't have info about the src value type or its object type, the array and string fast paths are
// generated
labelString = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
}
arrayOpnd = GenerateArrayTest(src, labelHelper, stringFastPath ? labelString : labelHelper, ldLen, false);
}
else if(src->IsArrayRegOpnd())
{
IR::ArrayRegOpnd *const arrayRegOpnd = src->AsArrayRegOpnd();
if(arrayRegOpnd->LengthSym())
{
arrayLengthOpnd = IR::RegOpnd::New(arrayRegOpnd->LengthSym(), TyUint32, m_func);
DebugOnly(arrayLengthOpnd->FreezeSymValue());
autoReuseArrayLengthOpnd.Initialize(arrayLengthOpnd, m_func);
}
}
}
const IR::AutoReuseOpnd autoReuseArrayOpnd(arrayOpnd, m_func);
IR::RegOpnd *lengthOpnd = nullptr;
IR::AutoReuseOpnd autoReuseLengthOpnd;
const auto EnsureLengthOpnd = [&]()
{
if(lengthOpnd)
{
return;
}
lengthOpnd = IR::RegOpnd::New(TyUint32, m_func);
autoReuseLengthOpnd.Initialize(lengthOpnd, m_func);
};
if(arrayFastPath)
{
if(arrayLengthOpnd)
{
lengthOpnd = arrayLengthOpnd;
autoReuseLengthOpnd.Initialize(lengthOpnd, m_func);
Assert(!stringFastPath);
}
else
{
// MOV length, [array + offset(length)] -- Load array length
EnsureLengthOpnd();
IR::IndirOpnd *const indirOpnd = IR::IndirOpnd::New(arrayOpnd, arrayOffsetOfLength, TyUint32, this->m_func);
InsertMove(lengthOpnd, indirOpnd, ldLen);
}
}
if(stringFastPath)
{
IR::LabelInstr *labelToVar = nullptr;
if(arrayFastPath)
{
// JMP $tovar
labelToVar = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
InsertBranch(Js::OpCode::Br, labelToVar, ldLen);
// $string:
ldLen->InsertBefore(labelString);
}
// CMP [src + offset(type)], static_stringtype -- check src isString
// JNE $helper
GenerateStringTest(src, ldLen, labelHelper, nullptr, !arrayFastPath);
// MOV length, [src + offset(length)] -- Load string length
EnsureLengthOpnd();
IR::IndirOpnd * indirOpnd = IR::IndirOpnd::New(src, offsetof(Js::JavascriptString, m_charLength), TyUint32, this->m_func);
InsertMove(lengthOpnd, indirOpnd, ldLen);
if(arrayFastPath)
{
// $toVar:
ldLen->InsertBefore(labelToVar);
}
}
Assert(lengthOpnd);
if(ldLen->HasBailOutInfo() && (ldLen->GetBailOutKind() & ~IR::BailOutKindBits) == IR::BailOutOnIrregularLength)
{
Assert(ldLen->GetBailOutKind() == IR::BailOutOnIrregularLength || ldLen->HasLazyBailOut());
Assert(dst->IsInt32());
// Since the length is an unsigned int32, verify that when interpreted as a signed int32, it is not negative
// test length, length
// js $helper
// mov dst, length
// jmp $fallthrough
InsertCompareBranch(
lengthOpnd,
IR::IntConstOpnd::New(0, lengthOpnd->GetType(), m_func, true),
Js::OpCode::BrLt_A,
labelHelper,
ldLen);
InsertMove(dst, lengthOpnd, ldLen);
InsertBranch(Js::OpCode::Br, ldLen->GetOrCreateContinueLabel(), ldLen);
// $helper:
// (Bail out with IR::BailOutOnIrregularLength)
ldLen->InsertBefore(labelHelper);
instrIsInHelperBlock = true;
ldLen->FreeDst();
ldLen->FreeSrc1();
GenerateBailOut(ldLen);
return false;
}
#if INT32VAR
// Since the length is an unsigned int32, verify that when interpreted as a signed int32, it is not negative
// test length, length
// js $helper
InsertCompareBranch(
lengthOpnd,
IR::IntConstOpnd::New(0, lengthOpnd->GetType(), m_func, true),
Js::OpCode::BrLt_A,
labelHelper,
ldLen);
#else
// Since the length is an unsigned int32, verify that when interpreted as a signed int32, it is not negative.
// Additionally, verify that the signed value's width is not greater than 31 bits, since it needs to be tagged.
// test length, 0xC0000000
// jne $helper
InsertTestBranch(
lengthOpnd,
IR::IntConstOpnd::New(0xC0000000, TyUint32, this->m_func, true),
Js::OpCode::BrNeq_A,
labelHelper,
ldLen);
#endif
#if INT32VAR
//
// dst_32 = MOV length
// dst_64 = OR dst_64, Js::AtomTag_IntPtr
//
Assert(dst->GetType() == TyVar);
IR::Opnd *dst32 = dst->Copy(this->m_func);
dst32->SetType(TyInt32);
// This will clear the top bits.
InsertMove(dst32, lengthOpnd, ldLen);
m_lowererMD.GenerateInt32ToVarConversion(dst, ldLen);
#else
// dst = SHL length, Js::VarTag_Shift -- restore the var tag on the result
InsertShift(
Js::OpCode::Shl_A,
false /* needFlags */,
dst,
lengthOpnd,
IR::IntConstOpnd::New(Js::VarTag_Shift, TyInt8, this->m_func),
ldLen);
// dst = ADD dst, AtomTag
InsertAdd(
false /* needFlags */,
dst,
dst,
IR::IntConstOpnd::New(Js::AtomTag_Int32, TyUint32, m_func, true),
ldLen);
#endif
// JMP $fallthrough
InsertBranch(Js::OpCode::Br, ldLen->GetOrCreateContinueLabel(), ldLen);
}
// $helper:
// (caller generates helper call)
ldLen->InsertBefore(labelHelper);
instrIsInHelperBlock = true;
return true; // fast path was generated, helper call will be in a helper block
}
void
Lowerer::GenerateFastInlineStringCodePointAt(IR::Instr* lastInstr, Func* func, IR::Opnd *strLength, IR::Opnd *srcIndex, IR::RegOpnd *lowerChar, IR::RegOpnd *strPtr)
{
//// Required State:
// strLength - UInt32
// srcIndex - TyVar if not Address
// lowerChar - TyMachReg
// strPtr - Addr
//// Instructions
// CMP [strLength], srcIndex + 1
// JBE charCodeAt
// CMP lowerChar 0xDC00
// JGE charCodeAt
// CMP lowerChar 0xD7FF
// JLE charCodeAt
// upperChar = MOVZX [strPtr + srcIndex + 1]
// CMP upperChar 0xE000
// JGE charCodeAt
// CMP lowerChar 0xDBFF
// JLE charCodeAt
// lowerChar = SUB lowerChar - 0xD800
// lowerChar = SHL lowerChar, 10
// lowerChar = ADD lowerChar + upperChar
// lowerChar = ADD lowerChar + 0x2400
// :charCodeAt
// :done
// Asserts
// Arm should change to Uint32 for the strLength
Assert(strLength->GetType() == TyUint32 || strLength->GetType() == TyMachReg);
Assert(srcIndex->GetType() == TyVar || srcIndex->IsAddrOpnd());
Assert(lowerChar->GetType() == TyMachReg || lowerChar->GetType() == TyUint32);
Assert(strPtr->IsRegOpnd());
IR::RegOpnd *tempReg = IR::RegOpnd::New(TyMachReg, func);
IR::LabelInstr *labelCharCodeAt = IR::LabelInstr::New(Js::OpCode::Label, func);
IR::IndirOpnd *tempIndirOpnd;
if (srcIndex->IsAddrOpnd())
{
uint32 length = Js::TaggedInt::ToUInt32(srcIndex->AsAddrOpnd()->m_address) + 1U;
InsertCompareBranch(strLength, IR::IntConstOpnd::New(length, TyUint32, func), Js::OpCode::BrLe_A, true, labelCharCodeAt, lastInstr);
tempIndirOpnd = IR::IndirOpnd::New(strPtr, (length) * sizeof(char16), TyUint16, func);
}
else
{
InsertMove(tempReg, srcIndex, lastInstr);
#if INT32VAR
IR::Opnd * reg32Bit = tempReg->UseWithNewType(TyInt32, func);
InsertMove(tempReg, reg32Bit, lastInstr);
tempReg = reg32Bit->AsRegOpnd();
#else
InsertShift(Js::OpCode::Shr_A, false, tempReg, tempReg, IR::IntConstOpnd::New(Js::VarTag_Shift, TyInt8, func), lastInstr);
#endif
InsertAdd(false, tempReg, tempReg, IR::IntConstOpnd::New(1, TyInt32, func), lastInstr);
InsertCompareBranch(strLength, tempReg, Js::OpCode::BrLe_A, true, labelCharCodeAt, lastInstr);
if(tempReg->GetSize() != MachPtr)
{
tempReg = tempReg->UseWithNewType(TyMachPtr, func)->AsRegOpnd();
}
tempIndirOpnd = IR::IndirOpnd::New(strPtr, tempReg, 1, TyUint16, func);
}
// By this point, we have added instructions before labelCharCodeAt to check for extra length required for the surrogate pair
// The branching for that is already handled, all we have to do now is to check for correct values.
// Validate char is in range [D800, DBFF]; otherwise just get a charCodeAt
InsertCompareBranch(lowerChar, IR::IntConstOpnd::New(0xDC00, TyUint32, func), Js::OpCode::BrGe_A, labelCharCodeAt, lastInstr);
InsertCompareBranch(lowerChar, IR::IntConstOpnd::New(0xD7FF, TyUint32, func), Js::OpCode::BrLe_A, labelCharCodeAt, lastInstr);
// upperChar = MOVZX r3, [r1 + r3 * 2] -- this is the value of the upper surrogate pair char
IR::RegOpnd *upperChar = IR::RegOpnd::New(TyInt32, func);
InsertMove(upperChar, tempIndirOpnd, lastInstr);
// Validate upper is in range [DC00, DFFF]; otherwise just get a charCodeAt
InsertCompareBranch(upperChar, IR::IntConstOpnd::New(0xE000, TyUint32, func), Js::OpCode::BrGe_A, labelCharCodeAt, lastInstr);
InsertCompareBranch(upperChar, IR::IntConstOpnd::New(0xDBFF, TyUint32, func), Js::OpCode::BrLe_A, labelCharCodeAt, lastInstr);
// (lower - 0xD800) << 10 + second - 0xDC00 + 0x10000 -- 0x10000 - 0xDC00 = 0x2400
// lowerChar = SUB lowerChar - 0xD800
// lowerChar = SHL lowerChar, 10
// lowerChar = ADD lowerChar + upperChar
// lowerChar = ADD lowerChar + 0x2400
InsertSub(false, lowerChar, lowerChar, IR::IntConstOpnd::New(0xD800, TyUint32, func), lastInstr);
InsertShift(Js::OpCode::Shl_A, false, lowerChar, lowerChar, IR::IntConstOpnd::New(10, TyUint32, func), lastInstr);
InsertAdd(false, lowerChar, lowerChar, upperChar, lastInstr);
InsertAdd(false, lowerChar, lowerChar, IR::IntConstOpnd::New(0x2400, TyUint32, func), lastInstr);
lastInstr->InsertBefore(labelCharCodeAt);
}
bool
Lowerer::GenerateFastInlineStringFromCodePoint(IR::Instr* instr)
{
Assert(instr->m_opcode == Js::OpCode::CallDirect);
// ArgOut sequence
// s8.var = StartCall 2 (0x2).i32 #000c
// arg1(s9)<0>.var = ArgOut_A s2.var, s8.var #0014 //Implicit this, String object
// arg2(s10)<4>.var = ArgOut_A s3.var, arg1(s9)<0>.var #0018 //First argument to FromCharCode
// arg1(s11)<0>.u32 = ArgOut_A_InlineSpecialized 0x012C26C0 (DynamicObject).var, arg2(s10)<4>.var #
// s0[LikelyTaggedInt].var = CallDirect String_FromCodePoint.u32, arg1(s11)<0>.u32 #001c
IR::Opnd * linkOpnd = instr->GetSrc2();
IR::Instr * tmpInstr = Inline::GetDefInstr(linkOpnd);// linkOpnd->AsSymOpnd()->m_sym->AsStackSym()->m_instrDef;
linkOpnd = tmpInstr->GetSrc2();
#if DBG
IntConstType argCount = linkOpnd->AsSymOpnd()->m_sym->AsStackSym()->GetArgSlotNum();
Assert(argCount == 2);
#endif
IR::Instr *argInstr = Inline::GetDefInstr(linkOpnd);
Assert(argInstr->m_opcode == Js::OpCode::ArgOut_A);
IR::Opnd *src1 = argInstr->GetSrc1();
if (src1->GetValueType().IsLikelyInt())
{
//Trying to generate this code
// MOV resultOpnd, dst
// MOV fromCharCodeIntArgOpnd, src1
// SAR fromCharCodeIntArgOpnd, Js::VarTag_Shift
// JAE $Helper
// CMP fromCharCodeIntArgOpnd, Js::ScriptContext::CharStringCacheSize
//
// JAE $labelWCharStringCheck <
// MOV resultOpnd, GetCharStringCache[fromCharCodeIntArgOpnd]
// TST resultOpnd, resultOpnd //Check for null
// JEQ $helper
// JMP $Done
//
//$labelWCharStringCheck:
// resultOpnd = Call HelperGetStringForCharW
// JMP $Done
//$helper:
IR::RegOpnd * resultOpnd = nullptr;
if (!instr->GetDst()->IsRegOpnd() || instr->GetDst()->IsEqual(src1))
{
resultOpnd = IR::RegOpnd::New(TyVar, this->m_func);
}
else
{
resultOpnd = instr->GetDst()->AsRegOpnd();
}
IR::LabelInstr *doneLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
IR::LabelInstr *labelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true);
IR::RegOpnd * fromCodePointIntArgOpnd = IR::RegOpnd::New(TyVar, instr->m_func);
IR::AutoReuseOpnd autoReuseFromCodePointIntArgOpnd(fromCodePointIntArgOpnd, instr->m_func);
InsertMove(fromCodePointIntArgOpnd, src1, instr);
//Check for tagged int and get the untagged version.
fromCodePointIntArgOpnd = GenerateUntagVar(fromCodePointIntArgOpnd, labelHelper, instr);
GenerateGetSingleCharString(fromCodePointIntArgOpnd, resultOpnd, labelHelper, doneLabel, instr, true);
instr->InsertBefore(labelHelper);
instr->InsertAfter(doneLabel);
RelocateCallDirectToHelperPath(tmpInstr, labelHelper);
}
return true;
}
bool
Lowerer::GenerateFastInlineStringFromCharCode(IR::Instr* instr)
{
Assert(instr->m_opcode == Js::OpCode::CallDirect);
// ArgOut sequence
// s8.var = StartCall 2 (0x2).i32 #000c
// arg1(s9)<0>.var = ArgOut_A s2.var, s8.var #0014 //Implicit this, String object
// arg2(s10)<4>.var = ArgOut_A s3.var, arg1(s9)<0>.var #0018 //First argument to FromCharCode
// arg1(s11)<0>.u32 = ArgOut_A_InlineSpecialized 0x012C26C0 (DynamicObject).var, arg2(s10)<4>.var #
// s0[LikelyTaggedInt].var = CallDirect String_FromCharCode.u32, arg1(s11)<0>.u32 #001c
IR::Opnd * linkOpnd = instr->GetSrc2();
IR::Instr * tmpInstr = Inline::GetDefInstr(linkOpnd);// linkOpnd->AsSymOpnd()->m_sym->AsStackSym()->m_instrDef;
linkOpnd = tmpInstr->GetSrc2();
#if DBG
IntConstType argCount = linkOpnd->AsSymOpnd()->m_sym->AsStackSym()->GetArgSlotNum();
Assert(argCount == 2);
#endif
IR::Instr *argInstr = Inline::GetDefInstr(linkOpnd);
Assert(argInstr->m_opcode == Js::OpCode::ArgOut_A);
IR::Opnd *src1 = argInstr->GetSrc1();
if (src1->GetValueType().IsLikelyInt())
{
//Trying to generate this code
// MOV resultOpnd, dst
// MOV fromCharCodeIntArgOpnd, src1
// SAR fromCharCodeIntArgOpnd, Js::VarTag_Shift
// JAE $Helper
// CMP fromCharCodeIntArgOpnd, Js::ScriptContext::CharStringCacheSize
//
// JAE $labelWCharStringCheck <
// MOV resultOpnd, GetCharStringCache[fromCharCodeIntArgOpnd]
// TST resultOpnd, resultOpnd //Check for null
// JEQ $helper
// JMP $Done
//
//$labelWCharStringCheck:
// resultOpnd = Call HelperGetStringForCharW
// JMP $Done
//$helper:
IR::RegOpnd * resultOpnd = nullptr;
if (!instr->GetDst()->IsRegOpnd() || instr->GetDst()->IsEqual(src1))
{
resultOpnd = IR::RegOpnd::New(TyVar, this->m_func);
}
else
{
resultOpnd = instr->GetDst()->AsRegOpnd();
}
IR::LabelInstr *labelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true);
IR::RegOpnd * fromCharCodeIntArgOpnd = IR::RegOpnd::New(TyVar, instr->m_func);
IR::AutoReuseOpnd autoReuseFromCharCodeIntArgOpnd(fromCharCodeIntArgOpnd, instr->m_func);
InsertMove(fromCharCodeIntArgOpnd, src1, instr);
//Check for tagged int and get the untagged version.
fromCharCodeIntArgOpnd = GenerateUntagVar(fromCharCodeIntArgOpnd, labelHelper, instr);
IR::LabelInstr *doneLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
GenerateGetSingleCharString(fromCharCodeIntArgOpnd, resultOpnd, labelHelper, doneLabel, instr, false);
instr->InsertBefore(labelHelper);
instr->InsertAfter(doneLabel);
RelocateCallDirectToHelperPath(tmpInstr, labelHelper);
}
return true;
}
void
Lowerer::GenerateGetSingleCharString(IR::RegOpnd * charCodeOpnd, IR::Opnd * resultOpnd, IR::LabelInstr * labelHelper, IR::LabelInstr * doneLabel, IR::Instr * instr, bool isCodePoint)
{
// MOV cacheReg, CharStringCache
// CMP charCodeOpnd, Js::ScriptContext::CharStringCacheSize
// JAE $labelWCharStringCheck <
// MOV resultOpnd, cacheReg[charCodeOpnd]
// TST resultOpnd, resultOpnd //Check for null
// JEQ $helper
// JMP $Done
//
//$labelWCharStringCheck:
// Arg1 = charCodeOpnd
// Arg0 = cacheReg
// resultOpnd = Call HelperGetStringForCharW/CodePoint
// JMP $Done
//$helper:
IR::LabelInstr *labelWCharStringCheck = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
//Try to load from in CharStringCacheA
IR::RegOpnd *cacheRegOpnd = IR::RegOpnd::New(TyVar, instr->m_func);
IR::AutoReuseOpnd autoReuseCacheRegOpnd(cacheRegOpnd, instr->m_func);
Assert(Js::JavascriptLibrary::GetCharStringCacheAOffset() == Js::JavascriptLibrary::GetCharStringCacheOffset());
InsertMove(cacheRegOpnd, this->LoadLibraryValueOpnd(instr, LibraryValue::ValueCharStringCache), instr);
InsertCompareBranch(charCodeOpnd, IR::IntConstOpnd::New(Js::CharStringCache::CharStringCacheSize, TyUint32, this->m_func), Js::OpCode::BrGe_A, true, labelWCharStringCheck, instr);
InsertMove(resultOpnd, IR::IndirOpnd::New(cacheRegOpnd, charCodeOpnd, this->m_lowererMD.GetDefaultIndirScale(), TyVar, instr->m_func), instr);
InsertTestBranch(resultOpnd, resultOpnd, Js::OpCode::BrEq_A, labelHelper, instr);
InsertMove(instr->GetDst(), resultOpnd, instr);
InsertBranch(Js::OpCode::Br, doneLabel, instr);
instr->InsertBefore(labelWCharStringCheck);
IR::JnHelperMethod helperMethod;
if (isCodePoint)
{
helperMethod = IR::HelperGetStringForCharCodePoint;
}
else
{
InsertMove(charCodeOpnd, charCodeOpnd->UseWithNewType(TyUint16, instr->m_func), instr);
helperMethod = IR::HelperGetStringForChar;
}
//Try to load from in CharStringCacheW or CharStringCacheCodePoint, this is a helper call.
this->m_lowererMD.LoadHelperArgument(instr, charCodeOpnd);
this->m_lowererMD.LoadHelperArgument(instr, cacheRegOpnd);
IR::Instr* helperCallInstr = IR::Instr::New(Js::OpCode::Call, resultOpnd, IR::HelperCallOpnd::New(helperMethod, this->m_func), this->m_func);
instr->InsertBefore(helperCallInstr);
this->m_lowererMD.LowerCall(helperCallInstr, 0);
InsertMove(instr->GetDst(), resultOpnd, instr);
InsertBranch(Js::OpCode::Br, doneLabel, instr);
}
bool
Lowerer::GenerateFastInlineGlobalObjectParseInt(IR::Instr *instr)
{
Assert(instr->m_opcode == Js::OpCode::CallDirect);
// ArgOut sequence
// s8.var = StartCall 2 (0x2).i32 #000c
// arg1(s9)<0>.var = ArgOut_A s2.var, s8.var #0014 //Implicit this, global object
// arg2(s10)<4>.var = ArgOut_A s3.var, arg1(s9)<0>.var #0018 //First argument to parseInt
// arg1(s11)<0>.u32 = ArgOut_A_InlineSpecialized 0x012C26C0 (DynamicObject).var, arg2(s10)<4>.var #
// s0[LikelyTaggedInt].var = CallDirect GlobalObject_ParseInt.u32, arg1(s11)<0>.u32 #001c
IR::Opnd * linkOpnd = instr->GetSrc2();
IR::Instr * tmpInstr = Inline::GetDefInstr(linkOpnd);// linkOpnd->AsSymOpnd()->m_sym->AsStackSym()->m_instrDef;
linkOpnd = tmpInstr->GetSrc2();
#if DBG
IntConstType argCount = linkOpnd->AsSymOpnd()->m_sym->AsStackSym()->GetArgSlotNum();
Assert(argCount == 2);
#endif
IR::Instr *argInstr = Inline::GetDefInstr(linkOpnd);
Assert(argInstr->m_opcode == Js::OpCode::ArgOut_A);
IR::Opnd *parseIntArgOpnd = argInstr->GetSrc1();
if (parseIntArgOpnd->GetValueType().IsLikelyNumber())
{
//If likely int check for tagged int and set the dst
IR::LabelInstr *doneLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
IR::LabelInstr *labelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true);
if (!parseIntArgOpnd->IsTaggedInt())
{
this->m_lowererMD.GenerateSmIntTest(parseIntArgOpnd, instr, labelHelper);
}
if (instr->GetDst())
{
this->InsertMove(instr->GetDst(), parseIntArgOpnd, instr);
}
InsertBranch(Js::OpCode::Br, doneLabel, instr);
instr->InsertBefore(labelHelper);
instr->InsertAfter(doneLabel);
RelocateCallDirectToHelperPath(tmpInstr, labelHelper);
}
return true;
}
void
Lowerer::GenerateFastInlineArrayPop(IR::Instr * instr)
{
Assert(instr->m_opcode == Js::OpCode::InlineArrayPop);
IR::Opnd *arrayOpnd = instr->GetSrc1();
IR::LabelInstr *bailOutLabelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true);
IR::LabelInstr *doneLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
bool isLikelyNativeArray = arrayOpnd->GetValueType().IsLikelyNativeArray();
if (ShouldGenerateArrayFastPath(arrayOpnd, false, false, false))
{
IR::LabelInstr *labelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true);
if(isLikelyNativeArray)
{
//We bailOut on cases like length == 0, Array Test failing cases (Runtime helper cannot handle these cases)
GenerateFastPop(arrayOpnd, instr, labelHelper, doneLabel, bailOutLabelHelper);
}
else
{
//We jump to helper on cases like length == 0, Array Test failing cases
GenerateFastPop(arrayOpnd, instr, labelHelper, doneLabel, labelHelper);
}
instr->InsertBefore(labelHelper);
///JMP to $doneLabel
InsertBranch(Js::OpCode::Br, true, doneLabel, labelHelper);
}
else
{
//We assume here that the array will be a Var array. - Runtime Helper calls assume this.
Assert(!isLikelyNativeArray);
}
instr->InsertAfter(doneLabel);
if(isLikelyNativeArray)
{
//Lower IR::BailOutConventionalNativeArrayAccessOnly here.
LowerOneBailOutKind(instr, IR::BailOutConventionalNativeArrayAccessOnly, false, false);
instr->InsertAfter(bailOutLabelHelper);
}
GenerateHelperToArrayPopFastPath(instr, doneLabel, bailOutLabelHelper);
}
void
Lowerer::GenerateFastInlineIsArray(IR::Instr * instr)
{
Assert(instr->m_opcode == Js::OpCode::CallDirect);
IR::Opnd * dst = instr->GetDst();
Assert(dst);
//CallDirect src2
IR::Opnd * linkOpnd = instr->GetSrc2();
//ArgOut_A_InlineSpecialized
IR::Instr * tmpInstr = linkOpnd->AsSymOpnd()->m_sym->AsStackSym()->m_instrDef;
IR::Opnd * argsOpnd[2] = { 0 };
bool result = instr->FetchOperands(argsOpnd, 2);
Assert(result);
AnalysisAssert(argsOpnd[1]);
IR::LabelInstr *helperLabel = InsertLabel(true, instr);
IR::Instr * insertInstr = helperLabel;
IR::LabelInstr *doneLabel = InsertLabel(false, instr->m_next);
ValueType valueType = argsOpnd[1]->GetValueType();
IR::RegOpnd * src = GetRegOpnd(argsOpnd[1], insertInstr, m_func, argsOpnd[1]->GetType());
IR::LabelInstr *checkNotArrayLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func, valueType.IsLikelyArray());
IR::LabelInstr *notArrayLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func, valueType.IsLikelyArray());
if (!src->IsNotTaggedValue())
{
m_lowererMD.GenerateObjectTest(src, insertInstr, notArrayLabel);
}
// MOV typeOpnd, [opnd + offset(type)]
IR::RegOpnd *typeOpnd = IR::RegOpnd::New(TyMachPtr, m_func);
const IR::AutoReuseOpnd autoReuseTypeOpnd(typeOpnd, m_func);
IR::IndirOpnd *indirOpnd = IR::IndirOpnd::New(src, Js::RecyclableObject::GetOffsetOfType(), TyMachPtr, m_func);
InsertMove(typeOpnd, indirOpnd, insertInstr);
// MOV typeIdOpnd, [typeOpnd + offset(typeId)]
IR::RegOpnd *typeIdOpnd = IR::RegOpnd::New(TyMachPtr, m_func);
const IR::AutoReuseOpnd autoReuseTypeIdOpnd(typeIdOpnd, m_func);
indirOpnd = IR::IndirOpnd::New(typeOpnd, Js::Type::GetOffsetOfTypeId(), TyInt32, m_func);
InsertMove(typeIdOpnd, indirOpnd, insertInstr);
// CMP typeIdOpnd, TypeIds_ArrayFirst
// JLT $notArray
InsertCompareBranch(
typeIdOpnd,
IR::IntConstOpnd::New(Js::TypeIds_ArrayFirst, TyInt32, m_func),
Js::OpCode::BrLt_A,
checkNotArrayLabel,
insertInstr);
// CMP typeIdOpnd, TypeIds_ArrayLastWithES5
// JGT $notArray
InsertCompareBranch(
typeIdOpnd,
IR::IntConstOpnd::New(Js::TypeIds_ArrayLastWithES5, TyInt32, m_func),
Js::OpCode::BrGt_A,
notArrayLabel,
insertInstr);
// MOV dst, True
InsertMove(dst, LoadLibraryValueOpnd(instr, LibraryValue::ValueTrue), insertInstr);
// JMP $done
InsertBranch(Js::OpCode::Br, doneLabel, insertInstr);
// $checkNotArray:
insertInstr->InsertBefore(checkNotArrayLabel);
// CMP typeIdOpnd, TypeIds_Proxy
// JEQ $helperLabel
InsertCompareBranch(
typeIdOpnd,
IR::IntConstOpnd::New(Js::TypeIds_Proxy, TyInt32, m_func),
Js::OpCode::BrEq_A,
helperLabel,
insertInstr);
CompileAssert(Js::TypeIds_Proxy < Js::TypeIds_ArrayFirst);
// CMP typeIdOpnd, TypeIds_HostDispatch
// JEQ $helperLabel
InsertCompareBranch(
typeIdOpnd,
IR::IntConstOpnd::New(Js::TypeIds_HostDispatch, TyInt32, m_func),
Js::OpCode::BrEq_A,
helperLabel,
insertInstr);
CompileAssert(Js::TypeIds_HostDispatch < Js::TypeIds_ArrayFirst);
// $notObjectLabel:
insertInstr->InsertBefore(notArrayLabel);
// MOV dst, False
InsertMove(dst, LoadLibraryValueOpnd(instr, LibraryValue::ValueFalse), insertInstr);
InsertBranch(Js::OpCode::Br, doneLabel, insertInstr);
RelocateCallDirectToHelperPath(tmpInstr, helperLabel);
}
void
Lowerer::GenerateFastInlineHasOwnProperty(IR::Instr * instr)
{
Assert(instr->m_opcode == Js::OpCode::CallDirect);
//CallDirect src2
IR::Opnd * linkOpnd = instr->GetSrc2();
//ArgOut_A_InlineSpecialized
IR::Instr * tmpInstr = linkOpnd->AsSymOpnd()->m_sym->AsStackSym()->m_instrDef;
IR::Opnd * argsOpnd[2] = { 0 };
bool result = instr->FetchOperands(argsOpnd, 2);
Assert(result);
AnalysisAssert(argsOpnd[0] && argsOpnd[1]);
if (argsOpnd[1]->GetValueType().IsNotString()
|| argsOpnd[0]->GetValueType().IsNotObject()
|| !argsOpnd[0]->IsRegOpnd()
|| !argsOpnd[1]->IsRegOpnd())
{
return;
}
IR::RegOpnd * thisObj = argsOpnd[0]->AsRegOpnd();
IR::RegOpnd * propOpnd = argsOpnd[1]->AsRegOpnd();
// fast path case where hasOwnProperty is being called using a property name loaded via a for-in loop
bool generateForInFastpath = propOpnd->GetValueType().IsString()
&& propOpnd->m_sym->m_isSingleDef
&& (propOpnd->m_sym->m_instrDef->m_opcode == Js::OpCode::BrOnEmpty
|| propOpnd->m_sym->m_instrDef->m_opcode == Js::OpCode::BrOnNotEmpty);
IR::LabelInstr * doneLabel = InsertLabel(false, instr->m_next);
IR::LabelInstr * labelHelper = InsertLabel(true, instr);
IR::LabelInstr * cacheMissLabel = generateForInFastpath ? IR::LabelInstr::New(Js::OpCode::Label, m_func, true) : labelHelper;
IR::Instr * insertInstr = labelHelper;
// GenerateObjectTest(propOpnd, $labelHelper)
// CMP indexOpnd, PropertyString::`vtable'
// JNE $helper
// GenerateObjectTest(thisObj, $labelHelper)
// MOV inlineCacheOpnd, propOpnd->lsElemInlineCache
// MOV objectTypeOpnd, thisObj->type
// GenerateDynamicLoadPolymorphicInlineCacheSlot(inlineCacheOpnd, objectTypeOpnd) ; loads inline cache for given type
// GenerateLocalInlineCacheCheck(objectTypeOpnd, inlineCacheOpnd, $notInlineSlotsLabel) ; check for type in inline slots, jump to $notInlineSlotsLabel on failure
// MOV dst, ValueTrue
// JMP $done
// $notInlineSlotsLabel:
// GenerateLoadTaggedType(objectTypeOpnd, opndTaggedType)
// GenerateLocalInlineCacheCheck(opndTaggedType, inlineCacheOpnd, $cacheMissLabel) ; check for type in aux slot, jump to $cacheMissLabel on failure
// MOV dst, ValueTrue
// JMP $done
m_lowererMD.GenerateObjectTest(propOpnd, insertInstr, labelHelper);
InsertCompareBranch(IR::IndirOpnd::New(propOpnd, 0, TyMachPtr, m_func), LoadVTableValueOpnd(insertInstr, VTableValue::VtablePropertyString), Js::OpCode::BrNeq_A, labelHelper, insertInstr);
m_lowererMD.GenerateObjectTest(thisObj, insertInstr, labelHelper);
IR::RegOpnd * inlineCacheOpnd = IR::RegOpnd::New(TyMachPtr, m_func);
InsertMove(inlineCacheOpnd, IR::IndirOpnd::New(propOpnd, Js::PropertyString::GetOffsetOfLdElemInlineCache(), TyMachPtr, m_func), insertInstr);
IR::RegOpnd * objectTypeOpnd = IR::RegOpnd::New(TyMachPtr, m_func);
InsertMove(objectTypeOpnd, IR::IndirOpnd::New(thisObj, Js::RecyclableObject::GetOffsetOfType(), TyMachPtr, m_func), insertInstr);
GenerateDynamicLoadPolymorphicInlineCacheSlot(insertInstr, inlineCacheOpnd, objectTypeOpnd);
IR::LabelInstr * notInlineSlotsLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func);
GenerateLocalInlineCacheCheck(insertInstr, objectTypeOpnd, inlineCacheOpnd, notInlineSlotsLabel);
InsertMove(instr->GetDst(), LoadLibraryValueOpnd(instr, LibraryValue::ValueTrue), insertInstr);
InsertBranch(Js::OpCode::Br, doneLabel, insertInstr);
insertInstr->InsertBefore(notInlineSlotsLabel);
IR::RegOpnd * opndTaggedType = IR::RegOpnd::New(TyMachReg, m_func);
m_lowererMD.GenerateLoadTaggedType(insertInstr, objectTypeOpnd, opndTaggedType);
GenerateLocalInlineCacheCheck(insertInstr, opndTaggedType, inlineCacheOpnd, cacheMissLabel);
InsertMove(instr->GetDst(), LoadLibraryValueOpnd(instr, LibraryValue::ValueTrue), insertInstr);
InsertBranch(Js::OpCode::Br, doneLabel, insertInstr);
if (!generateForInFastpath)
{
RelocateCallDirectToHelperPath(tmpInstr, labelHelper);
return;
}
insertInstr->InsertBefore(cacheMissLabel);
// CMP forInEnumeratorOpnd->canUseJitFastPath, 0
// JEQ $labelHelper
// MOV cachedDataTypeOpnd, forInEnumeratorOpnd->enumeratorInitialType
// CMP thisObj->type, cachedDataTypeOpnd
// JNE $labelHelper
// CMP forInEnumeratorOpnd->enumeratingPrototype, 0
// JNE $falseLabel
// MOV dst, True
// JMP $doneLabel
// $falseLabel: [helper]
// MOV dst, False
// JMP $doneLabel
// $labelHelper: [helper]
// CallDirect code
// ...
// $doneLabel:
IR::Opnd * forInEnumeratorOpnd = argsOpnd[1]->AsRegOpnd()->m_sym->m_instrDef->GetSrc1();
// go to helper if we can't use JIT fastpath
IR::Opnd * canUseJitFastPathOpnd = GetForInEnumeratorFieldOpnd(forInEnumeratorOpnd, Js::ForInObjectEnumerator::GetOffsetOfCanUseJitFastPath(), TyInt8);
InsertCompareBranch(canUseJitFastPathOpnd, IR::IntConstOpnd::New(0, TyInt8, m_func), Js::OpCode::BrEq_A, labelHelper, insertInstr);
// go to helper if initial type is not same as the object we are querying
IR::RegOpnd * cachedDataTypeOpnd = IR::RegOpnd::New(TyMachPtr, m_func);
InsertMove(cachedDataTypeOpnd, GetForInEnumeratorFieldOpnd(forInEnumeratorOpnd, Js::ForInObjectEnumerator::GetOffsetOfEnumeratorInitialType(), TyMachPtr), insertInstr);
InsertCompareBranch(cachedDataTypeOpnd, IR::IndirOpnd::New(thisObj, Js::DynamicObject::GetOffsetOfType(), TyMachPtr, m_func), Js::OpCode::BrNeq_A, labelHelper, insertInstr);
// if we haven't yet gone to helper, then we can check if we are enumerating the prototype to know if property is an own property
IR::LabelInstr *falseLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func, true);
IR::Opnd * enumeratingPrototype = GetForInEnumeratorFieldOpnd(forInEnumeratorOpnd, Js::ForInObjectEnumerator::GetOffsetOfEnumeratingPrototype(), TyInt8);
InsertCompareBranch(enumeratingPrototype, IR::IntConstOpnd::New(0, TyInt8, m_func), Js::OpCode::BrNeq_A, falseLabel, insertInstr);
// assume true is the main path
InsertMove(instr->GetDst(), LoadLibraryValueOpnd(instr, LibraryValue::ValueTrue), insertInstr);
InsertBranch(Js::OpCode::Br, doneLabel, insertInstr);
// load false on helper path
insertInstr->InsertBefore(falseLabel);
InsertMove(instr->GetDst(), LoadLibraryValueOpnd(instr, LibraryValue::ValueFalse), insertInstr);
InsertBranch(Js::OpCode::Br, doneLabel, insertInstr);
RelocateCallDirectToHelperPath(tmpInstr, labelHelper);
}
bool
Lowerer::ShouldGenerateStringReplaceFastPath(IR::Instr * callInstr, IntConstType argCount)
{
// a.replace(b,c)
// We want to emit the fast path if 'a' and 'c' are strings and 'b' is a regex
//
// argout sequence:
// arg1(s12)<0>.var = ArgOut_A s2.var, s11.var #0014 <---- a
// arg2(s13)<4>.var = ArgOut_A s3.var, arg1(s12)<0>.var #0018 <---- b
// arg3(s14)<8>.var = ArgOut_A s4.var, arg2(s13)<4>.var #001c <---- c
// s0[LikelyString].var = CallI s5[ffunc].var, arg3(s14)<8>.var #0020
IR::Opnd *linkOpnd = callInstr->GetSrc2();
Assert(argCount == 2);
while(linkOpnd->IsSymOpnd())
{
IR::SymOpnd *src2 = linkOpnd->AsSymOpnd();
StackSym *sym = src2->m_sym->AsStackSym();
Assert(sym->m_isSingleDef);
IR::Instr *argInstr = sym->m_instrDef;
Assert(argCount >= 0);
// check to see if 'a' and 'c' are likely strings
if((argCount == 2 || argCount == 0) && (!argInstr->GetSrc1()->GetValueType().IsLikelyString()))
{
return false;
}
// we want 'b' to be regex. Don't generate fastpath if it is a tagged int
if((argCount == 1) && (argInstr->GetSrc1()->IsTaggedInt()))
{
return false;
}
argCount--;
linkOpnd = argInstr->GetSrc2();
}
return true;
}
bool
Lowerer::GenerateFastReplace(IR::Opnd* strOpnd, IR::Opnd* src1, IR::Opnd* src2, IR::Instr *callInstr, IR::Instr *insertInstr, IR::LabelInstr *labelHelper, IR::LabelInstr *doneLabel)
{
// a.replace(b,c)
// We want to emit the fast path if 'a' and 'c' are strings and 'b' is a regex
//
// strOpnd --> a
// src1 --> b
// src2 --> c
IR::Opnd * callDst = callInstr->GetDst();
Assert(strOpnd->GetValueType().IsLikelyString() && src2->GetValueType().IsLikelyString());
if(!strOpnd->GetValueType().IsString())
{
strOpnd = GetRegOpnd(strOpnd, insertInstr, m_func, TyVar);
this->GenerateStringTest(strOpnd->AsRegOpnd(), insertInstr, labelHelper);
}
if(!src1->IsNotTaggedValue())
{
m_lowererMD.GenerateObjectTest(src1, insertInstr, labelHelper);
}
IR::Opnd * vtableOpnd = LoadVTableValueOpnd(insertInstr, VTableValue::VtableJavascriptRegExp);
// cmp [regex], vtableAddress
// jne $labelHelper
src1 = GetRegOpnd(src1, insertInstr, m_func, TyVar);
InsertCompareBranch(
IR::IndirOpnd::New(src1->AsRegOpnd(), 0, TyMachPtr, insertInstr->m_func),
vtableOpnd,
Js::OpCode::BrNeq_A,
labelHelper,
insertInstr);
if(!src2->GetValueType().IsString())
{
src2 = GetRegOpnd(src2, insertInstr, m_func, TyVar);
this->GenerateStringTest(src2->AsRegOpnd(), insertInstr, labelHelper);
}
IR::Instr * helperCallInstr = IR::Instr::New(LowererMD::MDCallOpcode, insertInstr->m_func);
if (callDst)
{
helperCallInstr->SetDst(callDst);
}
insertInstr->InsertBefore(helperCallInstr);
if (insertInstr->HasBailOutInfo() && BailOutInfo::IsBailOutOnImplicitCalls(insertInstr->GetBailOutKind()))
{
helperCallInstr = AddBailoutToHelperCallInstr(helperCallInstr, insertInstr->GetBailOutInfo(), insertInstr->GetBailOutKind(), insertInstr);
}
//scriptContext, pRegEx, pThis, pReplace (to be pushed in reverse order)
// pReplace, pThis, pRegEx
this->m_lowererMD.LoadHelperArgument(helperCallInstr, src2);
this->m_lowererMD.LoadHelperArgument(helperCallInstr, strOpnd);
this->m_lowererMD.LoadHelperArgument(helperCallInstr, src1);
// script context
LoadScriptContext(helperCallInstr);
if(callDst)
{
m_lowererMD.ChangeToHelperCall(helperCallInstr, IR::JnHelperMethod::HelperRegExp_ReplaceStringResultUsed);
}
else
{
m_lowererMD.ChangeToHelperCall(helperCallInstr, IR::JnHelperMethod::HelperRegExp_ReplaceStringResultNotUsed);
}
return true;
}
///----
void
Lowerer::GenerateFastInlineStringSplitMatch(IR::Instr * instr)
{
// a.split(b,c (optional) )
// We want to emit the fast path when
// 1. c is not present, and
// 2. 'a' is a string and 'b' is a regex.
//
// a.match(b)
// We want to emit the fast path when 'a' is a string and 'b' is a regex.
Assert(instr->m_opcode == Js::OpCode::CallDirect);
IR::Opnd * callDst = instr->GetDst();
//helperCallOpnd
IR::Opnd * src1 = instr->GetSrc1();
//ArgOut_A_InlineSpecialized
IR::Instr * tmpInstr = instr->GetSrc2()->AsSymOpnd()->m_sym->AsStackSym()->m_instrDef;
IR::Opnd * argsOpnd[2];
if(!instr->FetchOperands(argsOpnd, 2))
{
return;
}
if(!argsOpnd[0]->GetValueType().IsLikelyString() || argsOpnd[1]->IsTaggedInt())
{
return;
}
IR::LabelInstr *labelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true);
if(!argsOpnd[0]->GetValueType().IsString())
{
argsOpnd[0] = GetRegOpnd(argsOpnd[0], instr, m_func, TyVar);
this->GenerateStringTest(argsOpnd[0]->AsRegOpnd(), instr, labelHelper);
}
if(!argsOpnd[1]->IsNotTaggedValue())
{
m_lowererMD.GenerateObjectTest(argsOpnd[1], instr, labelHelper);
}
IR::Opnd * vtableOpnd = LoadVTableValueOpnd(instr, VTableValue::VtableJavascriptRegExp);
// cmp [regex], vtableAddress
// jne $labelHelper
argsOpnd[1] = GetRegOpnd(argsOpnd[1], instr, m_func, TyVar);
InsertCompareBranch(
IR::IndirOpnd::New(argsOpnd[1]->AsRegOpnd(), 0, TyMachPtr, instr->m_func),
vtableOpnd,
Js::OpCode::BrNeq_A,
labelHelper,
instr);
IR::Instr * helperCallInstr = IR::Instr::New(LowererMD::MDCallOpcode, instr->m_func);
if (callDst)
{
helperCallInstr->SetDst(callDst);
}
instr->InsertBefore(helperCallInstr);
if (instr->HasBailOutInfo() && BailOutInfo::IsBailOutOnImplicitCalls(instr->GetBailOutKind()))
{
helperCallInstr = AddBailoutToHelperCallInstr(helperCallInstr, instr->GetBailOutInfo(), instr->GetBailOutKind(), instr);
}
// [stackAllocationPointer, ]scriptcontext, regexp, input[, limit] (to be pushed in reverse order)
if(src1->AsHelperCallOpnd()->m_fnHelper == IR::JnHelperMethod::HelperString_Split)
{
//limit
//As we are optimizing only for two operands, make limit UINT_MAX
IR::Opnd* limit = IR::IntConstOpnd::New(UINT_MAX, TyUint32, instr->m_func);
this->m_lowererMD.LoadHelperArgument(helperCallInstr, limit);
}
//input, regexp
this->m_lowererMD.LoadHelperArgument(helperCallInstr, argsOpnd[0]);
this->m_lowererMD.LoadHelperArgument(helperCallInstr, argsOpnd[1]);
// script context
LoadScriptContext(helperCallInstr);
IR::JnHelperMethod helperMethod = IR::JnHelperMethod::HelperInvalid;
IR::AutoReuseOpnd autoReuseStackAllocationOpnd;
if(callDst && instr->dstIsTempObject)
{
switch(src1->AsHelperCallOpnd()->m_fnHelper)
{
case IR::JnHelperMethod::HelperString_Split:
helperMethod = IR::JnHelperMethod::HelperRegExp_SplitResultUsedAndMayBeTemp;
break;
case IR::JnHelperMethod::HelperString_Match:
helperMethod = IR::JnHelperMethod::HelperRegExp_MatchResultUsedAndMayBeTemp;
break;
default:
Assert(false);
__assume(false);
}
// Allocate some space on the stack for the result array
IR::RegOpnd *const stackAllocationOpnd = IR::RegOpnd::New(TyVar, m_func);
autoReuseStackAllocationOpnd.Initialize(stackAllocationOpnd, m_func);
stackAllocationOpnd->SetValueType(callDst->GetValueType());
GenerateMarkTempAlloc(stackAllocationOpnd, Js::JavascriptArray::StackAllocationSize, helperCallInstr);
m_lowererMD.LoadHelperArgument(helperCallInstr, stackAllocationOpnd);
}
else
{
switch(src1->AsHelperCallOpnd()->m_fnHelper)
{
case IR::JnHelperMethod::HelperString_Split:
helperMethod =
callDst
? IR::JnHelperMethod::HelperRegExp_SplitResultUsed
: IR::JnHelperMethod::HelperRegExp_SplitResultNotUsed;
break;
case IR::JnHelperMethod::HelperString_Match:
helperMethod =
callDst
? IR::JnHelperMethod::HelperRegExp_MatchResultUsed
: IR::JnHelperMethod::HelperRegExp_MatchResultNotUsed;
break;
default:
Assert(false);
__assume(false);
}
}
m_lowererMD.ChangeToHelperCall(helperCallInstr, helperMethod);
IR::LabelInstr *doneLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
instr->InsertAfter(doneLabel);
instr->InsertBefore(labelHelper);
InsertBranch(Js::OpCode::Br, true, doneLabel, labelHelper);
RelocateCallDirectToHelperPath(tmpInstr, labelHelper);
}
void
Lowerer::GenerateFastInlineRegExpExec(IR::Instr * instr)
{
// a.exec(b)
// We want to emit the fast path when 'a' is a regex and 'b' is a string
Assert(instr->m_opcode == Js::OpCode::CallDirect);
IR::Opnd * callDst = instr->GetDst();
//ArgOut_A_InlineSpecialized
IR::Instr * tmpInstr = instr->GetSrc2()->AsSymOpnd()->m_sym->AsStackSym()->m_instrDef;
IR::Opnd * argsOpnd[2];
if (!instr->FetchOperands(argsOpnd, 2))
{
return;
}
IR::Opnd *opndString = argsOpnd[1];
if(!opndString->GetValueType().IsLikelyString() || argsOpnd[0]->IsTaggedInt())
{
return;
}
IR::LabelInstr *labelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true);
if(!opndString->GetValueType().IsString())
{
opndString = GetRegOpnd(opndString, instr, m_func, TyVar);
this->GenerateStringTest(opndString->AsRegOpnd(), instr, labelHelper);
}
IR::Opnd *opndRegex = argsOpnd[0];
if(!opndRegex->IsNotTaggedValue())
{
m_lowererMD.GenerateObjectTest(opndRegex, instr, labelHelper);
}
IR::Opnd * vtableOpnd = LoadVTableValueOpnd(instr, VTableValue::VtableJavascriptRegExp);
// cmp [regex], vtableAddress
// jne $labelHelper
opndRegex = GetRegOpnd(opndRegex, instr, m_func, TyVar);
InsertCompareBranch(
IR::IndirOpnd::New(opndRegex->AsRegOpnd(), 0, TyMachPtr, instr->m_func),
vtableOpnd,
Js::OpCode::BrNeq_A,
labelHelper,
instr);
IR::LabelInstr *doneLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func);
if (!PHASE_OFF(Js::ExecBOIFastPathPhase, m_func))
{
// Load pattern from regex operand
IR::RegOpnd *opndPattern = IR::RegOpnd::New(TyMachPtr, m_func);
Lowerer::InsertMove(
opndPattern,
IR::IndirOpnd::New(opndRegex->AsRegOpnd(), Js::JavascriptRegExp::GetOffsetOfPattern(), TyMachPtr, m_func),
instr);
// Load program from pattern
IR::RegOpnd *opndProgram = IR::RegOpnd::New(TyMachPtr, m_func);
Lowerer::InsertMove(
opndProgram,
IR::IndirOpnd::New(opndPattern, offsetof(UnifiedRegex::RegexPattern, rep) + offsetof(UnifiedRegex::RegexPattern::UnifiedRep, program), TyMachPtr, m_func),
instr);
IR::LabelInstr *labelFastHelper = IR::LabelInstr::New(Js::OpCode::Label, m_func);
// We want the program's tag to be BOILiteral2Tag
InsertCompareBranch(
IR::IndirOpnd::New(opndProgram, (int32)UnifiedRegex::Program::GetOffsetOfTag(), TyUint8, m_func),
IR::IntConstOpnd::New((IntConstType)UnifiedRegex::Program::GetBOILiteral2Tag(), TyUint8, m_func),
Js::OpCode::BrNeq_A,
labelFastHelper,
instr);
// Test the program's flags for "global"
InsertTestBranch(
IR::IndirOpnd::New(opndProgram, offsetof(UnifiedRegex::Program, flags), TyUint8, m_func),
IR::IntConstOpnd::New(UnifiedRegex::GlobalRegexFlag, TyUint8, m_func),
Js::OpCode::BrNeq_A,
labelFastHelper,
instr);
IR::LabelInstr *labelNoMatch = IR::LabelInstr::New(Js::OpCode::Label, m_func);
// If string length < 2...
InsertCompareBranch(
IR::IndirOpnd::New(opndString->AsRegOpnd(), offsetof(Js::JavascriptString, m_charLength), TyUint32, m_func),
IR::IntConstOpnd::New(2, TyUint32, m_func),
Js::OpCode::BrLt_A,
labelNoMatch,
instr);
// ...or the DWORD doesn't match the pattern...
IR::RegOpnd *opndBuffer = IR::RegOpnd::New(TyMachReg, m_func);
Lowerer::InsertMove(
opndBuffer,
IR::IndirOpnd::New(opndString->AsRegOpnd(), offsetof(Js::JavascriptString, m_pszValue), TyMachPtr, m_func),
instr);
IR::LabelInstr *labelGotString = IR::LabelInstr::New(Js::OpCode::Label, m_func);
InsertTestBranch(opndBuffer, opndBuffer, Js::OpCode::BrNeq_A, labelGotString, instr);
m_lowererMD.LoadHelperArgument(instr, opndString);
IR::Instr *instrCall = IR::Instr::New(Js::OpCode::Call, opndBuffer, IR::HelperCallOpnd::New(IR::HelperString_GetSz, m_func), m_func);
instr->InsertBefore(instrCall);
m_lowererMD.LowerCall(instrCall, 0);
instr->InsertBefore(labelGotString);
IR::RegOpnd *opndBufferDWORD = IR::RegOpnd::New(TyUint32, m_func);
Lowerer::InsertMove(
opndBufferDWORD,
IR::IndirOpnd::New(opndBuffer, 0, TyUint32, m_func),
instr);
InsertCompareBranch(
IR::IndirOpnd::New(opndProgram, (int32)(UnifiedRegex::Program::GetOffsetOfRep() + UnifiedRegex::Program::GetOffsetOfBOILiteral2Literal()), TyUint32, m_func),
opndBufferDWORD,
Js::OpCode::BrEq_A,
labelFastHelper,
instr);
// ...then set the last index to 0...
instr->InsertBefore(labelNoMatch);
Lowerer::InsertMove(
IR::IndirOpnd::New(opndRegex->AsRegOpnd(), Js::JavascriptRegExp::GetOffsetOfLastIndexVar(), TyVar, m_func),
IR::AddrOpnd::NewNull(m_func),
instr);
Lowerer::InsertMove(
IR::IndirOpnd::New(opndRegex->AsRegOpnd(), Js::JavascriptRegExp::GetOffsetOfLastIndexOrFlag(), TyUint32, m_func),
IR::IntConstOpnd::New(0, TyUint32, m_func),
instr);
// ...and set the dst to null...
if (callDst)
{
Lowerer::InsertMove(
callDst,
LoadLibraryValueOpnd(instr, LibraryValue::ValueNull),
instr);
}
// ...and we're done.
this->InsertBranch(Js::OpCode::Br, doneLabel, instr);
instr->InsertBefore(labelFastHelper);
}
IR::Instr * helperCallInstr = IR::Instr::New(LowererMD::MDCallOpcode, instr->m_func);
if (callDst)
{
helperCallInstr->SetDst(callDst);
}
instr->InsertBefore(helperCallInstr);
if (instr->HasBailOutInfo() && BailOutInfo::IsBailOutOnImplicitCalls(instr->GetBailOutKind()))
{
helperCallInstr = AddBailoutToHelperCallInstr(helperCallInstr, instr->GetBailOutInfo(), instr->GetBailOutKind(), instr);
}
// [stackAllocationPointer, ]scriptcontext, regexp, string (to be pushed in reverse order)
//string, regexp
this->m_lowererMD.LoadHelperArgument(helperCallInstr, opndString);
this->m_lowererMD.LoadHelperArgument(helperCallInstr, opndRegex);
// script context
LoadScriptContext(helperCallInstr);
IR::JnHelperMethod helperMethod;
IR::AutoReuseOpnd autoReuseStackAllocationOpnd;
if (callDst)
{
if (instr->dstIsTempObject)
{
helperMethod = IR::JnHelperMethod::HelperRegExp_ExecResultUsedAndMayBeTemp;
// Allocate some space on the stack for the result array
IR::RegOpnd *const stackAllocationOpnd = IR::RegOpnd::New(TyVar, m_func);
autoReuseStackAllocationOpnd.Initialize(stackAllocationOpnd, m_func);
stackAllocationOpnd->SetValueType(callDst->GetValueType());
GenerateMarkTempAlloc(stackAllocationOpnd, Js::JavascriptArray::StackAllocationSize, helperCallInstr);
m_lowererMD.LoadHelperArgument(helperCallInstr, stackAllocationOpnd);
}
else
{
helperMethod = IR::JnHelperMethod::HelperRegExp_ExecResultUsed;
}
}
else
{
helperMethod = IR::JnHelperMethod::HelperRegExp_ExecResultNotUsed;
}
m_lowererMD.ChangeToHelperCall(helperCallInstr, helperMethod);
instr->InsertAfter(doneLabel);
instr->InsertBefore(labelHelper);
InsertBranch(Js::OpCode::Br, true, doneLabel, labelHelper);
RelocateCallDirectToHelperPath(tmpInstr, labelHelper);
}
// Generate a fast path for the "in" operator that check quickly if we have an array or not and if the index of the data is contained in the array's length.
void Lowerer::GenerateFastArrayIsIn(IR::Instr * instr)
{
// operator "foo in bar"
IR::Opnd* src1 = instr->GetSrc1(); // foo
IR::Opnd* src2 = instr->GetSrc2(); // bar
if (
!src1->GetValueType().IsLikelyInt() ||
// Do not do a fast path if we know for sure we don't have an int
src1->IsNotInt() ||
!src2->GetValueType().IsLikelyArray() ||
!src2->GetValueType().HasNoMissingValues())
{
return;
}
IR::LabelInstr* helperLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func, true);
IR::LabelInstr* doneLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func);
IR::LabelInstr* isArrayLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func);
IR::RegOpnd* src1Untagged = GenerateUntagVar(src1->AsRegOpnd(), helperLabel, instr);
IR::RegOpnd* src2RegOpnd = IR::RegOpnd::New(TyMachPtr, m_func);
InsertMove(src2RegOpnd, src2, instr);
IR::AutoReuseOpnd autoReuseArrayOpnd;
m_lowererMD.GenerateObjectTest(src2RegOpnd, instr, helperLabel);
IR::RegOpnd* arrayOpnd = src2RegOpnd->Copy(instr->m_func)->AsRegOpnd();
autoReuseArrayOpnd.Initialize(arrayOpnd, instr->m_func, false /* autoDelete */);
IR::Opnd* vtableOpnd = LoadVTableValueOpnd(instr, VTableValue::VtableJavascriptArray);
InsertCompareBranch(
IR::IndirOpnd::New(arrayOpnd, 0, TyMachPtr, instr->m_func),
vtableOpnd,
Js::OpCode::BrEq_A,
isArrayLabel,
instr);
vtableOpnd = LoadVTableValueOpnd(instr, VTableValue::VtableNativeIntArray);
InsertCompareBranch(
IR::IndirOpnd::New(arrayOpnd, 0, TyMachPtr, instr->m_func),
vtableOpnd,
Js::OpCode::BrEq_A,
isArrayLabel,
instr);
vtableOpnd = LoadVTableValueOpnd(instr, VTableValue::VtableNativeFloatArray);
InsertCompareBranch(
IR::IndirOpnd::New(arrayOpnd, 0, TyMachPtr, instr->m_func),
vtableOpnd,
Js::OpCode::BrNeq_A,
helperLabel,
instr);
instr->InsertBefore(isArrayLabel);
InsertTestBranch(
IR::IndirOpnd::New(src2RegOpnd, Js::JavascriptArray::GetOffsetOfArrayFlags(), TyUint8, m_func),
IR::IntConstOpnd::New(static_cast<uint8>(Js::DynamicObjectFlags::HasNoMissingValues), TyUint8, m_func, true),
Js::OpCode::BrEq_A,
helperLabel,
instr);
IR::AutoReuseOpnd autoReuseHeadSegmentOpnd;
IR::AutoReuseOpnd autoReuseHeadSegmentLengthOpnd;
IR::IndirOpnd* indirOpnd = IR::IndirOpnd::New(src2RegOpnd, Js::JavascriptArray::GetOffsetOfHead(), TyMachPtr, this->m_func);
IR::RegOpnd* headSegmentOpnd = IR::RegOpnd::New(TyMachPtr, this->m_func);
autoReuseHeadSegmentOpnd.Initialize(headSegmentOpnd, m_func);
InsertMove(headSegmentOpnd, indirOpnd, instr);
IR::Opnd* headSegmentLengthOpnd = IR::IndirOpnd::New(headSegmentOpnd, Js::SparseArraySegmentBase::GetOffsetOfLength(), TyUint32, m_func);
autoReuseHeadSegmentLengthOpnd.Initialize(headSegmentLengthOpnd, m_func);
InsertCompareBranch(
src1Untagged,
headSegmentLengthOpnd,
Js::OpCode::BrGe_A,
helperLabel,
instr);
InsertCompareBranch(
src1Untagged,
IR::IntConstOpnd::New(0, src1Untagged->GetType(), this->m_func),
Js::OpCode::BrLt_A,
helperLabel,
instr);
InsertMove(instr->GetDst(), LoadLibraryValueOpnd(instr, LibraryValue::ValueTrue), instr);
InsertBranch(Js::OpCode::Br, doneLabel, instr);
instr->InsertBefore(helperLabel);
instr->InsertAfter(doneLabel);
}
// Generate a fast path for the "in" operator to use the cache where the key may be a PropertyString or Symbol.
void Lowerer::GenerateFastObjectIsIn(IR::Instr * instr)
{
IR::RegOpnd* baseOpnd = GetRegOpnd(instr->GetSrc2(), instr, m_func, TyVar);
IR::RegOpnd* indexOpnd = GetRegOpnd(instr->GetSrc1(), instr, m_func, TyVar);
bool likelyStringIndex = indexOpnd->GetValueType().IsLikelyString();
bool likelySymbolIndex = indexOpnd->GetValueType().IsLikelySymbol();
if (!baseOpnd->GetValueType().IsLikelyObject() || !(likelyStringIndex || likelySymbolIndex))
{
return;
}
IR::LabelInstr* helperLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func, true);
IR::LabelInstr* doneLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func);
if (likelyStringIndex)
{
GeneratePropertyStringTest(indexOpnd, instr, helperLabel, false /*isStore*/);
const uint32 inlineCacheOffset = Js::PropertyString::GetOffsetOfLdElemInlineCache();
const uint32 hitRateOffset = Js::PropertyString::GetOffsetOfHitRate();
GenerateFastIsInSymbolOrStringIndex(instr, indexOpnd, baseOpnd, instr->GetDst(), inlineCacheOffset, hitRateOffset, helperLabel, doneLabel);
}
else
{
Assert(likelySymbolIndex);
GenerateSymbolTest(indexOpnd, instr, helperLabel);
const uint32 inlineCacheOffset = Js::JavascriptSymbol::GetOffsetOfLdElemInlineCache();
const uint32 hitRateOffset = Js::JavascriptSymbol::GetOffsetOfHitRate();
GenerateFastIsInSymbolOrStringIndex(instr, indexOpnd, baseOpnd, instr->GetDst(), inlineCacheOffset, hitRateOffset, helperLabel, doneLabel);
}
instr->InsertBefore(helperLabel);
instr->InsertAfter(doneLabel);
}
// Given an operand, either cast it or move it to a register
IR::RegOpnd * Lowerer::GetRegOpnd(IR::Opnd* opnd, IR::Instr* insertInstr, Func* func, IRType type)
{
if (opnd->IsRegOpnd())
{
return opnd->AsRegOpnd();
}
IR::RegOpnd *regOpnd = IR::RegOpnd::New(type, func);
InsertMove(regOpnd, opnd, insertInstr);
return regOpnd;
}
template <bool Saturate>
void Lowerer::GenerateTruncWithCheck(_In_ IR::Instr* instr)
{
Assert(instr->GetSrc1()->IsFloat());
if (instr->GetDst()->IsInt32() || instr->GetDst()->IsUInt32())
{
m_lowererMD.GenerateTruncWithCheck<Saturate>(instr);
}
else
{
Assert(instr->GetDst()->IsInt64());
LoadScriptContext(instr);
if (instr->GetSrc1()->IsFloat32())
{
m_lowererMD.LoadFloatHelperArgument(instr, instr->GetSrc1());
}
else
{
m_lowererMD.LoadDoubleHelperArgument(instr, instr->GetSrc1());
}
IR::JnHelperMethod helper;
if (Saturate)
{
IR::JnHelperMethod helperList[2][2] = { IR::HelperF32ToI64Sat, IR::HelperF32ToU64Sat, IR::HelperF64ToI64Sat ,IR::HelperF64ToU64Sat };
helper = helperList[instr->GetSrc1()->GetType() != TyFloat32][instr->GetDst()->GetType() == TyUint64];
}
else
{
IR::JnHelperMethod helperList[2][2] = { IR::HelperF32ToI64, IR::HelperF32ToU64, IR::HelperF64ToI64 ,IR::HelperF64ToU64 };
helper = helperList[instr->GetSrc1()->GetType() != TyFloat32][instr->GetDst()->GetType() == TyUint64];
}
instr->UnlinkSrc1();
this->m_lowererMD.ChangeToHelperCall(instr, helper);
}
}
void
Lowerer::RelocateCallDirectToHelperPath(IR::Instr* argoutInlineSpecialized, IR::LabelInstr* labelHelper)
{
IR::Opnd *linkOpnd = argoutInlineSpecialized->GetSrc2(); //ArgOut_A_InlineSpecialized src2; link to actual argouts.
argoutInlineSpecialized->Unlink();
labelHelper->InsertAfter(argoutInlineSpecialized);
while(linkOpnd->IsSymOpnd())
{
IR::SymOpnd *src2 = linkOpnd->AsSymOpnd();
StackSym *sym = src2->m_sym->AsStackSym();
Assert(sym->m_isSingleDef);
IR::Instr *argInstr = sym->m_instrDef;
Assert(argInstr->m_opcode == Js::OpCode::ArgOut_A);
argInstr->Unlink();
labelHelper->InsertAfter(argInstr);
linkOpnd = argInstr->GetSrc2();
}
// Move startcall
Assert(linkOpnd->IsRegOpnd());
StackSym *sym = linkOpnd->AsRegOpnd()->m_sym;
Assert(sym->m_isSingleDef);
IR::Instr *startCall = sym->m_instrDef;
Assert(startCall->m_opcode == Js::OpCode::StartCall);
startCall->Unlink();
labelHelper->InsertAfter(startCall);
}
bool
Lowerer::GenerateFastInlineStringCharCodeAt(IR::Instr * instr, Js::BuiltinFunction index)
{
Assert(instr->m_opcode == Js::OpCode::CallDirect);
//CallDirect src2
IR::Opnd * linkOpnd = instr->GetSrc2();
//ArgOut_A_InlineSpecialized
IR::Instr * tmpInstr = linkOpnd->AsSymOpnd()->m_sym->AsStackSym()->m_instrDef;
IR::Opnd * argsOpnd[2] = {0};
bool result = instr->FetchOperands(argsOpnd, 2);
Assert(result);
IR::LabelInstr *doneLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
instr->InsertAfter(doneLabel);
IR::LabelInstr *labelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true);
bool success = GenerateFastCharAt(index, instr->GetDst(), argsOpnd[0], argsOpnd[1],
instr, instr, labelHelper, doneLabel);
instr->InsertBefore(labelHelper);
if (!success)
{
return false;
}
InsertBranch(Js::OpCode::Br, true, doneLabel, labelHelper);
RelocateCallDirectToHelperPath(tmpInstr, labelHelper);
return true;
}
void
Lowerer::GenerateCtz(IR::Instr* instr)
{
Assert(instr->GetDst()->IsInt32() || instr->GetDst()->IsInt64());
Assert(instr->GetSrc1()->IsInt32() || instr->GetSrc1()->IsInt64());
m_lowererMD.GenerateCtz(instr);
}
void
Lowerer::GeneratePopCnt(IR::Instr* instr)
{
Assert(instr->GetSrc1()->IsInt32() || instr->GetSrc1()->IsUInt32() || instr->GetSrc1()->IsInt64());
Assert(instr->GetDst()->IsInt32() || instr->GetDst()->IsUInt32() || instr->GetDst()->IsInt64());
m_lowererMD.GeneratePopCnt(instr);
}
void
Lowerer::GenerateFastInlineMathClz(IR::Instr* instr)
{
Assert(instr->GetDst()->IsInt32() || instr->GetDst()->IsInt64());
Assert(instr->GetSrc1()->IsInt32() || instr->GetSrc1()->IsInt64());
m_lowererMD.GenerateClz(instr);
}
void
Lowerer::GenerateFastInlineMathImul(IR::Instr* instr)
{
IR::Opnd* src1 = instr->GetSrc1();
IR::Opnd* src2 = instr->GetSrc2();
IR::Opnd* dst = instr->GetDst();
Assert(dst->IsInt32());
Assert(src1->IsInt32());
Assert(src2->IsInt32());
IR::Instr* imul = IR::Instr::New(LowererMD::MDImulOpcode, dst, src1, src2, instr->m_func);
instr->InsertBefore(imul);
LowererMD::Legalize(imul);
instr->Remove();
}
void
Lowerer::LowerReinterpretPrimitive(IR::Instr* instr)
{
Assert(m_func->GetJITFunctionBody()->IsWasmFunction());
IR::Opnd* src1 = instr->GetSrc1();
IR::Opnd* dst = instr->GetDst();
Assert(dst->GetSize() == src1->GetSize());
Assert((dst->IsFloat32() && src1->IsInt32()) ||
(dst->IsInt32() && src1->IsFloat32()) ||
(dst->IsInt64() && src1->IsFloat64()) ||
(dst->IsFloat64() && src1->IsInt64()) );
m_lowererMD.EmitReinterpretPrimitive(dst, src1, instr);
instr->Remove();
}
void
Lowerer::GenerateFastInlineMathFround(IR::Instr* instr)
{
IR::Opnd* src1 = instr->GetSrc1();
IR::Opnd* dst = instr->GetDst();
Assert(dst->IsFloat());
Assert(src1->IsFloat());
// This function is supposed to convert a float to the closest float32 representation.
// However, it is a bit loose about types, which the ARM64 encoder takes issue with.
#ifdef _M_ARM64
LowererMD::GenerateFastInlineMathFround(instr);
#else
IR::Instr* fcvt64to32 = IR::Instr::New(LowererMD::MDConvertFloat64ToFloat32Opcode, dst, src1, instr->m_func);
instr->InsertBefore(fcvt64to32);
LowererMD::Legalize(fcvt64to32);
if (dst->IsFloat64())
{
IR::Instr* fcvt32to64 = IR::Instr::New(LowererMD::MDConvertFloat32ToFloat64Opcode, dst, dst, instr->m_func);
instr->InsertBefore(fcvt32to64);
LowererMD::Legalize(fcvt32to64);
}
instr->Remove();
#endif
return;
}
bool
Lowerer::GenerateFastInlineStringReplace(IR::Instr * instr)
{
Assert(instr->m_opcode == Js::OpCode::CallDirect);
//CallDirect src2
IR::Opnd * linkOpnd = instr->GetSrc2();
//ArgOut_A_InlineSpecialized
IR::Instr * tmpInstr = linkOpnd->AsSymOpnd()->m_sym->AsStackSym()->m_instrDef;
IR::Opnd * argsOpnd[3] = {0};
bool result = instr->FetchOperands(argsOpnd, 3);
Assert(result);
AnalysisAssert(argsOpnd[0] && argsOpnd[1] && argsOpnd[2]);
if (!argsOpnd[0]->GetValueType().IsLikelyString()
|| argsOpnd[1]->GetValueType().IsNotObject()
|| !argsOpnd[2]->GetValueType().IsLikelyString())
{
return false;
}
IR::LabelInstr *doneLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
instr->InsertAfter(doneLabel);
IR::LabelInstr *labelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true);
bool success = this->GenerateFastReplace(argsOpnd[0], argsOpnd[1], argsOpnd[2],
instr, instr, labelHelper, doneLabel);
instr->InsertBefore(labelHelper);
if (!success)
{
return false;
}
InsertBranch(Js::OpCode::Br, true, doneLabel, labelHelper);
RelocateCallDirectToHelperPath(tmpInstr, labelHelper);
return true;
}
#ifdef ENABLE_DOM_FAST_PATH
/*
Lower the DOMFastPathGetter opcode
We have inliner generated bytecode:
(dst)helpArg1: ExtendArg_A (src1)thisObject (src2)null
(dst)helpArg2: ExtendArg_A (src1)funcObject (src2)helpArg1
method: DOMFastPathGetter (src1)HelperCall (src2)helpArg2
We'll convert it to a JavascriptFunction entry method call:
CALL Helper funcObject CallInfo(CallFlags_Value, 3) thisObj
*/
void
Lowerer::LowerFastInlineDOMFastPathGetter(IR::Instr* instr)
{
IR::Opnd* helperOpnd = instr->UnlinkSrc1();
Assert(helperOpnd->IsHelperCallOpnd());
IR::Opnd *linkOpnd = instr->UnlinkSrc2();
Assert(linkOpnd->IsRegOpnd());
IR::Instr* prevInstr = linkOpnd->AsRegOpnd()->m_sym->m_instrDef;
Assert(prevInstr->m_opcode == Js::OpCode::ExtendArg_A);
IR::Opnd* funcObj = prevInstr->GetSrc1();
Assert(funcObj->IsRegOpnd());
// If the Extended_arg was CSE's across a loop or hoisted out of a loop,
// adding a new reference down here might cause funcObj to now be liveOnBackEdge.
// Use the addToLiveOnBackEdgeSyms bit vector to add it to a loop if we encounter one.
// We'll clear it once we reach the Extended arg.
this->addToLiveOnBackEdgeSyms->Set(funcObj->AsRegOpnd()->m_sym->m_id);
Assert(prevInstr->GetSrc2() != nullptr);
prevInstr = prevInstr->GetSrc2()->AsRegOpnd()->m_sym->m_instrDef;
Assert(prevInstr->m_opcode == Js::OpCode::ExtendArg_A);
IR::Opnd* thisObj = prevInstr->GetSrc1();
Assert(prevInstr->GetSrc2() == nullptr);
Assert(thisObj->IsRegOpnd());
this->addToLiveOnBackEdgeSyms->Set(thisObj->AsRegOpnd()->m_sym->m_id);
const auto info = Lowerer::MakeCallInfoConst(Js::CallFlags_Value, 1, m_func);
m_lowererMD.LoadHelperArgument(instr, thisObj);
m_lowererMD.LoadHelperArgument(instr, info);
m_lowererMD.LoadHelperArgument(instr, funcObj);
instr->m_opcode = Js::OpCode::Call;
IR::HelperCallOpnd *helperCallOpnd = Lowerer::CreateHelperCallOpnd(helperOpnd->AsHelperCallOpnd()->m_fnHelper, 3, m_func);
instr->SetSrc1(helperCallOpnd);
m_lowererMD.LowerCall(instr, 3); // we have funcobj, callInfo, and this.
}
#endif
void
Lowerer::GenerateFastInlineArrayPush(IR::Instr * instr)
{
Assert(instr->m_opcode == Js::OpCode::InlineArrayPush);
IR::Opnd * baseOpnd = instr->GetSrc1();
IR::Opnd * srcOpnd = instr->GetSrc2();
bool returnLength = false;
if(instr->GetDst())
{
returnLength = true;
}
IR::LabelInstr * bailOutLabelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true);
IR::LabelInstr *doneLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
instr->InsertAfter(doneLabel);
IR::LabelInstr *labelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true);
//Don't Generate fast path according to ShouldGenerateArrayFastPath()
//AND, Don't Generate fast path if the array is LikelyNative and the element is not specialized
if(ShouldGenerateArrayFastPath(baseOpnd, false, false, false) &&
!(baseOpnd->GetValueType().IsLikelyNativeArray() && srcOpnd->IsVar()))
{
GenerateFastPush(baseOpnd, srcOpnd, instr, instr, labelHelper, doneLabel, bailOutLabelHelper, returnLength);
instr->InsertBefore(labelHelper);
InsertBranch(Js::OpCode::Br, true, doneLabel, labelHelper);
}
if(baseOpnd->GetValueType().IsLikelyNativeArray())
{
//Lower IR::BailOutConventionalNativeArrayAccessOnly here.
LowerOneBailOutKind(instr, IR::BailOutConventionalNativeArrayAccessOnly, false, false);
instr->InsertAfter(bailOutLabelHelper);
InsertBranch(Js::OpCode::Br, doneLabel, bailOutLabelHelper);
}
GenerateHelperToArrayPushFastPath(instr, bailOutLabelHelper);
}
bool Lowerer::GenerateFastPop(IR::Opnd *baseOpndParam, IR::Instr *callInstr, IR::LabelInstr *labelHelper, IR::LabelInstr *doneLabel, IR::LabelInstr * bailOutLabelHelper)
{
Assert(ShouldGenerateArrayFastPath(baseOpndParam, false, false, false));
// TEST baseOpnd, AtomTag -- check baseOpnd not tagged int
// JNE $helper
// CMP [baseOpnd], JavascriptArray::`vtable' -- check baseOpnd isArray
// JNE $helper
// MOV r2, [baseOpnd + offset(length)] -- Load array length
IR::RegOpnd * baseOpnd = baseOpndParam->AsRegOpnd();
const IR::AutoReuseOpnd autoReuseBaseOpnd(baseOpnd, m_func);
ValueType arrValueType(baseOpndParam->GetValueType());
IR::RegOpnd *arrayOpnd = baseOpnd;
IR::RegOpnd *arrayLengthOpnd = nullptr;
IR::AutoReuseOpnd autoReuseArrayLengthOpnd;
if(!arrValueType.IsAnyOptimizedArray())
{
arrayOpnd = GenerateArrayTest(baseOpnd, bailOutLabelHelper, bailOutLabelHelper, callInstr, false, true);
arrValueType = arrayOpnd->GetValueType().ToDefiniteObject().SetHasNoMissingValues(false);
}
else if(arrayOpnd->IsArrayRegOpnd())
{
IR::ArrayRegOpnd *const arrayRegOpnd = arrayOpnd->AsArrayRegOpnd();
if(arrayRegOpnd->LengthSym())
{
arrayLengthOpnd = IR::RegOpnd::New(arrayRegOpnd->LengthSym(), arrayRegOpnd->LengthSym()->GetType(), m_func);
DebugOnly(arrayLengthOpnd->FreezeSymValue());
autoReuseArrayLengthOpnd.Initialize(arrayLengthOpnd, m_func);
}
}
const IR::AutoReuseOpnd autoReuseArrayOpnd(arrayOpnd, m_func);
IR::AutoReuseOpnd autoReuseMutableArrayLengthOpnd;
{
IR::RegOpnd *const mutableArrayLengthOpnd = IR::RegOpnd::New(TyUint32, m_func);
autoReuseMutableArrayLengthOpnd.Initialize(mutableArrayLengthOpnd, m_func);
if(arrayLengthOpnd)
{
// mov mutableArrayLength, arrayLength
InsertMove(mutableArrayLengthOpnd, arrayLengthOpnd, callInstr);
}
else
{
// MOV mutableArrayLength, [array + offset(length)] -- Load array length
// We know this index is safe since, so mark it as UInt32 to avoid unnecessary conversion/checks
InsertMove(
mutableArrayLengthOpnd,
IR::IndirOpnd::New(
arrayOpnd,
Js::JavascriptArray::GetOffsetOfLength(),
mutableArrayLengthOpnd->GetType(),
this->m_func),
callInstr);
}
arrayLengthOpnd = mutableArrayLengthOpnd;
}
InsertCompareBranch(arrayLengthOpnd, IR::IntConstOpnd::New(0, TyUint32, this->m_func), Js::OpCode::BrEq_A, true, bailOutLabelHelper, callInstr);
InsertSub(false, arrayLengthOpnd, arrayLengthOpnd, IR::IntConstOpnd::New(1, TyUint32, this->m_func),callInstr);
IR::IndirOpnd *arrayRef = IR::IndirOpnd::New(arrayOpnd, arrayLengthOpnd, TyVar, this->m_func);
arrayRef->GetBaseOpnd()->SetValueType(arrValueType);
//Array length is going to overflow, hence don't check for Array.length and Segment.length overflow.
bool isTypedArrayElement, isStringIndex;
IR::IndirOpnd *const indirOpnd =
GenerateFastElemICommon(
callInstr,
false,
arrayRef,
labelHelper,
labelHelper,
nullptr,
&isTypedArrayElement,
&isStringIndex,
nullptr,
nullptr,
nullptr /*pLabelSegmentLengthIncreased*/,
true /*checkArrayLengthOverflow*/,
true /* forceGenerateFastPath */,
false/* = returnLength */,
bailOutLabelHelper /* = bailOutLabelInstr*/);
Assert(!isTypedArrayElement);
Assert(indirOpnd);
return true;
}
bool Lowerer::GenerateFastPush(IR::Opnd *baseOpndParam, IR::Opnd *src, IR::Instr *callInstr,
IR::Instr *insertInstr, IR::LabelInstr *labelHelper, IR::LabelInstr *doneLabel, IR::LabelInstr * bailOutLabelHelper, bool returnLength)
{
Assert(ShouldGenerateArrayFastPath(baseOpndParam, false, false, false));
// TEST baseOpnd, AtomTag -- check baseOpnd not tagged int
// JNE $helper
// CMP [baseOpnd], JavascriptArray::`vtable' -- check baseOpnd isArray
// JNE $helper
// MOV r2, [baseOpnd + offset(length)] -- Load array length
IR::RegOpnd * baseOpnd = baseOpndParam->AsRegOpnd();
const IR::AutoReuseOpnd autoReuseBaseOpnd(baseOpnd, m_func);
ValueType arrValueType(baseOpndParam->GetValueType());
IR::RegOpnd *arrayOpnd = baseOpnd;
IR::RegOpnd *arrayLengthOpnd = nullptr;
IR::AutoReuseOpnd autoReuseArrayLengthOpnd;
if(!arrValueType.IsAnyOptimizedArray())
{
arrayOpnd = GenerateArrayTest(baseOpnd, labelHelper, labelHelper, insertInstr, false, true);
arrValueType = arrayOpnd->GetValueType().ToDefiniteObject().SetHasNoMissingValues(false);
}
else if(arrayOpnd->IsArrayRegOpnd())
{
IR::ArrayRegOpnd *const arrayRegOpnd = arrayOpnd->AsArrayRegOpnd();
if(arrayRegOpnd->LengthSym())
{
arrayLengthOpnd = IR::RegOpnd::New(arrayRegOpnd->LengthSym(), arrayRegOpnd->LengthSym()->GetType(), m_func);
DebugOnly(arrayLengthOpnd->FreezeSymValue());
autoReuseArrayLengthOpnd.Initialize(arrayLengthOpnd, m_func);
}
}
const IR::AutoReuseOpnd autoReuseArrayOpnd(arrayOpnd, m_func);
if(!arrayLengthOpnd)
{
// MOV arrayLength, [array + offset(length)] -- Load array length
// We know this index is safe since, so mark it as UInt32 to avoid unnecessary conversion/checks
arrayLengthOpnd = IR::RegOpnd::New(TyUint32, m_func);
autoReuseArrayLengthOpnd.Initialize(arrayLengthOpnd, m_func);
InsertMove(
arrayLengthOpnd,
IR::IndirOpnd::New(arrayOpnd, Js::JavascriptArray::GetOffsetOfLength(), arrayLengthOpnd->GetType(), this->m_func),
insertInstr);
}
IR::IndirOpnd *arrayRef = IR::IndirOpnd::New(arrayOpnd, arrayLengthOpnd, TyVar, this->m_func);
arrayRef->GetBaseOpnd()->SetValueType(arrValueType);
if (returnLength && src->IsEqual(insertInstr->GetDst()))
{
//If the dst is same as the src, then dst is going to be overridden by GenerateFastElemICommon in process of updating the length.
//Save it in a temp register.
IR::RegOpnd *opnd = IR::RegOpnd::New(src->GetType(), this->m_func);
InsertMove(opnd, src, insertInstr);
src = opnd;
}
//Array length is going to overflow, hence don't check for Array.length and Segment.length overflow.
bool isTypedArrayElement, isStringIndex;
IR::IndirOpnd *const indirOpnd =
GenerateFastElemICommon(
insertInstr,
true,
arrayRef,
labelHelper,
labelHelper,
nullptr,
&isTypedArrayElement,
&isStringIndex,
nullptr,
nullptr,
nullptr /*pLabelSegmentLengthIncreased*/,
false /*checkArrayLengthOverflow*/,
true /* forceGenerateFastPath */,
returnLength,
bailOutLabelHelper);
Assert(!isTypedArrayElement);
Assert(indirOpnd);
// MOV [r3 + r2], src
InsertMoveWithBarrier(indirOpnd, src, insertInstr);
return true;
}
bool
Lowerer::GenerateFastCharAt(Js::BuiltinFunction index, IR::Opnd *dst, IR::Opnd *srcStr, IR::Opnd *srcIndex, IR::Instr *callInstr,
IR::Instr *insertInstr, IR::LabelInstr *labelHelper, IR::LabelInstr *doneLabel)
{
// if regSrcStr is not object, JMP $helper
// CMP [regSrcStr + offset(type)] , static string type -- check base string type
// JNE $helper
// MOV r1, [regSrcStr + offset(m_pszValue)]
// TEST r1, r1
// JEQ $helper
// MOV r2, srcIndex
// If r2 is not int, JMP $helper
// Convert r2 to int
// CMP [regSrcStr + offsetof(length)], r2
// JBE $helper
// MOVZX r2, [r1 + r2 * 2]
// if (charAt)
// PUSH r1
// PUSH scriptContext
// CALL GetStringFromChar
// MOV dst, EAX
// else (charCodeAt)
// if (codePointAt)
// Lowerer.GenerateFastCodePointAt -- Common inline functions
// Convert r2 to Var
// MOV dst, r2
bool isInt = false;
bool isNotTaggedValue = false;
if (srcStr->IsRegOpnd())
{
if (srcStr->AsRegOpnd()->IsTaggedInt())
{
isInt = true;
}
else if (srcStr->AsRegOpnd()->IsNotTaggedValue())
{
isNotTaggedValue = true;
}
}
IR::RegOpnd *regSrcStr = GetRegOpnd(srcStr, insertInstr, m_func, TyVar);
if (!isNotTaggedValue)
{
if (!isInt)
{
m_lowererMD.GenerateObjectTest(regSrcStr, insertInstr, labelHelper);
}
else
{
// Insert delete branch opcode to tell the dbChecks not to assert on this helper label
IR::Instr *fakeBr = IR::PragmaInstr::New(Js::OpCode::DeletedNonHelperBranch, 0, this->m_func);
insertInstr->InsertBefore(fakeBr);
InsertBranch(Js::OpCode::Br, labelHelper, insertInstr);
}
}
// Bail out if index a constant and is less than zero.
if (srcIndex->IsAddrOpnd() && Js::TaggedInt::ToInt32(srcIndex->AsAddrOpnd()->m_address) < 0)
{
labelHelper->isOpHelper = false;
InsertBranch(Js::OpCode::Br, labelHelper, insertInstr);
return false;
}
GenerateStringTest(regSrcStr, insertInstr, labelHelper, nullptr, false);
// r1 contains the value of the char16* pointer inside JavascriptString.
// MOV r1, [regSrcStr + offset(m_pszValue)]
IR::RegOpnd *r1 = IR::RegOpnd::New(TyMachReg, this->m_func);
IR::IndirOpnd * indirOpnd = IR::IndirOpnd::New(regSrcStr->AsRegOpnd(), Js::JavascriptString::GetOffsetOfpszValue(), TyMachPtr, this->m_func);
InsertMove(r1, indirOpnd, insertInstr);
// TEST r1, r1 -- Null pointer test
// JEQ $helper
InsertTestBranch(r1, r1, Js::OpCode::BrEq_A, labelHelper, insertInstr);
IR::RegOpnd *strLength = IR::RegOpnd::New(TyUint32, m_func);
InsertMove(strLength, IR::IndirOpnd::New(regSrcStr, offsetof(Js::JavascriptString, m_charLength), TyUint32, this->m_func), insertInstr);
IR::Opnd* indexOpnd = nullptr;
if (srcIndex->IsAddrOpnd())
{
uint32 indexValue = Js::TaggedInt::ToUInt32(srcIndex->AsAddrOpnd()->m_address);
// CMP [regSrcStr + offsetof(length)], index
// Use unsigned compare, this should handle negative indexes as well (they become > INT_MAX)
// JBE $helper
InsertCompareBranch(strLength, IR::IntConstOpnd::New(indexValue, TyUint32, m_func), Js::OpCode::BrLe_A, true, labelHelper, insertInstr);
// Mask off the sign so that poisoning will work for negative indices
#if TARGET_32
uint32 maskedIndex = CONFIG_FLAG_RELEASE(PoisonStringLoad) ? (indexValue & INT32_MAX) : indexValue;
#else
uint32 maskedIndex = indexValue;
#endif
indirOpnd = IR::IndirOpnd::New(r1, maskedIndex * sizeof(char16), TyUint16, this->m_func);
indexOpnd = IR::IntConstOpnd::New(maskedIndex, TyMachPtr, m_func);
}
else
{
IR::RegOpnd *r2 = IR::RegOpnd::New(TyVar, this->m_func);
// MOV r2, srcIndex
InsertMove(r2, srcIndex, insertInstr);
r2 = GenerateUntagVar(r2, labelHelper, insertInstr);
// CMP [regSrcStr + offsetof(length)], r2
// Use unsigned compare, this should handle negative indexes as well (they become > INT_MAX)
// JBE $helper
InsertCompareBranch(strLength, r2, Js::OpCode::BrLe_A, true, labelHelper, insertInstr);
#if TARGET_32
if (CONFIG_FLAG_RELEASE(PoisonStringLoad))
{
// Mask off the sign so that poisoning will work for negative indices
InsertAnd(r2, r2, IR::IntConstOpnd::New(INT32_MAX, TyInt32, m_func), insertInstr);
}
#endif
if (r2->GetSize() != MachPtr)
{
r2 = r2->UseWithNewType(TyMachPtr, this->m_func)->AsRegOpnd();
}
indexOpnd = r2;
indirOpnd = IR::IndirOpnd::New(r1, r2, 1, TyUint16, this->m_func);
}
IR::RegOpnd* maskOpnd = nullptr;
if (CONFIG_FLAG_RELEASE(PoisonStringLoad))
{
maskOpnd = IR::RegOpnd::New(TyMachPtr, m_func);
if (strLength->GetSize() != MachPtr)
{
strLength = strLength->UseWithNewType(TyMachPtr, this->m_func)->AsRegOpnd();
}
InsertSub(false, maskOpnd, indexOpnd, strLength, insertInstr);
InsertShift(Js::OpCode::Shr_A, false, maskOpnd, maskOpnd, IR::IntConstOpnd::New(MachRegInt * 8 - 1, TyInt8, m_func), insertInstr);
if (maskOpnd->GetSize() != TyUint32)
{
maskOpnd = maskOpnd->UseWithNewType(TyUint32, this->m_func)->AsRegOpnd();
}
}
// MOVZX charReg, [r1 + r2 * 2] -- this is the value of the char
IR::RegOpnd *charReg = IR::RegOpnd::New(TyUint32, this->m_func);
InsertMove(charReg, indirOpnd, insertInstr);
if (CONFIG_FLAG_RELEASE(PoisonStringLoad))
{
InsertAnd(charReg, charReg, maskOpnd, insertInstr);
}
if (index == Js::BuiltinFunction::JavascriptString_CharAt)
{
IR::Opnd *resultOpnd;
if (dst->IsEqual(srcStr))
{
resultOpnd = IR::RegOpnd::New(TyVar, this->m_func);
}
else
{
resultOpnd = dst;
}
GenerateGetSingleCharString(charReg, resultOpnd, labelHelper, doneLabel, insertInstr, false);
}
else
{
Assert(index == Js::BuiltinFunction::JavascriptString_CharCodeAt || index == Js::BuiltinFunction::JavascriptString_CodePointAt);
if (index == Js::BuiltinFunction::JavascriptString_CodePointAt)
{
GenerateFastInlineStringCodePointAt(insertInstr, this->m_func, strLength, srcIndex, charReg, r1);
}
if (charReg->GetSize() != MachPtr)
{
charReg = charReg->UseWithNewType(TyMachPtr, this->m_func)->AsRegOpnd();
}
m_lowererMD.GenerateInt32ToVarConversion(charReg, insertInstr);
// MOV dst, charReg
InsertMove(dst, charReg, insertInstr);
}
return true;
}
IR::Opnd*
Lowerer::GenerateArgOutForInlineeStackArgs(IR::Instr* callInstr, IR::Instr* stackArgsInstr)
{
Assert(callInstr->m_func->IsInlinee());
Func *func = callInstr->m_func;
uint32 actualCount = func->actualCount - 1; // don't count this pointer
Assert(actualCount < Js::InlineeCallInfo::MaxInlineeArgoutCount);
const auto firstRealArgStackSym = func->GetInlineeArgvSlotOpnd()->m_sym->AsStackSym();
this->m_func->SetArgOffset(firstRealArgStackSym, firstRealArgStackSym->m_offset + MachPtr); //Start after this pointer
IR::SymOpnd *firstArg = IR::SymOpnd::New(firstRealArgStackSym, TyMachPtr, func);
const IR::AutoReuseOpnd autoReuseFirstArg(firstArg, func);
IR::RegOpnd* argInOpnd = IR::RegOpnd::New(TyMachReg, func);
const IR::AutoReuseOpnd autoReuseArgInOpnd(argInOpnd, func);
InsertLea(argInOpnd, firstArg, callInstr);
IR::IndirOpnd *argIndirOpnd = nullptr;
IR::Instr* argout = nullptr;
#if defined(_M_IX86)
// Maintain alignment
if ((actualCount & 1) == 0)
{
IR::Instr *alignPush = IR::Instr::New(Js::OpCode::PUSH, this->m_func);
alignPush->SetSrc1(IR::IntConstOpnd::New(1, TyInt32, this->m_func));
callInstr->InsertBefore(alignPush);
}
#endif
for(uint i = actualCount; i > 0; i--)
{
argIndirOpnd = IR::IndirOpnd::New(argInOpnd, (i - 1) * MachPtr, TyMachReg, func);
argout = IR::Instr::New(Js::OpCode::ArgOut_A_Dynamic, func);
argout->SetSrc1(argIndirOpnd);
callInstr->InsertBefore(argout);
// i represents ith arguments from actuals, with is i + 3 counting this, callInfo and function object
this->m_lowererMD.LoadDynamicArgument(argout, i + 3);
}
return IR::IntConstOpnd::New(func->actualCount, TyMachReg, func);
}
// For AMD64 and ARM only.
void
Lowerer::LowerInlineSpreadArgOutLoopUsingRegisters(IR::Instr *callInstr, IR::RegOpnd *indexOpnd, IR::RegOpnd *arrayElementsStartOpnd)
{
Func *const func = callInstr->m_func;
IR::LabelInstr *oneArgLabel = IR::LabelInstr::New(Js::OpCode::Label, func);
InsertCompareBranch(indexOpnd, IR::IntConstOpnd::New(1, TyUint8, func), Js::OpCode::BrEq_A, true, oneArgLabel, callInstr);
IR::LabelInstr *startLoopLabel = InsertLoopTopLabel(callInstr);
Loop * loop = startLoopLabel->GetLoop();
loop->regAlloc.liveOnBackEdgeSyms->Set(indexOpnd->m_sym->m_id);
loop->regAlloc.liveOnBackEdgeSyms->Set(arrayElementsStartOpnd->m_sym->m_id);
InsertSub(false, indexOpnd, indexOpnd, IR::IntConstOpnd::New(1, TyInt8, func), callInstr);
IR::IndirOpnd *elemPtrOpnd = IR::IndirOpnd::New(arrayElementsStartOpnd, indexOpnd, this->m_lowererMD.GetDefaultIndirScale(), TyMachPtr, func);
// Generate argout for n+2 arg (skipping function object + this)
IR::Instr *argout = IR::Instr::New(Js::OpCode::ArgOut_A_Dynamic, func);
// X64 requires a reg opnd
IR::RegOpnd *elemRegOpnd = IR::RegOpnd::New(TyMachPtr, func);
Lowerer::InsertMove(elemRegOpnd, elemPtrOpnd, callInstr);
argout->SetSrc1(elemRegOpnd);
argout->SetSrc2(indexOpnd);
callInstr->InsertBefore(argout);
this->m_lowererMD.LoadDynamicArgumentUsingLength(argout);
InsertCompareBranch(indexOpnd, IR::IntConstOpnd::New(1, TyUint8, func), Js::OpCode::BrNeq_A, true, startLoopLabel, callInstr);
// Emit final argument into register 4 on AMD64 and ARM
callInstr->InsertBefore(oneArgLabel);
argout = IR::Instr::New(Js::OpCode::ArgOut_A_Dynamic, func);
argout->SetSrc1(elemPtrOpnd);
callInstr->InsertBefore(argout);
this->m_lowererMD.LoadDynamicArgument(argout, 4); //4 to denote this is 4th register after this, callinfo & function object
}
IR::Instr *
Lowerer::LowerCallIDynamicSpread(IR::Instr *callInstr, ushort callFlags)
{
Assert(callInstr->m_opcode == Js::OpCode::CallIDynamicSpread);
IR::Instr * insertBeforeInstrForCFG = nullptr;
Func *const func = callInstr->m_func;
if (func->IsInlinee())
{
throw Js::RejitException(RejitReason::InlineSpreadDisabled);
}
IR::Instr *spreadArrayInstr = callInstr;
IR::SymOpnd *argLinkOpnd = spreadArrayInstr->UnlinkSrc2()->AsSymOpnd();
StackSym *argLinkSym = argLinkOpnd->m_sym->AsStackSym();
AssertMsg(argLinkSym->IsArgSlotSym() && argLinkSym->m_isSingleDef, "Arg tree not single def...");
argLinkOpnd->Free(this->m_func);
spreadArrayInstr = argLinkSym->m_instrDef;
Assert(spreadArrayInstr->m_opcode == Js::OpCode::ArgOut_A_SpreadArg);
IR::Opnd *arraySrcOpnd = spreadArrayInstr->UnlinkSrc1();
IR::RegOpnd *arrayOpnd = GetRegOpnd(arraySrcOpnd, spreadArrayInstr, func, TyMachPtr);
argLinkOpnd = spreadArrayInstr->UnlinkSrc2()->AsSymOpnd();
// Walk the arg chain and find the start call
argLinkSym = argLinkOpnd->m_sym->AsStackSym();
AssertMsg(argLinkSym->IsArgSlotSym() && argLinkSym->m_isSingleDef, "Arg tree not single def...");
argLinkOpnd->Free(this->m_func);
// Nothing to be done for the function object, emit as normal
IR::Instr *thisInstr = argLinkSym->m_instrDef;
IR::RegOpnd *thisOpnd = thisInstr->UnlinkSrc2()->AsRegOpnd();
argLinkSym = thisOpnd->m_sym->AsStackSym();
thisInstr->Unlink();
thisInstr->FreeDst();
// Remove the array ArgOut instr and StartCall, they are no longer needed
spreadArrayInstr->Unlink();
spreadArrayInstr->FreeDst();
IR::Instr *startCallInstr = argLinkSym->m_instrDef;
Assert(startCallInstr->m_opcode == Js::OpCode::StartCall);
insertBeforeInstrForCFG = startCallInstr->GetNextRealInstr();
startCallInstr->Remove();
IR::RegOpnd *argsLengthOpnd = IR::RegOpnd::New(TyUint32, func);
IR::IndirOpnd *arrayLengthPtrOpnd = IR::IndirOpnd::New(arrayOpnd, Js::JavascriptArray::GetOffsetOfLength(), TyUint32, func);
Lowerer::InsertMove(argsLengthOpnd, arrayLengthPtrOpnd, callInstr);
// Don't bother expanding args if there are zero
IR::LabelInstr *zeroArgsLabel = IR::LabelInstr::New(Js::OpCode::Label, func);
InsertCompareBranch(argsLengthOpnd, IR::IntConstOpnd::New(0, TyInt8, func), Js::OpCode::BrEq_A, true, zeroArgsLabel, callInstr);
IR::RegOpnd *indexOpnd = IR::RegOpnd::New(TyUint32, func);
Lowerer::InsertMove(indexOpnd, argsLengthOpnd, callInstr);
// Get the array head offset and length
IR::IndirOpnd *arrayHeadPtrOpnd = IR::IndirOpnd::New(arrayOpnd, Js::JavascriptArray::GetOffsetOfHead(), TyMachPtr, func);
IR::RegOpnd *arrayElementsStartOpnd = IR::RegOpnd::New(TyMachPtr, func);
InsertAdd(false, arrayElementsStartOpnd, arrayHeadPtrOpnd, IR::IntConstOpnd::New(offsetof(Js::SparseArraySegment<Js::Var>, elements), TyUint8, func), callInstr);
this->m_lowererMD.LowerInlineSpreadArgOutLoop(callInstr, indexOpnd, arrayElementsStartOpnd);
// Resume if we have zero args
callInstr->InsertBefore(zeroArgsLabel);
// Lower call
callInstr->m_opcode = Js::OpCode::CallIDynamic;
callInstr = m_lowererMD.LowerCallIDynamic(callInstr, thisInstr, argsLengthOpnd, callFlags, insertBeforeInstrForCFG);
return callInstr;
}
IR::Instr *
Lowerer::LowerCallIDynamic(IR::Instr * callInstr, ushort callFlags)
{
if (!this->m_func->GetHasStackArgs())
{
throw Js::RejitException(RejitReason::InlineApplyDisabled);
}
IR::Instr * insertBeforeInstrForCFG = nullptr;
// Lower args and look for StartCall
IR::Instr * argInstr = callInstr;
IR::SymOpnd * argLinkOpnd = argInstr->UnlinkSrc2()->AsSymOpnd();
StackSym * argLinkSym = argLinkOpnd->m_sym->AsStackSym();
AssertMsg(argLinkSym->IsArgSlotSym() && argLinkSym->m_isSingleDef, "Arg tree not single def...");
argLinkOpnd->Free(this->m_func);
argInstr = argLinkSym->m_instrDef;
Assert(argInstr->m_opcode == Js::OpCode::ArgOut_A_Dynamic);
IR::Instr* saveThisArgOutInstr = argInstr;
saveThisArgOutInstr->Unlink();
saveThisArgOutInstr->FreeDst();
argLinkOpnd = argInstr->UnlinkSrc2()->AsSymOpnd();
argLinkSym = argLinkOpnd->m_sym->AsStackSym();
AssertMsg(argLinkSym->IsArgSlotSym() && argLinkSym->m_isSingleDef, "Arg tree not single def...");
argLinkOpnd->Free(this->m_func);
argInstr = argLinkSym->m_instrDef;
Assert(argInstr->m_opcode == Js::OpCode::ArgOut_A_FromStackArgs);
IR::Opnd* argsLength = m_lowererMD.GenerateArgOutForStackArgs(callInstr, argInstr);
IR::RegOpnd* startCallDstOpnd = argInstr->UnlinkSrc2()->AsRegOpnd();
argLinkSym = startCallDstOpnd->m_sym->AsStackSym();
startCallDstOpnd->Free(this->m_func);
argInstr->Remove();// Remove ArgOut_A_FromStackArgs
argInstr = argLinkSym->m_instrDef;
Assert(argInstr->m_opcode == Js::OpCode::StartCall);
insertBeforeInstrForCFG = argInstr->GetNextRealInstr();
argInstr->Remove(); //Remove start call
return m_lowererMD.LowerCallIDynamic(callInstr, saveThisArgOutInstr, argsLength, callFlags, insertBeforeInstrForCFG);
}
//This is only for x64 & ARM.
IR::Opnd*
Lowerer::GenerateArgOutForStackArgs(IR::Instr* callInstr, IR::Instr* stackArgsInstr)
{
// For architectures were we only pass 4 parameters in registers, the
// generated code looks something like this:
// s25.var = LdLen_A s4.var
// s26.var = Ld_A s25.var
// BrEq_I4 $L3, s25.var,0 // If we have no further arguments to pass, don't pass them
// $L2:
// BrEq_I4 $L4, s25.var,1 // Loop through the rest of the arguments, putting them on the stack
// s25.var = SUB_I4 s25.var, 0x1
// s10.var = LdElemI_A [s4.var+s25.var].var
// ArgOut_A_Dynamic s10.var, s25.var
// Br $L2
// $L4:
// s25.var = LdImm 0 // set s25 to 0, since it'll be 1 on the way into this block
// s10.var = LdElemI_A [s4.var + 0 * MachReg].var // The last one has to be put into argslot 4, since this is likely a register, not a stack location.
// ArgOut_A_Dynamic s10.var, 4
// $L3:
//
// Generalizing this for more register-passed parameters gives us code
// something like this:
// s25.var = LdLen_A s4.var
// s26.var = Ld_A s25.var
// BrLe_I4 $L3, s25.var,0 // If we have no further arguments to pass, don't pass them
// $L2:
// BrLe_I4 $L4, s25.var,INT_REG_COUNT-3 // Loop through the rest of the arguments up to the number passed in registers, putting them on the stack
// s25.var = SUB_I4 s25.var, 0x1
// s10.var = LdElemI_A [s4.var+s25.var].var
// ArgOut_A_Dynamic s10.var, s25.var
// Br $L2
// $L4:
// foreach of the remaining ones, N going down from (the number we can pass in regs -1) to 1 (0 omitted as we know that it'll be at least one register argument):
// BrEq_I4 $L__N, s25.var, N
// end foreach
// foreach of the remaining ones, N going down from (the number we can pass in regs -1) to 0:
// $L__N:
// s10.var = LdElemI_A [s4.var + N * MachReg].var // The last one has to be put into argslot 4, since this is likely a register, not a stack location.
// ArgOut_A_Dynamic s10.var, N+3
// end foreach
// $L3:
#if defined(_M_IX86)
// We get a compilation error on x86 due to assigning a negative to a uint
// TODO: don't even define this function on x86 - we Assert(false) anyway there.
// Alternatively, don't define when INT_ARG_REG_COUNT - 4 < 0
AssertOrFailFast(false);
return nullptr;
#else
Assert(stackArgsInstr->m_opcode == Js::OpCode::ArgOut_A_FromStackArgs);
Assert(callInstr->m_opcode == Js::OpCode::CallIDynamic);
this->m_lowererMD.GenerateFunctionObjectTest(callInstr, callInstr->GetSrc1()->AsRegOpnd(), false);
if (callInstr->m_func->IsInlinee())
{
return this->GenerateArgOutForInlineeStackArgs(callInstr, stackArgsInstr);
}
Func *func = callInstr->m_func;
IR::RegOpnd* stackArgs = stackArgsInstr->GetSrc1()->AsRegOpnd();
IR::RegOpnd* ldLenDstOpnd = IR::RegOpnd::New(TyMachReg, func);
const IR::AutoReuseOpnd autoReuseLdLenDstOpnd(ldLenDstOpnd, func);
IR::Instr* ldLen = IR::Instr::New(Js::OpCode::LdLen_A, ldLenDstOpnd ,stackArgs, func);
ldLenDstOpnd->SetValueType(ValueType::GetTaggedInt()); /*LdLen_A works only on stack arguments*/
callInstr->InsertBefore(ldLen);
GenerateFastRealStackArgumentsLdLen(ldLen);
IR::Instr* saveLenInstr = IR::Instr::New(Js::OpCode::MOV, IR::RegOpnd::New(TyMachReg, func), ldLenDstOpnd, func);
saveLenInstr->GetDst()->SetValueType(ValueType::GetTaggedInt());
callInstr->InsertBefore(saveLenInstr);
IR::LabelInstr* doneArgs = IR::LabelInstr::New(Js::OpCode::Label, func);
IR::Instr* branchDoneArgs = IR::BranchInstr::New(Js::OpCode::BrEq_I4, doneArgs, ldLenDstOpnd, IR::IntConstOpnd::New(0, TyInt8, func),func);
callInstr->InsertBefore(branchDoneArgs);
this->m_lowererMD.EmitInt4Instr(branchDoneArgs);
IR::LabelInstr* startLoop = InsertLoopTopLabel(callInstr);
Loop * loop = startLoop->GetLoop();
IR::LabelInstr* endLoop = IR::LabelInstr::New(Js::OpCode::Label, func);
IR::Instr* branchOutOfLoop = IR::BranchInstr::New(Js::OpCode::BrLe_I4, endLoop, ldLenDstOpnd, IR::IntConstOpnd::New(INT_ARG_REG_COUNT - 3, TyInt8, func),func);
callInstr->InsertBefore(branchOutOfLoop);
this->m_lowererMD.EmitInt4Instr(branchOutOfLoop);
IR::Instr* subInstr = IR::Instr::New(Js::OpCode::Sub_I4, ldLenDstOpnd, ldLenDstOpnd, IR::IntConstOpnd::New(1, TyMachReg, func),func);
callInstr->InsertBefore(subInstr);
this->m_lowererMD.EmitInt4Instr(subInstr);
IR::IndirOpnd *nthArgument = IR::IndirOpnd::New(stackArgs, ldLenDstOpnd, TyMachReg, func);
IR::RegOpnd* ldElemDstOpnd = IR::RegOpnd::New(TyMachReg,func);
const IR::AutoReuseOpnd autoReuseldElemDstOpnd(ldElemDstOpnd, func);
IR::Instr* ldElem = IR::Instr::New(Js::OpCode::LdElemI_A, ldElemDstOpnd, nthArgument, func);
callInstr->InsertBefore(ldElem);
GenerateFastStackArgumentsLdElemI(ldElem);
IR::Instr* argout = IR::Instr::New(Js::OpCode::ArgOut_A_Dynamic, func);
argout->SetSrc1(ldElemDstOpnd);
argout->SetSrc2(ldLenDstOpnd);
callInstr->InsertBefore(argout);
this->m_lowererMD.LoadDynamicArgumentUsingLength(argout);
IR::BranchInstr *tailBranch = IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, startLoop, func);
callInstr->InsertBefore(tailBranch);
callInstr->InsertBefore(endLoop);
loop->regAlloc.liveOnBackEdgeSyms->Set(ldLenDstOpnd->m_sym->m_id);
// Note: This loop iteratively adds instructions in two locations; in the block
// of branches that jump to the "load elements to argOuts" instructions, and in
// the the block of load elements to argOuts instructions themselves.
// 4 to denote this is 4th register after this, callinfo & function object
// INT_ARG_REG_COUNT is the number of parameters passed in int regs
uint current_reg_pass = INT_ARG_REG_COUNT - 4;
do
{
// If we're on this pass we know we have to do at least one of these, so skip
// the branch if we're on the last one.
if (current_reg_pass != INT_ARG_REG_COUNT - 4)
{
IR::LabelInstr* loadBlockLabel = IR::LabelInstr::New(Js::OpCode::Label, func);
IR::Instr* branchToBlock = IR::BranchInstr::New(Js::OpCode::BrEq_I4, loadBlockLabel, ldLenDstOpnd, IR::IntConstOpnd::New(current_reg_pass + 1, TyInt8, func), func);
endLoop->InsertAfter(branchToBlock);
callInstr->InsertBefore(loadBlockLabel);
}
// TODO: We can further optimize this with a GenerateFastStackArgumentsLdElemI that can
// handle us passing along constant argument references and encode them into the offset
// instead of having to use an IndirOpnd; this would allow us to save a few bytes here,
// and reduce register pressure a hair
// stemp.var = LdImm current_reg_pass
IR::RegOpnd* localTemp = IR::RegOpnd::New(TyInt32, func);
// We need to make it a tagged int because GenerateFastStackArgumentsLdElemI asserts if
// it is not.
localTemp->SetValueType(ValueType::GetTaggedInt());
const IR::AutoReuseOpnd autoReuseldElemDstOpnd3(localTemp, func);
this->InsertMove(localTemp, IR::IntConstOpnd::New(current_reg_pass, TyInt8, func, true), callInstr);
// sTemp = LdElem_I [s4.var + current_reg_pass (aka stemp.var) ]
nthArgument = IR::IndirOpnd::New(stackArgs, localTemp, TyMachReg, func);
ldElemDstOpnd = IR::RegOpnd::New(TyMachReg, func);
const IR::AutoReuseOpnd autoReuseldElemDstOpnd2(ldElemDstOpnd, func);
ldElem = IR::Instr::New(Js::OpCode::LdElemI_A, ldElemDstOpnd, nthArgument, func);
callInstr->InsertBefore(ldElem);
GenerateFastStackArgumentsLdElemI(ldElem);
argout = IR::Instr::New(Js::OpCode::ArgOut_A_Dynamic, func);
argout->SetSrc1(ldElemDstOpnd);
callInstr->InsertBefore(argout);
this->m_lowererMD.LoadDynamicArgument(argout, current_reg_pass + 4);
}
while (current_reg_pass-- != 0);
callInstr->InsertBefore(doneArgs);
/*return the length which will be used for callInfo generations & stack allocation*/
return saveLenInstr->GetDst()->AsRegOpnd();
#endif
}
void
Lowerer::GenerateLoadStackArgumentByIndex(IR::Opnd *dst, IR::RegOpnd *indexOpnd, IR::Instr *instr, int32 offset, Func *func)
{
// Load argument set dst = [ebp + index].
IR::RegOpnd *ebpOpnd = IR::Opnd::CreateFramePointerOpnd(func);
IR::IndirOpnd *argIndirOpnd = nullptr;
// The stack looks like this:
// [new.target or FrameDisplay] <== EBP + formalParamOffset (4) + callInfo.Count
// arguments[n] <== EBP + formalParamOffset (4) + n
// ...
// arguments[1] <== EBP + formalParamOffset (4) + 2
// arguments[0] <== EBP + formalParamOffset (4) + 1
// this or new.target <== EBP + formalParamOffset (4)
// callinfo
// function object
// return addr
// EBP-> EBP chain
//actual arguments offset is LowererMD::GetFormalParamOffset() + 1 (this)
int32 actualOffset = GetFormalParamOffset() + offset;
Assert(GetFormalParamOffset() == 4);
const BYTE indirScale = this->m_lowererMD.GetDefaultIndirScale();
argIndirOpnd = IR::IndirOpnd::New(ebpOpnd, indexOpnd, indirScale, TyMachReg, this->m_func);
argIndirOpnd->SetOffset(actualOffset << indirScale);
Lowerer::InsertMove(dst, argIndirOpnd, instr);
}
//This function assumes there is stackargs bailout and index is always on the range.
bool
Lowerer::GenerateFastStackArgumentsLdElemI(IR::Instr* ldElem)
{
// MOV dst, ebp [(valueOpnd + 5) *4] // 5 for the stack layout
//
IR::IndirOpnd *indirOpnd = ldElem->GetSrc1()->AsIndirOpnd();
// Now load the index and check if it is an integer.
IR::RegOpnd *indexOpnd = indirOpnd->GetIndexOpnd();
Assert (indexOpnd && indexOpnd->IsTaggedInt());
if(ldElem->m_func->IsInlinee())
{
IR::IndirOpnd *argIndirOpnd = GetArgsIndirOpndForInlinee(ldElem, indexOpnd);
Lowerer::InsertMove(ldElem->GetDst(), argIndirOpnd, ldElem);
}
else
{
GenerateLoadStackArgumentByIndex(ldElem->GetDst(), indexOpnd, ldElem, indirOpnd->GetOffset() + 1, m_func); // +1 to offset 'this'
}
ldElem->Remove();
return false;
}
IR::IndirOpnd*
Lowerer::GetArgsIndirOpndForInlinee(IR::Instr* ldElem, IR::Opnd* valueOpnd)
{
Assert(ldElem->m_func->IsInlinee());
IR::IndirOpnd* argIndirOpnd = nullptr;
// Address of argument after 'this'
const auto firstRealArgStackSym = ldElem->m_func->GetInlineeArgvSlotOpnd()->m_sym->AsStackSym();
this->m_func->SetArgOffset(firstRealArgStackSym, firstRealArgStackSym->m_offset + MachPtr); //Start after this pointer
IR::SymOpnd *firstArg = IR::SymOpnd::New(firstRealArgStackSym, TyMachPtr, ldElem->m_func);
const IR::AutoReuseOpnd autoReuseFirstArg(firstArg, m_func);
IR::RegOpnd *const baseOpnd = IR::RegOpnd::New(TyMachReg, ldElem->m_func);
const IR::AutoReuseOpnd autoReuseBaseOpnd(baseOpnd, m_func);
InsertLea(baseOpnd, firstArg, ldElem);
if (valueOpnd->IsIntConstOpnd())
{
IntConstType offset = valueOpnd->AsIntConstOpnd()->GetValue() * MachPtr;
// TODO: Assert(Math::FitsInDWord(offset));
argIndirOpnd = IR::IndirOpnd::New(baseOpnd, (int32)offset, TyMachReg, ldElem->m_func);
}
else
{
Assert(valueOpnd->IsRegOpnd());
const BYTE indirScale = this->m_lowererMD.GetDefaultIndirScale();
argIndirOpnd = IR::IndirOpnd::New(baseOpnd, valueOpnd->AsRegOpnd(), indirScale, TyMachReg, ldElem->m_func);
}
return argIndirOpnd;
}
IR::IndirOpnd*
Lowerer::GetArgsIndirOpndForTopFunction(IR::Instr* ldElem, IR::Opnd* valueOpnd)
{
// Load argument set dst = [ebp + index] (or grab from the generator object if m_func is a generator function).
IR::RegOpnd *baseOpnd = m_func->GetJITFunctionBody()->IsCoroutine() ? LoadGeneratorArgsPtr(ldElem) : IR::Opnd::CreateFramePointerOpnd(m_func);
IR::IndirOpnd* argIndirOpnd = nullptr;
// The stack looks like this:
// ...
// arguments[1]
// arguments[0]
// this
// callinfo
// function object
// return addr
// EBP-> EBP chain
//actual arguments offset is LowererMD::GetFormalParamOffset() + 1 (this)
uint16 actualOffset = m_func->GetJITFunctionBody()->IsCoroutine() ? 1 : GetFormalParamOffset() + 1; //5
Assert(actualOffset == 5 || m_func->GetJITFunctionBody()->IsGenerator());
if (valueOpnd->IsIntConstOpnd())
{
IntConstType offset = (valueOpnd->AsIntConstOpnd()->GetValue() + actualOffset) * MachPtr;
// TODO: Assert(Math::FitsInDWord(offset));
argIndirOpnd = IR::IndirOpnd::New(baseOpnd, (int32)offset, TyMachReg, this->m_func);
}
else
{
const BYTE indirScale = this->m_lowererMD.GetDefaultIndirScale();
argIndirOpnd = IR::IndirOpnd::New(baseOpnd->AsRegOpnd(), valueOpnd->AsRegOpnd(), indirScale, TyMachReg, this->m_func);
// Need to offset valueOpnd by 5. Instead of changing valueOpnd, we can just add an offset to the indir. Changing
// valueOpnd requires creation of a temp sym (if it's not already a temp) so that the value of the sym that
// valueOpnd represents is not changed.
argIndirOpnd->SetOffset(actualOffset << indirScale);
}
return argIndirOpnd;
}
void
Lowerer::GenerateCheckForArgumentsLength(IR::Instr* ldElem, IR::LabelInstr* labelCreateHeapArgs, IR::Opnd* actualParamOpnd, IR::Opnd* valueOpnd, Js::OpCode opcode)
{
// Check if index < nr_actuals.
InsertCompare(actualParamOpnd, valueOpnd, ldElem);
// Jump to helper if index >= nr_actuals.
// Do an unsigned check here so that a negative index will also fail.
// (GenerateLdValueFromCheckedIndexOpnd does not guarantee positive index on x86.)
InsertBranch(opcode, true, labelCreateHeapArgs, ldElem);
}
bool
Lowerer::GenerateFastArgumentsLdElemI(IR::Instr* ldElem, IR::LabelInstr *labelFallThru)
{
// ---GenerateSmIntTest
// ---GenerateLdValueFromCheckedIndexOpnd
// ---LoadInputParamCount
// CMP actualParamOpnd, valueOpnd //Compare between the actual count & the index count (say i in arguments[i])
// JLE $labelCreateHeapArgs
// MOV dst, ebp [(valueOpnd + 5) *4] // 5 for the stack layout
// JMP $fallthrough
//
//labelCreateHeapArgs:
// ---Bail out to create Heap Arguments object
Assert(ldElem->DoStackArgsOpt());
IR::IndirOpnd *indirOpnd = ldElem->GetSrc1()->AsIndirOpnd();
bool isInlinee = ldElem->m_func->IsInlinee();
Func *func = ldElem->m_func;
IR::LabelInstr *labelCreateHeapArgs = IR::LabelInstr::New(Js::OpCode::Label, func, true);
// Now load the index and check if it is an integer.
bool emittedFastPath = false;
bool isNotInt = false;
IntConstType value = 0;
IR::RegOpnd *indexOpnd = indirOpnd->GetIndexOpnd();
IR::Opnd *valueOpnd = nullptr;
IR::Opnd *actualParamOpnd = nullptr;
bool hasIntConstIndex = indirOpnd->TryGetIntConstIndexValue(true, &value, &isNotInt);
if (isNotInt || (isInlinee && hasIntConstIndex && value >= (ldElem->m_func->actualCount - 1)))
{
//Outside the range of actuals, skip
}
else if (labelFallThru != nullptr && !(hasIntConstIndex && value < 0)) //if index is not a negative int constant
{
if (isInlinee)
{
actualParamOpnd = IR::IntConstOpnd::New(ldElem->m_func->actualCount - 1, TyInt32, func);
}
else
{
// Load actuals count, LoadHeapArguments will reuse the generated instructions here
IR::Instr *loadInputParamCountInstr = this->m_lowererMD.LoadInputParamCount(ldElem, -1 /* don't include 'this' while counting actuals. */);
actualParamOpnd = loadInputParamCountInstr->GetDst()->UseWithNewType(TyInt32,this->m_func);
}
if (hasIntConstIndex)
{
//Constant index
valueOpnd = IR::IntConstOpnd::New(value, TyInt32, func);
}
else
{
//Load valueOpnd from the index
valueOpnd =
m_lowererMD.LoadNonnegativeIndex(
indexOpnd,
(
#if INT32VAR
indexOpnd->GetType() == TyUint32
#else
// On 32-bit platforms, skip the negative check since for now, the unsigned upper bound check covers it
true
#endif
),
labelCreateHeapArgs,
labelCreateHeapArgs,
ldElem);
}
if (isInlinee)
{
if (!hasIntConstIndex)
{
//Runtime check if to make sure length is within the arguments.length range.
GenerateCheckForArgumentsLength(ldElem, labelCreateHeapArgs, valueOpnd, actualParamOpnd, Js::OpCode::BrGe_A);
}
}
else
{
GenerateCheckForArgumentsLength(ldElem, labelCreateHeapArgs, actualParamOpnd, valueOpnd, Js::OpCode::BrLe_A);
}
IR::Opnd *argIndirOpnd = nullptr;
if (isInlinee)
{
argIndirOpnd = GetArgsIndirOpndForInlinee(ldElem, valueOpnd);
}
else
{
argIndirOpnd = GetArgsIndirOpndForTopFunction(ldElem, valueOpnd);
}
Lowerer::InsertMove(ldElem->GetDst(), argIndirOpnd, ldElem);
// JMP $done
InsertBranch(Js::OpCode::Br, labelFallThru, ldElem);
// $labelCreateHeapArgs:
ldElem->InsertBefore(labelCreateHeapArgs);
emittedFastPath = true;
}
if (!emittedFastPath)
{
throw Js::RejitException(RejitReason::DisableStackArgOpt);
}
return emittedFastPath;
}
bool
Lowerer::GenerateFastRealStackArgumentsLdLen(IR::Instr *ldLen)
{
if(ldLen->m_func->IsInlinee())
{
//Get the length of the arguments
Lowerer::InsertMove(ldLen->GetDst(),
IR::IntConstOpnd::New(ldLen->m_func->actualCount - 1, TyUint32, ldLen->m_func),
ldLen);
}
else
{
IR::Instr *loadInputParamCountInstr = this->m_lowererMD.LoadInputParamCount(ldLen, -1);
IR::RegOpnd *actualCountOpnd = loadInputParamCountInstr->GetDst()->AsRegOpnd();
Lowerer::InsertMove(ldLen->GetDst(), actualCountOpnd, ldLen);
}
ldLen->Remove();
return false;
}
bool
Lowerer::GenerateFastArgumentsLdLen(IR::Instr *ldLen, IR::LabelInstr* labelFallThru)
{
// TEST argslot, argslot //Test if the arguments slot is zero
// JNE $helper
// actualCountOpnd <-LoadInputParamCount fastpath
// SHL actualCountOpnd, actualCountOpnd, 1 // Left shift for tagging
// INC actualCountOpnd // Tagging
// MOV dst, actualCountOpnd
// JMP $fallthrough
//$helper:
Assert(ldLen->DoStackArgsOpt());
if(ldLen->m_func->IsInlinee())
{
//Get the length of the arguments
Lowerer::InsertMove(ldLen->GetDst(),
IR::AddrOpnd::New(Js::TaggedInt::ToVarUnchecked(ldLen->m_func->actualCount - 1), IR::AddrOpndKindConstantVar, ldLen->m_func), // -1 to exclude this pointer
ldLen);
}
else
{
IR::Instr *loadInputParamCountInstr = this->m_lowererMD.LoadInputParamCount(ldLen, -1);
IR::RegOpnd *actualCountOpnd = loadInputParamCountInstr->GetDst()->AsRegOpnd();
this->m_lowererMD.GenerateInt32ToVarConversion(actualCountOpnd, ldLen);
Lowerer::InsertMove(ldLen->GetDst(), actualCountOpnd, ldLen);
}
return true;
}
IR::RegOpnd*
Lowerer::GenerateFunctionTypeFromFixedFunctionObject(IR::Instr *insertInstrPt, IR::Opnd* functionObjOpnd)
{
IR::RegOpnd * functionTypeRegOpnd = IR::RegOpnd::New(TyMachReg, this->m_func);
IR::Opnd *functionTypeOpnd = nullptr;
if(functionObjOpnd->IsAddrOpnd())
{
IR::AddrOpnd* functionObjAddrOpnd = functionObjOpnd->AsAddrOpnd();
// functionTypeRegOpnd = MOV [fixed function address + type offset]
functionObjAddrOpnd->m_address;
functionTypeOpnd = IR::MemRefOpnd::New((void *)((intptr_t)functionObjAddrOpnd->m_address + Js::RecyclableObject::GetOffsetOfType()), TyMachPtr, this->m_func,
IR::AddrOpndKindDynamicObjectTypeRef);
}
else
{
functionTypeOpnd = IR::IndirOpnd::New(functionObjOpnd->AsRegOpnd(), Js::RecyclableObject::GetOffsetOfType(), TyMachPtr, this->m_func);
}
Lowerer::InsertMove(functionTypeRegOpnd, functionTypeOpnd, insertInstrPt);
return functionTypeRegOpnd;
}
void
Lowerer::FinalLower()
{
this->m_lowererMD.FinalLower();
// We check if there are any lazy bailouts in
// LowererMD::FinalLower, so only insert the thunk
// if needed
if (this->m_func->HasLazyBailOut())
{
this->InsertLazyBailOutThunk();
}
// Ensure that the StartLabel and EndLabel are inserted
// before the prolog and after the epilog respectively
IR::LabelInstr * startLabel = m_func->GetFuncStartLabel();
if (startLabel != nullptr)
{
m_func->m_headInstr->InsertAfter(startLabel);
}
IR::LabelInstr * endLabel = m_func->GetFuncEndLabel();
if (endLabel != nullptr)
{
m_func->m_tailInstr->GetPrevRealInstr()->InsertBefore(endLabel);
}
}
void
Lowerer::InsertLazyBailOutThunk()
{
#if defined(_M_IX86) || defined(_M_X64)
if (!this->m_func->IsTopFunc())
{
return;
}
Assert(this->m_func->GetLazyBailOutRecordSlot() != nullptr);
IR::Instr *tailInstr = this->m_func->m_tailInstr;
// Label (LazyBailOutThunk):
IR::LabelInstr *lazyBailOutLabel = IR::LabelInstr::New(Js::OpCode::LazyBailOutThunkLabel, this->m_func, true /* isOpHelper */);
lazyBailOutLabel->m_hasNonBranchRef = true; // Make sure that this label isn't removed
LABELNAMESET(lazyBailOutLabel, "LazyBailOutThunk");
tailInstr->InsertBefore(lazyBailOutLabel);
#ifdef _M_X64
// 1. Save registers used for parameters, and rax, if necessary, into the shadow space allocated for register parameters:
// mov [rsp + 16], RegArg1 (if branchConditionOpnd)
// mov [rsp + 8], RegArg0
// mov [rsp], rax
extern const IRType RegTypes[RegNumCount];
const RegNum regs[3] = { RegRAX, RegArg0, RegArg1 };
for (int i = 2; i >= 0; i--)
{
RegNum reg = regs[i];
const IRType regType = RegTypes[reg];
Lowerer::InsertMove(
IR::SymOpnd::New(this->m_func->m_symTable->GetArgSlotSym(static_cast<Js::ArgSlot>(i + 1)), regType, this->m_func),
IR::RegOpnd::New(nullptr, reg, regType, this->m_func),
tailInstr
);
}
#endif
// 2. Always enable implicit call flag
// If StFld/StElem instructions have both LazyBailOut and BailOnImplicitCallPreop and the operation turns out to not
// be an implicit call, at that point, we have already disabled the implicit calls flag. We would then do lazy bailout
// and not go back to the remaining code. Therefore, we need to re-enable implicit calls again in the thunk.
IR::Opnd *disableImplicitCallFlagAddress = this->m_lowererMD.GenerateMemRef(
this->m_func->GetThreadContextInfo()->GetDisableImplicitFlagsAddr(),
TyInt8,
tailInstr /* insertBeforeInstr */
);
#ifdef _M_X64
// On x64, we might decide to load the address of implicit flag to a register,
// but since we are in Lowerer (past RegAlloc), all the operands won't have any
// registers assigned to them. We force them to be rcx (because they are going
// to be replaced anyway).
// TODO: This hack doesn't work with ARM/ARM64
// Will need to revisit this if we decide to do lazy bailout on those platforms
IR::Instr *moveInstr = Lowerer::InsertMove(
disableImplicitCallFlagAddress,
IR::IntConstOpnd::New(DisableImplicitNoFlag, TyInt8, this->m_func, true),
tailInstr /* insertBeforeInstr */
);
if (moveInstr->GetDst()->IsIndirOpnd())
{
moveInstr->GetDst()->AsIndirOpnd()->GetBaseOpnd()->AsRegOpnd()->SetReg(RegArg0);
}
if (moveInstr->m_prev->GetDst()->IsRegOpnd())
{
moveInstr->m_prev->GetDst()->AsRegOpnd()->SetReg(RegArg0);
}
#else
Lowerer::InsertMove(
disableImplicitCallFlagAddress,
IR::IntConstOpnd::New(DisableImplicitNoFlag, TyInt8, this->m_func, true),
tailInstr /* insertBeforeInstr */
);
#endif
#ifdef _M_X64
// 3. mov rcx, [rbp + offset] ; for bailout record
IR::RegOpnd *arg0 = IR::RegOpnd::New(nullptr, RegArg0, TyMachPtr, this->m_func);
IR::SymOpnd *bailOutRecordAddr = IR::SymOpnd::New(this->m_func->GetLazyBailOutRecordSlot(), TyMachPtr, this->m_func);
Lowerer::InsertMove(arg0, bailOutRecordAddr, tailInstr, false /* generateWriteBarrier */);
#else
// 3. Put the BailOutRecord on the stack for x86
IR::Instr *const newInstr = IR::Instr::New(Js::OpCode::PUSH, this->m_func);
IR::SymOpnd *bailOutRecordAddr = IR::SymOpnd::New(this->m_func->GetLazyBailOutRecordSlot(), TyMachPtr, this->m_func);
newInstr->SetSrc1(bailOutRecordAddr);
tailInstr->InsertBefore(newInstr);
#endif
// 4. call SaveAllRegistersAndBailOut
IR::Instr *callInstr = IR::Instr::New(Js::OpCode::Call, this->m_func);
callInstr->SetSrc1(IR::HelperCallOpnd::New(IR::HelperSaveAllRegistersAndBailOut, this->m_func));
tailInstr->InsertBefore(callInstr);
m_lowererMD.LowerCall(callInstr, 0);
// 5. jmp to function's epilog
IR::LabelInstr *exitLabel = this->m_func->m_exitInstr->GetPrevLabelInstr();
IR::BranchInstr *branchInstr = IR::BranchInstr::New(Js::OpCode::JMP, exitLabel, this->m_func);
tailInstr->InsertBefore(branchInstr);
#endif
}
void
Lowerer::EHBailoutPatchUp()
{
Assert(this->m_func->isPostLayout);
// 1. Insert return thunks for all the regions.
// 2. Set the hasBailedOut bit to true on all bailout paths in EH regions.
// 3. Insert code after every bailout in a try or catch region to save the return value on the stack, and jump to the return thunk (See Region.h) of that region.
// 4. Insert code right before the epilog, to restore the return value (saved in 2.) from a bailout into eax.
IR::LabelInstr * restoreReturnValueFromBailoutLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
IR::LabelInstr * epilogLabel;
IR::Instr * exitPrevInstr = this->m_func->m_exitInstr->GetPrevRealInstrOrLabel();
if (exitPrevInstr->IsLabelInstr())
{
epilogLabel = exitPrevInstr->AsLabelInstr();
}
else
{
epilogLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
this->m_func->m_exitInstr->InsertBefore(epilogLabel);
}
IR::Instr * tmpInstr = nullptr;
bool restoreReturnFromBailoutEmitted = false;
FOREACH_INSTR_IN_FUNC_EDITING(instr, instrNext, this->m_func)
{
if (instr->IsLabelInstr())
{
this->currentRegion = instr->AsLabelInstr()->GetRegion();
}
// Consider (radua): Assert(this->currentRegion) here?
if (this->currentRegion)
{
RegionType currentRegionType = this->currentRegion->GetType();
if (currentRegionType == RegionTypeTry || currentRegionType == RegionTypeCatch || currentRegionType == RegionTypeFinally)
{
if (this->currentRegion->IsNonExceptingFinally())
{
Region * parent = this->currentRegion->GetParent();
while (parent->IsNonExceptingFinally())
{
parent = parent->GetParent();
}
if (parent->GetType() == RegionTypeRoot)
{
continue;
}
}
this->InsertReturnThunkForRegion(this->currentRegion, restoreReturnValueFromBailoutLabel);
if (instr->HasBailOutInfo())
{
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)
{
this->EmitRestoreReturnValueFromEHBailout(restoreReturnValueFromBailoutLabel, epilogLabel);
restoreReturnFromBailoutEmitted = true;
}
}
}
}
}
NEXT_INSTR_IN_FUNC_EDITING
}
bool
Lowerer::GenerateFastLdFld(IR::Instr * const instrLdFld, IR::JnHelperMethod helperMethod, IR::JnHelperMethod polymorphicHelperMethod,
IR::LabelInstr ** labelBailOut, IR::RegOpnd* typeOpnd, bool* pIsHelper, IR::LabelInstr** pLabelHelper)
{
// Generates:
//
// r1 = object->type
// if (r1 is taggedInt) goto helper
// Load inline cache
// if monomorphic
// r2 = address of the monomorphic inline cache
// if polymorphic
// r2 = address of the polymorphic inline cache array
// r3 = (type >> PIC shift amount) & (PIC size - 1)
// r2 = r2 + r3
// Try load property using proto cache (if protoFirst)
// Try load property using local cache
// Try loading property using proto cache (if !protoFirst)
// Try loading property using flags cache
//
// Loading property using local cache:
// if (r1 == r2->u.local.type)
// result = load inline slot r2->u.local.slotIndex from r1
// goto fallthru
// if ((r1 | InlineCacheAuxSlotTypeTag) == r2->u.local.type)
// result = load aux slot r2->u.local.slotIndex from r1
// goto fallthru
//
// Loading property using proto cache:
// if (r1 == r2->u.proto.type)
// r3 = r2->u.proto.prototypeObject
// result = load inline slot r2->u.proto.slotIndex from r3
// goto fallthru
// if (r1 | InlineCacheAuxSlotTypeTag) == r2.u.proto.type)
// r3 = r2->u.proto.prototypeObject
// result = load aux slot r2->u.proto.slotIndex from r3
// goto fallthru
//
// Loading property using flags cache:
// if (r2->u.accessor.flags & (Js::InlineCacheGetterFlag | Js::InlineCacheSetterFlag) == 0)
// if (r1 == r2->u.accessor.type)
// result = load inline slot r2->u.accessor.slotIndex from r1
// goto fallthru
// if ((r1 | InlineCacheAuxSlotTypeTag) == r2->u.accessor.type)
// result = load aux slot r2->u.accessor.slotIndex from r1
// goto fallthru
//
// Loading an inline slot:
// result = [r1 + slotIndex * sizeof(Var)]
//
// Loading an aux slot:
// slotArray = r1->auxSlots
// result = [slotArray + slotIndex * sizeof(Var)]
//
// We only emit the code block for a type of cache (local/proto/flags) if the profile data
// indicates that type of cache was used to load the property in the past.
// We don't emit the type check with aux slot tag if the profile data indicates that we didn't
// load the property from an aux slot before.
// We don't emit the type check without an aux slot tag if the profile data indicates that we didn't
// load the property from an inline slot before.
IR::Opnd * opndSrc = instrLdFld->GetSrc1();
AssertMsg(opndSrc->IsSymOpnd() && opndSrc->AsSymOpnd()->IsPropertySymOpnd() && opndSrc->AsSymOpnd()->m_sym->IsPropertySym(), "Expected PropertySym as src of LdFld");
Assert(!instrLdFld->DoStackArgsOpt());
IR::PropertySymOpnd * propertySymOpnd = opndSrc->AsPropertySymOpnd();
PropertySym * propertySym = propertySymOpnd->m_sym->AsPropertySym();
PHASE_PRINT_TESTTRACE(
Js::ObjTypeSpecPhase,
this->m_func,
_u("Field load: %s, property ID: %d, func: %s, cache ID: %d, cloned cache: false\n"),
Js::OpCodeUtil::GetOpCodeName(instrLdFld->m_opcode),
propertySym->m_propertyId,
this->m_func->GetJITFunctionBody()->GetDisplayName(),
propertySymOpnd->m_inlineCacheIndex);
Assert(pIsHelper != nullptr);
bool& isHelper = *pIsHelper;
Assert(pLabelHelper != nullptr);
IR::LabelInstr*& labelHelper = *pLabelHelper;
bool doLocal = true;
bool doProto = instrLdFld->m_opcode == Js::OpCode::LdMethodFld
|| instrLdFld->m_opcode == Js::OpCode::LdRootMethodFld
|| instrLdFld->m_opcode == Js::OpCode::ScopedLdMethodFld;
bool doProtoFirst = doProto;
bool doInlineSlots = true;
bool doAuxSlots = true;
if (!PHASE_OFF(Js::ProfileBasedFldFastPathPhase, this->m_func) && instrLdFld->IsProfiledInstr())
{
IR::ProfiledInstr * profiledInstrLdFld = instrLdFld->AsProfiledInstr();
if (profiledInstrLdFld->u.FldInfo().flags != Js::FldInfo_NoInfo)
{
doProto = !!(profiledInstrLdFld->u.FldInfo().flags & Js::FldInfo_FromProto);
doLocal = !!(profiledInstrLdFld->u.FldInfo().flags & Js::FldInfo_FromLocal);
if ((profiledInstrLdFld->u.FldInfo().flags & (Js::FldInfo_FromInlineSlots | Js::FldInfo_FromAuxSlots)) == Js::FldInfo_FromInlineSlots)
{
// If the inline slots flag is set and the aux slots flag is not, only generate the inline slots check
doAuxSlots = false;
}
else if ((profiledInstrLdFld->u.FldInfo().flags & (Js::FldInfo_FromInlineSlots | Js::FldInfo_FromAuxSlots)) == Js::FldInfo_FromAuxSlots)
{
// If the aux slots flag is set and the inline slots flag is not, only generate the aux slots check
doInlineSlots = false;
}
}
else if (!profiledInstrLdFld->u.FldInfo().valueType.IsUninitialized())
{
// We have value type info about the field but no flags. This means we shouldn't generate any
// fast paths for this field load.
doLocal = false;
doProto = false;
}
}
if (!doLocal && !doProto)
{
return false;
}
IR::LabelInstr * labelFallThru = instrLdFld->GetOrCreateContinueLabel();
if (labelHelper == nullptr)
{
labelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, isHelper);
}
IR::RegOpnd * opndBase = propertySymOpnd->CreatePropertyOwnerOpnd(m_func);
bool usePolymorphicInlineCache = !!propertySymOpnd->m_runtimePolymorphicInlineCache;
IR::RegOpnd * opndInlineCache = IR::RegOpnd::New(TyMachPtr, this->m_func);
if (usePolymorphicInlineCache)
{
Lowerer::InsertMove(opndInlineCache, IR::AddrOpnd::New(propertySymOpnd->m_runtimePolymorphicInlineCache->GetInlineCachesAddr(), IR::AddrOpndKindDynamicInlineCache, this->m_func, true), instrLdFld);
}
else
{
Lowerer::InsertMove(opndInlineCache, this->LoadRuntimeInlineCacheOpnd(instrLdFld, propertySymOpnd, isHelper), instrLdFld);
}
if (typeOpnd == nullptr)
{
typeOpnd = IR::RegOpnd::New(TyMachPtr, this->m_func);
GenerateObjectTestAndTypeLoad(instrLdFld, opndBase, typeOpnd, labelHelper);
}
if (usePolymorphicInlineCache)
{
LowererMD::GenerateLoadPolymorphicInlineCacheSlot(instrLdFld, opndInlineCache, typeOpnd, propertySymOpnd->m_runtimePolymorphicInlineCache->GetSize());
}
IR::LabelInstr * labelNext = nullptr;
IR::Opnd * opndDst = instrLdFld->GetDst();
IR::RegOpnd * opndTaggedType = nullptr;
IR::BranchInstr * labelNextBranchToPatch = nullptr;
if (doProto && doProtoFirst)
{
if (doInlineSlots)
{
labelNext = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, isHelper);
labelNextBranchToPatch = GenerateProtoInlineCacheCheck(instrLdFld, typeOpnd, opndInlineCache, labelNext);
GenerateLdFldFromProtoInlineCache(instrLdFld, opndBase, opndDst, opndInlineCache, labelFallThru, true);
instrLdFld->InsertBefore(labelNext);
}
if (doAuxSlots)
{
if (opndTaggedType == nullptr)
{
opndTaggedType = IR::RegOpnd::New(TyMachPtr, this->m_func);
LowererMD::GenerateLoadTaggedType(instrLdFld, typeOpnd, opndTaggedType);
}
labelNext = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, isHelper);
labelNextBranchToPatch = GenerateProtoInlineCacheCheck(instrLdFld, opndTaggedType, opndInlineCache, labelNext);
GenerateLdFldFromProtoInlineCache(instrLdFld, opndBase, opndDst, opndInlineCache, labelFallThru, false);
instrLdFld->InsertBefore(labelNext);
}
}
if (doLocal)
{
if (doInlineSlots)
{
labelNext = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, isHelper);
labelNextBranchToPatch = GenerateLocalInlineCacheCheck(instrLdFld, typeOpnd, opndInlineCache, labelNext);
GenerateLdFldFromLocalInlineCache(instrLdFld, opndBase, opndDst, opndInlineCache, labelFallThru, true);
instrLdFld->InsertBefore(labelNext);
}
if (doAuxSlots)
{
if (opndTaggedType == nullptr)
{
opndTaggedType = IR::RegOpnd::New(TyMachPtr, this->m_func);
LowererMD::GenerateLoadTaggedType(instrLdFld, typeOpnd, opndTaggedType);
}
labelNext = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, isHelper);
labelNextBranchToPatch = GenerateLocalInlineCacheCheck(instrLdFld, opndTaggedType, opndInlineCache, labelNext);
GenerateLdFldFromLocalInlineCache(instrLdFld, opndBase, opndDst, opndInlineCache, labelFallThru, false);
instrLdFld->InsertBefore(labelNext);
}
}
if (doProto && !doProtoFirst)
{
if (doInlineSlots)
{
labelNext = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, isHelper);
labelNextBranchToPatch = GenerateProtoInlineCacheCheck(instrLdFld, typeOpnd, opndInlineCache, labelNext);
GenerateLdFldFromProtoInlineCache(instrLdFld, opndBase, opndDst, opndInlineCache, labelFallThru, true);
instrLdFld->InsertBefore(labelNext);
}
if (doAuxSlots)
{
if (opndTaggedType == nullptr)
{
opndTaggedType = IR::RegOpnd::New(TyMachPtr, this->m_func);
LowererMD::GenerateLoadTaggedType(instrLdFld, typeOpnd, opndTaggedType);
}
labelNext = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, isHelper);
labelNextBranchToPatch = GenerateProtoInlineCacheCheck(instrLdFld, opndTaggedType, opndInlineCache, labelNext);
GenerateLdFldFromProtoInlineCache(instrLdFld, opndBase, opndDst, opndInlineCache, labelFallThru, false);
instrLdFld->InsertBefore(labelNext);
}
}
Assert(labelNextBranchToPatch);
labelNextBranchToPatch->SetTarget(labelHelper);
labelNext->Remove();
// $helper:
// dst = CALL Helper(inlineCache, base, field, scriptContext)
// $fallthru:
isHelper = true;
// Return false to indicate the original instruction was not lowered. Caller will insert the helper label.
return false;
}
void
Lowerer::GenerateAuxSlotAdjustmentRequiredCheck(
IR::Instr * instrToInsertBefore,
IR::RegOpnd * opndInlineCache,
IR::LabelInstr * labelHelper)
{
// regSlotCap = MOV [&(inlineCache->u.local.rawUInt16)] // sized to 16 bits
IR::RegOpnd * regSlotCap = IR::RegOpnd::New(TyMachReg, instrToInsertBefore->m_func);
IR::IndirOpnd * memSlotCap = IR::IndirOpnd::New(opndInlineCache, (int32)offsetof(Js::InlineCache, u.local.rawUInt16), TyUint16, instrToInsertBefore->m_func);
InsertMove(regSlotCap, memSlotCap, instrToInsertBefore);
IR::IntConstOpnd * constSelectorBitCount = IR::IntConstOpnd::New(Js::InlineCache::CacheLayoutSelectorBitCount, TyUint16, instrToInsertBefore->m_func, /* dontEncode = */ true);
#if _M_ARM64
IR::Instr * testBranch = InsertBranch(Js::OpCode::TBZ, labelHelper, instrToInsertBefore);
testBranch->SetSrc1(regSlotCap);
testBranch->SetSrc2(constSelectorBitCount);
#else
// SAR regSlotCap, Js::InlineCache::CacheLayoutSelectorBitCount
InsertShiftBranch(Js::OpCode::Shr_A, regSlotCap, regSlotCap, constSelectorBitCount, Js::OpCode::BrNeq_A, true, labelHelper, instrToInsertBefore);
#endif
}
void
Lowerer::GenerateSetObjectTypeFromInlineCache(
IR::Instr * instrToInsertBefore,
IR::RegOpnd * opndBase,
IR::RegOpnd * opndInlineCache,
bool isTypeTagged)
{
// regNewType = MOV [&(inlineCache->u.local.type)]
IR::RegOpnd * regNewType = IR::RegOpnd::New(TyMachReg, instrToInsertBefore->m_func);
IR::IndirOpnd * memNewType = IR::IndirOpnd::New(opndInlineCache, (int32)offsetof(Js::InlineCache, u.local.type), TyMachReg, instrToInsertBefore->m_func);
InsertMove(regNewType, memNewType, instrToInsertBefore);
// AND regNewType, ~InlineCacheAuxSlotTypeTag
if (isTypeTagged)
{
// On 64-bit platforms IntConstOpnd isn't big enough to hold TyMachReg values.
IR::IntConstOpnd * constTypeTagComplement = IR::IntConstOpnd::New(~InlineCacheAuxSlotTypeTag, TyMachReg, instrToInsertBefore->m_func, /* dontEncode = */ true);
InsertAnd(regNewType, regNewType, constTypeTagComplement, instrToInsertBefore);
}
// MOV base->type, regNewType
IR::IndirOpnd * memObjType = IR::IndirOpnd::New(opndBase, Js::RecyclableObject::GetOffsetOfType(), TyMachReg, instrToInsertBefore->m_func);
InsertMove(memObjType, regNewType, instrToInsertBefore);
}
bool
Lowerer::GenerateFastStFld(IR::Instr * const instrStFld, IR::JnHelperMethod helperMethod, IR::JnHelperMethod polymorphicHelperMethod, IR::LabelInstr ** labelBailOut, IR::RegOpnd* typeOpnd,
bool* pIsHelper, IR::LabelInstr** pLabelHelper, bool withPutFlags, Js::PropertyOperationFlags flags)
{
// Generates:
//
// r1 = object->type
// if (r1 is taggedInt) goto helper
// Load inline cache
// if monomorphic
// r2 = address of the monomorphic inline cache
// if polymorphic
// r2 = address of the polymorphic inline cache array
// r3 = (type >> PIC shift amount) & (PIC size - 1)
// r2 = r2 + r3
// Try store property using local cache
//
// Loading property using local cache:
// if (r1 == r2->u.local.type)
// store value to inline slot r2->u.local.slotIndex on r1
// goto fallthru
// if ((r1 | InlineCacheAuxSlotTypeTag) == r2->u.local.type)
// store value to aux slot r2->u.local.slotIndex on r1
// goto fallthru
//
// Storing to an inline slot:
// [r1 + slotIndex * sizeof(Var)] = value
//
// Storing to an aux slot:
// slotArray = r1->auxSlots
// [slotArray + slotIndex * sizeof(Var)] = value
//
// We don't emit the type check with aux slot tag if the profile data indicates that we didn't
// store the property to an aux slot before.
// We don't emit the type check without an aux slot tag if the profile data indicates that we didn't
// store the property to an inline slot before.
IR::Opnd * opndSrc = instrStFld->GetSrc1();
IR::Opnd * opndDst = instrStFld->GetDst();
AssertMsg(opndDst->IsSymOpnd() && opndDst->AsSymOpnd()->IsPropertySymOpnd() && opndDst->AsSymOpnd()->m_sym->IsPropertySym(), "Expected PropertySym as dst of StFld");
IR::PropertySymOpnd * propertySymOpnd = opndDst->AsPropertySymOpnd();
PropertySym * propertySym = propertySymOpnd->m_sym->AsPropertySym();
PHASE_PRINT_TESTTRACE(
Js::ObjTypeSpecPhase,
this->m_func,
_u("Field store: %s, property ID: %u, func: %s, cache ID: %d, cloned cache: false\n"),
Js::OpCodeUtil::GetOpCodeName(instrStFld->m_opcode),
propertySym->m_propertyId,
this->m_func->GetJITFunctionBody()->GetDisplayName(),
propertySymOpnd->m_inlineCacheIndex);
Assert(pIsHelper != nullptr);
bool& isHelper = *pIsHelper;
Assert(pLabelHelper != nullptr);
IR::LabelInstr*& labelHelper = *pLabelHelper;
bool doStore = true;
bool doAdd = false;
bool doInlineSlots = true;
bool doAuxSlots = true;
if (!PHASE_OFF(Js::ProfileBasedFldFastPathPhase, this->m_func) && instrStFld->IsProfiledInstr())
{
IR::ProfiledInstr * profiledInstrStFld = instrStFld->AsProfiledInstr();
if (profiledInstrStFld->u.FldInfo().flags != Js::FldInfo_NoInfo)
{
if (!(profiledInstrStFld->u.FldInfo().flags & (Js::FldInfo_FromLocal | Js::FldInfo_FromLocalWithoutProperty)))
{
return false;
}
if (!PHASE_OFF(Js::AddFldFastPathPhase, this->m_func))
{
// We always try to do the store field fast path, unless the profile specifically says we never set, but always add a property here.
if ((profiledInstrStFld->u.FldInfo().flags & (Js::FldInfo_FromLocal | Js::FldInfo_FromLocalWithoutProperty)) == Js::FldInfo_FromLocalWithoutProperty)
{
doStore = false;
}
// On the other hand, we only emit the add field fast path, if the profile explicitly says we do add properties here.
if (!!(profiledInstrStFld->u.FldInfo().flags & Js::FldInfo_FromLocalWithoutProperty))
{
doAdd = true;
}
}
else
{
#if ENABLE_DEBUG_CONFIG_OPTIONS
char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
#endif
PHASE_PRINT_TRACE(Js::AddFldFastPathPhase, this->m_func,
_u("AddFldFastPath: function: %s(%s) property ID: %u no fast path, because the phase is off.\n"),
this->m_func->GetJITFunctionBody()->GetDisplayName(), this->m_func->GetDebugNumberSet(debugStringBuffer),
propertySym->m_propertyId);
}
if ((profiledInstrStFld->u.FldInfo().flags & (Js::FldInfo_FromInlineSlots | Js::FldInfo_FromAuxSlots)) == Js::FldInfo_FromInlineSlots)
{
// If the inline slots flag is set and the aux slots flag is not, only generate the inline slots check
doAuxSlots = false;
}
else if ((profiledInstrStFld->u.FldInfo().flags & (Js::FldInfo_FromInlineSlots | Js::FldInfo_FromAuxSlots)) == Js::FldInfo_FromAuxSlots)
{
// If the aux slots flag is set and the inline slots flag is not, only generate the aux slots check
doInlineSlots = false;
}
}
else if (!profiledInstrStFld->u.FldInfo().valueType.IsUninitialized())
{
// We have value type info about the field but no flags. This means we shouldn't generate any
// fast paths for this field store.
return false;
}
}
Assert(doStore || doAdd);
if (labelHelper == nullptr)
{
labelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true);
}
IR::LabelInstr * labelFallThru = instrStFld->GetOrCreateContinueLabel();
IR::RegOpnd * opndBase = propertySymOpnd->CreatePropertyOwnerOpnd(m_func);
bool usePolymorphicInlineCache = !!propertySymOpnd->m_runtimePolymorphicInlineCache;
if (doAdd)
{
#if ENABLE_DEBUG_CONFIG_OPTIONS
char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
#endif
PHASE_PRINT_TRACE(Js::AddFldFastPathPhase, this->m_func,
_u("AddFldFastPath: function: %s(%s) property ID: %d %s fast path for %s.\n"),
this->m_func->GetJITFunctionBody()->GetDisplayName(), this->m_func->GetDebugNumberSet(debugStringBuffer),
propertySym->m_propertyId,
usePolymorphicInlineCache ? _u("poly") : _u("mono"), doStore ? _u("store and add") : _u("add only"));
}
IR::RegOpnd * opndInlineCache = IR::RegOpnd::New(TyMachPtr, this->m_func);
if (usePolymorphicInlineCache)
{
Lowerer::InsertMove(opndInlineCache, IR::AddrOpnd::New(propertySymOpnd->m_runtimePolymorphicInlineCache->GetInlineCachesAddr(), IR::AddrOpndKindDynamicInlineCache, this->m_func, true), instrStFld);
}
else
{
Lowerer::InsertMove(opndInlineCache, this->LoadRuntimeInlineCacheOpnd(instrStFld, propertySymOpnd, isHelper), instrStFld);
}
if (typeOpnd == nullptr)
{
typeOpnd = IR::RegOpnd::New(TyMachPtr, this->m_func);
GenerateObjectTestAndTypeLoad(instrStFld, opndBase, typeOpnd, labelHelper);
}
if (usePolymorphicInlineCache)
{
LowererMD::GenerateLoadPolymorphicInlineCacheSlot(instrStFld, opndInlineCache, typeOpnd, propertySymOpnd->m_runtimePolymorphicInlineCache->GetSize());
}
IR::LabelInstr * labelNext = nullptr;
IR::RegOpnd * opndTaggedType = nullptr;
IR::BranchInstr * lastBranchToNext = nullptr;
if (doStore)
{
if (doInlineSlots)
{
labelNext = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, isHelper);
lastBranchToNext = GenerateLocalInlineCacheCheck(instrStFld, typeOpnd, opndInlineCache, labelNext);
this->GetLowererMD()->GenerateStFldFromLocalInlineCache(instrStFld, opndBase, opndSrc, opndInlineCache, labelFallThru, true);
instrStFld->InsertBefore(labelNext);
}
if (doAuxSlots)
{
if (opndTaggedType == nullptr)
{
opndTaggedType = IR::RegOpnd::New(TyMachPtr, this->m_func);
LowererMD::GenerateLoadTaggedType(instrStFld, typeOpnd, opndTaggedType);
}
labelNext = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, isHelper);
lastBranchToNext = GenerateLocalInlineCacheCheck(instrStFld, opndTaggedType, opndInlineCache, labelNext);
this->GetLowererMD()->GenerateStFldFromLocalInlineCache(instrStFld, opndBase, opndSrc, opndInlineCache, labelFallThru, false);
instrStFld->InsertBefore(labelNext);
}
}
if (doAdd)
{
if (doInlineSlots)
{
labelNext = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, isHelper);
lastBranchToNext = GenerateLocalInlineCacheCheck(instrStFld, typeOpnd, opndInlineCache, labelNext, true);
GenerateSetObjectTypeFromInlineCache(instrStFld, opndBase, opndInlineCache, false);
this->GetLowererMD()->GenerateStFldFromLocalInlineCache(instrStFld, opndBase, opndSrc, opndInlineCache, labelFallThru, true);
instrStFld->InsertBefore(labelNext);
}
if (doAuxSlots)
{
if (opndTaggedType == nullptr)
{
opndTaggedType = IR::RegOpnd::New(TyMachPtr, this->m_func);
LowererMD::GenerateLoadTaggedType(instrStFld, typeOpnd, opndTaggedType);
}
labelNext = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
lastBranchToNext = GenerateLocalInlineCacheCheck(instrStFld, opndTaggedType, opndInlineCache, labelNext, true);
GenerateAuxSlotAdjustmentRequiredCheck(instrStFld, opndInlineCache, labelHelper);
GenerateSetObjectTypeFromInlineCache(instrStFld, opndBase, opndInlineCache, true);
this->GetLowererMD()->GenerateStFldFromLocalInlineCache(instrStFld, opndBase, opndSrc, opndInlineCache, labelFallThru, false);
instrStFld->InsertBefore(labelNext);
}
}
Assert(lastBranchToNext);
lastBranchToNext->SetTarget(labelHelper);
labelNext->Remove();
// $helper:
// CALL Helper(inlineCache, base, field, src, scriptContext)
// $fallthru:
isHelper = true;
// Return false to indicate the original instruction was not lowered. Caller will insert the helper label.
return false;
}
bool Lowerer::GenerateFastStFldForCustomProperty(IR::Instr *const instr, IR::LabelInstr * *const labelHelperRef)
{
Assert(instr);
Assert(labelHelperRef);
Assert(!*labelHelperRef);
switch(instr->m_opcode)
{
case Js::OpCode::StFld:
case Js::OpCode::StFldStrict:
break;
default:
return false;
}
IR::SymOpnd *const symOpnd = instr->GetDst()->AsSymOpnd();
PropertySym *const propertySym = symOpnd->m_sym->AsPropertySym();
if(propertySym->m_propertyId != Js::PropertyIds::lastIndex || !symOpnd->IsPropertySymOpnd())
{
return false;
}
const ValueType objectValueType(symOpnd->GetPropertyOwnerValueType());
if(!objectValueType.IsLikelyRegExp())
{
return false;
}
if(instr->HasBailOutInfo())
{
const IR::BailOutKind bailOutKind = instr->GetBailOutKind();
if(!BailOutInfo::IsBailOutOnImplicitCalls(bailOutKind) || bailOutKind & IR::BailOutKindBits)
{
// Other bailout kinds will likely need bailout checks that would not be generated here. In particular, if a type
// check is necessary here to guard against downstream property accesses on the same object, the type check will
// fail and cause a bailout if the object is a RegExp object since the "lastIndex" property accesses are not cached.
return false;
}
}
Func *const func = instr->m_func;
IR::RegOpnd *const objectOpnd = symOpnd->CreatePropertyOwnerOpnd(func);
const IR::AutoReuseOpnd autoReuseObjectOpnd(objectOpnd, func);
IR::LabelInstr *labelHelper = nullptr;
if(!objectOpnd->IsNotTaggedValue())
{
// test object, 1
// jnz $helper
if(!labelHelper)
{
*labelHelperRef = labelHelper = IR::LabelInstr::New(Js::OpCode::Label, func, true);
}
m_lowererMD.GenerateObjectTest(objectOpnd, instr, labelHelper);
}
if(!objectValueType.IsObject())
{
// cmp [object], Js::JavascriptRegExp::vtable
// jne $helper
if(!labelHelper)
{
*labelHelperRef = labelHelper = IR::LabelInstr::New(Js::OpCode::Label, func, true);
}
InsertCompareBranch(
IR::IndirOpnd::New(objectOpnd, 0, TyMachPtr, func),
LoadVTableValueOpnd(instr, VTableValue::VtableJavascriptRegExp),
Js::OpCode::BrNeq_A,
labelHelper,
instr);
objectOpnd->SetValueType(objectValueType.ToDefiniteObject());
}
// mov [object + offset(lastIndexVar)], src
// mov [object + offset(lastIndexOrFlag)], Js::JavascriptRegExp::NotCachedValue
// jmp $done
InsertMove(
IR::IndirOpnd::New(objectOpnd, Js::JavascriptRegExp::GetOffsetOfLastIndexVar(), TyVar, func),
instr->GetSrc1(),
instr);
InsertMove(
IR::IndirOpnd::New(objectOpnd, Js::JavascriptRegExp::GetOffsetOfLastIndexOrFlag(), TyUint32, func),
IR::IntConstOpnd::New(Js::JavascriptRegExp::NotCachedValue, TyUint32, func, true),
instr);
InsertBranch(Js::OpCode::Br, instr->GetOrCreateContinueLabel(), instr);
return true;
}
IR::RegOpnd *
Lowerer::GenerateIsBuiltinRecyclableObject(IR::RegOpnd *regOpnd, IR::Instr *insertInstr, IR::LabelInstr *labelHelper, bool checkObjectAndDynamicObject, IR::LabelInstr *labelContinue, bool isInHelper)
{
// CMP [srcReg], Js::DynamicObject::`vtable'
// JEQ $fallThough
// MOV r1, [src1 + offset(type)] -- get the type id
// MOV r1, [r1 + offset(typeId)]
// ADD r1, ~TypeIds_LastStaticType -- if (typeId > TypeIds_LastStaticType && typeId <= TypeIds_LastBuiltinDynamicObject)
// CMP r1, (TypeIds_LastBuiltinDynamicObject - TypeIds_LastStaticType - 1)
// JA $helper
//fallThrough:
IR::LabelInstr *labelFallthrough = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, isInHelper);
if (checkObjectAndDynamicObject)
{
if (!regOpnd->IsNotTaggedValue())
{
m_lowererMD.GenerateObjectTest(regOpnd, insertInstr, labelHelper);
}
GenerateIsDynamicObject(regOpnd, insertInstr, labelFallthrough, true);
}
IR::RegOpnd * typeRegOpnd = IR::RegOpnd::New(TyMachReg, this->m_func);
IR::RegOpnd * typeIdRegOpnd = IR::RegOpnd::New(TyInt32, this->m_func);
IR::IndirOpnd *indirOpnd;
// MOV typeRegOpnd, [src1 + offset(type)]
indirOpnd = IR::IndirOpnd::New(regOpnd, Js::RecyclableObject::GetOffsetOfType(), TyMachReg, this->m_func);
InsertMove(typeRegOpnd, indirOpnd, insertInstr);
// MOV typeIdRegOpnd, [typeRegOpnd + offset(typeId)]
indirOpnd = IR::IndirOpnd::New(typeRegOpnd, Js::Type::GetOffsetOfTypeId(), TyInt32, this->m_func);
InsertMove(typeIdRegOpnd, indirOpnd, insertInstr);
// ADD typeIdRegOpnd, ~TypeIds_LastStaticType
InsertAdd(false, typeIdRegOpnd, typeIdRegOpnd,
IR::IntConstOpnd::New(~Js::TypeIds_LastStaticType, TyInt32, this->m_func, true), insertInstr);
// CMP typeIdRegOpnd, (TypeIds_LastBuiltinDynamicObject - TypeIds_LastStaticType - 1)
InsertCompare(
typeIdRegOpnd,
IR::IntConstOpnd::New(Js::TypeIds_LastBuiltinDynamicObject - Js::TypeIds_LastStaticType - 1, TyInt32, this->m_func),
insertInstr);
if (labelContinue)
{
// On success, go to continuation label.
InsertBranch(Js::OpCode::BrLe_A, true, labelContinue, insertInstr);
}
else
{
// On failure, go to helper.
InsertBranch(Js::OpCode::BrGt_A, true, labelHelper, insertInstr);
}
// $fallThrough
insertInstr->InsertBefore(labelFallthrough);
return typeRegOpnd;
}
void Lowerer::GenerateIsDynamicObject(IR::RegOpnd *regOpnd, IR::Instr *insertInstr, IR::LabelInstr *labelHelper, bool fContinueLabel)
{
// CMP [srcReg], Js::DynamicObject::`vtable'
InsertCompare(
IR::IndirOpnd::New(regOpnd, 0, TyMachPtr, m_func),
LoadVTableValueOpnd(insertInstr, VTableValue::VtableDynamicObject),
insertInstr);
if (fContinueLabel)
{
// JEQ $fallThough
Lowerer::InsertBranch(Js::OpCode::BrEq_A, labelHelper, insertInstr);
}
else
{
// JNE $helper
Lowerer::InsertBranch(Js::OpCode::BrNeq_A, labelHelper, insertInstr);
}
}
void Lowerer::GenerateIsRecyclableObject(IR::RegOpnd *regOpnd, IR::Instr *insertInstr, IR::LabelInstr *labelHelper, bool checkObjectAndDynamicObject)
{
// CMP [srcReg], Js::DynamicObject::`vtable'
// JEQ $fallThough
// MOV r1, [src1 + offset(type)] -- get the type id
// MOV r1, [r1 + offset(typeId)]
// ADD r1, ~TypeIds_LastJavascriptPrimitiveType -- if (typeId > TypeIds_LastJavascriptPrimitiveType && typeId <= TypeIds_LastTrueJavascriptObjectType)
// CMP r1, (TypeIds_LastTrueJavascriptObjectType - TypeIds_LastJavascriptPrimitiveType - 1)
// JA $helper
//fallThrough:
IR::LabelInstr *labelFallthrough = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
if (checkObjectAndDynamicObject)
{
if (!regOpnd->IsNotTaggedValue())
{
m_lowererMD.GenerateObjectTest(regOpnd, insertInstr, labelHelper);
}
this->GenerateIsDynamicObject(regOpnd, insertInstr, labelFallthrough, true);
}
IR::RegOpnd * typeRegOpnd = IR::RegOpnd::New(TyMachReg, this->m_func);
IR::RegOpnd * typeIdRegOpnd = IR::RegOpnd::New(TyInt32, this->m_func);
// MOV r1, [src1 + offset(type)]
InsertMove(typeRegOpnd, IR::IndirOpnd::New(regOpnd, Js::RecyclableObject::GetOffsetOfType(), TyMachReg, this->m_func), insertInstr);
// MOV r1, [r1 + offset(typeId)]
InsertMove(typeIdRegOpnd, IR::IndirOpnd::New(typeRegOpnd, Js::Type::GetOffsetOfTypeId(), TyInt32, this->m_func), insertInstr);
// ADD r1, ~TypeIds_LastJavascriptPrimitiveType
InsertAdd(false, typeIdRegOpnd, typeIdRegOpnd, IR::IntConstOpnd::New(~Js::TypeIds_LastJavascriptPrimitiveType, TyInt32, this->m_func, true), insertInstr);
// CMP r1, (TypeIds_LastTrueJavascriptObjectType - TypeIds_LastJavascriptPrimitiveType - 1)
InsertCompare(
typeIdRegOpnd,
IR::IntConstOpnd::New(Js::TypeIds_LastTrueJavascriptObjectType - Js::TypeIds_LastJavascriptPrimitiveType - 1, TyInt32, this->m_func),
insertInstr);
// JA $helper
InsertBranch(Js::OpCode::BrGe_A, true, labelHelper, insertInstr);
// $fallThrough
insertInstr->InsertBefore(labelFallthrough);
}
bool
Lowerer::GenerateLdThisCheck(IR::Instr * instr)
{
//
// If not a recyclable object, jump to $helper
// MOV dst, src1 -- return the object itself
// JMP $fallthrough
// $helper:
// (caller generates helper call)
// $fallthrough:
//
IR::RegOpnd * src1 = instr->GetSrc1()->AsRegOpnd();
IR::LabelInstr * helper = IR::LabelInstr::New(Js::OpCode::Label, m_func, true);
IR::LabelInstr * fallthrough = IR::LabelInstr::New(Js::OpCode::Label, m_func);
GenerateIsRecyclableObject(src1, instr, helper);
// MOV dst, src1
if (instr->GetDst() && !instr->GetDst()->IsEqual(src1))
{
InsertMove(instr->GetDst(), src1, instr);
}
// JMP $fallthrough
InsertBranch(Js::OpCode::Br, fallthrough, instr);
// $helper:
// (caller generates helper call)
// $fallthrough:
instr->InsertBefore(helper);
instr->InsertAfter(fallthrough);
return true;
}
//
// TEST src, Js::AtomTag
// JNE $done
// MOV typeReg, objectSrc + offsetof(RecyclableObject::type)
// CMP [typeReg + offsetof(Type::typeid)], TypeIds_ActivationObject
// JEQ $helper
// $done:
// MOV dst, src
// JMP $fallthru
// helper:
// MOV dst, undefined
// $fallthru:
bool
Lowerer::GenerateLdThisStrict(IR::Instr* instr)
{
IR::RegOpnd * src1 = instr->GetSrc1()->AsRegOpnd();
IR::RegOpnd * typeReg = IR::RegOpnd::New(TyMachReg, this->m_func);
IR::LabelInstr * done = IR::LabelInstr::New(Js::OpCode::Label, m_func);
IR::LabelInstr * fallthru = IR::LabelInstr::New(Js::OpCode::Label, m_func);
IR::LabelInstr * helper = IR::LabelInstr::New(Js::OpCode::Label, m_func, /*helper*/true);
bool assign = instr->GetDst() && !instr->GetDst()->IsEqual(src1);
if (!src1->IsNotTaggedValue())
{
// TEST src1, Js::AtomTag
// JNE $done
this->m_lowererMD.GenerateObjectTest(src1, instr, assign ? done : fallthru);
}
IR::IndirOpnd * indirOpnd = IR::IndirOpnd::New(src1, Js::RecyclableObject::GetOffsetOfType(), TyMachReg, this->m_func);
Lowerer::InsertMove(typeReg, indirOpnd, instr);
IR::IndirOpnd * typeID = IR::IndirOpnd::New(typeReg, Js::Type::GetOffsetOfTypeId(), TyInt32, this->m_func);
IR::Opnd * activationObject = IR::IntConstOpnd::New(Js::TypeIds_ActivationObject, TyMachReg, this->m_func);
Lowerer::InsertCompare(typeID, activationObject, instr);
// JEQ $helper
Lowerer::InsertBranch(Js::OpCode::BrEq_A, helper, instr);
if (assign)
{
// $done:
instr->InsertBefore(done);
// MOV dst, src
Lowerer::InsertMove(instr->GetDst(), src1, instr);
}
// JMP $fallthru
Lowerer::InsertBranch(Js::OpCode::Br, fallthru, instr);
instr->InsertBefore(helper);
if (instr->GetDst())
{
// MOV dst, undefined
Lowerer::InsertMove(instr->GetDst(), LoadLibraryValueOpnd(instr, LibraryValue::ValueUndefined), instr);
}
// $fallthru:
instr->InsertAfter(fallthru);
return true;
}
// given object instanceof function, functionReg is a register with function,
// objectReg is a register with instance and inlineCache is an InstIsInlineCache.
// We want to generate:
//
// fallback on helper (will patch the inline cache) if function does not match the cache
// MOV dst, Js::false
// CMP functionReg, [&(inlineCache->function)]
// JNE helper
//
// fallback if object is a tagged int
// TEST objectReg, Js::AtomTag
// JNE done
//
// return false if object is a primitive
// CMP [typeReg + offsetof(Type::typeid)], TypeIds_LastJavascriptPrimitiveType
// JLE done
// fallback if object's type is not the cached type
// MOV typeReg, objectSrc + offsetof(RecyclableObject::type)
// CMP typeReg, [&(inlineCache->type]
// JNE checkPrimType
// use the cached result and fallthrough
// MOV dst, [&(inlineCache->result)]
// JMP done
//
//
// $helper
// $done
bool
Lowerer::GenerateFastIsInst(IR::Instr * instr)
{
IR::LabelInstr * helper = IR::LabelInstr::New(Js::OpCode::Label, m_func, true);
IR::LabelInstr * done = IR::LabelInstr::New(Js::OpCode::Label, m_func);
IR::RegOpnd * typeReg = IR::RegOpnd::New(TyMachReg, this->m_func);
IR::Opnd * objectSrc;
IR::Opnd * functionSrc;
intptr_t inlineCache;
IR::Instr * instrArg;
// We are going to use the extra ArgOut_A instructions to lower the helper call later,
// so we leave them alone here and clean them up then.
inlineCache = instr->m_func->GetJITFunctionBody()->GetIsInstInlineCache(instr->GetSrc1()->AsIntConstOpnd()->AsUint32());
Assert(instr->GetSrc2()->AsRegOpnd()->m_sym->m_isSingleDef);
instrArg = instr->GetSrc2()->AsRegOpnd()->m_sym->m_instrDef;
objectSrc = instrArg->GetSrc1();
Assert(instrArg->GetSrc2()->AsRegOpnd()->m_sym->m_isSingleDef);
instrArg = instrArg->GetSrc2()->AsRegOpnd()->m_sym->m_instrDef;
functionSrc = instrArg->GetSrc1();
Assert(instrArg->GetSrc2() == nullptr);
// MOV dst, Js::false
InsertMove(instr->GetDst(), LoadLibraryValueOpnd(instr, LibraryValue::ValueFalse), instr);
IR::RegOpnd * functionReg = GetRegOpnd(functionSrc, instr, m_func, TyMachReg);
// CMP functionReg, [&(inlineCache->function)]
{
IR::Opnd* cacheFunction = IR::MemRefOpnd::New(inlineCache + Js::IsInstInlineCache::OffsetOfFunction(), TyMachReg, m_func, IR::AddrOpndKindDynamicIsInstInlineCacheFunctionRef);
InsertCompare(functionReg, cacheFunction, instr);
}
// JNE helper
InsertBranch(Js::OpCode::BrNeq_A, helper, instr);
IR::RegOpnd * objectReg = GetRegOpnd(objectSrc, instr, m_func, TyMachReg);
// TEST objectReg, Js::AtomTag
// JNE done
m_lowererMD.GenerateObjectTest(objectReg, instr, done);
// MOV typeReg, objectSrc + offsetof(RecyclableObject::type)
InsertMove(typeReg, IR::IndirOpnd::New(objectReg, Js::RecyclableObject::GetOffsetOfType(), TyMachReg, m_func), instr);
// CMP [typeReg + offsetof(Type::typeid)], TypeIds_LastJavascriptPrimitiveType
{
IR::IndirOpnd * typeId = IR::IndirOpnd::New(typeReg, Js::Type::GetOffsetOfTypeId(), TyInt32, m_func);
IR::IntConstOpnd * lastPrimitive = IR::IntConstOpnd::New(Js::TypeId::TypeIds_LastJavascriptPrimitiveType, TyInt32, m_func);
InsertCompare(typeId, lastPrimitive, instr);
}
// JLE done
InsertBranch(Js::OpCode::BrLe_A, done, instr);
// CMP typeReg, [&(inlineCache->type]
{
IR::Opnd * cacheType = IR::MemRefOpnd::New(inlineCache + Js::IsInstInlineCache::OffsetOfType(), TyMachReg, m_func, IR::AddrOpndKindDynamicIsInstInlineCacheTypeRef);
InsertCompare(typeReg, cacheType, instr);
}
// JNE helper
InsertBranch(Js::OpCode::BrNeq_A, helper, instr);
// MOV dst, [&(inlineCache->result)]
{
IR::Opnd * cacheResult = IR::MemRefOpnd::New(inlineCache + Js::IsInstInlineCache::OffsetOfResult(), TyMachReg, m_func, IR::AddrOpndKindDynamicIsInstInlineCacheResultRef);
InsertMove(instr->GetDst(), cacheResult, instr);
}
// JMP done
InsertBranch(Js::OpCode::Br, done, instr);
// LABEL helper
instr->InsertBefore(helper);
instr->InsertAfter(done);
return true;
}
void Lowerer::GenerateBooleanNegate(IR::Instr * instr, IR::Opnd * srcBool, IR::Opnd * dst)
{
// dst = src
// dst = dst ^ (true ^ false) (= !src)
Lowerer::InsertMove(dst, srcBool, instr);
ScriptContextInfo* sci = instr->m_func->GetScriptContextInfo();
IR::AddrOpnd* xorval = IR::AddrOpnd::New(sci->GetTrueAddr() ^ sci->GetFalseAddr(), IR::AddrOpndKindDynamicMisc, instr->m_func, true);
InsertXor(dst, dst, xorval, instr);
}
bool Lowerer::GenerateJSBooleanTest(IR::RegOpnd * regSrc, IR::Instr * insertInstr, IR::LabelInstr * labelTarget, bool fContinueLabel)
{
if (regSrc->GetValueType().IsBoolean())
{
if (fContinueLabel)
{
// JMP $labelTarget
InsertBranch(Js::OpCode::Br, labelTarget, insertInstr);
#if DBG
if (labelTarget->isOpHelper)
{
labelTarget->m_noHelperAssert = true;
}
#endif
}
return false;
}
IR::IndirOpnd * vtablePtrOpnd = IR::IndirOpnd::New(regSrc, 0, TyMachPtr, this->m_func);
IR::Opnd * jsBooleanVTable = LoadVTableValueOpnd(insertInstr, VTableValue::VtableJavascriptBoolean);
InsertCompare(vtablePtrOpnd, jsBooleanVTable, insertInstr);
if (fContinueLabel)
{
// JEQ $labelTarget
InsertBranch(Js::OpCode::BrEq_A, labelTarget, insertInstr);
// $helper
InsertLabel(true, insertInstr);
}
else
{
// JNE $labelTarget
InsertBranch(Js::OpCode::BrNeq_A, labelTarget, insertInstr);
}
return true;
}
bool Lowerer::GenerateFastEqBoolInt(IR::Instr * instr, bool *pNeedHelper, bool isInHelper)
{
Assert(instr);
// There's a total of 8 modes for this function, based on these inferred flags
bool isBranchNotCompare = instr->IsBranchInstr();
bool isStrict = false;
bool isNegOp = false;
switch (instr->m_opcode)
{
case Js::OpCode::BrSrEq_A:
case Js::OpCode::BrSrNotNeq_A:
case Js::OpCode::BrSrNeq_A:
case Js::OpCode::BrSrNotEq_A:
case Js::OpCode::CmSrEq_A:
case Js::OpCode::CmSrNeq_A:
isStrict = true;
break;
default:
break;
}
switch (instr->m_opcode)
{
case Js::OpCode::BrSrEq_A:
case Js::OpCode::BrSrNotNeq_A:
case Js::OpCode::CmSrEq_A:
case Js::OpCode::BrEq_A:
case Js::OpCode::BrNotNeq_A:
case Js::OpCode::CmEq_A:
isNegOp = false;
break;
case Js::OpCode::BrSrNeq_A:
case Js::OpCode::BrSrNotEq_A:
case Js::OpCode::CmSrNeq_A:
case Js::OpCode::BrNeq_A:
case Js::OpCode::BrNotEq_A:
case Js::OpCode::CmNeq_A:
isNegOp = true;
break;
default:
// This opcode is not one of the ones that should be handled here.
return false;
break;
}
IR::Opnd *src1 = instr->GetSrc1();
IR::Opnd *src2 = instr->GetSrc2();
// The instrucions given to this _should_ all be 2-arg.
Assert(src1 && src2);
if (!(src1 && src2))
{
return false;
}
// If it's a branch instruction, we'll want these to be defined
//IR::BranchInstr *instrBranch = nullptr;
IR::LabelInstr *targetInstr = nullptr;
IR::LabelInstr *labelFallthrough = nullptr;
if (isBranchNotCompare)
{
IR::BranchInstr * instrBranch = instr->AsBranchInstr();
targetInstr = instrBranch->GetTarget();
labelFallthrough = instrBranch->GetOrCreateContinueLabel(isInHelper);
}
// Assume we need the helper until we can show otherwise.
*pNeedHelper = true;
// If we don't know the final types well enough at JIT time, a helper block to set
// the inputs to the correct types will be needed.
IR::LabelInstr *labelHelper = nullptr;
// If we're doing a compare and can handle it early, then we want to skip the helper
IR::LabelInstr *labelDone = instr->GetOrCreateContinueLabel(isInHelper);
// Normallize for orderings
IR::Opnd *srcBool = nullptr;
IR::Opnd *srcInt = nullptr;
if (src1->GetValueType().IsLikelyBoolean() && src2->GetValueType().IsLikelyTaggedInt())
{
srcBool = src1;
srcInt = src2;
}
else if (src1->GetValueType().IsLikelyTaggedInt() && src2->GetValueType().IsLikelyBoolean())
{
srcInt = src1;
srcBool = src2;
}
else
{
return false;
}
// If either instruction is constant, we can simplify the check. If both are constant, we can eliminate it
bool srcIntConst = false;
bool srcIntConstVal = false;
// If we're comparing with a number that is not 0 or 1, then the two are inequal by default
bool srcIntIsBoolable = false;
bool srcBoolConst = false;
bool srcBoolConstVal = false;
if (srcInt->IsIntConstOpnd())
{
IR::IntConstOpnd * constSrcInt = srcInt->AsIntConstOpnd();
IntConstType constIntVal = constSrcInt->GetValue();
srcIntConst = true;
if (constIntVal == 0)
{
srcIntConstVal = false;
srcIntIsBoolable = true;
}
else if (constIntVal == 1)
{
srcIntConstVal = true;
srcIntIsBoolable = true;
}
}
else if (srcInt->IsAddrOpnd())
{
IR::AddrOpnd * addrSrcInt = srcInt->AsAddrOpnd();
if (!(addrSrcInt && addrSrcInt->IsVar() && Js::TaggedInt::Is(addrSrcInt->m_address)))
{
return false;
}
int32 constIntVal = Js::TaggedInt::ToInt32(addrSrcInt->m_address);
srcIntConst = true;
if (constIntVal == 0)
{
srcIntConstVal = false;
srcIntIsBoolable = true;
}
else if (constIntVal == 1)
{
srcIntConstVal = true;
srcIntIsBoolable = true;
}
}
else if (srcInt->IsConstOpnd())
{
// Not handled yet
return false;
}
if (srcBool->IsIntConstOpnd())
{
IR::IntConstOpnd * constSrcBool = srcBool->AsIntConstOpnd();
IntConstType constIntVal = constSrcBool->GetValue();
srcBoolConst = true;
srcBoolConstVal = constIntVal != 0;
}
else if (srcBool->IsAddrOpnd())
{
IR::AddrOpnd * addrSrcBool = srcInt->AsAddrOpnd();
if (!(addrSrcBool && addrSrcBool->IsVar() && Js::TaggedInt::Is(addrSrcBool->m_address)))
{
return false;
}
int32 value = Js::TaggedInt::ToInt32(addrSrcBool->m_address);
srcBoolConst = true;
srcBoolConstVal = value != 0;
}
else if (srcBool->IsConstOpnd())
{
// Not handled yet
return false;
}
// Do these checks here, since that way we avoid emitting instructions before exiting earlier
if (srcInt->GetValueType().IsTaggedInt() && srcBool->GetValueType().IsBoolean()) {
// ok, we know the types, so no helper needed
*pNeedHelper = false;
}
else
{
labelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true);
// check the types and jump to the helper if incorrect
if (!srcInt->IsConstOpnd() && !srcInt->GetValueType().IsTaggedInt())
{
this->m_lowererMD.GenerateSmIntTest(srcInt->AsRegOpnd(), instr, labelHelper);
}
if (!srcBool->IsConstOpnd() && !srcBool->GetValueType().IsBoolean())
{
if (!srcBool->GetValueType().IsObject())
{
this->m_lowererMD.GenerateObjectTest(srcBool->AsRegOpnd(), instr, labelHelper, false);
}
GenerateJSBooleanTest(srcBool->AsRegOpnd(), instr, labelHelper, false);
}
}
// At this point, we know both which operand is an integer and which is a boolean,
// whether either operand is constant, and what the constant true/false values are
// for any constant operands. This should allow us to emit some decent code.
LibraryValue equalResultValue = !isNegOp ? LibraryValue::ValueTrue : LibraryValue::ValueFalse;
LibraryValue inequalResultValue = !isNegOp ? LibraryValue::ValueFalse : LibraryValue::ValueTrue;
IR::LabelInstr *equalResultTarget = !isNegOp ? targetInstr : labelFallthrough;
IR::LabelInstr *inequalResultTarget = !isNegOp ? labelFallthrough : targetInstr;
// For the Sr instructions, we now know that the types are different, so we can immediately
// decide what the result will be.
if (isStrict)
{
if (isBranchNotCompare)
{
instr->InsertBefore(IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, inequalResultTarget, this->m_func));
#if DBG
// Since we're not making a non-helper path to one of the branches, we need to tell
// DbCheckPostLower that we are going to have a non-helper label without non-helper
// branches.
// Note: this following line isn't good practice in general
equalResultTarget->m_noHelperAssert = true;
#endif
}
else
{
Lowerer::InsertMove(instr->GetDst(), this->LoadLibraryValueOpnd(instr, inequalResultValue), instr);
instr->InsertBefore(IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, labelDone, this->m_func));
}
}
// Now that we've checked the types, we can lower some instructions to quickly do the check
// in the case that it's not a type-strict strict equality/inequality check.
else if (srcIntConst && srcBoolConst)
{
// If both arguments are constant, we can statically determine the result.
bool sameVal = srcIntConstVal == srcBoolConstVal;
if (isBranchNotCompare)
{
// For constant branches, branch to the target
Assert(instr);
IR::LabelInstr * target = sameVal && srcIntIsBoolable ? equalResultTarget : inequalResultTarget;
instr->InsertBefore(IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, target, this->m_func));
#if DBG
// Since we're not making a non-helper path to one of the branches, we need to tell
// DbCheckPostLower that we are going to have a non-helper label without non-helper
// branches.
// Note: this following line isn't good practice in general
(sameVal && srcIntIsBoolable ? inequalResultTarget : equalResultTarget)->m_noHelperAssert = true;
#endif
}
else
{
// For constant compares, load the constant result
Lowerer::InsertMove(instr->GetDst(), this->LoadLibraryValueOpnd(instr, sameVal && srcIntIsBoolable ? equalResultValue : inequalResultValue), instr);
instr->InsertBefore(IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, labelDone, this->m_func));
}
}
else if (!srcIntConst && !srcBoolConst)
{
// If neither is constant, we can still do a bit better than loading the helper
IR::LabelInstr * firstFalse = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
IR::LabelInstr * forceInequal = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
// We branch based on the zero-ness of the integer argument to two checks against the boolean argument
this->m_lowererMD.GenerateTaggedZeroTest(srcInt->AsRegOpnd(), instr, firstFalse);
// If it's not zero, then it's either 1, in which case it's true, or it's something else, in which
// case the two will compare as inequal
InsertCompareBranch(
IR::IntConstOpnd::New((((IntConstType)1) << Js::VarTag_Shift) + Js::AtomTag, IRType::TyVar, this->m_func, true),
srcInt->AsRegOpnd(),
Js::OpCode::BrNeq_A,
isBranchNotCompare ? inequalResultTarget : forceInequal, // in the case of branching, we can go straight to the inequal target; for compares, we need to load the value
instr,
true);
if (isBranchNotCompare)
{
// if the int evaluates to 1 (true)
InsertCompareBranch(
srcBool,
LoadLibraryValueOpnd(instr, LibraryValue::ValueTrue),
instr->m_opcode,
targetInstr,
instr);
instr->InsertBefore(IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, labelFallthrough, this->m_func));
// if the int evaluates to 0 (false)
instr->InsertBefore(firstFalse);
InsertCompareBranch(
srcBool,
LoadLibraryValueOpnd(instr, LibraryValue::ValueFalse),
instr->m_opcode,
targetInstr,
instr);
instr->InsertBefore(IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, labelFallthrough, this->m_func));
}
else
{
// the int resolves to 1 (true)
// Load either the bool or its complement into the dst reg, depending on the opcode
if (isNegOp)
{
GenerateBooleanNegate(instr, srcBool, instr->GetDst());
}
else
{
this->InsertMove(instr->GetDst(), srcBool, instr);
}
instr->InsertBefore(IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, labelDone, this->m_func));
// the int resolves to 0 (false)
// Handle the complement case
instr->InsertBefore(firstFalse);
if (!isNegOp)
{
GenerateBooleanNegate(instr, srcBool, instr->GetDst());
}
else
{
this->InsertMove(instr->GetDst(), srcBool, instr);
}
instr->InsertBefore(IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, labelDone, this->m_func));
// the int resolves to something other than 0 or 1 (inequal to a bool)
instr->InsertBefore(forceInequal);
Lowerer::InsertMove(instr->GetDst(), this->LoadLibraryValueOpnd(instr, inequalResultValue), instr);
instr->InsertBefore(IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, labelDone, this->m_func));
}
}
else if (srcIntConst)
{
if (isBranchNotCompare)
{
if (srcIntIsBoolable)
{
LibraryValue intval = srcIntConstVal ? LibraryValue::ValueTrue : LibraryValue::ValueFalse;
InsertCompareBranch(
srcBool,
LoadLibraryValueOpnd(instr, intval),
instr->m_opcode,
targetInstr,
instr);
instr->InsertBefore(IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, labelFallthrough, this->m_func));
}
else
{
// Since a constant int that isn't 0 or 1 will always be inequal to bools, just jump to the inequal result
instr->InsertBefore(IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, inequalResultTarget, this->m_func));
#if DBG
// Since we're not making a non-helper path to one of the branches, we need to tell
// DbCheckPostLower that we are going to have a non-helper label without non-helper
// branches.
// Note: this following line isn't good practice in general
equalResultTarget->m_noHelperAssert = true;
#endif
}
}
else
{
if (srcIntIsBoolable)
{
bool directPassthrough = isNegOp != srcIntConstVal;
if (directPassthrough)
{
// If this case is hit, the result value is the same as the value in srcBool
this->InsertMove(instr->GetDst(), srcBool, instr);
}
else
{
// Otherwise, the result value is the negation of the value in srcBool
GenerateBooleanNegate(instr, srcBool, instr->GetDst());
}
instr->InsertBefore(IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, labelDone, this->m_func));
}
else
{
Lowerer::InsertMove(instr->GetDst(), this->LoadLibraryValueOpnd(instr, inequalResultValue), instr);
instr->InsertBefore(IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, labelDone, this->m_func));
}
}
}
else if (srcBoolConst)
{
if (isBranchNotCompare)
{
this->m_lowererMD.GenerateTaggedZeroTest(srcInt->AsRegOpnd(), instr, srcBoolConstVal ? inequalResultTarget : equalResultTarget);
if (srcBoolConstVal)
{
// If it's not zero, then it's either 1, in which case it's true, or it's something else, in which
// case we have an issue.
InsertCompareBranch(IR::IntConstOpnd::New((((IntConstType)1) << Js::VarTag_Shift) + Js::AtomTag, IRType::TyVar, this->m_func), srcInt->AsRegOpnd(), Js::OpCode::BrNeq_A, inequalResultTarget, instr, true);
}
instr->InsertBefore(IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, srcBoolConstVal ? equalResultTarget : inequalResultTarget, this->m_func));
}
else
{
IR::LabelInstr* isNonZero = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
IR::LabelInstr* isZero = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
this->m_lowererMD.GenerateTaggedZeroTest(srcInt->AsRegOpnd(), instr, isZero);
if (srcBoolConstVal)
{
// If it's not zero, then it's either 1, in which case it's true, or it's something else, in which
// case we have an issue.
InsertCompareBranch(IR::IntConstOpnd::New((((IntConstType)1) << Js::VarTag_Shift) + Js::AtomTag, IRType::TyVar, this->m_func), srcInt->AsRegOpnd(), Js::OpCode::BrNeq_A, isZero, instr, true);
}
instr->InsertBefore(isNonZero);
Lowerer::InsertMove(instr->GetDst(), this->LoadLibraryValueOpnd(instr, srcBoolConstVal ? equalResultValue : inequalResultValue), instr);
instr->InsertBefore(IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, labelDone, this->m_func));
instr->InsertBefore(isZero);
Lowerer::InsertMove(instr->GetDst(), this->LoadLibraryValueOpnd(instr, !srcBoolConstVal ? equalResultValue : inequalResultValue), instr);
instr->InsertBefore(IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, labelDone, this->m_func));
}
}
if (*pNeedHelper)
{
instr->InsertBefore(labelHelper);
}
return true;
}
// Generate fast path for StrictEquals when one of the source have a definite valuetype
bool Lowerer::GenerateFastBrOrCmEqDefinite(IR::Instr * instr, IR::JnHelperMethod helperMethod, bool *pNeedHelper, bool isBranch, bool isInHelper)
{
IR::Opnd *src1 = instr->GetSrc1();
IR::Opnd *src2 = instr->GetSrc2();
if (!src1->GetValueType().IsDefinite() && !src2->GetValueType().IsDefinite())
{
return false;
}
if (src1->IsEqual(src2))
{
return false;
}
if (src1->GetValueType().IsDefinite() && src2->GetValueType().IsDefinite())
{
if (src1->IsTaggedValue() || src2->IsTaggedValue())
{
return true;
}
}
IR::LabelInstr * labelBranchSuccess = nullptr;
IR::LabelInstr * labelBranchFailure = nullptr;
IR::LabelInstr * labelFallThrough = instr->GetOrCreateContinueLabel();
IR::LabelInstr * labelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, isInHelper);
LibraryValue successValueType = ValueInvalid;
LibraryValue failureValueType = ValueInvalid;
IR::Opnd * definiteSrc = src1->GetValueType().IsDefinite() ? src1 : src2;
IR::Opnd * likelySrc = src1->GetValueType().IsDefinite() ? src2 : src1;
bool isEqual = !instr->IsNeq();
if (!isBranch)
{
labelBranchSuccess = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, false);
labelBranchFailure = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, false);
successValueType = isEqual ? LibraryValue::ValueTrue : LibraryValue::ValueFalse;
failureValueType = isEqual ? LibraryValue::ValueFalse : LibraryValue::ValueTrue;
}
else
{
labelBranchSuccess = isEqual ? instr->AsBranchInstr()->GetTarget() : labelFallThrough;
labelBranchFailure = isEqual ? labelFallThrough : instr->AsBranchInstr()->GetTarget();
}
Assert(likelySrc->IsRegOpnd());
if (definiteSrc->GetValueType().IsAnyArray() || definiteSrc->GetValueType().IsSymbol() || definiteSrc->GetValueType().IsBoolean() || definiteSrc->GetValueType().IsPrimitiveOrObject())
{
InsertCompareBranch(src1, src2, Js::OpCode::BrEq_A, labelBranchSuccess, instr);
IR::BranchInstr * branch = IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, labelBranchFailure, this->m_func);
instr->InsertBefore(branch);
*pNeedHelper = false;
}
else if (definiteSrc->GetValueType().IsObject() && !CONFIG_FLAG(ESBigInt))
{
InsertCompareBranch(src1, src2, Js::OpCode::BrEq_A, labelBranchSuccess, instr);
if (!likelySrc->GetValueType().IsDefinite())
{
m_lowererMD.GenerateObjectTest(likelySrc->AsRegOpnd(), instr, labelBranchFailure);
IR::RegOpnd * likelyTypeReg = IR::RegOpnd::New(TyMachReg, this->m_func);
IR::IndirOpnd * likelyType = IR::IndirOpnd::New(likelySrc->AsRegOpnd(), Js::RecyclableObject::GetOffsetOfType(), TyMachReg, this->m_func);
Lowerer::InsertMove(likelyTypeReg, likelyType, instr);
IR::Opnd *likelyFlags = IR::IndirOpnd::New(likelyTypeReg, Js::Type::GetOffsetOfFlags(), TyInt8, this->m_func);
InsertTestBranch(likelyFlags, IR::IntConstOpnd::New(TypeFlagMask_EngineExternal, TyInt8, this->m_func), Js::OpCode::BrNeq_A, labelHelper, instr);
}
else
{
*pNeedHelper = false;
}
IR::BranchInstr * branch = IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, labelBranchFailure, this->m_func);
instr->InsertBefore(branch);
}
else if (definiteSrc->IsTaggedInt())
{
InsertCompareBranch(src1, src2, Js::OpCode::BrEq_A, labelBranchSuccess, instr);
IR::BranchInstr * branch = IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, labelHelper, this->m_func);
instr->InsertBefore(branch);
}
else
{
return true;
}
if (!isBranch)
{
instr->InsertBefore(labelBranchSuccess);
InsertMove(instr->GetDst(), LoadLibraryValueOpnd(instr, successValueType), instr);
InsertBranch(Js::OpCode::Br, labelFallThrough, instr);
instr->InsertBefore(labelBranchFailure);
InsertMove(instr->GetDst(), LoadLibraryValueOpnd(instr, failureValueType), instr);
InsertBranch(Js::OpCode::Br, labelFallThrough, instr);
}
instr->InsertBefore(labelHelper);
return true;
}
// Generate fast path for Strict Equals when both sources are likely boolean/likely object/likely symbol
bool Lowerer::GenerateFastBrEqLikely(IR::BranchInstr * instrBranch, bool *pNeedHelper, bool isInHelper)
{
IR::Opnd *src1 = instrBranch->GetSrc1();
IR::Opnd *src2 = instrBranch->GetSrc2();
IR::LabelInstr *targetInstr = instrBranch->GetTarget();
IR::LabelInstr *labelEqualLikely = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, isInHelper);
IR::LabelInstr *labelTrue = instrBranch->GetOrCreateContinueLabel(isInHelper);
IR::LabelInstr *labelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true);
*pNeedHelper = true;
if (!this->GenerateFastBooleanAndObjectEqLikely(instrBranch, src1, src2, labelHelper, labelEqualLikely, pNeedHelper, isInHelper))
{
return false;
}
instrBranch->InsertBefore(labelEqualLikely);
IR::BranchInstr *newBranch = IR::BranchInstr::New(instrBranch->m_opcode, targetInstr, src1, src2, this->m_func);
instrBranch->InsertBefore(newBranch);
this->m_lowererMD.LowerCondBranch(newBranch);
newBranch = IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, labelTrue, this->m_func);
instrBranch->InsertBefore(newBranch);
instrBranch->InsertBefore(labelHelper);
return true;
}
bool Lowerer::GenerateFastBooleanAndObjectEqLikely(IR::Instr * instr, IR::Opnd *src1, IR::Opnd *src2, IR::LabelInstr * labelHelper, IR::LabelInstr * labelEqualLikely, bool *pNeedHelper, bool isInHelper)
{
*pNeedHelper = true;
if (!src1 || !src2)
{
return false;
}
bool isStrictCompare = false;
bool isStrictMode = this->m_func->GetJITFunctionBody()->IsStrictMode();
switch (instr->m_opcode)
{
case Js::OpCode::BrSrEq_A:
case Js::OpCode::BrSrNotNeq_A:
case Js::OpCode::BrSrNeq_A:
case Js::OpCode::BrSrNotEq_A:
case Js::OpCode::CmSrEq_A:
case Js::OpCode::CmSrNeq_A:
isStrictCompare = true;
break;
}
if (src1->GetValueType().IsLikelyBoolean() && src2->GetValueType().IsLikelyBoolean())
{
//
// Booleans
//
if (isStrictCompare)
{
if (!src1->GetValueType().IsBoolean() && !src2->GetValueType().IsBoolean())
{
this->m_lowererMD.GenerateObjectTest(src2->AsRegOpnd(), instr, labelHelper, false);
if (GenerateJSBooleanTest(src2->AsRegOpnd(), instr, labelEqualLikely, true))
{
instr->InsertBefore(IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, labelHelper, this->m_func));
}
}
else
{
*pNeedHelper = false;
}
}
else
{
this->m_lowererMD.GenerateObjectTest(src1->AsRegOpnd(), instr, labelHelper, false);
GenerateJSBooleanTest(src1->AsRegOpnd(), instr, labelHelper, false);
this->m_lowererMD.GenerateObjectTest(src2->AsRegOpnd(), instr, labelHelper, false);
if (GenerateJSBooleanTest(src2->AsRegOpnd(), instr, labelEqualLikely, true))
{
instr->InsertBefore(IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, labelHelper, this->m_func));
}
}
}
else if (src1->GetValueType().HasBeenObject() && src2->GetValueType().HasBeenObject())
{
//
// Objects
//
IR::LabelInstr *labelTypeIdCheck = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, isInHelper);
if (!isStrictCompare)
{
// If not strictBr, verify both sides are dynamic objects
this->m_lowererMD.GenerateObjectTest(src1->AsRegOpnd(), instr, labelHelper, false);
this->m_lowererMD.GenerateObjectTest(src2->AsRegOpnd(), instr, labelHelper, false);
GenerateIsDynamicObject(src1->AsRegOpnd(), instr, labelTypeIdCheck, false);
}
else
{
this->m_lowererMD.GenerateObjectTest(src2->AsRegOpnd(), instr, labelHelper, false);
}
GenerateIsDynamicObject(src2->AsRegOpnd(), instr, labelEqualLikely, true);
instr->InsertBefore(labelTypeIdCheck);
if (isStrictMode)
{
labelTypeIdCheck->isOpHelper = true;
IR::BranchInstr *branchToHelper = IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, labelHelper, this->m_func);
instr->InsertBefore(branchToHelper);
}
else
{
if (!ExternalLowerer::TryGenerateFastExternalEqTest(src1, src2, instr, labelHelper, labelEqualLikely, this, isStrictCompare, isInHelper))
{
if (!isStrictCompare)
{
GenerateIsBuiltinRecyclableObject(src1->AsRegOpnd(), instr, labelHelper, false /*checkObjectAndDynamicObject*/, nullptr /*labelContinue*/, isInHelper);
}
GenerateIsBuiltinRecyclableObject(src2->AsRegOpnd(), instr, labelHelper, false /*checkObjectAndDynamicObject*/, nullptr /*labelContinue*/, isInHelper);
}
}
}
else if (src1->GetValueType().IsLikelySymbol() && src2->GetValueType().IsLikelySymbol())
{
this->GenerateSymbolTest(src1->AsRegOpnd(), instr, labelHelper, nullptr, true);
this->GenerateSymbolTest(src2->AsRegOpnd(), instr, labelHelper, nullptr, true);
}
else
{
return false;
}
return true;
}
bool Lowerer::GenerateFastCmEqLikely(IR::Instr * instr, bool *pNeedHelper, bool isInHelper)
{
*pNeedHelper = false;
Assert(instr->m_opcode == Js::OpCode::CmSrEq_A ||
instr->m_opcode == Js::OpCode::CmSrNeq_A ||
instr->m_opcode == Js::OpCode::CmEq_A ||
instr->m_opcode == Js::OpCode::CmNeq_A);
bool isNegOp = false;
bool isStrict = false;
switch (instr->m_opcode)
{
case Js::OpCode::CmSrEq_A:
isStrict = true;
break;
case Js::OpCode::CmSrNeq_A:
isStrict = true;
case Js::OpCode::CmNeq_A:
isNegOp = true;
break;
}
IR::Opnd *src1 = instr->GetSrc1();
IR::Opnd *src2 = instr->GetSrc2();
IR::LabelInstr *labelEqualLikely = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, isInHelper);
IR::LabelInstr *labelDone = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, isInHelper);
IR::LabelInstr *labelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true);
if (!this->GenerateFastBooleanAndObjectEqLikely(instr, src1, src2, labelHelper, labelEqualLikely, pNeedHelper, isInHelper))
{
return false;
}
instr->InsertBefore(labelEqualLikely);
// $labelEqualLikely
//
// Will only come here for
// if src2 is dynamic object(matches Js::DynamicObject::`vtable'), for non strict cm both src1 and src2 should be dynamic object
// or if src2 is builtin recyclableobject(typeId > TypeIds_LastStaticType && typeId <= TypeIds_LastBuiltinDynamicObject)
// or if CustomExternalType with no operations usage flags
//
// src1->IsEqual(src2)
// MOV DST SUCCESS
// JMP $DONE
// CMP src1, src2
// MOV DST SUCCESS
// JEQ $DONE
// MOV DST FAILURE
// JMP $DONE
LibraryValue successValueType = !isNegOp ? LibraryValue::ValueTrue : LibraryValue::ValueFalse;
LibraryValue failureValueType = !isNegOp ? LibraryValue::ValueFalse : LibraryValue::ValueTrue;
if (src1->IsEqual(src2))
{
Lowerer::InsertMove(instr->GetDst(), this->LoadLibraryValueOpnd(instr, successValueType), instr);
instr->InsertBefore(IR::BranchInstr::New(this->m_lowererMD.MDUncondBranchOpcode, labelDone, this->m_func));
}
else
{
IR::LabelInstr *cmEqual = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, isInHelper);
this->InsertCompareBranch(src1, src2, isStrict ? Js::OpCode::BrSrEq_A : Js::OpCode::BrEq_A, cmEqual, instr);
Lowerer::InsertMove(instr->GetDst(), this->LoadLibraryValueOpnd(instr, failureValueType), instr);
instr->InsertBefore(IR::BranchInstr::New(this->m_lowererMD.MDUncondBranchOpcode, labelDone, this->m_func));
instr->InsertBefore(cmEqual);
Lowerer::InsertMove(instr->GetDst(), this->LoadLibraryValueOpnd(instr, successValueType), instr);
instr->InsertBefore(IR::BranchInstr::New(this->m_lowererMD.MDUncondBranchOpcode, labelDone, this->m_func));
}
instr->InsertBefore(labelHelper);
instr->InsertAfter(labelDone);
return true;
}
bool
Lowerer::GenerateFastBrOrCmString(IR::Instr* instr)
{
IR::RegOpnd *srcReg1 = instr->GetSrc1()->IsRegOpnd() ? instr->GetSrc1()->AsRegOpnd() : nullptr;
IR::RegOpnd *srcReg2 = instr->GetSrc2()->IsRegOpnd() ? instr->GetSrc2()->AsRegOpnd() : nullptr;
if (!srcReg1 ||
!srcReg2 ||
srcReg1->IsTaggedInt() ||
srcReg2->IsTaggedInt() ||
(!srcReg1->GetValueType().HasHadStringTag() && !srcReg2->GetValueType().IsString()) ||
(!srcReg2->GetValueType().HasHadStringTag() && !srcReg1->GetValueType().IsString()))
{
return false;
}
IR::LabelInstr *labelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true);
IR::LabelInstr *labelBranchFail = nullptr;
IR::LabelInstr *labelBranchSuccess = nullptr;
bool isEqual = false;
bool isStrict = false;
bool isBranch = true;
bool isCmNegOp = false;
switch (instr->m_opcode)
{
case Js::OpCode::BrSrEq_A:
case Js::OpCode::BrSrNotNeq_A:
isStrict = true;
case Js::OpCode::BrEq_A:
case Js::OpCode::BrNotNeq_A:
labelBranchFail = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
labelBranchSuccess = instr->AsBranchInstr()->GetTarget();
instr->InsertAfter(labelBranchFail);
isEqual = true;
break;
case Js::OpCode::BrSrNeq_A:
case Js::OpCode::BrSrNotEq_A:
isStrict = true;
case Js::OpCode::BrNeq_A:
case Js::OpCode::BrNotEq_A:
labelBranchSuccess = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
labelBranchFail = instr->AsBranchInstr()->GetTarget();
instr->InsertAfter(labelBranchSuccess);
isEqual = false;
break;
case Js::OpCode::CmSrEq_A:
isStrict = true;
case Js::OpCode::CmEq_A:
isEqual = true;
isBranch = false;
break;
case Js::OpCode::CmSrNeq_A:
isStrict = true;
case Js::OpCode::CmNeq_A:
isEqual = false;
isBranch = false;
isCmNegOp = true;
break;
default:
Assume(UNREACHED);
}
if (!isBranch)
{
labelBranchSuccess = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
labelBranchFail = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
}
GenerateFastStringCheck(instr, srcReg1, srcReg2, isEqual, isStrict, labelHelper, labelBranchSuccess, labelBranchFail);
IR::LabelInstr *labelFallthrough = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
if (!isBranch)
{
const LibraryValue successValueType = !isCmNegOp ? LibraryValue::ValueTrue : LibraryValue::ValueFalse;
const LibraryValue failureValueType = !isCmNegOp ? LibraryValue::ValueFalse : LibraryValue::ValueTrue;
instr->InsertBefore(labelBranchSuccess);
InsertMove(instr->GetDst(), LoadLibraryValueOpnd(instr, successValueType), instr);
InsertBranch(Js::OpCode::Br, labelFallthrough, instr);
instr->InsertBefore(labelBranchFail);
InsertMove(instr->GetDst(), LoadLibraryValueOpnd(instr, failureValueType), instr);
InsertBranch(Js::OpCode::Br, labelFallthrough, instr);
}
instr->InsertBefore(labelHelper);
instr->InsertAfter(labelFallthrough);
#if DBG
// The fast-path for strings assumes the case where 2 strings are equal is rare, and marks that path as 'helper'.
// This breaks the helper label dbchecks as it can result in non-helper blocks be reachable only from helper blocks.
// Use m_isHelperToNonHelperBranch and m_noHelperAssert to fix this.
IR::Instr *blockEndInstr;
if (isEqual)
{
blockEndInstr = labelHelper->GetNextBranchOrLabel();
}
else
{
blockEndInstr = instr->GetNextBranchOrLabel();
}
if (blockEndInstr->IsBranchInstr())
{
blockEndInstr->AsBranchInstr()->m_isHelperToNonHelperBranch = true;
}
labelFallthrough->m_noHelperAssert = true;
#endif
return true;
}
bool
Lowerer::GenerateFastStringCheck(IR::Instr *instr, IR::RegOpnd *srcReg1, IR::RegOpnd *srcReg2, bool isEqual, bool isStrict, IR::LabelInstr *labelHelper, IR::LabelInstr *labelBranchSuccess, IR::LabelInstr *labelBranchFail)
{
Assert(instr->m_opcode == Js::OpCode::BrSrEq_A ||
instr->m_opcode == Js::OpCode::BrSrNeq_A ||
instr->m_opcode == Js::OpCode::BrEq_A ||
instr->m_opcode == Js::OpCode::BrNeq_A ||
instr->m_opcode == Js::OpCode::BrSrNotEq_A ||
instr->m_opcode == Js::OpCode::BrSrNotNeq_A ||
instr->m_opcode == Js::OpCode::BrNotEq_A ||
instr->m_opcode == Js::OpCode::BrNotNeq_A ||
instr->m_opcode == Js::OpCode::CmEq_A ||
instr->m_opcode == Js::OpCode::CmNeq_A ||
instr->m_opcode == Js::OpCode::CmSrEq_A ||
instr->m_opcode == Js::OpCode::CmSrNeq_A);
// if src1 is not string
// generate object test, if not equal jump to $helper
// compare type check to string, if not jump to $helper
//
// if strict mode generate string test as above for src2 and jump to $failure if failed any time
// else if not strict generate string test as above for src2 and jump to $helper if failed any time
//
// Compare length of src1 and src2 if not equal goto $failure
//
// if src1 is not flat string jump to $helper
//
// if src1 and src2 m_pszValue pointer match goto $success
//
// if src2 is not flat string jump to $helper
//
// if first character of src1 and src2 doesn't match goto $failure
//
// shift left by 1 length of src1 (length*2)
//
// wmemcmp src1 and src2 flat strings till length * 2
//
// test eax (result of wmemcmp)
// if equal jump to $success else to $failure
//
// $success
// jmp to $fallthrough
// $failure
// jmp to $fallthrough
// $helper
//
// $fallthrough
// Generates:
// GenerateObjectTest(src1);
// CMP srcReg1, srcReg2
// JEQ $success
// MOV s1, [srcReg1 + offset(Type)]
// CMP type, static_string_type
// JNE $helper
// GenerateObjectTest(src2);
// MOV s2, [srcReg2 + offset(Type)]
// CMP type, static_string_type
// JNE $fail ; if src1 is string but not src2, src1 !== src2 if isStrict
// MOV s3, [srcReg1,offset(m_charLength)]
// CMP [srcReg2,offset(m_charLength)], s3
// JNE $fail <--- length check done
// MOV s4, [srcReg1,offset(m_pszValue)]
// CMP s4, 0
// JEQ $helper
// MOV s5, [srcReg2,offset(m_pszValue)]
// CMP s5, 0
// JEQ $helper
// MOV s6,[s4]
// CMP [s5], s6 -First character comparison
// JNE $fail
// SHL length, 1
// eax = wmemcmp(src1String, src2String, length*2)
// TEST eax, eax
// JEQ $success
// JMP $fail
IR::Instr* instrInsert = instr;
GenerateStringTest(srcReg1, instrInsert, labelHelper);
if (srcReg1->IsEqual(srcReg2))
{
InsertBranch(Js::OpCode::Br, labelBranchSuccess, instrInsert);
#if DBG
if (instr->IsBranchInstr())
{
// we might have other cases on helper path which will generate branch to the target
instr->AsBranchInstr()->GetTarget()->m_noHelperAssert = true;
}
#endif
return true;
}
// CMP srcReg1, srcReg2 - Ptr comparison
// JEQ $branchSuccess
InsertCompareBranch(srcReg1, srcReg2, Js::OpCode::BrEq_A, labelBranchSuccess, instrInsert);
if (isStrict)
{
GenerateStringTest(srcReg2, instrInsert, labelBranchFail);
}
else
{
GenerateStringTest(srcReg2, instrInsert, labelHelper);
}
if (isStrict && (srcReg1->m_sym->m_isStrEmpty || srcReg2->m_sym->m_isStrEmpty))
{
IR::RegOpnd* otherOpnd = srcReg1->m_sym->m_isStrEmpty ? srcReg2 : srcReg1;
InsertCompareBranch(IR::IndirOpnd::New(otherOpnd, Js::JavascriptString::GetOffsetOfcharLength(), TyUint32, m_func), IR::IntConstOpnd::New(0, TyUint32, this->m_func, true), Js::OpCode::BrNeq_A, labelBranchFail, instrInsert);
return true;
}
// MOV s3, [srcReg1,offset(m_charLength)]
// CMP [srcReg2,offset(m_charLength)], s3
// JNE $branchfail
IR::RegOpnd * src1LengthOpnd = IR::RegOpnd::New(TyUint32, m_func);
InsertMove(src1LengthOpnd, IR::IndirOpnd::New(srcReg1, Js::JavascriptString::GetOffsetOfcharLength(), TyUint32, m_func), instrInsert);
InsertCompareBranch(IR::IndirOpnd::New(srcReg2, Js::JavascriptString::GetOffsetOfcharLength(), TyUint32, m_func), src1LengthOpnd, Js::OpCode::BrNeq_A, labelBranchFail, instrInsert);
// MOV s4, [src1,offset(m_pszValue)]
// CMP s4, 0
// JEQ $helper
// MOV s5, [src2,offset(m_pszValue)]
// CMP s5, 0
// JEQ $helper
IR::RegOpnd * src1FlatString = IR::RegOpnd::New(TyMachPtr, m_func);
InsertMove(src1FlatString, IR::IndirOpnd::New(srcReg1, Js::JavascriptString::GetOffsetOfpszValue(), TyMachPtr, m_func), instrInsert);
InsertCompareBranch(src1FlatString, IR::IntConstOpnd::New(0, TyUint32, m_func), Js::OpCode::BrEq_A, labelHelper, instrInsert);
IR::RegOpnd * src2FlatString = IR::RegOpnd::New(TyMachPtr, m_func);
InsertMove(src2FlatString, IR::IndirOpnd::New(srcReg2, Js::JavascriptString::GetOffsetOfpszValue(), TyMachPtr, m_func), instrInsert);
InsertCompareBranch(src2FlatString, IR::IntConstOpnd::New(0, TyUint32, m_func), Js::OpCode::BrEq_A, labelHelper, instrInsert);
// MOV s6,[s4]
// CMP [s5], s6 -First character comparison
// JNE $branchfail
IR::RegOpnd * src1FirstChar = IR::RegOpnd::New(TyUint16, m_func);
InsertMove(src1FirstChar, IR::IndirOpnd::New(src1FlatString, 0, TyUint16, m_func), instrInsert);
InsertCompareBranch(IR::IndirOpnd::New(src2FlatString, 0, TyUint16, m_func), src1FirstChar, Js::OpCode::BrNeq_A, labelBranchFail, instrInsert);
// eax = wmemcmp(src1String, src2String, length)
m_lowererMD.LoadHelperArgument(instr, src1LengthOpnd);
m_lowererMD.LoadHelperArgument(instr, src1FlatString);
m_lowererMD.LoadHelperArgument(instr, src2FlatString);
IR::RegOpnd *dstOpnd = IR::RegOpnd::New(TyInt32, this->m_func);
IR::Instr *instrCall = IR::Instr::New(Js::OpCode::Call, dstOpnd, IR::HelperCallOpnd::New(IR::HelperWMemCmp, m_func), m_func);
instr->InsertBefore(instrCall);
m_lowererMD.LowerCall(instrCall, 3);
// TEST eax, eax
// JEQ success
InsertTestBranch(dstOpnd, dstOpnd, Js::OpCode::BrEq_A, labelBranchSuccess, instrInsert);
// JMP fail
InsertBranch(Js::OpCode::Br, labelBranchFail, instrInsert);
return true;
}
bool Lowerer::GenerateFastBrBool(IR::BranchInstr *const instr)
{
Assert(instr);
Assert(instr->m_opcode == Js::OpCode::BrFalse_A || instr->m_opcode == Js::OpCode::BrTrue_A);
Func *const func = instr->m_func;
if(!instr->GetSrc1()->IsRegOpnd())
{
LowererMD::ChangeToAssign(instr->HoistSrc1(Js::OpCode::Ld_A));
}
IR::RegOpnd *const src = instr->GetSrc1()->Copy(func)->AsRegOpnd();
const IR::AutoReuseOpnd autoReuseSrc(src, func);
const ValueType srcOriginalValueType(src->GetValueType());
ValueType srcValueType(srcOriginalValueType);
IR::LabelInstr *const labelTarget = instr->GetTarget();
IR::LabelInstr *const labelFallthrough = instr->GetOrCreateContinueLabel();
if(labelTarget == labelFallthrough)
{
// Nothing to do
instr->Remove();
return false;
}
const bool branchOnFalse = instr->m_opcode == Js::OpCode::BrFalse_A;
IR::LabelInstr *const labelFalse = branchOnFalse ? labelTarget : labelFallthrough;
IR::LabelInstr *const labelTrue = branchOnFalse ? labelFallthrough : labelTarget;
const Js::OpCode compareWithFalseBranchToTargetOpCode = branchOnFalse ? Js::OpCode::BrEq_A : Js::OpCode::BrNeq_A;
IR::LabelInstr *lastLabelBeforeHelper = nullptr;
/// Typespec'd float
if (instr->GetSrc1()->GetType() == TyFloat64)
{
InsertFloatCheckForZeroOrNanBranch(instr->GetSrc1(), branchOnFalse, labelTarget, labelFallthrough, instr);
Lowerer::InsertBranch(Js::OpCode::Br, labelFallthrough, instr);
instr->Remove();
return false;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Null fast path
if (srcValueType.HasBeenNull() || srcOriginalValueType.IsUninitialized())
{
if(srcValueType.IsNull())
{
// jmp $false
InsertBranch(Js::OpCode::Br, labelFalse, instr);
// Skip lowering call to helper
Assert(instr->m_prev->IsBranchInstr());
instr->Remove();
return false;
}
// cmp src, null
// je $false
InsertCompareBranch(
src,
LoadLibraryValueOpnd(instr, LibraryValue::ValueNull),
Js::OpCode::BrEq_A,
labelFalse,
instr);
src->SetValueType(srcValueType = srcValueType.SetIsNotAnyOf(ValueType::Null));
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Undefined fast path
if(srcValueType.HasBeenUndefined() || srcOriginalValueType.IsUninitialized())
{
if(srcValueType.IsUndefined())
{
// jmp $false
InsertBranch(Js::OpCode::Br, labelFalse, instr);
// Skip lowering call to helper
Assert(instr->m_prev->IsBranchInstr());
instr->Remove();
return false;
}
// cmp src, undefined
// je $false
InsertCompareBranch(
src,
LoadLibraryValueOpnd(instr, LibraryValue::ValueUndefined),
Js::OpCode::BrEq_A,
labelFalse,
instr);
src->SetValueType(srcValueType = srcValueType.SetIsNotAnyOf(ValueType::Undefined));
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Tagged int fast path
const bool isNotInt = src->IsNotInt();
bool checkedForTaggedInt = isNotInt;
if( (
srcValueType.HasBeenInt() ||
srcValueType.HasBeenUnknownNumber() ||
srcOriginalValueType.IsUninitialized()
) && !isNotInt)
{
checkedForTaggedInt = true;
IR::LabelInstr *notTaggedIntLabel = nullptr;
if(!src->IsTaggedInt())
{
// test src, 1
// jz $notTaggedInt
notTaggedIntLabel = IR::LabelInstr::New(Js::OpCode::Label, func);
m_lowererMD.GenerateSmIntTest(src, instr, notTaggedIntLabel);
}
// cmp src, tag(0)
// je/jne $target
m_lowererMD.GenerateTaggedZeroTest(src, instr);
Lowerer::InsertBranch(compareWithFalseBranchToTargetOpCode, labelTarget, instr);
if(src->IsTaggedInt())
{
// Skip lowering call to helper
Assert(instr->m_prev->IsBranchInstr());
instr->Remove();
return false;
}
// jmp $fallthrough
Lowerer::InsertBranch(Js::OpCode::Br, labelFallthrough, instr);
// $notTaggedInt:
if(notTaggedIntLabel)
{
instr->InsertBefore(notTaggedIntLabel);
lastLabelBeforeHelper = notTaggedIntLabel;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Float fast path
bool generateFloatTest = srcValueType.IsLikelyFloat();
#ifdef _M_IX86
if (!AutoSystemInfo::Data.SSE2Available())
{
generateFloatTest = false;
}
#endif
bool checkedForTaggedFloat =
#if FLOATVAR
srcValueType.IsNotNumber();
#else
true; // there are no tagged floats, indicate that it has been checked
#endif
if (generateFloatTest)
{
// if(srcValueType.IsFloat()) // skip tagged int check?
//
// ValueType::IsFloat() does not guarantee that the storage is not in a tagged int.
// The tagged int check is necessary. It does, however, guarantee that as long as the value is not
// stored in a tagged int, that it is definitely stored in a JavascriptNumber/TaggedFloat.
IR::LabelInstr *const notFloatLabel = IR::LabelInstr::New(Js::OpCode::Label, func);
if(!checkedForTaggedInt)
{
checkedForTaggedInt = true;
m_lowererMD.GenerateSmIntTest(src, instr, notFloatLabel, nullptr, true);
}
// cmp [src], JavascriptNumber::vtable
// jne $notFloat
#if FLOATVAR
checkedForTaggedFloat = true;
IR::RegOpnd *const floatOpnd = m_lowererMD.CheckFloatAndUntag(src, instr, notFloatLabel);
#else
m_lowererMD.GenerateFloatTest(src, instr, notFloatLabel);
IR::IndirOpnd *const floatOpnd = IR::IndirOpnd::New(src, Js::JavascriptNumber::GetValueOffset(), TyMachDouble, func);
#endif
// cmp src, 0.0
// jp $false
// je/jne $target
// jmp $fallthrough
InsertFloatCheckForZeroOrNanBranch(floatOpnd, branchOnFalse, labelTarget, labelFallthrough, instr);
Lowerer::InsertBranch(Js::OpCode::Br, labelFallthrough, instr);
// $notFloat:
instr->InsertBefore(notFloatLabel);
lastLabelBeforeHelper = notFloatLabel;
src->SetValueType(srcValueType = srcValueType.SetIsNotAnyOf(ValueType::AnyNumber));
}
IR::LabelInstr *labelHelper = nullptr;
bool _didObjectTest = checkedForTaggedInt && checkedForTaggedFloat;
const auto EnsureObjectTest = [&]()
{
if(_didObjectTest)
{
return;
}
if(!labelHelper)
{
labelHelper = IR::LabelInstr::New(Js::OpCode::Label, func, true);
}
m_lowererMD.GenerateObjectTest(src, instr, labelHelper);
_didObjectTest = true;
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Boolean fast path
if (srcValueType.HasBeenBoolean() || srcOriginalValueType.IsUninitialized())
{
IR::LabelInstr *notBooleanLabel = nullptr;
if (!srcValueType.IsBoolean())
{
EnsureObjectTest();
// cmp [src], JavascriptBoolean::vtable
// jne $notBoolean
notBooleanLabel = IR::LabelInstr::New(Js::OpCode::Label, func);
InsertCompareBranch(
IR::IndirOpnd::New(src, 0, TyMachPtr, func),
LoadVTableValueOpnd(instr, VTableValue::VtableJavascriptBoolean),
Js::OpCode::BrNeq_A,
notBooleanLabel,
instr);
}
// cmp src, false
// je/jne $target
InsertCompareBranch(
src,
LoadLibraryValueOpnd(instr, LibraryValue::ValueFalse),
compareWithFalseBranchToTargetOpCode,
labelTarget,
instr);
if (srcValueType.IsBoolean())
{
// Skip lowering call to helper
Assert(!labelHelper);
Assert(instr->m_prev->IsBranchInstr());
instr->Remove();
return false;
}
// jmp $fallthrough
Lowerer::InsertBranch(Js::OpCode::Br, labelFallthrough, instr);
if (notBooleanLabel)
{
instr->InsertBefore(notBooleanLabel);
lastLabelBeforeHelper = notBooleanLabel;
}
src->SetValueType(srcValueType = srcValueType.SetIsNotAnyOf(ValueType::Boolean));
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// String fast path
if(srcValueType.HasBeenString())
{
IR::LabelInstr *notStringLabel = nullptr;
if(!srcValueType.IsString())
{
EnsureObjectTest();
notStringLabel = IR::LabelInstr::New(Js::OpCode::Label, func);
GenerateStringTest(src, instr, notStringLabel, nullptr, false);
}
// cmp [src + offset(length)], 0
// jeq/jne $target
InsertCompareBranch(
IR::IndirOpnd::New(src, Js::JavascriptString::GetOffsetOfcharLength(), TyUint32, func),
IR::IntConstOpnd::New(0, TyUint32, func, true),
compareWithFalseBranchToTargetOpCode,
labelTarget,
instr);
if(srcValueType.IsString())
{
// Skip lowering call to helper
Assert(!labelHelper);
Assert(instr->m_prev->IsBranchInstr());
instr->Remove();
return false;
}
// jmp $fallthrough
Lowerer::InsertBranch(Js::OpCode::Br, labelFallthrough, instr);
if(notStringLabel)
{
instr->InsertBefore(notStringLabel);
lastLabelBeforeHelper = notStringLabel;
}
src->SetValueType(srcValueType = srcValueType.SetIsNotAnyOf(ValueType::String));
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Object fast path
if (srcValueType.IsLikelyObject())
{
if(srcValueType.IsObject())
{
if(srcValueType.GetObjectType() > ObjectType::Object)
{
// Specific object types that are tracked are equivalent to 'true'
// jmp $true
InsertBranch(Js::OpCode::Br, labelTrue, instr);
// Skip lowering call to helper
Assert(!labelHelper);
Assert(instr->m_prev->IsBranchInstr());
instr->Remove();
return false;
}
}
else
{
EnsureObjectTest();
}
// mov srcType, [src + offset(type)] -- load type
IR::RegOpnd *const srcType = IR::RegOpnd::New(TyMachPtr, func);
const IR::AutoReuseOpnd autoReuseR1(srcType, func);
InsertMove(srcType, IR::IndirOpnd::New(src, Js::RecyclableObject::GetOffsetOfType(), TyMachPtr, func), instr);
// test [srcType + offset(flags)], TypeFlagMask_IsFalsy -- check if falsy
// jnz $false
InsertTestBranch(
IR::IndirOpnd::New(srcType, Js::Type::GetOffsetOfFlags(), TyUint8, func),
IR::IntConstOpnd::New(TypeFlagMask_IsFalsy, TyUint8, func),
Js::OpCode::BrNeq_A,
labelFalse,
instr);
// cmp [srcType + offset(typeId)], TypeIds_LastJavascriptPrimitiveType -- check base TypeIds_LastJavascriptPrimitiveType
// ja $true
InsertCompareBranch(
IR::IndirOpnd::New(srcType, Js::Type::GetOffsetOfTypeId(), TyInt32, func),
IR::IntConstOpnd::New(Js::TypeIds_LastJavascriptPrimitiveType, TyInt32, func),
Js::OpCode::BrGt_A,
true /* isUnsigned */,
labelTrue,
instr);
if(!labelHelper)
{
labelHelper = IR::LabelInstr::New(Js::OpCode::Label, func, true);
}
lastLabelBeforeHelper = nullptr;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Helper call
// $helper:
if(lastLabelBeforeHelper)
{
Assert(instr->m_prev == lastLabelBeforeHelper);
lastLabelBeforeHelper->isOpHelper = true;
}
if (labelHelper)
{
Assert(labelHelper->isOpHelper);
instr->InsertBefore(labelHelper);
}
// call JavascriptConversion::ToBoolean
IR::RegOpnd *const toBoolDst = IR::RegOpnd::New(TyInt32, func);
const IR::AutoReuseOpnd autoReuseToBoolDst(toBoolDst, func);
IR::Instr *const callInstr = IR::Instr::New(Js::OpCode::Call, toBoolDst, instr->GetSrc1(), func);
instr->InsertBefore(callInstr);
LowerUnaryHelperMem(callInstr, IR::HelperConv_ToBoolean);
// test eax, eax
InsertTest(toBoolDst, toBoolDst, instr);
// je/jne $target
Assert(instr->IsBranchInstr());
instr->FreeSrc1();
instr->m_opcode = LowererMD::MDBranchOpcode(compareWithFalseBranchToTargetOpCode);
Assert(instr->AsBranchInstr()->GetTarget() == labelTarget);
// Skip lowering another call to helper
return false;
}
// Helper method used in LowerMD by all platforms.
// Creates HelperCallOpnd or DiagHelperCallOpnd, based on helperMethod and state.
// static
IR::HelperCallOpnd*
Lowerer::CreateHelperCallOpnd(IR::JnHelperMethod helperMethod, int helperArgCount, Func* func)
{
Assert(func);
IR::HelperCallOpnd* helperCallOpnd;
if (CONFIG_FLAG(EnableContinueAfterExceptionWrappersForHelpers) &&
func->IsJitInDebugMode() &&
HelperMethodAttributes::CanThrow(helperMethod))
{
// Create DiagHelperCallOpnd to indicate that it's needed to wrap original helper with try-catch wrapper,
// so that we can ignore exception and bailout to next stmt in debugger.
// For details, see: Lib\Runtime\Debug\DiagHelperMethodWrapper.{h,cpp}.
helperCallOpnd = IR::DiagHelperCallOpnd::New(helperMethod, func, helperArgCount);
}
else
{
helperCallOpnd = IR::HelperCallOpnd::New(helperMethod, func);
}
return helperCallOpnd;
}
bool
Lowerer::TryGenerateFastBrOrCmTypeOf(IR::Instr *instr, IR::Instr **prev, bool isNeqOp, bool *pfNoLower)
{
Assert(prev);
Assert(instr->m_opcode == Js::OpCode::BrSrEq_A ||
instr->m_opcode == Js::OpCode::BrSrNeq_A ||
instr->m_opcode == Js::OpCode::BrSrNotEq_A ||
instr->m_opcode == Js::OpCode::BrSrNotNeq_A ||
instr->m_opcode == Js::OpCode::CmSrEq_A ||
instr->m_opcode == Js::OpCode::CmSrNeq_A ||
instr->m_opcode == Js::OpCode::BrEq_A ||
instr->m_opcode == Js::OpCode::BrNeq_A ||
instr->m_opcode == Js::OpCode::BrNotEq_A ||
instr->m_opcode == Js::OpCode::BrNotNeq_A ||
instr->m_opcode == Js::OpCode::CmEq_A ||
instr->m_opcode == Js::OpCode::CmNeq_A);
//
// instr - (Br/Cm)(Sr)(N(ot))eq_A
// instr->m_prev - typeOf
//
IR::Instr *instrLd = instr->GetPrevRealInstrOrLabel();
bool skippedLoads = false;
//Skip intermediate Ld_A which might be inserted by flow graph peeps
while (instrLd && instrLd->m_opcode == Js::OpCode::Ld_A )
{
if (!(instrLd->GetDst()->IsRegOpnd() && instrLd->GetDst()->AsRegOpnd()->m_fgPeepTmp))
{
return false;
}
if (instrLd->HasBailOutInfo())
{
return false;
}
instrLd = instrLd->GetPrevRealInstrOrLabel();
skippedLoads = true;
}
IR::Instr *typeOf = instrLd;
IR::RegOpnd *instrSrc1 = instr->GetSrc1()->IsRegOpnd() ? instr->GetSrc1()->AsRegOpnd() : nullptr;
IR::RegOpnd *instrSrc2 = instr->GetSrc2()->IsRegOpnd() ? instr->GetSrc2()->AsRegOpnd() : nullptr;
if (typeOf && (typeOf->m_opcode == Js::OpCode::Typeof))
{
IR::RegOpnd *typeOfDst = typeOf->GetDst()->IsRegOpnd() ? typeOf->GetDst()->AsRegOpnd() : nullptr;
if (typeOfDst && instrSrc1 && instrSrc2)
{
do
{
IR::RegOpnd *typeOpnd = nullptr;
IR::RegOpnd *idOpnd = nullptr;
if (instrSrc1->m_sym == typeOfDst->m_sym)
{
typeOpnd = instrSrc1;
idOpnd = instrSrc2;
}
else if (instrSrc2->m_sym == typeOfDst->m_sym)
{
typeOpnd = instrSrc2;
idOpnd = instrSrc1;
}
else
{
// Neither source turned out to be the typeOpnd
break;
}
if (!typeOpnd->m_isTempLastUse)
{
break;
}
if (!(idOpnd->m_sym->m_isSingleDef && idOpnd->m_sym->m_isStrConst))
{
return false;
}
// The second argument to [Cm|Br]TypeOf is the typeid.
IR::IntConstOpnd *typeIdOpnd = nullptr;
Assert(idOpnd->m_sym->m_isSingleDef);
Assert(idOpnd->m_sym->m_instrDef->GetSrc1()->IsAddrOpnd());
// We can't optimize non-javascript type strings.
JITJavascriptString *typeNameJsString = JITJavascriptString::FromVar(idOpnd->m_sym->m_instrDef->GetSrc1()->AsAddrOpnd()->m_localAddress);
const char16 *typeName = typeNameJsString->GetString();
Js::InternalString typeNameString(typeName, typeNameJsString->GetLength());
if (Js::InternalStringComparer::Equals(typeNameString, Js::Type::UndefinedTypeNameString))
{
typeIdOpnd = IR::IntConstOpnd::New(Js::TypeIds_Undefined, TyInt32, instr->m_func);
}
else if (Js::InternalStringComparer::Equals(typeNameString, Js::Type::ObjectTypeNameString))
{
typeIdOpnd = IR::IntConstOpnd::New(Js::TypeIds_Object, TyInt32, instr->m_func);
}
else if (Js::InternalStringComparer::Equals(typeNameString, Js::Type::BooleanTypeNameString))
{
typeIdOpnd = IR::IntConstOpnd::New(Js::TypeIds_Boolean, TyInt32, instr->m_func);
}
else if (Js::InternalStringComparer::Equals(typeNameString, Js::Type::NumberTypeNameString))
{
typeIdOpnd = IR::IntConstOpnd::New(Js::TypeIds_Number, TyInt32, instr->m_func);
}
else if (Js::InternalStringComparer::Equals(typeNameString, Js::Type::StringTypeNameString))
{
typeIdOpnd = IR::IntConstOpnd::New(Js::TypeIds_String, TyInt32, instr->m_func);
}
else if (Js::InternalStringComparer::Equals(typeNameString, Js::Type::FunctionTypeNameString))
{
typeIdOpnd = IR::IntConstOpnd::New(Js::TypeIds_Function, TyInt32, instr->m_func);
}
else
{
return false;
}
if (skippedLoads)
{
//validate none of dst of Ld_A overlaps with typeof src or dst
IR::Opnd* typeOfSrc = typeOf->GetSrc1();
instrLd = typeOf->GetNextRealInstr();
while (instrLd != instr)
{
if (instrLd->GetDst()->IsEqual(typeOfDst) || instrLd->GetDst()->IsEqual(typeOfSrc))
{
return false;
}
instrLd = instrLd->GetNextRealInstr();
}
typeOf->Unlink();
instr->InsertBefore(typeOf);
}
// The first argument to [Cm|Br]TypeOf is the first arg to the TypeOf instruction.
IR::Opnd *objectOpnd = typeOf->GetSrc1();
Assert(objectOpnd->IsRegOpnd());
// Now emit this instruction and remove the ldstr and typeOf.
*prev = typeOf->m_prev;
*pfNoLower = false;
if (instr->IsBranchInstr())
{
GenerateFastBrTypeOf(instr, objectOpnd->AsRegOpnd(), typeIdOpnd, typeOf, pfNoLower, isNeqOp);
}
else
{
GenerateFastCmTypeOf(instr, objectOpnd->AsRegOpnd(), typeIdOpnd, typeOf, pfNoLower, isNeqOp);
}
return true;
} while (false);
}
}
if (instrSrc1 && instrSrc1->GetStackSym()->IsSingleDef() && instrSrc2 && instrSrc2->GetStackSym()->IsSingleDef() &&
(
((instrSrc1->GetStackSym()->GetInstrDef()->m_opcode == Js::OpCode::Typeof) &&
((instrSrc2->GetStackSym()->GetInstrDef()->m_opcode == Js::OpCode::Typeof) || instrSrc2->GetStackSym()->GetIsStrConst()))
||
((instrSrc2->GetStackSym()->GetInstrDef()->m_opcode == Js::OpCode::Typeof) &&
((instrSrc1->GetStackSym()->GetInstrDef()->m_opcode == Js::OpCode::Typeof) || instrSrc1->GetStackSym()->GetIsStrConst()))
)
)
{
*pfNoLower = true;
if (instr->IsBranchInstr())
{
InsertCompareBranch(instrSrc1, instrSrc2, isNeqOp ? Js::OpCode::BrNeq_A : Js::OpCode::BrEq_A, instr->AsBranchInstr()->GetTarget(), instr);
instr->Remove();
}
else
{
if (instrSrc1->IsEqual(instrSrc2))
{
InsertMove(instr->GetDst(), LoadLibraryValueOpnd(instr, isNeqOp ? LibraryValue::ValueFalse : LibraryValue::ValueTrue), instr);
}
else
{
// t1 = typeof o1
// t2 = typeof o2
// dst = t1 == t2
// MOV dst, true
// CMP t1, t2
// x86, amd64
// CMOVNE dst, false
// arm
// BEQ $done
// MOV dst, false
// $done
if (instr->GetDst()->IsEqual(instrSrc1))
{
IR::Instr* hoistInstr = m_lowererMD.ChangeToAssign(instr->HoistSrc1(Js::OpCode::Ld_A));
instrSrc1 = hoistInstr->GetDst()->AsRegOpnd();
}
if (instr->GetDst()->IsEqual(instrSrc2))
{
IR::Instr* hoistInstr = m_lowererMD.ChangeToAssign(instr->HoistSrc2(Js::OpCode::Ld_A));
instrSrc2 = hoistInstr->GetDst()->AsRegOpnd();
}
InsertMove(instr->GetDst(), LoadLibraryValueOpnd(instr, LibraryValue::ValueTrue), instr);
#if defined(_M_ARM32_OR_ARM64)
IR::LabelInstr * doneLabel = IR::LabelInstr::New(Js::OpCode::Label, instr->m_func);
InsertCompareBranch(instrSrc1, instrSrc2, isNeqOp ? Js::OpCode::BrNeq_A : Js::OpCode::BrEq_A, doneLabel, instr);
InsertMove(instr->GetDst(), LoadLibraryValueOpnd(instr, LibraryValue::ValueFalse), instr);
instr->InsertBefore(doneLabel);
#else
InsertCompare(instrSrc1, instrSrc2, instr);
LowererMD::InsertCmovCC(isNeqOp ? Js::OpCode::CMOVE : Js::OpCode::CMOVNE, instr->GetDst(), LoadLibraryValueOpnd(instr, LibraryValue::ValueFalse), instr);
#endif
}
instr->Remove();
}
return true;
}
return false;
}
void
Lowerer::GenerateFalsyObjectTest(IR::Instr * insertInstr, IR::RegOpnd * typeOpnd, IR::LabelInstr * falsyLabel)
{
IR::Opnd *flagsOpnd = IR::IndirOpnd::New(typeOpnd, Js::Type::GetOffsetOfFlags(), TyInt32, this->m_func);
InsertTestBranch(flagsOpnd, IR::IntConstOpnd::New(TypeFlagMask_IsFalsy, TyInt32, this->m_func), Js::OpCode::BrNeq_A, falsyLabel, insertInstr);
}
void
Lowerer::GenerateFalsyObjectTest(IR::Instr *insertInstr, IR::RegOpnd *typeOpnd, Js::TypeId typeIdToCheck, IR::LabelInstr* target, IR::LabelInstr* done, bool isNeqOp)
{
if (!this->m_func->GetThreadContextInfo()->CanBeFalsy(typeIdToCheck) && typeIdToCheck != Js::TypeIds_Undefined)
{
// Don't need the check for falsy, the typeId we are looking for doesn't care
return;
}
IR::Opnd *flagsOpnd = IR::IndirOpnd::New(typeOpnd, Js::Type::GetOffsetOfFlags(), TyInt32, this->m_func);
InsertTest(flagsOpnd, IR::IntConstOpnd::New(TypeFlagMask_IsFalsy, TyInt32, this->m_func), insertInstr);
if (typeIdToCheck == Js::TypeIds_Undefined)
{
//Falsy object returns true for undefined ((typeof falsyObj) == "undefined")
InsertBranch( Js::OpCode::BrNeq_A, true, isNeqOp ? done : target, insertInstr);
}
else
{
//Falsy object returns false for all other types ((typeof falsyObj) != "function")
InsertBranch( Js::OpCode::BrNeq_A, true, isNeqOp? target : done , insertInstr);
}
}
///----------------------------------------------------------------------------
///
/// LowererMD::GenerateFastBrTypeOf
///
///----------------------------------------------------------------------------
void
Lowerer::GenerateFastBrTypeOf(IR::Instr *branch, IR::RegOpnd *object, IR::IntConstOpnd *typeIdOpnd, IR::Instr *typeOf, bool *pfNoLower, bool isNeqOp)
{
Js::TypeId typeId = static_cast<Js::TypeId>(typeIdOpnd->GetValue());
IR::LabelInstr *target = branch->AsBranchInstr()->GetTarget();
IR::LabelInstr *done = IR::LabelInstr::New(Js::OpCode::Label, m_func, false);
IR::LabelInstr *helper = IR::LabelInstr::New(Js::OpCode::Label, m_func, true);
IR::RegOpnd *typeRegOpnd = IR::RegOpnd::New(TyMachReg, m_func);
switch(branch->m_opcode)
{
case Js::OpCode::BrSrNeq_A:
case Js::OpCode::BrNeq_A:
case Js::OpCode::BrSrNotEq_A:
case Js::OpCode::BrNotEq_A:
case Js::OpCode::BrSrEq_A:
case Js::OpCode::BrEq_A:
case Js::OpCode::BrSrNotNeq_A:
case Js::OpCode::BrNotNeq_A:
break;
default:
Assert(UNREACHED);
__assume(UNREACHED);
}
// JNE/BNE (typeId == Js::TypeIds_Number) ? $target : $done
IR::LabelInstr *label = (typeId == Js::TypeIds_Number) ? target : done;
if (isNeqOp)
label = (label == target) ? done : target;
m_lowererMD.GenerateObjectTest(object, branch, label);
// MOV typeRegOpnd, [object + offset(Type)]
InsertMove(typeRegOpnd,
IR::IndirOpnd::New(object, Js::RecyclableObject::GetOffsetOfType(), TyMachReg, m_func),
branch);
GenerateFalsyObjectTest(branch, typeRegOpnd, typeId, target, done, isNeqOp);
// MOV objTypeId, [typeRegOpnd + offset(TypeId)]
IR::RegOpnd* objTypeIdOpnd = IR::RegOpnd::New(TyInt32, m_func);
InsertMove(objTypeIdOpnd,
IR::IndirOpnd::New(typeRegOpnd, Js::Type::GetOffsetOfTypeId(), TyInt32, m_func),
branch);
// CMP objTypeId, typeId
// JEQ/JGE $done
if (typeId == Js::TypeIds_Object)
{
InsertCompareBranch(objTypeIdOpnd, typeIdOpnd, Js::OpCode::BrGe_A, isNeqOp ? done : target, branch);
}
else if (typeId == Js::TypeIds_Function)
{
InsertCompareBranch(objTypeIdOpnd, typeIdOpnd, Js::OpCode::BrEq_A, isNeqOp ? done : target, branch);
}
else if (typeId == Js::TypeIds_Number)
{
//Check for the typeIds between TypeIds_FirstNumberType <= typeIds <= TypeIds_LastNumberType
InsertSub(false, objTypeIdOpnd, objTypeIdOpnd, IR::IntConstOpnd::New(Js::TypeIds_FirstNumberType, TyInt32, branch->m_func),branch);
InsertCompare(objTypeIdOpnd, IR::IntConstOpnd::New(Js::TypeIds_LastNumberType - Js::TypeIds_FirstNumberType, TyInt32, branch->m_func), branch);
InsertBranch(isNeqOp ? Js::OpCode::BrGt_A : Js::OpCode::BrLe_A, true, target, branch);
}
else
{
InsertCompare(objTypeIdOpnd, typeIdOpnd, branch);
InsertBranch(isNeqOp ? Js::OpCode::BrNeq_A : Js::OpCode::BrEq_A, target, branch);
}
// This could be 'null' which, for historical reasons, has a TypeId < TypeIds_Object but
// is still a Javascript "object."
if (typeId == Js::TypeIds_Object)
{
// CMP object, 0xXXXXXXXX
// JEQ isNeqOp ? $done : $target
InsertCompareBranch(object,
LoadLibraryValueOpnd(branch, LibraryValue::ValueNull),
Js::OpCode::BrEq_A,
isNeqOp ? done : target,
branch);
}
branch->InsertAfter(done); // Get this label first
// "object" or "function" may come from HostDispatch. Needs helper if that's the case.
if (typeId == Js::TypeIds_Object || typeId == Js::TypeIds_Function)
{
// CMP objTypeId, TypeIds_Proxy. typeof proxy could be 'object' or 'function' depends on the target
// JNE isNeqOp ? $target : $done
InsertCompareBranch(objTypeIdOpnd,
IR::IntConstOpnd::New(Js::TypeIds_Proxy, TyInt32, m_func),
Js::OpCode::BrEq_A,
helper,
branch);
// CMP objTypeId, TypeIds_HostDispatch
// JNE isNeqOp ? $target : $done
InsertCompareBranch(objTypeIdOpnd,
IR::IntConstOpnd::New(Js::TypeIds_HostDispatch, TyInt32, m_func),
Js::OpCode::BrNeq_A,
isNeqOp ? target : done,
branch);
// Now emit Typeof and lower it like we would've for the helper call.
{
branch->InsertBefore(helper);
typeOf->Unlink();
branch->InsertBefore(typeOf);
if (branch->HasBailOutInfo() && BailOutInfo::IsBailOutOnImplicitCalls(branch->GetBailOutKind()) &&
(!typeOf->HasBailOutInfo() || !BailOutInfo::IsBailOutOnImplicitCalls(typeOf->GetBailOutKind())))
{
typeOf = AddBailoutToHelperCallInstr(typeOf, branch->GetBailOutInfo(), branch->GetBailOutKind(), branch);
}
LowerUnaryHelperMem(typeOf, IR::HelperOp_Typeof);
}
}
else // Other primitive types don't need helper
{
typeOf->Remove();
branch->Remove();
*pfNoLower = true;
}
// $done:
}
///----------------------------------------------------------------------------
///
/// LowererMD::GenerateFastCmTypeOf
///
///----------------------------------------------------------------------------
void
Lowerer::GenerateFastCmTypeOf(IR::Instr *compare, IR::RegOpnd *object, IR::IntConstOpnd *typeIdOpnd, IR::Instr *typeOf, bool *pfNoLower, bool isNeqOp)
{
Assert(compare->m_opcode == Js::OpCode::CmSrEq_A ||
compare->m_opcode == Js::OpCode::CmEq_A ||
compare->m_opcode == Js::OpCode::CmSrNeq_A ||
compare->m_opcode == Js::OpCode::CmNeq_A);
Js::TypeId typeId = static_cast<Js::TypeId>(typeIdOpnd->GetValue());
IR::LabelInstr *movFalse = IR::LabelInstr::New(Js::OpCode::Label, m_func, false);
IR::LabelInstr *done = IR::LabelInstr::New(Js::OpCode::Label, m_func, false);
IR::LabelInstr *helper= IR::LabelInstr::New(Js::OpCode::Label, m_func, true);
IR::RegOpnd *dst = compare->GetDst()->IsRegOpnd() ? compare->GetDst()->AsRegOpnd() : nullptr;
IR::RegOpnd *typeRegOpnd = IR::RegOpnd::New(TyMachReg, m_func);
Assert(dst);
if (dst->IsEqual(object))
{
//dst same as the src of typeof. As we need to move true to dst first we need to save the src to a new opnd
IR::RegOpnd *newObject = IR::RegOpnd::New(object->GetType(), m_func);
InsertMove(newObject, object, compare); //Save src
object = newObject;
}
// mov dst, 'true'
InsertMove(dst,
LoadLibraryValueOpnd(compare, LibraryValue::ValueTrue),
compare);
// TEST object, 1
// JNE (typeId == Js::TypeIds_Number) ? $done : $movFalse
IR::LabelInstr *target = (typeId == Js::TypeIds_Number) ? done : movFalse;
if (isNeqOp)
{
target = (target == done) ? movFalse : done;
}
m_lowererMD.GenerateObjectTest(object, compare, target);
// MOV typeRegOpnd, [object + offset(Type)]
InsertMove(typeRegOpnd,
IR::IndirOpnd::New(object, Js::RecyclableObject::GetOffsetOfType(), TyMachReg, m_func),
compare);
GenerateFalsyObjectTest(compare, typeRegOpnd, typeId, done, movFalse, isNeqOp);
// MOV objTypeId, [typeRegOpnd + offset(TypeId)]
IR::RegOpnd* objTypeIdOpnd = IR::RegOpnd::New(TyInt32, m_func);
InsertMove(objTypeIdOpnd,
IR::IndirOpnd::New(typeRegOpnd, Js::Type::GetOffsetOfTypeId(), TyInt32, m_func),
compare);
// CMP objTypeId, typeId
// JEQ/JGE $done
if (typeId == Js::TypeIds_Object)
{
InsertCompareBranch(objTypeIdOpnd, typeIdOpnd, Js::OpCode::BrGe_A, isNeqOp ? movFalse : done, compare);
}
else if (typeId == Js::TypeIds_Function)
{
InsertCompareBranch(objTypeIdOpnd, typeIdOpnd, Js::OpCode::BrEq_A, isNeqOp ? movFalse : done, compare);
}
else if (typeId == Js::TypeIds_Number)
{
//Check for the typeIds between TypeIds_FirstNumberType <= typeIds <= TypeIds_LastNumberType
InsertCompareBranch(objTypeIdOpnd,
IR::IntConstOpnd::New(Js::TypeIds_LastNumberType, TyInt32, compare->m_func),
Js::OpCode::BrGt_A,
isNeqOp ? done : movFalse,
compare);
InsertCompareBranch(objTypeIdOpnd,
IR::IntConstOpnd::New(Js::TypeIds_FirstNumberType, TyInt32, compare->m_func),
isNeqOp? Js::OpCode::BrLt_A : Js::OpCode::BrGe_A,
done,
compare);
}
else
{
InsertCompareBranch(objTypeIdOpnd, typeIdOpnd, isNeqOp ? Js::OpCode::BrNeq_A : Js::OpCode::BrEq_A, done, compare);
}
// This could be 'null' which, for historical reasons, has a TypeId < TypeIds_Object but
// is still a Javascript "object."
if (typeId == Js::TypeIds_Object)
{
// CMP object, 0xXXXXXXXX
// JEQ isNeqOp ? $movFalse : $done
InsertCompareBranch(object,
LoadLibraryValueOpnd(compare, LibraryValue::ValueNull),
Js::OpCode::BrEq_A,
isNeqOp ? movFalse : done,
compare);
}
compare->InsertAfter(done); // Get this label first
// "object" or "function" may come from HostDispatch. Needs helper if that's the case.
if (typeId == Js::TypeIds_Object || typeId == Js::TypeIds_Function)
{
// CMP objTypeId, TypeIds_Proxy
// JNE isNeqOp ? $done : $movFalse
InsertCompareBranch(objTypeIdOpnd,
IR::IntConstOpnd::New(Js::TypeIds_Proxy, TyInt32, m_func),
Js::OpCode::BrEq_A,
helper,
compare);
// CMP objTypeId, TypeIds_HostDispatch
// JNE isNeqOp ? $done : $movFalse
InsertCompareBranch(objTypeIdOpnd,
IR::IntConstOpnd::New(Js::TypeIds_HostDispatch, TyInt32, m_func),
Js::OpCode::BrNeq_A,
isNeqOp ? done : movFalse,
compare);
// Now emit Typeof like we would've for the helper call.
{
compare->InsertBefore(helper);
typeOf->Unlink();
compare->InsertBefore(typeOf);
if (compare->HasBailOutInfo() && BailOutInfo::IsBailOutOnImplicitCalls(compare->GetBailOutKind()) &&
(!typeOf->HasBailOutInfo() || !BailOutInfo::IsBailOutOnImplicitCalls(typeOf->GetBailOutKind())))
{
typeOf = AddBailoutToHelperCallInstr(typeOf, compare->GetBailOutInfo(), compare->GetBailOutKind(), compare);
}
LowerUnaryHelperMem(typeOf, IR::HelperOp_Typeof);
}
// JMP/B $done
InsertBranch(Js::OpCode::Br, done, done);
}
else // Other primitive types don't need helper
{
typeOf->Remove();
dst = compare->UnlinkDst()->AsRegOpnd();
compare->Remove();
*pfNoLower = true;
}
// $movFalse: (insert before $done)
done->InsertBefore(movFalse);
// MOV dst, 'false'
InsertMove(dst, LoadLibraryValueOpnd(done, LibraryValue::ValueFalse), done);
// $done:
}
void
Lowerer::GenerateCheckForCallFlagNew(IR::Instr* instrInsert)
{
Func *func = instrInsert->m_func;
IR::LabelInstr * labelDone = IR::LabelInstr::New(Js::OpCode::Label, func, false);
Assert(!func->IsInlinee());
// MOV s1, [ebp + 4] // s1 = call info
// AND s2, s1, Js::CallFlags_New // s2 = s1 & Js::CallFlags_New
// CMP s2, 0
// JNE $Done
// CALL RuntimeTypeError
// $Done
IR::SymOpnd* callInfoOpnd = Lowerer::LoadCallInfo(instrInsert);
Assert(Js::CallInfo::ksizeofCount == 24);
IR::RegOpnd* isNewFlagSetRegOpnd = IR::RegOpnd::New(TyMachReg, func);
InsertAnd(isNewFlagSetRegOpnd, callInfoOpnd, IR::IntConstOpnd::New((IntConstType)Js::CallFlags_New << Js::CallInfo::ksizeofCount, TyMachReg, func, true), instrInsert);
InsertTestBranch(isNewFlagSetRegOpnd, isNewFlagSetRegOpnd, Js::OpCode::BrNeq_A, labelDone, instrInsert);
IR::Instr *throwInstr = IR::Instr::New(
Js::OpCode::RuntimeTypeError,
IR::RegOpnd::New(TyMachReg, m_func),
IR::IntConstOpnd::New(SCODE_CODE(JSERR_ClassConstructorCannotBeCalledWithoutNew), TyInt32, m_func),
m_func);
instrInsert->InsertBefore(throwInstr);
this->LowerUnaryHelperMem(throwInstr, IR::HelperOp_RuntimeTypeError);
instrInsert->InsertBefore(labelDone);
instrInsert->Remove();
}
void
Lowerer::GenerateJavascriptOperatorsIsConstructorGotoElse(IR::Instr *instrInsert, IR::RegOpnd *instanceRegOpnd, IR::LabelInstr *labelReturnTrue, IR::LabelInstr *labelReturnFalse)
{
// $ProxyLoop:
// // if (!VarIs<RecyclableObject>(instance)) { goto $ReturnFalse }; // omitted: VarIs<RecyclableObject>(instance) always true
// MOV s0, instance->type
// MOV s1, s0->typeId
// CMP s1, TypeIds_Proxy
// JNE $NotProxy
//
// MOV instance, instance->target
// JMP $ProxyLoop
//
// $NotProxy:
// CMP s1, TypeIds_Function
// JNE $ReturnFalse // external
//
// MOV s0, instance->functionInfo
// MOV s1, s0->attributes
// TEST s1, ErrorOnNew
// JNE $ReturnFalse // external
//
// JMP $ReturnTrue // external
Func *func = instrInsert->m_func;
IR::LabelInstr *labelProxyLoop = InsertLoopTopLabel(instrInsert);
IR::LabelInstr *labelNotProxy = IR::LabelInstr::New(Js::OpCode::Label, func, false);
IR::RegOpnd *indir0RegOpnd = IR::RegOpnd::New(TyMachPtr, func);
IR::RegOpnd *indir1RegOpnd = IR::RegOpnd::New(TyUint32, func);
Loop * loop = labelProxyLoop->GetLoop();
loop->regAlloc.liveOnBackEdgeSyms->Set(instanceRegOpnd->m_sym->m_id);
IR::IndirOpnd *indirOpnd = IR::IndirOpnd::New(instanceRegOpnd, Js::RecyclableObject::GetOffsetOfType(), TyMachPtr, func);
Lowerer::InsertMove(indir0RegOpnd, indirOpnd, instrInsert);
indirOpnd = IR::IndirOpnd::New(indir0RegOpnd, Js::Type::GetOffsetOfTypeId(), TyUint32, func);
Lowerer::InsertMove(indir1RegOpnd, indirOpnd, instrInsert);
InsertCompareBranch(indir1RegOpnd, IR::IntConstOpnd::New(Js::TypeIds_Proxy, TyUint32, func, true), Js::OpCode::BrNeq_A, labelNotProxy, instrInsert);
indirOpnd = IR::IndirOpnd::New(instanceRegOpnd, Js::JavascriptProxy::GetOffsetOfTarget(), TyMachPtr, func);
Lowerer::InsertMove(instanceRegOpnd, indirOpnd, instrInsert);
InsertBranch(Js::OpCode::Br, labelProxyLoop, instrInsert);
instrInsert->InsertBefore(labelNotProxy);
InsertCompareBranch(indir1RegOpnd, IR::IntConstOpnd::New(Js::TypeIds_Function, TyUint32, func, true), Js::OpCode::BrNeq_A, labelReturnFalse, instrInsert);
indirOpnd = IR::IndirOpnd::New(instanceRegOpnd, Js::JavascriptFunction::GetOffsetOfFunctionInfo(), TyMachPtr, func);
Lowerer::InsertMove(indir0RegOpnd, indirOpnd, instrInsert);
indirOpnd = IR::IndirOpnd::New(indir0RegOpnd, Js::FunctionInfo::GetAttributesOffset(), TyUint32, func);
Lowerer::InsertMove(indir1RegOpnd, indirOpnd, instrInsert);
InsertTestBranch(indir1RegOpnd, IR::IntConstOpnd::New(Js::FunctionInfo::Attributes::ErrorOnNew, TyUint32, func, true), Js::OpCode::BrNeq_A, labelReturnFalse, instrInsert);
InsertBranch(Js::OpCode::Br, labelReturnTrue, instrInsert);
}
void
Lowerer::GenerateRecyclableObjectGetPrototypeNullptrGoto(IR::Instr *instrInsert, IR::RegOpnd *instanceRegOpnd, IR::LabelInstr *labelReturnNullptr)
{
// MOV instance, instance->type
// MOV flags, instance->flags
// TEST flags, TypeFlagMask_HasSpecialPrototype
// JNE $ReturnNullptr // external, bypassing nullptr check
// MOV instance, instance->prototype
Func *func = instrInsert->m_func;
IR::RegOpnd *flagsRegOpnd = IR::RegOpnd::New(TyUint32, func);
IR::IndirOpnd *indirOpnd = IR::IndirOpnd::New(instanceRegOpnd, Js::RecyclableObject::GetOffsetOfType(), TyMachPtr, func);
Lowerer::InsertMove(instanceRegOpnd, indirOpnd, instrInsert);
indirOpnd = IR::IndirOpnd::New(instanceRegOpnd, Js::Type::GetOffsetOfFlags(), TyUint32, func);
Lowerer::InsertMove(flagsRegOpnd, indirOpnd, instrInsert);
InsertTestBranch(flagsRegOpnd, IR::IntConstOpnd::New(TypeFlagMask_HasSpecialPrototype, TyUint32, func, true), Js::OpCode::BrNeq_A, labelReturnNullptr, instrInsert);
indirOpnd = IR::IndirOpnd::New(instanceRegOpnd, Js::Type::GetOffsetOfPrototype(), TyMachPtr, func);
Lowerer::InsertMove(instanceRegOpnd, indirOpnd, instrInsert);
}
void
Lowerer::GenerateRecyclableObjectIsElse(IR::Instr *instrInsert, IR::RegOpnd *instanceRegOpnd, IR::LabelInstr *labelFalse)
{
Func *func = instrInsert->m_func;
#if INT32VAR
InsertTestBranch(instanceRegOpnd, IR::AddrOpnd::New((Js::Var)0xffff000000000000, IR::AddrOpndKindConstantVar, func, true), Js::OpCode::BrNeq_A, labelFalse, instrInsert);
#else
InsertTestBranch(instanceRegOpnd, IR::IntConstOpnd::New(Js::AtomTag, TyUint32, func, true), Js::OpCode::BrNeq_A, labelFalse, instrInsert);
#endif
}
void
Lowerer::GenerateLdHomeObj(IR::Instr* instr)
{
// MOV dst, undefined
// MOV instance, functionObject // functionObject through stack params or src1
// CMP [instance], VtableStackScriptFunction
// JE $Done
// MOV instance, instance->homeObj
// TEST instance, instance
// JZ $Done
// MOV dst, instance
// $Done:
Func *func = instr->m_func;
IR::LabelInstr *labelDone = IR::LabelInstr::New(Js::OpCode::Label, func, false);
IR::LabelInstr *labelInlineFunc = IR::LabelInstr::New(Js::OpCode::Label, func, false);
IR::LabelInstr *testLabel = IR::LabelInstr::New(Js::OpCode::Label, func, false);
IR::LabelInstr *scriptFuncLabel = IR::LabelInstr::New(Js::OpCode::Label, func, false);
IR::Opnd *opndUndefAddress = this->LoadLibraryValueOpnd(instr, LibraryValue::ValueUndefined);
IR::RegOpnd *instanceRegOpnd = IR::RegOpnd::New(TyMachPtr, func);
IR::Opnd *dstOpnd = instr->GetDst();
Assert(dstOpnd->IsRegOpnd());
Lowerer::InsertMove(dstOpnd, opndUndefAddress, instr);
IR::Opnd * functionObjOpnd = nullptr;
m_lowererMD.LoadFunctionObjectOpnd(instr, functionObjOpnd);
Lowerer::InsertMove(instanceRegOpnd, functionObjOpnd, instr);
IR::Opnd * vtableAddressOpnd = this->LoadVTableValueOpnd(instr, VTableValue::VtableStackScriptFunction);
IR::BranchInstr* branchInstr = InsertCompareBranch(IR::IndirOpnd::New(instanceRegOpnd, 0, TyMachPtr, func), vtableAddressOpnd,
Js::OpCode::BrEq_A, true, labelDone, instr);
InsertObjectPoison(instanceRegOpnd, branchInstr, instr, false);
if (func->GetJITFunctionBody()->HasHomeObj())
{
// Is this an function with inline cache and home obj??
IR::Opnd * vtableAddressInlineFuncHomObjOpnd = this->LoadVTableValueOpnd(instr, VTableValue::VtableScriptFunctionWithInlineCacheAndHomeObj);
IR::BranchInstr* inlineFuncHomObjOpndBr = InsertCompareBranch(IR::IndirOpnd::New(instanceRegOpnd, 0, TyMachPtr, func), vtableAddressInlineFuncHomObjOpnd, Js::OpCode::BrNeq_A, labelInlineFunc, instr);
InsertObjectPoison(instanceRegOpnd, inlineFuncHomObjOpndBr, instr, false);
IR::IndirOpnd *indirInlineFuncHomeObjOpnd = IR::IndirOpnd::New(instanceRegOpnd, Js::FunctionWithHomeObj<Js::ScriptFunctionWithInlineCache>::GetOffsetOfHomeObj(), TyMachPtr, func);
Lowerer::InsertMove(instanceRegOpnd, indirInlineFuncHomeObjOpnd, instr);
InsertBranch(Js::OpCode::Br, testLabel, instr);
instr->InsertBefore(labelInlineFunc);
// Is this a function with inline cache, home obj and computed name??
IR::Opnd * vtableAddressInlineFuncHomObjCompNameOpnd = this->LoadVTableValueOpnd(instr, VTableValue::VtableScriptFunctionWithInlineCacheHomeObjAndComputedName);
IR::BranchInstr* inlineFuncHomObjCompNameBr = InsertCompareBranch(IR::IndirOpnd::New(instanceRegOpnd, 0, TyMachPtr, func), vtableAddressInlineFuncHomObjCompNameOpnd, Js::OpCode::BrNeq_A, scriptFuncLabel, instr);
InsertObjectPoison(instanceRegOpnd, inlineFuncHomObjCompNameBr, instr, false);
IR::IndirOpnd *indirInlineFuncHomeObjCompNameOpnd = IR::IndirOpnd::New(instanceRegOpnd, Js::FunctionWithComputedName<Js::FunctionWithHomeObj<Js::ScriptFunctionWithInlineCache>>::GetOffsetOfHomeObj(), TyMachPtr, func);
Lowerer::InsertMove(instanceRegOpnd, indirInlineFuncHomeObjCompNameOpnd, instr);
InsertBranch(Js::OpCode::Br, testLabel, instr);
instr->InsertBefore(scriptFuncLabel);
IR::IndirOpnd *indirOpnd = IR::IndirOpnd::New(instanceRegOpnd, Js::ScriptFunctionWithHomeObj::GetOffsetOfHomeObj(), TyMachPtr, func);
Lowerer::InsertMove(instanceRegOpnd, indirOpnd, instr);
}
else
{
// Even if the function does not have home object in eval cases we still have the LdHomeObj opcode
InsertBranch(Js::OpCode::Br, labelDone, instr);
}
instr->InsertBefore(testLabel);
InsertTestBranch(instanceRegOpnd, instanceRegOpnd, Js::OpCode::BrEq_A, labelDone, instr);
Lowerer::InsertMove(dstOpnd, instanceRegOpnd, instr);
instr->InsertBefore(labelDone);
instr->Remove();
}
void
Lowerer::GenerateLdHomeObjProto(IR::Instr* instr)
{
// MOV dst, undefined
// MOV instance, src1 // homeObj
// TEST instance, instance
// JZ $Done
//
// if (!VarIs<RecyclableObject>(instance)) goto $Done
// MOV type, [instance+Offset(type)]
// MOV typeId, [type+Offset(typeId)]
// CMP typeId, TypeIds_Null
// JEQ $Err
// CMP typeId, TypeIds_Undefined
// JNE $NoErr
//
// $Err:
// ThrowRuntimeReferenceError(JSERR_BadSuperReference);
//
// $NoErr:
// instance = ((RecyclableObject*)instance)->GetPrototype();
// if (instance == nullptr) goto $Done;
//
// if (!VarIs<RecyclableObject>(instance)) goto $Done
//
// MOV dst, instance
// $Done:
Func *func = instr->m_func;
IR::Opnd *src1Opnd = instr->UnlinkSrc1();
IR::LabelInstr *labelDone = IR::LabelInstr::New(Js::OpCode::Label, func, false);
IR::LabelInstr *labelErr = IR::LabelInstr::New(Js::OpCode::Label, func, false);
IR::LabelInstr *labelNoErr = IR::LabelInstr::New(Js::OpCode::Label, func, false);
IR::Opnd *opndUndefAddress = this->LoadLibraryValueOpnd(instr, LibraryValue::ValueUndefined);
IR::RegOpnd *instanceRegOpnd = IR::RegOpnd::New(TyMachPtr, func);
IR::RegOpnd *typeRegOpnd = IR::RegOpnd::New(TyMachPtr, func);
IR::RegOpnd *typeIdRegOpnd = IR::RegOpnd::New(TyUint32, func);
IR::Opnd *dstOpnd = instr->GetDst();
Assert(dstOpnd->IsRegOpnd());
Lowerer::InsertMove(dstOpnd, opndUndefAddress, instr);
Lowerer::InsertMove(instanceRegOpnd, src1Opnd, instr);
InsertTestBranch(instanceRegOpnd, instanceRegOpnd, Js::OpCode::BrEq_A, labelDone, instr);
this->GenerateRecyclableObjectIsElse(instr, instanceRegOpnd, labelDone);
IR::IndirOpnd *indirOpnd = IR::IndirOpnd::New(instanceRegOpnd, Js::RecyclableObject::GetOffsetOfType(), TyMachPtr, func);
Lowerer::InsertMove(typeRegOpnd, indirOpnd, instr);
indirOpnd = IR::IndirOpnd::New(typeRegOpnd, Js::Type::GetOffsetOfTypeId(), TyUint32, func);
Lowerer::InsertMove(typeIdRegOpnd, indirOpnd, instr);
InsertCompareBranch(typeIdRegOpnd, IR::IntConstOpnd::New(Js::TypeId::TypeIds_Null, TyUint32, func, true), Js::OpCode::BrEq_A, labelErr, instr);
InsertCompareBranch(typeIdRegOpnd, IR::IntConstOpnd::New(Js::TypeId::TypeIds_Undefined, TyUint32, func, true), Js::OpCode::BrNeq_A, labelNoErr, instr);
instr->InsertBefore(labelErr);
this->GenerateRuntimeError(instr, JSERR_BadSuperReference, IR::HelperOp_RuntimeReferenceError);
instr->InsertBefore(labelNoErr);
this->GenerateRecyclableObjectGetPrototypeNullptrGoto(instr, instanceRegOpnd, labelDone);
this->GenerateRecyclableObjectIsElse(instr, instanceRegOpnd, labelDone);
Lowerer::InsertMove(dstOpnd, instanceRegOpnd, instr);
instr->InsertBefore(labelDone);
instr->Remove();
}
void
Lowerer::GenerateLdFuncObj(IR::Instr* instr)
{
// MOV dst, functionObject // functionObject through stack params or src1
IR::Opnd *dstOpnd = instr->GetDst();
IR::Opnd *functionObjOpnd = nullptr;
m_lowererMD.LoadFunctionObjectOpnd(instr, functionObjOpnd);
Lowerer::InsertMove(dstOpnd, functionObjOpnd, instr);
instr->Remove();
}
void
Lowerer::GenerateLdFuncObjProto(IR::Instr* instr)
{
// MOV instance, src1
//
// instance = ((RecyclableObject*)instance)->GetPrototype();
// if (instance == nullptr) goto $ThrowTypeError;
//
// MOV dst, instance
//
// if (!JavascriptOperators::IsConstructor(instance))
// goto $ThrowTypeError;
// else
// goto $Done;
//
// $helperLabelThrowTypeError:
// ThrowRuntimeTypeError(JSERR_NotAConstructor);
//
// $Done:
Func *func = instr->m_func;
IR::Opnd *src1Opnd = instr->UnlinkSrc1();
IR::LabelInstr *labelDone = IR::LabelInstr::New(Js::OpCode::Label, func, false);
IR::LabelInstr *helperLabelThrowTypeError = IR::LabelInstr::New(Js::OpCode::Label, func, false);
IR::RegOpnd *instanceRegOpnd = IR::RegOpnd::New(TyMachPtr, func);
IR::Opnd *dstOpnd = instr->GetDst();
Lowerer::InsertMove(instanceRegOpnd, src1Opnd, instr);
this->GenerateRecyclableObjectGetPrototypeNullptrGoto(instr, instanceRegOpnd, helperLabelThrowTypeError);
Lowerer::InsertMove(dstOpnd, instanceRegOpnd, instr);
this->GenerateJavascriptOperatorsIsConstructorGotoElse(instr, instanceRegOpnd, labelDone, helperLabelThrowTypeError);
instr->InsertBefore(helperLabelThrowTypeError);
this->GenerateRuntimeError(instr, JSERR_NotAConstructor, IR::HelperOp_RuntimeTypeError);
instr->InsertBefore(labelDone);
instr->Remove();
}
void
Lowerer::GenerateLoadNewTarget(IR::Instr* instrInsert)
{
Func *func = instrInsert->m_func;
IR::LabelInstr * labelDone = IR::LabelInstr::New(Js::OpCode::Label, func, false);
IR::LabelInstr * labelLoadArgNewTarget = IR::LabelInstr::New(Js::OpCode::Label, func, false);
IR::Opnd* opndUndefAddress = this->LoadLibraryValueOpnd(instrInsert, LibraryValue::ValueUndefined);
Assert(!func->IsInlinee());
if (func->GetJITFunctionBody()->IsCoroutine())
{
instrInsert->SetSrc1(opndUndefAddress);
LowererMD::ChangeToAssign(instrInsert);
return;
}
// MOV dst, undefined // dst = undefined
// MOV s1, callInfo // s1 = callInfo
// TEST s1, Js::CallFlags_NewTarget << 24 // if (callInfo.Flags & Js::CallFlags_NewTarget)
// JNE $LoadLastArgument // goto $LoadLastArgument
// TEST s1, Js::CallFlags_New << 24 // if (!(callInfo.Flags & Js::CallFlags_New))
// JE $Done // goto $Done
// MOV dst, functionObject // dst = functionObject
// JMP $Done // goto $Done
// $LoadLastArgument
// AND s1, s1, (0x00FFFFFF) // s2 = callInfo.Count == arguments.length + 2
// MOV dst, [ebp + (s1 - 1) * sizeof(Var) + formalParamOffset * sizeof(Var) ] // points to new.target
// $Done
IR::Opnd *dstOpnd = instrInsert->GetDst();
Assert(dstOpnd->IsRegOpnd());
Lowerer::InsertMove(dstOpnd, opndUndefAddress, instrInsert);
IR::SymOpnd *callInfoOpnd = Lowerer::LoadCallInfo(instrInsert);
Assert(Js::CallInfo::ksizeofCount == 24);
IR::RegOpnd *s1 = IR::RegOpnd::New(TyUint32, func);
Lowerer::InsertMove(s1, callInfoOpnd, instrInsert);
InsertTestBranch(s1, IR::IntConstOpnd::New((IntConstType)Js::CallFlags_NewTarget << Js::CallInfo::ksizeofCount, TyUint32, func, true), Js::OpCode::BrNeq_A, labelLoadArgNewTarget, instrInsert);
InsertTestBranch(s1, IR::IntConstOpnd::New((IntConstType)Js::CallFlags_New << Js::CallInfo::ksizeofCount, TyUint32, func, true), Js::OpCode::BrEq_A, labelDone, instrInsert);
IR::Instr* loadFuncInstr = IR::Instr::New(Js::OpCode::AND, func);
loadFuncInstr->SetDst(instrInsert->GetDst());
LoadFuncExpression(loadFuncInstr);
instrInsert->InsertBefore(loadFuncInstr);
InsertBranch(Js::OpCode::Br, labelDone, instrInsert);
instrInsert->InsertBefore(labelLoadArgNewTarget);
InsertAnd(s1, s1, IR::IntConstOpnd::New(0x00FFFFFF, TyUint32, func, true), instrInsert); // callInfo.Count
// [formalOffset (4) + callInfo.Count] points to 'new.target' - see diagram in GenerateLoadStackArgumentByIndex()
GenerateLoadStackArgumentByIndex(dstOpnd, s1, instrInsert, 0, m_func);
instrInsert->InsertBefore(labelDone);
instrInsert->Remove();
}
void
Lowerer::GenerateGetCurrentFunctionObject(IR::Instr * instr)
{
Func * func = this->m_func;
IR::Instr * insertBeforeInstr = instr->m_next;
IR::RegOpnd * functionObjectOpnd = instr->GetDst()->AsRegOpnd();
IR::Opnd * vtableAddressOpnd = this->LoadVTableValueOpnd(insertBeforeInstr, VTableValue::VtableStackScriptFunction);
IR::LabelInstr * labelDone = IR::LabelInstr::New(Js::OpCode::Label, func, false);
IR::BranchInstr *branchInstr = InsertCompareBranch(IR::IndirOpnd::New(functionObjectOpnd, 0, TyMachPtr, func), vtableAddressOpnd,
Js::OpCode::BrNeq_A, true, labelDone, insertBeforeInstr);
InsertObjectPoison(functionObjectOpnd, branchInstr, insertBeforeInstr, false);
IR::RegOpnd * boxedFunctionObjectOpnd = IR::RegOpnd::New(TyMachPtr, func);
InsertMove(boxedFunctionObjectOpnd, IR::IndirOpnd::New(functionObjectOpnd,
Js::StackScriptFunction::GetOffsetOfBoxedScriptFunction(), TyMachPtr, func), insertBeforeInstr);
InsertTestBranch(boxedFunctionObjectOpnd, boxedFunctionObjectOpnd, Js::OpCode::BrEq_A, true, labelDone, insertBeforeInstr);
InsertMove(functionObjectOpnd, boxedFunctionObjectOpnd, insertBeforeInstr);
insertBeforeInstr->InsertBefore(labelDone);
}
IR::Opnd *
Lowerer::GetInlineCacheFromFuncObjectForRuntimeUse(IR::Instr * instr, IR::PropertySymOpnd * propSymOpnd, bool isHelper)
{
// MOV s1, [ebp + 8] //s1 = function object
// MOV s2, [s1 + offset(hasInlineCaches)]
// TEST s2, s2
// JE $L1
// MOV s3, [s1 + offset(m_inlineCaches)] //s3 = inlineCaches from function object
// MOV s4, [s3 + index*scale] //s4 = inlineCaches[index]
// JMP $L2
// $L1
// MOV s3, propSym->m_runtimeCache
// $L2
byte indirScale = this->m_lowererMD.GetDefaultIndirScale();
IR::RegOpnd * funcObjOpnd = IR::RegOpnd::New(TyMachPtr, this->m_func);
IR::Instr * funcObjInstr = IR::Instr::New(Js::OpCode::Ld_A, funcObjOpnd, instr->m_func);
instr->InsertBefore(funcObjInstr);
LoadFuncExpression(funcObjInstr);
IR::RegOpnd * funcObjHasInlineCachesOpnd = IR::RegOpnd::New(TyMachPtr, instr->m_func);
this->InsertMove(funcObjHasInlineCachesOpnd, IR::IndirOpnd::New(funcObjOpnd, Js::ScriptFunction::GetOffsetOfHasInlineCaches(), TyUint8, instr->m_func), instr);
IR::LabelInstr * inlineCachesNullLabel = IR::LabelInstr::New(Js::OpCode::Label, instr->m_func, isHelper);
InsertTestBranch(funcObjHasInlineCachesOpnd, funcObjHasInlineCachesOpnd, Js::OpCode::BrEq_A, inlineCachesNullLabel, instr);
IR::RegOpnd * inlineCachesOpnd = IR::RegOpnd::New(TyMachPtr, instr->m_func);
Lowerer::InsertMove(inlineCachesOpnd, IR::IndirOpnd::New(funcObjOpnd, Js::ScriptFunctionWithInlineCache::GetOffsetOfInlineCaches(), TyMachPtr, instr->m_func), instr);
IR::RegOpnd * inlineCacheOpnd = IR::RegOpnd::New(TyMachPtr, instr->m_func);
IR::RegOpnd * indexOpnd = IR::RegOpnd::New(TyMachReg, instr->m_func);
int inlineCacheOffset;
if (!Int32Math::Mul(sizeof(Js::InlineCache *), propSymOpnd->m_inlineCacheIndex, &inlineCacheOffset))
{
Lowerer::InsertMove(inlineCacheOpnd, IR::IndirOpnd::New(inlineCachesOpnd, inlineCacheOffset, TyMachPtr, instr->m_func), instr);
}
else
{
Lowerer::InsertMove(indexOpnd, IR::IntConstOpnd::New(propSymOpnd->m_inlineCacheIndex, TyUint32, instr->m_func), instr);
Lowerer::InsertMove(inlineCacheOpnd, IR::IndirOpnd::New(inlineCachesOpnd, indexOpnd, indirScale, TyMachPtr, instr->m_func), instr);
}
IR::LabelInstr * continueLabel = IR::LabelInstr::New(Js::OpCode::Label, instr->m_func, isHelper);
InsertBranch(LowererMD::MDUncondBranchOpcode, continueLabel, instr);
IR::Instr * ldCacheFromPropSymOpndInstr = this->InsertMove(inlineCacheOpnd, IR::AddrOpnd::New(propSymOpnd->m_runtimeInlineCache, IR::AddrOpndKindDynamicInlineCache, this->m_func), instr);
ldCacheFromPropSymOpndInstr->InsertBefore(inlineCachesNullLabel);
ldCacheFromPropSymOpndInstr->InsertAfter(continueLabel);
return inlineCacheOpnd;
}
IR::Instr *
Lowerer::LowerInitClass(IR::Instr * instr)
{
// scriptContext
IR::Instr * prevInstr = LoadScriptContext(instr);
// extends
if (instr->GetSrc2() != nullptr)
{
IR::Opnd * extendsOpnd = instr->UnlinkSrc2();
m_lowererMD.LoadHelperArgument(instr, extendsOpnd);
}
else
{
IR::AddrOpnd* extendsOpnd = IR::AddrOpnd::NewNull(this->m_func);
m_lowererMD.LoadHelperArgument(instr, extendsOpnd);
}
// constructor
IR::Opnd * ctorOpnd = instr->UnlinkSrc1();
m_lowererMD.LoadHelperArgument(instr, ctorOpnd);
// call
m_lowererMD.ChangeToHelperCall(instr, IR::HelperOP_InitClass);
return prevInstr;
}
void
Lowerer::LowerNewConcatStrMulti(IR::Instr * instr)
{
IR::IntConstOpnd * countOpnd = instr->UnlinkSrc1()->AsIntConstOpnd();
IR::RegOpnd * dstOpnd = instr->UnlinkDst()->AsRegOpnd();
uint8 count = (uint8)countOpnd->GetValue();
Assert(dstOpnd->GetValueType().IsString());
GenerateRecyclerAlloc(IR::HelperAllocMemForConcatStringMulti, Js::ConcatStringMulti::GetAllocSize(count), dstOpnd, instr);
GenerateRecyclerMemInit(dstOpnd, 0, this->LoadVTableValueOpnd(instr, VTableValue::VtableConcatStringMulti), instr);
GenerateRecyclerMemInit(dstOpnd, Js::ConcatStringMulti::GetOffsetOfType(),
this->LoadLibraryValueOpnd(instr, LibraryValue::ValueStringTypeStatic), instr);
GenerateRecyclerMemInitNull(dstOpnd, Js::ConcatStringMulti::GetOffsetOfpszValue(), instr);
GenerateRecyclerMemInit(dstOpnd, Js::ConcatStringMulti::GetOffsetOfcharLength(), 0, instr);
GenerateRecyclerMemInit(dstOpnd, Js::ConcatStringMulti::GetOffsetOfSlotCount(), countOpnd->AsUint32(), instr);
instr->Remove();
}
void
Lowerer::LowerNewConcatStrMultiBE(IR::Instr * instr)
{
// Lower
// t1 = SetConcatStrMultiBE s1
// t2 = SetConcatStrMultiBE s2, t1
// t3 = SetConcatStrMultiBE s3, t2
// s = NewConcatStrMultiBE 3, t3
// to
// s = new concat string
// s+0 = s1
// s+1 = s2
// s+2 = s3
Assert(instr->GetSrc1()->IsConstOpnd());
Assert(instr->GetDst()->IsRegOpnd());
IR::RegOpnd * newString = instr->GetDst()->AsRegOpnd();
IR::Opnd * newConcatItemOpnd = nullptr;
uint index = instr->GetSrc1()->AsIntConstOpnd()->AsUint32() - 1;
IR::Instr * concatItemInstr = nullptr;
IR::Opnd * linkOpnd = instr->GetSrc2();
while (linkOpnd)
{
Assert(linkOpnd->IsRegOpnd());
concatItemInstr = linkOpnd->GetStackSym()->GetInstrDef();
Assert(concatItemInstr->m_opcode == Js::OpCode::SetConcatStrMultiItemBE);
IR::Opnd * concatItemOpnd = concatItemInstr->GetSrc1();
Assert(concatItemOpnd->IsRegOpnd());
// If one of the concat items is equal to the dst of the concat expressions (s = s + a + b),
// hoist the load of that item to before the setting of the new string to the dst.
if (concatItemOpnd->IsEqual(newString))
{
if (!newConcatItemOpnd)
{
IR::Instr * hoistSrcInstr = concatItemInstr->HoistSrc1(Js::OpCode::Ld_A);
newConcatItemOpnd = hoistSrcInstr->GetDst();
}
concatItemOpnd = newConcatItemOpnd;
}
else
{
// If only some of the SetConcatStrMultiItemBE instructions were CSE'd and the rest, along with the NewConcatStrMultiBE
// instruction, were in a loop, the strings on the CSE'd Set*BE instructions will become live on back edge. Add them to
// addToLiveOnBackEdgeSyms here and clear when we reach the Set*BE instruction.
// Note that we are doing this only for string opnds which are not the same as the dst of the concat expression. Reasoning
// behind this is that if a loop has a concat expression with one of its sources same as the dst, the Set*BE instruction
// for the dst wouldn't have been CSE'd as the dst's value is changing in the loop and the backward pass should have set the
// symbol as live on backedge.
this->addToLiveOnBackEdgeSyms->Set(concatItemOpnd->GetStackSym()->m_id);
}
IR::Instr * newConcatItemInstr = IR::Instr::New(Js::OpCode::SetConcatStrMultiItem,
IR::IndirOpnd::New(newString, index, TyVar, instr->m_func),
concatItemOpnd,
instr->m_func);
instr->InsertAfter(newConcatItemInstr);
this->LowerSetConcatStrMultiItem(newConcatItemInstr);
linkOpnd = concatItemInstr->GetSrc2();
index--;
}
Assert(index == -1);
this->LowerNewConcatStrMulti(instr);
}
void
Lowerer::LowerSetConcatStrMultiItem(IR::Instr * instr)
{
Func * func = this->m_func;
IR::IndirOpnd * dstOpnd = instr->GetDst()->AsIndirOpnd();
IR::RegOpnd * concatStrOpnd = dstOpnd->GetBaseOpnd();
IR::RegOpnd * srcOpnd = instr->UnlinkSrc1()->AsRegOpnd();
Assert(concatStrOpnd->GetValueType().IsString());
Assert(srcOpnd->GetValueType().IsString());
srcOpnd = GenerateGetImmutableOrScriptUnreferencedString(srcOpnd, instr, IR::HelperOp_CompoundStringCloneForConcat);
instr->SetSrc1(srcOpnd);
IR::IndirOpnd * dstLength = IR::IndirOpnd::New(concatStrOpnd, Js::ConcatStringMulti::GetOffsetOfcharLength(), TyUint32, func);
IR::Opnd * srcLength;
if (srcOpnd->m_sym->m_isStrConst)
{
srcLength = IR::IntConstOpnd::New(JITJavascriptString::FromVar(srcOpnd->m_sym->GetConstAddress(true))->GetLength(), TyUint32, func);
}
else
{
srcLength = IR::RegOpnd::New(TyUint32, func);
InsertMove(srcLength, IR::IndirOpnd::New(srcOpnd, Js::ConcatStringMulti::GetOffsetOfcharLength(), TyUint32, func), instr);
}
IR::Instr *onOverflowInsertBeforeInstr;
InsertAddWithOverflowCheck(false, dstLength, dstLength, srcLength, instr, &onOverflowInsertBeforeInstr);
IR::Instr* callInstr = IR::Instr::New(Js::OpCode::Call, func);
callInstr->SetSrc1(IR::HelperCallOpnd::New(IR::HelperOp_OutOfMemoryError, func));
instr->InsertBefore(onOverflowInsertBeforeInstr);
onOverflowInsertBeforeInstr->InsertBefore(callInstr);
this->m_lowererMD.LowerCall(callInstr, 0);
dstOpnd->SetOffset(dstOpnd->GetOffset() * sizeof(Js::JavascriptString *) + Js::ConcatStringMulti::GetOffsetOfSlots());
LowererMD::ChangeToWriteBarrierAssign(instr, func);
}
IR::RegOpnd *
Lowerer::GenerateGetImmutableOrScriptUnreferencedString(IR::RegOpnd * strOpnd, IR::Instr * insertBeforeInstr, IR::JnHelperMethod helperMethod, bool reloadDst)
{
if (strOpnd->m_sym->m_isStrConst)
{
return strOpnd;
}
Func * const func = this->m_func;
IR::RegOpnd *dstOpnd = reloadDst == true ? IR::RegOpnd::New(TyVar, func) : strOpnd;
IR::LabelInstr * helperLabel = IR::LabelInstr::New(Js::OpCode::Label, func, true);
IR::LabelInstr * doneLabel = IR::LabelInstr::New(Js::OpCode::Label, func);
if (!strOpnd->IsNotTaggedValue())
{
this->m_lowererMD.GenerateObjectTest(strOpnd, insertBeforeInstr, doneLabel);
}
// CMP [strOpnd], Js::CompoundString::`vtable'
// JEQ $helper
InsertCompareBranch(
IR::IndirOpnd::New(strOpnd, 0, TyMachPtr, func),
this->LoadVTableValueOpnd(insertBeforeInstr, VTableValue::VtableCompoundString),
Js::OpCode::BrEq_A,
helperLabel,
insertBeforeInstr);
if (reloadDst)
{
InsertMove(dstOpnd, strOpnd, insertBeforeInstr);
}
InsertBranch(Js::OpCode::Br, doneLabel, insertBeforeInstr);
insertBeforeInstr->InsertBefore(helperLabel);
this->m_lowererMD.LoadHelperArgument(insertBeforeInstr, strOpnd);
IR::Instr* callInstr = IR::Instr::New(Js::OpCode::Call, dstOpnd, func);
callInstr->SetSrc1(IR::HelperCallOpnd::New(helperMethod, func));
insertBeforeInstr->InsertBefore(callInstr);
this->m_lowererMD.LowerCall(callInstr, 0);
insertBeforeInstr->InsertBefore(doneLabel);
return dstOpnd;
}
void
Lowerer::LowerConvStrCommon(IR::JnHelperMethod helper, IR::Instr * instr)
{
IR::RegOpnd * src1Opnd = instr->UnlinkSrc1()->AsRegOpnd();
if (!src1Opnd->GetValueType().IsNotString())
{
IR::LabelInstr * helperLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func, true);
IR::LabelInstr * doneLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func);
this->GenerateStringTest(src1Opnd, instr, helperLabel);
InsertMove(instr->GetDst(), src1Opnd, instr);
InsertBranch(Js::OpCode::Br, doneLabel, instr);
instr->InsertBefore(helperLabel);
instr->InsertAfter(doneLabel);
}
if (instr->GetSrc2())
{
this->m_lowererMD.LoadHelperArgument(instr, instr->UnlinkSrc2());
}
this->LoadScriptContext(instr);
this->m_lowererMD.LoadHelperArgument(instr, src1Opnd);
this->m_lowererMD.ChangeToHelperCall(instr, helper);
}
void
Lowerer::LowerConvStr(IR::Instr * instr)
{
LowerConvStrCommon(IR::HelperOp_ConvString, instr);
}
void
Lowerer::LowerCoerseStr(IR::Instr* instr)
{
LowerConvStrCommon(IR::HelperOp_CoerseString, instr);
}
///----------------------------------------------------------------------------
///
/// Lowerer::LowerCoerseStrOrRegex - This method is used for String.Replace(arg1, arg2)
/// where arg1 is regex or string
/// if arg1 is not regex, then do String.Replace(CoerseStr(arg1), arg2);
///
/// CoerseStrOrRegex arg1
///
/// if (value == regex) goto :done
/// else
///helper:
/// ConvStr value
///done:
///----------------------------------------------------------------------------
void
Lowerer::LowerCoerseStrOrRegex(IR::Instr* instr)
{
IR::RegOpnd * src1Opnd = instr->GetSrc1()->AsRegOpnd();
IR::LabelInstr * helperLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func, true);
IR::LabelInstr * doneLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func);
// if (value == regex) goto :done
if (!src1Opnd->IsNotTaggedValue())
{
this->m_lowererMD.GenerateObjectTest(src1Opnd, instr, helperLabel);
}
IR::Opnd * vtableOpnd = LoadVTableValueOpnd(instr, VTableValue::VtableJavascriptRegExp);
InsertCompareBranch(IR::IndirOpnd::New(src1Opnd, 0, TyMachPtr, instr->m_func),
vtableOpnd, Js::OpCode::BrNeq_A, helperLabel, instr);
InsertMove(instr->GetDst(), src1Opnd, instr);
InsertBranch(Js::OpCode::Br, doneLabel, instr);
instr->InsertBefore(helperLabel);
instr->InsertAfter(doneLabel);
// helper: ConvStr value
LowerConvStr(instr);
}
///----------------------------------------------------------------------------
///
/// Lowerer::LowerCoerseRegex - This method is used for String.Match(arg1)
/// if arg1 is regex, then pass CreateRegEx(arg1) to String.Match
///
///----------------------------------------------------------------------------
void
Lowerer::LowerCoerseRegex(IR::Instr* instr)
{
IR::LabelInstr * helperLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func, true);
IR::LabelInstr * doneLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func);
IR::RegOpnd * src1Opnd = instr->UnlinkSrc1()->AsRegOpnd();
if (!src1Opnd->IsNotTaggedValue())
{
this->m_lowererMD.GenerateObjectTest(src1Opnd, instr, helperLabel);
}
IR::Opnd * vtableOpnd = LoadVTableValueOpnd(instr, VTableValue::VtableJavascriptRegExp);
InsertCompareBranch(IR::IndirOpnd::New(src1Opnd, 0, TyMachPtr, instr->m_func),
vtableOpnd, Js::OpCode::BrNeq_A, helperLabel, instr);
InsertMove(instr->GetDst(), src1Opnd, instr);
InsertBranch(Js::OpCode::Br, doneLabel, instr);
instr->InsertBefore(helperLabel);
instr->InsertAfter(doneLabel);
this->LoadScriptContext(instr);
this->m_lowererMD.LoadHelperArgument(instr, IR::AddrOpnd::NewNull(instr->m_func)); // option
this->m_lowererMD.LoadHelperArgument(instr, src1Opnd); // regex
this->m_lowererMD.ChangeToHelperCall(instr, IR::HelperOp_CoerseRegex);
}
void
Lowerer::LowerConvPrimStr(IR::Instr * instr)
{
LowerConvStrCommon(IR::HelperOp_ConvPrimitiveString, instr);
}
void
Lowerer::GenerateRecyclerAlloc(IR::JnHelperMethod allocHelper, size_t allocSize, IR::RegOpnd* newObjDst, IR::Instr* insertionPointInstr, bool inOpHelper)
{
size_t alignedSize = HeapInfo::GetAlignedSizeNoCheck(allocSize);
this->GenerateRecyclerAllocAligned(allocHelper, alignedSize, newObjDst, insertionPointInstr, inOpHelper);
}
void
Lowerer::GenerateMemInit(IR::RegOpnd * opnd, int32 offset, int32 value, IR::Instr * insertBeforeInstr, bool isZeroed)
{
IRType type = TyInt32;
if (isZeroed)
{
if (value == 0)
{
// Recycler memory are zero initialized
return;
}
if (value > 0 && value <= USHORT_MAX)
{
// Recycler memory are zero initialized, so we can just initialize the 8 or 16 bits of value
type = (value <= UCHAR_MAX)? TyUint8 : TyUint16;
}
}
Func * func = this->m_func;
InsertMove(IR::IndirOpnd::New(opnd, offset, type, func), IR::IntConstOpnd::New(value, type, func), insertBeforeInstr);
}
void
Lowerer::GenerateMemInit(IR::RegOpnd * opnd, int32 offset, uint32 value, IR::Instr * insertBeforeInstr, bool isZeroed)
{
IRType type = TyUint32;
if (isZeroed)
{
if (value == 0)
{
// Recycler memory are zero initialized
return;
}
if (value <= USHORT_MAX)
{
// Recycler memory are zero initialized, so we can just initialize the 8 or 16 bits of value
type = (value <= UCHAR_MAX)? TyUint8 : TyUint16;
}
}
Func * func = this->m_func;
InsertMove(IR::IndirOpnd::New(opnd, offset, type, func), IR::IntConstOpnd::New(value, type, func), insertBeforeInstr);
}
void
Lowerer::GenerateMemInitNull(IR::RegOpnd * opnd, int32 offset, IR::Instr * insertBeforeInstr, bool isZeroed)
{
if (isZeroed)
{
return;
}
GenerateMemInit(opnd, offset, IR::AddrOpnd::NewNull(m_func), insertBeforeInstr);
}
void
Lowerer::GenerateMemInit(IR::RegOpnd * opnd, int32 offset, IR::Opnd * value, IR::Instr * insertBeforeInstr, bool isZeroed)
{
IRType type = value->GetType();
Func * func = this->m_func;
InsertMove(IR::IndirOpnd::New(opnd, offset, type, func), value, insertBeforeInstr);
}
void
Lowerer::GenerateMemInit(IR::RegOpnd * opnd, IR::RegOpnd * offset, IR::Opnd * value, IR::Instr * insertBeforeInstr, bool isZeroed)
{
IRType type = value->GetType();
Func * func = this->m_func;
InsertMove(IR::IndirOpnd::New(opnd, offset, type, func), value, insertBeforeInstr);
}
void
Lowerer::GenerateRecyclerMemInit(IR::RegOpnd * opnd, int32 offset, int32 value, IR::Instr * insertBeforeInstr)
{
GenerateMemInit(opnd, offset, value, insertBeforeInstr, true);
}
void
Lowerer::GenerateRecyclerMemInit(IR::RegOpnd * opnd, int32 offset, uint32 value, IR::Instr * insertBeforeInstr)
{
GenerateMemInit(opnd, offset, value, insertBeforeInstr, true);
}
void
Lowerer::GenerateRecyclerMemInitNull(IR::RegOpnd * opnd, int32 offset, IR::Instr * insertBeforeInstr)
{
GenerateMemInitNull(opnd, offset, insertBeforeInstr, true);
}
void
Lowerer::GenerateRecyclerMemInit(IR::RegOpnd * opnd, int32 offset, IR::Opnd * value, IR::Instr * insertBeforeInstr)
{
GenerateMemInit(opnd, offset, value, insertBeforeInstr, true);
}
void
Lowerer::GenerateMemCopy(IR::Opnd * dst, IR::Opnd * src, uint32 size, IR::Instr * insertBeforeInstr)
{
Func * func = this->m_func;
this->m_lowererMD.LoadHelperArgument(insertBeforeInstr, IR::IntConstOpnd::New(size, TyUint32, func));
this->m_lowererMD.LoadHelperArgument(insertBeforeInstr, src);
this->m_lowererMD.LoadHelperArgument(insertBeforeInstr, dst);
IR::Instr * memcpyInstr = IR::Instr::New(Js::OpCode::Call, func);
memcpyInstr->SetSrc1(IR::HelperCallOpnd::New(IR::HelperMemCpy, func));
insertBeforeInstr->InsertBefore(memcpyInstr);
m_lowererMD.LowerCall(memcpyInstr, 3);
}
bool
Lowerer::GenerateSimplifiedInt4Rem(
IR::Instr *const remInstr,
IR::LabelInstr *const skipBailOutLabel) const
{
Assert(remInstr);
Assert(remInstr->m_opcode == Js::OpCode::Rem_I4 || remInstr->m_opcode == Js::OpCode::RemU_I4);
auto *dst = remInstr->GetDst(), *src1 = remInstr->GetSrc1(), *src2 = remInstr->GetSrc2();
Assert(src1 && src2);
Assert(dst->IsRegOpnd());
bool isModByPowerOf2 = (remInstr->HasBailOutInfo() && remInstr->GetBailOutKind() == IR::BailOnModByPowerOf2);
if (PHASE_OFF(Js::Phase::MathFastPathPhase, remInstr->m_func->GetTopFunc()) && !isModByPowerOf2)
return false;
if (!(src2->IsIntConstOpnd() && Math::IsPow2(src2->AsIntConstOpnd()->AsInt32())) && !isModByPowerOf2)
{
return false;
}
// We have:
// s3 = s1 % s2 , where s2 = +2^i
//
// Generate:
// test s1, s1
// js $slowPathLabel
// s3 = and s1, 0x00..fff (2^i - 1)
// jmp $doneLabel
// $slowPathLabel:
// (Slow path)
// (Neg zero check)
// (Bailout code)
// $doneLabel:
IR::LabelInstr *doneLabel = skipBailOutLabel, *slowPathLabel;
if (!doneLabel)
{
doneLabel = IR::LabelInstr::New(Js::OpCode::Label, remInstr->m_func);
remInstr->InsertAfter(doneLabel);
}
slowPathLabel = IR::LabelInstr::New(Js::OpCode::Label, remInstr->m_func, isModByPowerOf2);
remInstr->InsertBefore(slowPathLabel);
// test s1, s1
InsertTest(src1, src1, slowPathLabel);
// jsb $slowPathLabel
InsertBranch(LowererMD::MDCompareWithZeroBranchOpcode(Js::OpCode::BrLt_A), slowPathLabel, slowPathLabel);
// s3 = and s1, 0x00..fff (2^i - 1)
IR::Opnd* maskOpnd;
if(isModByPowerOf2)
{
Assert(isModByPowerOf2);
maskOpnd = IR::RegOpnd::New(TyInt32, remInstr->m_func);
// mov maskOpnd, s2
InsertMove(maskOpnd, src2, slowPathLabel);
// dec maskOpnd
InsertSub(/*needFlags*/ true, maskOpnd, maskOpnd, IR::IntConstOpnd::New(1, TyInt32, this->m_func, /*dontEncode*/true), slowPathLabel);
// maskOpnd < 0 goto $slowPath
InsertBranch(LowererMD::MDCompareWithZeroBranchOpcode(Js::OpCode::BrLt_A), slowPathLabel, slowPathLabel);
// TEST src2, maskOpnd
InsertTestBranch(src2, maskOpnd, Js::OpCode::BrNeq_A, slowPathLabel, slowPathLabel);
}
else
{
Assert(src2->IsIntConstOpnd());
int32 mask = src2->AsIntConstOpnd()->AsInt32() - 1;
maskOpnd = IR::IntConstOpnd::New(mask, TyInt32, remInstr->m_func);
}
// dst = src1 & maskOpnd
InsertAnd(dst, src1, maskOpnd, slowPathLabel);
// jmp $doneLabel
InsertBranch(Js::OpCode::Br, doneLabel, slowPathLabel);
return true;
}
#if DBG
bool
Lowerer::ValidOpcodeAfterLower(IR::Instr* instr, Func * func)
{
Js::OpCode opcode = instr->m_opcode;
if (opcode > Js::OpCode::MDStart)
{
return true;
}
switch (opcode)
{
case Js::OpCode::Ret:
case Js::OpCode::Label:
case Js::OpCode::StatementBoundary:
case Js::OpCode::DeletedNonHelperBranch:
case Js::OpCode::FunctionEntry:
case Js::OpCode::FunctionExit:
case Js::OpCode::TryCatch:
case Js::OpCode::TryFinally:
case Js::OpCode::Catch:
case Js::OpCode::GeneratorResumeJumpTable:
case Js::OpCode::Break:
#ifdef _M_X64
case Js::OpCode::PrologStart:
case Js::OpCode::PrologEnd:
#endif
#ifdef _M_IX86
case Js::OpCode::BailOutStackRestore:
#endif
return true;
case Js::OpCode::RestoreOutParam:
Assert(func->isPostRegAlloc);
return true;
// These may be removed by peep
case Js::OpCode::StartCall:
case Js::OpCode::LoweredStartCall:
case Js::OpCode::Nop:
case Js::OpCode::ArgOut_A_InlineBuiltIn:
return func && !func->isPostPeeps;
case Js::OpCode::InlineeStart:
case Js::OpCode::InlineeEnd:
return instr->m_func->m_hasInlineArgsOpt;
#ifdef _M_X64
case Js::OpCode::LdArgSize:
case Js::OpCode::LdSpillSize:
return func && !func->isPostFinalLower;
#endif
case Js::OpCode::Leave:
Assert(!func->IsLoopBodyInTry());
Assert(func->HasTry() && func->DoOptimizeTry());
return func && !func->isPostFinalLower; //Lowered in FinalLower phase
case Js::OpCode::LazyBailOutThunkLabel:
return func && func->HasLazyBailOut() && func->isPostFinalLower; //Lowered in FinalLower phase
};
return false;
}
#endif
void Lowerer::LowerProfiledBeginSwitch(IR::JitProfilingInstr* instr)
{
Assert(instr->isBeginSwitch);
m_lowererMD.LoadHelperArgument(instr, instr->UnlinkSrc1());
m_lowererMD.LoadHelperArgument(instr, IR::Opnd::CreateProfileIdOpnd(instr->profileId, m_func));
m_lowererMD.LoadHelperArgument(instr, CreateFunctionBodyOpnd(instr->m_func));
instr->SetSrc1(IR::HelperCallOpnd::New(IR::HelperSimpleProfiledSwitch, m_func));
m_lowererMD.LowerCall(instr, 0);
}
void Lowerer::LowerProfiledBinaryOp(IR::JitProfilingInstr* instr, IR::JnHelperMethod meth)
{
m_lowererMD.LoadHelperArgument(instr, instr->UnlinkSrc2());
m_lowererMD.LoadHelperArgument(instr, instr->UnlinkSrc1());
m_lowererMD.LoadHelperArgument(instr, IR::Opnd::CreateProfileIdOpnd(instr->profileId, m_func));
m_lowererMD.LoadHelperArgument(instr, CreateFunctionBodyOpnd(instr->m_func));
instr->SetSrc1(IR::HelperCallOpnd::New(meth, m_func));
m_lowererMD.LowerCall(instr, 0);
}
void Lowerer::GenerateNullOutGeneratorFrame(IR::Instr* insertInstr)
{
// null out frame pointer on generator object to signal completion to JavascriptGenerator::CallGenerator
// s = MOV prm1
// s[offset of JavascriptGenerator::frame] = MOV nullptr
StackSym *symSrc = StackSym::NewImplicitParamSym(3, m_func);
m_func->SetArgOffset(symSrc, LowererMD::GetFormalParamOffset() * MachPtr);
IR::SymOpnd *srcOpnd = IR::SymOpnd::New(symSrc, TyMachPtr, m_func);
IR::RegOpnd *dstOpnd = IR::RegOpnd::New(TyMachReg, m_func);
InsertMove(dstOpnd, srcOpnd, insertInstr);
IR::IndirOpnd *indirOpnd = IR::IndirOpnd::New(dstOpnd, Js::JavascriptGenerator::GetFrameOffset(), TyMachPtr, m_func);
IR::AddrOpnd *addrOpnd = IR::AddrOpnd::NewNull(m_func);
InsertMove(indirOpnd, addrOpnd, insertInstr);
}
void Lowerer::LowerFunctionExit(IR::Instr* funcExit)
{
if (m_func->GetJITFunctionBody()->IsCoroutine())
{
GenerateNullOutGeneratorFrame(funcExit->m_prev);
}
if (!m_func->DoSimpleJitDynamicProfile())
{
return;
}
IR::Instr* callInstr = IR::Instr::New(Js::OpCode::Call, m_func);
callInstr->SetSrc1(IR::HelperCallOpnd::New(IR::HelperSimpleCleanImplicitCallFlags, m_func));
funcExit->m_prev->InsertBefore(callInstr);
m_lowererMD.LoadHelperArgument(callInstr, CreateFunctionBodyOpnd(funcExit->m_func));
m_lowererMD.LowerCall(callInstr, 0);
}
void Lowerer::LowerFunctionEntry(IR::Instr* funcEntry)
{
Assert(funcEntry->m_opcode == Js::OpCode::FunctionEntry);
//Don't do a body call increment for loops or asm.js
if (m_func->IsLoopBody() || m_func->GetJITFunctionBody()->IsAsmJsMode())
{
return;
}
IR::Instr *const insertBeforeInstr = this->m_func->GetFunctionEntryInsertionPoint();
LowerFunctionBodyCallCountChange(insertBeforeInstr);
if (m_func->DoSimpleJitDynamicProfile())
{
// Only generate the argument profiling if the function expects to have some arguments to profile and only if
// it has implicit ArgIns (the latter is a restriction imposed by the Interpreter, so it is mirrored in SimpleJit)
if (m_func->GetJITFunctionBody()->GetInParamsCount() > 1 && m_func->GetJITFunctionBody()->HasImplicitArgIns())
{
// Call out to the argument profiling helper
IR::Instr* callInstr = IR::Instr::New(Js::OpCode::Call, m_func);
callInstr->SetSrc1(IR::HelperCallOpnd::New(IR::HelperSimpleProfileParameters, m_func));
insertBeforeInstr->InsertBefore(callInstr);
m_lowererMD.LoadHelperArgument(callInstr, IR::Opnd::CreateFramePointerOpnd(m_func));
m_lowererMD.LowerCall(callInstr, 0);
}
// Clear existing ImplicitCallFlags
const auto starFlag = GetImplicitCallFlagsOpnd();
this->InsertMove(starFlag, CreateClearImplicitCallFlagsOpnd(), insertBeforeInstr);
}
}
void Lowerer::LowerFunctionBodyCallCountChange(IR::Instr *const insertBeforeInstr)
{
Assert(insertBeforeInstr);
Func *const func = insertBeforeInstr->m_func;
const bool isSimpleJit = func->IsSimpleJit();
if ((isSimpleJit && PHASE_OFF(Js::FullJitPhase, m_func)))
{
return;
}
// mov countAddress, <countAddress>
IR::RegOpnd *const countAddressOpnd = IR::RegOpnd::New(StackSym::New(TyMachPtr, func), TyMachPtr, func);
const IR::AutoReuseOpnd autoReuseCountAddressOpnd(countAddressOpnd, func);
InsertMove(
countAddressOpnd,
IR::AddrOpnd::New((Js::Var)func->GetWorkItem()->GetCallsCountAddress(), IR::AddrOpndKindDynamicMisc, func, true),
insertBeforeInstr);
IR::IndirOpnd *const countOpnd = IR::IndirOpnd::New(countAddressOpnd, 0, TyUint32, func);
const IR::AutoReuseOpnd autoReuseCountOpnd(countOpnd, func);
if(!isSimpleJit)
{
InsertAdd(false, countOpnd, countOpnd, IR::IntConstOpnd::New(1, TyUint32, func), insertBeforeInstr);
return;
}
IR::Instr *onOverflowInsertBeforeInstr;
InsertDecUInt32PreventOverflow(
countOpnd,
countOpnd,
insertBeforeInstr,
&onOverflowInsertBeforeInstr);
// ($overflow:)
// TransitionFromSimpleJit(framePointer)
m_lowererMD.LoadHelperArgument(onOverflowInsertBeforeInstr, IR::Opnd::CreateFramePointerOpnd(func));
IR::Instr *const callInstr = IR::Instr::New(Js::OpCode::Call, func);
callInstr->SetSrc1(IR::HelperCallOpnd::New(IR::HelperTransitionFromSimpleJit, func));
onOverflowInsertBeforeInstr->InsertBefore(callInstr);
m_lowererMD.LowerCall(callInstr, 0);
}
IR::Opnd*
Lowerer::GetImplicitCallFlagsOpnd()
{
return GetImplicitCallFlagsOpnd(m_func);
}
IR::Opnd*
Lowerer::GetImplicitCallFlagsOpnd(Func * func)
{
return IR::MemRefOpnd::New(func->GetThreadContextInfo()->GetImplicitCallFlagsAddr(), GetImplicitCallFlagsType(), func);
}
IR::Opnd*
Lowerer::CreateClearImplicitCallFlagsOpnd()
{
return IR::IntConstOpnd::New(Js::ImplicitCall_None, GetImplicitCallFlagsType(), m_func);
}
void
Lowerer::GenerateFlagInlineCacheCheckForGetterSetter(
IR::Instr * insertBeforeInstr,
IR::RegOpnd * opndInlineCache,
IR::LabelInstr * labelNext)
{
uint accessorFlagMask;
if (PHASE_OFF(Js::InlineGettersPhase, insertBeforeInstr->m_func))
{
accessorFlagMask = Js::InlineCache::GetSetterFlagMask();
}
else if (PHASE_OFF(Js::InlineSettersPhase, insertBeforeInstr->m_func))
{
accessorFlagMask = Js::InlineCache::GetGetterFlagMask();
}
else
{
accessorFlagMask = Js::InlineCache::GetGetterSetterFlagMask();
}
// Generate:
//
// TEST [&(inlineCache->u.accessor.flags)], Js::InlineCacheGetterFlag | Js::InlineCacheSetterFlag
// JEQ $next
IR::Opnd * flagsOpnd = IR::IndirOpnd::New(opndInlineCache, (int32)offsetof(Js::InlineCache, u.accessor.rawUInt16), TyInt8, insertBeforeInstr->m_func);
IR::Opnd * accessorOpnd = IR::IntConstOpnd::New(accessorFlagMask, TyInt8, this->m_func);
InsertTestBranch(flagsOpnd, accessorOpnd, Js::OpCode::BrEq_A, labelNext, insertBeforeInstr);
}
IR::BranchInstr *
Lowerer::GenerateLocalInlineCacheCheck(
IR::Instr * instrLdSt,
IR::RegOpnd * opndType,
IR::RegOpnd * inlineCache,
IR::LabelInstr * labelNext,
bool checkTypeWithoutProperty)
{
// Generate:
//
// CMP s1, [&(inlineCache->u.local.type/typeWithoutProperty)]
// JNE $next
IR::Opnd* typeOpnd;
if (checkTypeWithoutProperty)
{
typeOpnd = IR::IndirOpnd::New(inlineCache, (int32)offsetof(Js::InlineCache, u.local.typeWithoutProperty), TyMachReg, instrLdSt->m_func);
}
else
{
typeOpnd = IR::IndirOpnd::New(inlineCache, (int32)offsetof(Js::InlineCache, u.local.type), TyMachReg, instrLdSt->m_func);
}
InsertCompare(opndType, typeOpnd, instrLdSt);
return InsertBranch(Js::OpCode::BrNeq_A, labelNext, instrLdSt);
}
IR::BranchInstr *
Lowerer::GenerateProtoInlineCacheCheck(
IR::Instr * instrLdSt,
IR::RegOpnd * opndType,
IR::RegOpnd * inlineCache,
IR::LabelInstr * labelNext)
{
// Generate:
//
// CMP s1, [&(inlineCache->u.proto.type)]
// JNE $next
IR::Opnd* typeOpnd = IR::IndirOpnd::New(inlineCache, (int32)offsetof(Js::InlineCache, u.proto.type), TyMachReg, instrLdSt->m_func);
InsertCompare(opndType, typeOpnd, instrLdSt);
return InsertBranch(Js::OpCode::BrNeq_A, labelNext, instrLdSt);
}
void
Lowerer::GenerateFlagInlineCacheCheck(
IR::Instr * instrLdSt,
IR::RegOpnd * opndType,
IR::RegOpnd * opndInlineCache,
IR::LabelInstr * labelNext)
{
// Generate:
//
// CMP s1, [&(inlineCache->u.accessor.type)]
// JNE $next
IR::Opnd* typeOpnd = IR::IndirOpnd::New(opndInlineCache, (int32)offsetof(Js::InlineCache, u.accessor.type), TyMachReg, instrLdSt->m_func);
// CMP s1, [&(inlineCache->u.flag.type)]
InsertCompareBranch(opndType, typeOpnd, Js::OpCode::BrNeq_A, labelNext, instrLdSt);
}
void
Lowerer::GenerateLdFldFromLocalInlineCache(
IR::Instr * instrLdFld,
IR::RegOpnd * opndBase,
IR::Opnd * opndDst,
IR::RegOpnd * opndInlineCache,
IR::LabelInstr * labelFallThru,
bool isInlineSlot)
{
// Generate:
//
// s1 = MOV base->slots -- load the slot array
// s2 = MOVZXw [&(inlineCache->u.local.slotIndex)] -- load the cached slot index
// dst = MOV [s1 + s2 * Scale] -- load the value directly from the slot
// JMP $fallthru
IR::IndirOpnd * opndIndir = nullptr;
IR::RegOpnd * opndSlotArray = nullptr;
if (!isInlineSlot)
{
opndSlotArray = IR::RegOpnd::New(TyMachReg, instrLdFld->m_func);
opndIndir = IR::IndirOpnd::New(opndBase, Js::DynamicObject::GetOffsetOfAuxSlots(), TyMachReg, instrLdFld->m_func);
InsertMove(opndSlotArray, opndIndir, instrLdFld);
}
// s2 = MOVZXw [&(inlineCache->u.local.slotIndex)] -- load the cached slot index
IR::RegOpnd * opndReg2 = IR::RegOpnd::New(TyMachReg, instrLdFld->m_func);
opndIndir = IR::IndirOpnd::New(opndInlineCache, (int32)offsetof(Js::InlineCache, u.local.slotIndex), TyUint16, instrLdFld->m_func);
InsertMove(opndReg2, opndIndir, instrLdFld);
if (isInlineSlot)
{
// dst = MOV [base + s2 * Scale] -- load the value directly from the slot
opndIndir = IR::IndirOpnd::New(opndBase, opndReg2, LowererMD::GetDefaultIndirScale(), TyMachReg, instrLdFld->m_func);
InsertMove(opndDst, opndIndir, instrLdFld);
}
else
{
// dst = MOV [s1 + s2 * Scale] -- load the value directly from the slot
opndIndir = IR::IndirOpnd::New(opndSlotArray, opndReg2, LowererMD::GetDefaultIndirScale(), TyMachReg, instrLdFld->m_func);
InsertMove(opndDst, opndIndir, instrLdFld);
}
// JMP $fallthru
InsertBranch(Js::OpCode::Br, labelFallThru, instrLdFld);
}
void
Lowerer::GenerateLdFldFromProtoInlineCache(
IR::Instr * instrLdFld,
IR::RegOpnd * opndBase,
IR::Opnd * opndDst,
IR::RegOpnd * inlineCache,
IR::LabelInstr * labelFallThru,
bool isInlineSlot)
{
// Generate:
//
// s1 = MOV [&(inlineCache->u.proto.prototypeObject)] -- load the cached prototype object
// s1 = MOV [&s1->slots] -- load the slot array
// s2 = MOVZXW [&(inlineCache->u.proto.slotIndex)] -- load the cached slot index
// dst = MOV [s1 + s2*4]
// JMP $fallthru
IR::IndirOpnd * opndIndir = nullptr;
IR::RegOpnd * opndProtoSlots = nullptr;
// s1 = MOV [&(inlineCache->u.proto.prototypeObject)] -- load the cached prototype object
IR::RegOpnd * opndProto = IR::RegOpnd::New(TyMachReg, instrLdFld->m_func);
opndIndir = IR::IndirOpnd::New(inlineCache, (int32)offsetof(Js::InlineCache, u.proto.prototypeObject), TyMachReg, instrLdFld->m_func);
InsertMove(opndProto, opndIndir, instrLdFld);
if (!isInlineSlot)
{
// s1 = MOV [&s1->slots] -- load the slot array
opndProtoSlots = IR::RegOpnd::New(TyMachReg, instrLdFld->m_func);
opndIndir = IR::IndirOpnd::New(opndProto, Js::DynamicObject::GetOffsetOfAuxSlots(), TyMachReg, instrLdFld->m_func);
InsertMove(opndProtoSlots, opndIndir, instrLdFld);
}
// s2 = MOVZXW [&(inlineCache->u.proto.slotIndex)] -- load the cached slot index
IR::RegOpnd * opndSlotIndex = IR::RegOpnd::New(TyMachReg, instrLdFld->m_func);
opndIndir = IR::IndirOpnd::New(inlineCache, (int32)offsetof(Js::InlineCache, u.proto.slotIndex), TyUint16, instrLdFld->m_func);
InsertMove(opndSlotIndex, opndIndir, instrLdFld);
if (isInlineSlot)
{
// dst = MOV [s1 + s2*4]
opndIndir = IR::IndirOpnd::New(opndProto, opndSlotIndex, LowererMD::GetDefaultIndirScale(), TyMachReg, instrLdFld->m_func);
InsertMove(opndDst, opndIndir, instrLdFld);
}
else
{
// dst = MOV [s1 + s2*4]
opndIndir = IR::IndirOpnd::New(opndProtoSlots, opndSlotIndex, LowererMD::GetDefaultIndirScale(), TyMachReg, instrLdFld->m_func);
InsertMove(opndDst, opndIndir, instrLdFld);
}
// JMP $fallthru
InsertBranch(Js::OpCode::Br, labelFallThru, instrLdFld);
}
void
Lowerer::GenerateLdFldFromFlagInlineCache(
IR::Instr * insertBeforeInstr,
IR::RegOpnd * opndBase,
IR::Opnd * opndDst,
IR::RegOpnd * opndInlineCache,
IR::LabelInstr * labelFallThru,
bool isInlineSlot)
{
// Generate:
//
// s1 = MOV [&(inlineCache->u.accessor.object)] -- load the cached prototype object
// s1 = MOV [&s1->slots] -- load the slot array
// s2 = MOVZXW [&(inlineCache->u.accessor.slotIndex)] -- load the cached slot index
// dst = MOV [s1 + s2 * 4]
// JMP $fallthru
IR::IndirOpnd * opndIndir = nullptr;
IR::RegOpnd * opndObjSlots = nullptr;
// s1 = MOV [&(inlineCache->u.accessor.object)] -- load the cached prototype object
IR::RegOpnd * opndObject = IR::RegOpnd::New(TyMachReg, this->m_func);
opndIndir = IR::IndirOpnd::New(opndInlineCache, (int32)offsetof(Js::InlineCache, u.accessor.object), TyMachReg, this->m_func);
InsertMove(opndObject, opndIndir, insertBeforeInstr);
if (!isInlineSlot)
{
// s1 = MOV [&s1->slots] -- load the slot array
opndObjSlots = IR::RegOpnd::New(TyMachReg, this->m_func);
opndIndir = IR::IndirOpnd::New(opndObject, Js::DynamicObject::GetOffsetOfAuxSlots(), TyMachReg, this->m_func);
InsertMove(opndObjSlots, opndIndir, insertBeforeInstr);
}
// s2 = MOVZXW [&(inlineCache->u.accessor.slotIndex)] -- load the cached slot index
IR::RegOpnd * opndSlotIndex = IR::RegOpnd::New(TyMachReg, this->m_func);
opndIndir = IR::IndirOpnd::New(opndInlineCache, (int32)offsetof(Js::InlineCache, u.accessor.slotIndex), TyUint16, this->m_func);
InsertMove(opndSlotIndex, opndIndir, insertBeforeInstr);
if (isInlineSlot)
{
// dst = MOV [s1 + s2 * 4]
opndIndir = IR::IndirOpnd::New(opndObject, opndSlotIndex, this->m_lowererMD.GetDefaultIndirScale(), TyMachReg, this->m_func);
InsertMove(opndDst, opndIndir, insertBeforeInstr);
}
else
{
// dst = MOV [s1 + s2 * 4]
opndIndir = IR::IndirOpnd::New(opndObjSlots, opndSlotIndex, this->m_lowererMD.GetDefaultIndirScale(), TyMachReg, this->m_func);
InsertMove(opndDst, opndIndir, insertBeforeInstr);
}
// JMP $fallthru
InsertBranch(Js::OpCode::Br, labelFallThru, insertBeforeInstr);
}
void
Lowerer::LowerSpreadArrayLiteral(IR::Instr *instr)
{
LoadScriptContext(instr);
IR::Opnd *src2Opnd = instr->UnlinkSrc2();
m_lowererMD.LoadHelperArgument(instr, src2Opnd);
IR::Opnd *src1Opnd = instr->UnlinkSrc1();
m_lowererMD.LoadHelperArgument(instr, src1Opnd);
this->m_lowererMD.ChangeToHelperCall(instr, IR::HelperSpreadArrayLiteral);
}
IR::Instr *
Lowerer::LowerSpreadCall(IR::Instr *instr, Js::CallFlags callFlags, bool setupProfiledVersion)
{
// Get the target function object, and emit function object test.
IR::RegOpnd * functionObjOpnd = instr->UnlinkSrc1()->AsRegOpnd();
functionObjOpnd->m_isCallArg = true;
if (!(callFlags & Js::CallFlags_New) && !setupProfiledVersion)
{
IR::LabelInstr* continueAfterExLabel = InsertContinueAfterExceptionLabelForDebugger(m_func, instr, false);
this->m_lowererMD.GenerateFunctionObjectTest(instr, functionObjOpnd, false, continueAfterExLabel);
}
IR::Instr *spreadIndicesInstr;
spreadIndicesInstr = GetLdSpreadIndicesInstr(instr);
Assert(spreadIndicesInstr->m_opcode == Js::OpCode::LdSpreadIndices);
// Get AuxArray
IR::Opnd *spreadIndicesOpnd = spreadIndicesInstr->UnlinkSrc1();
// Remove LdSpreadIndices from the argument chain
instr->ReplaceSrc2(spreadIndicesInstr->UnlinkSrc2());
// Emit the normal args
if (!(callFlags & Js::CallFlags_New))
{
callFlags = (Js::CallFlags)(callFlags | (instr->GetDst() ? Js::CallFlags_Value : Js::CallFlags_NotUsed));
}
// Profiled helper call requires three more parameters, ArrayProfileId, profileId, and the frame pointer.
// This is just following the convention of HelperProfiledNewScObjArray call.
const unsigned short extraArgsCount = setupProfiledVersion ? 5 : 2; // function object and AuxArray
int32 argCount = this->m_lowererMD.LowerCallArgs(instr, (ushort)callFlags, extraArgsCount);
// Emit our extra (first) args for the Spread helper in reverse order
if (setupProfiledVersion)
{
IR::JitProfilingInstr* jitInstr = (IR::JitProfilingInstr*)instr;
m_lowererMD.LoadHelperArgument(instr, IR::Opnd::CreateProfileIdOpnd(jitInstr->arrayProfileId, m_func));
m_lowererMD.LoadHelperArgument(instr, IR::Opnd::CreateProfileIdOpnd(jitInstr->profileId, m_func));
m_lowererMD.LoadHelperArgument(instr, IR::Opnd::CreateFramePointerOpnd(m_func));
}
m_lowererMD.LoadHelperArgument(instr, functionObjOpnd);
m_lowererMD.LoadHelperArgument(instr, spreadIndicesOpnd);
// Change the call target to our helper
IR::HelperCallOpnd *helperOpnd = IR::HelperCallOpnd::New(setupProfiledVersion ? IR::HelperProfiledNewScObjArraySpread : IR::HelperSpreadCall, this->m_func);
instr->SetSrc1(helperOpnd);
return this->m_lowererMD.LowerCall(instr, (Js::ArgSlot)argCount);
}
void
Lowerer::LowerDivI4Common(IR::Instr * instr)
{
Assert(instr);
Assert((instr->m_opcode == Js::OpCode::Rem_I4 || instr->m_opcode == Js::OpCode::Div_I4) ||
(instr->m_opcode == Js::OpCode::RemU_I4 || instr->m_opcode == Js::OpCode::DivU_I4));
Assert(m_func->GetJITFunctionBody()->IsAsmJsMode());
const bool isRem = instr->m_opcode == Js::OpCode::Rem_I4 || instr->m_opcode == Js::OpCode::RemU_I4;
// MIN_INT/-1 path is only needed for signed operations
// TEST src2, src2
// JEQ $div0
// CMP src1, MIN_INT
// JEQ $minInt
// JMP $div
// $div0: [helper]
// MOV dst, 0
// JMP $done
// $minInt: [helper]
// CMP src2, -1
// JNE $div
// dst = MOV src1 / 0
// JMP $done
// $div:
// dst = IDIV src2, src1
// $done:
IR::LabelInstr * div0Label = InsertLabel(true, instr);
IR::LabelInstr * divLabel = InsertLabel(false, instr);
IR::LabelInstr * doneLabel = InsertLabel(false, instr->m_next);
IR::Opnd * dst = instr->GetDst();
IR::Opnd * src1 = instr->GetSrc1();
IR::Opnd * src2 = instr->GetSrc2();
bool isWasm = m_func->GetJITFunctionBody()->IsWasmFunction();
Assert(!isWasm || isRem);
if (!isWasm)
{
InsertTestBranch(src2, src2, Js::OpCode::BrEq_A, div0Label, div0Label);
InsertMove(dst, IR::IntConstOpnd::NewFromType(0, dst->GetType(), m_func), divLabel);
InsertBranch(Js::OpCode::Br, doneLabel, divLabel);
}
if (instr->GetSrc1()->IsSigned())
{
IR::LabelInstr * minIntLabel = nullptr;
// we need to check for INT_MIN/-1 if divisor is either -1 or variable, and dividend is either INT_MIN or variable
int64 intMin = IRType_IsInt64(src1->GetType()) ? LONGLONG_MIN : INT_MIN;
bool needsMinOverNeg1Check = !(src2->IsImmediateOpnd() && src2->GetImmediateValue(m_func) != -1);
if (src1->IsImmediateOpnd())
{
if (needsMinOverNeg1Check && src1->GetImmediateValue(m_func) == intMin)
{
minIntLabel = InsertLabel(true, divLabel);
InsertBranch(Js::OpCode::Br, minIntLabel, div0Label);
}
else
{
needsMinOverNeg1Check = false;
}
}
else if(needsMinOverNeg1Check)
{
minIntLabel = InsertLabel(true, divLabel);
InsertCompareBranch(src1, IR::IntConstOpnd::NewFromType(intMin, src1->GetType(), m_func), Js::OpCode::BrEq_A, minIntLabel, div0Label);
}
if (needsMinOverNeg1Check)
{
Assert(minIntLabel);
Assert(!src2->IsImmediateOpnd() || src2->GetImmediateValue(m_func) == -1);
if (!src2->IsImmediateOpnd())
{
InsertCompareBranch(src2, IR::IntConstOpnd::NewFromType(-1, src2->GetType(), m_func), Js::OpCode::BrNeq_A, divLabel, divLabel);
}
InsertMove(dst, !isRem ? src1 : IR::IntConstOpnd::NewFromType(0, dst->GetType(), m_func), divLabel);
InsertBranch(Js::OpCode::Br, doneLabel, divLabel);
}
}
InsertBranch(Js::OpCode::Br, divLabel, div0Label);
m_lowererMD.EmitInt4Instr(instr);
}
void
Lowerer::LowerRemI4(IR::Instr * instr)
{
Assert(instr);
Assert(instr->m_opcode == Js::OpCode::Rem_I4 || instr->m_opcode == Js::OpCode::RemU_I4);
//Generate fast path for const divisors
if (m_lowererMD.GenerateFastDivAndRem(instr))
{
return;
}
if (m_func->GetJITFunctionBody()->IsAsmJsMode())
{
LowerDivI4Common(instr);
}
else
{
m_lowererMD.EmitInt4Instr(instr);
}
}
void
Lowerer::LowerTrapIfZero(IR::Instr * const instr)
{
Assert(instr);
Assert(instr->m_opcode == Js::OpCode::TrapIfZero);
Assert(instr->GetSrc1());
Assert(m_func->GetJITFunctionBody()->IsWasmFunction());
IR::Opnd * src1 = instr->GetSrc1();
if (src1->IsImmediateOpnd())
{
if (src1->GetImmediateValue(m_func) == 0)
{
GenerateThrow(IR::IntConstOpnd::NewFromType(SCODE_CODE(WASMERR_DivideByZero), TyInt32, m_func), instr);
}
}
else
{
IR::LabelInstr * doneLabel = InsertLabel(false, instr->m_next);
InsertCompareBranch(src1, IR::IntConstOpnd::NewFromType(0, src1->GetType(), m_func), Js::OpCode::BrNeq_A, doneLabel, doneLabel);
InsertLabel(true, doneLabel);
GenerateThrow(IR::IntConstOpnd::NewFromType(SCODE_CODE(WASMERR_DivideByZero), TyInt32, m_func), doneLabel);
}
LowererMD::ChangeToAssign(instr);
}
IR::Instr*
Lowerer::LowerTrapIfUnalignedAccess(IR::Instr * const instr)
{
IR::Opnd* dst = instr->UnlinkDst();
IR::Opnd* src1 = instr->UnlinkSrc1();
IR::Opnd* src2 = instr->GetSrc2();
Assert(instr);
Assert(instr->m_opcode == Js::OpCode::TrapIfUnalignedAccess);
Assert(src1 && !src1->IsVar());
Assert(src2 && src2->IsImmediateOpnd());
Assert(src2->GetSize() > 1);
uint32 mask = src2->GetSize() - 1;
uint32 cmpValue = (uint32)src2->GetImmediateValue(m_func);
InsertMove(dst, src1, instr);
IR::IntConstOpnd* maskOpnd = IR::IntConstOpnd::New(mask, src1->GetType(), m_func);
IR::RegOpnd* maskedOpnd = IR::RegOpnd::New(src1->GetType(), m_func);
IR::Instr* maskInstr = IR::Instr::New(Js::OpCode::And_I4, maskedOpnd, src1, maskOpnd, m_func);
instr->InsertBefore(maskInstr);
IR::IntConstOpnd* cmpOpnd = IR::IntConstOpnd::New(cmpValue, maskedOpnd->GetType(), m_func, true);
IR::LabelInstr* alignedLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func);
IR::Instr* branch = IR::BranchInstr::New(Js::OpCode::BrEq_I4, alignedLabel, maskedOpnd, cmpOpnd, m_func);
instr->InsertBefore(branch);
InsertLabel(true, instr);
GenerateThrow(IR::IntConstOpnd::NewFromType(SCODE_CODE(WASMERR_UnalignedAtomicAccess), TyInt32, m_func), instr);
instr->InsertBefore(alignedLabel);
instr->Remove();
// The check and branch are not fully lowered yet, let them go in the lower loop.
return branch;
}
void
Lowerer::LowerTrapIfMinIntOverNegOne(IR::Instr * const instr)
{
Assert(instr);
Assert(instr->m_opcode == Js::OpCode::TrapIfMinIntOverNegOne);
Assert(instr->GetSrc1());
Assert(instr->GetSrc2());
Assert(m_func->GetJITFunctionBody()->IsWasmFunction());
IR::LabelInstr * doneLabel = InsertLabel(false, instr->m_next);
IR::Opnd * src1 = instr->GetSrc1();
IR::Opnd * src2 = instr->UnlinkSrc2();
int64 intMin = src1->IsInt64() ? LONGLONG_MIN : INT_MIN;
if (src1->IsImmediateOpnd())
{
if (src1->GetImmediateValue(m_func) != intMin)
{
// Const value not min int, will not trap
doneLabel->Remove();
src2->Free(m_func);
LowererMD::ChangeToAssign(instr);
return;
}
// Is min int no need to do check
}
else
{
InsertCompareBranch(src1, IR::IntConstOpnd::NewFromType(intMin, src1->GetType(), m_func), Js::OpCode::BrNeq_A, doneLabel, doneLabel);
}
if (src2->IsImmediateOpnd())
{
if (src2->GetImmediateValue(m_func) != -1)
{
// Const value not min int, will not trap
doneLabel->Remove();
src2->Free(m_func);
LowererMD::ChangeToAssign(instr);
return;
}
// Is -1 no need to do check
src2->Free(m_func);
}
else
{
InsertCompareBranch(src2, IR::IntConstOpnd::NewFromType(-1, src2->GetType(), m_func), Js::OpCode::BrNeq_A, doneLabel, doneLabel);
}
InsertLabel(true, doneLabel);
GenerateThrow(IR::IntConstOpnd::NewFromType(SCODE_CODE(VBSERR_Overflow), TyInt32, m_func), doneLabel);
LowererMD::ChangeToAssign(instr);
}
void
Lowerer::GenerateThrow(IR::Opnd* errorCode, IR::Instr * instr)
{
IR::Instr *throwInstr = IR::Instr::New(Js::OpCode::RuntimeTypeError, IR::RegOpnd::New(TyMachReg, m_func), errorCode, m_func);
instr->InsertBefore(throwInstr);
const bool isWasm = m_func->GetJITFunctionBody() && m_func->GetJITFunctionBody()->IsWasmFunction();
LowerUnaryHelperMem(throwInstr, isWasm ? IR::HelperOp_WebAssemblyRuntimeError : IR::HelperOp_RuntimeTypeError);
}
void
Lowerer::LowerDivI4(IR::Instr * instr)
{
Assert(instr);
Assert(instr->m_opcode == Js::OpCode::Div_I4 || instr->m_opcode == Js::OpCode::DivU_I4);
#ifdef _M_IX86
if (
instr->GetDst() && instr->GetDst()->IsInt64() ||
instr->GetSrc1() && instr->GetSrc1()->IsInt64() ||
instr->GetSrc2() && instr->GetSrc2()->IsInt64()
)
{
m_lowererMD.EmitInt64Instr(instr);
return;
}
#endif
Assert(instr->GetSrc2());
if (m_func->GetJITFunctionBody()->IsWasmFunction())
{
if (!m_lowererMD.GenerateFastDivAndRem(instr))
{
m_lowererMD.EmitInt4Instr(instr);
}
return;
}
if (m_func->GetJITFunctionBody()->IsAsmJsMode())
{
if (!m_lowererMD.GenerateFastDivAndRem(instr))
{
LowerDivI4Common(instr);
}
return;
}
if(!instr->HasBailOutInfo())
{
if (!m_lowererMD.GenerateFastDivAndRem(instr))
{
m_lowererMD.EmitInt4Instr(instr);
}
return;
}
Assert(!(instr->GetBailOutKind() & ~(IR::BailOnDivResultNotInt | IR::BailOutOnNegativeZero | IR::BailOutOnDivByZero | IR::BailOutOnDivOfMinInt)));
IR::BailOutKind bailOutKind = instr->GetBailOutKind();
// Split out and generate the bailout instruction
const auto nonBailOutInstr = IR::Instr::New(instr->m_opcode, instr->m_func);
instr->TransferTo(nonBailOutInstr);
instr->InsertBefore(nonBailOutInstr);
IR::LabelInstr * doneLabel = IR::LabelInstr::New(Js::OpCode::Label, instr->m_func);
instr->InsertAfter(doneLabel);
// Generate the bailout helper call. 'instr' will be changed to the CALL into the bailout function, so it can't be used for
// ordering instructions anymore.
IR::LabelInstr * bailOutLabel = GenerateBailOut(instr);
IR::Opnd * denominatorOpnd = nonBailOutInstr->GetSrc2();
IR::Opnd * nominatorOpnd = nonBailOutInstr->GetSrc1();
bool isFastDiv = false;
if (bailOutKind & IR::BailOutOnDivOfMinInt)
{
// Bailout if numerator is MIN_INT (could also check for denominator being -1
// before bailing out, but does not seem worth the extra code..)
InsertCompareBranch(nominatorOpnd, IR::IntConstOpnd::New(INT32_MIN, TyInt32, this->m_func, true), Js::OpCode::BrEq_A, bailOutLabel, nonBailOutInstr);
}
if (denominatorOpnd->IsIntConstOpnd() && Math::IsPow2(denominatorOpnd->AsIntConstOpnd()->AsInt32()))
{
Assert((bailOutKind & (IR::BailOutOnNegativeZero | IR::BailOutOnDivByZero)) == 0);
if (Math::IsPow2(denominatorOpnd->AsIntConstOpnd()->AsInt32()))
{
int pow2 = denominatorOpnd->AsIntConstOpnd()->AsInt32();
InsertTestBranch(nominatorOpnd, IR::IntConstOpnd::New(pow2 - 1, TyInt32, this->m_func),
Js::OpCode::BrNeq_A, bailOutLabel, nonBailOutInstr);
nonBailOutInstr->m_opcode = Js::OpCode::Shr_A;
nonBailOutInstr->ReplaceSrc2(IR::IntConstOpnd::New(Math::Log2(pow2), TyInt32, this->m_func));
LowererMD::ChangeToShift(nonBailOutInstr, false);
LowererMD::Legalize(nonBailOutInstr);
isFastDiv = true;
}
else
{
isFastDiv = m_lowererMD.GenerateFastDivAndRem(nonBailOutInstr, bailOutLabel);
}
}
if (!isFastDiv)
{
if (bailOutKind & IR::BailOutOnDivByZero)
{
// Bailout if denominator is 0
InsertTestBranch(denominatorOpnd, denominatorOpnd, Js::OpCode::BrEq_A, bailOutLabel, nonBailOutInstr);
}
// Lower the div and bailout if there is a reminder (machine specific)
IR::Instr * insertBeforeInstr = m_lowererMD.LowerDivI4AndBailOnReminder(nonBailOutInstr, bailOutLabel);
IR::Opnd * resultOpnd = nonBailOutInstr->GetDst();
if (bailOutKind & IR::BailOutOnNegativeZero)
{
// TEST result, result
// JNE skipNegDenominatorCheckLabel // Result not 0
// TEST denominator, denominator
// JNSB/BMI bailout // bail if negative
// skipNegDenominatorCheckLabel:
IR::LabelInstr * skipNegDenominatorCheckLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func);
// Skip negative denominator check if the result is not 0
InsertTestBranch(resultOpnd, resultOpnd, Js::OpCode::BrNeq_A, skipNegDenominatorCheckLabel, insertBeforeInstr);
IR::LabelInstr * negDenominatorCheckLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func, true);
insertBeforeInstr->InsertBefore(negDenominatorCheckLabel);
// Jump to done if the denominator is not negative
InsertTestBranch(denominatorOpnd, denominatorOpnd,
LowererMD::MDCompareWithZeroBranchOpcode(Js::OpCode::BrLt_A), bailOutLabel, insertBeforeInstr);
insertBeforeInstr->InsertBefore(skipNegDenominatorCheckLabel);
}
}
// We are all fine, jump around the bailout to done
InsertBranch(Js::OpCode::Br, doneLabel, bailOutLabel);
}
void
Lowerer::LowerRemR8(IR::Instr * instr)
{
Assert(instr);
Assert(instr->m_opcode == Js::OpCode::Rem_A);
Assert(m_func->GetJITFunctionBody()->IsAsmJsMode());
m_lowererMD.LoadDoubleHelperArgument(instr, instr->UnlinkSrc2());
m_lowererMD.LoadDoubleHelperArgument(instr, instr->UnlinkSrc1());
instr->SetSrc1(IR::HelperCallOpnd::New(IR::JnHelperMethod::HelperOp_Rem_Double, m_func));
m_lowererMD.LowerCall(instr, 0);
}
void
Lowerer::LowerNewScopeSlots(IR::Instr * instr, bool doStackSlots)
{
Func * func = m_func;
if (PHASE_OFF(Js::NewScopeSlotFastPathPhase, func))
{
this->LowerUnaryHelperMemWithFunctionInfo(instr, IR::HelperOP_NewScopeSlots);
return;
}
uint const count = instr->GetSrc1()->AsIntConstOpnd()->AsUint32();
uint const allocSize = count * sizeof(Js::Var);
uint const actualSlotCount = count - Js::ScopeSlots::FirstSlotIndex;
IR::RegOpnd * dst = instr->UnlinkDst()->AsRegOpnd();
// dst = RecyclerAlloc(allocSize)
// dst[EncodedSlotCountSlotIndex] = min(actualSlotCount, MaxEncodedSlotCount);
// dst[ScopeMetadataSlotIndex] = FunctionBody;
// mov undefinedOpnd, undefined
// dst[FirstSlotIndex..count] = undefinedOpnd;
// Note: stack allocation of both scope slots and frame display are done together
// in lowering of NewStackFrameDisplay
if (!doStackSlots)
{
GenerateRecyclerAlloc(IR::HelperAllocMemForVarArray, allocSize, dst, instr);
}
m_lowererMD.GenerateMemInit(dst, Js::ScopeSlots::EncodedSlotCountSlotIndex * sizeof(Js::Var),
(size_t)min<uint>(actualSlotCount, Js::ScopeSlots::MaxEncodedSlotCount), instr, !doStackSlots);
IR::Opnd * functionInfoOpnd = this->LoadFunctionInfoOpnd(instr);
GenerateMemInit(dst, Js::ScopeSlots::ScopeMetadataSlotIndex * sizeof(Js::Var),
functionInfoOpnd, instr, !doStackSlots);
IR::Opnd * undefinedOpnd = this->LoadLibraryValueOpnd(instr, LibraryValue::ValueUndefined);
const IR::AutoReuseOpnd autoReuseUndefinedOpnd(undefinedOpnd, func);
// avoid using a register for the undefined pointer if we are going to assign 1 or 2
if (actualSlotCount > 2)
{
undefinedOpnd = GetRegOpnd(undefinedOpnd, instr, func, TyVar);
}
int const loopUnrollCount = 8;
if (actualSlotCount <= loopUnrollCount * 2)
{
// Just generate all the assignment in straight line code
// mov[dst + Js::FirstSlotIndex], undefinedOpnd
// ...
// mov[dst + count - 1], undefinedOpnd
for (unsigned int i = Js::ScopeSlots::FirstSlotIndex; i < count; i++)
{
GenerateMemInit(dst, sizeof(Js::Var) * i, undefinedOpnd, instr, !doStackSlots);
}
}
else
{
// Just generate all the assignment in loop of loopUnrollCount and the rest as straight line code
//
// lea currOpnd, [dst + sizeof(Var) * (loopAssignCount + Js::ScopeSlots::FirstSlotIndex - loopUnrollCount)];
// mov [currOpnd + loopUnrollCount + leftOverAssignCount - 1] , undefinedOpnd
// mov [currOpnd + loopUnrollCount + leftOverAssignCount - 2] , undefinedOpnd
// ...
// mov [currOpnd + loopUnrollCount], undefinedOpnd
// $LoopTop:
// mov [currOpnd + loopUnrollCount - 1], undefinedOpnd
// mov [currOpnd + loopUnrollCount - 2], undefinedOpnd
// ...
// mov [currOpnd], undefinedOpnd
// lea currOpnd, [currOpnd - loopUnrollCount]
// cmp dst, currOpnd
// jlt $Looptop
uint nLoop = actualSlotCount / loopUnrollCount;
uint loopAssignCount = nLoop * loopUnrollCount;
uint leftOverAssignCount = actualSlotCount - loopAssignCount; // The left over assignments
IR::RegOpnd * currOpnd = IR::RegOpnd::New(TyMachPtr, func);
const IR::AutoReuseOpnd autoReuseCurrOpnd(currOpnd, m_func);
InsertLea(
currOpnd,
IR::IndirOpnd::New(
dst,
sizeof(Js::Var) * (loopAssignCount + Js::ScopeSlots::FirstSlotIndex - loopUnrollCount),
TyMachPtr,
func),
instr);
for (unsigned int i = 0; i < leftOverAssignCount; i++)
{
GenerateMemInit(currOpnd, sizeof(Js::Var) * (loopUnrollCount + leftOverAssignCount - i - 1), undefinedOpnd, instr, !doStackSlots);
}
IR::LabelInstr * loopTop = InsertLoopTopLabel(instr);
Loop * loop = loopTop->GetLoop();
for (unsigned int i = 0; i < loopUnrollCount; i++)
{
GenerateMemInit(currOpnd, sizeof(Js::Var) * (loopUnrollCount - i - 1), undefinedOpnd, instr, !doStackSlots);
}
InsertLea(currOpnd, IR::IndirOpnd::New(currOpnd, -((int)sizeof(Js::Var) * loopUnrollCount), TyMachPtr, func), instr);
InsertCompareBranch(dst, currOpnd, Js::OpCode::BrLt_A, true, loopTop, instr);
loop->regAlloc.liveOnBackEdgeSyms->Set(currOpnd->m_sym->m_id);
loop->regAlloc.liveOnBackEdgeSyms->Set(dst->m_sym->m_id);
loop->regAlloc.liveOnBackEdgeSyms->Set(undefinedOpnd->AsRegOpnd()->m_sym->m_id);
}
if (!doStackSlots)
{
InsertMove(IR::RegOpnd::New(instr->m_func->GetLocalClosureSym(), TyMachPtr, func), dst, instr);
}
instr->Remove();
}
void Lowerer::LowerLdInnerFrameDisplay(IR::Instr *instr)
{
bool isStrict = instr->m_func->GetJITFunctionBody()->IsStrictMode();
if (isStrict)
{
if (instr->GetSrc2())
{
this->LowerBinaryHelperMem(instr, IR::HelperScrObj_LdStrictInnerFrameDisplay);
}
else
{
#if DBG
instr->m_opcode = Js::OpCode::LdInnerFrameDisplayNoParent;
#endif
this->LowerUnaryHelperMem(instr, IR::HelperScrObj_LdStrictInnerFrameDisplayNoParent);
}
}
else
{
if (instr->GetSrc2())
{
this->LowerBinaryHelperMem(instr, IR::HelperScrObj_LdInnerFrameDisplay);
}
else
{
#if DBG
instr->m_opcode = Js::OpCode::LdInnerFrameDisplayNoParent;
#endif
this->LowerUnaryHelperMem(instr, IR::HelperScrObj_LdInnerFrameDisplayNoParent);
}
}
}
void Lowerer::LowerLdFrameDisplay(IR::Instr *instr, bool doStackFrameDisplay)
{
bool isStrict = instr->m_func->GetJITFunctionBody()->IsStrictMode();
uint16 envDepth = instr->m_func->GetJITFunctionBody()->GetEnvDepth();
Func *func = this->m_func;
// envDepth of -1 indicates unknown depth (eval expression or HTML event handler).
// We could still fast-path these by generating a loop over the (dynamically loaded) scope chain length,
// but I doubt it's worth it.
// If the dst opnd is a byte code temp, that indicates we're prepending a block scope or some such and
// shouldn't attempt to do this.
if (envDepth == (uint16)-1 ||
(!doStackFrameDisplay && (instr->isNonFastPathFrameDisplay || instr->GetDst()->AsRegOpnd()->m_sym->IsTempReg(instr->m_func))) ||
PHASE_OFF(Js::FrameDisplayFastPathPhase, func))
{
if (isStrict)
{
if (instr->GetSrc2())
{
this->LowerBinaryHelperMem(instr, IR::HelperScrObj_LdStrictFrameDisplay);
}
else
{
#if DBG
instr->m_opcode = Js::OpCode::LdFrameDisplayNoParent;
#endif
this->LowerUnaryHelperMem(instr, IR::HelperScrObj_LdStrictFrameDisplayNoParent);
}
}
else
{
if (instr->GetSrc2())
{
this->LowerBinaryHelperMem(instr, IR::HelperScrObj_LdFrameDisplay);
}
else
{
#if DBG
instr->m_opcode = Js::OpCode::LdFrameDisplayNoParent;
#endif
this->LowerUnaryHelperMem(instr, IR::HelperScrObj_LdFrameDisplayNoParent);
}
}
return;
}
uint16 frameDispLength = envDepth + 1;
Assert(frameDispLength > 0);
IR::RegOpnd *dstOpnd = instr->UnlinkDst()->AsRegOpnd();
IR::RegOpnd *currentFrameOpnd = instr->UnlinkSrc1()->AsRegOpnd();
uint allocSize = sizeof(Js::FrameDisplay) + (frameDispLength * sizeof(Js::Var));
if (doStackFrameDisplay)
{
IR::Instr *insertInstr = func->GetFunctionEntryInsertionPoint();
// Initialize stack pointers for scope slots and frame display together at the top of the function
// (in case we bail out before executing the instructions).
IR::LabelInstr *labelNoStackFunc = IR::LabelInstr::New(Js::OpCode::Label, m_func, true);
IR::LabelInstr *labelDone = IR::LabelInstr::New(Js::OpCode::Label, m_func);
// Check whether stack functions have been disabled since we jitted.
// If they have, then we must allocate closure memory on the heap.
InsertTestBranch(IR::MemRefOpnd::New(m_func->GetJITFunctionBody()->GetFlagsAddr(), TyInt8, m_func),
IR::IntConstOpnd::New(Js::FunctionBody::Flags_StackNestedFunc, TyInt8, m_func, true),
Js::OpCode::BrEq_A, labelNoStackFunc, insertInstr);
// allocSize is greater than TyMachPtr and hence changing the initial size to TyMisc
StackSym * stackSym = StackSym::New(TyMisc, instr->m_func);
m_func->StackAllocate(stackSym, allocSize);
InsertLea(dstOpnd, IR::SymOpnd::New(stackSym, TyMachPtr, func), insertInstr);
uint scopeSlotAllocSize =
(m_func->GetJITFunctionBody()->GetScopeSlotArraySize() + Js::ScopeSlots::FirstSlotIndex) * sizeof(Js::Var);
stackSym = StackSym::New(TyMisc, instr->m_func);
m_func->StackAllocate(stackSym, scopeSlotAllocSize);
InsertLea(currentFrameOpnd, IR::SymOpnd::New(stackSym, TyMachPtr, func), insertInstr);
InsertBranch(Js::OpCode::Br, labelDone, insertInstr);
insertInstr->InsertBefore(labelNoStackFunc);
GenerateRecyclerAlloc(IR::HelperAllocMemForFrameDisplay, allocSize, dstOpnd, insertInstr, true);
GenerateRecyclerAlloc(IR::HelperAllocMemForVarArray, scopeSlotAllocSize, currentFrameOpnd, insertInstr, true);
insertInstr->InsertBefore(labelDone);
InsertMove(IR::SymOpnd::New(m_func->GetLocalFrameDisplaySym(), 0, TyMachReg, m_func), dstOpnd, insertInstr);
InsertMove(IR::SymOpnd::New(m_func->GetLocalClosureSym(), 0, TyMachReg, m_func), currentFrameOpnd, insertInstr);
}
else
{
GenerateRecyclerAlloc(IR::HelperAllocMemForFrameDisplay, allocSize, dstOpnd, instr);
}
// Copy contents of environment
// Work back to front to leave the head element(s) in cache
if (envDepth > 0)
{
IR::RegOpnd *envOpnd = instr->UnlinkSrc2()->AsRegOpnd();
for (uint16 i = envDepth; i >= 1; i--)
{
IR::Opnd *scopeOpnd = IR::RegOpnd::New(TyMachReg, func);
IR::Opnd *envLoadOpnd =
IR::IndirOpnd::New(envOpnd, Js::FrameDisplay::GetOffsetOfScopes() + ((i - 1) * sizeof(Js::Var)), TyMachReg, func);
InsertMove(scopeOpnd, envLoadOpnd, instr);
IR::Opnd *dstStoreOpnd =
IR::IndirOpnd::New(dstOpnd, Js::FrameDisplay::GetOffsetOfScopes() + (i * sizeof(Js::Var)), TyMachReg, func);
InsertMove(dstStoreOpnd, scopeOpnd, instr);
}
}
// Assign current element.
InsertMove(
IR::IndirOpnd::New(dstOpnd, Js::FrameDisplay::GetOffsetOfScopes(), TyMachReg, func),
currentFrameOpnd,
instr);
// Combine tag, strict mode flag, and length
uintptr_t bits = 1 |
(isStrict << (Js::FrameDisplay::GetOffsetOfStrictMode() * 8)) |
(frameDispLength << (Js::FrameDisplay::GetOffsetOfLength() * 8));
InsertMove(
IR::IndirOpnd::New(dstOpnd, 0, TyMachReg, func),
IR::IntConstOpnd::New(bits, TyMachReg, func, true),
instr);
instr->Remove();
}
IR::AddrOpnd *Lowerer::CreateFunctionBodyOpnd(Func *const func) const
{
return IR::AddrOpnd::New(func->GetJITFunctionBody()->GetAddr(), IR::AddrOpndKindDynamicFunctionBody, m_func, true);
}
IR::AddrOpnd *Lowerer::CreateFunctionBodyOpnd(Js::FunctionBody *const functionBody) const
{
// TODO: OOP JIT, CreateFunctionBodyOpnd
Assert(!m_func->IsOOPJIT());
return IR::AddrOpnd::New(functionBody, IR::AddrOpndKindDynamicFunctionBody, m_func, true);
}
bool
Lowerer::GenerateRecyclerOrMarkTempAlloc(IR::Instr * instr, IR::RegOpnd * dstOpnd, IR::JnHelperMethod allocHelper, size_t allocSize, IR::SymOpnd ** tempObjectSymOpnd)
{
if (instr->dstIsTempObject)
{
*tempObjectSymOpnd = GenerateMarkTempAlloc(dstOpnd, allocSize, instr);
return false;
}
this->GenerateRecyclerAlloc(allocHelper, allocSize, dstOpnd, instr);
*tempObjectSymOpnd = nullptr;
return true;
}
IR::SymOpnd *
Lowerer::GenerateMarkTempAlloc(IR::RegOpnd *const dstOpnd, const size_t allocSize, IR::Instr *const insertBeforeInstr)
{
Assert(dstOpnd);
Assert(allocSize != 0);
Assert(insertBeforeInstr);
Func *const func = insertBeforeInstr->m_func;
// Allocate stack space for the reg exp instance, and a slot for the boxed value
StackSym *const tempObjectSym = StackSym::New(TyMisc, func);
m_func->StackAllocate(tempObjectSym, (int)(allocSize + sizeof(void *)));
IR::SymOpnd * tempObjectOpnd = IR::SymOpnd::New(tempObjectSym, sizeof(void *), TyVar, func);
InsertLea(dstOpnd, tempObjectOpnd, insertBeforeInstr);
// Initialize the boxed instance slot
if (this->outerMostLoopLabel == nullptr)
{
GenerateMemInit(dstOpnd, -(int)sizeof(void *), IR::AddrOpnd::NewNull(func), insertBeforeInstr, false);
}
else if (!PHASE_OFF(Js::HoistMarkTempInitPhase, this->m_func))
{
InsertMove(IR::SymOpnd::New(tempObjectSym, TyMachPtr, func), IR::AddrOpnd::NewNull(func), this->outerMostLoopLabel, false);
}
return tempObjectOpnd;
}
void Lowerer::LowerBrFncCachedScopeEq(IR::Instr *instr)
{
Assert(instr->m_opcode == Js::OpCode::BrFncCachedScopeEq || instr->m_opcode == Js::OpCode::BrFncCachedScopeNeq);
Js::OpCode opcode = (instr->m_opcode == Js::OpCode::BrFncCachedScopeEq ? Js::OpCode::BrEq_A : Js::OpCode::BrNeq_A);
IR::RegOpnd *src1Reg = instr->UnlinkSrc1()->AsRegOpnd();
IR::IndirOpnd *indirOpnd = IR::IndirOpnd::New(src1Reg, Js::ScriptFunction::GetOffsetOfCachedScopeObj(), TyMachReg, this->m_func);
this->InsertCompareBranch(indirOpnd, instr->UnlinkSrc2(), opcode, false, instr->AsBranchInstr()->GetTarget(), instr->m_next);
instr->Remove();
}
IR::Instr* Lowerer::InsertLoweredRegionStartMarker(IR::Instr* instrToInsertBefore)
{
AssertMsg(instrToInsertBefore->m_prev != nullptr, "Can't insert lowered region start marker as the first instr in the func.");
IR::LabelInstr* startMarkerLabel = IR::LabelInstr::New(Js::OpCode::Label, instrToInsertBefore->m_func);
instrToInsertBefore->InsertBefore(startMarkerLabel);
return startMarkerLabel;
}
IR::Instr* Lowerer::RemoveLoweredRegionStartMarker(IR::Instr* startMarkerInstr)
{
AssertMsg(startMarkerInstr->m_prev != nullptr, "Lowered region start marker became the first instruction in the func after lowering?");
IR::Instr* prevInstr = startMarkerInstr->m_prev;
startMarkerInstr->Remove();
return prevInstr;
}
IR::Instr* Lowerer::GetLdSpreadIndicesInstr(IR::Instr *instr)
{
IR::Opnd *src2 = instr->GetSrc2();
if (!src2->IsSymOpnd())
{
return nullptr;
}
IR::SymOpnd * argLinkOpnd = src2->AsSymOpnd();
StackSym * argLinkSym = argLinkOpnd->m_sym->AsStackSym();
Assert(argLinkSym->IsSingleDef());
return argLinkSym->m_instrDef;
}
bool Lowerer::IsSpreadCall(IR::Instr *instr)
{
IR::Instr *lastInstr = GetLdSpreadIndicesInstr(instr);
return lastInstr && lastInstr->m_opcode == Js::OpCode::LdSpreadIndices;
}
// When under debugger, generate a new label to be used as safe place to jump after ignore exception,
// insert it after insertAfterInstr, and return the label inserted.
// Returns nullptr/NoOP for non-debugger code path.
//static
IR::LabelInstr* Lowerer::InsertContinueAfterExceptionLabelForDebugger(Func* func, IR::Instr* insertAfterInstr, bool isHelper)
{
Assert(func);
Assert(insertAfterInstr);
IR::LabelInstr* continueAfterExLabel = nullptr;
if (func->IsJitInDebugMode())
{
continueAfterExLabel = IR::LabelInstr::New(Js::OpCode::Label, func, isHelper);
insertAfterInstr->InsertAfter(continueAfterExLabel);
}
return continueAfterExLabel;
}
void Lowerer::GenerateSingleCharStrJumpTableLookup(IR::Instr * instr)
{
IR::MultiBranchInstr * multiBrInstr = instr->AsBranchInstr()->AsMultiBrInstr();
Func * func = instr->m_func;
IR::LabelInstr * helperLabel = IR::LabelInstr::New(Js::OpCode::Label, func, true);
IR::LabelInstr * continueLabel = IR::LabelInstr::New(Js::OpCode::Label, func);
// MOV strLengthOpnd, str->length
IR::RegOpnd * strLengthOpnd = IR::RegOpnd::New(TyUint32, func);
InsertMove(strLengthOpnd, IR::IndirOpnd::New(instr->GetSrc1()->AsRegOpnd(), Js::JavascriptString::GetOffsetOfcharLength(), TyUint32, func), instr);
// CMP strLengthOpnd, 1
// JNE defaultLabel
IR::LabelInstr * defaultLabelInstr = (IR::LabelInstr *)multiBrInstr->GetBranchJumpTable()->defaultTarget;
InsertCompareBranch(strLengthOpnd, IR::IntConstOpnd::New(1, TyUint32, func), Js::OpCode::BrNeq_A, defaultLabelInstr, instr);
// MOV strBuffer, str->psz
IR::RegOpnd * strBufferOpnd = IR::RegOpnd::New(TyMachPtr, func);
InsertMove(strBufferOpnd, IR::IndirOpnd::New(instr->GetSrc1()->AsRegOpnd(), Js::JavascriptString::GetOffsetOfpszValue(), TyMachPtr, func), instr);
// TST strBuffer, strBuffer
// JNE $continue
InsertTestBranch(strBufferOpnd, strBufferOpnd, Js::OpCode::BrNeq_A, continueLabel, instr);
// $helper:
// PUSH str
// CALL JavascriptString::GetSzHelper
// MOV strBuffer, eax
// $continue:
instr->InsertBefore(helperLabel);
m_lowererMD.LoadHelperArgument(instr, instr->GetSrc1());
IR::Instr * instrCall = IR::Instr::New(Js::OpCode::Call, strBufferOpnd, IR::HelperCallOpnd::New(IR::HelperString_GetSz, func), func);
instr->InsertBefore(instrCall);
m_lowererMD.LowerCall(instrCall, 0);
instr->InsertBefore(continueLabel);
// MOV charOpnd, [strBuffer]
IR::RegOpnd * charOpnd = IR::RegOpnd::New(TyUint32, func);
InsertMove(charOpnd, IR::IndirOpnd::New(strBufferOpnd, 0, TyUint16, func), instr);
if (multiBrInstr->m_baseCaseValue != 0)
{
// SUB charOpnd, baseIndex
InsertSub(false, charOpnd, charOpnd, IR::IntConstOpnd::New(multiBrInstr->m_baseCaseValue, TyUint32, func), instr);
}
// CMP charOpnd, lastCaseIndex - baseCaseIndex
// JA defaultLabel
InsertCompareBranch(charOpnd, IR::IntConstOpnd::New(multiBrInstr->m_lastCaseValue - multiBrInstr->m_baseCaseValue, TyUint32, func),
Js::OpCode::BrGt_A, true, defaultLabelInstr, instr);
instr->UnlinkSrc1();
LowerJumpTableMultiBranch(multiBrInstr, charOpnd);
}
void Lowerer::GenerateSwitchStringLookup(IR::Instr * instr)
{
/* Collect information about string length in all the case*/
charcount_t minLength = UINT_MAX;
charcount_t maxLength = 0;
BVUnit32 bvLength;
instr->AsBranchInstr()->AsMultiBrInstr()->GetBranchDictionary()->dictionary.Map([&](JITJavascriptString * str, void *)
{
charcount_t len = str->GetLength();
minLength = min(minLength, str->GetLength());
maxLength = max(maxLength, str->GetLength());
if (len < 32)
{
bvLength.Set(len);
}
});
Func * func = instr->m_func;
IR::RegOpnd * strLengthOpnd = IR::RegOpnd::New(TyUint32, func);
InsertMove(strLengthOpnd, IR::IndirOpnd::New(instr->GetSrc1()->AsRegOpnd(), Js::JavascriptString::GetOffsetOfcharLength(), TyUint32, func), instr);
IR::LabelInstr * defaultLabelInstr = (IR::LabelInstr *)instr->AsBranchInstr()->AsMultiBrInstr()->GetBranchDictionary()->defaultTarget;
if (minLength == maxLength)
{
// Generate single length filter
InsertCompareBranch(strLengthOpnd, IR::IntConstOpnd::New(minLength, TyUint32, func), Js::OpCode::BrNeq_A, defaultLabelInstr, instr);
}
else if (maxLength < 32)
{
// Generate bit filter
// Jump to default label if the bit is not on for the length % 32
IR::IntConstOpnd * lenBitMaskOpnd = IR::IntConstOpnd::New(bvLength.GetWord(), TyUint32, func);
InsertBitTestBranch(lenBitMaskOpnd, strLengthOpnd, false, defaultLabelInstr, instr);
// Jump to default label if the bit is > 32
InsertTestBranch(strLengthOpnd, IR::IntConstOpnd::New(UINT32_MAX ^ 31, TyUint32, func), Js::OpCode::BrNeq_A, defaultLabelInstr, instr);
}
else
{
// CONSIDER: Generate range filter
}
this->LowerMultiBr(instr, IR::HelperOp_SwitchStringLookUp);
}
IR::Instr *
Lowerer::LowerGetCachedFunc(IR::Instr *instr)
{
// src1 is an ActivationObjectEx, and we want to get the function object identified by the index (src2)
// dst = MOV (src1)->GetFuncCacheEntry(src2)->func
//
// => [src1 + (offsetof(src1, cache) + (src2 * sizeof(FuncCacheEntry)) + offsetof(FuncCacheEntry, func))]
IR::IntConstOpnd *src2Opnd = instr->UnlinkSrc2()->AsIntConstOpnd();
IR::RegOpnd *src1Opnd = instr->UnlinkSrc1()->AsRegOpnd();
IR::Instr *instrPrev = instr->m_prev;
instr->SetSrc1(IR::IndirOpnd::New(src1Opnd, int32((src2Opnd->GetValue() * sizeof(Js::FuncCacheEntry)) + Js::ActivationObjectEx::GetOffsetOfCache() + offsetof(Js::FuncCacheEntry, func)), TyVar, this->m_func));
this->m_lowererMD.ChangeToAssign(instr);
src2Opnd->Free(this->m_func);
return instrPrev;
}
IR::Instr *
Lowerer::LowerCommitScope(IR::Instr *instrCommit)
{
IR::Instr *instrPrev = instrCommit->m_prev;
IR::RegOpnd *baseOpnd = instrCommit->UnlinkSrc1()->AsRegOpnd();
IR::Opnd *opnd;
IR::Instr * insertInstr = instrCommit->m_next;
// Write undef to all the local var slots.
opnd = IR::IndirOpnd::New(baseOpnd, Js::ActivationObjectEx::GetOffsetOfCommitFlag(), TyInt8, this->m_func);
instrCommit->SetDst(opnd);
instrCommit->SetSrc1(IR::IntConstOpnd::New(1, TyInt8, this->m_func));
LowererMD::ChangeToAssign(instrCommit);
const Js::PropertyIdArray *propIds = instrCommit->m_func->GetJITFunctionBody()->GetFormalsPropIdArray();
uint firstVarSlot = (uint)Js::ActivationObjectEx::GetFirstVarSlot(propIds);
if (firstVarSlot < propIds->count)
{
// Instead of re-using the address of "undefined" for each store, put the address in a register and re-use that.
IR::RegOpnd *undefOpnd = IR::RegOpnd::New(TyMachReg, this->m_func);
InsertMove(undefOpnd, LoadLibraryValueOpnd(insertInstr, LibraryValue::ValueUndefined), insertInstr);
IR::RegOpnd *slotBaseOpnd = IR::RegOpnd::New(TyMachReg, this->m_func);
// Load a pointer to the aux slots. We assume that all ActivationObject's have only aux slots.
opnd = IR::IndirOpnd::New(baseOpnd, Js::DynamicObject::GetOffsetOfAuxSlots(), TyMachReg, this->m_func);
InsertMove(slotBaseOpnd, opnd, insertInstr);
for (uint i = firstVarSlot; i < propIds->count; i++)
{
opnd = IR::IndirOpnd::New(slotBaseOpnd, i << this->m_lowererMD.GetDefaultIndirScale(), TyMachReg, this->m_func);
InsertMove(opnd, undefOpnd, insertInstr);
}
}
return instrPrev;
}
IR::Instr *
Lowerer::LowerTry(IR::Instr* instr, bool tryCatch)
{
if (this->m_func->hasBailout)
{
this->EnsureBailoutReturnValueSym();
}
this->EnsureHasBailedOutSym();
IR::SymOpnd * hasBailedOutOpnd = IR::SymOpnd::New(this->m_func->m_hasBailedOutSym, TyUint32, this->m_func);
IR::Instr * setInstr = IR::Instr::New(LowererMD::GetStoreOp(TyUint32), hasBailedOutOpnd, IR::IntConstOpnd::New(0, TyUint32, this->m_func), this->m_func);
instr->InsertBefore(setInstr);
LowererMD::Legalize(setInstr);
return m_lowererMD.LowerTry(instr, tryCatch ? IR::HelperOp_TryCatch : ((this->m_func->DoOptimizeTry() || (this->m_func->IsSimpleJit() && this->m_func->hasBailout))? IR::HelperOp_TryFinally : IR::HelperOp_TryFinallyNoOpt));
}
IR::Instr *
Lowerer::LowerCatch(IR::Instr * instr)
{
// t1 = catch => t2 = catch
// => t1 = t2
IR::Opnd *catchObj = instr->UnlinkDst();
IR::RegOpnd *catchParamReg = IR::RegOpnd::New(TyMachPtr, this->m_func);
catchParamReg->SetReg(CATCH_OBJ_REG);
instr->SetDst(catchParamReg);
IR::Instr * mov = IR::Instr::New(Js::OpCode::Ld_A, catchObj, catchParamReg, this->m_func);
this->m_lowererMD.ChangeToAssign(mov);
instr->InsertAfter(mov);
return instr->m_prev;
}
IR::Instr *
Lowerer::LowerLeave(IR::Instr * leaveInstr, IR::LabelInstr * targetInstr, bool fromFinalLower, bool isOrphanedLeave)
{
if (isOrphanedLeave)
{
Assert(this->m_func->IsLoopBodyInTry());
leaveInstr->m_opcode = LowererMD::MDUncondBranchOpcode;
return leaveInstr->m_prev;
}
IR::Instr * instrPrev = leaveInstr->m_prev;
IR::LabelOpnd *labelOpnd = IR::LabelOpnd::New(targetInstr, this->m_func);
m_lowererMD.LowerEHRegionReturn(leaveInstr, labelOpnd);
if (fromFinalLower)
{
instrPrev = leaveInstr->m_prev;
}
leaveInstr->Remove();
return instrPrev;
}
void
Lowerer::EnsureBailoutReturnValueSym()
{
if (this->m_func->m_bailoutReturnValueSym == nullptr)
{
this->m_func->m_bailoutReturnValueSym = StackSym::New(TyVar, this->m_func);
this->m_func->StackAllocate(this->m_func->m_bailoutReturnValueSym, sizeof(Js::Var));
}
}
void
Lowerer::EnsureHasBailedOutSym()
{
if (this->m_func->m_hasBailedOutSym == nullptr)
{
this->m_func->m_hasBailedOutSym = StackSym::New(TyUint32, this->m_func);
this->m_func->StackAllocate(this->m_func->m_hasBailedOutSym, MachRegInt);
}
}
void
Lowerer::InsertReturnThunkForRegion(Region* region, IR::LabelInstr* restoreLabel)
{
Assert(this->m_func->isPostLayout);
Assert(region->GetType() == RegionTypeTry || region->GetType() == RegionTypeCatch || region->GetType() == RegionTypeFinally);
if (!region->returnThunkEmitted)
{
this->m_func->m_exitInstr->InsertAfter(region->GetBailoutReturnThunkLabel());
bool newLastInstrInserted = false;
IR::Instr * insertBeforeInstr = region->GetBailoutReturnThunkLabel()->m_next;
if (insertBeforeInstr == nullptr)
{
Assert(this->m_func->m_exitInstr == this->m_func->m_tailInstr);
insertBeforeInstr = IR::Instr::New(Js::OpCode::Nop, this->m_func);
newLastInstrInserted = true;
region->GetBailoutReturnThunkLabel()->InsertAfter(insertBeforeInstr);
this->m_func->m_tailInstr = insertBeforeInstr;
}
IR::LabelOpnd * continuationAddr;
// We insert return thunk to the region's parent return thunk label
// For non exception finallys, we do not need a return thunk
// Because, we are not calling none xception finallys from within amd64_callWithFakeFrame
// But a non exception finally maybe within other eh regions that need a return thunk
if (region->IsNonExceptingFinally())
{
Assert(region->GetParent()->GetType() != RegionTypeRoot);
Region *ancestor = region->GetParent()->GetFirstAncestorOfNonExceptingFinallyParent();
Assert(ancestor && !ancestor->IsNonExceptingFinally());
if (ancestor->GetType() != RegionTypeRoot)
{
continuationAddr = IR::LabelOpnd::New(ancestor->GetBailoutReturnThunkLabel(), this->m_func);
}
else
{
continuationAddr = IR::LabelOpnd::New(restoreLabel, this->m_func);
}
}
else if (region->GetParent()->IsNonExceptingFinally())
{
Region *ancestor = region->GetFirstAncestorOfNonExceptingFinally();
if (ancestor && ancestor->GetType() != RegionTypeRoot)
{
continuationAddr = IR::LabelOpnd::New(ancestor->GetBailoutReturnThunkLabel(), this->m_func);
}
else
{
continuationAddr = IR::LabelOpnd::New(restoreLabel, this->m_func);
}
}
else if (region->GetParent()->GetType() != RegionTypeRoot)
{
continuationAddr = IR::LabelOpnd::New(region->GetParent()->GetBailoutReturnThunkLabel(), this->m_func);
}
else
{
continuationAddr = IR::LabelOpnd::New(restoreLabel, this->m_func);
}
IR::Instr * lastInstr = m_lowererMD.LowerEHRegionReturn(insertBeforeInstr, continuationAddr);
if (newLastInstrInserted)
{
Assert(this->m_func->m_tailInstr == insertBeforeInstr);
insertBeforeInstr->Remove();
this->m_func->m_tailInstr = lastInstr;
}
region->returnThunkEmitted = true;
}
}
void
Lowerer::SetHasBailedOut(IR::Instr * bailoutInstr)
{
Assert(this->m_func->isPostLayout);
IR::SymOpnd * hasBailedOutOpnd = IR::SymOpnd::New(this->m_func->m_hasBailedOutSym, TyUint32, this->m_func);
IR::Instr * setInstr = IR::Instr::New(LowererMD::GetStoreOp(TyUint32), hasBailedOutOpnd, IR::IntConstOpnd::New(1, TyUint32, this->m_func), this->m_func);
bailoutInstr->InsertBefore(setInstr);
LowererMD::Legalize(setInstr);
}
IR::Instr*
Lowerer::EmitEHBailoutStackRestore(IR::Instr * bailoutInstr)
{
Assert(this->m_func->isPostLayout);
#ifdef _M_IX86
BailOutInfo * bailoutInfo = bailoutInstr->GetBailOutInfo();
uint totalLiveArgCount = 0;
if (bailoutInfo->startCallCount != 0)
{
uint totalStackToBeRestored = 0;
uint stackAlignmentAdjustment = 0;
for (uint i = 0; i < bailoutInfo->startCallCount; i++)
{
uint startCallLiveArgCount = bailoutInfo->startCallInfo[i].isOrphanedCall ? 0 : bailoutInfo->GetStartCallOutParamCount(i);
if ((Math::Align<int32>(startCallLiveArgCount * MachPtr, MachStackAlignment) - (startCallLiveArgCount * MachPtr)) != 0)
{
stackAlignmentAdjustment++;
}
totalLiveArgCount += startCallLiveArgCount;
}
totalStackToBeRestored = (totalLiveArgCount + stackAlignmentAdjustment) * MachPtr;
IR::RegOpnd * espOpnd = IR::RegOpnd::New(NULL, LowererMD::GetRegStackPointer(), TyMachReg, this->m_func);
IR::Opnd * opnd = IR::IndirOpnd::New(espOpnd, totalStackToBeRestored, TyMachReg, this->m_func);
IR::Instr * stackRestoreInstr = IR::Instr::New(Js::OpCode::LEA, espOpnd, opnd, this->m_func);
bailoutInstr->InsertAfter(stackRestoreInstr);
return stackRestoreInstr;
}
#endif
return bailoutInstr;
}
void
Lowerer::EmitSaveEHBailoutReturnValueAndJumpToRetThunk(IR::Instr * insertAfterInstr)
{
Assert(this->m_func->isPostLayout);
// After the CALL SaveAllRegistersAndBailout instruction, emit
//
// MOV bailoutReturnValueSym, eax
// JMP $currentRegion->bailoutReturnThunkLabel
IR::SymOpnd * bailoutReturnValueSymOpnd = IR::SymOpnd::New(this->m_func->m_bailoutReturnValueSym, TyVar, this->m_func);
IR::RegOpnd *eaxOpnd = IR::RegOpnd::New(NULL, LowererMD::GetRegReturn(TyMachReg), TyMachReg, this->m_func);
IR::Instr * movInstr = IR::Instr::New(LowererMD::GetStoreOp(TyVar), bailoutReturnValueSymOpnd, eaxOpnd, this->m_func);
insertAfterInstr->InsertAfter(movInstr);
LowererMD::Legalize(movInstr);
IR::BranchInstr * jumpInstr = IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, this->currentRegion->GetBailoutReturnThunkLabel(), this->m_func);
movInstr->InsertAfter(jumpInstr);
}
void
Lowerer::EmitRestoreReturnValueFromEHBailout(IR::LabelInstr * restoreLabel, IR::LabelInstr * epilogLabel)
{
Assert(this->m_func->isPostLayout);
// JMP $epilog
// $restore:
// MOV eax, bailoutReturnValueSym
// $epilog:
IR::SymOpnd * bailoutReturnValueSymOpnd = IR::SymOpnd::New(this->m_func->m_bailoutReturnValueSym, TyVar, this->m_func);
IR::RegOpnd * eaxOpnd = IR::RegOpnd::New(NULL, LowererMD::GetRegReturn(TyMachReg), TyMachReg, this->m_func);
IR::Instr * movInstr = IR::Instr::New(LowererMD::GetLoadOp(TyVar), eaxOpnd, bailoutReturnValueSymOpnd, this->m_func);
epilogLabel->InsertBefore(restoreLabel);
epilogLabel->InsertBefore(movInstr);
LowererMD::Legalize(movInstr);
restoreLabel->InsertBefore(IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, epilogLabel, this->m_func));
}
void
Lowerer::InsertBitTestBranch(IR::Opnd * bitMaskOpnd, IR::Opnd * bitIndex, bool jumpIfBitOn, IR::LabelInstr * targetLabel, IR::Instr * insertBeforeInstr)
{
#if defined(_M_IX86) || defined(_M_AMD64)
// Generate bit test and branch
// BT bitMaskOpnd, bitIndex
// JB/JAE targetLabel
Func * func = this->m_func;
IR::Instr * instr = IR::Instr::New(Js::OpCode::BT, func);
instr->SetSrc1(bitMaskOpnd);
instr->SetSrc2(bitIndex);
insertBeforeInstr->InsertBefore(instr);
if (!(bitMaskOpnd->IsRegOpnd() || bitMaskOpnd->IsIndirOpnd() || bitMaskOpnd->IsMemRefOpnd()))
{
instr->HoistSrc1(Js::OpCode::MOV);
}
InsertBranch(jumpIfBitOn ? Js::OpCode::JB : Js::OpCode::JAE, targetLabel, insertBeforeInstr);
#elif defined(_M_ARM)
// ARM don't have bit test instruction, so just generated
// MOV r1, 1
// SHL r1, bitIndex
// TEST bitMaskOpnd, r1
// BEQ/BNEQ targetLabel
Func * func = this->m_func;
IR::RegOpnd * lenBitOpnd = IR::RegOpnd::New(TyUint32, func);
InsertMove(lenBitOpnd, IR::IntConstOpnd::New(1, TyUint32, this->m_func), insertBeforeInstr);
InsertShift(Js::OpCode::Shl_I4, false, lenBitOpnd, lenBitOpnd, bitIndex, insertBeforeInstr);
InsertTestBranch(lenBitOpnd, bitMaskOpnd, jumpIfBitOn ? Js::OpCode::BrNeq_A : Js::OpCode::BrEq_A, targetLabel, insertBeforeInstr);
#elif defined(_M_ARM64)
if (bitIndex->IsImmediateOpnd())
{
// TBZ/TBNZ bitMaskOpnd, bitIndex, targetLabel
IR::Instr* branchInstr = InsertBranch(jumpIfBitOn ? Js::OpCode::TBNZ : Js::OpCode::TBZ, targetLabel, insertBeforeInstr);
branchInstr->SetSrc1(bitMaskOpnd);
branchInstr->SetSrc2(bitIndex);
}
else
{
// TBZ/TBNZ require an immediate for the bit to test, so shift the mask to place the bit we want to test at bit zero, and then test bit zero.
Func * func = this->m_func;
IR::RegOpnd * maskOpnd = IR::RegOpnd::New(TyUint32, func);
InsertShift(Js::OpCode::Shr_I4, false, maskOpnd, bitMaskOpnd, bitIndex, insertBeforeInstr);
IR::Instr* branchInstr = InsertBranch(jumpIfBitOn ? Js::OpCode::TBNZ : Js::OpCode::TBZ, targetLabel, insertBeforeInstr);
branchInstr->SetSrc1(maskOpnd);
branchInstr->SetSrc2(IR::IntConstOpnd::New(0, TyUint32, this->m_func));
}
#else
AssertMsg(false, "Not implemented");
#endif
}
//
// Generates an object test and then a string test with the static string type
//
void
Lowerer::GenerateStringTest(IR::RegOpnd *srcReg, IR::Instr *insertInstr, IR::LabelInstr *labelHelper, IR::LabelInstr * continueLabel, bool generateObjectCheck)
{
Assert(srcReg);
if (!srcReg->GetValueType().IsString())
{
if (generateObjectCheck && !srcReg->IsNotTaggedValue())
{
this->m_lowererMD.GenerateObjectTest(srcReg, insertInstr, labelHelper);
}
// CMP [regSrcStr + offset(type)] , static string type -- check base string type
// BrEq/BrNeq labelHelper.
IR::IndirOpnd * src1 = IR::IndirOpnd::New(srcReg, Js::RecyclableObject::GetOffsetOfType(), TyMachReg, m_func);
IR::Opnd * src2 = this->LoadLibraryValueOpnd(insertInstr, LibraryValue::ValueStringTypeStatic);
IR::BranchInstr* branchInstr = nullptr;
if (continueLabel)
{
branchInstr = InsertCompareBranch(src1, src2, Js::OpCode::BrEq_A, continueLabel, insertInstr);
}
else
{
branchInstr = InsertCompareBranch(src1, src2, Js::OpCode::BrNeq_A, labelHelper, insertInstr);
}
InsertObjectPoison(srcReg, branchInstr, insertInstr, false);
}
}
//
// Generates an object test and then a symbol test with the static symbol type
//
void
Lowerer::GenerateSymbolTest(IR::RegOpnd *srcReg, IR::Instr *insertInstr, IR::LabelInstr *labelHelper, IR::LabelInstr * continueLabel, bool generateObjectCheck)
{
Assert(srcReg);
if (!srcReg->GetValueType().IsSymbol())
{
if (generateObjectCheck && !srcReg->IsNotTaggedValue())
{
this->m_lowererMD.GenerateObjectTest(srcReg, insertInstr, labelHelper);
}
// CMP [regSrcStr + offset(type)] , static symbol type -- check base symbol type
// BrEq/BrNeq labelHelper.
IR::IndirOpnd * src1 = IR::IndirOpnd::New(srcReg, Js::RecyclableObject::GetOffsetOfType(), TyMachReg, m_func);
IR::Opnd * src2 = this->LoadLibraryValueOpnd(insertInstr, LibraryValue::ValueSymbolTypeStatic);
if (continueLabel)
{
InsertCompareBranch(src1, src2, Js::OpCode::BrEq_A, continueLabel, insertInstr);
}
else
{
InsertCompareBranch(src1, src2, Js::OpCode::BrNeq_A, labelHelper, insertInstr);
}
}
}
void
Lowerer::LowerConvNum(IR::Instr *instrLoad, bool noMathFastPath)
{
if (PHASE_OFF(Js::OtherFastPathPhase, this->m_func) || noMathFastPath || !instrLoad->GetSrc1()->IsRegOpnd())
{
this->LowerUnaryHelperMemWithTemp2(instrLoad, IR_HELPER_OP_FULL_OR_INPLACE(ConvNumber));
return;
}
// MOV dst, src1
// TEST src1, 1
// JNE $done
// call ToNumber
//$done:
bool isInt = false;
bool isNotInt = false;
IR::RegOpnd *src1 = instrLoad->GetSrc1()->AsRegOpnd();
IR::LabelInstr *labelDone = NULL;
IR::Instr *instr;
if (src1->IsTaggedInt())
{
isInt = true;
}
else if (src1->IsNotInt())
{
isNotInt = true;
}
if (!isNotInt)
{
// MOV dst, src1
instr = Lowerer::InsertMove(instrLoad->GetDst(), src1, instrLoad);
if (!isInt)
{
labelDone = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
bool didTest = m_lowererMD.GenerateObjectTest(src1, instrLoad, labelDone);
if (didTest)
{
// This label is needed only to mark the helper block
IR::LabelInstr * labelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true);
instrLoad->InsertBefore(labelHelper);
}
}
}
if (!isInt)
{
if (labelDone)
{
instrLoad->InsertAfter(labelDone);
}
this->LowerUnaryHelperMemWithTemp2(instrLoad, IR_HELPER_OP_FULL_OR_INPLACE(ConvNumber));
}
else
{
instrLoad->Remove();
}
}
IR::Opnd *
Lowerer::LoadSlotArrayWithCachedLocalType(IR::Instr * instrInsert, IR::PropertySymOpnd *propertySymOpnd)
{
IR::RegOpnd *opndBase = propertySymOpnd->CreatePropertyOwnerOpnd(m_func);
if (propertySymOpnd->UsesAuxSlot())
{
// If we use the auxiliary slot array, load it and return it
IR::RegOpnd * opndSlotArray;
if (propertySymOpnd->IsAuxSlotPtrSymAvailable() || propertySymOpnd->ProducesAuxSlotPtr())
{
// We want to reload and/or reuse the shared aux slot ptr sym
StackSym * auxSlotPtrSym = propertySymOpnd->GetAuxSlotPtrSym();
Assert(auxSlotPtrSym != nullptr);
opndSlotArray = IR::RegOpnd::New(auxSlotPtrSym, TyMachReg, this->m_func);
opndSlotArray->SetIsJITOptimizedReg(true);
if (!propertySymOpnd->ProducesAuxSlotPtr())
{
// No need to reload
return opndSlotArray;
}
}
else
{
opndSlotArray = IR::RegOpnd::New(TyMachReg, this->m_func);
}
IR::Opnd *opndIndir = IR::IndirOpnd::New(opndBase, Js::DynamicObject::GetOffsetOfAuxSlots(), TyMachReg, this->m_func);
Lowerer::InsertMove(opndSlotArray, opndIndir, instrInsert);
return opndSlotArray;
}
else
{
// If we use inline slot return the address to the object header
return opndBase;
}
}
IR::Opnd *
Lowerer::LoadSlotArrayWithCachedProtoType(IR::Instr * instrInsert, IR::PropertySymOpnd *propertySymOpnd)
{
// Get the prototype object from the cache
intptr_t prototypeObject = propertySymOpnd->GetProtoObject();
Assert(prototypeObject != 0);
if (propertySymOpnd->UsesAuxSlot())
{
// If we use the auxiliary slot array, load it from the prototype object and return it
IR::RegOpnd *opndSlotArray = IR::RegOpnd::New(TyMachReg, this->m_func);
IR::Opnd *opnd = IR::MemRefOpnd::New((char*)prototypeObject + Js::DynamicObject::GetOffsetOfAuxSlots(), TyMachReg, this->m_func, IR::AddrOpndKindDynamicAuxSlotArrayRef);
Lowerer::InsertMove(opndSlotArray, opnd, instrInsert);
return opndSlotArray;
}
else
{
// If we use inline slot return the address of the prototype object
return IR::MemRefOpnd::New(prototypeObject, TyMachReg, this->m_func);
}
}
IR::Instr *
Lowerer::LowerLdAsmJsEnv(IR::Instr * instr)
{
Assert(m_func->GetJITFunctionBody()->IsAsmJsMode());
IR::Opnd * functionObjOpnd;
IR::Instr * instrPrev = this->m_lowererMD.LoadFunctionObjectOpnd(instr, functionObjOpnd);
Assert(!instr->GetSrc1());
IR::IndirOpnd *indirOpnd = IR::IndirOpnd::New(functionObjOpnd->AsRegOpnd(), Js::AsmJsScriptFunction::GetOffsetOfModuleMemory(), TyMachPtr, m_func);
instr->SetSrc1(indirOpnd);
LowererMD::ChangeToAssign(instr);
return instrPrev;
}
IR::Instr *
Lowerer::LowerLdNativeCodeData(IR::Instr * instr)
{
Assert(!instr->GetSrc1());
Assert(m_func->IsTopFunc());
IR::Instr * instrPrev = instr->m_prev;
instr->SetSrc1(IR::MemRefOpnd::New((void*)m_func->GetWorkItem()->GetWorkItemData()->nativeDataAddr, TyMachPtr, m_func, IR::AddrOpndKindDynamicNativeCodeDataRef));
LowererMD::ChangeToAssign(instr);
return instrPrev;
}
IR::Instr *
Lowerer::LowerLdEnv(IR::Instr * instr)
{
IR::Opnd * src1 = instr->GetSrc1();
IR::Opnd * functionObjOpnd;
IR::Instr * instrPrev = this->m_lowererMD.LoadFunctionObjectOpnd(instr, functionObjOpnd);
Assert(!instr->GetSrc1());
if (src1 == nullptr || functionObjOpnd->IsRegOpnd())
{
IR::IndirOpnd *indirOpnd = IR::IndirOpnd::New(functionObjOpnd->AsRegOpnd(),
Js::ScriptFunction::GetOffsetOfEnvironment(), TyMachPtr, m_func);
instr->SetSrc1(indirOpnd);
}
else
{
Assert(functionObjOpnd->IsAddrOpnd());
IR::AddrOpnd* functionObjAddrOpnd = functionObjOpnd->AsAddrOpnd();
IR::MemRefOpnd* functionEnvMemRefOpnd = IR::MemRefOpnd::New((void *)((intptr_t)functionObjAddrOpnd->m_address + Js::ScriptFunction::GetOffsetOfEnvironment()),
TyMachPtr, this->m_func, IR::AddrOpndKindDynamicFunctionEnvironmentRef);
instr->SetSrc1(functionEnvMemRefOpnd);
}
LowererMD::ChangeToAssign(instr);
return instrPrev;
}
IR::Instr *
Lowerer::LowerLdSuper(IR::Instr *instr, IR::JnHelperMethod helperOpCode)
{
IR::Opnd * functionObjOpnd;
IR::Instr * instrPrev = m_lowererMD.LoadFunctionObjectOpnd(instr, functionObjOpnd);
LoadScriptContext(instr);
m_lowererMD.LoadHelperArgument(instr, functionObjOpnd);
m_lowererMD.ChangeToHelperCall(instr, helperOpCode);
return instrPrev;
}
IR::Instr *
Lowerer::LowerFrameDisplayCheck(IR::Instr * instr)
{
IR::Instr *instrPrev = instr->m_prev;
IR::Instr *insertInstr = instr->m_next;
IR::AddrOpnd *addrOpnd = instr->UnlinkSrc2()->AsAddrOpnd();
FrameDisplayCheckRecord *record = (FrameDisplayCheckRecord*)addrOpnd->m_address;
IR::LabelInstr *errorLabel = nullptr;
IR::LabelInstr *continueLabel = nullptr;
IR::RegOpnd *envOpnd = instr->GetDst()->AsRegOpnd();
uint32 frameDisplayOffset = Js::FrameDisplay::GetOffsetOfScopes()/sizeof(Js::Var);
if (record->slotId != (uint32)-1 && record->slotId > frameDisplayOffset)
{
// Check that the frame display has enough scopes in it to satisfy the code.
errorLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func, true);
continueLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func);
IR::IndirOpnd * indirOpnd = IR::IndirOpnd::New(envOpnd,
Js::FrameDisplay::GetOffsetOfLength(),
TyUint16, m_func, true);
IR::IntConstOpnd *slotIdOpnd = IR::IntConstOpnd::New(record->slotId - frameDisplayOffset, TyUint16, m_func);
InsertCompareBranch(indirOpnd, slotIdOpnd, Js::OpCode::BrLe_A, true, errorLabel, insertInstr);
}
if (record->table)
{
// Check the size of each of the slot arrays in the scope chain.
FOREACH_HASHTABLE_ENTRY(uint32, bucket, record->table)
{
uint32 slotId = bucket.element;
if (slotId != (uint32)-1 && slotId > Js::ScopeSlots::FirstSlotIndex)
{
if (errorLabel == nullptr)
{
errorLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func, true);
continueLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func);
}
IR::IndirOpnd * indirOpnd = IR::IndirOpnd::New(envOpnd,
bucket.value * sizeof(Js::Var),
TyVar, m_func, true);
IR::RegOpnd * slotArrayOpnd = IR::RegOpnd::New(TyVar, m_func);
InsertMove(slotArrayOpnd, indirOpnd, insertInstr);
indirOpnd = IR::IndirOpnd::New(slotArrayOpnd,
Js::ScopeSlots::EncodedSlotCountSlotIndex * sizeof(Js::Var),
TyVar, m_func, true);
IR::IntConstOpnd * slotIdOpnd = IR::IntConstOpnd::New(slotId - Js::ScopeSlots::FirstSlotIndex,
TyUint32, m_func);
InsertCompareBranch(indirOpnd, slotIdOpnd, Js::OpCode::BrLe_A, true, errorLabel, insertInstr);
}
}
NEXT_HASHTABLE_ENTRY;
}
if (errorLabel)
{
InsertBranch(Js::OpCode::Br, continueLabel, insertInstr);
insertInstr->InsertBefore(errorLabel);
IR::Instr * instrHelper = IR::Instr::New(Js::OpCode::Call, m_func);
insertInstr->InsertBefore(instrHelper);
m_lowererMD.ChangeToHelperCall(instrHelper, IR::HelperOp_FatalInternalError);
insertInstr->InsertBefore(continueLabel);
}
m_lowererMD.ChangeToAssign(instr);
return instrPrev;
}
IR::Instr *
Lowerer::LowerSlotArrayCheck(IR::Instr * instr)
{
IR::Instr *instrPrev = instr->m_prev;
IR::Instr *insertInstr = instr->m_next;
IR::RegOpnd *slotArrayOpnd = instr->GetDst()->AsRegOpnd();
StackSym *stackSym = slotArrayOpnd->m_sym;
IR::IntConstOpnd *slotIdOpnd = instr->UnlinkSrc2()->AsIntConstOpnd();
uint32 slotId = (uint32)slotIdOpnd->GetValue();
Assert(slotId != (uint32)-1 && slotId >= Js::ScopeSlots::FirstSlotIndex);
if (slotId > Js::ScopeSlots::FirstSlotIndex)
{
if (m_func->DoStackFrameDisplay() && stackSym->m_id == m_func->GetLocalClosureSym()->m_id)
{
// The pointer we loaded points to the reserved/known address where the slot array can be boxed.
// Deref to get the real value.
IR::IndirOpnd * srcOpnd = IR::IndirOpnd::New(IR::RegOpnd::New(stackSym, TyVar, m_func), 0, TyVar, m_func);
IR::RegOpnd * dstOpnd = IR::RegOpnd::New(TyVar, m_func);
InsertMove(dstOpnd, srcOpnd, insertInstr);
stackSym = dstOpnd->m_sym;
}
IR::LabelInstr *errorLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func, true);
IR::LabelInstr *continueLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func);
IR::IndirOpnd * indirOpnd = IR::IndirOpnd::New(IR::RegOpnd::New(stackSym, TyVar, m_func),
Js::ScopeSlots::EncodedSlotCountSlotIndex * sizeof(Js::Var),
TyVar, m_func, true);
slotIdOpnd->SetValue(slotId - Js::ScopeSlots::FirstSlotIndex);
InsertCompareBranch(indirOpnd, slotIdOpnd, Js::OpCode::BrGt_A, true, continueLabel, insertInstr);
insertInstr->InsertBefore(errorLabel);
IR::Instr * instrHelper = IR::Instr::New(Js::OpCode::Call, m_func);
insertInstr->InsertBefore(instrHelper);
m_lowererMD.ChangeToHelperCall(instrHelper, IR::HelperOp_FatalInternalError);
insertInstr->InsertBefore(continueLabel);
}
m_lowererMD.ChangeToAssign(instr);
return instrPrev;
}
IR::RegOpnd *
Lowerer::LoadIndexFromLikelyFloat(
IR::RegOpnd *indexOpnd,
const bool skipNegativeCheck,
IR::LabelInstr *const notIntLabel,
IR::LabelInstr *const negativeLabel,
IR::Instr *const insertBeforeInstr)
{
#ifdef _M_IX86
// We should only generate this if sse2 is available
Assert(AutoSystemInfo::Data.SSE2Available());
#endif
Func *func = insertBeforeInstr->m_func;
IR::LabelInstr * fallThrough = IR::LabelInstr::New(Js::OpCode::Label, func);
IR::RegOpnd *int32IndexOpnd = nullptr;
// If we know for sure that it's not an int, do not check to see if it's a tagged int
if (indexOpnd->IsNotInt())
{
int32IndexOpnd = IR::RegOpnd::New(TyInt32, func);
}
else
{
IR::LabelInstr * convertToUint = IR::LabelInstr::New(Js::OpCode::Label, func);
// First generate test for tagged int even though profile data says likely float. Indices are usually int and we need a fast path before we try to convert float to int
// mov intIndex, index
// sar intIndex, 1
// jae convertToInt
int32IndexOpnd = GenerateUntagVar(indexOpnd, convertToUint, insertBeforeInstr, !indexOpnd->IsTaggedInt());
if (!skipNegativeCheck)
{
// test index, index
// js $notTaggedIntOrNegative
InsertTestBranch(int32IndexOpnd, int32IndexOpnd, LowererMD::MDCompareWithZeroBranchOpcode(Js::OpCode::BrLt_A), negativeLabel, insertBeforeInstr);
}
InsertBranch(Js::OpCode::Br, fallThrough, insertBeforeInstr);
insertBeforeInstr->InsertBefore(convertToUint);
}
// try to convert float to int in a fast path
#if FLOATVAR
IR::RegOpnd* floatIndexOpnd = m_lowererMD.CheckFloatAndUntag(indexOpnd, insertBeforeInstr, notIntLabel);
#else
m_lowererMD.GenerateFloatTest(indexOpnd, insertBeforeInstr, notIntLabel);
IR::IndirOpnd * floatIndexOpnd = IR::IndirOpnd::New(indexOpnd, Js::JavascriptNumber::GetValueOffset(), TyMachDouble, this->m_func);
#endif
IR::LabelInstr * doneConvUint32 = IR::LabelInstr::New(Js::OpCode::Label, func);
IR::LabelInstr * helperConvUint32 = IR::LabelInstr::New(Js::OpCode::Label, func, true /*helper*/);
m_lowererMD.ConvertFloatToInt32(int32IndexOpnd, floatIndexOpnd, helperConvUint32, doneConvUint32, insertBeforeInstr);
// helper path
insertBeforeInstr->InsertBefore(helperConvUint32);
m_lowererMD.LoadDoubleHelperArgument(insertBeforeInstr, floatIndexOpnd);
IR::Instr * helperCall = IR::Instr::New(Js::OpCode::Call, int32IndexOpnd, this->m_func);
insertBeforeInstr->InsertBefore(helperCall);
#if DBG
// This call to Conv_ToUint32Core wont be reentrant as we would only call it for floats
this->ClearAndSaveImplicitCallCheckOnHelperCallCheckState();
#endif
m_lowererMD.ChangeToHelperCall(helperCall, IR::HelperConv_ToUInt32Core);
#if DBG
this->RestoreImplicitCallCheckOnHelperCallCheckState();
#endif
// main path
insertBeforeInstr->InsertBefore(doneConvUint32);
//Convert uint32 to back to float for comparison that conversion was indeed successful
IR::RegOpnd *floatOpndFromUint32 = IR::RegOpnd::New(TyFloat64, func);
m_lowererMD.EmitUIntToFloat(floatOpndFromUint32, int32IndexOpnd->UseWithNewType(TyUint32, this->m_func), insertBeforeInstr);
// compare with float from the original indexOpnd, we need floatIndex == (float64)(uint32)floatIndex
InsertCompareBranch(floatOpndFromUint32, floatIndexOpnd, Js::OpCode::BrNeq_A, notIntLabel, insertBeforeInstr, false);
insertBeforeInstr->InsertBefore(fallThrough);
return int32IndexOpnd;
}
void
Lowerer::AllocStackForInObjectEnumeratorArray()
{
Func * func = this->m_func;
Assert(func->IsTopFunc());
if (func->m_forInLoopMaxDepth)
{
func->m_forInEnumeratorArrayOffset = func->StackAllocate(sizeof(Js::ForInObjectEnumerator) * this->m_func->m_forInLoopMaxDepth);
}
}
IR::RegOpnd *
Lowerer::GenerateForInEnumeratorLoad(IR::Opnd * forInEnumeratorOpnd, IR::Instr * insertBeforeInstr)
{
Func * func = insertBeforeInstr->m_func;
if (forInEnumeratorOpnd->IsSymOpnd())
{
StackSym * stackSym = forInEnumeratorOpnd->AsSymOpnd()->GetStackSym();
Assert(!stackSym->m_allocated);
uint forInLoopLevel = stackSym->m_offset;
Assert(func->m_forInLoopBaseDepth + forInLoopLevel < this->m_func->m_forInLoopMaxDepth);
stackSym->m_offset = this->m_func->m_forInEnumeratorArrayOffset + ((func->m_forInLoopBaseDepth + forInLoopLevel) * sizeof(Js::ForInObjectEnumerator));
stackSym->m_allocated = true;
}
else
{
Assert(forInEnumeratorOpnd->IsIndirOpnd());
if (forInEnumeratorOpnd->AsIndirOpnd()->GetOffset() == 0)
{
return forInEnumeratorOpnd->AsIndirOpnd()->GetBaseOpnd();
}
}
IR::RegOpnd * forInEnumeratorRegOpnd = IR::RegOpnd::New(TyMachPtr, func);
InsertLea(forInEnumeratorRegOpnd, forInEnumeratorOpnd, insertBeforeInstr);
return forInEnumeratorRegOpnd;
}
void
Lowerer::GenerateHasObjectArrayCheck(IR::RegOpnd * objectOpnd, IR::RegOpnd * typeOpnd, IR::LabelInstr * hasObjectArrayLabel, IR::Instr * insertBeforeInstr)
{
// CMP [objectOpnd + offset(objectArray)], nullptr
// JEQ $noObjectArrayLabel
// TEST[objectOpnd + offset(objectArray)], ObjectArrayFlagsTag (used as flags)
// JEQ $noObjectArrayLabel
// MOV typeHandlerOpnd, [typeOpnd + offset(typeHandler)]
// CMP typeHandler->OffsetOfInlineSlots, Js::DynamicTypeHandler::GetOffsetOfObjectHeaderInlineSlots()
// JNE $hasObjectArrayLabel
// $$noObjectArrayLabel: (fall thru)
Func * func = this->m_func;
IR::LabelInstr * noObjectArrayLabel = IR::LabelInstr::New(Js::OpCode::Label, func);
IR::IndirOpnd * objectArrayOpnd = IR::IndirOpnd::New(objectOpnd, Js::DynamicObject::GetOffsetOfObjectArray(), TyMachPtr, func);
InsertCompareBranch(objectArrayOpnd, IR::AddrOpnd::NewNull(func), Js::OpCode::BrEq_A, noObjectArrayLabel, insertBeforeInstr);
InsertTestBranch(objectArrayOpnd, IR::IntConstOpnd::New((uint32)Js::DynamicObjectFlags::ObjectArrayFlagsTag, TyUint8, func),
Js::OpCode::BrNeq_A, noObjectArrayLabel, insertBeforeInstr);
IR::RegOpnd * typeHandlerOpnd = IR::RegOpnd::New(TyMachPtr, func);
InsertMove(typeHandlerOpnd, IR::IndirOpnd::New(typeOpnd, Js::DynamicType::GetOffsetOfTypeHandler(), TyMachPtr, func), insertBeforeInstr);
InsertCompareBranch(IR::IndirOpnd::New(typeHandlerOpnd, Js::DynamicTypeHandler::GetOffsetOfOffsetOfInlineSlots(), TyUint16, func),
IR::IntConstOpnd::New(Js::DynamicTypeHandler::GetOffsetOfObjectHeaderInlineSlots(), TyUint16, func),
Js::OpCode::BrNeq_A, hasObjectArrayLabel, insertBeforeInstr);
insertBeforeInstr->InsertBefore(noObjectArrayLabel);
}
void
Lowerer::GenerateInitForInEnumeratorFastPath(IR::Instr * instr, Js::EnumeratorCache * forInCache)
{
Func * func = this->m_func;
IR::LabelInstr * helperLabel = IR::LabelInstr::New(Js::OpCode::Label, func, true);
IR::RegOpnd * objectOpnd = instr->GetSrc1()->AsRegOpnd();
// Tagged check and object check
m_lowererMD.GenerateObjectTest(objectOpnd, instr, helperLabel);
GenerateIsDynamicObject(objectOpnd, instr, helperLabel);
// Type check with cache
//
// MOV typeOpnd, [objectOpnd + offset(type)]
// CMP [&forInCache->type], typeOpnd
// JNE $helper
IR::RegOpnd * typeOpnd = IR::RegOpnd::New(TyMachPtr, func);
InsertMove(typeOpnd, IR::IndirOpnd::New(objectOpnd, Js::DynamicObject::GetOffsetOfType(), TyMachPtr, func), instr);
InsertCompareBranch(IR::MemRefOpnd::New(&forInCache->type, TyMachPtr, func, IR::AddrOpndKindForInCacheType), typeOpnd, Js::OpCode::BrNeq_A, helperLabel, instr);
// Check forInCacheData->EnumNonEnumerable == false
//
// MOV forInCacheDataOpnd, [&forInCache->data]
// CMP forInCacheDataOpnd->enumNonEnumerable, 0
// JNE $helper
IR::RegOpnd * forInCacheDataOpnd = IR::RegOpnd::New(TyMachPtr, func);
InsertMove(forInCacheDataOpnd, IR::MemRefOpnd::New(&forInCache->data, TyMachPtr, func, IR::AddrOpndKindForInCacheData), instr);
InsertCompareBranch(IR::IndirOpnd::New(forInCacheDataOpnd, Js::DynamicObjectPropertyEnumerator::GetOffsetOfCachedDataEnumNonEnumerable(), TyUint8, func),
IR::IntConstOpnd::New(0, TyUint8, func), Js::OpCode::BrNeq_A, helperLabel, instr);
// Check has object array
GenerateHasObjectArrayCheck(objectOpnd, typeOpnd, helperLabel, instr);
// Check first prototype with enumerable properties
//
// MOV prototypeObjectOpnd, [type + offset(prototype)]
// MOV prototypeTypeOpnd, [prototypeObjectOpnd + offset(type)]
// CMP [prototypeTypeOpnd + offset(typeId)], TypeIds_Null
// JEQ $noPrototypeWithEnumerablePropertiesLabel
//
// $checkFirstPrototypeLoopTopLabel:
// CMP [prototypeTypeOpnd + offset(typeId)], TypeIds_LastStaticType
// JLE $helper
// CMP [prototypeTypeOpnd, offset(hasNoEnumerableProperties], 0
// JEQ $helper
// <hasObjectArrayCheck prototypeObjectOpnd, prototypeTypeOpnd>
//
// MOV prototypeObjectOpnd, [prototypeTypeOpnd + offset(protottype)] (load next prototype)
//
// MOV prototypeTypeOpnd, [prototypeObjectOpnd + offset(type)] (tail dup TypeIds_Null check)
// CMP [prototypeTypeOpnd + offset(typeId)], TypeIds_Null
// JNE $checkFirstPrototypeLoopTopLabel
//
// $noPrototypeWithEnumerablePropertiesLabel:
//
IR::LabelInstr * noPrototypeWithEnumerablePropertiesLabel = IR::LabelInstr::New(Js::OpCode::Label, func);
IR::RegOpnd * prototypeObjectOpnd = IR::RegOpnd::New(TyMachPtr, func);
IR::RegOpnd * prototypeTypeOpnd = IR::RegOpnd::New(TyMachPtr, func);
IR::IndirOpnd * prototypeTypeIdOpnd = IR::IndirOpnd::New(prototypeTypeOpnd, Js::DynamicType::GetOffsetOfTypeId(), TyUint32, func);
InsertMove(prototypeObjectOpnd, IR::IndirOpnd::New(typeOpnd, Js::DynamicType::GetOffsetOfPrototype(), TyMachPtr, func), instr);
InsertMove(prototypeTypeOpnd, IR::IndirOpnd::New(prototypeObjectOpnd, Js::DynamicObject::GetOffsetOfType(), TyMachPtr, func), instr);
InsertCompareBranch(prototypeTypeIdOpnd, IR::IntConstOpnd::New(Js::TypeId::TypeIds_Null, TyUint32, func), Js::OpCode::BrEq_A, noPrototypeWithEnumerablePropertiesLabel, instr);
IR::LabelInstr * checkFirstPrototypeLoopTopLabel = InsertLoopTopLabel(instr);
Loop * loop = checkFirstPrototypeLoopTopLabel->GetLoop();
loop->regAlloc.liveOnBackEdgeSyms->Set(prototypeObjectOpnd->m_sym->m_id);
loop->regAlloc.liveOnBackEdgeSyms->Set(prototypeTypeOpnd->m_sym->m_id);
InsertCompareBranch(prototypeTypeIdOpnd, IR::IntConstOpnd::New(Js::TypeId::TypeIds_LastStaticType, TyUint32, func), Js::OpCode::BrLe_A, helperLabel, instr);
// No need to do EnsureObjectReady. Defer init type may not have this bit set, so we will go to helper and call EnsureObjectReady then
InsertCompareBranch(IR::IndirOpnd::New(prototypeTypeOpnd, Js::DynamicType::GetOffsetOfHasNoEnumerableProperties(), TyUint8, func),
IR::IntConstOpnd::New(0, TyUint8, func), Js::OpCode::BrEq_A, helperLabel, instr);
GenerateHasObjectArrayCheck(prototypeObjectOpnd, prototypeTypeOpnd, helperLabel, instr);
InsertMove(prototypeObjectOpnd, IR::IndirOpnd::New(prototypeTypeOpnd, Js::DynamicType::GetOffsetOfPrototype(), TyMachPtr, func), instr);
// Tail dup the TypeIds_Null check
InsertMove(prototypeTypeOpnd, IR::IndirOpnd::New(prototypeObjectOpnd, Js::DynamicObject::GetOffsetOfType(), TyMachPtr, func), instr);
InsertCompareBranch(prototypeTypeIdOpnd, IR::IntConstOpnd::New(Js::TypeId::TypeIds_Null, TyUint32, func), Js::OpCode::BrNeq_A, checkFirstPrototypeLoopTopLabel, instr);
instr->InsertBefore(noPrototypeWithEnumerablePropertiesLabel);
// Initialize DynamicObjectPropertyEnumerator fields
IR::Opnd * forInEnumeratorOpnd = instr->GetSrc2();
InsertMove(GetForInEnumeratorFieldOpnd(forInEnumeratorOpnd, Js::ForInObjectEnumerator::GetOffsetOfEnumeratorScriptContext(), TyMachPtr),
LoadScriptContextOpnd(instr), instr);
InsertMove(GetForInEnumeratorFieldOpnd(forInEnumeratorOpnd, Js::ForInObjectEnumerator::GetOffsetOfEnumeratorObject(), TyMachPtr),
objectOpnd, instr);
InsertMove(GetForInEnumeratorFieldOpnd(forInEnumeratorOpnd, Js::ForInObjectEnumerator::GetOffsetOfEnumeratorInitialType(), TyMachPtr),
typeOpnd, instr);
InsertMove(GetForInEnumeratorFieldOpnd(forInEnumeratorOpnd, Js::ForInObjectEnumerator::GetOffsetOfEnumeratorObjectIndex(), TyInt32),
IR::IntConstOpnd::New(Js::Constants::NoBigSlot, TyInt32, func), instr);
IR::RegOpnd * initialPropertyCountOpnd = IR::RegOpnd::New(TyInt32, func);
InsertMove(initialPropertyCountOpnd,
IR::IndirOpnd::New(forInCacheDataOpnd, Js::DynamicObjectPropertyEnumerator::GetOffsetOfCachedDataPropertyCount(), TyInt32, func), instr);
InsertMove(GetForInEnumeratorFieldOpnd(forInEnumeratorOpnd, Js::ForInObjectEnumerator::GetOffsetOfEnumeratorInitialPropertyCount(), TyInt32),
initialPropertyCountOpnd, instr);
InsertMove(GetForInEnumeratorFieldOpnd(forInEnumeratorOpnd, Js::ForInObjectEnumerator::GetOffsetOfEnumeratorEnumeratedCount(), TyInt32),
IR::IntConstOpnd::New(0, TyInt32, func), instr);
InsertMove(GetForInEnumeratorFieldOpnd(forInEnumeratorOpnd, Js::ForInObjectEnumerator::GetOffsetOfEnumeratorFlags(), TyUint8),
IR::IntConstOpnd::New((uint8)(Js::EnumeratorFlags::UseCache | Js::EnumeratorFlags::SnapShotSemantics), TyUint8, func), instr);
InsertMove(GetForInEnumeratorFieldOpnd(forInEnumeratorOpnd, Js::ForInObjectEnumerator::GetOffsetOfEnumeratorCachedData(), TyMachPtr),
forInCacheDataOpnd, instr);
// Initialize rest of the JavascriptStaticEnumerator fields
InsertMove(GetForInEnumeratorFieldOpnd(forInEnumeratorOpnd, Js::ForInObjectEnumerator::GetOffsetOfEnumeratorCurrentEnumerator(), TyMachPtr),
IR::AddrOpnd::NewNull(func), instr);
InsertMove(GetForInEnumeratorFieldOpnd(forInEnumeratorOpnd, Js::ForInObjectEnumerator::GetOffsetOfEnumeratorPrefixEnumerator(), TyMachPtr),
IR::AddrOpnd::NewNull(func), instr);
InsertMove(GetForInEnumeratorFieldOpnd(forInEnumeratorOpnd, Js::ForInObjectEnumerator::GetOffsetOfEnumeratorArrayEnumerator(), TyMachPtr),
IR::AddrOpnd::NewNull(func), instr);
// Initialize rest of the ForInObjectEnumerator fields
InsertMove(GetForInEnumeratorFieldOpnd(forInEnumeratorOpnd, Js::ForInObjectEnumerator::GetOffsetOfShadowData(), TyMachPtr),
IR::AddrOpnd::NewNull(func), instr);
// Initialize can UseJitFastPath = true and enumeratingPrototype = false at the same time.
InsertMove(GetForInEnumeratorFieldOpnd(forInEnumeratorOpnd, Js::ForInObjectEnumerator::GetOffsetOfStates(), TyUint16),
IR::IntConstOpnd::New(1, TyUint16, func, true), instr);
IR::LabelInstr* doneLabel = IR::LabelInstr::New(Js::OpCode::Label, func);
InsertBranch(Js::OpCode::Br, doneLabel, instr);
instr->InsertBefore(helperLabel);
instr->InsertAfter(doneLabel);
}
void
Lowerer::LowerInitForInEnumerator(IR::Instr * instr)
{
Js::EnumeratorCache * forInCache = nullptr;
Func * func = instr->m_func;
if (instr->IsProfiledInstr())
{
uint profileId = instr->AsProfiledInstr()->u.profileId;
forInCache = instr->m_func->GetJITFunctionBody()->GetForInCache(profileId);
Assert(forInCache != nullptr);
if (!func->IsSimpleJit()
#if ENABLE_TTD
&& (func->IsOOPJIT() || !func->GetScriptContext()->GetThreadContext()->IsRuntimeInTTDMode())
//TODO: We will need to enable OOPJIT info to exclude this if we have a TTD Runtime
#endif
)
{
GenerateInitForInEnumeratorFastPath(instr, forInCache);
}
}
IR::RegOpnd * forInEnumeratorRegOpnd = GenerateForInEnumeratorLoad(instr->UnlinkSrc2(), instr);
instr->SetSrc2(forInEnumeratorRegOpnd);
m_lowererMD.LoadHelperArgument(instr, IR::AddrOpnd::New(forInCache, IR::AddrOpndKindForInCache, func));
this->LowerBinaryHelperMem(instr, IR::HelperOp_OP_InitForInEnumerator);
}
IR::LabelInstr *
Lowerer::InsertLoopTopLabel(IR::Instr * insertBeforeInstr)
{
Func * func = this->m_func;
IR::LabelInstr * loopTopLabel = IR::LabelInstr::New(Js::OpCode::Label, func);
loopTopLabel->m_isLoopTop = true;
Loop *loop = JitAnew(func->m_alloc, Loop, func->m_alloc, func);
loopTopLabel->SetLoop(loop);
loop->SetLoopTopInstr(loopTopLabel);
loop->regAlloc.liveOnBackEdgeSyms = AllocatorNew(JitArenaAllocator, func->m_alloc, BVSparse<JitArenaAllocator>, func->m_alloc);
insertBeforeInstr->InsertBefore(loopTopLabel);
return loopTopLabel;
}
IR::Instr *
Lowerer::AddBailoutToHelperCallInstr(IR::Instr * helperCallInstr, BailOutInfo * bailoutInfo, IR::BailOutKind bailoutKind, IR::Instr * primaryBailoutInstr)
{
helperCallInstr = helperCallInstr->ConvertToBailOutInstr(bailoutInfo, bailoutKind);
if (bailoutInfo->bailOutInstr == primaryBailoutInstr)
{
IR::Instr * instrShare = primaryBailoutInstr->ShareBailOut();
LowerBailTarget(instrShare);
}
return helperCallInstr;
}
void
Lowerer::GenerateAuxSlotPtrLoad(IR::PropertySymOpnd *propertySymOpnd, IR::Instr * instrInsert)
{
StackSym * auxSlotPtrSym = propertySymOpnd->GetAuxSlotPtrSym();
Assert(auxSlotPtrSym);
Func * func = instrInsert->m_func;
IR::Opnd *opndIndir = IR::IndirOpnd::New(propertySymOpnd->CreatePropertyOwnerOpnd(func), Js::DynamicObject::GetOffsetOfAuxSlots(), TyMachReg, func);
IR::RegOpnd *regOpnd = IR::RegOpnd::New(auxSlotPtrSym, TyMachReg, func);
regOpnd->SetIsJITOptimizedReg(true);
InsertMove(regOpnd, opndIndir, instrInsert);
}
void
Lowerer::InsertAndLegalize(IR::Instr * instr, IR::Instr* insertBeforeInstr)
{
insertBeforeInstr->InsertBefore(instr);
LowererMD::Legalize(instr);
}
IR::Instr*
Lowerer::InsertObjectCheck(IR::RegOpnd *funcOpnd, IR::Instr *insertBeforeInstr, IR::BailOutKind bailOutKind, BailOutInfo *bailOutInfo)
{
IR::Instr *bailOutIfNotObject = IR::BailOutInstr::New(Js::OpCode::BailOnNotObject, bailOutKind, bailOutInfo, bailOutInfo->bailOutFunc);
// Bailout when funcOpnd is not an object.
bailOutIfNotObject->SetSrc1(funcOpnd);
bailOutIfNotObject->SetByteCodeOffset(insertBeforeInstr);
insertBeforeInstr->InsertBefore(bailOutIfNotObject);
return bailOutIfNotObject;
}
IR::Instr*
Lowerer::InsertFunctionTypeIdCheck(IR::RegOpnd * funcOpnd, IR::Instr* insertBeforeInstr, IR::BailOutKind bailOutKind, BailOutInfo *bailOutInfo)
{
IR::Instr *bailOutIfNotFunction = IR::BailOutInstr::New(Js::OpCode::BailOnNotEqual, bailOutKind, bailOutInfo, bailOutInfo->bailOutFunc);
// functionTypeRegOpnd = Ld functionRegOpnd->type
IR::IndirOpnd *functionTypeIndirOpnd = IR::IndirOpnd::New(funcOpnd, Js::RecyclableObject::GetOffsetOfType(), TyMachPtr, insertBeforeInstr->m_func);
IR::RegOpnd *functionTypeRegOpnd = IR::RegOpnd::New(TyVar, insertBeforeInstr->m_func->GetTopFunc());
IR::Instr *instr = IR::Instr::New(Js::OpCode::Ld_A, functionTypeRegOpnd, functionTypeIndirOpnd, insertBeforeInstr->m_func);
if (instr->m_func->HasByteCodeOffset())
{
instr->SetByteCodeOffset(insertBeforeInstr);
}
insertBeforeInstr->InsertBefore(instr);
CompileAssert(sizeof(Js::TypeId) == sizeof(int32));
// if (functionTypeRegOpnd->typeId != TypeIds_Function) goto $noInlineLabel
// BrNeq_I4 $noInlineLabel, functionTypeRegOpnd->typeId, TypeIds_Function
IR::IndirOpnd *functionTypeIdIndirOpnd = IR::IndirOpnd::New(functionTypeRegOpnd, Js::Type::GetOffsetOfTypeId(), TyInt32, insertBeforeInstr->m_func);
IR::IntConstOpnd *typeIdFunctionConstOpnd = IR::IntConstOpnd::New(Js::TypeIds_Function, TyInt32, insertBeforeInstr->m_func);
bailOutIfNotFunction->SetSrc1(functionTypeIdIndirOpnd);
bailOutIfNotFunction->SetSrc2(typeIdFunctionConstOpnd);
insertBeforeInstr->InsertBefore(bailOutIfNotFunction);
return bailOutIfNotFunction;
}
IR::Instr*
Lowerer::InsertFunctionInfoCheck(IR::RegOpnd * funcOpnd, IR::Instr *insertBeforeInstr, IR::AddrOpnd* inlinedFuncInfo, IR::BailOutKind bailOutKind, BailOutInfo *bailOutInfo)
{
IR::Instr *bailOutIfWrongFuncInfo = IR::BailOutInstr::New(Js::OpCode::BailOnNotEqual, bailOutKind, bailOutInfo, bailOutInfo->bailOutFunc);
// if (VarTo<JavascriptFunction>(r1)->functionInfo != funcInfo) goto noInlineLabel
// BrNeq_A noInlineLabel, r1->functionInfo, funcInfo
IR::IndirOpnd* opndFuncInfo = IR::IndirOpnd::New(funcOpnd, Js::JavascriptFunction::GetOffsetOfFunctionInfo(), TyMachPtr, insertBeforeInstr->m_func);
bailOutIfWrongFuncInfo->SetSrc1(opndFuncInfo);
bailOutIfWrongFuncInfo->SetSrc2(inlinedFuncInfo);
insertBeforeInstr->InsertBefore(bailOutIfWrongFuncInfo);
return bailOutIfWrongFuncInfo;
}
#if DBG
void
Lowerer::LegalizeVerifyRange(IR::Instr * instrStart, IR::Instr * instrLast)
{
FOREACH_INSTR_IN_RANGE(verifyLegalizeInstr, instrStart, instrLast)
{
LowererMD::Legalize<true>(verifyLegalizeInstr);
}
NEXT_INSTR_IN_RANGE;
}
void
Lowerer::ReconcileWithLowererStateOnHelperCall(IR::Instr * callInstr, IR::JnHelperMethod helperMethod)
{
AssertMsg((this->helperCallCheckState & HelperCallCheckState_NoHelperCalls) == 0, "Emitting an helper call when we didn't allow helper calls");
if (HelperMethodAttributes::CanBeReentrant(helperMethod))
{
if (this->helperCallCheckState & HelperCallCheckState_ImplicitCallsBailout)
{
if (!callInstr->HasBailOutInfo() ||
!BailOutInfo::IsBailOutOnImplicitCalls(callInstr->GetBailOutKind()))
{
Output::Print(_u("HelperMethod : %s\n"), IR::GetMethodName(helperMethod));
AssertMsg(false, "Helper call doesn't have BailOutOnImplicitCalls when it should");
}
}
if (!OpCodeAttr::HasImplicitCall(m_currentInstrOpCode) && !OpCodeAttr::OpndHasImplicitCall(m_currentInstrOpCode)
// Special case where we allow support implicit calls, but FromVar says it doesn't have implicit calls
&& m_currentInstrOpCode != Js::OpCode::FromVar
)
{
Output::Print(_u("HelperMethod : %s, OpCode: %s"), IR::GetMethodName(helperMethod), Js::OpCodeUtil::GetOpCodeName(m_currentInstrOpCode));
callInstr->DumpByteCodeOffset();
Output::Print(_u("\n"));
AssertMsg(false, "OpCode and Helper implicit call attribute mismatch");
}
}
}
void
Lowerer::ClearAndSaveImplicitCallCheckOnHelperCallCheckState()
{
this->oldHelperCallCheckState = this->helperCallCheckState;
this->helperCallCheckState = HelperCallCheckState(this->helperCallCheckState & ~HelperCallCheckState_ImplicitCallsBailout);
}
void
Lowerer::RestoreImplicitCallCheckOnHelperCallCheckState()
{
if (this->oldHelperCallCheckState & HelperCallCheckState_ImplicitCallsBailout)
{
this->helperCallCheckState = HelperCallCheckState(this->helperCallCheckState | HelperCallCheckState_ImplicitCallsBailout);
this->oldHelperCallCheckState = HelperCallCheckState_None;
}
}
IR::Instr*
Lowerer::LowerCheckLowerIntBound(IR::Instr * instr)
{
IR::Instr * instrPrev = instr->m_prev;
IR::LabelInstr * continueLabel = IR::LabelInstr::New(Js::OpCode::Label, instr->m_func, false /*isOpHelper*/);
Assert(instr->GetSrc1()->IsInt32() || instr->GetSrc1()->IsUInt32());
InsertCompareBranch(instr->GetSrc1(), instr->GetSrc2(), Js::OpCode::BrGe_A, continueLabel, instr);
IR::Instr* helperCallInstr = IR::Instr::New(LowererMD::MDCallOpcode, instr->m_func);
instr->InsertBefore(helperCallInstr);
m_lowererMD.ChangeToHelperCall(helperCallInstr, IR::HelperIntRangeCheckFailure);
instr->InsertAfter(continueLabel);
instr->Remove();
return instrPrev;
}
IR::Instr*
Lowerer::LowerCheckUpperIntBound(IR::Instr * instr)
{
bool lowerBoundCheckPresent = instr->m_prev->m_opcode == Js::OpCode::CheckLowerIntBound;
IR::Instr * instrPrev = lowerBoundCheckPresent ? instr->m_prev->m_prev : instr->m_prev;
IR::Instr * lowerBoundCheckInstr = lowerBoundCheckPresent ? instr->m_prev : nullptr;
IR::LabelInstr * continueLabel = IR::LabelInstr::New(Js::OpCode::Label, instr->m_func, false /*isOpHelper*/);
IR::LabelInstr * helperLabel = IR::LabelInstr::New(Js::OpCode::Label, instr->m_func, true /*isOpHelper*/);
Assert(instr->GetSrc1()->IsInt32() || instr->GetSrc1()->IsUInt32());
if (lowerBoundCheckInstr)
{
InsertCompareBranch(instr->UnlinkSrc1(), instr->UnlinkSrc2(), Js::OpCode::BrGt_A, helperLabel, instr);
Assert(lowerBoundCheckInstr->GetSrc1()->IsInt32() || lowerBoundCheckInstr->GetSrc1()->IsUInt32());
InsertCompareBranch(lowerBoundCheckInstr->UnlinkSrc1(), lowerBoundCheckInstr->UnlinkSrc2(), Js::OpCode::BrGe_A, continueLabel, instr);
}
else
{
InsertCompareBranch(instr->UnlinkSrc1(), instr->UnlinkSrc2(), Js::OpCode::BrLe_A, continueLabel, instr);
}
instr->InsertBefore(helperLabel);
IR::Instr* helperCallInstr = IR::Instr::New(LowererMD::MDCallOpcode, instr->m_func);
instr->InsertBefore(helperCallInstr);
m_lowererMD.ChangeToHelperCall(helperCallInstr, IR::HelperIntRangeCheckFailure);
instr->InsertAfter(continueLabel);
instr->Remove();
if (lowerBoundCheckInstr)
{
lowerBoundCheckInstr->Remove();
}
return instrPrev;
}
#endif