| //------------------------------------------------------------------------------------------------------- |
| // Copyright (C) Microsoft Corporation and contributors. All rights reserved. |
| // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. |
| //------------------------------------------------------------------------------------------------------- |
| #include "Backend.h" |
| |
| |
| uint8 OpCodeToHash[(uint)Js::OpCode::Count]; |
| static Js::OpCode HashToOpCode[(uint)Js::OpCode::Count]; |
| |
| class CSEInit |
| { |
| public: |
| // Initializer for OpCodeToHash and HashToOpCode maps. |
| CSEInit() |
| { |
| uint8 hash = 1; |
| |
| for (Js::OpCode opcode = (Js::OpCode)0; opcode < Js::OpCode::Count; opcode = (Js::OpCode)(opcode + 1)) |
| { |
| if (Js::OpCodeUtil::IsValidOpcode(opcode) && OpCodeAttr::CanCSE(opcode) && !OpCodeAttr::ByteCodeOnly(opcode)) |
| { |
| OpCodeToHash[(int)opcode] = hash; |
| HashToOpCode[hash] = opcode; |
| hash++; |
| AssertMsg(hash != 0, "Too many CSE'able opcodes"); |
| } |
| |
| } |
| } |
| }CSEInit_Dummy; |
| |
| |
| bool |
| GlobOpt::GetHash(IR::Instr *instr, Value *src1Val, Value *src2Val, ExprAttributes exprAttributes, ExprHash *pHash) |
| { |
| Js::OpCode opcode = instr->m_opcode; |
| |
| // Candidate? |
| if (!OpCodeAttr::CanCSE(opcode) && (opcode != Js::OpCode::StElemI_A && opcode != Js::OpCode::StElemI_A_Strict)) |
| { |
| return false; |
| } |
| |
| ValueNumber valNum1 = 0; |
| ValueNumber valNum2 = 0; |
| |
| // Get the value number of src1 and src2 |
| if (instr->GetSrc1()) |
| { |
| if (!src1Val) |
| { |
| return false; |
| } |
| valNum1 = src1Val->GetValueNumber(); |
| if (instr->GetSrc2()) |
| { |
| if (!src2Val) |
| { |
| return false; |
| } |
| valNum2 = src2Val->GetValueNumber(); |
| } |
| } |
| |
| if (src1Val) |
| { |
| valNum1 = src1Val->GetValueNumber(); |
| if (src2Val) |
| { |
| valNum2 = src2Val->GetValueNumber(); |
| } |
| } |
| |
| switch (opcode) |
| { |
| case Js::OpCode::Ld_I4: |
| case Js::OpCode::Ld_A: |
| // Copy-prop should handle these |
| return false; |
| case Js::OpCode::Add_I4: |
| opcode = Js::OpCode::Add_A; |
| break; |
| case Js::OpCode::Sub_I4: |
| opcode = Js::OpCode::Sub_A; |
| break; |
| case Js::OpCode::Mul_I4: |
| opcode = Js::OpCode::Mul_A; |
| break; |
| case Js::OpCode::Rem_I4: |
| opcode = Js::OpCode::Rem_A; |
| break; |
| case Js::OpCode::Div_I4: |
| opcode = Js::OpCode::Div_A; |
| break; |
| case Js::OpCode::Neg_I4: |
| opcode = Js::OpCode::Neg_A; |
| break; |
| case Js::OpCode::Not_I4: |
| opcode = Js::OpCode::Not_A; |
| break; |
| case Js::OpCode::And_I4: |
| opcode = Js::OpCode::And_A; |
| break; |
| case Js::OpCode::Or_I4: |
| opcode = Js::OpCode::Or_A; |
| break; |
| case Js::OpCode::Xor_I4: |
| opcode = Js::OpCode::Xor_A; |
| break; |
| case Js::OpCode::Shl_I4: |
| opcode = Js::OpCode::Shl_A; |
| break; |
| case Js::OpCode::Shr_I4: |
| opcode = Js::OpCode::Shr_A; |
| break; |
| case Js::OpCode::ShrU_I4: |
| opcode = Js::OpCode::ShrU_A; |
| break; |
| case Js::OpCode::StElemI_A: |
| case Js::OpCode::StElemI_A_Strict: |
| // Make the load available as a CSE |
| opcode = Js::OpCode::LdElemI_A; |
| break; |
| case Js::OpCode::CmEq_I4: |
| opcode = Js::OpCode::CmEq_A; |
| break; |
| case Js::OpCode::CmNeq_I4: |
| opcode = Js::OpCode::CmNeq_A; |
| break; |
| case Js::OpCode::CmLt_I4: |
| opcode = Js::OpCode::CmLt_A; |
| break; |
| case Js::OpCode::CmLe_I4: |
| opcode = Js::OpCode::CmLe_A; |
| break; |
| case Js::OpCode::CmGt_I4: |
| opcode = Js::OpCode::CmGt_A; |
| break; |
| case Js::OpCode::CmGe_I4: |
| opcode = Js::OpCode::CmGe_A; |
| break; |
| case Js::OpCode::CmUnLt_I4: |
| opcode = Js::OpCode::CmUnLt_A; |
| break; |
| case Js::OpCode::CmUnLe_I4: |
| opcode = Js::OpCode::CmUnLe_A; |
| break; |
| case Js::OpCode::CmUnGt_I4: |
| opcode = Js::OpCode::CmUnGt_A; |
| break; |
| case Js::OpCode::CmUnGe_I4: |
| opcode = Js::OpCode::CmUnGe_A; |
| break; |
| } |
| |
| pHash->Init(opcode, valNum1, valNum2, exprAttributes); |
| |
| #if DBG_DUMP |
| if (!pHash->IsValid() && Js::Configuration::Global.flags.Trace.IsEnabled(Js::CSEPhase, this->func->GetSourceContextId(), this->func->GetLocalFunctionId())) |
| { |
| Output::Print(_u(" >>>> CSE: Value numbers too big to be hashed in function %s!\n"), this->func->GetJITFunctionBody()->GetDisplayName()); |
| } |
| #endif |
| #if ENABLE_DEBUG_CONFIG_OPTIONS |
| if (!pHash->IsValid() && Js::Configuration::Global.flags.TestTrace.IsEnabled(Js::CSEPhase, this->func->GetSourceContextId(), this->func->GetLocalFunctionId())) |
| { |
| Output::Print(_u(" >>>> CSE: Value numbers too big to be hashed in function %s!\n"), this->func->GetJITFunctionBody()->GetDisplayName()); |
| } |
| #endif |
| |
| return pHash->IsValid(); |
| } |
| |
| void |
| GlobOpt::CSEAddInstr( |
| BasicBlock *block, |
| IR::Instr *instr, |
| Value *dstVal, |
| Value *src1Val, |
| Value *src2Val, |
| Value *dstIndirIndexVal, |
| Value *src1IndirIndexVal) |
| { |
| ExprAttributes exprAttributes; |
| ExprHash hash; |
| |
| if (!this->DoCSE()) |
| { |
| return; |
| } |
| |
| bool isArray = false; |
| |
| switch(instr->m_opcode) |
| { |
| case Js::OpCode::LdElemI_A: |
| case Js::OpCode::LdArrViewElem: |
| case Js::OpCode::StElemI_A: |
| case Js::OpCode::StElemI_A_Strict: |
| { |
| // For arrays, hash the value # of the baseOpnd and indexOpnd |
| IR::IndirOpnd *arrayOpnd; |
| Value *indirIndexVal; |
| if (instr->m_opcode == Js::OpCode::StElemI_A || instr->m_opcode == Js::OpCode::StElemI_A_Strict) |
| { |
| if (!this->CanCSEArrayStore(instr)) |
| { |
| return; |
| } |
| dstVal = src1Val; |
| arrayOpnd = instr->GetDst()->AsIndirOpnd(); |
| indirIndexVal = dstIndirIndexVal; |
| } |
| else |
| { |
| // all the LdElem and Ld*ArrViewElem |
| arrayOpnd = instr->GetSrc1()->AsIndirOpnd(); |
| indirIndexVal = src1IndirIndexVal; |
| } |
| |
| src1Val = block->globOptData.FindValue(arrayOpnd->GetBaseOpnd()->m_sym); |
| if(indirIndexVal) |
| { |
| src2Val = indirIndexVal; |
| } |
| else if (arrayOpnd->GetIndexOpnd()) |
| { |
| src2Val = block->globOptData.FindValue(arrayOpnd->GetIndexOpnd()->m_sym); |
| } |
| else |
| { |
| return; |
| } |
| isArray = true; |
| |
| // for typed array do not add instructions whose dst are guaranteed to be int or number |
| // as we will try to eliminate bound check for these typed arrays |
| if (src1Val->GetValueInfo()->IsLikelyOptimizedVirtualTypedArray()) |
| { |
| exprAttributes = DstIsIntOrNumberAttributes(!instr->dstIsAlwaysConvertedToInt32, !instr->dstIsAlwaysConvertedToNumber); |
| } |
| break; |
| } |
| |
| case Js::OpCode::Mul_I4: |
| // If int32 overflow is ignored, we only add MULs with 53-bit overflow check to expr map |
| if (instr->HasBailOutInfo() && (instr->GetBailOutKind() & IR::BailOutOnMulOverflow) && |
| !instr->ShouldCheckFor32BitOverflow() && instr->ignoreOverflowBitCount != 53) |
| { |
| return; |
| } |
| |
| // fall-through |
| |
| case Js::OpCode::Neg_I4: |
| case Js::OpCode::Add_I4: |
| case Js::OpCode::Sub_I4: |
| case Js::OpCode::DivU_I4: |
| case Js::OpCode::Div_I4: |
| case Js::OpCode::RemU_I4: |
| case Js::OpCode::Rem_I4: |
| case Js::OpCode::ShrU_I4: |
| { |
| // Can't CSE and Add where overflow doesn't matter (and no bailout) with one where it does matter... Record whether int |
| // overflow or negative zero were ignored. |
| exprAttributes = IntMathExprAttributes(ignoredIntOverflowForCurrentInstr, ignoredNegativeZeroForCurrentInstr); |
| break; |
| } |
| |
| case Js::OpCode::InlineMathFloor: |
| case Js::OpCode::InlineMathCeil: |
| case Js::OpCode::InlineMathRound: |
| if (!instr->ShouldCheckForNegativeZero()) |
| { |
| return; |
| } |
| break; |
| |
| case Js::OpCode::Conv_Prim: |
| exprAttributes = ConvAttributes(instr->GetDst()->IsUnsigned(), instr->GetSrc1()->IsUnsigned()); |
| break; |
| } |
| |
| ValueInfo *valueInfo = NULL; |
| |
| if (instr->GetDst()) |
| { |
| if (!dstVal) |
| { |
| return; |
| } |
| |
| valueInfo = dstVal->GetValueInfo(); |
| if(valueInfo->GetSymStore() == NULL && |
| !(isArray && valueInfo->HasIntConstantValue() && valueInfo->IsIntAndLikelyTagged() && instr->GetSrc1()->IsAddrOpnd())) |
| { |
| return; |
| } |
| } |
| |
| if (!this->GetHash(instr, src1Val, src2Val, exprAttributes, &hash)) |
| { |
| return; |
| } |
| |
| int32 intConstantValue; |
| if(valueInfo && !valueInfo->GetSymStore() && valueInfo->TryGetIntConstantValue(&intConstantValue)) |
| { |
| Assert(isArray); |
| Assert(valueInfo->IsIntAndLikelyTagged()); |
| Assert(instr->GetSrc1()->IsAddrOpnd()); |
| |
| // We need a sym associated with a value in the expression value table. Hoist the address into a stack sym associated |
| // with the int constant value. |
| StackSym *const constStackSym = GetOrCreateTaggedIntConstantStackSym(intConstantValue); |
| instr->HoistSrc1(Js::OpCode::Ld_A, RegNOREG, constStackSym); |
| currentBlock->globOptData.SetValue(dstVal, constStackSym); |
| } |
| |
| // We have a candidate. Add it to the exprToValueMap. |
| Value ** pVal = block->globOptData.exprToValueMap->FindOrInsertNew(hash); |
| *pVal = dstVal; |
| |
| if (isArray) |
| { |
| block->globOptData.liveArrayValues->Set(hash); |
| } |
| |
| if (MayNeedBailOnImplicitCall(instr, src1Val, src2Val)) |
| { |
| this->currentBlock->globOptData.hasCSECandidates = true; |
| |
| // Use LiveFields to track is object.valueOf/toString could get overridden. |
| IR::Opnd *src1 = instr->GetSrc1(); |
| if (src1) |
| { |
| if (src1->IsRegOpnd()) |
| { |
| StackSym *varSym = src1->AsRegOpnd()->m_sym; |
| |
| if (varSym->IsTypeSpec()) |
| { |
| varSym = varSym->GetVarEquivSym(this->func); |
| } |
| block->globOptData.liveFields->Set(varSym->m_id); |
| } |
| IR::Opnd *src2 = instr->GetSrc2(); |
| if (src2 && src2->IsRegOpnd()) |
| { |
| StackSym *varSym = src2->AsRegOpnd()->m_sym; |
| |
| if (varSym->IsTypeSpec()) |
| { |
| varSym = varSym->GetVarEquivSym(this->func); |
| } |
| block->globOptData.liveFields->Set(varSym->m_id); |
| } |
| } |
| } |
| } |
| |
| |
| static void TransformIntoUnreachable(IntConstType errorCode, IR::Instr* instr) |
| { |
| instr->m_opcode = Js::OpCode::Unreachable_Void; |
| instr->ReplaceSrc1(IR::IntConstOpnd::New(SCODE_CODE(errorCode), TyInt32, instr->m_func)); |
| instr->UnlinkDst(); |
| } |
| |
| void |
| GlobOpt::OptimizeChecks(IR::Instr * const instr) |
| { |
| IR::Opnd* src1 = instr->GetSrc1(); |
| IR::Opnd* src2 = instr->GetSrc2(); |
| |
| switch (instr->m_opcode) |
| { |
| case Js::OpCode::TrapIfZero: |
| if (src1 && src1->IsImmediateOpnd()) |
| { |
| int64 val = src1->GetImmediateValue(func); |
| if (val != 0) |
| { |
| instr->m_opcode = Js::OpCode::Ld_I4; |
| } |
| else |
| { |
| TransformIntoUnreachable(WASMERR_DivideByZero, instr); |
| InsertByteCodeUses(instr); |
| RemoveCodeAfterNoFallthroughInstr(instr); //remove dead code |
| } |
| } |
| break; |
| case Js::OpCode::TrapIfMinIntOverNegOne: |
| { |
| int checksLeft = 2; |
| if (src1 && src1->IsImmediateOpnd()) |
| { |
| int64 val = src1->GetImmediateValue(func); |
| bool isMintInt = src1->GetSize() == 8 ? val == LONGLONG_MIN : (int32)val == INT_MIN; |
| if (!isMintInt) |
| { |
| instr->m_opcode = Js::OpCode::Ld_I4; |
| } |
| else |
| { |
| checksLeft--; |
| } |
| } |
| if (src2 && src2->IsImmediateOpnd()) |
| { |
| int64 val = src2->GetImmediateValue(func); |
| bool isNegOne = src2->GetSize() == 8 ? val == -1 : (int32)val == -1; |
| if (!isNegOne) |
| { |
| instr->m_opcode = Js::OpCode::Ld_I4; |
| } |
| else |
| { |
| checksLeft--; |
| } |
| } |
| |
| if (!checksLeft) |
| { |
| TransformIntoUnreachable(VBSERR_Overflow, instr); |
| instr->FreeSrc2(); |
| InsertByteCodeUses(instr); |
| RemoveCodeAfterNoFallthroughInstr(instr); //remove dead code |
| } |
| else if (instr->m_opcode == Js::OpCode::Ld_I4) |
| { |
| instr->FreeSrc2(); |
| } |
| break; |
| } |
| default: |
| return; |
| } |
| } |
| |
| bool |
| GlobOpt::CSEOptimize(BasicBlock *block, IR::Instr * *const instrRef, Value **pSrc1Val, Value **pSrc2Val, Value **pSrc1IndirIndexVal, bool intMathExprOnly) |
| { |
| Assert(instrRef); |
| IR::Instr *&instr = *instrRef; |
| Assert(instr); |
| if (!this->DoCSE()) |
| { |
| return false; |
| } |
| |
| Value *src1Val = *pSrc1Val; |
| Value *src2Val = *pSrc2Val; |
| Value *src1IndirIndexVal = *pSrc1IndirIndexVal; |
| bool isArray = false; |
| ExprAttributes exprAttributes; |
| ExprHash hash; |
| |
| // For arrays, hash the value # of the baseOpnd and indexOpnd |
| switch(instr->m_opcode) |
| { |
| case Js::OpCode::LdArrViewElem: |
| case Js::OpCode::LdElemI_A: |
| { |
| if(intMathExprOnly) |
| { |
| return false; |
| } |
| |
| IR::IndirOpnd *arrayOpnd = instr->GetSrc1()->AsIndirOpnd(); |
| |
| src1Val = block->globOptData.FindValue(arrayOpnd->GetBaseOpnd()->m_sym); |
| if(src1IndirIndexVal) |
| { |
| src2Val = src1IndirIndexVal; |
| } |
| else if (arrayOpnd->GetIndexOpnd()) |
| { |
| src2Val = block->globOptData.FindValue(arrayOpnd->GetIndexOpnd()->m_sym); |
| } |
| else |
| { |
| return false; |
| } |
| // for typed array do not add instructions whose dst are guaranteed to be int or number |
| // as we will try to eliminate bound check for these typed arrays |
| if (src1Val->GetValueInfo()->IsLikelyOptimizedVirtualTypedArray()) |
| { |
| exprAttributes = DstIsIntOrNumberAttributes(!instr->dstIsAlwaysConvertedToInt32, !instr->dstIsAlwaysConvertedToNumber); |
| } |
| isArray = true; |
| break; |
| } |
| |
| case Js::OpCode::Neg_A: |
| case Js::OpCode::Add_A: |
| case Js::OpCode::Sub_A: |
| case Js::OpCode::Mul_A: |
| case Js::OpCode::Div_A: |
| case Js::OpCode::Rem_A: |
| case Js::OpCode::ShrU_A: |
| // If the previously-computed matching expression ignored int overflow or negative zero, those attributes must match |
| // to be able to CSE this expression |
| if(intMathExprOnly && !ignoredIntOverflowForCurrentInstr && !ignoredNegativeZeroForCurrentInstr) |
| { |
| // Already tried CSE with default attributes |
| return false; |
| } |
| exprAttributes = IntMathExprAttributes(ignoredIntOverflowForCurrentInstr, ignoredNegativeZeroForCurrentInstr); |
| break; |
| |
| case Js::OpCode::Conv_Prim: |
| exprAttributes = ConvAttributes(instr->GetDst()->IsUnsigned(), instr->GetSrc1()->IsUnsigned()); |
| break; |
| |
| default: |
| if(intMathExprOnly) |
| { |
| return false; |
| } |
| break; |
| } |
| |
| if (!this->GetHash(instr, src1Val, src2Val, exprAttributes, &hash)) |
| { |
| return false; |
| } |
| |
| // See if we have a value for that expression |
| Value ** pVal = block->globOptData.exprToValueMap->Get(hash); |
| |
| if (pVal == NULL) |
| { |
| return false; |
| } |
| |
| ValueInfo *valueInfo = NULL; |
| Sym *symStore = NULL; |
| Value *val = NULL; |
| |
| if (instr->GetDst()) |
| { |
| |
| if (*pVal == NULL) |
| { |
| return false; |
| } |
| |
| val = *pVal; |
| |
| // Make sure the array value is still live. We can't CSE something like: |
| // ... = A[i]; |
| // B[j] = ...; |
| // ... = A[i]; |
| if (isArray && !block->globOptData.liveArrayValues->Test(hash)) |
| { |
| return false; |
| } |
| |
| // See if the symStore is still valid |
| valueInfo = val->GetValueInfo(); |
| symStore = valueInfo->GetSymStore(); |
| Value * symStoreVal = NULL; |
| |
| int32 intConstantValue; |
| if (!symStore && valueInfo->TryGetIntConstantValue(&intConstantValue)) |
| { |
| // Handle: |
| // A[i] = 10; |
| // ... = A[i]; |
| if (!isArray) |
| { |
| return false; |
| } |
| if (!valueInfo->IsIntAndLikelyTagged()) |
| { |
| return false; |
| } |
| |
| symStore = GetTaggedIntConstantStackSym(intConstantValue); |
| } |
| |
| if(!symStore || !symStore->IsStackSym()) |
| { |
| return false; |
| } |
| symStoreVal = block->globOptData.FindValue(symStore); |
| |
| if (!symStoreVal || symStoreVal->GetValueNumber() != val->GetValueNumber()) |
| { |
| return false; |
| } |
| val = symStoreVal; |
| valueInfo = val->GetValueInfo(); |
| } |
| |
| // Make sure srcs still have same values |
| |
| if (instr->GetSrc1()) |
| { |
| if (!src1Val) |
| { |
| return false; |
| } |
| if (hash.GetSrc1ValueNumber() != src1Val->GetValueNumber()) |
| { |
| return false; |
| } |
| |
| IR::Opnd *src1 = instr->GetSrc1(); |
| |
| if (src1->IsSymOpnd() && src1->AsSymOpnd()->IsPropertySymOpnd()) |
| { |
| Assert(instr->m_opcode == Js::OpCode::CheckFixedFld); |
| IR::PropertySymOpnd *propOpnd = src1->AsSymOpnd()->AsPropertySymOpnd(); |
| |
| if (!propOpnd->IsTypeChecked() && !propOpnd->IsRootObjectNonConfigurableFieldLoad()) |
| { |
| // Require m_CachedTypeChecked for 2 reasons: |
| // - We may be relying on this instruction to do a type check for a downstream reference. |
| // - This instruction may have a different inline cache, and thus need to check a different type, |
| // than the upstream check. |
| // REVIEW: We could process this differently somehow to get the type check on the next reference. |
| return false; |
| } |
| } |
| if (instr->GetSrc2()) |
| { |
| if (!src2Val) |
| { |
| return false; |
| } |
| if (hash.GetSrc2ValueNumber() != src2Val->GetValueNumber()) |
| { |
| return false; |
| } |
| } |
| } |
| bool needsBailOnImplicitCall = false; |
| |
| // Need implicit call bailouts? |
| if (this->MayNeedBailOnImplicitCall(instr, src1Val, src2Val)) |
| { |
| needsBailOnImplicitCall = true; |
| |
| IR::Opnd *src1 = instr->GetSrc1(); |
| |
| if (instr->m_opcode != Js::OpCode::StElemI_A && instr->m_opcode != Js::OpCode::StElemI_A_Strict |
| && src1 && src1->IsRegOpnd()) |
| { |
| StackSym *sym1 = src1->AsRegOpnd()->m_sym; |
| |
| if (block->globOptData.IsTypeSpecialized(sym1) || block->globOptData.liveInt32Syms->Test(sym1->m_id)) |
| { |
| IR::Opnd *src2 = instr->GetSrc2(); |
| |
| if (!src2 || src2->IsImmediateOpnd()) |
| { |
| needsBailOnImplicitCall = false; |
| } |
| else if (src2->IsRegOpnd()) |
| { |
| StackSym *sym2 = src2->AsRegOpnd()->m_sym; |
| if (block->globOptData.IsTypeSpecialized(sym2) || block->globOptData.liveInt32Syms->Test(sym2->m_id)) |
| { |
| needsBailOnImplicitCall = false; |
| } |
| } |
| } |
| } |
| } |
| |
| if (needsBailOnImplicitCall) |
| { |
| IR::Opnd *src1 = instr->GetSrc1(); |
| if (src1) |
| { |
| if (src1->IsRegOpnd()) |
| { |
| StackSym *varSym1 = src1->AsRegOpnd()->m_sym; |
| |
| Assert(!varSym1->IsTypeSpec()); |
| if (!block->globOptData.liveFields->Test(varSym1->m_id)) |
| { |
| return false; |
| } |
| IR::Opnd *src2 = instr->GetSrc2(); |
| if (src2 && src2->IsRegOpnd()) |
| { |
| StackSym *varSym2 = src2->AsRegOpnd()->m_sym; |
| |
| Assert(!varSym2->IsTypeSpec()); |
| if (!block->globOptData.liveFields->Test(varSym2->m_id)) |
| { |
| return false; |
| } |
| } |
| } |
| } |
| } |
| // in asmjs we can have a symstore with a different type |
| // x = HEAPF32[i >> 2] |
| // y = HEAPI32[i >> 2] |
| if (instr->GetDst() && (instr->GetDst()->GetType() != symStore->AsStackSym()->GetType())) |
| { |
| Assert(GetIsAsmJSFunc()); |
| return false; |
| } |
| |
| // SIMD_JS |
| if (instr->m_opcode == Js::OpCode::ExtendArg_A) |
| { |
| // we don't want to CSE ExtendArgs, only the operation using them. To do that, we mimic CSE by transferring the symStore valueInfo to the dst. |
| IR::Opnd *dst = instr->GetDst(); |
| Value *dstVal = this->currentBlock->globOptData.FindValue(symStore); |
| this->currentBlock->globOptData.SetValue(dstVal, dst); |
| dst->AsRegOpnd()->m_sym->CopySymAttrs(symStore->AsStackSym()); |
| return false; |
| } |
| |
| // |
| // Success, do the CSE rewrite. |
| // |
| |
| #if DBG_DUMP |
| if (Js::Configuration::Global.flags.Trace.IsEnabled(Js::CSEPhase, this->func->GetSourceContextId(), this->func->GetLocalFunctionId())) |
| { |
| Output::Print(_u(" --- CSE (%s): "), this->func->GetJITFunctionBody()->GetDisplayName()); |
| instr->Dump(); |
| } |
| #endif |
| #if ENABLE_DEBUG_CONFIG_OPTIONS |
| if (Js::Configuration::Global.flags.TestTrace.IsEnabled(Js::CSEPhase, this->func->GetSourceContextId(), this->func->GetLocalFunctionId())) |
| { |
| Output::Print(_u(" --- CSE (%s): %s\n"), this->func->GetJITFunctionBody()->GetDisplayName(), Js::OpCodeUtil::GetOpCodeName(instr->m_opcode)); |
| } |
| #endif |
| |
| this->CaptureByteCodeSymUses(instr); |
| |
| if (!instr->GetDst()) |
| { |
| instr->m_opcode = Js::OpCode::Nop; |
| return true; |
| } |
| |
| AnalysisAssert(valueInfo); |
| |
| IR::Opnd *cseOpnd; |
| |
| cseOpnd = IR::RegOpnd::New(symStore->AsStackSym(), instr->GetDst()->GetType(), instr->m_func); |
| cseOpnd->SetValueType(valueInfo->Type()); |
| cseOpnd->SetIsJITOptimizedReg(true); |
| |
| if (needsBailOnImplicitCall) |
| { |
| this->CaptureNoImplicitCallUses(cseOpnd, false); |
| } |
| |
| int32 intConstantValue; |
| if (valueInfo->TryGetIntConstantValue(&intConstantValue) && valueInfo->IsIntAndLikelyTagged()) |
| { |
| cseOpnd->Free(func); |
| cseOpnd = IR::AddrOpnd::New(Js::TaggedInt::ToVarUnchecked(intConstantValue), IR::AddrOpndKindConstantVar, instr->m_func); |
| cseOpnd->SetValueType(valueInfo->Type()); |
| } |
| |
| *pSrc1Val = val; |
| |
| { |
| // Profiled instructions have data that is interpreted differently based on the op code. Since we're changing the op |
| // code and due to other similar potential issues, always create a new instr instead of changing the existing one. |
| IR::Instr *const originalInstr = instr; |
| instr = IR::Instr::New(Js::OpCode::Ld_A, instr->GetDst(), cseOpnd, instr->m_func); |
| originalInstr->TransferDstAttributesTo(instr); |
| block->InsertInstrBefore(instr, originalInstr); |
| block->RemoveInstr(originalInstr); |
| } |
| |
| *pSrc2Val = NULL; |
| *pSrc1IndirIndexVal = NULL; |
| return true; |
| } |
| |
| void |
| GlobOpt::ProcessArrayValueKills(IR::Instr *instr) |
| { |
| switch (instr->m_opcode) |
| { |
| case Js::OpCode::StElemI_A: |
| case Js::OpCode::StElemI_A_Strict: |
| case Js::OpCode::DeleteElemI_A: |
| case Js::OpCode::DeleteElemIStrict_A: |
| case Js::OpCode::StFld: |
| case Js::OpCode::StRootFld: |
| case Js::OpCode::StFldStrict: |
| case Js::OpCode::StRootFldStrict: |
| case Js::OpCode::StSlot: |
| case Js::OpCode::StSlotChkUndecl: |
| case Js::OpCode::DeleteFld: |
| case Js::OpCode::DeleteRootFld: |
| case Js::OpCode::DeleteFldStrict: |
| case Js::OpCode::DeleteRootFldStrict: |
| case Js::OpCode::StArrViewElem: |
| // These array helpers may change A.length (and A[i] could be A.length)... |
| case Js::OpCode::InlineArrayPush: |
| case Js::OpCode::InlineArrayPop: |
| this->currentBlock->globOptData.liveArrayValues->ClearAll(); |
| break; |
| |
| case Js::OpCode::CallDirect: |
| Assert(instr->GetSrc1()); |
| switch(instr->GetSrc1()->AsHelperCallOpnd()->m_fnHelper) |
| { |
| // These array helpers may change A[i] |
| case IR::HelperArray_Reverse: |
| case IR::HelperArray_Shift: |
| case IR::HelperArray_Unshift: |
| case IR::HelperArray_Splice: |
| this->currentBlock->globOptData.liveArrayValues->ClearAll(); |
| break; |
| } |
| break; |
| default: |
| if (instr->UsesAllFields()) |
| { |
| this->currentBlock->globOptData.liveArrayValues->ClearAll(); |
| } |
| break; |
| } |
| } |
| |
| bool |
| GlobOpt::NeedBailOnImplicitCallForCSE(BasicBlock const *block, bool isForwardPass) |
| { |
| return isForwardPass && block->globOptData.hasCSECandidates; |
| } |
| |
| bool |
| GlobOpt::DoCSE() |
| { |
| if (PHASE_OFF(Js::CSEPhase, this->func)) |
| { |
| return false; |
| } |
| if (this->IsLoopPrePass()) |
| { |
| return false; |
| } |
| |
| if (PHASE_FORCE(Js::CSEPhase, this->func)) |
| { |
| // Force always turn it on |
| return true; |
| } |
| |
| if (!this->DoFieldOpts(this->currentBlock->loop) && !GetIsAsmJSFunc()) |
| { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool |
| GlobOpt::CanCSEArrayStore(IR::Instr *instr) |
| { |
| IR::Opnd *arrayOpnd = instr->GetDst(); |
| Assert(arrayOpnd->IsIndirOpnd()); |
| IR::RegOpnd *baseOpnd = arrayOpnd->AsIndirOpnd()->GetBaseOpnd(); |
| |
| ValueType baseValueType(baseOpnd->GetValueType()); |
| |
| // Only handle definite arrays for now. Typed Arrays would require truncation of the CSE'd value. |
| if (!baseValueType.IsArrayOrObjectWithArray()) |
| { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| |
| #if DBG_DUMP |
| void |
| DumpExpr(ExprHash hash) |
| { |
| Output::Print(_u("Opcode: %10s src1Val: %d src2Val: %d\n"), Js::OpCodeUtil::GetOpCodeName(HashToOpCode[(int)hash.GetOpcode()]), hash.GetSrc1ValueNumber(), hash.GetSrc2ValueNumber()); |
| } |
| #endif |