Skip to content

Commit

Permalink
Stack Arguments Optimization with presence of Formals.
Browse files Browse the repository at this point in the history
Premise:
Whenever there is a use of "arguments" object in a function, we are supposed to create a Heap arguments object on the stack - so that both the formals and arguments element access can access a common memory.

The creation of this "heap arguments" object on the stack is currently optimized(not created) away in the jitted code, when there are no formals present in the function (but not in the presence of formals).

This change enables "heap arguments" object to be optimized away during the presence of formals.

This PR contains the following changes

Dead Store of ArgIns (when the formals are not being used in the body of the function )
PropertyId Array is moved from the Bytecode's auxiliary data to the function body itself.
¿ Layout change of all opcode that currently explicitly uses Property Id Array as one of its source operand.
¿ Byte code cache has been regenerated for Intl and Promise.
¿ Refactoring of all code that reads property id array of formals from auxiliary data.

LdPropId is no more accompanied with LdHeapArguments instruction.

Removal of Heap arguments creation in the jitted code, when the function has formals.
Removal of Scope object creation upon heap arguments optimization.
During javascript stack walking, we no more use the existing heap arguments object on the frame. Instead we create a copy of the heap arguments object and give it to the .caller. This makes us not to worry about other functions changing the formals value, interrupting the stack arguments optimization. -TEST instr at the beginning of the LdElem Fast path is also removed.
Field copy prop is disabled for arguments property (to avoid foo.arguments being field copy-proped)
Functioning of Heap Arguments Optimization with Formals.

We track several Instructions to facilitate this optimization in the forward and backward pass, along with the help from the front end. Parse:
We disable this optimization, when there are any write to formals.
We also disable this optimization, when we have any non-local references inside nested functions, deferred nested functions, and presence of any nested functions.
ForwardPass:

Track Scope object in LdHeapArgs (all versions of this opcode)
Track Formals array in LdSlotArr
Track Formals in LdSlot.
Trace Stack Sym for formals in inlinee.
Insert BailOnStackArgsOutOfActualsRange on LdElemI_A, when optimization is turned on.
DeadStorePass:

We are sure at this point, whether we will be doing the Stack arguments optimization for formals or not.
Insert ArgIns for formals at the beginning of the function.
Replace LdSlot with Ld_A with the tracked stack syms for formals.
Dead store all scope object related instrs (we are sure that only formals use scope object).
Remove BailOnStackArgsOutOfActualsRange, depending on whether the optimization is still turned on or not.
Perf Impact:

This change improves node.js - Specifically acme-benchmark(~16.3%) and tcpSlowPerf (~18.36%).
Speedometer benchmark - gets an overall 12 % improvement (Specifically 30% in react test)
This also has impact in real-world websites.
IE Perf Lab: http://ieperf-web-01/perfresults/Comparison?testRunId=419859&baseRunId=419858&lab=IEPERF&getRelatedRunData=false&priorities=0,1&categories=failures,warnings,improvements,no-change&search=
More than 95% websites show that they get benefited from this optimization [Trace was collected using web crawler].
Other benchmarks look flat.
  • Loading branch information
satheeshravi committed Jun 28, 2016
1 parent f0a9fa7 commit 91e0e91
Show file tree
Hide file tree
Showing 73 changed files with 11,995 additions and 10,541 deletions.
297 changes: 296 additions & 1 deletion lib/Backend/BackwardPass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,84 @@ BackwardPass::CleanupBackwardPassInfoInFlowGraph()
}
NEXT_BLOCK_IN_FUNC_DEAD_OR_ALIVE;
}

/*
* We Insert ArgIns at the start of the function for all the formals.
* Unused formals will be deadstored during the deadstore pass.
* We need ArgIns only for the outermost function(inliner).
*/
void
BackwardPass::InsertArgInsForFormals()
{
if (func->IsStackArgsEnabled() && !func->GetJnFunction()->GetHasImplicitArgIns())
{
IR::Instr * insertAfterInstr = func->m_headInstr->m_next;
AssertMsg(insertAfterInstr->IsLabelInstr(), "First Instr of the first block should always have a label");

Js::ArgSlot paramsCount = insertAfterInstr->m_func->GetJnFunction()->GetInParamsCount() - 1;
IR::Instr * argInInstr = nullptr;
for (Js::ArgSlot argumentIndex = 1; argumentIndex <= paramsCount; argumentIndex++)
{
IR::SymOpnd * srcOpnd;
StackSym * symSrc = StackSym::NewParamSlotSym(argumentIndex + 1, func);
StackSym * symDst = StackSym::New(func);
IR::RegOpnd * dstOpnd = IR::RegOpnd::New(symDst, TyVar, func);

func->SetArgOffset(symSrc, (argumentIndex + LowererMD::GetFormalParamOffset()) * MachPtr);

srcOpnd = IR::SymOpnd::New(symSrc, TyVar, func);

argInInstr = IR::Instr::New(Js::OpCode::ArgIn_A, dstOpnd, srcOpnd, func);
insertAfterInstr->InsertAfter(argInInstr);
insertAfterInstr = argInInstr;

AssertMsg(!func->HasStackSymForFormal(argumentIndex - 1), "Already has a stack sym for this formal?");
this->func->TrackStackSymForFormalIndex(argumentIndex - 1, symDst);
}

if (PHASE_VERBOSE_TRACE1(Js::StackArgFormalsOptPhase) && paramsCount > 0)
{
Output::Print(_u("StackArgFormals : %s (%d) :Inserting ArgIn_A for LdSlot (formals) in the start of Deadstore pass. \n"), func->GetJnFunction()->GetDisplayName(), func->GetJnFunction()->GetFunctionNumber());
Output::Flush();
}
}
}

void
BackwardPass::ProcessBailOnStackArgsOutOfActualsRange()
{
IR::Instr * instr = this->currentInstr;

if (tag == Js::DeadStorePhase && instr->m_opcode == Js::OpCode::LdElemI_A && instr->HasBailOutInfo() && !IsPrePass())
{
if (instr->DoStackArgsOpt(this->func))
{
AssertMsg(instr->GetBailOutKind() & IR::BailOnStackArgsOutOfActualsRange, "Stack args bail out is not set when the optimization is turned on? ");
if (instr->GetBailOutKind() & ~IR::BailOnStackArgsOutOfActualsRange)
{
Assert(instr->GetBailOutKind() == (IR::BailOnStackArgsOutOfActualsRange | IR::BailOutOnImplicitCallsPreOp));
//We are sure at this point, that we will not have any implicit calls as we don't have any call to helper.
instr->SetBailOutKind(IR::BailOnStackArgsOutOfActualsRange);
}
}
else if (instr->GetBailOutKind() & IR::BailOnStackArgsOutOfActualsRange)
{
//If we don't decide to do StackArgs, then remove the bail out at this point.
//We would have optimistically set the bailout in the forward pass, and by the end of forward pass - we
//turned off stack args for some reason. So we are removing it in the deadstore pass.
IR::BailOutKind bailOutKind = instr->GetBailOutKind() & ~IR::BailOnStackArgsOutOfActualsRange;
if (bailOutKind == IR::BailOutInvalid)
{
instr->ClearBailOutInfo();
}
else
{
instr->SetBailOutKind(bailOutKind);
}
}
}
}

void
BackwardPass::Optimize()
{
Expand All @@ -260,6 +338,18 @@ BackwardPass::Optimize()
return;
}

if (tag == Js::DeadStorePhase)
{
if (!this->func->DoLoopFastPaths() || !this->func->DoFastPaths())
{
//arguments[] access is similar to array fast path hence disable when array fastpath is disabled.
//loopFastPath is always true except explicitly disabled
//defaultDoFastPath can be false when we the source code size is huge
func->SetHasStackArgs(false);
}
InsertArgInsForFormals();
}

NoRecoverMemoryJitArenaAllocator localAlloc(tag == Js::BackwardPhase? _u("BE-Backward") : _u("BE-DeadStore"),
this->func->m_alloc->GetPageAllocator(), Js::Throw::OutOfMemory);

Expand Down Expand Up @@ -1818,6 +1908,17 @@ BackwardPass::ProcessBailOutInfo(IR::Instr * instr)
BVSparse<JitArenaAllocator> * byteCodeUpwardExposedUsed = byteCodeUsesInstr->byteCodeUpwardExposedUsed;
if (byteCodeUpwardExposedUsed != nullptr)
{
//Stack Args for Formals optimization.
//We will restore the scope object in the bail out path, when we restore Heap arguments object.
//So we don't to mark the sym for scope object separately for restoration.
//When StackArgs for formal opt is ON , Scope object sym is used by formals access only, which will be replaced by ArgIns and Ld_A
//So it is ok to clear the bit here.
//Clearing the bit in byteCodeUpwardExposedUsed here.
if (instr->m_func->IsStackArgsEnabled() && instr->m_func->GetScopeObjSym())
{
byteCodeUpwardExposedUsed->Clear(instr->m_func->GetScopeObjSym()->m_id);
}

this->currentBlock->byteCodeUpwardExposedUsed->Or(byteCodeUpwardExposedUsed);
#if DBG
FOREACH_BITSET_IN_SPARSEBV(symId, byteCodeUpwardExposedUsed)
Expand Down Expand Up @@ -2415,6 +2516,8 @@ BackwardPass::ProcessBlock(BasicBlock * block)
this->currentInstr = instr;
this->currentRegion = this->currentBlock->GetFirstInstr()->AsLabelInstr()->GetRegion();

ProcessBailOnStackArgsOutOfActualsRange();

if (ProcessNoImplicitCallUses(instr) || this->ProcessBailOutInfo(instr))
{
continue;
Expand All @@ -2427,6 +2530,11 @@ BackwardPass::ProcessBlock(BasicBlock * block)
continue;
}

if (CanDeadStoreInstrForScopeObjRemoval() && DeadStoreOrChangeInstrForScopeObjRemoval())
{
continue;
}

bool hasLiveFields = (block->upwardExposedFields && !block->upwardExposedFields->IsEmpty());

IR::Opnd * opnd = instr->GetDst();
Expand Down Expand Up @@ -2502,6 +2610,11 @@ BackwardPass::ProcessBlock(BasicBlock * block)
{
switch(instr->m_opcode)
{
case Js::OpCode::LdSlot:
{
DeadStoreOrChangeInstrForScopeObjRemoval();
break;
}
case Js::OpCode::InlineArrayPush:
case Js::OpCode::InlineArrayPop:
{
Expand Down Expand Up @@ -2735,6 +2848,162 @@ BackwardPass::ProcessBlock(BasicBlock * block)
#endif
}

bool
BackwardPass::CanDeadStoreInstrForScopeObjRemoval(Sym *sym) const
{
if (tag == Js::DeadStorePhase && this->currentInstr->m_func->IsStackArgsEnabled())
{
Func * currFunc = this->currentInstr->m_func;
switch (this->currentInstr->m_opcode)
{
case Js::OpCode::LdHeapArguments:
case Js::OpCode::LdHeapArgsCached:
case Js::OpCode::LdLetHeapArguments:
case Js::OpCode::LdLetHeapArgsCached:
{
if (this->currentInstr->GetSrc1()->IsScopeObjOpnd(currFunc))
{
/*
* We don't really dead store these instructions. We just want the source sym of these instructions (which is the scope object)
* to NOT be tracked as USED by this instruction.
* In case of LdXXHeapArgsXXX opcodes, they will effectively be lowered to dest = MOV NULL, in the lowerer phase.
*/
return true;
}
break;
}
case Js::OpCode::LdSlot:
{
if (sym && IsFormalParamSym(currFunc, sym))
{
return true;
}
break;
}
case Js::OpCode::CommitScope:
{
return this->currentInstr->GetSrc1()->IsScopeObjOpnd(currFunc);
}
case Js::OpCode::BrFncCachedScopeEq:
case Js::OpCode::BrFncCachedScopeNeq:
{
return this->currentInstr->GetSrc2()->IsScopeObjOpnd(currFunc);
}
}
}
return false;
}

/*
* This is for Eliminating Scope Object Creation during Heap arguments optimization.
*/
bool
BackwardPass::DeadStoreOrChangeInstrForScopeObjRemoval()
{
IR::Instr * instr = this->currentInstr;
Func * currFunc = instr->m_func;

if (this->tag == Js::DeadStorePhase && instr->m_func->IsStackArgsEnabled() && !IsPrePass())
{
switch (instr->m_opcode)
{
/*
* This LdSlot loads the formal from the formals array. We replace this a Ld_A <ArgInSym>.
* ArgInSym is inserted at the beginning of the function during the start of the deadstore pass- for the top func.
* In case of inlinee, it will be from the source sym of the ArgOut Instruction to the inlinee.
*/
case Js::OpCode::LdSlot:
{
IR::Opnd * src1 = instr->GetSrc1();
if (src1 && src1->IsSymOpnd())
{
Sym * sym = src1->AsSymOpnd()->m_sym;
Assert(sym);
if (IsFormalParamSym(currFunc, sym))
{
AssertMsg(!currFunc->GetJnFunction()->GetHasImplicitArgIns(), "We don't have mappings between named formals and arguments object here");

instr->m_opcode = Js::OpCode::Ld_A;
PropertySym * propSym = sym->AsPropertySym();
Js::ArgSlot value = (Js::ArgSlot)propSym->m_propertyId;

Assert(currFunc->HasStackSymForFormal(value));
StackSym * paramStackSym = currFunc->GetStackSymForFormal(value);
instr->ReplaceSrc1(IR::RegOpnd::New(paramStackSym, TyVar, currFunc));
this->currentBlock->upwardExposedUses->Set(paramStackSym->m_id);

if (PHASE_VERBOSE_TRACE1(Js::StackArgFormalsOptPhase))
{
Output::Print(_u("StackArgFormals : %s (%d) :Replacing LdSlot with Ld_A in Deadstore pass. \n"), instr->m_func->GetJnFunction()->GetDisplayName(), instr->m_func->GetJnFunction()->GetFunctionNumber());
Output::Flush();
}
}
}
break;
}
case Js::OpCode::CommitScope:
{
if (instr->GetSrc1()->IsScopeObjOpnd(currFunc))
{
instr->Remove();
return true;
}
break;
}
case Js::OpCode::BrFncCachedScopeEq:
case Js::OpCode::BrFncCachedScopeNeq:
{
if (instr->GetSrc2()->IsScopeObjOpnd(currFunc))
{
instr->Remove();
return true;
}
break;
}
}
}
return false;
}

void
BackwardPass::TraceDeadStoreOfInstrsForScopeObjectRemoval()
{
IR::Instr * instr = this->currentInstr;

if (instr->m_func->IsStackArgsEnabled())
{
if ((instr->m_opcode == Js::OpCode::InitCachedScope || instr->m_opcode == Js::OpCode::NewScopeObject) && !IsPrePass())
{
if (PHASE_TRACE1(Js::StackArgFormalsOptPhase))
{
Output::Print(_u("StackArgFormals : %s (%d) :Removing Scope object creation in Deadstore pass. \n"), instr->m_func->GetJnFunction()->GetDisplayName(), instr->m_func->GetJnFunction()->GetFunctionNumber());
Output::Flush();
}
}
}
}

bool
BackwardPass::IsFormalParamSym(Func * func, Sym * sym) const
{
Assert(sym);

if (sym->IsPropertySym())
{
//If the sym is a propertySym, then see if the propertyId is within the range of the formals
//We can have other properties stored in the scope object other than the formals (following the formals).
PropertySym * propSym = sym->AsPropertySym();
IntConstType value = propSym->m_propertyId;
return func->IsFormalsArraySym(propSym->m_stackSym->m_id) &&
(value >= 0 && value < func->GetJnFunction()->GetInParamsCount() - 1);
}
else
{
Assert(sym->IsStackSym());
return !!func->IsFormalsArraySym(sym->AsStackSym()->m_id);
}
}

#if DBG_DUMP
void
BackwardPass::DumpBlockData(BasicBlock * block)
Expand Down Expand Up @@ -3772,6 +4041,12 @@ bool
BackwardPass::ProcessSymUse(Sym * sym, bool isRegOpndUse, BOOLEAN isNonByteCodeUse)
{
BasicBlock * block = this->currentBlock;

if (CanDeadStoreInstrForScopeObjRemoval(sym))
{
return false;
}

if (sym->IsPropertySym())
{
PropertySym * propertySym = sym->AsPropertySym();
Expand Down Expand Up @@ -6258,7 +6533,14 @@ BackwardPass::DeadStoreInstr(IR::Instr *instr)
tempBv.Copy(this->currentBlock->byteCodeUpwardExposedUsed);
#endif
PropertySym *unusedPropertySym = nullptr;
GlobOpt::TrackByteCodeSymUsed(instr, this->currentBlock->byteCodeUpwardExposedUsed, &unusedPropertySym);

// Do not track the Scope Obj - we will be restoring it in the bailout path while restoring Heap arguments object.
// See InterpreterStackFrame::TrySetFrameObjectInHeapArgObj
if (!(instr->m_func->IsStackArgsEnabled() && instr->m_opcode == Js::OpCode::LdSlotArr &&
instr->GetSrc1() && instr->GetSrc1()->IsScopeObjOpnd(instr->m_func)))
{
GlobOpt::TrackByteCodeSymUsed(instr, this->currentBlock->byteCodeUpwardExposedUsed, &unusedPropertySym);
}
#if DBG
BVSparse<JitArenaAllocator> tempBv2(this->tempAlloc);
tempBv2.Copy(this->currentBlock->byteCodeUpwardExposedUsed);
Expand Down Expand Up @@ -6296,6 +6578,19 @@ BackwardPass::DeadStoreInstr(IR::Instr *instr)
}
#endif


if (instr->m_opcode == Js::OpCode::ArgIn_A)
{
//Ignore tracking ArgIn for "this", as argInsCount only tracks other params - unless it is a asmjs function(which doesn't have a "this").
if (instr->GetSrc1()->AsSymOpnd()->m_sym->AsStackSym()->GetParamSlotNum() != 1 || func->GetJnFunction()->GetIsAsmjsMode())
{
Assert(this->func->argInsCount > 0);
this->func->argInsCount--;
}
}

TraceDeadStoreOfInstrsForScopeObjectRemoval();

block->RemoveInstr(instr);
return true;
}
Expand Down
6 changes: 6 additions & 0 deletions lib/Backend/BackwardPass.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ class BackwardPass
void ProcessLoopCollectionPass(BasicBlock *const lastBlock);
void ProcessLoop(BasicBlock * lastBlock);
void ProcessBlock(BasicBlock * block);
bool IsFormalParamSym(Func * func, Sym * sym) const;
bool CanDeadStoreInstrForScopeObjRemoval(Sym *sym = nullptr) const;
void TraceDeadStoreOfInstrsForScopeObjectRemoval();
void InsertArgInsForFormals();
void ProcessBailOnStackArgsOutOfActualsRange();
bool DeadStoreOrChangeInstrForScopeObjRemoval();
void ProcessUse(IR::Opnd * opnd);
bool ProcessDef(IR::Opnd * opnd);
void ProcessTransfers(IR::Instr * instr);
Expand Down
Loading

0 comments on commit 91e0e91

Please sign in to comment.