blob: 0100c11a8bcb5c43574373ab0af2d2c15026d291 [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"
IR::Instr *Lowerer::PreLowerPeepInstr(IR::Instr *instr, IR::Instr **pInstrPrev)
{
if (PHASE_OFF(Js::PreLowererPeepsPhase, this->m_func))
{
return instr;
}
switch (instr->m_opcode)
{
#if defined(_M_IX86) || defined(_M_X64)
// this sort of addressing mode magic only applies to x86 platforms
case Js::OpCode::Add_I4:
instr = this->PeepShiftAdd(instr);
*pInstrPrev = instr->m_prev;
break;
#endif
case Js::OpCode::Shl_I4:
instr = this->PeepShl(instr);
*pInstrPrev = instr->m_prev;
break;
case Js::OpCode::BrTrue_I4:
case Js::OpCode::BrFalse_I4:
instr = this->PeepBrBool(instr);
*pInstrPrev = instr->m_prev;
break;
}
return instr;
}
IR::Instr *
Lowerer::TryShiftAdd(IR::Instr *instrAdd, IR::Opnd * opndFold, IR::Opnd * opndAdd)
{
Assert(instrAdd->m_opcode == Js::OpCode::Add_I4);
if (!opndFold->GetIsDead())
{
return instrAdd;
}
if (!(opndAdd->IsRegOpnd() || (opndAdd->IsInt32() && opndAdd->IsIntConstOpnd())))
{
return instrAdd;
}
if (!opndFold->IsRegOpnd() || !opndFold->AsRegOpnd()->m_sym->IsSingleDef())
{
return instrAdd;
}
IR::Instr * instrDef = opndFold->AsRegOpnd()->m_sym->GetInstrDef();
if ((instrDef->m_opcode != Js::OpCode::Shl_I4 && instrDef->m_opcode != Js::OpCode::Mul_I4) || !instrDef->GetSrc1()->IsRegOpnd() || !instrDef->GetSrc2()->IsIntConstOpnd())
{
return instrAdd;
}
if (instrDef->HasBailOutInfo())
{
return instrAdd;
}
byte scale = 0;
IntConstType constVal = instrDef->GetSrc2()->AsIntConstOpnd()->GetValue();
if (instrDef->m_opcode == Js::OpCode::Shl_I4)
{
if (constVal < 0 || constVal > 3)
{
return instrAdd;
}
scale = (byte)constVal;
}
else
{
Assert(instrDef->m_opcode == Js::OpCode::Mul_I4);
switch (constVal)
{
case 1:
scale = 0;
break;
case 2:
scale = 1;
break;
case 4:
scale = 2;
break;
case 8:
scale = 3;
break;
default:
return instrAdd;
}
}
StackSym * defSrc1Sym = instrDef->GetSrc1()->AsRegOpnd()->m_sym;
StackSym * foldSym = opndFold->AsRegOpnd()->m_sym;
FOREACH_INSTR_IN_RANGE(instrIter, instrDef->m_next, instrAdd->m_prev)
{
// if any branch between def-use, don't do peeps on it because branch target might depend on the def
if (instrIter->IsBranchInstr())
{
return instrAdd;
}
if (instrIter->HasBailOutInfo())
{
return instrAdd;
}
if (instrIter->FindRegDef(defSrc1Sym))
{
return instrAdd;
}
if (instrIter->FindRegUse(foldSym))
{
return instrAdd;
}
} NEXT_INSTR_IN_RANGE;
#if DBG_DUMP
if (PHASE_TRACE(Js::PreLowererPeepsPhase, instrAdd->m_func))
{
Output::Print(_u("PeepShiftAdd : %s (%d) : folding:\n"), instrAdd->m_func->GetJITFunctionBody()->GetDisplayName(), instrAdd->m_func->GetFunctionNumber());
instrDef->Dump();
instrAdd->Dump();
}
#endif
IR::IndirOpnd * leaOpnd = nullptr;
if (opndAdd->IsRegOpnd())
{
leaOpnd = IR::IndirOpnd::New(opndAdd->AsRegOpnd(), instrDef->UnlinkSrc1()->AsRegOpnd(), scale, opndFold->GetType(), instrAdd->m_func);
}
else
{
Assert(opndAdd->IsInt32() && opndAdd->IsIntConstOpnd());
leaOpnd = IR::IndirOpnd::New(instrDef->UnlinkSrc1()->AsRegOpnd(), opndAdd->AsIntConstOpnd()->AsInt32(), scale, opndFold->GetType(), instrAdd->m_func);
}
IR::Instr * leaInstr = InsertLea(instrAdd->UnlinkDst()->AsRegOpnd(), leaOpnd, instrAdd);
#if DBG_DUMP
if (PHASE_TRACE(Js::PreLowererPeepsPhase, instrAdd->m_func))
{
Output::Print(_u("into:\n"), instrAdd->m_func->GetJITFunctionBody()->GetDisplayName(), instrAdd->m_func->GetFunctionNumber());
leaInstr->Dump();
}
#endif
instrAdd->Remove();
instrDef->Remove();
return leaInstr;
}
IR::Instr *
Lowerer::PeepShiftAdd(IR::Instr *instrAdd)
{
Assert(instrAdd->m_opcode == Js::OpCode::Add_I4);
// Peep:
// t1 = SHL X, 0|1|2|3 / t1 = MUL X, 1|2|4|8
// t2 = ADD t1, Y
//
// Into:
// t2 = LEA [X * scale + Y]
if (instrAdd->HasBailOutInfo())
{
return instrAdd;
}
if (!instrAdd->GetDst()->IsRegOpnd())
{
return instrAdd;
}
IR::Opnd * src2 = instrAdd->GetSrc2();
IR::Opnd * src1 = instrAdd->GetSrc1();
// we can't remove t1 in case both srcs are uses of t1
if (src1->IsEqual(src2))
{
return instrAdd;
}
IR::Instr * resultInstr = TryShiftAdd(instrAdd, src1, src2);
if (resultInstr->m_opcode == Js::OpCode::Add_I4)
{
resultInstr = TryShiftAdd(instrAdd, src2, src1);
}
return resultInstr;
}
IR::Instr *Lowerer::PeepShl(IR::Instr *instrShl)
{
IR::Opnd *src1;
IR::Opnd *src2;
IR::Instr *instrDef;
src1 = instrShl->GetSrc1();
src2 = instrShl->GetSrc2();
// Peep:
// t1 = SHR X, cst
// t2 = SHL t1, cst
//
// Into:
// t2 = AND X, mask
if (!src1->IsRegOpnd() || !src2->IsIntConstOpnd())
{
return instrShl;
}
if (!src1->AsRegOpnd()->m_sym->IsSingleDef())
{
return instrShl;
}
if (instrShl->HasBailOutInfo())
{
return instrShl;
}
instrDef = src1->AsRegOpnd()->m_sym->GetInstrDef();
if (instrDef->m_opcode != Js::OpCode::Shr_I4 || !instrDef->GetSrc2()->IsIntConstOpnd()
|| instrDef->GetSrc2()->AsIntConstOpnd()->GetValue() != src2->AsIntConstOpnd()->GetValue()
|| !instrDef->GetSrc1()->IsRegOpnd())
{
return instrShl;
}
if (!src1->GetIsDead())
{
return instrShl;
}
if (instrDef->HasBailOutInfo())
{
return instrShl;
}
FOREACH_INSTR_IN_RANGE(instrIter, instrDef->m_next, instrShl->m_prev)
{
if (instrIter->HasBailOutInfo())
{
return instrShl;
}
if (instrIter->FindRegDef(instrDef->GetSrc1()->AsRegOpnd()->m_sym))
{
return instrShl;
}
if (instrIter->FindRegUse(src1->AsRegOpnd()->m_sym))
{
return instrShl;
}
// if any branch between def-use, don't do peeps on it because branch target might depend on the def
if (instrIter->IsBranchInstr())
{
return instrShl;
}
} NEXT_INSTR_IN_RANGE;
instrShl->FreeSrc1();
instrShl->SetSrc1(instrDef->UnlinkSrc1());
instrDef->Remove();
IntConstType oldValue = src2->AsIntConstOpnd()->GetValue();
// Left shift operator (<<) on arm32 is implemented by LSL which doesn't discard bits beyond lowerest 5-bit.
// Need to discard such bits to conform to << in JavaScript. This is not a problem for x86 and x64 because
// behavior of SHL is consistent with JavaScript but keep the below line for clarity.
oldValue %= sizeof(int32) * 8;
oldValue = ~((1 << oldValue) - 1);
src2->AsIntConstOpnd()->SetValue(oldValue);
instrShl->m_opcode = Js::OpCode::And_I4;
return instrShl;
}
IR::Instr *Lowerer::PeepBrBool(IR::Instr *instrBr)
{
IR::Opnd *src1;
IR::Instr *instrBinOp, *instrCm1, *instrCm2;
// Peep:
// t1 = CmCC_I4 a, b
// t2 = CmCC_i4 c, d
// t3 = AND/OR t1, t2
// BrTrue/False t3, $L_true
//
// Into:
// BrCC a, b, $L_true/false
// BrCC c, d, $L_true
//$L_false:
src1 = instrBr->GetSrc1();
if (!src1->IsRegOpnd())
{
return instrBr;
}
Assert(!instrBr->HasBailOutInfo());
instrBinOp = instrBr->GetPrevRealInstrOrLabel();
if (instrBinOp->m_opcode != Js::OpCode::And_I4 && instrBinOp->m_opcode != Js::OpCode::Or_I4)
{
return instrBr;
}
if (!instrBinOp->GetDst()->IsEqual(src1))
{
return instrBr;
}
IR::RegOpnd *src1Reg = src1->AsRegOpnd();
if (!src1Reg->m_sym->IsSingleDef() || !src1Reg->GetIsDead())
{
return instrBr;
}
Assert(!instrBinOp->HasBailOutInfo());
instrCm2 = instrBinOp->GetPrevRealInstrOrLabel();
if (!instrCm2->IsCmCC_I4())
{
return instrBr;
}
IR::RegOpnd *cm2DstReg = instrCm2->GetDst()->AsRegOpnd();
if (!cm2DstReg->m_sym->IsSingleDef())
{
return instrBr;
}
if (cm2DstReg->IsEqual(instrBinOp->GetSrc1()))
{
if (!instrBinOp->GetSrc1()->AsRegOpnd()->GetIsDead())
{
return instrBr;
}
}
else if (cm2DstReg->IsEqual(instrBinOp->GetSrc2()))
{
if (!instrBinOp->GetSrc2()->AsRegOpnd()->GetIsDead())
{
return instrBr;
}
}
else
{
return instrBr;
}
Assert(!instrCm2->HasBailOutInfo());
instrCm1 = instrCm2->GetPrevRealInstrOrLabel();
if (!instrCm1->IsCmCC_I4())
{
return instrBr;
}
Assert(!instrCm1->GetDst()->IsEqual(instrCm2->GetDst()));
IR::RegOpnd *cm1DstReg = instrCm1->GetDst()->AsRegOpnd();
if (!cm1DstReg->m_sym->IsSingleDef())
{
return instrBr;
}
if (cm1DstReg->IsEqual(instrBinOp->GetSrc1()))
{
if (!instrBinOp->GetSrc1()->AsRegOpnd()->GetIsDead())
{
return instrBr;
}
}
else if (cm1DstReg->IsEqual(instrBinOp->GetSrc2()))
{
if (!instrBinOp->GetSrc2()->AsRegOpnd()->GetIsDead())
{
return instrBr;
}
}
else
{
return instrBr;
}
Assert(!instrCm1->HasBailOutInfo());
IR::LabelInstr *falseLabel = instrBr->AsBranchInstr()->GetTarget();
IR::LabelInstr *trueLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
instrBr->InsertAfter(trueLabel);
IR::BranchInstr *instrBr1;
IR::BranchInstr *instrBr2;
if (instrBinOp->m_opcode == Js::OpCode::And_I4)
{
instrBr1 = instrCm1->ChangeCmCCToBranchInstr(instrBr->m_opcode == Js::OpCode::BrFalse_I4 ? falseLabel : trueLabel);
instrBr1->Invert();
instrBr2 = instrCm2->ChangeCmCCToBranchInstr(falseLabel);
if (instrBr->m_opcode == Js::OpCode::BrFalse_I4)
{
instrBr2->Invert();
}
}
else
{
Assert(instrBinOp->m_opcode == Js::OpCode::Or_I4);
instrBr1 = instrCm1->ChangeCmCCToBranchInstr(instrBr->m_opcode == Js::OpCode::BrTrue_I4 ? falseLabel : trueLabel);
instrBr2 = instrCm2->ChangeCmCCToBranchInstr(falseLabel);
if (instrBr->m_opcode == Js::OpCode::BrFalse_I4)
{
instrBr2->Invert();
}
}
instrBinOp->Remove();
instrBr->Remove();
return instrBr2;
}