blob: 275e0bca8c13d449aa62936a4da87a6f3675c83b [file]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#include "Backend.h"
namespace IR
{
void
Instr::Init(Js::OpCode opcode, IRKind kind, Func * func)
{
Assert(!OpCodeAttr::ByteCodeOnly(opcode));
this->m_opcode = opcode;
this->m_kind = kind;
this->m_func = func;
#ifdef BAILOUT_INJECTION
this->bailOutByteCodeLocation = (uint)-1;
#endif
}
uint32
Instr::GetByteCodeOffset() const
{
Assert(m_func->HasByteCodeOffset());
return m_number;
}
void
Instr::SetByteCodeOffset(uint32 offset)
{
Assert(m_func->HasByteCodeOffset());
Assert(m_number == Js::Constants::NoByteCodeOffset);
m_number = offset;
}
void
Instr::SetByteCodeOffset(IR::Instr * instr)
{
SetByteCodeOffset(instr->GetByteCodeOffset());
}
void
Instr::ClearByteCodeOffset()
{
Assert(m_func->HasByteCodeOffset());
m_number = Js::Constants::NoByteCodeOffset;
}
uint32
Instr::GetNumber() const
{
Assert(m_func->HasInstrNumber());
return m_number;
}
void
Instr::SetNumber(uint32 number)
{
Assert(m_func->HasInstrNumber());
m_number = number;
}
bool
Instr::IsPlainInstr() const
{
return this->GetKind() == IR::InstrKindInstr;
}
bool
Instr::DoStackArgsOpt(Func *topFunc) const
{
return this->usesStackArgumentsObject && m_func->IsStackArgsEnabled();
}
bool
Instr::HasTypeCheckBailOut() const
{
return this->HasBailOutInfo() && IR::IsTypeCheckBailOutKind(this->GetBailOutKind());
}
bool
Instr::HasEquivalentTypeCheckBailOut() const
{
return this->HasBailOutInfo() && IR::IsEquivalentTypeCheckBailOutKind(this->GetBailOutKind());
}
void
Instr::ChangeEquivalentToMonoTypeCheckBailOut()
{
Assert(this->HasEquivalentTypeCheckBailOut());
this->SetBailOutKind(IR::EquivalentToMonoTypeCheckBailOutKind(this->GetBailOutKind()));
}
intptr_t
Instr::TryOptimizeInstrWithFixedDataProperty(IR::Instr **pInstr, GlobOpt * globopt)
{
IR::Instr *&instr = *pInstr;
Assert(OpCodeAttr::CanLoadFixedFields(instr->m_opcode));
IR::Opnd * src1 = instr->GetSrc1();
Assert(src1 && src1->IsSymOpnd() && src1->AsSymOpnd()->IsPropertySymOpnd());
IR::PropertySymOpnd * propSymOpnd = src1->AsSymOpnd()->AsPropertySymOpnd();
if (propSymOpnd->HasFixedValue() && !propSymOpnd->IsPoly())
{
intptr_t fixedValue = propSymOpnd->GetFieldValueAsFixedData();
Assert(instr->IsProfiledInstr());
ValueType valType = instr->AsProfiledInstr()->u.FldInfo().valueType;
if (fixedValue && ((Js::TaggedInt::Is(fixedValue) && (valType.IsUninitialized() || valType.IsLikelyInt())) || PHASE_ON1(Js::FixDataVarPropsPhase)))
{
// Change Ld[Root]Fld to CheckFixedFld, which doesn't need a dst.
instr->m_opcode = Js::OpCode::CheckFixedFld;
IR::RegOpnd* dataValueDstOpnd = instr->UnlinkDst()->AsRegOpnd();
if (globopt)
{
globopt->GenerateBailAtOperation(&instr, !propSymOpnd->HasEquivalentTypeSet() ? IR::BailOutFailedFixedFieldTypeCheck : IR::BailOutFailedEquivalentFixedFieldTypeCheck);
}
else
{
instr = instr->ConvertToBailOutInstr(instr, !propSymOpnd->HasEquivalentTypeSet() ? IR::BailOutFailedFixedFieldTypeCheck : IR::BailOutFailedEquivalentFixedFieldTypeCheck);
}
IR::Instr* loadInstr = IR::Instr::NewConstantLoad(dataValueDstOpnd, (intptr_t)fixedValue, valType, instr->m_func);
OUTPUT_VERBOSE_TRACE(Js::UseFixedDataPropsPhase,
_u("FixedFields: Replacing the source (fixed Data prop) with property id %u with 0x%x .\n"),
propSymOpnd->GetPropertyId(), fixedValue);
instr->InsertAfter(loadInstr);
propSymOpnd->SetUsesFixedValue(true);
return fixedValue;
}
}
return 0;
}
///----------------------------------------------------------------------------
///
/// Instr::IsEqual
/// Check if this instruction is the same instruction as compareInstr. Two
/// instructions are equal if kind, opcode, dst, src1 and src2 from both instrs
/// are the same.
///
///----------------------------------------------------------------------------
bool
Instr::IsEqual(IR::Instr *compareInstr) const
{
Assert(compareInstr);
if (this->GetKind() == compareInstr->GetKind()
&& this->m_opcode == compareInstr->m_opcode)
{
IR::Opnd *dst = this->GetDst();
IR::Opnd *src1 = this->GetSrc1();
IR::Opnd *src2 = this->GetSrc2();
IR::Opnd *compareDst = compareInstr->GetDst();
IR::Opnd *compareSrc1 = compareInstr->GetSrc1();
IR::Opnd *compareSrc2 = compareInstr->GetSrc2();
// when both dst and compareDst are null, they are equal, same applies to src1, src2
if ((dst != compareDst) && (!dst || !compareDst || !dst->IsEqual(compareDst)))
{
return false;
}
if ((src1 != compareSrc1) && (!src1 || !compareSrc1 || !src1->IsEqual(compareSrc1)))
{
return false;
}
if ((src2 != compareSrc2) && (!src2 || !compareSrc2 || !src2->IsEqual(compareSrc2)))
{
return false;
}
return true;
}
else
{
return false;
}
}
///----------------------------------------------------------------------------
///
/// Instr::InsertBefore
///
/// Insert 'instr' before 'this' instruction.
///
///----------------------------------------------------------------------------
void
Instr::InsertBefore(Instr *instr)
{
Assert(!instr->IsLinked());
Instr * prevInstr = this->m_prev;
instr->m_prev = prevInstr;
this->m_prev = instr;
if (prevInstr)
{
prevInstr->m_next = instr;
}
instr->m_next = this;
}
///----------------------------------------------------------------------------
///
/// Instr::InsertAfter
///
/// Insert 'instr' after 'this' instruction.
///
///----------------------------------------------------------------------------
void
Instr::InsertAfter(Instr *instr)
{
Assert(!instr->IsLinked());
Instr * nextInstr = this->m_next;
instr->m_next = nextInstr;
this->m_next = instr;
if (nextInstr)
{
nextInstr->m_prev = instr;
}
instr->m_prev = this;
}
///----------------------------------------------------------------------------
///
/// Instr::InsertRangeBefore
///
///----------------------------------------------------------------------------
void
Instr::InsertRangeBefore(Instr *startInstr, Instr *endInstr)
{
Instr * prevInstr = this->m_prev;
startInstr->m_prev = prevInstr;
this->m_prev = endInstr;
if (prevInstr)
{
prevInstr->m_next = startInstr;
}
endInstr->m_next = this;
}
///----------------------------------------------------------------------------
///
/// Instr::InsertMultipleBefore - Inserts multiple instr
///
///----------------------------------------------------------------------------
void
Instr::InsertMultipleBefore(Instr *endInstr)
{
Instr *startInstr = endInstr->m_prev;
if (startInstr) // more than one instruction to insert
{
while (startInstr->m_prev)
{
startInstr = startInstr->m_prev;
}
return this->InsertRangeBefore(startInstr, endInstr);
}
return this->InsertBefore(endInstr);
}
///----------------------------------------------------------------------------
///
/// Instr::InsertRangeAfter
///
///----------------------------------------------------------------------------
void
Instr::InsertRangeAfter(Instr *startInstr, Instr *endInstr)
{
Instr * nextInstr = this->m_next;
endInstr->m_next = nextInstr;
this->m_next = startInstr;
if (nextInstr)
{
nextInstr->m_prev = endInstr;
}
startInstr->m_prev = this;
}
///----------------------------------------------------------------------------
///
/// Instr::InsertMultipleAfter - Inserts multiple instr
///
///----------------------------------------------------------------------------
void
Instr::InsertMultipleAfter(Instr *endInstr)
{
Instr *startInstr = endInstr->m_prev;
if (startInstr) //more than one instruction to insert
{
while (startInstr->m_prev)
{
startInstr = startInstr->m_prev;
}
return this->InsertRangeAfter(startInstr, endInstr);
}
return this->InsertAfter(endInstr);
}
///----------------------------------------------------------------------------
///
/// Instr::Free
///
/// Free this instruction by putting it on a free list.
///
///----------------------------------------------------------------------------
void
Instr::Free()
{
AssertMsg(!this->IsLabelInstr() || !this->AsLabelInstr()->m_hasNonBranchRef,
"Cannot free label with non-branch reference");
switch (this->GetKind())
{
case InstrKindBranch:
{
IR::BranchInstr *branchInstr = this->AsBranchInstr();
branchInstr->ClearTarget();
break;
}
}
IR::Opnd * dstOpnd = this->GetDst();
if (dstOpnd)
{
StackSym * stackSym = dstOpnd->GetStackSym();
if (stackSym)
{
if (stackSym->m_isSingleDef)
{
Assert(!stackSym->m_isEncodedConstant);
if (stackSym->m_instrDef == this)
{
Assert(!dstOpnd->isFakeDst);
if (stackSym->IsConst())
{
// keep the instruction around so we can get the constant value
// from the symbol
return;
}
Assert(this->m_func->GetTopFunc()->allowRemoveBailOutArgInstr || !stackSym->m_isBailOutReferenced);
stackSym->m_instrDef = nullptr;
}
else
{
Assert(dstOpnd->isFakeDst);
}
}
else
{
// Encoded constants are not single-defs anymore, and therefore not isConst.
Assert((!stackSym->m_isConst && stackSym->constantValue == 0)
|| (stackSym->m_isEncodedConstant && stackSym->constantValue != 0));
}
}
}
ClearBailOutInfo();
JitAdelete(this->m_func->m_alloc, this);
}
///----------------------------------------------------------------------------
///
/// Instr::Unlink
///
/// Unlink this instr from the instr list.
///
///----------------------------------------------------------------------------
void
Instr::Unlink()
{
m_prev->m_next = m_next;
if (m_next)
{
m_next->m_prev = m_prev;
}
else
{
Assert(this == this->m_func->m_tailInstr);
}
#if DBG_DUMP
// Transferring the globOptInstrString to the next non-Label Instruction
if(this->globOptInstrString != nullptr && m_next && m_next->globOptInstrString == nullptr && !m_next->IsLabelInstr())
{
m_next->globOptInstrString = this->globOptInstrString;
}
#endif
#if DBG
m_prev = nullptr;
m_next = nullptr;
#endif
}
///----------------------------------------------------------------------------
///
/// Instr::Remove
///
/// Unlink and free this instr.
///
///----------------------------------------------------------------------------
void
Instr::Remove()
{
this->Unlink();
this->Free();
}
void
Instr::SwapOpnds()
{
IR::Opnd *opndTemp = m_src1;
m_src1 = m_src2;
m_src2 = opndTemp;
}
// Copy a vanilla instruction.
Instr *
Instr::Copy()
{
Instr * instrCopy;
if (this->HasBailOutInfo() || this->HasAuxBailOut())
{
instrCopy = BailOutInstr::New(this->m_opcode, this->GetBailOutKind(), this->GetBailOutInfo(), this->m_func);
instrCopy->SetByteCodeOffset(this->GetByteCodeOffset());
if (this->HasAuxBailOut())
{
instrCopy->hasAuxBailOut = true;
instrCopy->SetAuxBailOutKind(this->GetAuxBailOutKind());
}
}
else
{
switch (this->GetKind())
{
case InstrKindInstr:
instrCopy = Instr::New(this->m_opcode, this->m_func);
break;
case InstrKindProfiled:
instrCopy = this->AsProfiledInstr()->CopyProfiledInstr();
break;
case InstrKindJitProfiling:
instrCopy = this->AsJitProfilingInstr()->CopyJitProfiling();
break;
case InstrKindPragma:
instrCopy = this->AsPragmaInstr()->CopyPragma();
break;
default:
instrCopy = nullptr;
AnalysisAssertMsg(UNREACHED, "Copy of other instr kinds NYI");
}
}
Opnd * opnd = this->GetDst();
if (opnd)
{
instrCopy->SetDst(opnd->Copy(this->m_func));
}
opnd = this->GetSrc1();
if (opnd)
{
instrCopy->SetSrc1(opnd->Copy(this->m_func));
opnd = this->GetSrc2();
if (opnd)
{
instrCopy->SetSrc2(opnd->Copy(this->m_func));
}
}
instrCopy->isInlineeEntryInstr = this->isInlineeEntryInstr;
if (this->m_func->DoMaintainByteCodeOffset())
{
instrCopy->SetByteCodeOffset(this->GetByteCodeOffset());
}
instrCopy->usesStackArgumentsObject = this->usesStackArgumentsObject;
return instrCopy;
}
LabelInstr *
LabelInstr::CloneLabel(BOOL fCreate)
{
Func * func = this->m_func;
Cloner * cloner = func->GetCloner();
IR::LabelInstr * instrLabel = nullptr;
AssertMsg(cloner, "Use Func::BeginClone to initialize cloner");
if (cloner->labelMap == nullptr)
{
if (!fCreate)
{
return nullptr;
}
cloner->labelMap = HashTable<LabelInstr*>::New(cloner->alloc, 7);
}
else
{
IR::LabelInstr ** map = cloner->labelMap->Get(this->m_id);
if (map)
{
instrLabel = *map;
}
}
if (instrLabel == nullptr)
{
if (!fCreate)
{
return nullptr;
}
if (this->IsProfiledLabelInstr())
{
instrLabel = IR::ProfiledLabelInstr::New(this->m_opcode, func, this->AsProfiledLabelInstr()->loopImplicitCallFlags, this->AsProfiledLabelInstr()->loopFlags);
#if DBG
instrLabel->AsProfiledLabelInstr()->loopNum = this->AsProfiledLabelInstr()->loopNum;
#endif
}
else
{
instrLabel = IR::LabelInstr::New(this->m_opcode, func, this->isOpHelper);
}
instrLabel->m_isLoopTop = this->m_isLoopTop;
cloner->labelMap->FindOrInsert(instrLabel, this->m_id);
}
return instrLabel;
}
ProfiledLabelInstr::ProfiledLabelInstr(JitArenaAllocator * allocator)
: LabelInstr(allocator)
{
}
ProfiledLabelInstr *
ProfiledLabelInstr::New(Js::OpCode opcode, Func *func, Js::ImplicitCallFlags flags, Js::LoopFlags loopFlags)
{
ProfiledLabelInstr * profiledLabelInstr = JitAnew(func->m_alloc, ProfiledLabelInstr, func->m_alloc);
profiledLabelInstr->Init(opcode, InstrKindProfiledLabel, func, false);
profiledLabelInstr->loopImplicitCallFlags = flags;
profiledLabelInstr->loopFlags = loopFlags;
return profiledLabelInstr;
}
void
BranchInstr::RetargetClonedBranch()
{
IR::LabelInstr * instrLabel = this->m_branchTarget->CloneLabel(false);
if (instrLabel == nullptr)
{
// Jumping outside the cloned range. No retarget.
return;
}
this->SetTarget(instrLabel);
}
PragmaInstr *
PragmaInstr::ClonePragma()
{
return this->CopyPragma();
}
PragmaInstr *
PragmaInstr::CopyPragma()
{
IR::PragmaInstr * instrPragma = IR::PragmaInstr::New(this->m_opcode, 0, this->m_func);
return instrPragma;
}
Instr *
Instr::CloneInstr() const
{
if (this->HasBailOutInfo() || this->HasAuxBailOut())
{
return ((BailOutInstr *)this)->CloneBailOut();
}
IR::Instr *clone = IR::Instr::New(this->m_opcode, this->m_func);
clone->isInlineeEntryInstr = this->isInlineeEntryInstr;
return clone;
}
// Clone a vanilla instruction, replacing single-def StackSym's with new syms where appropriate.
Instr *
Instr::Clone()
{
Func * func = this->m_func;
Cloner *cloner = func->GetCloner();
IR::Instr * instrClone;
IR::Opnd * opnd;
switch (this->GetKind())
{
case InstrKindInstr:
instrClone = this->CloneInstr();
break;
case InstrKindBranch:
instrClone = this->AsBranchInstr()->CloneBranchInstr();
break;
case InstrKindProfiled:
instrClone = this->AsProfiledInstr()->CloneProfiledInstr();
break;
case InstrKindLabel:
case InstrKindProfiledLabel:
instrClone = this->AsLabelInstr()->CloneLabel(true);
break;
case InstrKindPragma:
instrClone = this->AsPragmaInstr()->ClonePragma();
break;
case InstrKindJitProfiling:
instrClone = this->AsJitProfilingInstr()->CloneJitProfiling();
break;
default:
AssertMsg(0, "Clone of this instr kind NYI");
return nullptr;
}
opnd = this->GetDst();
if (opnd)
{
instrClone->SetDst(opnd->CloneDef(func));
}
opnd = this->GetSrc1();
if (opnd)
{
instrClone->SetSrc1(opnd->CloneUse(func));
opnd = this->GetSrc2();
if (opnd)
{
instrClone->SetSrc2(opnd->CloneUse(func));
}
}
if (this->m_func->DoMaintainByteCodeOffset())
{
instrClone->SetByteCodeOffset(this->GetByteCodeOffset());
}
instrClone->usesStackArgumentsObject = this->usesStackArgumentsObject;
cloner->AddInstr(this, instrClone);
return instrClone;
}
// Clone a range of instructions.
Instr *
Instr::CloneRange(
Instr * instrStart, Instr * instrLast, Instr * instrAfter, Lowerer *lowerer, JitArenaAllocator * alloc, bool (*fMapTest)(IR::Instr*), bool clonedInstrGetOrigArgSlotSym)
{
IR::Instr * instrReturn = instrAfter;
Func * topFunc = instrStart->m_func->GetTopFunc();
topFunc->BeginClone(lowerer, alloc);
topFunc->GetCloner()->clonedInstrGetOrigArgSlotSym = clonedInstrGetOrigArgSlotSym;
FOREACH_INSTR_IN_RANGE(instr, instrStart, instrLast)
{
Instr * instrClone = instr->Clone();
instrAfter->InsertAfter(instrClone);
instrAfter = instrClone;
instr->isCloned = true;
if (fMapTest(instrClone))
{
IR::LabelInstr *instrLabel = IR::LabelInstr::New(Js::OpCode::Label, instr->m_func);
instrClone->InsertBefore(instrLabel);
topFunc->GetCloneMap()->Item(instr, instrLabel);
}
}
NEXT_INSTR_IN_RANGE;
topFunc->EndClone();
return instrReturn;
}
///----------------------------------------------------------------------------
///
/// Instr::MoveRangeAfter
///
/// Move a range of instruction after another instruction
///
///----------------------------------------------------------------------------
void
Instr::MoveRangeAfter(Instr * instrStart, Instr * instrLast, Instr * instrAfter)
{
if (instrLast->m_next != nullptr)
{
instrLast->m_next->m_prev = instrStart->m_prev;
}
else
{
instrLast->m_func->m_tailInstr = instrStart->m_prev;
}
if (instrStart->m_prev != nullptr)
{
instrStart->m_prev->m_next = instrLast->m_next;
}
else
{
instrStart->m_func->m_headInstr = instrLast->m_next;
}
instrStart->m_prev = instrAfter;
instrLast->m_next = instrAfter->m_next;
if (instrAfter->m_next != nullptr)
{
instrAfter->m_next->m_prev = instrLast;
}
else
{
instrAfter->m_func->m_tailInstr = instrLast;
}
instrAfter->m_next = instrStart;
}
JitProfilingInstr *
JitProfilingInstr::New(Js::OpCode opcode, Opnd *dstOpnd, Opnd *src1Opnd, Opnd *src2Opnd, Func * func)
{
JitProfilingInstr * profiledInstr = JitProfilingInstr::New(opcode, dstOpnd, src1Opnd, func);
profiledInstr->SetSrc2(src2Opnd);
return profiledInstr;
}
JitProfilingInstr *
JitProfilingInstr::New(Js::OpCode opcode, Opnd *dstOpnd, Opnd *src1Opnd, Func * func)
{
Assert(func->DoSimpleJitDynamicProfile());
JitProfilingInstr * profiledInstr = JitAnew(func->m_alloc, IR::JitProfilingInstr);
profiledInstr->Init(opcode, InstrKindJitProfiling, func);
if (dstOpnd)
{
profiledInstr->SetDst(dstOpnd);
}
if (src1Opnd)
{
profiledInstr->SetSrc1(src1Opnd);
}
#if DBG
profiledInstr->profileId = Js::Constants::NoProfileId;
profiledInstr->arrayProfileId = Js::Constants::NoProfileId;
profiledInstr->inlineCacheIndex = Js::Constants::NoInlineCacheIndex;
Assert(profiledInstr->loopNumber == 0u - 1);
#endif
// default these to false.
profiledInstr->isProfiledReturnCall = false;
profiledInstr->isBeginSwitch = false;
profiledInstr->isNewArray = false;
profiledInstr->isLoopHelper = false;
return profiledInstr;
}
JitProfilingInstr*
JitProfilingInstr::CloneJitProfiling() const
{
// Adapted from Profiled::CloneProfiledInstr. Note that the dst and srcs are not set.
Assert(!(this->HasBailOutInfo() || this->HasAuxBailOut())); // Shouldn't have bailout info in a jitprofiling instr
return this->CopyJitProfiling();
}
JitProfilingInstr*
JitProfilingInstr::CopyJitProfiling() const
{
// Adapted from Profiled::CopyProfiledInstr. Note that the dst and srcs are not set.
IR::JitProfilingInstr * jitProfInstr;
jitProfInstr = JitAnew(this->m_func->m_alloc, IR::JitProfilingInstr);
jitProfInstr->Init(this->m_opcode, InstrKindProfiled, this->m_func);
jitProfInstr->isProfiledReturnCall = this->isProfiledReturnCall;
jitProfInstr->isBeginSwitch = this->isBeginSwitch;
jitProfInstr->isNewArray = this->isNewArray;
jitProfInstr->isLoopHelper = this->isLoopHelper;
jitProfInstr->profileId = this->profileId;
jitProfInstr->arrayProfileId = this->arrayProfileId;
jitProfInstr->inlineCacheIndex = this->inlineCacheIndex;
Assert(jitProfInstr->loopNumber == this->loopNumber);
return jitProfInstr;
}
ProfiledInstr *
ProfiledInstr::New(Js::OpCode opcode, Opnd *dstOpnd, Opnd *src1Opnd, Opnd *src2Opnd, Func * func)
{
ProfiledInstr * profiledInstr = ProfiledInstr::New(opcode, dstOpnd, src1Opnd, func);
profiledInstr->SetSrc2(src2Opnd);
return profiledInstr;
}
ProfiledInstr *
ProfiledInstr::New(Js::OpCode opcode, Opnd *dstOpnd, Opnd *src1Opnd, Func * func)
{
ProfiledInstr * profiledInstr = JitAnew(func->m_alloc, IR::ProfiledInstr);
profiledInstr->Init(opcode, InstrKindProfiled, func);
if (dstOpnd)
{
profiledInstr->SetDst(dstOpnd);
}
if (src1Opnd)
{
profiledInstr->SetSrc1(src1Opnd);
}
profiledInstr->u.ldElemInfo = nullptr;
return profiledInstr;
}
ProfiledInstr *
ProfiledInstr::CloneProfiledInstr() const
{
IR::ProfiledInstr * profiledInstr;
if (this->HasBailOutInfo() || this->HasAuxBailOut())
{
profiledInstr = ((ProfiledBailOutInstr *)this)->CloneBailOut();
profiledInstr->u = this->u;
}
else
{
profiledInstr = this->CopyProfiledInstr();
}
return profiledInstr;
}
ProfiledInstr *
ProfiledInstr::CopyProfiledInstr() const
{
IR::ProfiledInstr * profiledInstr;
profiledInstr = JitAnew(this->m_func->m_alloc, IR::ProfiledInstr);
profiledInstr->Init(this->m_opcode, InstrKindProfiled, this->m_func);
profiledInstr->u = this->u;
return profiledInstr;
}
ByteCodeUsesInstr *
ByteCodeUsesInstr::New(IR::Instr * originalBytecodeInstr)
{
Func* func = originalBytecodeInstr->m_func;
ByteCodeUsesInstr * byteCodeUses = JitAnew(func->m_alloc, IR::ByteCodeUsesInstr);
byteCodeUses->Init(Js::OpCode::ByteCodeUses, InstrKindByteCodeUses, func);
byteCodeUses->byteCodeUpwardExposedUsed = nullptr;
byteCodeUses->propertySymUse = nullptr;
byteCodeUses->SetByteCodeOffset(originalBytecodeInstr);
return byteCodeUses;
}
ByteCodeUsesInstr *
ByteCodeUsesInstr::New(Func * func, uint32 offset)
{
ByteCodeUsesInstr * byteCodeUses = JitAnew(func->m_alloc, IR::ByteCodeUsesInstr);
byteCodeUses->Init(Js::OpCode::ByteCodeUses, InstrKindByteCodeUses, func);
byteCodeUses->byteCodeUpwardExposedUsed = nullptr;
byteCodeUses->propertySymUse = nullptr;
byteCodeUses->SetByteCodeOffset(offset);
return byteCodeUses;
}
const BVSparse<JitArenaAllocator> * ByteCodeUsesInstr::GetByteCodeUpwardExposedUsed() const
{
return this->byteCodeUpwardExposedUsed;
}
// In the case of instances where you would like to add a ByteCodeUses to some sym,
// which doesn't have an operand associated with it (like a block closure sym), use
// this to set it without needing to pass the check for JIT-Optimized registers.
void ByteCodeUsesInstr::SetNonOpndSymbol(uint symId)
{
if (!this->byteCodeUpwardExposedUsed)
{
this->byteCodeUpwardExposedUsed = JitAnew(m_func->m_alloc, BVSparse<JitArenaAllocator>, m_func->m_alloc);
}
this->byteCodeUpwardExposedUsed->Set(symId);
}
// In cases where the operand you're working on may be changed between when you get
// access to it and when you determine that you can set it in the ByteCodeUsesInstr
// set method, cache the values and use this caller.
void ByteCodeUsesInstr::SetRemovedOpndSymbol(bool isJITOptimizedReg, uint symId)
{
if (isJITOptimizedReg)
{
AssertMsg(false, "Tried to add a jit-optimized register to a ByteCodeUses instruction!");
// Although we assert on debug builds, we should actually be ok with release builds
// if we ignore the operand; not ignoring it, however, can cause us to introduce an
// inconsistency in bytecode register lifetimes.
return;
}
if(!this->byteCodeUpwardExposedUsed)
{
this->byteCodeUpwardExposedUsed = JitAnew(m_func->m_alloc, BVSparse<JitArenaAllocator>, m_func->m_alloc);
}
this->byteCodeUpwardExposedUsed->Set(symId);
}
void ByteCodeUsesInstr::Set(IR::Opnd * originalOperand)
{
Assert(originalOperand && originalOperand->GetStackSym());
bool isJITOptimizedReg = originalOperand->GetIsJITOptimizedReg();
SymID symId = originalOperand->GetStackSym()->m_id;
if (isJITOptimizedReg)
{
AssertMsg(false, "Tried to add a jit-optimized register to a ByteCodeUses instruction!");
// Although we assert on debug builds, we should actually be ok with release builds
// if we ignore the operand; not ignoring it, however, can cause us to introduce an
// inconsistency in bytecode register lifetimes.
return;
}
if (!this->byteCodeUpwardExposedUsed)
{
this->byteCodeUpwardExposedUsed = JitAnew(m_func->m_alloc, BVSparse<JitArenaAllocator>, m_func->m_alloc);
}
this->byteCodeUpwardExposedUsed->Set(symId);
}
void ByteCodeUsesInstr::Clear(uint symId)
{
Assert(byteCodeUpwardExposedUsed != nullptr);
this->byteCodeUpwardExposedUsed->Clear(symId);
}
void ByteCodeUsesInstr::SetBV(BVSparse<JitArenaAllocator>* newbv)
{
Assert(byteCodeUpwardExposedUsed == nullptr && newbv != nullptr);
byteCodeUpwardExposedUsed = newbv;
}
// If possible, we want to aggregate with subsequent ByteCodeUses Instructions, so
// that we can do some optimizations in other places where we can simplify args in
// a compare, but still need to generate them for bailouts. Without this, we cause
// problems because we end up with an instruction losing atomicity in terms of its
// bytecode use and generation lifetimes.
void ByteCodeUsesInstr::Aggregate()
{
IR::Instr* scanner = this->m_next;
while (scanner && scanner->m_opcode == Js::OpCode::ByteCodeUses && scanner->GetByteCodeOffset() == this->GetByteCodeOffset() && scanner->GetDst() == nullptr)
{
IR::ByteCodeUsesInstr* target = scanner->AsByteCodeUsesInstr();
Assert(this->m_func == target->m_func);
if (target->byteCodeUpwardExposedUsed)
{
if (this->byteCodeUpwardExposedUsed)
{
this->byteCodeUpwardExposedUsed->Or(target->byteCodeUpwardExposedUsed);
JitAdelete(target->byteCodeUpwardExposedUsed->GetAllocator(), target->byteCodeUpwardExposedUsed);
target->byteCodeUpwardExposedUsed = nullptr;
}
else
{
this->byteCodeUpwardExposedUsed = target->byteCodeUpwardExposedUsed;
target->byteCodeUpwardExposedUsed = nullptr;
}
}
scanner = scanner->m_next;
}
}
BailOutInfo *
Instr::GetBailOutInfo() const
{
Assert(this->HasBailOutInfo() || this->HasAuxBailOut());
switch (this->m_kind)
{
case InstrKindInstr:
return ((BailOutInstr const *)this)->bailOutInfo;
case InstrKindProfiled:
return ((ProfiledBailOutInstr const *)this)->bailOutInfo;
case InstrKindBranch:
return ((BranchBailOutInstr const *)this)->bailOutInfo;
default:
Assert(false);
__assume(false);
}
}
BailOutKind
Instr::GetBailOutKind() const
{
Assert(this->HasBailOutInfo());
switch (this->m_kind)
{
case InstrKindInstr:
return ((BailOutInstr const *)this)->bailOutKind;
case InstrKindProfiled:
return ((ProfiledBailOutInstr const *)this)->bailOutKind;
case InstrKindBranch:
return ((BranchBailOutInstr const *)this)->bailOutKind;
default:
Assert(false);
return BailOutInvalid;
}
}
BailOutKind
Instr::GetBailOutKindNoBits() const
{
return GetBailOutKind() & ~IR::BailOutKindBits;
}
BailOutKind
Instr::GetAuxBailOutKind() const
{
Assert(this->HasAuxBailOut());
switch (this->m_kind)
{
case InstrKindInstr:
return ((BailOutInstr const *)this)->auxBailOutKind;
case InstrKindProfiled:
return ((ProfiledBailOutInstr const *)this)->auxBailOutKind;
case InstrKindBranch:
return ((BranchBailOutInstr const *)this)->auxBailOutKind;
default:
Assert(false);
return BailOutInvalid;
}
}
void Instr::SetBailOutKind(const IR::BailOutKind bailOutKind)
{
Assert(this->HasBailOutInfo());
Assert(bailOutKind != IR::BailOutInvalid);
this->SetBailOutKind_NoAssert(bailOutKind);
}
// Helper to set bail out kind, doesn't assert.
void Instr::SetBailOutKind_NoAssert(const IR::BailOutKind bailOutKind)
{
Assert(IsValidBailOutKindAndBits(bailOutKind));
switch (this->m_kind)
{
case InstrKindInstr:
((BailOutInstr *)this)->bailOutKind = bailOutKind;
break;
case InstrKindProfiled:
((ProfiledBailOutInstr *)this)->bailOutKind = bailOutKind;
break;
case InstrKindBranch:
((BranchBailOutInstr *)this)->bailOutKind = bailOutKind;
break;
default:
Assert(false);
__assume(false);
}
}
void Instr::SetAuxBailOutKind(const IR::BailOutKind bailOutKind)
{
switch (this->m_kind)
{
case InstrKindInstr:
((BailOutInstr *)this)->auxBailOutKind = bailOutKind;
break;
case InstrKindProfiled:
((ProfiledBailOutInstr *)this)->auxBailOutKind = bailOutKind;
break;
case InstrKindBranch:
((BranchBailOutInstr *)this)->auxBailOutKind = bailOutKind;
break;
default:
Assert(false);
__assume(false);
}
}
BailOutInfo *
Instr::UnlinkBailOutInfo()
{
BailOutInfo *bailOutInfo;
Assert(this->HasBailOutInfo() || this->HasAuxBailOut());
switch (this->m_kind)
{
case InstrKindInstr:
bailOutInfo = ((BailOutInstr const *)this)->bailOutInfo;
((BailOutInstr *)this)->bailOutInfo = nullptr;
break;
case InstrKindProfiled:
bailOutInfo = ((ProfiledBailOutInstr const *)this)->bailOutInfo;
((ProfiledBailOutInstr *)this)->bailOutInfo = nullptr;
break;
case InstrKindBranch:
bailOutInfo = ((BranchBailOutInstr const *)this)->bailOutInfo;
((BranchBailOutInstr *)this)->bailOutInfo = nullptr;
break;
default:
Assert(false);
return nullptr;
}
Assert(bailOutInfo);
#if 0
if (bailOutInfo->bailOutInstr == this)
{
bailOutInfo->bailOutInstr = nullptr;
}
#endif
this->hasBailOutInfo = false;
this->hasAuxBailOut = false;
return bailOutInfo;
}
bool
Instr::ReplaceBailOutInfo(BailOutInfo *newBailOutInfo)
{
BailOutInfo *oldBailOutInfo;
bool deleteOld = false;
#if DBG
newBailOutInfo->wasCopied = true;
#endif
Assert(this->HasBailOutInfo() || this->HasAuxBailOut());
switch (this->m_kind)
{
case InstrKindInstr:
oldBailOutInfo = ((BailOutInstr *)this)->bailOutInfo;
((BailOutInstr *)this)->bailOutInfo = newBailOutInfo;
break;
case InstrKindProfiled:
oldBailOutInfo = ((ProfiledBailOutInstr *)this)->bailOutInfo;
((ProfiledBailOutInstr *)this)->bailOutInfo = newBailOutInfo;
break;
case InstrKindBranch:
AssertMsg(!this->HasBailOutInfo() && this->HasAuxBailOut(), "ReplaceBailOutInfo is not used with InstrKindBranch for non-aux bailout");
oldBailOutInfo = ((BranchBailOutInstr *)this)->bailOutInfo;
((BranchBailOutInstr *)this)->bailOutInfo = newBailOutInfo;
break;
default:
Assert(false);
__assume(UNREACHED);
}
Assert(!oldBailOutInfo->wasCloned && !oldBailOutInfo->wasCopied);
if (oldBailOutInfo->bailOutInstr == this)
{
JitArenaAllocator * alloc = this->m_func->m_alloc;
oldBailOutInfo->Clear(alloc);
JitAdelete(alloc, oldBailOutInfo);
deleteOld = true;
}
return deleteOld;
}
IR::Instr *Instr::ShareBailOut()
{
BailOutInfo *const bailOutInfo = GetBailOutInfo();
bailOutInfo->bailOutInstr = nullptr;
#if DBG
bailOutInfo->wasCopied = true;
#endif
IR::Instr *const sharedBail =
IR::BailOutInstr::New(Js::OpCode::BailTarget, IR::BailOutShared, bailOutInfo, bailOutInfo->bailOutFunc);
sharedBail->SetByteCodeOffset(this);
InsertAfter(sharedBail);
Assert(bailOutInfo->bailOutInstr == sharedBail);
return sharedBail;
}
void
Instr::UnlinkStartCallFromBailOutInfo(IR::Instr *endInstr) const
{
#ifdef _M_IX86
// The StartCall instruction is being deleted, or is being moved and may later be deleted,
// so remove its references from bailouts in the given range.
// This only happens during cloning, which is rare, and only across the range of instructions
// that evaluate outgoing arguments, which is long only in synthetic cases.
Assert(this->m_opcode == Js::OpCode::StartCall);
if (!this->m_func->hasBailout)
{
return;
}
FOREACH_INSTR_IN_RANGE(instr, this->m_next, endInstr)
{
if (instr->HasBailOutInfo())
{
BailOutInfo *bailOutInfo = instr->GetBailOutInfo();
bailOutInfo->UnlinkStartCall(this);
}
}
NEXT_INSTR_IN_RANGE;
#endif
}
Opnd *Instr::FindCallArgumentOpnd(const Js::ArgSlot argSlot, IR::Instr * *const ownerInstrRef)
{
Assert(OpCodeAttr::CallInstr(m_opcode));
Assert(argSlot != static_cast<Js::ArgSlot>(0));
IR::Instr *argInstr = this;
Assert(argInstr->GetSrc2());
Assert(argInstr->GetSrc2()->IsSymOpnd());
do
{
StackSym *const linkSym = argInstr->GetSrc2()->AsSymOpnd()->m_sym->AsStackSym();
Assert(linkSym->IsSingleDef());
Assert(linkSym->IsArgSlotSym());
argInstr = linkSym->m_instrDef;
Assert(argInstr->GetSrc2());
if(argInstr->m_opcode == Js::OpCode::ArgOut_A_InlineSpecialized)
{
// This is a fake ArgOut, skip it
continue;
}
if(linkSym->GetArgSlotNum() == argSlot)
{
if(ownerInstrRef)
{
*ownerInstrRef = argInstr;
}
return argInstr->GetSrc1();
}
} while(argInstr->GetSrc2()->IsSymOpnd());
return nullptr;
}
bool
Instr::FetchOperands(_Out_writes_(argsOpndLength) IR::Opnd **argsOpnd, uint argsOpndLength)
{
return this->ForEachCallDirectArgOutInstrBackward([&](IR::Instr *argOutInstr, uint argNum)
{
argsOpnd[argNum] = argOutInstr->GetSrc1();
return argNum == 0;
}, argsOpndLength);
}
bool Instr::ShouldCheckForNegativeZero() const
{
return !ignoreNegativeZero;
}
bool Instr::IsDstNotAlwaysConvertedToInt32() const
{
return !dstIsAlwaysConvertedToInt32;
}
bool Instr::IsDstNotAlwaysConvertedToNumber() const
{
return !dstIsAlwaysConvertedToNumber;
}
bool Instr::ShouldCheckForIntOverflow() const
{
return ShouldCheckFor32BitOverflow() || ShouldCheckForNon32BitOverflow();
}
bool Instr::ShouldCheckFor32BitOverflow() const
{
return !(ignoreIntOverflow || ignoreIntOverflowInRange);
}
bool Instr::ShouldCheckForNon32BitOverflow() const
{
return ignoreOverflowBitCount != 32;
}
template <typename InstrType> struct IRKindMap;
template <> struct IRKindMap<IR::Instr> { static const IRKind InstrKind = InstrKindInstr; };
template <> struct IRKindMap<IR::ProfiledInstr> { static const IRKind InstrKind = InstrKindProfiled; };
template <> struct IRKindMap<IR::BranchInstr> { static const IRKind InstrKind = InstrKindBranch; };
template <typename InstrType>
BailOutInstrTemplate<InstrType> *
BailOutInstrTemplate<InstrType>::New(Js::OpCode opcode, BailOutKind kind, IR::Instr * bailOutTarget, Func * func)
{
Assert(func == bailOutTarget->m_func);
BailOutInfo * bailOutInfo = JitAnew(func->m_alloc, BailOutInfo, bailOutTarget->GetByteCodeOffset(), func);
#if ENABLE_DEBUG_CONFIG_OPTIONS
bailOutInfo->bailOutOpcode = opcode;
#endif
return BailOutInstrTemplate::New(opcode, kind, bailOutInfo, func);
}
template <typename InstrType>
BailOutInstrTemplate<InstrType> *
BailOutInstrTemplate<InstrType>::New(Js::OpCode opcode, IR::Opnd *dst, BailOutKind kind, IR::Instr * bailOutTarget, Func * func)
{
BailOutInstrTemplate *instr = BailOutInstrTemplate::New(opcode, kind, bailOutTarget, func);
instr->SetDst(dst);
return instr;
}
template <typename InstrType>
BailOutInstrTemplate<InstrType> *
BailOutInstrTemplate<InstrType>::New(Js::OpCode opcode, IR::Opnd *dst, IR::Opnd *src1, BailOutKind kind, IR::Instr * bailOutTarget, Func * func)
{
BailOutInstrTemplate *instr = BailOutInstrTemplate::New(opcode, dst, kind, bailOutTarget, func);
instr->SetSrc1(src1);
return instr;
}
template <typename InstrType>
BailOutInstrTemplate<InstrType> *
BailOutInstrTemplate<InstrType>::New(Js::OpCode opcode, IR::Opnd *dst, IR::Opnd *src1, IR::Opnd *src2, BailOutKind kind, IR::Instr * bailOutTarget, Func * func)
{
BailOutInstrTemplate *instr = BailOutInstrTemplate::New(opcode, dst, src1, kind, bailOutTarget, func);
instr->SetSrc2(src2);
return instr;
}
template <typename InstrType>
BailOutInstrTemplate<InstrType> *
BailOutInstrTemplate<InstrType>::New(Js::OpCode opcode, BailOutKind kind, BailOutInfo * bailOutInfo, Func * func)
{
Assert(func == bailOutInfo->bailOutFunc);
Assert(IsValidBailOutKindAndBits(kind));
BailOutInstrTemplate * bailOutInstr = JitAnew(func->m_alloc, BailOutInstrTemplate);
bailOutInstr->Init(opcode, IRKindMap<InstrType>::InstrKind, func);
#if ENABLE_DEBUG_CONFIG_OPTIONS
bailOutInfo->bailOutOpcode = opcode;
#endif
bailOutInstr->bailOutInfo = bailOutInfo;
bailOutInstr->bailOutKind = kind;
bailOutInstr->auxBailOutKind = BailOutInvalid;
if (bailOutInfo->bailOutInstr == nullptr)
{
bailOutInfo->bailOutInstr = bailOutInstr;
}
else if (bailOutInfo->sharedBailOutKind)
{
if (bailOutInfo->bailOutInstr->HasBailOutInfo())
{
bailOutInfo->sharedBailOutKind = bailOutInfo->bailOutInstr->GetBailOutKind() == kind;
}
else
{
// Rare cases where we have already generated the bailout record. Unlikely they share the same bailout kind as this is hit only when we try to
// share bailout in lowerer. See Instr::ShareBailOut.
bailOutInfo->sharedBailOutKind = false;
}
}
func->hasBailout = true;
// Indicate that the function has bailout instructions
// This information is used to determine whether to free jitted loop bodies
// If the function has bailout instructions, we keep the loop bodies alive
// in case we bail out to the interpreter, so that we can reuse the jitted
// loop bodies
func->GetJITOutput()->SetHasBailoutInstr(true);
return bailOutInstr;
}
template <typename InstrType>
BailOutInstrTemplate<InstrType> *
BailOutInstrTemplate<InstrType>::CloneBailOut() const
{
Assert(this->m_func->hasBailout);
Assert(!this->bailOutInfo->wasCloned);
BailOutInstrTemplate * bailOutInstr = BailOutInstrTemplate::New(this->m_opcode, this->bailOutKind, this->bailOutInfo, this->bailOutInfo->bailOutFunc);
bailOutInstr->hasAuxBailOut = this->hasAuxBailOut;
bailOutInstr->auxBailOutKind = this->auxBailOutKind;
bailOutInstr->bailOutInfo->wasCloned = true;
// the new copy is in the slow path and generate the real bailout
bailOutInstr->bailOutInfo->bailOutInstr = bailOutInstr;
return bailOutInstr;
}
template class BailOutInstrTemplate<IR::Instr>;
///----------------------------------------------------------------------------
///
/// EntryInstr::New
///
/// Create an EntryInstr.
///
///----------------------------------------------------------------------------
EntryInstr *
EntryInstr::New(Js::OpCode opcode, Func *func)
{
EntryInstr * entryInstr;
entryInstr = JitAnew(func->m_alloc, IR::EntryInstr);
entryInstr->Init(opcode, InstrKindEntry, func);
return entryInstr;
}
///----------------------------------------------------------------------------
///
/// ExitInstr::New
///
/// Create an ExitInstr.
///
///----------------------------------------------------------------------------
ExitInstr *
ExitInstr::New(Js::OpCode opcode, Func *func)
{
ExitInstr * exitInstr;
exitInstr = JitAnew(func->m_alloc, IR::ExitInstr);
exitInstr->Init(opcode, InstrKindExit, func);
return exitInstr;
}
///----------------------------------------------------------------------------
///
/// LabelInstr::New
///
/// Create a label.
///
///----------------------------------------------------------------------------
LabelInstr *
LabelInstr::New(Js::OpCode opcode, Func *func, bool isOpHelper)
{
LabelInstr * labelInstr;
labelInstr = JitAnew(func->m_alloc, IR::LabelInstr, func->m_alloc);
labelInstr->Init(opcode, InstrKindLabel, func, isOpHelper);
return labelInstr;
}
void
LabelInstr::Init(Js::OpCode opcode, IRKind kind, Func *func, bool isOpHelper)
{
// Pass in the region when this is called from anywhere between the Lowerer and EHBailoutPatchUp code?
__super::Init(opcode, kind, func);
this->isOpHelper = isOpHelper;
this->m_pc.pc = nullptr;
this->m_id = ++(func->GetTopFunc()->m_labelCount);
AssertMsg(this->m_id != 0, "Label numbers wrapped around?");
}
///----------------------------------------------------------------------------
///
/// LabelInstr::AddLabelRef
///
/// Add a branch to the list of label references.
///
///----------------------------------------------------------------------------
void
LabelInstr::AddLabelRef(BranchInstr *branchRef)
{
this->labelRefs.Prepend(branchRef);
}
///----------------------------------------------------------------------------
///
/// LabelInstr::RemoveLabelRef
///
/// Remove a branch from the list of label references.
///
///----------------------------------------------------------------------------
void
LabelInstr::RemoveLabelRef(BranchInstr *branchRef)
{
FOREACH_SLISTCOUNTED_ENTRY_EDITING(BranchInstr*, branchEntry, &this->labelRefs, iter)
{
if (branchEntry == branchRef)
{
iter.RemoveCurrent();
return;
}
} NEXT_SLISTCOUNTED_ENTRY_EDITING;
AssertMsg(UNREACHED, "Branch not found on labelRef list");
}
///----------------------------------------------------------------------------
///
/// BranchInstr::New
///
/// Create a Br (unconditional) BranchInstr.
///
///----------------------------------------------------------------------------
BranchInstr *
BranchInstr::New(Js::OpCode opcode, LabelInstr * branchTarget, Func *func)
{
BranchInstr * branchInstr;
branchInstr = JitAnew(func->m_alloc, IR::BranchInstr);
branchInstr->Init(opcode, InstrKindBranch, func);
branchInstr->SetTarget(branchTarget);
branchInstr->m_dst = nullptr;
branchInstr->m_src1 = nullptr;
branchInstr->m_src2 = nullptr;
branchInstr->m_byteCodeReg = Js::Constants::NoRegister;
#if DBG
branchInstr->m_isHelperToNonHelperBranch = false;
#endif
return branchInstr;
}
///----------------------------------------------------------------------------
///
/// BranchInstr::New
///
/// Create a BrB BranchInstr (1-operand conditional branch).
///
///----------------------------------------------------------------------------
BranchInstr *
BranchInstr::New(Js::OpCode opcode, LabelInstr * branchTarget, Opnd *srcOpnd, Func *func)
{
BranchInstr * branchInstr;
branchInstr = BranchInstr::New(opcode, branchTarget, func);
branchInstr->SetSrc1(srcOpnd);
return branchInstr;
}
///----------------------------------------------------------------------------
///
/// BranchInstr::New
///
/// Create a BrBReturn BranchInstr (1-operand conditional branch. If condition fails return the result of the condition).
///
///----------------------------------------------------------------------------
BranchInstr *
BranchInstr::New(Js::OpCode opcode, Opnd* destOpnd, LabelInstr * branchTarget, Opnd *srcOpnd, Func *func)
{
BranchInstr * branchInstr;
branchInstr = BranchInstr::New(opcode, branchTarget, func);
branchInstr->SetSrc1(srcOpnd);
branchInstr->SetDst(destOpnd);
return branchInstr;
}
///----------------------------------------------------------------------------
///
/// BranchInstr::New
///
/// Create a BrReg2 BranchInstr (2-operand conditional branch).
///
///----------------------------------------------------------------------------
BranchInstr *
BranchInstr::New(Js::OpCode opcode, LabelInstr * branchTarget, Opnd *src1Opnd, Opnd *src2Opnd, Func *func)
{
BranchInstr * branchInstr;
branchInstr = BranchInstr::New(opcode, branchTarget, src1Opnd, func);
branchInstr->SetSrc2(src2Opnd);
return branchInstr;
}
///----------------------------------------------------------------------------
///
/// MultiBranchInstr::New
///
/// Create a MultiBr BranchInstr (unconditional multi branch).
///
///----------------------------------------------------------------------------
MultiBranchInstr *
MultiBranchInstr::New(Js::OpCode opcode, IR::Opnd * srcOpnd, Func * func)
{
MultiBranchInstr * multiBranchInstr;
multiBranchInstr = MultiBranchInstr::New(opcode, func);
multiBranchInstr->SetSrc1(srcOpnd);
return multiBranchInstr;
}
MultiBranchInstr *
MultiBranchInstr::New(Js::OpCode opcode, Func * func)
{
JitArenaAllocator * m_funcAlloc = func->m_alloc;
MultiBranchInstr * multiBranchInstr;
multiBranchInstr = JitAnew(m_funcAlloc, IR::MultiBranchInstr);
multiBranchInstr->Init(opcode, InstrKindBranch, func);
return multiBranchInstr;
}
bool
BranchInstr::ReplaceTarget(IR::LabelInstr * oldLabelInstr, IR::LabelInstr * newLabelInstr)
{
if (this->IsMultiBranch())
{
return this->AsMultiBrInstr()->ReplaceTarget(oldLabelInstr, newLabelInstr);
}
if (this->GetTarget() == oldLabelInstr)
{
this->SetTarget(newLabelInstr);
return true;
}
return false;
}
bool
MultiBranchInstr::ReplaceTarget(IR::LabelInstr * oldLabelInstr, IR::LabelInstr * newLabelInstr)
{
Assert(this->IsMultiBranch());
bool remapped = false;
this->UpdateMultiBrLabels([=, &remapped](IR::LabelInstr * targetLabel) -> IR::LabelInstr *
{
if (targetLabel == oldLabelInstr)
{
this->ChangeLabelRef(targetLabel, newLabelInstr);
remapped = true;
return newLabelInstr;
}
return targetLabel;
});
return remapped;
}
void
MultiBranchInstr::ClearTarget()
{
Assert(IsMultiBranch());
MapMultiBrLabels([&](LabelInstr *const targetLabel)
{
ChangeLabelRef(targetLabel, nullptr);
});
m_branchTargets = nullptr;
}
BranchInstr *
BranchInstr::CloneBranchInstr() const
{
AssertMsg(!this->IsMultiBranch(),"Cloning Not supported for MultiBranchInstr");
Func * func = this->m_func;
// See if the target has already been cloned.
IR::LabelInstr * instrLabel = this->GetTarget()->CloneLabel(false);
if (instrLabel == nullptr)
{
// We didn't find a clone for this label.
// We'll go back and retarget the cloned branch if the target turns up in the cloned range.
instrLabel = this->GetTarget();
func->GetCloner()->fRetargetClonedBranch = TRUE;
}
return IR::BranchInstr::New(this->m_opcode, instrLabel, func);
}
void
BranchInstr::Invert()
{
/*
* If one of the operands to a relational operator is 'undefined', the result
* is always false. Don't invert such branches as they result in a jump to
* the wrong target.
*/
switch (this->m_opcode)
{
case Js::OpCode::BrGt_A:
this->m_opcode = Js::OpCode::BrNotGt_A;
break;
case Js::OpCode::BrNotGt_A:
this->m_opcode = Js::OpCode::BrGt_A;
break;
case Js::OpCode::BrGe_A:
this->m_opcode = Js::OpCode::BrNotGe_A;
break;
case Js::OpCode::BrNotGe_A:
this->m_opcode = Js::OpCode::BrGe_A;
break;
case Js::OpCode::BrLt_A:
this->m_opcode = Js::OpCode::BrNotLt_A;
break;
case Js::OpCode::BrNotLt_A:
this->m_opcode = Js::OpCode::BrLt_A;
break;
case Js::OpCode::BrLe_A:
this->m_opcode = Js::OpCode::BrNotLe_A;
break;
case Js::OpCode::BrNotLe_A:
this->m_opcode = Js::OpCode::BrLe_A;
break;
case Js::OpCode::BrEq_A:
this->m_opcode = Js::OpCode::BrNotEq_A;
break;
case Js::OpCode::BrNotEq_A:
this->m_opcode = Js::OpCode::BrEq_A;
break;
case Js::OpCode::BrNeq_A:
this->m_opcode = Js::OpCode::BrNotNeq_A;
break;
case Js::OpCode::BrNotNeq_A:
this->m_opcode = Js::OpCode::BrNeq_A;
break;
case Js::OpCode::Br:
break;
case Js::OpCode::BrFalse_A:
this->m_opcode = Js::OpCode::BrTrue_A;
break;
case Js::OpCode::BrTrue_A:
this->m_opcode = Js::OpCode::BrFalse_A;
break;
case Js::OpCode::BrSrEq_A:
this->m_opcode = Js::OpCode::BrSrNotEq_A;
break;
case Js::OpCode::BrSrNotEq_A:
this->m_opcode = Js::OpCode::BrSrEq_A;
break;
case Js::OpCode::BrSrNeq_A:
this->m_opcode = Js::OpCode::BrSrNotNeq_A;
break;
case Js::OpCode::BrSrNotNeq_A:
this->m_opcode = Js::OpCode::BrSrNeq_A;
break;
case Js::OpCode::BrOnHasProperty:
this->m_opcode = Js::OpCode::BrOnNoProperty;
break;
case Js::OpCode::BrOnNoProperty:
this->m_opcode = Js::OpCode::BrOnHasProperty;
break;
case Js::OpCode::BrTrue_I4:
this->m_opcode = Js::OpCode::BrFalse_I4;
break;
case Js::OpCode::BrFalse_I4:
this->m_opcode = Js::OpCode::BrTrue_I4;
break;
case Js::OpCode::BrEq_I4:
this->m_opcode = Js::OpCode::BrNeq_I4;
break;
case Js::OpCode::BrNeq_I4:
this->m_opcode = Js::OpCode::BrEq_I4;
break;
case Js::OpCode::BrGe_I4:
this->m_opcode = Js::OpCode::BrLt_I4;
break;
case Js::OpCode::BrGt_I4:
this->m_opcode = Js::OpCode::BrLe_I4;
break;
case Js::OpCode::BrLe_I4:
this->m_opcode = Js::OpCode::BrGt_I4;
break;
case Js::OpCode::BrLt_I4:
this->m_opcode = Js::OpCode::BrGe_I4;
break;
case Js::OpCode::BrUnGe_A:
this->m_opcode = Js::OpCode::BrUnLt_A;
break;
case Js::OpCode::BrUnGt_A:
this->m_opcode = Js::OpCode::BrUnLe_A;
break;
case Js::OpCode::BrUnLe_A:
this->m_opcode = Js::OpCode::BrUnGt_A;
break;
case Js::OpCode::BrUnLt_A:
this->m_opcode = Js::OpCode::BrUnGe_A;
break;
case Js::OpCode::BrUnGe_I4:
this->m_opcode = Js::OpCode::BrUnLt_I4;
break;
case Js::OpCode::BrUnGt_I4:
this->m_opcode = Js::OpCode::BrUnLe_I4;
break;
case Js::OpCode::BrUnLe_I4:
this->m_opcode = Js::OpCode::BrUnGt_I4;
break;
case Js::OpCode::BrUnLt_I4:
this->m_opcode = Js::OpCode::BrUnGe_I4;
break;
case Js::OpCode::BrOnEmpty:
this->m_opcode = Js::OpCode::BrOnNotEmpty;
break;
case Js::OpCode::BrOnNotEmpty:
this->m_opcode = Js::OpCode::BrOnEmpty;
break;
case Js::OpCode::BrHasSideEffects:
this->m_opcode = Js::OpCode::BrNotHasSideEffects;
break;
case Js::OpCode::BrFncEqApply:
this->m_opcode = Js::OpCode::BrFncNeqApply;
break;
case Js::OpCode::BrFncNeqApply:
this->m_opcode = Js::OpCode::BrFncEqApply;
break;
case Js::OpCode::BrNotHasSideEffects:
this->m_opcode = Js::OpCode::BrHasSideEffects;
break;
case Js::OpCode::BrNotAddr_A:
this->m_opcode = Js::OpCode::BrAddr_A;
break;
case Js::OpCode::BrAddr_A:
this->m_opcode = Js::OpCode::BrNotAddr_A;
break;
case Js::OpCode::BrFncCachedScopeEq:
this->m_opcode = Js::OpCode::BrFncCachedScopeNeq;
break;
case Js::OpCode::BrFncCachedScopeNeq:
this->m_opcode = Js::OpCode::BrFncCachedScopeEq;
break;
case Js::OpCode::BrOnException:
this->m_opcode = Js::OpCode::BrOnNoException;
break;
default:
AssertMsg(UNREACHED, "Unhandled branch");
}
}
bool
BranchInstr::IsLoopTail(Func * func)
{
Assert(func->isPostLower);
IR::LabelInstr * target = this->GetTarget();
if (!target->m_isLoopTop)
{
return false;
}
IR::BranchInstr * lastBranchInstr = nullptr;
uint32 lastBranchNum = 0;
FOREACH_SLISTCOUNTED_ENTRY(IR::BranchInstr *, ref, &target->labelRefs)
{
if (ref->GetNumber() > lastBranchNum)
{
lastBranchInstr = ref;
lastBranchNum = lastBranchInstr->GetNumber();
}
}
NEXT_SLISTCOUNTED_ENTRY;
if (this == lastBranchInstr)
{
return true;
}
return false;
}
///----------------------------------------------------------------------------
///
/// PragmaInstr::New
///
/// Create a PragmaInstr.
///
///----------------------------------------------------------------------------
PragmaInstr *
PragmaInstr::New(Js::OpCode opcode, uint32 index, Func *func)
{
PragmaInstr * pragmaInstr;
pragmaInstr = JitAnew(func->m_alloc, IR::PragmaInstr);
pragmaInstr->Init(opcode, InstrKindPragma, func);
pragmaInstr->m_statementIndex = index;
return pragmaInstr;
}
///----------------------------------------------------------------------------
///
/// PragmaInstr::Instr
///
/// Record the information encoded in the pragma
///
///----------------------------------------------------------------------------
#if DBG_DUMP | defined(VTUNE_PROFILING)
void
PragmaInstr::Record(uint32 nativeBufferOffset)
{
// Currently the only pragma instructions are for Source Info
Assert(this->m_func->GetTopFunc()->DoRecordNativeMap());
if (!m_func->IsOOPJIT())
{
m_func->GetTopFunc()->GetInProcJITEntryPointInfo()->RecordNativeMap(nativeBufferOffset, m_statementIndex);
}
}
#endif
///----------------------------------------------------------------------------
///
/// Instr::New
///
/// Create an Instr.
///
///----------------------------------------------------------------------------
Instr *
Instr::New(Js::OpCode opcode, Func *func)
{
Instr * instr;
instr = JitAnew(func->m_alloc, IR::Instr);
instr->Init(opcode, InstrKindInstr, func);
return instr;
}
///----------------------------------------------------------------------------
///
/// Instr::New
///
/// Create an Instr with dst.
///
///----------------------------------------------------------------------------
Instr *
Instr::New(Js::OpCode opcode, Opnd *dstOpnd, Func *func)
{
Instr * instr;
instr = Instr::New(opcode, func);
instr->SetDst(dstOpnd);
return instr;
}
///----------------------------------------------------------------------------
///
/// Instr::New
///
/// Create an Instr with dst and a src.
///
///----------------------------------------------------------------------------
Instr *
Instr::New(Js::OpCode opcode, Opnd *dstOpnd, Opnd *src1Opnd, Func *func)
{
Instr * instr;
instr = Instr::New(opcode, dstOpnd, func);
instr->SetSrc1(src1Opnd);
return instr;
}
///----------------------------------------------------------------------------
///
/// Instr::New
///
/// Create an Instr with dst and 2 srcs.
///
///----------------------------------------------------------------------------
Instr *
Instr::New(Js::OpCode opcode, Opnd *dstOpnd, Opnd *src1Opnd, Opnd *src2Opnd, Func *func)
{
Instr * instr;
instr = Instr::New(opcode, dstOpnd, src1Opnd, func);
instr->SetSrc2(src2Opnd);
return instr;
}
///----------------------------------------------------------------------------
///
/// Instr::SetDst
///
/// Set the dst for 'this' instruction. Automatically maintain isSingleDef
/// and instrDef of stackSyms.
///
///----------------------------------------------------------------------------
Opnd *
Instr::SetDst(Opnd * newDst)
{
AssertMsg(newDst != nullptr, "Calling SetDst with a NULL dst");
AssertMsg(this->m_dst == nullptr, "Calling SetDst without unlinking/freeing the current dst");
Assert(!(newDst->IsRegOpnd() && newDst->AsRegOpnd()->IsSymValueFrozen()));
newDst = newDst->Use(m_func);
this->m_dst = newDst;
// If newDst isSingleDef, set instrDef
StackSym *stackSym;
if (newDst->IsRegOpnd() && newDst->AsRegOpnd()->m_sym)
{
stackSym = newDst->AsRegOpnd()->m_sym->AsStackSym();
}
else if (newDst->IsSymOpnd() && newDst->AsSymOpnd()->m_sym->IsStackSym())
{
stackSym = newDst->AsSymOpnd()->m_sym->AsStackSym();
}
else
{
stackSym = nullptr;
}
if (stackSym && stackSym->m_isSingleDef)
{
if (stackSym->m_instrDef)
{
AssertMsg(!stackSym->IsArgSlotSym(), "Arg Slot sym needs to be single def to maintain the StartCall arg links");
// Multiple defs, clear isSingleDef flag
stackSym->m_isSingleDef = false;
stackSym->m_instrDef = nullptr;
stackSym->m_isConst = false;
stackSym->m_isIntConst = false;
stackSym->m_isInt64Const= false;
stackSym->m_isTaggableIntConst = false;
stackSym->m_isNotInt = false;
stackSym->m_isStrConst = false;
stackSym->m_isStrEmpty = false;
stackSym->m_isFltConst = false;
}
else
{
stackSym->m_instrDef = this;
}
}
return newDst;
}
Opnd *
Instr::SetFakeDst(Opnd * newDst)
{
AssertMsg(newDst != nullptr, "Calling SetDst with a NULL dst");
AssertMsg(this->m_dst == nullptr, "Calling SetDst without unlinking/freeing the current dst");
Assert(!(newDst->IsRegOpnd() && newDst->AsRegOpnd()->IsSymValueFrozen()));
newDst = newDst->Use(m_func);
this->m_dst = newDst;
#if DBG
newDst->isFakeDst = true;
#endif
return newDst;
}
///----------------------------------------------------------------------------
///
/// Instr::UnlinkDst
///
/// Unlinks the dst for 'this' instruction. Automatically maintains
/// instrDef of stackSyms.
///
///----------------------------------------------------------------------------
Opnd *
Instr::UnlinkDst()
{
Opnd * oldDst = this->m_dst;
StackSym *stackSym = nullptr;
// If oldDst isSingleDef, clear instrDef
if (oldDst->IsRegOpnd())
{
stackSym = oldDst->AsRegOpnd()->m_sym;
}
else if (oldDst->IsSymOpnd())
{
Sym *sym = oldDst->AsSymOpnd()->m_sym;
if (sym->IsStackSym())
{
stackSym = sym->AsStackSym();
}
}
#if DBG
if (oldDst->isFakeDst)
{
oldDst->isFakeDst = false;
}
#endif
if (stackSym && stackSym->m_isSingleDef)
{
AssertMsg(stackSym->m_instrDef == this, "m_instrDef incorrectly set");
stackSym->m_instrDef = nullptr;
}
oldDst->UnUse();
this->m_dst = nullptr;
return oldDst;
}
///----------------------------------------------------------------------------
///
/// Instr::FreeDst
///
/// Unlinks and free the dst for 'this' instruction.
///
///----------------------------------------------------------------------------
void
Instr::FreeDst()
{
Opnd * unlinkedDst;
unlinkedDst = this->UnlinkDst();
unlinkedDst->Free(this->m_func);
}
///----------------------------------------------------------------------------
///
/// Instr::ReplaceDst
///
/// Unlink this dst from this instr, free it, and replace it with newDst.
/// The new dst is returned.
///
///----------------------------------------------------------------------------
Opnd *
Instr::ReplaceDst(Opnd * newDst)
{
this->FreeDst();
return this->SetDst(newDst);
}
///----------------------------------------------------------------------------
///
/// Instr::SinkDst
///
/// Replace current dst with new symbol, and assign new symbol using the
/// given opcode to the previous dst.
///
///----------------------------------------------------------------------------
Instr *
Instr::SinkDst(Js::OpCode assignOpcode, RegNum regNum, IR::Instr *insertAfterInstr)
{
return SinkDst(assignOpcode, StackSym::New(TyVar, m_func), regNum, insertAfterInstr);
}
Instr *
Instr::SinkDst(Js::OpCode assignOpcode, StackSym * stackSym, RegNum regNum, IR::Instr *insertAfterInstr)
{
if(!insertAfterInstr)
{
insertAfterInstr = this;
}
Opnd *oldDst, *newDst;
Instr * newInstr;
IRType type;
oldDst = this->UnlinkDst();
type = oldDst->GetType();
newDst = this->SetDst(RegOpnd::New(stackSym, regNum, type, m_func));
newInstr = Instr::New(assignOpcode, oldDst, newDst, m_func);
insertAfterInstr->InsertAfter(newInstr);
return newInstr;
}
IR::Instr *
Instr::SinkInstrBefore(IR::Instr * instrTarget)
{
// Move this instruction down to the target location, preserving
// the use(s), if necessary, from redefinition between the original
// location and the new one.
if (this->m_next == instrTarget)
{
return this->m_prev;
}
StackSym *sym;
if (this->m_src1)
{
sym = this->m_src1->GetStackSym();
if (sym && !sym->m_isSingleDef)
{
this->HoistSrc1(Js::OpCode::Ld_A);
}
if (this->m_src2)
{
sym = this->m_src2->GetStackSym();
if (sym && !sym->m_isSingleDef)
{
this->HoistSrc2(Js::OpCode::Ld_A);
}
}
}
// Move the instruction down to the target. Return the instruction
// that preceded the sunk instruction at its original location.
// (This lets the caller find a Ld_A that this call inserted.)
IR::Instr * instrPrev = this->m_prev;
this->Unlink();
instrTarget->InsertBefore(this);
return instrPrev;
}
///----------------------------------------------------------------------------
///
/// Instr::UnlinkSrc1
///
/// Unlinks the src1 for 'this' instruction.
///
///----------------------------------------------------------------------------
Opnd *
Instr::UnlinkSrc1()
{
Opnd * oldSrc = this->m_src1;
oldSrc->UnUse();
this->m_src1 = nullptr;
return oldSrc;
}
///----------------------------------------------------------------------------
///
/// Instr::FreeSrc1
///
/// Unlinks and free the src1 for 'this' instruction.
///
///----------------------------------------------------------------------------
void
Instr::FreeSrc1()
{
Opnd * unlinkedSrc;
unlinkedSrc = this->UnlinkSrc1();
unlinkedSrc->Free(this->m_func);
}
///----------------------------------------------------------------------------
///
/// Instr::ReplaceSrc1
///
/// Unlink src1 from this instr, free it, and replace it with newSrc.
/// The new src is returned.
///
///----------------------------------------------------------------------------
Opnd *
Instr::ReplaceSrc1(Opnd * newSrc)
{
this->FreeSrc1();
return this->SetSrc1(newSrc);
}
///----------------------------------------------------------------------------
///
/// Instr::HoistSrc1
///
/// Replace current src with new symbol, and assign new symbol using the
/// given opcode from the previous src.
///
///----------------------------------------------------------------------------
Instr *
Instr::HoistSrc1(Js::OpCode assignOpcode, RegNum regNum, StackSym *newSym)
{
Opnd *oldSrc, *newSrc;
Instr * newInstr;
IRType type;
oldSrc = this->UnlinkSrc1();
type = oldSrc->GetType();
const bool creatingNewSym = !newSym;
if(creatingNewSym)
{
newSym = StackSym::New(type, m_func);
}
newSrc = this->SetSrc1(RegOpnd::New(newSym, regNum, type, m_func));
newSrc->SetValueType(oldSrc->GetValueType());
newInstr = Instr::New(assignOpcode, newSrc, oldSrc, m_func);
this->InsertBefore(newInstr);
if(creatingNewSym)
{
if (oldSrc->IsRegOpnd())
{
newSym->CopySymAttrs(oldSrc->AsRegOpnd()->m_sym);
}
else if (oldSrc->IsImmediateOpnd())
{
newSym->SetIsConst();
}
}
return newInstr;
}
///----------------------------------------------------------------------------
///
/// Instr::UnlinkSrc2
///
/// Unlinks the src2 for 'this' instruction.
///
///----------------------------------------------------------------------------
Opnd *
Instr::UnlinkSrc2()
{
Opnd * oldSrc = this->m_src2;
oldSrc->UnUse();
this->m_src2 = nullptr;
return oldSrc;
}
///----------------------------------------------------------------------------
///
/// Instr::FreeSrc2
///
/// Unlinks and free the src2 for 'this' instruction.
///
///----------------------------------------------------------------------------
void
Instr::FreeSrc2()
{
Opnd * unlinkedSrc;
unlinkedSrc = this->UnlinkSrc2();
unlinkedSrc->Free(this->m_func);
}
///----------------------------------------------------------------------------
///
/// Instr::ReplaceSrc2
///
/// Unlink src2 from this instr, free it, and replace it with newSrc.
/// The new src is returned.
///
///----------------------------------------------------------------------------
Opnd *
Instr::ReplaceSrc2(Opnd * newSrc)
{
this->FreeSrc2();
return this->SetSrc2(newSrc);
}
///----------------------------------------------------------------------------
///
/// Instr::HoistSrc2
///
/// Replace current src with new symbol, and assign new symbol using the
/// given opcode from the previous src.
///
///----------------------------------------------------------------------------
Instr *
Instr::HoistSrc2(Js::OpCode assignOpcode, RegNum regNum, StackSym *newSym)
{
Opnd *oldSrc, *newSrc;
Instr * newInstr;
IRType type;
oldSrc = this->UnlinkSrc2();
type = oldSrc->GetType();
const bool creatingNewSym = !newSym;
if(creatingNewSym)
{
newSym = StackSym::New(type, m_func);
}
newSrc = this->SetSrc2(RegOpnd::New(newSym, regNum, type, m_func));
newSrc->SetValueType(oldSrc->GetValueType());
newInstr = Instr::New(assignOpcode, newSrc, oldSrc, m_func);
this->InsertBefore(newInstr);
if(creatingNewSym)
{
if (oldSrc->IsRegOpnd())
{
newSym->CopySymAttrs(oldSrc->AsRegOpnd()->m_sym);
}
else if (oldSrc->IsIntConstOpnd())
{
newSym->SetIsIntConst(oldSrc->AsIntConstOpnd()->GetValue());
}
}
return newInstr;
}
///----------------------------------------------------------------------------
///
/// Instr::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.
///
///----------------------------------------------------------------------------
Instr *
Instr::HoistIndirOffset(IR::IndirOpnd *indirOpnd, RegNum regNum)
{
int32 offset = indirOpnd->GetOffset();
if (indirOpnd->GetIndexOpnd())
{
Assert(indirOpnd->GetBaseOpnd());
return HoistIndirOffsetAsAdd(indirOpnd, indirOpnd->GetBaseOpnd(), offset, regNum);
}
IntConstOpnd *offsetOpnd = IntConstOpnd::New(offset, TyInt32, this->m_func);
RegOpnd *indexOpnd = RegOpnd::New(StackSym::New(TyMachReg, this->m_func), regNum, TyMachReg, this->m_func);
#if defined(DBG) && defined(_M_ARM)
if (regNum == SCRATCH_REG)
{
AssertMsg(indirOpnd->GetBaseOpnd()->GetReg()!= SCRATCH_REG, "Why both are SCRATCH_REG");
if (this->GetSrc1() && this->GetSrc1()->IsRegOpnd())
{
Assert(this->GetSrc1()->AsRegOpnd()->GetReg() != SCRATCH_REG);
}
if (this->GetSrc2() && this->GetSrc2()->IsRegOpnd())
{
Assert(this->GetSrc2()->AsRegOpnd()->GetReg() != SCRATCH_REG);
}
if (this->GetDst() && this->GetDst()->IsRegOpnd())
{
Assert(this->GetDst()->AsRegOpnd()->GetReg() != SCRATCH_REG);
}
}
#endif
// Clear the offset and add a new reg as the index.
indirOpnd->SetOffset(0);
indirOpnd->SetIndexOpnd(indexOpnd);
Instr *instrAssign = LowererMD::CreateAssign(indexOpnd, offsetOpnd, this);
indexOpnd->m_sym->SetIsIntConst(offset);
return instrAssign;
}
IndirOpnd *
Instr::HoistMemRefAddress(MemRefOpnd *const memRefOpnd, const Js::OpCode loadOpCode)
{
Assert(memRefOpnd);
#if defined(_M_IX86) || defined(_M_X64)
Assert(!LowererMDArch::IsLegalMemLoc(memRefOpnd));
#endif
intptr_t address = memRefOpnd->GetMemLoc();
IR::AddrOpndKind kind = memRefOpnd->GetAddrKind();
Func *const func = m_func;
IR::AddrOpnd * addrOpnd = IR::AddrOpnd::New(address, kind, this->m_func, true);
IR::IndirOpnd * indirOpnd = func->GetTopFunc()->GetConstantAddressIndirOpnd(address, addrOpnd, kind, memRefOpnd->GetType(), loadOpCode);
if (indirOpnd == nullptr)
{
IR::RegOpnd * addressRegOpnd = IR::RegOpnd::New(TyMachPtr, func);
IR::Instr *const newInstr =
IR::Instr::New(
loadOpCode,
addressRegOpnd,
IR::AddrOpnd::New(address, kind, func, true),
func);
InsertBefore(newInstr);
indirOpnd = IR::IndirOpnd::New(addressRegOpnd, 0, memRefOpnd->GetType(), func, true);
#if DBG_DUMP
// TODO: michhol oop jit, make intptr
indirOpnd->SetAddrKind(kind, (void*)address);
#endif
}
return DeepReplace(memRefOpnd, indirOpnd)->AsIndirOpnd();
}
Opnd *
Instr::Replace(Opnd *oldOpnd, Opnd *newOpnd)
{
if (oldOpnd == this->GetDst())
{
return this->ReplaceDst(newOpnd);
}
else
{
return this->ReplaceSrc(oldOpnd, newOpnd);
}
}
Opnd *Instr::DeepReplace(Opnd *const oldOpnd, Opnd *const newOpnd)
{
Assert(oldOpnd);
Assert(newOpnd);
IR::Opnd *opnd = GetDst();
if(opnd && oldOpnd != opnd && oldOpnd->IsEqual(opnd))
{
ReplaceDst(newOpnd);
}
opnd = GetSrc1();
if(opnd && oldOpnd != opnd && oldOpnd->IsEqual(opnd))
{
ReplaceSrc1(newOpnd);
}
opnd = GetSrc2();
if(opnd && oldOpnd != opnd && oldOpnd->IsEqual(opnd))
{
ReplaceSrc2(newOpnd);
}
// Do this last because Replace will delete oldOpnd
return Replace(oldOpnd, newOpnd);
}
Instr *
Instr::HoistIndirOffsetAsAdd(IR::IndirOpnd *orgOpnd, IR::Opnd *baseOpnd, int offset, RegNum regNum)
{
IR::RegOpnd *newBaseOpnd = IR::RegOpnd::New(StackSym::New(TyMachPtr, this->m_func), regNum, TyMachPtr, this->m_func);
IR::IntConstOpnd *src2 = IR::IntConstOpnd::New(offset, TyInt32, this->m_func);
IR::Instr * instrAdd = IR::Instr::New(Js::OpCode::ADD, newBaseOpnd, baseOpnd, src2, this->m_func);
this->InsertBefore(instrAdd);
orgOpnd->ReplaceBaseOpnd(newBaseOpnd);
orgOpnd->SetOffset(0);
return instrAdd;
}
Instr *
Instr::HoistIndirIndexOpndAsAdd(IR::IndirOpnd *orgOpnd, IR::Opnd *baseOpnd, IR::Opnd *indexOpnd, RegNum regNum)
{
IR::RegOpnd *newBaseOpnd = IR::RegOpnd::New(StackSym::New(TyMachPtr, this->m_func), regNum, TyMachPtr, this->m_func);
IR::Instr * instrAdd = IR::Instr::New(Js::OpCode::ADD, newBaseOpnd, baseOpnd, indexOpnd, this->m_func);
this->InsertBefore(instrAdd);
orgOpnd->ReplaceBaseOpnd(newBaseOpnd);
orgOpnd->SetIndexOpnd(nullptr);
return instrAdd;
}
Instr *
Instr::HoistSymOffsetAsAdd(IR::SymOpnd *orgOpnd, IR::Opnd *baseOpnd, int offset, RegNum regNum)
{
IR::IndirOpnd *newIndirOpnd = IR::IndirOpnd::New(baseOpnd->AsRegOpnd(), 0, TyMachPtr, this->m_func);
this->Replace(orgOpnd, newIndirOpnd); // Replace SymOpnd with IndirOpnd
return this->HoistIndirOffsetAsAdd(newIndirOpnd, baseOpnd, offset, regNum);
}
///----------------------------------------------------------------------------
///
/// Instr::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.)
///
///----------------------------------------------------------------------------
Instr *
Instr::HoistSymOffset(SymOpnd *symOpnd, RegNum baseReg, uint32 offset, RegNum regNum)
{
IR::RegOpnd *baseOpnd = IR::RegOpnd::New(nullptr, baseReg, TyMachPtr, this->m_func);
IR::IndirOpnd *indirOpnd = IR::IndirOpnd::New(baseOpnd, offset, symOpnd->GetType(), this->m_func);
if (symOpnd == this->GetDst())
{
this->ReplaceDst(indirOpnd);
}
else
{
this->ReplaceSrc(symOpnd, indirOpnd);
}
return this->HoistIndirOffset(indirOpnd, regNum);
}
Opnd *
Instr::UnlinkSrc(Opnd *src)
{
if (src == this->GetSrc1())
{
return this->UnlinkSrc1();
}
else
{
AssertMsg(src == this->GetSrc2(), "Src not found");
return this->UnlinkSrc2();
}
}
///----------------------------------------------------------------------------
///
/// Instr::ReplaceSrc
///
/// Unlink oldSrc from this instr, free it, and replace it with newSrc.
/// The new src is returned.
///
///----------------------------------------------------------------------------
Opnd *
Instr::ReplaceSrc(Opnd *oldSrc, Opnd * newSrc)
{
if (oldSrc == this->GetSrc1())
{
return this->ReplaceSrc1(newSrc);
}
else
{
AssertMsg(oldSrc == this->GetSrc2(), "OldSrc not found");
return this->ReplaceSrc2(newSrc);
}
}
///----------------------------------------------------------------------------
///
/// Instr::IsRealInstr
///
/// Does this instr generate code?
///
///----------------------------------------------------------------------------
bool
Instr::IsRealInstr() const
{
switch (m_opcode)
{
case Js::OpCode::Label:
case Js::OpCode::StatementBoundary:
case Js::OpCode::NoImplicitCallUses:
case Js::OpCode::NoIntOverflowBoundary:
return false;
default:
return true;
}
}
///----------------------------------------------------------------------------
///
/// Instr::GetNextRealInstr
///
///----------------------------------------------------------------------------
IR::Instr *
Instr::GetNextRealInstr() const
{
IR::Instr *instr = this->m_next;
while (instr != nullptr && !instr->IsRealInstr())
{
AssertMsg(instr->m_next || instr->IsPragmaInstr(), "GetNextRealInstr() failed...");
instr = instr->m_next;
}
return instr;
}
///----------------------------------------------------------------------------
///
/// Instr::GetNextRealInstrOrLabel
///
///----------------------------------------------------------------------------
IR::Instr *
Instr::GetNextRealInstrOrLabel() const
{
IR::Instr *instr = this->m_next;
while (instr != nullptr && !instr->IsLabelInstr() && !instr->IsRealInstr())
{
instr = instr->m_next;
AssertMsg(instr, "GetNextRealInstrOrLabel() failed...");
}
return instr;
}
IR::Instr *
Instr::GetNextBranchOrLabel() const
{
IR::Instr *instr = this->m_next;
while (instr != nullptr && !instr->IsLabelInstr() && !instr->IsBranchInstr())
{
instr = instr->m_next;
}
return instr;
}
///----------------------------------------------------------------------------
///
/// Instr::GetPrevRealInstr
///
///----------------------------------------------------------------------------
IR::Instr *
Instr::GetPrevRealInstr() const
{
IR::Instr *instr = this->m_prev;
while (!instr->IsRealInstr())
{
instr = instr->m_prev;
AssertMsg(instr, "GetPrevRealInstr() failed...");
}
return instr;
}
///----------------------------------------------------------------------------
///
/// Instr::GetPrevRealInstrOrLabel
///
///----------------------------------------------------------------------------
IR::Instr *
Instr::GetPrevRealInstrOrLabel() const
{
IR::Instr *instr = this->m_prev;
while (!instr->IsLabelInstr() && !instr->IsRealInstr())
{
instr = instr->m_prev;
AssertMsg(instr, "GetPrevRealInstrOrLabel() failed...");
}
return instr;
}
///----------------------------------------------------------------------------
///
/// Instr::GetInsertBeforeByteCodeUsesInstr
/// Finds the instruction before which new instructions can be inserted, by skipping ByteCodeUses instructions associated with
/// this instruction.
///
///----------------------------------------------------------------------------
IR::Instr *Instr::GetInsertBeforeByteCodeUsesInstr()
{
const uint32 byteCodeOffset = GetByteCodeOffset();
IR::Instr *insertBeforeInstr = this;
IR::Instr *prevInstr = insertBeforeInstr->m_prev;
while(prevInstr && prevInstr->IsByteCodeUsesInstr() && prevInstr->GetByteCodeOffset() == byteCodeOffset)
{
insertBeforeInstr = prevInstr;
prevInstr = prevInstr->m_prev;
}
return insertBeforeInstr;
}
///----------------------------------------------------------------------------
///
/// Instr::GetOrCreateContinueLabel
///
///----------------------------------------------------------------------------
IR::LabelInstr *
Instr::GetOrCreateContinueLabel(const bool isHelper)
{
if(m_next && m_next->IsLabelInstr() && m_next->AsLabelInstr()->isOpHelper == isHelper)
{
return m_next->AsLabelInstr();
}
IR::LabelInstr *const label = IR::LabelInstr::New(Js::OpCode::Label, m_func, isHelper);
InsertAfter(label);
return label;
}
///----------------------------------------------------------------------------
///
/// Instr::FindRegUse
///
/// Search a reg use of the given sym. Return the RegOpnd that uses it.
///
///----------------------------------------------------------------------------
IR::RegOpnd *
Instr::FindRegUse(StackSym *sym)
{
IR::Opnd *src1 = this->GetSrc1();
// Check src1
if (src1)
{
if (src1->IsRegOpnd())
{
RegOpnd *regOpnd = src1->AsRegOpnd();
if (regOpnd->m_sym == sym)
{
return regOpnd;
}
}
else if (src1->IsIndirOpnd())
{
IndirOpnd * indirOpnd = src1->AsIndirOpnd();
RegOpnd * baseOpnd = indirOpnd->GetBaseOpnd();
if (baseOpnd != nullptr && baseOpnd->m_sym == sym)
{
return baseOpnd;
}
else if (indirOpnd->GetIndexOpnd() && indirOpnd->GetIndexOpnd()->m_sym == sym)
{
return indirOpnd->GetIndexOpnd();
}
}
IR::Opnd *src2 = this->GetSrc2();
// Check src2
if (src2)
{
if (src2->IsRegOpnd())
{
RegOpnd *regOpnd = src2->AsRegOpnd();
if (regOpnd->m_sym == sym)
{
return regOpnd;
}
}
else if (src2->IsIndirOpnd())
{
IR::IndirOpnd *indirOpnd = src2->AsIndirOpnd();
RegOpnd * baseOpnd = indirOpnd->GetBaseOpnd();
if (baseOpnd != nullptr && baseOpnd->m_sym == sym)
{
return baseOpnd;
}
else if (indirOpnd->GetIndexOpnd() && indirOpnd->GetIndexOpnd()->m_sym == sym)
{
return indirOpnd->GetIndexOpnd();
}
}
}
}
// Check uses in dst
IR::Opnd *dst = this->GetDst();
if (dst != nullptr && dst->IsIndirOpnd())
{
IR::IndirOpnd *indirOpnd = dst->AsIndirOpnd();
RegOpnd * baseOpnd = indirOpnd->GetBaseOpnd();
if (baseOpnd != nullptr && baseOpnd->m_sym == sym)
{
return baseOpnd;
}
else if (indirOpnd->GetIndexOpnd() && indirOpnd->GetIndexOpnd()->m_sym == sym)
{
return indirOpnd->GetIndexOpnd();
}
}
return nullptr;
}
IR::RegOpnd *
Instr::FindRegUseInRange(StackSym *sym, IR::Instr *instrBegin, IR::Instr *instrEnd)
{
FOREACH_INSTR_IN_RANGE(instr, instrBegin, instrEnd)
{
Assert(instr);
IR::RegOpnd *opnd = instr->FindRegUse(sym);
if (opnd)
{
return opnd;
}
}
NEXT_INSTR_IN_RANGE;
return nullptr;
}
///----------------------------------------------------------------------------
///
/// Instr::FindRegDef
///
/// Search a reg def of the given sym. Return the RegOpnd that defines it.
///
///----------------------------------------------------------------------------
IR::RegOpnd *
Instr::FindRegDef(StackSym *sym)
{
IR::Opnd *dst = this->GetDst();
if (dst)
{
if (dst->IsRegOpnd())
{
RegOpnd *regOpnd = dst->AsRegOpnd();
if (regOpnd->m_sym == sym)
{
return regOpnd;
}
}
}
return nullptr;
}
Instr*
Instr::FindSingleDefInstr(Js::OpCode opCode, Opnd* src)
{
RegOpnd* src1 = src->IsRegOpnd() ? src->AsRegOpnd() : nullptr;
return src1 &&
src1->m_sym->IsSingleDef() &&
src1->m_sym->GetInstrDef()->m_opcode == opCode ?
src1->m_sym->GetInstrDef() :
nullptr;
}
void
Instr::TransferDstAttributesTo(Instr * instr)
{
instr->dstIsTempNumber = this->dstIsTempNumber;
instr->dstIsTempNumberTransferred = this->dstIsTempNumberTransferred;
instr->dstIsTempObject = this->dstIsTempObject;
}
void
Instr::TransferTo(Instr * instr)
{
Assert(instr->m_dst == nullptr);
Assert(instr->m_src1 == nullptr);
Assert(instr->m_src2 == nullptr);
this->TransferDstAttributesTo(instr);
instr->usesStackArgumentsObject = this->usesStackArgumentsObject;
instr->isCloned = this->isCloned;
instr->ignoreNegativeZero = this->ignoreNegativeZero;
instr->ignoreIntOverflow = this->ignoreIntOverflow;
instr->ignoreIntOverflowInRange = this->ignoreIntOverflowInRange;
instr->ignoreOverflowBitCount = this->ignoreOverflowBitCount;
instr->loadedArrayHeadSegment = this->loadedArrayHeadSegment;
instr->loadedArrayHeadSegmentLength = this->loadedArrayHeadSegmentLength;
instr->extractedUpperBoundCheckWithoutHoisting = this->extractedUpperBoundCheckWithoutHoisting;
instr->m_number = this->m_number;
instr->m_src1 = this->m_src1;
instr->m_src2 = this->m_src2;
instr->dstIsAlwaysConvertedToInt32 = this->dstIsAlwaysConvertedToInt32;
instr->dstIsAlwaysConvertedToNumber = this->dstIsAlwaysConvertedToNumber;
instr->dataWidth = this->dataWidth;
IR::Opnd * dst = this->m_dst;
if (dst)
{
instr->m_dst = dst;
this->m_dst = nullptr;
if (dst->IsRegOpnd())
{
Sym * sym = dst->AsRegOpnd()->m_sym;
if (sym->IsStackSym() && sym->AsStackSym()->m_isSingleDef)
{
Assert(sym->AsStackSym()->m_instrDef == this);
StackSym * stackSym = sym->AsStackSym();
stackSym->m_instrDef = instr;
}
}
}
this->m_src1 = nullptr;
this->m_src2 = nullptr;
}
IR::Instr *
Instr::ConvertToBailOutInstr(IR::Instr * bailOutTarget, IR::BailOutKind kind, uint32 bailOutOffset)
{
Func * func = bailOutTarget->m_func;
BailOutInfo * bailOutInfo = JitAnew(func->m_alloc, BailOutInfo, bailOutOffset == Js::Constants::NoByteCodeOffset ? bailOutTarget->GetByteCodeOffset() : bailOutOffset , func);
#if ENABLE_DEBUG_CONFIG_OPTIONS
bailOutInfo->bailOutOpcode = this->m_opcode;
#endif
return this->ConvertToBailOutInstr(bailOutInfo, kind);
}
// Notes:
// - useAuxBailout = true specifies that this bailout further will be invisible to globopt, etc, and we'll use auxBailoutKind instead of BailoutKind.
// Currently this is used for BailOutIgnoreException for debugger.
//
// Here's typical workflow for scenario useAuxBailout = true.
// - IRBuilder::Build calls this with kind == BailOutIgnoreException
// - In here we save the kind to auxBailOut and save bail out info but set hasBailOutInfo to false.
// - During globopt optimizations presence of this bail out is not detected and instrs can add/remove bailouts as they need.
// - If they call to convert this instr to bail out instr, we set bailOutKind to what they want and replace bailOutInfo.
// ** This assumes that for aux bail out bailoutInfo does not really matter (if its pre/post op, etc) **
// - This is the case for ignore exception.
// - This will cause to share aux bail out with regular bail out.
// - In globopt right after OptInstr we check if there is aux bail out which wasn't shared with regular bail out,
// and if it's not, we convert it back to regular bail out.
IR::Instr *
Instr::ConvertToBailOutInstr(BailOutInfo * bailOutInfo, IR::BailOutKind kind, bool useAuxBailOut /* = false */)
{
Assert(!this->HasBailOutInfo());
AssertMsg(!useAuxBailOut || !this->HasAuxBailOut(), "Already aux bail out!");
Assert(!this->HasAuxBailOut() || this->GetAuxBailOutKind() != IR::BailOutInvalid);
IR::Instr * bailOutInstr = nullptr;
if (this->HasAuxBailOut())
{
// This instr has already been converted to bailout instr. Only possible with aux bail out.
// Typical scenario is when globopt calls to convert to e.g. BailOutOnImplicitCalls for the instr which
// was already converted to bail out instr with HasBailOutInfo() == false and HasAuxBailOutInfo() == true,
// so that aux bail out is hidden in between IRBuilder and lowerer.
AssertMsg((this->GetAuxBailOutKind() & ~(IR::BailOutIgnoreException | IR::BailOutForceByFlag)) == 0, "Only IR::BailOutIgnoreException|ForceByFlag supported here.");
// What we rely on here is:
// - bailout doesn't have any args.
// - bailout doesn't use offset as we get it from DebuggingFlags at time of bailout.
// Use prev debugger bailout kind as decoration, while keeping new kind as main.
this->SetBailOutKind_NoAssert(kind);
// Clear old (aux) info and set to the new bailOutInfo.
this->ReplaceBailOutInfo(bailOutInfo);
bailOutInfo->bailOutInstr = this;
this->hasBailOutInfo = true;
bailOutInstr = this;
}
else
{
switch (this->m_kind)
{
case InstrKindInstr:
bailOutInstr = IR::BailOutInstr::New(this->m_opcode, kind, bailOutInfo, bailOutInfo->bailOutFunc);
break;
case InstrKindProfiled:
bailOutInstr = IR::ProfiledBailOutInstr::New(this->m_opcode, kind, bailOutInfo, bailOutInfo->bailOutFunc);
bailOutInstr->AsProfiledInstr()->u = this->AsProfiledInstr()->u;
break;
case InstrKindBranch:
{
IR::BranchInstr * branchInstr = this->AsBranchInstr();
Assert(!branchInstr->IsMultiBranch());
IR::BranchBailOutInstr * branchBailOutInstr = IR::BranchBailOutInstr::New(this->m_opcode, kind, bailOutInfo, bailOutInfo->bailOutFunc);
branchBailOutInstr->SetTarget(branchInstr->GetTarget());
branchBailOutInstr->SetByteCodeReg(branchInstr->GetByteCodeReg());
bailOutInstr = branchBailOutInstr;
break;
}
default:
AnalysisAssert(false);
};
this->m_next->m_prev = bailOutInstr;
this->m_prev->m_next = bailOutInstr;
bailOutInstr->m_next = this->m_next;
bailOutInstr->m_prev = this->m_prev;
this->TransferTo(bailOutInstr);
this->Free();
}
if (useAuxBailOut)
{
// Move bail out kind from bailOutKind to auxBailOutKind and hide bailOutInfo as if this is not a bail out instr.
bailOutInstr->SetAuxBailOutKind(kind);
bailOutInstr->SetBailOutKind_NoAssert(IR::BailOutInvalid);
bailOutInstr->hasBailOutInfo = false;
bailOutInstr->hasAuxBailOut = true;
}
return bailOutInstr;
}
// Convert aux bailout to regular bail out.
// Called by globopt after all optimizations are done, in case we still have aux bail out on the instr.
void Instr::PromoteAuxBailOut()
{
Assert(!this->HasBailOutInfo());
Assert(this->GetAuxBailOutKind() != IR::BailOutInvalid);
this->SetBailOutKind_NoAssert(this->GetAuxBailOutKind());
this->SetAuxBailOutKind(IR::BailOutInvalid);
this->hasBailOutInfo = true;
this->hasAuxBailOut = false;
}
// Reset all tracks of aux bailout but don't rest the bail out info.
// Used after we extract aux bail out in lowerer.
void Instr::ResetAuxBailOut()
{
this->SetAuxBailOutKind(IR::BailOutInvalid);
this->hasAuxBailOut = false;
}
void
Instr::ClearBailOutInfo()
{
if (this->HasBailOutInfo() || this->HasAuxBailOut())
{
BailOutInfo * bailOutInfo = this->GetBailOutInfo();
Assert(bailOutInfo);
if (bailOutInfo->bailOutInstr == this)
{
JitArenaAllocator * alloc = this->m_func->m_alloc;
bailOutInfo->Clear(alloc);
JitAdelete(alloc, bailOutInfo);
}
this->hasBailOutInfo = false;
this->hasAuxBailOut = false;
}
}
bool Instr::HasAnyLoadHeapArgsOpCode()
{
switch (m_opcode)
{
case Js::OpCode::LdHeapArguments:
case Js::OpCode::LdHeapArgsCached:
case Js::OpCode::LdLetHeapArguments:
case Js::OpCode::LdLetHeapArgsCached:
return true;
}
return false;
}
bool Instr::CanHaveArgOutChain() const
{
return
this->m_opcode == Js::OpCode::CallI ||
this->m_opcode == Js::OpCode::CallIFixed ||
this->m_opcode == Js::OpCode::NewScObject ||
this->m_opcode == Js::OpCode::NewScObjectSpread ||
this->m_opcode == Js::OpCode::NewScObjArray ||
this->m_opcode == Js::OpCode::NewScObjArraySpread;
}
bool Instr::HasEmptyArgOutChain(IR::Instr** startCallInstrOut)
{
Assert(CanHaveArgOutChain());
if (GetSrc2()->IsRegOpnd())
{
IR::RegOpnd * argLinkOpnd = GetSrc2()->AsRegOpnd();
StackSym *argLinkSym = argLinkOpnd->m_sym->AsStackSym();
AssertMsg(!argLinkSym->IsArgSlotSym() && argLinkSym->m_isSingleDef, "Arg tree not single def...");
IR::Instr* startCallInstr = argLinkSym->m_instrDef;
AssertMsg(startCallInstr->m_opcode == Js::OpCode::StartCall, "Problem with arg chain.");
if (startCallInstrOut != nullptr)
{
*startCallInstrOut = startCallInstr;
}
return true;
}
return false;
}
bool Instr::HasFixedFunctionAddressTarget() const
{
Assert(
this->m_opcode == Js::OpCode::CallI ||
this->m_opcode == Js::OpCode::CallIFixed ||
this->m_opcode == Js::OpCode::NewScObject ||
this->m_opcode == Js::OpCode::NewScObjectSpread ||
this->m_opcode == Js::OpCode::NewScObjArray ||
this->m_opcode == Js::OpCode::NewScObjArraySpread ||
this->m_opcode == Js::OpCode::NewScObjectNoCtor);
return
this->GetSrc1() != nullptr &&
this->GetSrc1()->IsAddrOpnd() &&
this->GetSrc1()->AsAddrOpnd()->GetAddrOpndKind() == IR::AddrOpndKind::AddrOpndKindDynamicVar &&
this->GetSrc1()->AsAddrOpnd()->m_isFunction;
}
bool Instr::TransfersSrcValue()
{
// Return whether the instruction transfers a value to the destination.
// This is used to determine whether we should generate a value for the src so that it will
// match with the dst for copy prop.
// No point creating an unknown value for the src of a binary instr, as the dst will just be a different
// Don't create value for instruction without dst as well. The value doesn't go anywhere.
// if (src2 == nullptr) Disable copy prop for ScopedLdFld/ScopeStFld, etc., consider enabling that in the future
// Consider: Add opcode attribute to indicate whether the opcode would use the value or not
return this->GetDst() != nullptr && this->GetSrc2() == nullptr && !OpCodeAttr::DoNotTransfer(this->m_opcode) && !this->CallsAccessor();
}
void Instr::MoveArgs(bool generateByteCodeCapture)
{
Assert(this->m_opcode == Js::OpCode::InlineeStart || this->m_opcode == Js::OpCode::CallDirect ||
this->m_opcode == Js::OpCode::CallI || this->m_opcode == Js::OpCode::CallIFixed);
IR::Instr *argInsertInstr = this;
this->IterateArgInstrs([&](IR::Instr* argInstr)
{
if (generateByteCodeCapture)
{
argInstr->GenerateBytecodeArgOutCapture();
}
argInstr->Move(argInsertInstr);
argInsertInstr = argInstr;
return false;
});
}
void Instr::Move(IR::Instr* insertInstr)
{
this->Unlink();
this->ClearByteCodeOffset();
this->SetByteCodeOffset(insertInstr);
insertInstr->InsertBefore(this);
}
IR::Instr* Instr::GetBytecodeArgOutCapture()
{
Assert(this->m_opcode == Js::OpCode::ArgOut_A_Inline ||
this->m_opcode == Js::OpCode::ArgOut_A ||
this->m_opcode == Js::OpCode::ArgOut_A_InlineBuiltIn);
Assert(this->m_dst->GetStackSym()->m_isArgCaptured);
IR::Instr* instr = this->GetSrc1()->GetStackSym()->m_instrDef;
Assert(instr->m_opcode == Js::OpCode::BytecodeArgOutCapture);
return instr;
}
bool Instr::HasByteCodeArgOutCapture()
{
Assert(this->m_opcode == Js::OpCode::ArgOut_A_FixupForStackArgs ||
this->m_opcode == Js::OpCode::ArgOut_A_Inline ||
this->m_opcode == Js::OpCode::ArgOut_A ||
this->m_opcode == Js::OpCode::ArgOut_A_InlineBuiltIn ||
this->m_opcode == Js::OpCode::ArgOut_A_FromStackArgs);
if (this->m_dst->GetStackSym()->m_isArgCaptured)
{
Assert(GetBytecodeArgOutCapture() != nullptr);
return true;
}
return false;
}
void Instr::GenerateBytecodeArgOutCapture()
{
if (!HasByteCodeArgOutCapture())
{
this->m_dst->GetStackSym()->m_isArgCaptured = true;
StackSym* tmpSym = StackSym::NewArgSlotRegSym(this->GetDst()->GetStackSym()->GetArgSlotNum(), this->m_func, this->GetDst()->GetType());
IR::Instr* instr = this->HoistSrc1(Js::OpCode::BytecodeArgOutCapture, RegNOREG, tmpSym);
instr->SetByteCodeOffset(this);
}
}
void Instr::GenerateArgOutSnapshot()
{
StackSym* tmpSym = StackSym::NewArgSlotRegSym(this->GetDst()->GetStackSym()->GetArgSlotNum(), this->m_func);
IR::Instr* instr = this->HoistSrc1(Js::OpCode::Ld_A, RegNOREG, tmpSym);
instr->SetByteCodeOffset(this);
}
IR::Instr* Instr::GetArgOutSnapshot()
{
Assert(this->m_opcode == Js::OpCode::ArgOut_A_FixupForStackArgs);
IR::Instr* instr = this->GetSrc1()->GetStackSym()->m_instrDef;
Assert(instr->m_opcode == Js::OpCode::Ld_A);
return instr;
}
bool Instr::HasAnyImplicitCalls() const
{
// there can be no implicit calls in asm.js
if (m_func->GetJITFunctionBody()->IsAsmJsMode())
{
return false;
}
if (OpCodeAttr::HasImplicitCall(this->m_opcode))
{
return true;
}
if (OpCodeAttr::OpndHasImplicitCall(this->m_opcode))
{
if (this->m_dst &&
((this->m_dst->IsSymOpnd() && this->m_dst->AsSymOpnd()->m_sym->IsPropertySym()) ||
this->m_dst->IsIndirOpnd()))
{
return true;
}
IR::Opnd *src1 = this->GetSrc1();
if (src1)
{
if ((src1->IsSymOpnd() && src1->AsSymOpnd()->m_sym->IsPropertySym()) || src1->IsIndirOpnd())
{
return true;
}
if (!src1->GetValueType().IsPrimitive())
{
return true;
}
IR::Opnd *src2 = this->GetSrc2();
if (src2)
{
if ((src2->IsSymOpnd() && src2->AsSymOpnd()->m_sym->IsPropertySym()) || src2->IsIndirOpnd())
{
return true;
}
if (!src2->GetValueType().IsPrimitive())
{
return true;
}
}
}
}
return false;
}
bool Instr::HasAnySideEffects() const
{
return (hasSideEffects ||
OpCodeAttr::HasSideEffects(this->m_opcode) ||
this->HasAnyImplicitCalls());
}
bool Instr::AreAllOpndInt64() const
{
bool isDstInt64 = !m_dst || IRType_IsInt64(m_dst->GetType());
bool isSrc1Int64 = !m_src1 || IRType_IsInt64(m_src1->GetType());
bool isSrc2Int64 = !m_src2 || IRType_IsInt64(m_src2->GetType());
return isDstInt64 && isSrc1Int64 && isSrc2Int64;
}
FixedFieldInfo* Instr::GetFixedFunction() const
{
Assert(HasFixedFunctionAddressTarget());
FixedFieldInfo* function = (FixedFieldInfo*)this->m_src1->AsAddrOpnd()->m_metadata;
return function;
}
IR::Instr* Instr::GetNextArg()
{
Assert(this->m_opcode == Js::OpCode::ArgOut_A_FixupForStackArgs ||
this->m_opcode == Js::OpCode::ArgOut_A_Inline ||
this->m_opcode == Js::OpCode::ArgOut_A ||
this->m_opcode == Js::OpCode::ArgOut_A_InlineBuiltIn ||
this->m_opcode == Js::OpCode::InlineeStart);
IR::Instr* argInstr = this;
while (true)
{
StackSym* linkSym;
if (argInstr->GetSrc2()->IsRegOpnd())
{
linkSym = argInstr->GetSrc2()->AsRegOpnd()->m_sym->AsStackSym();
}
else
{
linkSym = argInstr->GetSrc2()->AsSymOpnd()->m_sym->AsStackSym();
Assert(linkSym->IsArgSlotSym());
}
Assert(linkSym->IsSingleDef());
argInstr = linkSym->m_instrDef;
if (argInstr->m_opcode == Js::OpCode::ArgOut_A_InlineSpecialized)
{
continue;
}
if (argInstr->m_opcode == Js::OpCode::StartCall)
{
break;
}
return argInstr;
}
return nullptr;
}
uint Instr::GetArgOutCount(bool getInterpreterArgOutCount)
{
// There are cases of inlining like .apply and .call target inlining, where we muck around with the ArgOut sequence,
// and make it different from the one the interpreter sees (and expects, on a bailout).
// In such cases, we set the interpreter version of the number of ArgOuts as the src2 of StartCall,
// and any code that queries the argout count for bailout purposes should look at the src2 (if available) of these instructions.
// If the src2 is not set, that means that the interpreter and the JIT versions of the argout count are the same.
Js::OpCode opcode = this->m_opcode;
Assert(opcode == Js::OpCode::StartCall ||
opcode == Js::OpCode::InlineeEnd || opcode == Js::OpCode::InlineBuiltInEnd|| opcode == Js::OpCode::InlineNonTrackingBuiltInEnd ||
opcode == Js::OpCode::EndCallForPolymorphicInlinee || opcode == Js::OpCode::LoweredStartCall);
if (!getInterpreterArgOutCount)
{
return this->GetSrc1()->AsIntConstOpnd()->AsUint32();
}
Assert(opcode == Js::OpCode::StartCall);
IntConstType argOutCount = !this->GetSrc2() ? this->GetSrc1()->AsIntConstOpnd()->GetValue() : this->GetSrc2()->AsIntConstOpnd()->GetValue();
Assert(argOutCount >= 0 && argOutCount < UINT32_MAX);
return (uint)argOutCount;
}
PropertySymOpnd *Instr::GetPropertySymOpnd() const
{
if (m_src1 && m_src1->IsSymOpnd() && m_src1->AsSymOpnd()->IsPropertySymOpnd())
{
return m_src1->AsPropertySymOpnd();
}
if (m_dst && m_dst->IsSymOpnd() && m_dst->AsSymOpnd()->IsPropertySymOpnd())
{
return m_dst->AsPropertySymOpnd();
}
return nullptr;
}
bool Instr::CallsAccessor(IR::PropertySymOpnd * methodOpnd)
{
if (methodOpnd)
{
Assert(methodOpnd->HasObjTypeSpecFldInfo());
return methodOpnd->UsesAccessor();
}
return CallsGetter() || CallsSetter();
}
bool Instr::CallsSetter()
{
return
this->IsProfiledInstr() &&
(this->m_dst && this->m_dst->IsSymOpnd() && this->m_dst->AsSymOpnd()->IsPropertySymOpnd()) &&
((this->AsProfiledInstr()->u.FldInfo().flags & Js::FldInfo_FromAccessor) != 0);
}
bool Instr::CallsGetter()
{
return
this->IsProfiledInstr() &&
(this->m_src1 && this->m_src1->IsSymOpnd() && this->m_src1->AsSymOpnd()->IsPropertySymOpnd()) &&
((this->AsProfiledInstr()->u.FldInfo().flags & Js::FldInfo_FromAccessor) != 0);
}
IR::Instr* IR::Instr::NewConstantLoad(IR::RegOpnd* dstOpnd, intptr_t varConst, ValueType type, Func* func, Js::Var varLocal/* = nullptr*/)
{
IR::Opnd *srcOpnd = nullptr;
IR::Instr *instr;
if (Js::TaggedInt::Is(varConst))
{
IntConstType value = Js::TaggedInt::ToInt32((Js::Var)varConst);
instr = IR::Instr::New(Js::OpCode::LdC_A_I4, dstOpnd, IR::IntConstOpnd::New(value, TyInt32, func), func);
if (dstOpnd->m_sym->IsSingleDef())
{
dstOpnd->m_sym->SetIsIntConst(value);
}
}
else
{
if (varConst == func->GetThreadContextInfo()->GetNullFrameDisplayAddr())
{
instr = IR::Instr::New(
Js::OpCode::Ld_A,
dstOpnd,
IR::AddrOpnd::New(
func->GetThreadContextInfo()->GetNullFrameDisplayAddr(),
IR::AddrOpndKindDynamicMisc,
func),
func);
}
else if (varConst == func->GetThreadContextInfo()->GetStrictNullFrameDisplayAddr())
{
instr = IR::Instr::New(
Js::OpCode::Ld_A,
dstOpnd,
IR::AddrOpnd::New(
func->GetThreadContextInfo()->GetStrictNullFrameDisplayAddr(),
IR::AddrOpndKindDynamicMisc,
func),
func);
}
else
{
ValueType valueType;
if(type.IsString())
{
srcOpnd = IR::AddrOpnd::New(varConst, IR::AddrOpndKindDynamicVar, func, true, varLocal);
instr = IR::Instr::New(Js::OpCode::LdStr, dstOpnd, srcOpnd, func);
Assert(dstOpnd->m_sym->m_isSingleDef);
if (dstOpnd->m_sym->IsSingleDef())
{
dstOpnd->m_sym->m_isStrConst = true;
dstOpnd->m_sym->m_isConst = true;
}
dstOpnd->SetValueType(ValueType::String);
srcOpnd->SetValueType(ValueType::String);
}
else if(type.IsNumber())
{
// TODO (michhol): OOP JIT. we may need to unbox before sending over const table
if (!func->IsOOPJIT())
{
srcOpnd = IR::FloatConstOpnd::New((Js::Var)varConst, TyFloat64, func);
}
else
{
srcOpnd = IR::FloatConstOpnd::New((Js::Var)varConst, TyFloat64, func
#if !FLOATVAR
,varLocal
#endif
);
}
instr = IR::Instr::New(Js::OpCode::LdC_A_R8, dstOpnd, srcOpnd, func);
if (dstOpnd->m_sym->IsSingleDef())
{
dstOpnd->m_sym->SetIsFloatConst();
#if FLOATVAR
dstOpnd->m_sym->m_isNotInt = FALSE;
#else
// Don't set m_isNotInt to true if the float constant value is an int32 or uint32. Uint32s may sometimes be
// treated as int32s for the purposes of int specialization.
dstOpnd->m_sym->m_isNotInt = !Js::JavascriptNumber::IsInt32OrUInt32(((IR::FloatConstOpnd*)srcOpnd)->m_value);
#endif
}
}
else
{
if (type.IsUndefined() || type.IsNull() || type.IsBoolean())
{
valueType = type;
}
else
{
valueType = ValueType::GetObject(ObjectType::Object);
}
srcOpnd = IR::AddrOpnd::New(varConst, IR::AddrOpndKindDynamicVar, func, true, varLocal);
instr = IR::Instr::New(Js::OpCode::Ld_A, dstOpnd, srcOpnd, func);
if (dstOpnd->m_sym->IsSingleDef())
{
dstOpnd->m_sym->m_isConst = true;
}
dstOpnd->SetValueType(valueType);
srcOpnd->SetValueType(valueType);
}
}
}
return instr;
}
bool Instr::UsesAllFields()
{
return OpCodeAttr::UseAllFields(this->m_opcode) || this->CallsAccessor();
}
BranchInstr *
Instr::ChangeCmCCToBranchInstr(LabelInstr *targetInstr)
{
Js::OpCode newOpcode;
switch (this->m_opcode)
{
case Js::OpCode::CmEq_A:
newOpcode = Js::OpCode::BrEq_A;
break;
case Js::OpCode::CmGe_A:
newOpcode = Js::OpCode::BrGe_A;
break;
case Js::OpCode::CmGt_A:
newOpcode = Js::OpCode::BrGt_A;
break;
case Js::OpCode::CmLt_A:
newOpcode = Js::OpCode::BrLt_A;
break;
case Js::OpCode::CmLe_A:
newOpcode = Js::OpCode::BrLe_A;
break;
case Js::OpCode::CmUnGe_A:
newOpcode = Js::OpCode::BrUnGe_A;
break;
case Js::OpCode::CmUnGt_A:
newOpcode = Js::OpCode::BrUnGt_A;
break;
case Js::OpCode::CmUnLt_A:
newOpcode = Js::OpCode::BrUnLt_A;
break;
case Js::OpCode::CmUnLe_A:
newOpcode = Js::OpCode::BrUnLe_A;
break;
case Js::OpCode::CmNeq_A:
newOpcode = Js::OpCode::BrNeq_A;
break;
case Js::OpCode::CmSrEq_A:
newOpcode = Js::OpCode::BrSrEq_A;
break;
case Js::OpCode::CmSrNeq_A:
newOpcode = Js::OpCode::BrSrNeq_A;
break;
case Js::OpCode::CmEq_I4:
newOpcode = Js::OpCode::BrEq_I4;
break;
case Js::OpCode::CmGe_I4:
newOpcode = Js::OpCode::BrGe_I4;
break;
case Js::OpCode::CmGt_I4:
newOpcode = Js::OpCode::BrGt_I4;
break;
case Js::OpCode::CmLt_I4:
newOpcode = Js::OpCode::BrLt_I4;
break;
case Js::OpCode::CmLe_I4:
newOpcode = Js::OpCode::BrLe_I4;
break;
case Js::OpCode::CmUnGe_I4:
newOpcode = Js::OpCode::BrUnGe_I4;
break;
case Js::OpCode::CmUnGt_I4:
newOpcode = Js::OpCode::BrUnGt_I4;
break;
case Js::OpCode::CmUnLt_I4:
newOpcode = Js::OpCode::BrUnLt_I4;
break;
case Js::OpCode::CmUnLe_I4:
newOpcode = Js::OpCode::BrUnLe_I4;
break;
case Js::OpCode::CmNeq_I4:
newOpcode = Js::OpCode::BrNeq_I4;
break;
default:
Assert(UNREACHED);
__assume(UNREACHED);
}
BranchInstr *instrBr = BranchInstr::New(newOpcode, targetInstr, this->m_func);
this->InsertBefore(instrBr);
instrBr->SetByteCodeOffset(this);
instrBr->SetSrc1(this->UnlinkSrc1());
instrBr->SetSrc2(this->UnlinkSrc2());
this->Remove();
return instrBr;
}
bool Instr::IsCmCC_A()
{
return (this->m_opcode >= Js::OpCode::CmEq_A && this->m_opcode <= Js::OpCode::CmSrNeq_A) && this->GetSrc1()->IsVar();
}
bool Instr::IsCmCC_R8()
{
return (this->m_opcode >= Js::OpCode::CmEq_A && this->m_opcode <= Js::OpCode::CmSrNeq_A) && this->GetSrc1()->IsFloat64();
}
bool Instr::IsCmCC_I4()
{
return (this->m_opcode >= Js::OpCode::CmEq_I4 && this->m_opcode <= Js::OpCode::CmUnGe_I4);
}
bool Instr::IsNeq()
{
switch (m_opcode)
{
case Js::OpCode::BrNeq_A:
case Js::OpCode::BrNeq_I4:
case Js::OpCode::BrNotEq_A:
case Js::OpCode::BrSrNeq_A:
case Js::OpCode::BrSrNotEq_A:
case Js::OpCode::CmNeq_A:
case Js::OpCode::CmNeq_I4:
case Js::OpCode::CmSrNeq_A:
return true;
default:
return false;
}
}
template <typename T>
bool Instr::BinaryCalculatorT(T src1Const, T src2Const, int64 *pResult)
{
T value = 0;
switch (this->m_opcode)
{
#define BINARY_U(OPCODE,HANDLER) \
case Js::OpCode::##OPCODE: \
value = HANDLER((typename SignedTypeTraits<T>::UnsignedType)src1Const, (typename SignedTypeTraits<T>::UnsignedType)src2Const); \
break;
#define BINARY(OPCODE,HANDLER) \
case Js::OpCode::##OPCODE: \
value = HANDLER(src1Const, src2Const); \
break;
BINARY(CmEq_I4, Js::AsmJsMath::CmpEq)
BINARY(CmNeq_I4, Js::AsmJsMath::CmpNe)
BINARY(CmLt_I4, Js::AsmJsMath::CmpLt)
BINARY(CmGt_I4, Js::AsmJsMath::CmpGt)
BINARY(CmLe_I4, Js::AsmJsMath::CmpLe)
BINARY(CmGe_I4, Js::AsmJsMath::CmpGe)
BINARY_U(CmUnLt_I4, Js::AsmJsMath::CmpLt)
BINARY_U(CmUnGt_I4, Js::AsmJsMath::CmpGt)
BINARY_U(CmUnLe_I4, Js::AsmJsMath::CmpLe)
BINARY_U(CmUnGe_I4, Js::AsmJsMath::CmpGe)
BINARY(Add_I4, Js::AsmJsMath::Add)
BINARY(Sub_I4, Js::AsmJsMath::Sub)
BINARY(Mul_I4, Js::AsmJsMath::Mul)
BINARY(And_I4, Js::AsmJsMath::And)
BINARY(Or_I4, Js::AsmJsMath::Or)
BINARY(Xor_I4, Js::AsmJsMath::Xor)
BINARY(Shl_I4, Wasm::WasmMath::Shl)
BINARY(Shr_I4, Wasm::WasmMath::Shr)
BINARY_U(ShrU_I4, Wasm::WasmMath::ShrU)
BINARY(Div_I4, Js::AsmJsMath::DivChecked)
BINARY_U(DivU_I4, Js::AsmJsMath::DivChecked)
BINARY(Rem_I4, Js::AsmJsMath::RemChecked)
BINARY_U(RemU_I4, Js::AsmJsMath::RemChecked)
default:
return false;
#undef BINARY
#undef BINARY_U
}
*pResult = value;
return true;
}
template bool Instr::BinaryCalculatorT<int>(int src1Const64, int src2Const64, int64 *pResult);
template bool Instr::BinaryCalculatorT<int64>(int64 src1Const64, int64 src2Const64, int64 *pResult);
bool Instr::BinaryCalculator(IntConstType src1Const, IntConstType src2Const, IntConstType *pResult)
{
IntConstType value = 0;
switch (this->m_opcode)
{
case Js::OpCode::Add_A:
if (IntConstMath::Add(src1Const, src2Const, &value))
{
return false;
}
break;
case Js::OpCode::Sub_A:
if (IntConstMath::Sub(src1Const, src2Const, &value))
{
return false;
}
break;
case Js::OpCode::Mul_A:
if (IntConstMath::Mul(src1Const, src2Const, &value))
{
return false;
}
if (value == 0)
{
// might be -0
// Bail for now...
return false;
}
break;
case Js::OpCode::Div_A:
if (src2Const == 0)
{
// Could fold to INF/-INF
// instr->HoistSrc1(Js::OpCode::Ld_A);
return false;
}
if (src1Const == 0 && src2Const < 0)
{
// folds to -0. Bail for now...
return false;
}
if (IntConstMath::Div(src1Const, src2Const, &value))
{
return false;
}
if (src1Const % src2Const != 0)
{
// Bail for now...
return false;
}
break;
case Js::OpCode::Rem_A:
if (src2Const == 0)
{
// Bail for now...
return false;
}
if (IntConstMath::Mod(src1Const, src2Const, &value))
{
return false;
}
if (value == 0)
{
// might be -0
// Bail for now...
return false;
}
break;
case Js::OpCode::Shl_A:
// We don't care about overflow here
IntConstMath::Shl(src1Const, src2Const & 0x1F, &value);
break;
case Js::OpCode::Shr_A:
// We don't care about overflow here, and there shouldn't be any
IntConstMath::Shr(src1Const, src2Const & 0x1F, &value);
break;
case Js::OpCode::ShrU_A:
// We don't care about overflow here, and there shouldn't be any
IntConstMath::ShrU(src1Const, src2Const & 0x1F, &value);
if (value < 0)
{
// ShrU produces a UInt32. If it doesn't fit in an Int32, bail as we don't
// track signs of int values.
return false;
}
break;
case Js::OpCode::And_A:
// We don't care about overflow here, and there shouldn't be any
IntConstMath::And(src1Const, src2Const, &value);
break;
case Js::OpCode::Or_A:
// We don't care about overflow here, and there shouldn't be any
IntConstMath::Or(src1Const, src2Const, &value);
break;
case Js::OpCode::Xor_A:
// We don't care about overflow here, and there shouldn't be any
IntConstMath::Xor(src1Const, src2Const, &value);
break;
case Js::OpCode::InlineMathMin:
value = src1Const < src2Const ? src1Const : src2Const;
break;
case Js::OpCode::InlineMathMax:
value = src1Const > src2Const ? src1Const : src2Const;
break;
default:
return false;
}
*pResult = value;
return true;
}
bool Instr::UnaryCalculator(IntConstType src1Const, IntConstType *pResult)
{
IntConstType value = 0;
switch (this->m_opcode)
{
case Js::OpCode::Neg_A:
if (src1Const == 0)
{
// Could fold to -0.0
return false;
}
if (IntConstMath::Neg(src1Const, &value))
{
return false;
}
break;
case Js::OpCode::Not_A:
IntConstMath::Not(src1Const, &value);
break;
case Js::OpCode::Ld_A:
if (this->HasBailOutInfo())
{
Assert(this->GetBailOutKind() == IR::BailOutExpectingInteger);
this->ClearBailOutInfo();
}
value = src1Const;
break;
case Js::OpCode::Conv_Num:
case Js::OpCode::Ld_I4:
value = src1Const;
break;
case Js::OpCode::Incr_A:
if (IntConstMath::Inc(src1Const, &value))
{
return false;
}
break;
case Js::OpCode::Decr_A:
if (IntConstMath::Dec(src1Const, &value))
{
return false;
}
break;
case Js::OpCode::InlineMathAbs:
if (src1Const == IntConstMin)
{
return false;
}
else
{
value = src1Const < 0 ? -src1Const : src1Const;
}
break;
case Js::OpCode::InlineMathClz:
DWORD clz;
DWORD src1Const32;
src1Const32 = (DWORD)src1Const;
if (_BitScanReverse(&clz, src1Const32))
{
value = 31 - clz;
}
else
{
value = 32;
}
this->ClearBailOutInfo();
break;
case Js::OpCode::InlineMathFloor:
value = src1Const;
this->ClearBailOutInfo();
break;
case Js::OpCode::InlineMathCeil:
value = src1Const;
this->ClearBailOutInfo();
break;
case Js::OpCode::InlineMathRound:
value = src1Const;
this->ClearBailOutInfo();
break;
case Js::OpCode::ToVar:
if (Js::TaggedInt::IsOverflow(src1Const))
{
return false;
}
else
{
value = src1Const;
this->ClearBailOutInfo();
break;
}
default:
return false;
}
*pResult = value;
return true;
}
#if ENABLE_DEBUG_CONFIG_OPTIONS
///----------------------------------------------------------------------------
///
/// Instr::DumpTestTrace
///
/// Dump this instr in TestTrace.
///
///----------------------------------------------------------------------------
void
Instr::DumpTestTrace()
{
Output::Print(_u("opcode: %s "), Js::OpCodeUtil::GetOpCodeName(m_opcode));
SymOpnd * symOpnd;
if (this->m_opcode == Js::OpCode::NewScFunc || this->m_opcode == Js::OpCode::NewScGenFunc)
{
Output::Print(_u("\n"));
return;
}
Opnd * src1 = this->GetSrc1();
if (!src1)
{
Output::Print(_u("\n"));
return;
}
if (src1->GetKind() != OpndKindSym)
{
Output::Print(_u("\n"));
return;
}
symOpnd = src1->AsSymOpnd();
if (symOpnd->m_sym->IsPropertySym())
{
PropertySym *propertySym = symOpnd->m_sym->AsPropertySym();
switch (propertySym->m_fieldKind)
{
case PropertyKindData:
if (!JITManager::GetJITManager()->IsOOPJITEnabled())
{
Js::PropertyRecord const* fieldName = propertySym->GetFunc()->GetInProcThreadContext()->GetPropertyRecord(propertySym->m_propertyId);
Output::Print(_u("field: %s "), fieldName->GetBuffer());
break;
}
// else fall through
case PropertyKindSlots:
Output::Print(_u("field: [%d] "), propertySym->m_propertyId);
break;
case PropertyKindLocalSlots:
Output::Print(_u("field: l[%d] "), propertySym->m_propertyId);
break;
default:
break;
}
Output::Print(_u("\n"));
}
}
///----------------------------------------------------------------------------
///
/// Instr::DumpFieldCopyPropTestTrace
///
/// Dump fieldcopyprop when testtrace is enabled.
///
///----------------------------------------------------------------------------
void
Instr::DumpFieldCopyPropTestTrace()
{
switch (m_opcode)
{
case Js::OpCode::LdSlot:
case Js::OpCode::LdSlotArr:
case Js::OpCode::LdFld:
case Js::OpCode::LdFldForTypeOf:
case Js::OpCode::LdRootFld:
case Js::OpCode::LdRootFldForTypeOf:
case Js::OpCode::LdMethodFld:
case Js::OpCode::LdRootMethodFld:
case Js::OpCode::LdMethodFromFlags:
case Js::OpCode::ScopedLdMethodFld:
case Js::OpCode::TypeofElem:
char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
Output::Print(_u("TestTrace fieldcopyprop: function %s (%s) "),
this->m_func->GetJITFunctionBody()->GetDisplayName(),
this->m_func->GetDebugNumberSet(debugStringBuffer));
if (this->IsInlined())
{
Output::Print(_u("inlined caller function %s (%s) "),
this->m_func->GetTopFunc()->GetJITFunctionBody()->GetDisplayName(),
this->m_func->GetTopFunc()->GetDebugNumberSet(debugStringBuffer));
}
this->DumpTestTrace();
default:
break;
}
}
#endif
#if ENABLE_DEBUG_CONFIG_OPTIONS
const char *
Instr::GetBailOutKindName() const
{
IR::BailOutKind kind = (IR::BailOutKind)0;
if (this->HasBailOutInfo())
{
kind |= this->GetBailOutKind();
}
if (this->HasAuxBailOut())
{
kind |= this->GetAuxBailOutKind();
}
return ::GetBailOutKindName(kind);
}
#endif
//
// Debug dumpers
//
#if DBG_DUMP
void
Instr::DumpByteCodeOffset()
{
if (m_func->HasByteCodeOffset())
{
Output::SkipToColumn(78);
Output::Print(_u("#"));
if (this->m_number != Js::Constants::NoByteCodeOffset)
{
Output::Print(_u("%04x"), this->m_number);
Output::Print(this->IsCloned()? _u("*") : _u(" "));
}
}
if (!this->m_func->IsTopFunc())
{
Output::SkipToColumn(78);
char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
Output::Print(_u(" Func #%s"), this->m_func->GetDebugNumberSet(debugStringBuffer));
}
#ifdef BAILOUT_INJECTION
if (this->bailOutByteCodeLocation != (uint)-1)
{
Output::SkipToColumn(85);
Output::Print(_u("@%4d"), this->bailOutByteCodeLocation);
}
#endif
if (this->m_opcode == Js::OpCode::InlineeStart)
{
Output::Print(_u(" %s"), this->m_func->GetJITFunctionBody()->GetDisplayName());
}
}
void
Instr::DumpGlobOptInstrString()
{
if(this->globOptInstrString)
{
Output::Print(_u("\n\n GLOBOPT INSTR: %s\n\n"), this->globOptInstrString);
}
}
///----------------------------------------------------------------------------
///
/// Instr::Dump
///
/// Dump this instr.
///
///----------------------------------------------------------------------------
void
Instr::Dump(IRDumpFlags flags)
{
bool const AsmDumpMode = flags & IRDumpFlags_AsmDumpMode;
bool const SimpleForm = !!(flags & IRDumpFlags_SimpleForm);
bool const SkipByteCodeOffset = !!(flags & IRDumpFlags_SkipByteCodeOffset);
const auto PrintOpCodeName = [&]() {
Output::SkipToColumn(23);
Output::Print(_u("%s "), Js::OpCodeUtil::GetOpCodeName(m_opcode));
Output::SkipToColumn(38);
};
// forward decl before goto statement
Opnd * dst = nullptr;
if(m_opcode == Js::OpCode::BoundCheck || m_opcode == Js::OpCode::UnsignedBoundCheck)
{
PrintOpCodeName();
// src1 <= src2 + dst
Assert(GetSrc1());
if(GetSrc1()->IsIntConstOpnd())
{
Output::Print(_u("%d"), GetSrc1()->AsIntConstOpnd()->GetValue());
}
else
{
GetSrc1()->Dump(flags, m_func);
}
bool useLessThanOrEqual = true;
bool usePlus = true;
bool dumpSrc2 = false;
int32 offset = GetDst() ? GetDst()->AsIntConstOpnd()->AsInt32() : 0;
if(GetSrc2())
{
if(GetSrc2()->IsIntConstOpnd())
{
#if DBG
int32 temp;
Assert(!Int32Math::Add(offset, GetSrc2()->AsIntConstOpnd()->AsInt32(), &temp));
#endif
offset += GetSrc2()->AsIntConstOpnd()->AsInt32();
}
else
{
dumpSrc2 = true;
if(offset == -1)
{
useLessThanOrEqual = false; // < instead of <=
offset = 0;
}
else if(offset < 0 && offset != IntConstMin)
{
usePlus = false;
offset = -offset;
}
}
}
Output::Print(_u(" %S "), useLessThanOrEqual ? "<=" : "<");
if(dumpSrc2)
{
GetSrc2()->Dump(flags, m_func);
}
if(offset != 0)
{
if(dumpSrc2)
{
Output::Print(_u(" %C "), usePlus ? '+' : '-');
}
Output::Print(_u("%d"), offset);
}
goto PrintByteCodeOffsetEtc;
}
Output::SkipToColumn(4);
dst = this->GetDst();
if (dst)
{
dst->Dump(flags, this->m_func);
bool const dumpMarkTemp = PHASE_DUMP(Js::MarkTempPhase, m_func)
|| PHASE_TRACE(Js::MarkTempPhase, m_func);
bool const dumpMarkTempNumber = dumpMarkTemp || PHASE_DUMP(Js::MarkTempNumberPhase, m_func)
|| PHASE_TRACE(Js::MarkTempNumberPhase, m_func);
bool const dumpMarkTempObject = dumpMarkTemp || PHASE_DUMP(Js::MarkTempObjectPhase, m_func)
|| PHASE_TRACE(Js::MarkTempObjectPhase, m_func);
if ((dumpMarkTempNumber && (this->dstIsTempNumberTransferred || this->dstIsTempNumber))
|| (dumpMarkTempObject && this->dstIsTempObject))
{
Output::Print(_u("["));
if (dumpMarkTempNumber)
{
if (Js::Configuration::Global.flags.Verbose || OpCodeAttr::TempNumberProducing(this->m_opcode))
{
if (this->dstIsTempNumberTransferred)
{
Assert(this->dstIsTempNumber);
Output::Print(_u("x"));
}
else if (this->dstIsTempNumber)
{
Output::Print(_u("#"));
}
}
}
if (dumpMarkTempObject)
{
if (Js::Configuration::Global.flags.Verbose || OpCodeAttr::TempObjectProducing(this->m_opcode))
{
if (this->dstIsTempObject)
{
Output::Print(_u("o"));
}
}
}
Output::Print(_u("tmp]"));
}
if(PHASE_DUMP(Js::TrackNegativeZeroPhase, m_func->GetTopFunc()) && !ShouldCheckForNegativeZero())
{
Output::Print(_u("[-0]"));
}
if (PHASE_DUMP(Js::TypedArrayVirtualPhase, m_func->GetTopFunc()) && (!IsDstNotAlwaysConvertedToInt32() || !IsDstNotAlwaysConvertedToNumber()))
{
if (!IsDstNotAlwaysConvertedToInt32())
Output::Print(_u("[->i]"));
else
Output::Print(_u("[->n]"));
}
if(PHASE_DUMP(Js::TrackIntOverflowPhase, m_func->GetTopFunc()))
{
// ignoring 32-bit overflow ?
if(!ShouldCheckFor32BitOverflow())
{
// ignoring 32-bits or more ?
if(ShouldCheckForNon32BitOverflow())
Output::Print(_u("[OF %d]"), ignoreOverflowBitCount);
else
Output::Print(_u("[OF]"));
}
}
Output::SkipToColumn(20);
Output::Print(_u("="));
}
PrintOpCodeName();
if (this->IsBranchInstr())
{
BranchInstr * branchInstr = this->AsBranchInstr();
LabelInstr * targetInstr = branchInstr->GetTarget();
bool labelPrinted = true;
if (targetInstr == NULL)
{
// Checking the 'm_isMultiBranch' field here directly as well to bypass asserting when tracing IR builder
if(branchInstr->m_isMultiBranch && branchInstr->IsMultiBranch())
{
IR::MultiBranchInstr * multiBranchInstr = branchInstr->AsMultiBrInstr();
// If this MultiBranchInstr has been lowered to a machine instruction, which means
// its opcode is not Js::OpCode::MultiBr, there is no need to print the labels.
if (this->m_opcode == Js::OpCode::MultiBr)
{
multiBranchInstr->MapMultiBrLabels([](IR::LabelInstr * labelInstr) -> void
{
Output::Print(_u("$L%d "), labelInstr->m_id);
});
}
else
{
labelPrinted = false;
}
}
else
{
Output::Print(_u("??"));
}
}
else
{
Output::Print(_u("$L%d"), targetInstr->m_id);
}
if (this->GetSrc1() && labelPrinted)
{
Output::Print(_u(", "));
}
}
else if (this->IsPragmaInstr() && this->m_opcode == Js::OpCode::StatementBoundary)
{
Output::Print(_u("#%d"), this->AsPragmaInstr()->m_statementIndex);
}
// scope
{
Opnd * src1 = this->GetSrc1();
if (this->m_opcode == Js::OpCode::NewScFunc || this->m_opcode == Js::OpCode::NewScGenFunc)
{
Assert(src1->IsIntConstOpnd());
Js::ParseableFunctionInfo * function = nullptr;
if (!m_func->IsOOPJIT())
{
function = ((Js::ParseableFunctionInfo *)m_func->GetJITFunctionBody()->GetAddr())->GetNestedFunctionForExecution((uint)src1->AsIntConstOpnd()->GetValue())->GetParseableFunctionInfo();
}
Output::Print(_u("func:%s()"), function ? function->GetDisplayName() : _u("???"));
Output::Print(_u(", env:"));
this->GetSrc2()->AsRegOpnd()->m_sym->Dump(flags);
}
else if (src1)
{
src1->Dump(flags, this->m_func);
Opnd * src2 = this->GetSrc2();
if (src2)
{
Output::Print(_u(", "));
src2->Dump(flags, this->m_func);
}
}
}
if (this->IsByteCodeUsesInstr())
{
if (this->AsByteCodeUsesInstr()->GetByteCodeUpwardExposedUsed())
{
bool first = true;
FOREACH_BITSET_IN_SPARSEBV(id, this->AsByteCodeUsesInstr()->GetByteCodeUpwardExposedUsed())
{
Output::Print(first? _u("s%d") : _u(", s%d"), id);
first = false;
}
NEXT_BITSET_IN_SPARSEBV;
}
if (this->AsByteCodeUsesInstr()->propertySymUse)
{
Output::Print(_u(" PropSym: %d"), this->AsByteCodeUsesInstr()->propertySymUse->m_id);
}
}
PrintByteCodeOffsetEtc:
if (!AsmDumpMode && !SkipByteCodeOffset)
{
this->DumpByteCodeOffset();
}
if (!SimpleForm)
{
if (this->HasBailOutInfo() || this->HasAuxBailOut())
{
BailOutInfo * bailOutInfo = this->GetBailOutInfo();
Output::SkipToColumn(85);
if (!AsmDumpMode)
{
Output::Print(_u("Bailout: #%04x"), bailOutInfo->bailOutOffset);
}
if (!bailOutInfo->bailOutFunc->IsTopFunc())
{
char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
Output::Print(_u(" Func %s"), bailOutInfo->bailOutFunc->GetDebugNumberSet(debugStringBuffer));
}
Output::Print(_u(" (%S)"), this->GetBailOutKindName());
}
}
if ((flags & IRDumpFlags_SkipEndLine) == 0)
{
Output::Print(_u("\n"));
}
}
///----------------------------------------------------------------------------
///
/// LabelInstr::Dump
///
/// Dump this label.
///
///----------------------------------------------------------------------------
void
LabelInstr::Dump(IRDumpFlags flags)
{
if (this->m_block != NULL)
{
this->m_block->DumpHeader();
}
Output::Print(_u("$L%d:"), this->m_id);
if (this->isOpHelper)
{
Output::Print(_u(" [helper]"));
}
if (this->m_isLoopTop)
{
Output::Print(_u(" >>>>>>>>>>>>> LOOP TOP >>>>>>>>>>>>>"));
}
if (this->IsProfiledLabelInstr())
{
Output::SkipToColumn(50);
switch (this->AsProfiledLabelInstr()->loopImplicitCallFlags)
{
case Js::ImplicitCall_HasNoInfo:
Output::Print(_u("Implicit call: ???"));
break;
case Js::ImplicitCall_None:
Output::Print(_u("Implicit call: no"));
break;
default:
Output::Print(_u("Implicit call: yes"));
break;
}
}
if ((flags & (IRDumpFlags_AsmDumpMode | IRDumpFlags_SkipByteCodeOffset)) == 0)
{
this->DumpByteCodeOffset();
}
Output::Print(_u("\n"));
}
void
PragmaInstr::Dump(IRDumpFlags flags)
{
if (Js::Configuration::Global.flags.PrintSrcInDump && this->m_opcode == Js::OpCode::StatementBoundary)
{
Js::FunctionBody * functionBody = nullptr;
if (!m_func->IsOOPJIT())
{
functionBody = ((Js::FunctionBody*)m_func->GetJITFunctionBody()->GetAddr());
}
if (functionBody)
{
functionBody->PrintStatementSourceLine(this->m_statementIndex);
}
}
__super::Dump(flags);
}
///----------------------------------------------------------------------------
///
/// Instr::Dump
///
/// Dump a window of instructions around this instr.
///
///----------------------------------------------------------------------------
void
Instr::Dump(int window)
{
Instr * instr;
int i;
Output::Print(_u("-------------------------------------------------------------------------------"));
if (this == NULL)
{
return;
}
for (i = 0, instr = this; (instr->m_prev != NULL && i < window/2); instr = instr->m_prev, ++i)
{} // Nothing
for (i = 0; (instr != nullptr && i < window); instr = instr->m_next, ++i)
{
if (instr == this)
{
Output::Print(_u("=>"));
}
instr->Dump();
}
}
void
Instr::Dump()
{
this->Dump(IRDumpFlags_None);
}
void
Instr::DumpSimple()
{
this->Dump(IRDumpFlags_SimpleForm);
}
char16 *
Instr::DumpString()
{
Output::CaptureStart();
this->Dump();
return Output::CaptureEnd();
}
void
Instr::DumpRange(Instr *instrEnd)
{
Output::Print(_u("-------------------------------------------------------------------------------\n"));
FOREACH_INSTR_IN_RANGE(instr, this, instrEnd)
{
instr->Dump();
}
NEXT_INSTR_IN_RANGE;
Output::Print(_u("-------------------------------------------------------------------------------\n"));
}
#endif
} // namespace IR