| //------------------------------------------------------------------------------------------------------- |
| // 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" |
| |
| bool |
| GlobOpt::DoFieldCopyProp() const |
| { |
| BasicBlock *block = this->currentBlock; |
| Loop *loop = block->loop; |
| if (this->isRecursiveCallOnLandingPad) |
| { |
| // The landing pad at this point only contains load hosted by PRE. |
| // These need to be copy-prop'd into the loop. |
| // We want to look at the implicit-call info of the loop, not it's parent. |
| |
| Assert(block->IsLandingPad()); |
| loop = block->next->loop; |
| Assert(loop); |
| } |
| |
| return DoFieldCopyProp(loop); |
| } |
| |
| bool |
| GlobOpt::DoFunctionFieldCopyProp() const |
| { |
| return DoFieldCopyProp(nullptr); |
| } |
| |
| bool |
| GlobOpt::DoFieldCopyProp(Loop * loop) const |
| { |
| if (PHASE_OFF(Js::CopyPropPhase, this->func)) |
| { |
| // Can't do field copy prop without copy prop |
| return false; |
| } |
| |
| if (PHASE_FORCE(Js::FieldCopyPropPhase, this->func)) |
| { |
| // Force always turns on field copy prop |
| return true; |
| } |
| |
| if (PHASE_OFF(Js::FieldCopyPropPhase, this->func)) |
| { |
| return false; |
| } |
| |
| return this->DoFieldOpts(loop); |
| } |
| |
| bool |
| GlobOpt::DoObjTypeSpec() const |
| { |
| return this->DoObjTypeSpec(this->currentBlock->loop); |
| } |
| |
| bool |
| GlobOpt::DoObjTypeSpec(Loop *loop) const |
| { |
| if (!this->func->DoFastPaths()) |
| { |
| return false; |
| } |
| if (PHASE_FORCE(Js::ObjTypeSpecPhase, this->func)) |
| { |
| return true; |
| } |
| if (PHASE_OFF(Js::ObjTypeSpecPhase, this->func)) |
| { |
| return false; |
| } |
| if (this->func->IsLoopBody() && this->func->HasProfileInfo() && this->func->GetReadOnlyProfileInfo()->IsObjTypeSpecDisabledInJitLoopBody()) |
| { |
| return false; |
| } |
| if (this->ImplicitCallFlagsAllowOpts(this->func)) |
| { |
| Assert(loop == nullptr || loop->CanDoFieldCopyProp()); |
| return true; |
| } |
| return loop != nullptr && loop->CanDoFieldCopyProp(); |
| } |
| |
| bool |
| GlobOpt::DoFieldOpts(Loop * loop) const |
| { |
| if (this->ImplicitCallFlagsAllowOpts(this->func)) |
| { |
| Assert(loop == nullptr || loop->CanDoFieldCopyProp()); |
| return true; |
| } |
| return loop != nullptr && loop->CanDoFieldCopyProp(); |
| } |
| |
| bool GlobOpt::DoFieldPRE() const |
| { |
| Loop *loop = this->currentBlock->loop; |
| |
| return DoFieldPRE(loop); |
| } |
| |
| bool |
| GlobOpt::DoFieldPRE(Loop *loop) const |
| { |
| if (PHASE_OFF(Js::FieldPREPhase, this->func)) |
| { |
| return false; |
| } |
| |
| if (PHASE_FORCE(Js::FieldPREPhase, func)) |
| { |
| // Force always turns on field PRE |
| return true; |
| } |
| |
| if (this->func->HasProfileInfo() && this->func->GetReadOnlyProfileInfo()->IsFieldPREDisabled()) |
| { |
| return false; |
| } |
| |
| return DoFieldOpts(loop); |
| } |
| |
| bool GlobOpt::HasMemOp(Loop *loop) |
| { |
| #pragma prefast(suppress: 6285, "logical-or of constants is by design") |
| return ( |
| loop && |
| loop->doMemOp && |
| ( |
| !PHASE_OFF(Js::MemSetPhase, this->func) || |
| !PHASE_OFF(Js::MemCopyPhase, this->func) |
| ) && |
| loop->memOpInfo && |
| loop->memOpInfo->candidates && |
| !loop->memOpInfo->candidates->Empty() |
| ); |
| } |
| |
| void |
| GlobOpt::KillLiveFields(StackSym * stackSym, BVSparse<JitArenaAllocator> * bv) |
| { |
| if (stackSym->IsTypeSpec()) |
| { |
| stackSym = stackSym->GetVarEquivSym(this->func); |
| } |
| Assert(stackSym); |
| |
| // If the sym has no objectSymInfo, it must not represent an object and, hence, has no type sym or |
| // property syms to kill. |
| if (!stackSym->HasObjectInfo() || stackSym->IsSingleDef()) |
| { |
| return; |
| } |
| |
| // Note that the m_writeGuardSym is killed here as well, because it is part of the |
| // m_propertySymList of the object. |
| ObjectSymInfo * objectSymInfo = stackSym->GetObjectInfo(); |
| PropertySym * propertySym = objectSymInfo->m_propertySymList; |
| while (propertySym != nullptr) |
| { |
| Assert(propertySym->m_stackSym == stackSym); |
| bv->Clear(propertySym->m_id); |
| if (this->IsLoopPrePass()) |
| { |
| for (Loop * loop = this->rootLoopPrePass; loop != nullptr; loop = loop->parent) |
| { |
| loop->fieldKilled->Set(propertySym->m_id); |
| } |
| } |
| else if (bv->IsEmpty()) |
| { |
| // shortcut |
| break; |
| } |
| propertySym = propertySym->m_nextInStackSymList; |
| } |
| |
| this->KillObjectType(stackSym, bv); |
| } |
| |
| void |
| GlobOpt::KillLiveFields(PropertySym * propertySym, BVSparse<JitArenaAllocator> * bv) |
| { |
| KillLiveFields(propertySym->m_propertyEquivSet, bv); |
| } |
| |
| void GlobOpt::KillLiveFields(BVSparse<JitArenaAllocator> *const fieldsToKill, BVSparse<JitArenaAllocator> *const bv) const |
| { |
| Assert(bv); |
| |
| if (fieldsToKill) |
| { |
| bv->Minus(fieldsToKill); |
| |
| if (this->IsLoopPrePass()) |
| { |
| for (Loop * loop = this->rootLoopPrePass; loop != nullptr; loop = loop->parent) |
| { |
| loop->fieldKilled->Or(fieldsToKill); |
| } |
| } |
| } |
| } |
| |
| void |
| GlobOpt::KillLiveElems(IR::IndirOpnd * indirOpnd, BVSparse<JitArenaAllocator> * bv, bool inGlobOpt, Func *func) |
| { |
| IR::RegOpnd *indexOpnd = indirOpnd->GetIndexOpnd(); |
| |
| // obj.x = 10; |
| // obj["x"] = ...; // This needs to kill obj.x... We need to kill all fields... |
| // |
| // Also, 'arguments[i] =' needs to kill all slots even if 'i' is an int. |
| // |
| // NOTE: we only need to kill slots here, not all fields. It may be good to separate these one day. |
| // |
| // Regarding the check for type specialization: |
| // - Type specialization does not always update the value to a definite type. |
| // - The loop prepass is conservative on values even when type specialization occurs. |
| // - We check the type specialization status for the sym as well. For the purpose of doing kills, we can assume that |
| // if type specialization happened, that fields don't need to be killed. Note that they may be killed in the next |
| // pass based on the value. |
| if (func->GetThisOrParentInlinerHasArguments() || |
| ( |
| indexOpnd && |
| ( |
| indexOpnd->m_sym->m_isNotNumber || |
| (inGlobOpt && !indexOpnd->GetValueType().IsNumber() && !currentBlock->globOptData.IsTypeSpecialized(indexOpnd->m_sym)) |
| ) |
| )) |
| { |
| this->KillAllFields(bv); // This also kills all property type values, as the same bit-vector tracks those stack syms |
| SetAnyPropertyMayBeWrittenTo(); |
| } |
| else if (inGlobOpt) |
| { |
| Value * indexValue = indexOpnd ? this->currentBlock->globOptData.FindValue(indexOpnd->GetSym()) : nullptr; |
| ValueInfo * indexValueInfo = indexValue ? indexValue->GetValueInfo() : nullptr; |
| int indexLowerBound = 0; |
| |
| if (indirOpnd->GetOffset() < 0 || (indexOpnd && (!indexValueInfo || !indexValueInfo->TryGetIntConstantLowerBound(&indexLowerBound, false) || indexLowerBound < 0))) |
| { |
| // Write/delete to a non-integer numeric index can't alias a name on the RHS of a dot, but it change object layout |
| this->KillAllObjectTypes(bv); |
| } |
| } |
| } |
| |
| void |
| GlobOpt::KillAllFields(BVSparse<JitArenaAllocator> * bv) |
| { |
| bv->ClearAll(); |
| if (this->IsLoopPrePass()) |
| { |
| for (Loop * loop = this->rootLoopPrePass; loop != nullptr; loop = loop->parent) |
| { |
| loop->allFieldsKilled = true; |
| } |
| } |
| } |
| |
| void |
| GlobOpt::SetAnyPropertyMayBeWrittenTo() |
| { |
| this->func->anyPropertyMayBeWrittenTo = true; |
| } |
| |
| void |
| GlobOpt::AddToPropertiesWrittenTo(Js::PropertyId propertyId) |
| { |
| this->func->EnsurePropertiesWrittenTo(); |
| this->func->propertiesWrittenTo->Item(propertyId); |
| } |
| |
| void |
| GlobOpt::ProcessFieldKills(IR::Instr *instr, BVSparse<JitArenaAllocator> *bv, bool inGlobOpt) |
| { |
| if (bv->IsEmpty() && (!this->IsLoopPrePass() || this->rootLoopPrePass->allFieldsKilled)) |
| { |
| return; |
| } |
| |
| if (instr->m_opcode == Js::OpCode::FromVar || instr->m_opcode == Js::OpCode::Conv_Prim) |
| { |
| return; |
| } |
| |
| IR::Opnd * dstOpnd = instr->GetDst(); |
| if (dstOpnd) |
| { |
| if (dstOpnd->IsRegOpnd()) |
| { |
| Sym * sym = dstOpnd->AsRegOpnd()->m_sym; |
| if (sym->IsStackSym()) |
| { |
| KillLiveFields(sym->AsStackSym(), bv); |
| } |
| } |
| else if (dstOpnd->IsSymOpnd()) |
| { |
| Sym * sym = dstOpnd->AsSymOpnd()->m_sym; |
| if (sym->IsStackSym()) |
| { |
| KillLiveFields(sym->AsStackSym(), bv); |
| } |
| else |
| { |
| Assert(sym->IsPropertySym()); |
| if (instr->m_opcode == Js::OpCode::InitLetFld || instr->m_opcode == Js::OpCode::InitConstFld || instr->m_opcode == Js::OpCode::InitFld) |
| { |
| // These can grow the aux slot of the activation object. |
| // We need to kill the slot array sym as well. |
| PropertySym * slotArraySym = PropertySym::Find(sym->AsPropertySym()->m_stackSym->m_id, |
| (Js::DynamicObject::GetOffsetOfAuxSlots())/sizeof(Js::Var) /*, PropertyKindSlotArray */, instr->m_func); |
| if (slotArraySym) |
| { |
| bv->Clear(slotArraySym->m_id); |
| } |
| } |
| } |
| } |
| } |
| |
| if (bv->IsEmpty() && (!this->IsLoopPrePass() || this->rootLoopPrePass->allFieldsKilled)) |
| { |
| return; |
| } |
| |
| Sym *sym; |
| IR::JnHelperMethod fnHelper; |
| switch(instr->m_opcode) |
| { |
| case Js::OpCode::StElemI_A: |
| case Js::OpCode::StElemI_A_Strict: |
| Assert(dstOpnd != nullptr); |
| KillLiveFields(this->lengthEquivBv, bv); |
| KillLiveElems(dstOpnd->AsIndirOpnd(), bv, inGlobOpt, instr->m_func); |
| if (inGlobOpt) |
| { |
| KillObjectHeaderInlinedTypeSyms(this->currentBlock, false); |
| } |
| break; |
| |
| case Js::OpCode::InitComputedProperty: |
| case Js::OpCode::InitGetElemI: |
| case Js::OpCode::InitSetElemI: |
| KillLiveElems(dstOpnd->AsIndirOpnd(), bv, inGlobOpt, instr->m_func); |
| if (inGlobOpt) |
| { |
| KillObjectHeaderInlinedTypeSyms(this->currentBlock, false); |
| } |
| break; |
| |
| case Js::OpCode::DeleteElemI_A: |
| case Js::OpCode::DeleteElemIStrict_A: |
| Assert(dstOpnd != nullptr); |
| KillLiveElems(instr->GetSrc1()->AsIndirOpnd(), bv, inGlobOpt, instr->m_func); |
| break; |
| |
| case Js::OpCode::DeleteFld: |
| case Js::OpCode::DeleteRootFld: |
| case Js::OpCode::DeleteFldStrict: |
| case Js::OpCode::DeleteRootFldStrict: |
| sym = instr->GetSrc1()->AsSymOpnd()->m_sym; |
| KillLiveFields(sym->AsPropertySym(), bv); |
| if (inGlobOpt) |
| { |
| AddToPropertiesWrittenTo(sym->AsPropertySym()->m_propertyId); |
| this->KillAllObjectTypes(bv); |
| } |
| break; |
| |
| case Js::OpCode::InitSetFld: |
| case Js::OpCode::InitGetFld: |
| case Js::OpCode::InitClassMemberGet: |
| case Js::OpCode::InitClassMemberSet: |
| sym = instr->GetDst()->AsSymOpnd()->m_sym; |
| KillLiveFields(sym->AsPropertySym(), bv); |
| if (inGlobOpt) |
| { |
| AddToPropertiesWrittenTo(sym->AsPropertySym()->m_propertyId); |
| this->KillAllObjectTypes(bv); |
| } |
| break; |
| case Js::OpCode::InitFld: |
| case Js::OpCode::StFld: |
| case Js::OpCode::StRootFld: |
| case Js::OpCode::StFldStrict: |
| case Js::OpCode::StRootFldStrict: |
| case Js::OpCode::StSlot: |
| case Js::OpCode::StSlotChkUndecl: |
| Assert(dstOpnd != nullptr); |
| sym = dstOpnd->AsSymOpnd()->m_sym; |
| if (inGlobOpt) |
| { |
| AddToPropertiesWrittenTo(sym->AsPropertySym()->m_propertyId); |
| } |
| if ((inGlobOpt && (sym->AsPropertySym()->m_propertyId == Js::PropertyIds::valueOf || sym->AsPropertySym()->m_propertyId == Js::PropertyIds::toString)) || |
| instr->CallsAccessor()) |
| { |
| // If overriding valueof/tostring, we might have expected a previous LdFld to bailout on implicitCalls but didn't. |
| // CSE's for example would have expected a bailout. Clear all fields to prevent optimizing across. |
| this->KillAllFields(bv); |
| } |
| else |
| { |
| KillLiveFields(sym->AsPropertySym(), bv); |
| } |
| break; |
| |
| case Js::OpCode::InlineArrayPush: |
| case Js::OpCode::InlineArrayPop: |
| KillLiveFields(this->lengthEquivBv, bv); |
| if (inGlobOpt) |
| { |
| // Deleting an item, or pushing a property to a non-array, may change object layout |
| KillAllObjectTypes(bv); |
| } |
| break; |
| |
| case Js::OpCode::InlineeStart: |
| case Js::OpCode::InlineeEnd: |
| Assert(!instr->UsesAllFields()); |
| |
| // Kill all live 'arguments' and 'caller' fields, as 'inlineeFunction.arguments' and 'inlineeFunction.caller' |
| // cannot be copy-propped across different instances of the same inlined function. |
| KillLiveFields(argumentsEquivBv, bv); |
| KillLiveFields(callerEquivBv, bv); |
| break; |
| |
| case Js::OpCode::CallDirect: |
| fnHelper = instr->GetSrc1()->AsHelperCallOpnd()->m_fnHelper; |
| |
| switch (fnHelper) |
| { |
| case IR::JnHelperMethod::HelperArray_Shift: |
| case IR::JnHelperMethod::HelperArray_Splice: |
| case IR::JnHelperMethod::HelperArray_Unshift: |
| // Kill length field for built-ins that can update it. |
| if (nullptr != this->lengthEquivBv) |
| { |
| KillLiveFields(this->lengthEquivBv, bv); |
| } |
| // fall through |
| |
| case IR::JnHelperMethod::HelperArray_Reverse: |
| // Deleting an item may change object layout |
| if (inGlobOpt) |
| { |
| KillAllObjectTypes(bv); |
| } |
| break; |
| |
| case IR::JnHelperMethod::HelperRegExp_Exec: |
| case IR::JnHelperMethod::HelperRegExp_ExecResultNotUsed: |
| case IR::JnHelperMethod::HelperRegExp_ExecResultUsed: |
| case IR::JnHelperMethod::HelperRegExp_ExecResultUsedAndMayBeTemp: |
| case IR::JnHelperMethod::HelperRegExp_MatchResultNotUsed: |
| case IR::JnHelperMethod::HelperRegExp_MatchResultUsed: |
| case IR::JnHelperMethod::HelperRegExp_MatchResultUsedAndMayBeTemp: |
| case IR::JnHelperMethod::HelperRegExp_ReplaceStringResultUsed: |
| case IR::JnHelperMethod::HelperRegExp_ReplaceStringResultNotUsed: |
| case IR::JnHelperMethod::HelperRegExp_SplitResultNotUsed: |
| case IR::JnHelperMethod::HelperRegExp_SplitResultUsed: |
| case IR::JnHelperMethod::HelperRegExp_SplitResultUsedAndMayBeTemp: |
| case IR::JnHelperMethod::HelperRegExp_SymbolSearch: |
| case IR::JnHelperMethod::HelperString_Match: |
| case IR::JnHelperMethod::HelperString_Search: |
| case IR::JnHelperMethod::HelperString_Split: |
| case IR::JnHelperMethod::HelperString_Replace: |
| // Consider: We may not need to kill all fields here. |
| // We need to kill all the built-in properties that can be written, though, and there are a lot of those. |
| this->KillAllFields(bv); |
| break; |
| } |
| break; |
| |
| case Js::OpCode::LdHeapArguments: |
| case Js::OpCode::LdLetHeapArguments: |
| case Js::OpCode::LdHeapArgsCached: |
| case Js::OpCode::LdLetHeapArgsCached: |
| if (inGlobOpt) { |
| this->KillLiveFields(this->slotSyms, bv); |
| } |
| break; |
| |
| case Js::OpCode::InitClass: |
| case Js::OpCode::InitProto: |
| case Js::OpCode::NewScObjectNoCtor: |
| if (inGlobOpt) |
| { |
| KillObjectHeaderInlinedTypeSyms(this->currentBlock, false); |
| } |
| break; |
| |
| default: |
| if (instr->UsesAllFields()) |
| { |
| // This also kills all property type values, as the same bit-vector tracks those stack syms. |
| this->KillAllFields(bv); |
| } |
| break; |
| } |
| } |
| |
| void |
| GlobOpt::ProcessFieldKills(IR::Instr * instr) |
| { |
| if (!this->DoFieldCopyProp() && !this->DoFieldRefOpts() && !DoCSE()) |
| { |
| Assert(this->currentBlock->globOptData.liveFields->IsEmpty()); |
| return; |
| } |
| |
| ProcessFieldKills(instr, this->currentBlock->globOptData.liveFields, true); |
| } |
| |
| Value * |
| GlobOpt::CreateFieldSrcValue(PropertySym * sym, PropertySym * originalSym, IR::Opnd ** ppOpnd, IR::Instr * instr) |
| { |
| #if DBG |
| // If the opcode going to kill all field values immediate anyway, we shouldn't be giving it a value |
| Assert(!instr->UsesAllFields()); |
| |
| AssertCanCopyPropOrCSEFieldLoad(instr); |
| |
| Assert(instr->GetSrc1() == *ppOpnd); |
| #endif |
| |
| // Only give a value to fields if we are doing field copy prop. |
| // Consider: We should always copy prop local slots, but the only use right now is LdSlot from jit loop body. |
| // This should have one onus load, and thus no need for copy prop of field itself. We may want to support |
| // copy prop LdSlot if there are other uses of local slots |
| if (!this->DoFieldCopyProp()) |
| { |
| return nullptr; |
| } |
| |
| BOOL wasLive = this->currentBlock->globOptData.liveFields->TestAndSet(sym->m_id); |
| |
| if (sym != originalSym) |
| { |
| this->currentBlock->globOptData.liveFields->TestAndSet(originalSym->m_id); |
| } |
| |
| if (!wasLive) |
| { |
| // We don't clear the value when we kill the field. |
| // Clear it to make sure we don't use the old value. |
| this->currentBlock->globOptData.ClearSymValue(sym); |
| this->currentBlock->globOptData.ClearSymValue(originalSym); |
| } |
| |
| Assert((*ppOpnd)->AsSymOpnd()->m_sym == sym || this->IsLoopPrePass()); |
| |
| // We don't use the sym store to do copy prop on hoisted fields, but create a value |
| // in case it can be copy prop out of the loop. |
| return this->NewGenericValue(ValueType::Uninitialized, *ppOpnd); |
| } |
| |
| bool |
| GlobOpt::NeedBailOnImplicitCallWithFieldOpts(Loop *loop, bool hasLiveFields) const |
| { |
| if (!(((this->DoFieldRefOpts(loop) || |
| this->DoFieldCopyProp(loop)) && |
| hasLiveFields))) |
| { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| IR::Instr * |
| GlobOpt::EnsureDisableImplicitCallRegion(Loop * loop) |
| { |
| Assert(loop->bailOutInfo != nullptr); |
| IR::Instr * endDisableImplicitCall = loop->endDisableImplicitCall; |
| if (endDisableImplicitCall) |
| { |
| return endDisableImplicitCall; |
| } |
| |
| IR::Instr * bailOutTarget = EnsureBailTarget(loop); |
| |
| Func * bailOutFunc = loop->GetFunc(); |
| Assert(loop->bailOutInfo->bailOutFunc == bailOutFunc); |
| |
| IR::MemRefOpnd * disableImplicitCallAddress = IR::MemRefOpnd::New(this->func->GetThreadContextInfo()->GetDisableImplicitFlagsAddr(), TyInt8, bailOutFunc); |
| IR::IntConstOpnd * disableImplicitCallAndExceptionValue = IR::IntConstOpnd::New(DisableImplicitCallAndExceptionFlag, TyInt8, bailOutFunc, true); |
| IR::IntConstOpnd * enableImplicitCallAndExceptionValue = IR::IntConstOpnd::New(DisableImplicitNoFlag, TyInt8, bailOutFunc, true); |
| |
| IR::Opnd * implicitCallFlags = Lowerer::GetImplicitCallFlagsOpnd(bailOutFunc); |
| IR::IntConstOpnd * noImplicitCall = IR::IntConstOpnd::New(Js::ImplicitCall_None, TyInt8, bailOutFunc, true); |
| |
| // Consider: if we are already doing implicit call in the outer loop, we don't need to clear the implicit call bit again |
| IR::Instr * clearImplicitCall = IR::Instr::New(Js::OpCode::Ld_A, implicitCallFlags, noImplicitCall, bailOutFunc); |
| bailOutTarget->InsertBefore(clearImplicitCall); |
| |
| IR::Instr * disableImplicitCall = IR::Instr::New(Js::OpCode::Ld_A, disableImplicitCallAddress, disableImplicitCallAndExceptionValue, bailOutFunc); |
| bailOutTarget->InsertBefore(disableImplicitCall); |
| |
| endDisableImplicitCall = IR::Instr::New(Js::OpCode::Ld_A, disableImplicitCallAddress, enableImplicitCallAndExceptionValue, bailOutFunc); |
| bailOutTarget->InsertBefore(endDisableImplicitCall); |
| |
| IR::BailOutInstr * bailOutInstr = IR::BailOutInstr::New(Js::OpCode::BailOnNotEqual, IR::BailOutOnImplicitCalls, loop->bailOutInfo, loop->bailOutInfo->bailOutFunc); |
| bailOutInstr->SetSrc1(implicitCallFlags); |
| bailOutInstr->SetSrc2(noImplicitCall); |
| bailOutTarget->InsertBefore(bailOutInstr); |
| |
| loop->endDisableImplicitCall = endDisableImplicitCall; |
| return endDisableImplicitCall; |
| } |
| |
| #if DBG |
| bool |
| GlobOpt::IsPropertySymId(SymID symId) const |
| { |
| return this->func->m_symTable->Find(symId)->IsPropertySym(); |
| } |
| |
| void |
| GlobOpt::AssertCanCopyPropOrCSEFieldLoad(IR::Instr * instr) |
| { |
| // Consider: Hoisting LdRootFld may have complication with exception if the field doesn't exist. |
| // We need to have another opcode for the hoisted version to avoid the exception and bailout. |
| |
| Assert(instr->m_opcode == Js::OpCode::LdSlot || instr->m_opcode == Js::OpCode::LdSlotArr |
| || instr->m_opcode == Js::OpCode::LdFld || instr->m_opcode == Js::OpCode::LdFldForCallApplyTarget |
| || instr->m_opcode == Js::OpCode::LdLen_A |
| || instr->m_opcode == Js::OpCode::LdRootFld || instr->m_opcode == Js::OpCode::LdSuperFld |
| || instr->m_opcode == Js::OpCode::LdFldForTypeOf || instr->m_opcode == Js::OpCode::LdRootFldForTypeOf |
| || instr->m_opcode == Js::OpCode::LdMethodFld || instr->m_opcode == Js::OpCode::LdMethodFldPolyInlineMiss |
| || instr->m_opcode == Js::OpCode::LdRootMethodFld |
| || instr->m_opcode == Js::OpCode::LdMethodFromFlags |
| || instr->m_opcode == Js::OpCode::ScopedLdMethodFld |
| || instr->m_opcode == Js::OpCode::CheckFixedFld |
| || instr->m_opcode == Js::OpCode::CheckPropertyGuardAndLoadType |
| || instr->m_opcode == Js::OpCode::ScopedLdFld |
| || instr->m_opcode == Js::OpCode::ScopedLdFldForTypeOf); |
| |
| Assert(instr->m_opcode == Js::OpCode::CheckFixedFld || instr->GetDst()->GetType() == TyVar || instr->m_func->GetJITFunctionBody()->IsAsmJsMode()); |
| Assert(instr->GetSrc1()->GetType() == TyVar || instr->m_func->GetJITFunctionBody()->IsAsmJsMode()); |
| Assert(instr->GetSrc1()->AsSymOpnd()->m_sym->IsPropertySym()); |
| Assert(instr->GetSrc2() == nullptr); |
| } |
| #endif |
| |
| StackSym * |
| GlobOpt::EnsureObjectTypeSym(StackSym * objectSym) |
| { |
| Assert(!objectSym->IsTypeSpec()); |
| |
| objectSym->EnsureObjectInfo(this->func); |
| |
| if (objectSym->HasObjectTypeSym()) |
| { |
| Assert(this->objectTypeSyms); |
| return objectSym->GetObjectTypeSym(); |
| } |
| |
| if (this->objectTypeSyms == nullptr) |
| { |
| this->objectTypeSyms = JitAnew(this->alloc, BVSparse<JitArenaAllocator>, this->alloc); |
| } |
| |
| StackSym * typeSym = StackSym::New(TyVar, this->func); |
| |
| objectSym->GetObjectInfo()->m_typeSym = typeSym; |
| |
| this->objectTypeSyms->Set(typeSym->m_id); |
| |
| return typeSym; |
| } |
| |
| PropertySym * |
| GlobOpt::EnsurePropertyWriteGuardSym(PropertySym * propertySym) |
| { |
| // Make sure that the PropertySym has a proto cache sym which is chained into the propertySym list. |
| if (!propertySym->m_writeGuardSym) |
| { |
| propertySym->m_writeGuardSym = PropertySym::New(propertySym->m_stackSym, propertySym->m_propertyId, (uint32)-1, (uint)-1, PropertyKindWriteGuard, this->func); |
| } |
| |
| return propertySym->m_writeGuardSym; |
| } |
| |
| void |
| GlobOpt::PreparePropertySymForTypeCheckSeq(PropertySym *propertySym) |
| { |
| Assert(!propertySym->m_stackSym->IsTypeSpec()); |
| EnsureObjectTypeSym(propertySym->m_stackSym); |
| EnsurePropertyWriteGuardSym(propertySym); |
| } |
| |
| bool |
| GlobOpt::IsPropertySymPreparedForTypeCheckSeq(PropertySym *propertySym) |
| { |
| Assert(!propertySym->m_stackSym->IsTypeSpec()); |
| |
| // The following doesn't need to be true. We may copy prop a constant into an object sym, which has |
| // previously been prepared for type check sequence optimization. |
| // Assert(!propertySym->m_stackSym->m_isIntConst || !propertySym->HasObjectTypeSym()); |
| |
| // The following doesn't need to be true. We may copy prop the object sym into a field load or store |
| // that doesn't have object type spec info and hence the operand wasn't prepared and doesn't have a write |
| // guard. The object sym, however, may have other field operations which are object type specialized and |
| // thus the type sym for it has been created. |
| // Assert(propertySym->HasObjectTypeSym() == propertySym->HasWriteGuardSym()); |
| |
| return propertySym->HasObjectTypeSym(); |
| } |
| |
| bool |
| GlobOpt::PreparePropertySymOpndForTypeCheckSeq(IR::PropertySymOpnd * propertySymOpnd, IR::Instr* instr, Loop * loop) |
| { |
| if (!DoFieldRefOpts(loop) || !OpCodeAttr::FastFldInstr(instr->m_opcode) || instr->CallsAccessor()) |
| { |
| return false; |
| } |
| |
| if (!propertySymOpnd->HasObjTypeSpecFldInfo()) |
| { |
| return false; |
| } |
| |
| ObjTypeSpecFldInfo* info = propertySymOpnd->GetObjTypeSpecInfo(); |
| |
| if (info->UsesAccessor() || info->IsRootObjectNonConfigurableFieldLoad()) |
| { |
| return false; |
| } |
| |
| if (info->IsPoly() && !info->GetEquivalentTypeSet()) |
| { |
| return false; |
| } |
| |
| PropertySym * propertySym = propertySymOpnd->m_sym->AsPropertySym(); |
| |
| PreparePropertySymForTypeCheckSeq(propertySym); |
| propertySymOpnd->SetTypeCheckSeqCandidate(true); |
| propertySymOpnd->SetIsBeingStored(propertySymOpnd == instr->GetDst()); |
| |
| return true; |
| } |
| |
| bool |
| GlobOpt::CheckIfPropOpEmitsTypeCheck(IR::Instr *instr, IR::PropertySymOpnd *opnd) |
| { |
| if (!DoFieldRefOpts() || !OpCodeAttr::FastFldInstr(instr->m_opcode)) |
| { |
| return false; |
| } |
| |
| if (!opnd->IsTypeCheckSeqCandidate()) |
| { |
| return false; |
| } |
| |
| return CheckIfInstrInTypeCheckSeqEmitsTypeCheck(instr, opnd); |
| } |
| |
| IR::PropertySymOpnd * |
| GlobOpt::CreateOpndForTypeCheckOnly(IR::PropertySymOpnd* opnd, Func* func) |
| { |
| // Used only for CheckObjType instruction today. Future users should make a call |
| // whether the new operand is jit optimized in their scenario or not. |
| |
| Assert(!opnd->IsRootObjectNonConfigurableFieldLoad()); |
| IR::PropertySymOpnd *newOpnd = opnd->CopyCommon(func); |
| |
| newOpnd->SetObjTypeSpecFldInfo(opnd->GetObjTypeSpecInfo()); |
| newOpnd->SetUsesAuxSlot(opnd->UsesAuxSlot()); |
| newOpnd->SetSlotIndex(opnd->GetSlotIndex()); |
| |
| newOpnd->objTypeSpecFlags = opnd->objTypeSpecFlags; |
| // If we're turning the instruction owning this operand into a CheckObjType, we will do a type check here |
| // only for the sake of downstream instructions, so the flags pertaining to this property access are |
| // irrelevant, because we don't do a property access here. |
| newOpnd->SetTypeCheckOnly(true); |
| newOpnd->usesFixedValue = false; |
| |
| newOpnd->finalType = opnd->finalType; |
| newOpnd->guardedPropOps = opnd->guardedPropOps != nullptr ? opnd->guardedPropOps->CopyNew() : nullptr; |
| newOpnd->writeGuards = opnd->writeGuards != nullptr ? opnd->writeGuards->CopyNew() : nullptr; |
| |
| newOpnd->SetIsJITOptimizedReg(true); |
| |
| return newOpnd; |
| } |
| |
| bool |
| GlobOpt::FinishOptPropOp(IR::Instr *instr, IR::PropertySymOpnd *opnd, BasicBlock* block, bool updateExistingValue, bool* emitsTypeCheckOut, bool* changesTypeValueOut) |
| { |
| if (!DoFieldRefOpts() || !OpCodeAttr::FastFldInstr(instr->m_opcode)) |
| { |
| return false; |
| } |
| |
| bool isTypeCheckSeqCandidate = opnd->IsTypeCheckSeqCandidate(); |
| bool isObjTypeSpecialized = false; |
| bool isObjTypeChecked = false; |
| |
| if (isTypeCheckSeqCandidate) |
| { |
| isObjTypeSpecialized = ProcessPropOpInTypeCheckSeq<true>(instr, opnd, block, updateExistingValue, emitsTypeCheckOut, changesTypeValueOut, &isObjTypeChecked); |
| } |
| |
| if (opnd == instr->GetDst() && this->objectTypeSyms) |
| { |
| if (block == nullptr) |
| { |
| block = this->currentBlock; |
| } |
| |
| // This is a property store that may change the layout of the object that it stores to. This means that |
| // it may change any aliased object. Do two things to address this: |
| // - Add all object types in this function to the set that may have had a property added. This will prevent |
| // final type optimization across this instruction. (Only needed here for non-specialized stores.) |
| // - Kill all type symbols that currently hold object-header-inlined types. Any of them may have their layout |
| // changed by the addition of a property. |
| |
| SymID opndId = opnd->HasObjectTypeSym() ? opnd->GetObjectTypeSym()->m_id : -1; |
| |
| if (!isObjTypeChecked) |
| { |
| if (block->globOptData.maybeWrittenTypeSyms == nullptr) |
| { |
| block->globOptData.maybeWrittenTypeSyms = JitAnew(this->alloc, BVSparse<JitArenaAllocator>, this->alloc); |
| } |
| if (isObjTypeSpecialized) |
| { |
| // The current object will be protected by a type check, unless no further accesses to it are |
| // protected by this access. |
| Assert(this->objectTypeSyms->Test(opndId)); |
| this->objectTypeSyms->Clear(opndId); |
| } |
| block->globOptData.maybeWrittenTypeSyms->Or(this->objectTypeSyms); |
| if (isObjTypeSpecialized) |
| { |
| this->objectTypeSyms->Set(opndId); |
| } |
| } |
| |
| if (!isObjTypeSpecialized || opnd->ChangesObjectLayout()) |
| { |
| this->KillObjectHeaderInlinedTypeSyms(block, isObjTypeSpecialized, opndId); |
| } |
| else if (!isObjTypeChecked && this->HasLiveObjectHeaderInlinedTypeSym(block, true, opndId)) |
| { |
| opnd->SetTypeCheckRequired(true); |
| } |
| } |
| |
| return isObjTypeSpecialized; |
| } |
| |
| void |
| GlobOpt::KillObjectHeaderInlinedTypeSyms(BasicBlock *block, bool isObjTypeSpecialized, SymID opndId) |
| { |
| this->MapObjectHeaderInlinedTypeSymsUntil(block, isObjTypeSpecialized, opndId, [&](SymID symId)->bool { this->currentBlock->globOptData.liveFields->Clear(symId); return false; }); |
| } |
| |
| bool |
| GlobOpt::HasLiveObjectHeaderInlinedTypeSym(BasicBlock *block, bool isObjTypeSpecialized, SymID opndId) |
| { |
| return this->MapObjectHeaderInlinedTypeSymsUntil(block, true, opndId, [&](SymID symId)->bool { return this->currentBlock->globOptData.liveFields->Test(symId); }); |
| } |
| |
| template<class Fn> |
| bool |
| GlobOpt::MapObjectHeaderInlinedTypeSymsUntil(BasicBlock *block, bool isObjTypeSpecialized, SymID opndId, Fn fn) |
| { |
| if (this->objectTypeSyms == nullptr) |
| { |
| return false; |
| } |
| |
| FOREACH_BITSET_IN_SPARSEBV(symId, this->objectTypeSyms) |
| { |
| if (symId == opndId && isObjTypeSpecialized) |
| { |
| // The current object will be protected by a type check, unless no further accesses to it are |
| // protected by this access. |
| continue; |
| } |
| Value *value = block->globOptData.FindObjectTypeValue(symId); |
| if (value) |
| { |
| JsTypeValueInfo *valueInfo = value->GetValueInfo()->AsJsType(); |
| Assert(valueInfo); |
| if (valueInfo->GetJsType() != nullptr) |
| { |
| JITTypeHolder type(valueInfo->GetJsType()); |
| if (Js::DynamicType::Is(type->GetTypeId())) |
| { |
| if (type->GetTypeHandler()->IsObjectHeaderInlinedTypeHandler()) |
| { |
| if (fn(symId)) |
| { |
| return true; |
| } |
| } |
| } |
| } |
| else if (valueInfo->GetJsTypeSet()) |
| { |
| Js::EquivalentTypeSet *typeSet = valueInfo->GetJsTypeSet(); |
| for (uint16 i = 0; i < typeSet->GetCount(); i++) |
| { |
| JITTypeHolder type = typeSet->GetType(i); |
| if (type != nullptr && Js::DynamicType::Is(type->GetTypeId())) |
| { |
| if (type->GetTypeHandler()->IsObjectHeaderInlinedTypeHandler()) |
| { |
| if (fn(symId)) |
| { |
| return true; |
| } |
| break; |
| } |
| } |
| } |
| } |
| } |
| } |
| NEXT_BITSET_IN_SPARSEBV; |
| |
| return false; |
| } |
| |
| bool |
| GlobOpt::AreTypeSetsIdentical(Js::EquivalentTypeSet * leftTypeSet, Js::EquivalentTypeSet * rightTypeSet) |
| { |
| return Js::EquivalentTypeSet::AreIdentical(leftTypeSet, rightTypeSet); |
| } |
| |
| bool |
| GlobOpt::IsSubsetOf(Js::EquivalentTypeSet * leftTypeSet, Js::EquivalentTypeSet * rightTypeSet) |
| { |
| return Js::EquivalentTypeSet::IsSubsetOf(leftTypeSet, rightTypeSet); |
| } |
| |
| bool |
| GlobOpt::CompareCurrentTypesWithExpectedTypes(JsTypeValueInfo *valueInfo, IR::PropertySymOpnd * propertySymOpnd) |
| { |
| bool isTypeDead = propertySymOpnd->IsTypeDead(); |
| |
| if (valueInfo == nullptr || (valueInfo->GetJsType() == nullptr && valueInfo->GetJsTypeSet() == nullptr)) |
| { |
| // No upstream types. Do a type check. |
| return !isTypeDead; |
| } |
| |
| if (!propertySymOpnd->HasEquivalentTypeSet() || propertySymOpnd->NeedsMonoCheck()) |
| { |
| JITTypeHolder opndType = propertySymOpnd->GetType(); |
| |
| if (valueInfo->GetJsType() != nullptr) |
| { |
| if (valueInfo->GetJsType() == propertySymOpnd->GetType()) |
| { |
| return true; |
| } |
| if (propertySymOpnd->HasInitialType() && valueInfo->GetJsType() == propertySymOpnd->GetInitialType()) |
| { |
| return !isTypeDead; |
| } |
| return false; |
| } |
| else |
| { |
| Assert(valueInfo->GetJsTypeSet()); |
| Js::EquivalentTypeSet *valueTypeSet = valueInfo->GetJsTypeSet(); |
| |
| if (valueTypeSet->Contains(opndType)) |
| { |
| return !isTypeDead; |
| } |
| if (propertySymOpnd->HasInitialType() && valueTypeSet->Contains(propertySymOpnd->GetInitialType())) |
| { |
| return !isTypeDead; |
| } |
| return false; |
| } |
| } |
| else |
| { |
| Js::EquivalentTypeSet * opndTypeSet = propertySymOpnd->GetEquivalentTypeSet(); |
| |
| if (valueInfo->GetJsType() != nullptr) |
| { |
| uint16 checkedTypeSetIndex; |
| if (opndTypeSet->Contains(valueInfo->GetJsType(), &checkedTypeSetIndex)) |
| { |
| return true; |
| } |
| return false; |
| } |
| else |
| { |
| if (IsSubsetOf(valueInfo->GetJsTypeSet(), opndTypeSet)) |
| { |
| return true; |
| } |
| if (propertySymOpnd->IsMono() ? |
| valueInfo->GetJsTypeSet()->Contains(propertySymOpnd->GetFirstEquivalentType()) : |
| IsSubsetOf(opndTypeSet, valueInfo->GetJsTypeSet())) |
| { |
| return true; |
| } |
| return false; |
| } |
| } |
| } |
| |
| bool |
| GlobOpt::ProcessPropOpInTypeCheckSeq(IR::Instr* instr, IR::PropertySymOpnd *opnd) |
| { |
| return ProcessPropOpInTypeCheckSeq<true>(instr, opnd, this->currentBlock, false); |
| } |
| |
| bool GlobOpt::CheckIfInstrInTypeCheckSeqEmitsTypeCheck(IR::Instr* instr, IR::PropertySymOpnd *opnd) |
| { |
| bool emitsTypeCheck; |
| ProcessPropOpInTypeCheckSeq<false>(instr, opnd, this->currentBlock, false, &emitsTypeCheck); |
| return emitsTypeCheck; |
| } |
| |
| template<bool makeChanges> |
| bool |
| GlobOpt::ProcessPropOpInTypeCheckSeq(IR::Instr* instr, IR::PropertySymOpnd *opnd, BasicBlock* block, bool updateExistingValue, bool* emitsTypeCheckOut, bool* changesTypeValueOut, bool *isTypeCheckedOut) |
| { |
| // We no longer mark types as dead in the backward pass, so we should never see an instr with a dead type here |
| // during the forward pass. For the time being we've retained the logic below to deal with dead types in case |
| // we ever wanted to revert back to more aggressive type killing that we had before. |
| Assert(!opnd->IsTypeDead()); |
| |
| Assert(opnd->IsTypeCheckSeqCandidate()); |
| Assert(opnd->HasObjectTypeSym()); |
| |
| bool isStore = opnd == instr->GetDst(); |
| bool isTypeDead = opnd->IsTypeDead(); |
| bool consumeType = makeChanges && !IsLoopPrePass(); |
| bool produceType = makeChanges && !isTypeDead; |
| bool isSpecialized = false; |
| bool emitsTypeCheck = false; |
| bool addsProperty = false; |
| |
| if (block == nullptr) |
| { |
| block = this->currentBlock; |
| } |
| |
| StackSym * typeSym = opnd->GetObjectTypeSym(); |
| |
| #if DBG |
| uint16 typeCheckSeqFlagsBefore; |
| Value* valueBefore = nullptr; |
| JsTypeValueInfo* valueInfoBefore = nullptr; |
| if (!makeChanges) |
| { |
| typeCheckSeqFlagsBefore = opnd->GetTypeCheckSeqFlags(); |
| valueBefore = block->globOptData.FindObjectTypeValue(typeSym); |
| if (valueBefore != nullptr) |
| { |
| Assert(valueBefore->GetValueInfo() != nullptr && valueBefore->GetValueInfo()->IsJsType()); |
| valueInfoBefore = valueBefore->GetValueInfo()->AsJsType(); |
| } |
| } |
| #endif |
| |
| Value *value = block->globOptData.FindObjectTypeValue(typeSym); |
| JsTypeValueInfo* valueInfo = value != nullptr ? value->GetValueInfo()->AsJsType() : nullptr; |
| |
| if (consumeType && valueInfo != nullptr) |
| { |
| opnd->SetTypeAvailable(true); |
| } |
| |
| bool doEquivTypeCheck = opnd->HasEquivalentTypeSet() && !opnd->NeedsMonoCheck(); |
| if (!doEquivTypeCheck) |
| { |
| AssertOrFailFast(!opnd->NeedsDepolymorphication()); |
| |
| // We need a monomorphic type check here (e.g., final type opt, fixed field check on non-proto property). |
| JITTypeHolder opndType = opnd->GetType(); |
| |
| if (valueInfo == nullptr || (valueInfo->GetJsType() == nullptr && valueInfo->GetJsTypeSet() == nullptr)) |
| { |
| // This is the initial type check. |
| opnd->SetTypeAvailable(false); |
| isSpecialized = !isTypeDead; |
| emitsTypeCheck = isSpecialized; |
| addsProperty = isStore && isSpecialized && opnd->HasInitialType(); |
| if (produceType) |
| { |
| SetObjectTypeFromTypeSym(typeSym, opndType, nullptr, block, updateExistingValue); |
| } |
| } |
| else if (valueInfo->GetJsType() != nullptr) |
| { |
| // We have a monomorphic type check upstream. Check against initial/final type. |
| const JITTypeHolder valueType(valueInfo->GetJsType()); |
| if (valueType == opndType) |
| { |
| // The type on this instruction matches the live value in the value table, so there is no need to |
| // refresh the value table. |
| isSpecialized = true; |
| if (isTypeCheckedOut) |
| { |
| *isTypeCheckedOut = true; |
| } |
| if (consumeType) |
| { |
| opnd->SetTypeChecked(true); |
| } |
| } |
| else if (opnd->HasInitialType() && valueType == opnd->GetInitialType()) |
| { |
| // Checked type matches the initial type at this store. |
| bool objectMayHaveAcquiredAdditionalProperties = |
| block->globOptData.maybeWrittenTypeSyms && |
| block->globOptData.maybeWrittenTypeSyms->Test(typeSym->m_id); |
| if (consumeType) |
| { |
| opnd->SetTypeChecked(!objectMayHaveAcquiredAdditionalProperties); |
| opnd->SetInitialTypeChecked(!objectMayHaveAcquiredAdditionalProperties); |
| } |
| if (produceType) |
| { |
| SetObjectTypeFromTypeSym(typeSym, opndType, nullptr, block, updateExistingValue); |
| } |
| isSpecialized = !isTypeDead || !objectMayHaveAcquiredAdditionalProperties; |
| emitsTypeCheck = isSpecialized && objectMayHaveAcquiredAdditionalProperties; |
| addsProperty = isSpecialized; |
| if (isTypeCheckedOut) |
| { |
| *isTypeCheckedOut = !objectMayHaveAcquiredAdditionalProperties; |
| } |
| } |
| else |
| { |
| // This must be a type mismatch situation, because the value is available, but doesn't match either |
| // the current type or the initial type. We will not optimize this instruction and we do not produce |
| // a new type value here. |
| isSpecialized = false; |
| |
| if (consumeType) |
| { |
| opnd->SetTypeMismatch(true); |
| } |
| } |
| } |
| else |
| { |
| // We have an equivalent type check upstream, but we require a particular type at this point. We |
| // can't treat it as "checked", but we may benefit from checking for the required type. |
| Assert(valueInfo->GetJsTypeSet()); |
| Js::EquivalentTypeSet *valueTypeSet = valueInfo->GetJsTypeSet(); |
| if (valueTypeSet->Contains(opndType)) |
| { |
| // Required type is in the type set we've checked. Check for the required type here, and |
| // note in the value info that we've narrowed down to this type. (But leave the type set in the |
| // value info so it can be merged with the same type set on other paths.) |
| isSpecialized = !isTypeDead; |
| emitsTypeCheck = isSpecialized; |
| if (produceType) |
| { |
| SetSingleTypeOnObjectTypeValue(value, opndType); |
| } |
| } |
| else if (opnd->HasInitialType() && valueTypeSet->Contains(opnd->GetInitialType())) |
| { |
| // Required initial type is in the type set we've checked. Check for the initial type here, and |
| // note in the value info that we've narrowed down to this type. (But leave the type set in the |
| // value info so it can be merged with the same type set on other paths.) |
| isSpecialized = !isTypeDead; |
| emitsTypeCheck = isSpecialized; |
| addsProperty = isSpecialized; |
| if (produceType) |
| { |
| SetSingleTypeOnObjectTypeValue(value, opndType); |
| } |
| } |
| else |
| { |
| // This must be a type mismatch situation, because the value is available, but doesn't match either |
| // the current type or the initial type. We will not optimize this instruction and we do not produce |
| // a new type value here. |
| isSpecialized = false; |
| |
| if (consumeType) |
| { |
| opnd->SetTypeMismatch(true); |
| } |
| } |
| } |
| } |
| else |
| { |
| Assert(!opnd->NeedsMonoCheck()); |
| |
| Js::EquivalentTypeSet * opndTypeSet = opnd->GetEquivalentTypeSet(); |
| uint16 checkedTypeSetIndex = (uint16)-1; |
| |
| if (opnd->NeedsDepolymorphication()) |
| { |
| // The opnd's type set (opndTypeSet) is non-equivalent. Test all the types coming from the valueInfo. |
| // If all of them are contained in opndTypeSet, and all of them have the same slot index in opnd's |
| // objtypespecfldinfo, then we can use that slot index and treat the set as equivalent. |
| // (Also test whether all types do/don't use aux slots.) |
| |
| uint16 slotIndex = Js::Constants::NoSlot; |
| bool auxSlot = false; |
| |
| // Do this work only if there is an upstream type value. We don't attempt to do a type check based on |
| // a non-equivalent set. |
| if (valueInfo != nullptr) |
| { |
| if (valueInfo->GetJsType() != nullptr) |
| { |
| opnd->TryDepolymorphication(valueInfo->GetJsType(), Js::Constants::NoSlot, false, &slotIndex, &auxSlot, &checkedTypeSetIndex); |
| } |
| else if (valueInfo->GetJsTypeSet() != nullptr) |
| { |
| Js::EquivalentTypeSet *typeSet = valueInfo->GetJsTypeSet(); |
| for (uint16 i = 0; i < typeSet->GetCount(); i++) |
| { |
| opnd->TryDepolymorphication(typeSet->GetType(i), slotIndex, auxSlot, &slotIndex, &auxSlot); |
| if (slotIndex == Js::Constants::NoSlot) |
| { |
| // Indicates failure/mismatch. We're done. |
| break; |
| } |
| } |
| } |
| } |
| |
| if (slotIndex == Js::Constants::NoSlot) |
| { |
| // Indicates failure/mismatch |
| isSpecialized = false; |
| if (consumeType) |
| { |
| opnd->SetTypeMismatch(true); |
| } |
| } |
| else |
| { |
| // Indicates we can optimize, as all upstream types are equivalent here. |
| |
| opnd->SetSlotIndex(slotIndex); |
| opnd->SetUsesAuxSlot(auxSlot); |
| |
| opnd->GetObjTypeSpecInfo()->SetSlotIndex(slotIndex); |
| opnd->GetObjTypeSpecInfo()->SetUsesAuxSlot(auxSlot); |
| |
| isSpecialized = true; |
| if (isTypeCheckedOut) |
| { |
| *isTypeCheckedOut = true; |
| } |
| if (consumeType) |
| { |
| opnd->SetTypeChecked(true); |
| } |
| if (checkedTypeSetIndex != (uint16)-1) |
| { |
| opnd->SetCheckedTypeSetIndex(checkedTypeSetIndex); |
| } |
| } |
| } |
| else if (valueInfo == nullptr || (valueInfo->GetJsType() == nullptr && valueInfo->GetJsTypeSet() == nullptr)) |
| { |
| // If we don't have a value for the type we will have to emit a type check and we produce a new type value here. |
| if (produceType) |
| { |
| if (opnd->IsMono()) |
| { |
| SetObjectTypeFromTypeSym(typeSym, opnd->GetFirstEquivalentType(), nullptr, block, updateExistingValue); |
| } |
| else |
| { |
| SetObjectTypeFromTypeSym(typeSym, nullptr, opndTypeSet, block, updateExistingValue); |
| } |
| } |
| isSpecialized = !isTypeDead; |
| emitsTypeCheck = isSpecialized; |
| } |
| else if (valueInfo->GetJsType() != nullptr ? |
| opndTypeSet->Contains(valueInfo->GetJsType(), &checkedTypeSetIndex) : |
| IsSubsetOf(valueInfo->GetJsTypeSet(), opndTypeSet)) |
| { |
| // All the types in the value info are contained in the set required by this access, |
| // meaning that they're equivalent to the opnd's type set. |
| // We won't have a type check, and we don't need to touch the type value. |
| isSpecialized = true; |
| if (isTypeCheckedOut) |
| { |
| *isTypeCheckedOut = true; |
| } |
| if (consumeType) |
| { |
| opnd->SetTypeChecked(true); |
| } |
| if (checkedTypeSetIndex != (uint16)-1) |
| { |
| opnd->SetCheckedTypeSetIndex(checkedTypeSetIndex); |
| } |
| } |
| else if (valueInfo->GetJsTypeSet() && |
| (opnd->IsMono() ? |
| valueInfo->GetJsTypeSet()->Contains(opnd->GetFirstEquivalentType()) : |
| IsSubsetOf(opndTypeSet, valueInfo->GetJsTypeSet()) |
| ) |
| ) |
| { |
| // We have an equivalent type check upstream, but we require a tighter type check at this point. |
| // We can't treat the operand as "checked", but check for equivalence with the tighter set and update the |
| // value info. |
| if (produceType) |
| { |
| if (opnd->IsMono()) |
| { |
| SetObjectTypeFromTypeSym(typeSym, opnd->GetFirstEquivalentType(), nullptr, block, updateExistingValue); |
| } |
| else |
| { |
| SetObjectTypeFromTypeSym(typeSym, nullptr, opndTypeSet, block, updateExistingValue); |
| } |
| } |
| isSpecialized = !isTypeDead; |
| emitsTypeCheck = isSpecialized; |
| } |
| else |
| { |
| // This must be a type mismatch situation, because the value is available, but doesn't match either |
| // the current type or the initial type. We will not optimize this instruction and we do not produce |
| // a new type value here. |
| isSpecialized = false; |
| |
| if (consumeType) |
| { |
| opnd->SetTypeMismatch(true); |
| } |
| } |
| } |
| |
| Assert(isSpecialized || (!emitsTypeCheck && !addsProperty)); |
| |
| if (consumeType && opnd->MayNeedWriteGuardProtection()) |
| { |
| Assert(!isStore); |
| PropertySym *propertySym = opnd->m_sym->AsPropertySym(); |
| Assert(propertySym->m_writeGuardSym); |
| opnd->SetWriteGuardChecked(!!block->globOptData.liveFields->Test(propertySym->m_writeGuardSym->m_id)); |
| } |
| |
| // Even specialized property adds must kill all types for other property adds. That's because any other object sym |
| // may, in fact, be an alias of the instance whose type is being modified here. (see Windows Blue Bug 541876) |
| if (makeChanges && addsProperty) |
| { |
| Assert(isStore && isSpecialized); |
| Assert(this->objectTypeSyms != nullptr); |
| Assert(this->objectTypeSyms->Test(typeSym->m_id)); |
| |
| if (block->globOptData.maybeWrittenTypeSyms == nullptr) |
| { |
| block->globOptData.maybeWrittenTypeSyms = JitAnew(this->alloc, BVSparse<JitArenaAllocator>, this->alloc); |
| } |
| |
| this->objectTypeSyms->Clear(typeSym->m_id); |
| block->globOptData.maybeWrittenTypeSyms->Or(this->objectTypeSyms); |
| this->objectTypeSyms->Set(typeSym->m_id); |
| } |
| |
| if (produceType && emitsTypeCheck && opnd->IsMono()) |
| { |
| // Consider (ObjTypeSpec): Represent maybeWrittenTypeSyms as a flag on value info of the type sym. |
| if (block->globOptData.maybeWrittenTypeSyms != nullptr) |
| { |
| // We're doing a type check here, so objtypespec of property adds is safe for this type |
| // from this point forward. |
| block->globOptData.maybeWrittenTypeSyms->Clear(typeSym->m_id); |
| } |
| } |
| |
| // Consider (ObjTypeSpec): Enable setting write guards live on instructions hoisted out of loops. Note that produceType |
| // is false if the type values on loop back edges don't match (see earlier comments). |
| // This means that hoisted instructions won't set write guards live if the type changes in the loop, even if |
| // the corresponding properties have not been written inside the loop. This may result in some unnecessary type |
| // checks and bailouts inside the loop. To enable this, we would need to verify the write guards are still live |
| // on the back edge (much like we're doing for types above). |
| |
| // Consider (ObjTypeSpec): Support polymorphic write guards as well. We can't currently distinguish between mono and |
| // poly write guards, and a type check can only protect operations matching with respect to polymorphism (see |
| // BackwardPass::TrackObjTypeSpecProperties for details), so for now we only target monomorphic operations. |
| if (produceType && emitsTypeCheck && opnd->IsMono()) |
| { |
| // If the type check we'll emit here protects some property operations that require a write guard (i.e. |
| // they must do an extra type check and property guard check, if they have been written to in this |
| // function), let's mark the write guards as live here, so we can accurately track if their properties |
| // have been written to. Make sure we only set those that we'll actually guard, i.e. those that match |
| // with respect to polymorphism. |
| if (opnd->GetWriteGuards() != nullptr) |
| { |
| block->globOptData.liveFields->Or(opnd->GetWriteGuards()); |
| } |
| } |
| |
| if (makeChanges && isTypeDead) |
| { |
| this->KillObjectType(opnd->GetObjectSym(), block->globOptData.liveFields); |
| } |
| |
| #if DBG |
| if (!makeChanges) |
| { |
| uint16 typeCheckSeqFlagsAfter = opnd->GetTypeCheckSeqFlags(); |
| Assert(typeCheckSeqFlagsBefore == typeCheckSeqFlagsAfter); |
| |
| Value* valueAfter = block->globOptData.FindObjectTypeValue(typeSym); |
| Assert(valueBefore == valueAfter); |
| if (valueAfter != nullptr) |
| { |
| Assert(valueBefore != nullptr); |
| Assert(valueAfter->GetValueInfo() != nullptr && valueAfter->GetValueInfo()->IsJsType()); |
| JsTypeValueInfo* valueInfoAfter = valueAfter->GetValueInfo()->AsJsType(); |
| Assert(valueInfoBefore == valueInfoAfter); |
| Assert(valueInfoBefore->GetJsType() == valueInfoAfter->GetJsType()); |
| Assert(valueInfoBefore->GetJsTypeSet() == valueInfoAfter->GetJsTypeSet()); |
| } |
| } |
| #endif |
| |
| if (emitsTypeCheckOut != nullptr) |
| { |
| *emitsTypeCheckOut = emitsTypeCheck; |
| } |
| |
| if (changesTypeValueOut != nullptr) |
| { |
| *changesTypeValueOut = isSpecialized && (emitsTypeCheck || addsProperty); |
| } |
| |
| return isSpecialized; |
| } |
| |
| void |
| GlobOpt::OptNewScObject(IR::Instr** instrPtr, Value* srcVal) |
| { |
| IR::Instr *&instr = *instrPtr; |
| |
| if (!instr->IsNewScObjectInstr() || IsLoopPrePass() || !this->DoFieldRefOpts() || PHASE_OFF(Js::ObjTypeSpecNewObjPhase, this->func)) |
| { |
| return; |
| } |
| |
| bool isCtorInlined = instr->m_opcode == Js::OpCode::NewScObjectNoCtor; |
| const JITTimeConstructorCache * ctorCache = instr->IsProfiledInstr() ? |
| instr->m_func->GetConstructorCache(static_cast<Js::ProfileId>(instr->AsProfiledInstr()->u.profileId)) : nullptr; |
| |
| // TODO: OOP JIT, enable assert |
| //Assert(ctorCache == nullptr || srcVal->GetValueInfo()->IsVarConstant() && Js::JavascriptFunction::Is(srcVal->GetValueInfo()->AsVarConstant()->VarValue())); |
| Assert(ctorCache == nullptr || !ctorCache->IsTypeFinal() || ctorCache->CtorHasNoExplicitReturnValue()); |
| |
| if (ctorCache != nullptr && !ctorCache->SkipNewScObject() && (isCtorInlined || ctorCache->IsTypeFinal())) |
| { |
| GenerateBailAtOperation(instrPtr, IR::BailOutFailedCtorGuardCheck); |
| } |
| } |
| |
| void |
| GlobOpt::ValueNumberObjectType(IR::Opnd *dstOpnd, IR::Instr *instr) |
| { |
| if (!dstOpnd->IsRegOpnd()) |
| { |
| return; |
| } |
| |
| if (dstOpnd->AsRegOpnd()->m_sym->IsTypeSpec()) |
| { |
| return; |
| } |
| |
| if (instr->IsNewScObjectInstr()) |
| { |
| // If we have a NewScObj* for which we have a valid constructor cache we know what type the created object will have. |
| // Let's produce the type value accordingly so we don't insert a type check and bailout in the constructor and |
| // potentially further downstream. |
| Assert(!PHASE_OFF(Js::ObjTypeSpecNewObjPhase, this->func) || !instr->HasBailOutInfo()); |
| |
| if (instr->HasBailOutInfo()) |
| { |
| Assert(instr->IsProfiledInstr()); |
| Assert(instr->GetBailOutKind() == IR::BailOutFailedCtorGuardCheck); |
| |
| bool isCtorInlined = instr->m_opcode == Js::OpCode::NewScObjectNoCtor; |
| JITTimeConstructorCache * ctorCache = instr->m_func->GetConstructorCache(static_cast<Js::ProfileId>(instr->AsProfiledInstr()->u.profileId)); |
| Assert(ctorCache != nullptr && (isCtorInlined || ctorCache->IsTypeFinal())); |
| |
| StackSym* objSym = dstOpnd->AsRegOpnd()->m_sym; |
| StackSym* dstTypeSym = EnsureObjectTypeSym(objSym); |
| Assert(this->currentBlock->globOptData.FindValue(dstTypeSym) == nullptr); |
| |
| SetObjectTypeFromTypeSym(dstTypeSym, ctorCache->GetType(), nullptr); |
| } |
| } |
| else |
| { |
| // If the dst opnd is a reg that has a type sym associated with it, then we are either killing |
| // the type's existing value or (in the case of a reg copy) assigning it the value of |
| // the src's type sym (if any). If the dst doesn't have a type sym, but the src does, let's |
| // give dst a new type sym and transfer the value. |
| Value *newValue = nullptr; |
| IR::Opnd * srcOpnd = instr->GetSrc1(); |
| |
| if (instr->m_opcode == Js::OpCode::Ld_A && srcOpnd->IsRegOpnd() && |
| !srcOpnd->AsRegOpnd()->m_sym->IsTypeSpec() && srcOpnd->AsRegOpnd()->m_sym->HasObjectTypeSym()) |
| { |
| StackSym *srcTypeSym = srcOpnd->AsRegOpnd()->m_sym->GetObjectTypeSym(); |
| newValue = this->currentBlock->globOptData.FindValue(srcTypeSym); |
| } |
| |
| if (newValue == nullptr) |
| { |
| if (dstOpnd->AsRegOpnd()->m_sym->HasObjectTypeSym()) |
| { |
| StackSym * typeSym = dstOpnd->AsRegOpnd()->m_sym->GetObjectTypeSym(); |
| this->currentBlock->globOptData.ClearSymValue(typeSym); |
| } |
| } |
| else |
| { |
| Assert(newValue->GetValueInfo()->IsJsType()); |
| StackSym * typeSym; |
| if (!dstOpnd->AsRegOpnd()->m_sym->HasObjectTypeSym()) |
| { |
| typeSym = nullptr; |
| } |
| typeSym = EnsureObjectTypeSym(dstOpnd->AsRegOpnd()->m_sym); |
| this->currentBlock->globOptData.SetValue(newValue, typeSym); |
| } |
| } |
| } |
| |
| IR::Instr * |
| GlobOpt::SetTypeCheckBailOut(IR::Opnd *opnd, IR::Instr *instr, BailOutInfo *bailOutInfo) |
| { |
| if (this->IsLoopPrePass() || !opnd->IsSymOpnd()) |
| { |
| return instr; |
| } |
| |
| if (!opnd->AsSymOpnd()->IsPropertySymOpnd()) |
| { |
| return instr; |
| } |
| |
| IR::PropertySymOpnd * propertySymOpnd = opnd->AsPropertySymOpnd(); |
| |
| AssertMsg(propertySymOpnd->TypeCheckSeqBitsSetOnlyIfCandidate(), "Property sym operand optimized despite not being a candidate?"); |
| AssertMsg(bailOutInfo == nullptr || !instr->HasBailOutInfo(), "Why are we adding new bailout info to an instruction that already has it?"); |
| |
| auto HandleBailout = [&](IR::BailOutKind bailOutKind)->void { |
| // At this point, we have a cached type that is live downstream or the type check is required |
| // for a fixed field load. If we can't do away with the type check, then we're going to need bailout, |
| // so lets add bailout info if we don't already have it. |
| if (!instr->HasBailOutInfo()) |
| { |
| if (bailOutInfo) |
| { |
| instr = instr->ConvertToBailOutInstr(bailOutInfo, bailOutKind); |
| } |
| else |
| { |
| GenerateBailAtOperation(&instr, bailOutKind); |
| BailOutInfo *bailOutInfo = instr->GetBailOutInfo(); |
| |
| // Consider (ObjTypeSpec): If we're checking a fixed field here the bailout could be due to polymorphism or |
| // due to a fixed field turning non-fixed. Consider distinguishing between the two. |
| bailOutInfo->polymorphicCacheIndex = propertySymOpnd->m_inlineCacheIndex; |
| } |
| } |
| else if (instr->GetBailOutKind() == IR::BailOutMarkTempObject) |
| { |
| Assert(!bailOutInfo); |
| Assert(instr->GetBailOutInfo()->polymorphicCacheIndex == -1); |
| instr->SetBailOutKind(bailOutKind | IR::BailOutMarkTempObject); |
| instr->GetBailOutInfo()->polymorphicCacheIndex = propertySymOpnd->m_inlineCacheIndex; |
| } |
| else |
| { |
| Assert(bailOutKind == instr->GetBailOutKind()); |
| } |
| }; |
| |
| bool isTypeCheckProtected; |
| IR::BailOutKind bailOutKind; |
| if (GlobOpt::NeedsTypeCheckBailOut(instr, propertySymOpnd, opnd == instr->GetDst(), &isTypeCheckProtected, &bailOutKind)) |
| { |
| HandleBailout(bailOutKind); |
| } |
| else |
| { |
| if (instr->m_opcode == Js::OpCode::LdMethodFromFlags) |
| { |
| // If LdMethodFromFlags is hoisted to the top of the loop, we should share the same bailout Info. |
| // We don't need to do anything for LdMethodFromFlags that cannot be field hoisted. |
| HandleBailout(IR::BailOutFailedInlineTypeCheck); |
| } |
| else if (instr->HasBailOutInfo()) |
| { |
| // If we already have a bailout info, but don't actually need it, let's remove it. This can happen if |
| // a CheckFixedFld added by the inliner (with bailout info) determined that the object's type has |
| // been checked upstream and no bailout is necessary here. |
| if (instr->m_opcode == Js::OpCode::CheckFixedFld) |
| { |
| AssertMsg(!PHASE_OFF(Js::FixedMethodsPhase, instr->m_func) || |
| !PHASE_OFF(Js::UseFixedDataPropsPhase, instr->m_func), "CheckFixedFld with fixed method/data phase disabled?"); |
| Assert(isTypeCheckProtected); |
| AssertMsg(instr->GetBailOutKind() == IR::BailOutFailedFixedFieldTypeCheck || instr->GetBailOutKind() == IR::BailOutFailedEquivalentFixedFieldTypeCheck, |
| "Only BailOutFailed[Equivalent]FixedFieldTypeCheck can be safely removed. Why does CheckFixedFld carry a different bailout kind?."); |
| instr->ClearBailOutInfo(); |
| } |
| else if (propertySymOpnd->MayNeedTypeCheckProtection() && propertySymOpnd->IsTypeCheckProtected()) |
| { |
| // Both the type and (if necessary) the proto object have been checked. |
| // We're doing a direct slot access. No possibility of bailout here (not even implicit call). |
| Assert(instr->GetBailOutKind() == IR::BailOutMarkTempObject); |
| instr->ClearBailOutInfo(); |
| } |
| } |
| } |
| |
| return instr; |
| } |
| |
| void |
| GlobOpt::SetSingleTypeOnObjectTypeValue(Value* value, const JITTypeHolder type) |
| { |
| UpdateObjectTypeValue(value, type, true, nullptr, false); |
| } |
| |
| void |
| GlobOpt::SetTypeSetOnObjectTypeValue(Value* value, Js::EquivalentTypeSet* typeSet) |
| { |
| UpdateObjectTypeValue(value, nullptr, false, typeSet, true); |
| } |
| |
| void |
| GlobOpt::UpdateObjectTypeValue(Value* value, const JITTypeHolder type, bool setType, Js::EquivalentTypeSet* typeSet, bool setTypeSet) |
| { |
| Assert(value->GetValueInfo() != nullptr && value->GetValueInfo()->IsJsType()); |
| JsTypeValueInfo* valueInfo = value->GetValueInfo()->AsJsType(); |
| |
| if (valueInfo->GetIsShared()) |
| { |
| valueInfo = valueInfo->Copy(this->alloc); |
| value->SetValueInfo(valueInfo); |
| } |
| |
| if (setType) |
| { |
| valueInfo->SetJsType(type); |
| } |
| if (setTypeSet) |
| { |
| valueInfo->SetJsTypeSet(typeSet); |
| } |
| } |
| |
| void |
| GlobOpt::SetObjectTypeFromTypeSym(StackSym *typeSym, Value* value, BasicBlock* block) |
| { |
| Assert(typeSym != nullptr); |
| Assert(value != nullptr); |
| Assert(value->GetValueInfo() != nullptr && value->GetValueInfo()->IsJsType()); |
| |
| SymID typeSymId = typeSym->m_id; |
| |
| if (block == nullptr) |
| { |
| block = this->currentBlock; |
| } |
| |
| block->globOptData.SetValue(value, typeSym); |
| block->globOptData.liveFields->Set(typeSymId); |
| } |
| |
| void |
| GlobOpt::SetObjectTypeFromTypeSym(StackSym *typeSym, const JITTypeHolder type, Js::EquivalentTypeSet * typeSet, BasicBlock* block, bool updateExistingValue) |
| { |
| if (block == nullptr) |
| { |
| block = this->currentBlock; |
| } |
| |
| SetObjectTypeFromTypeSym(typeSym, type, typeSet, &block->globOptData, updateExistingValue); |
| } |
| |
| void |
| GlobOpt::SetObjectTypeFromTypeSym(StackSym *typeSym, const JITTypeHolder type, Js::EquivalentTypeSet * typeSet, GlobOptBlockData *blockData, bool updateExistingValue) |
| { |
| Assert(typeSym != nullptr); |
| |
| SymID typeSymId = typeSym->m_id; |
| |
| if (blockData == nullptr) |
| { |
| blockData = &this->currentBlock->globOptData; |
| } |
| |
| if (updateExistingValue) |
| { |
| Value* value = blockData->FindValueFromMapDirect(typeSymId); |
| |
| // If we're trying to update an existing value, the value better exist. We only do this when updating a generic |
| // value created during loop pre-pass for field hoisting, so we expect the value info to still be blank. |
| Assert(value != nullptr && value->GetValueInfo() != nullptr && value->GetValueInfo()->IsJsType()); |
| JsTypeValueInfo* valueInfo = value->GetValueInfo()->AsJsType(); |
| Assert(valueInfo->GetJsType() == nullptr && valueInfo->GetJsTypeSet() == nullptr); |
| UpdateObjectTypeValue(value, type, true, typeSet, true); |
| } |
| else |
| { |
| JsTypeValueInfo* valueInfo = JsTypeValueInfo::New(this->alloc, type, typeSet); |
| this->SetSymStoreDirect(valueInfo, typeSym); |
| Value* value = NewValue(valueInfo); |
| blockData->SetValue(value, typeSym); |
| } |
| |
| blockData->liveFields->Set(typeSymId); |
| } |
| |
| void |
| GlobOpt::KillObjectType(StackSym* objectSym, BVSparse<JitArenaAllocator>* liveFields) |
| { |
| if (objectSym->IsTypeSpec()) |
| { |
| objectSym = objectSym->GetVarEquivSym(this->func); |
| } |
| |
| Assert(objectSym); |
| |
| // We may be conservatively attempting to kill type syms from object syms that don't actually |
| // participate in object type specialization and hence don't actually have type syms (yet). |
| if (!objectSym->HasObjectTypeSym()) |
| { |
| return; |
| } |
| |
| if (liveFields == nullptr) |
| { |
| liveFields = this->currentBlock->globOptData.liveFields; |
| } |
| |
| liveFields->Clear(objectSym->GetObjectTypeSym()->m_id); |
| } |
| |
| void |
| GlobOpt::KillAllObjectTypes(BVSparse<JitArenaAllocator>* liveFields) |
| { |
| if (this->objectTypeSyms && liveFields) |
| { |
| liveFields->Minus(this->objectTypeSyms); |
| } |
| } |
| |
| void |
| GlobOpt::EndFieldLifetime(IR::SymOpnd *symOpnd) |
| { |
| this->currentBlock->globOptData.liveFields->Clear(symOpnd->m_sym->m_id); |
| } |
| |
| PropertySym * |
| GlobOpt::CopyPropPropertySymObj(IR::SymOpnd *symOpnd, IR::Instr *instr) |
| { |
| Assert(symOpnd->m_sym->IsPropertySym()); |
| |
| PropertySym *propertySym = symOpnd->m_sym->AsPropertySym(); |
| |
| StackSym *objSym = propertySym->m_stackSym; |
| |
| Value * val = this->currentBlock->globOptData.FindValue(objSym); |
| |
| if (val && !PHASE_OFF(Js::ObjPtrCopyPropPhase, this->func)) |
| { |
| StackSym *copySym = this->currentBlock->globOptData.GetCopyPropSym(objSym, val); |
| if (copySym != nullptr) |
| { |
| PropertySym *newProp = PropertySym::FindOrCreate( |
| copySym->m_id, propertySym->m_propertyId, propertySym->GetPropertyIdIndex(), propertySym->GetInlineCacheIndex(), propertySym->m_fieldKind, this->func); |
| |
| if (!this->IsLoopPrePass() || SafeToCopyPropInPrepass(objSym, copySym, val)) |
| { |
| #if DBG_DUMP |
| if (Js::Configuration::Global.flags.Trace.IsEnabled(Js::GlobOptPhase, this->func->GetSourceContextId(), this->func->GetLocalFunctionId())) |
| { |
| Output::Print(_u("TRACE: ")); |
| symOpnd->Dump(); |
| Output::Print(_u(" : ")); |
| Output::Print(_u("Copy prop obj ptr s%d, new property: "), copySym->m_id); |
| newProp->Dump(); |
| Output::Print(_u("\n")); |
| } |
| #endif |
| |
| // Copy prop |
| this->CaptureByteCodeSymUses(instr); |
| |
| // If the old sym was part of an object type spec type check sequence, |
| // let's make sure the new one is prepped for it as well. |
| if (IsPropertySymPreparedForTypeCheckSeq(propertySym)) |
| { |
| PreparePropertySymForTypeCheckSeq(newProp); |
| } |
| symOpnd->m_sym = newProp; |
| symOpnd->SetIsJITOptimizedReg(true); |
| |
| if (symOpnd->IsPropertySymOpnd()) |
| { |
| IR::PropertySymOpnd *propertySymOpnd = symOpnd->AsPropertySymOpnd(); |
| |
| if (propertySymOpnd->IsTypeCheckSeqCandidate()) |
| { |
| // If the new pointer sym's expected type(s) don't match those in the inline-cache-based data for this access, |
| // we probably have a mismatch and can't safely objtypespec. If the saved objtypespecfldinfo isn't right for |
| // the new type, then we'll do an incorrect property access. |
| StackSym * newTypeSym = copySym->GetObjectTypeSym(); |
| Value * newValue = currentBlock->globOptData.FindObjectTypeValueNoLivenessCheck(newTypeSym); |
| JsTypeValueInfo * newValueInfo = newValue ? newValue->GetValueInfo()->AsJsType() : nullptr; |
| bool shouldOptimize = CompareCurrentTypesWithExpectedTypes(newValueInfo, propertySymOpnd); |
| if (!shouldOptimize) |
| { |
| propertySymOpnd->SetTypeCheckSeqCandidate(false); |
| } |
| } |
| |
| // This is no longer strictly necessary, since we don't set the type dead bits in the initial |
| // backward pass, but let's keep it around for now in case we choose to revert to the old model. |
| propertySymOpnd->SetTypeDeadIfTypeCheckSeqCandidate(false); |
| } |
| |
| if (this->IsLoopPrePass()) |
| { |
| this->prePassCopyPropSym->Set(copySym->m_id); |
| } |
| } |
| propertySym = newProp; |
| |
| if(instr->GetDst() && symOpnd->IsEqual(instr->GetDst())) |
| { |
| // Make sure any stack sym uses in the new destination property sym are unspecialized |
| instr = ToVarUses(instr, symOpnd, true, nullptr); |
| } |
| } |
| } |
| |
| return propertySym; |
| } |
| |
| void |
| GlobOpt::UpdateObjPtrValueType(IR::Opnd * opnd, IR::Instr * instr) |
| { |
| if (!opnd->IsSymOpnd() || !opnd->AsSymOpnd()->IsPropertySymOpnd()) |
| { |
| return; |
| } |
| |
| if (!instr->HasTypeCheckBailOut()) |
| { |
| // No type check bailout, we didn't check that type of the object pointer. |
| return; |
| } |
| |
| // Only check that fixed field should have type check bailout in loop prepass. |
| Assert(instr->m_opcode == Js::OpCode::CheckFixedFld || !this->IsLoopPrePass()); |
| |
| if (instr->m_opcode != Js::OpCode::CheckFixedFld) |
| { |
| // DeadStore pass may remove type check bailout, except CheckFixedFld which always needs |
| // type check bailout. So we can only change the type for CheckFixedFld. |
| // Consider: See if we can expand that in the future. |
| return; |
| } |
| |
| IR::PropertySymOpnd * propertySymOpnd = opnd->AsPropertySymOpnd(); |
| StackSym * objectSym = propertySymOpnd->GetObjectSym(); |
| Value * objVal = this->currentBlock->globOptData.FindValue(objectSym); |
| if (!objVal) |
| { |
| return; |
| } |
| |
| ValueType objValueType = objVal->GetValueInfo()->Type(); |
| if (objValueType.IsDefinite()) |
| { |
| return; |
| } |
| |
| ValueInfo *objValueInfo = objVal->GetValueInfo(); |
| |
| // It is possible for a valueInfo to be not definite and still have a byteCodeConstant as symStore, this is because we conservatively copy valueInfo in prePass |
| if (objValueInfo->GetSymStore() && objValueInfo->GetSymStore()->IsStackSym() && objValueInfo->GetSymStore()->AsStackSym()->IsFromByteCodeConstantTable()) |
| { |
| return; |
| } |
| |
| // Verify that the types we're checking for here have been locked so that the type ID's can't be changed |
| // without changing the type. |
| if (!propertySymOpnd->HasObjectTypeSym()) |
| { |
| return; |
| } |
| |
| StackSym * typeSym = propertySymOpnd->GetObjectTypeSym(); |
| Assert(typeSym); |
| Value * typeValue = currentBlock->globOptData.FindObjectTypeValue(typeSym); |
| if (!typeValue) |
| { |
| return; |
| } |
| JsTypeValueInfo * typeValueInfo = typeValue->GetValueInfo()->AsJsType(); |
| JITTypeHolder type = typeValueInfo->GetJsType(); |
| if (type != nullptr) |
| { |
| if (Js::DynamicType::Is(type->GetTypeId()) && |
| !type->GetTypeHandler()->IsLocked()) |
| { |
| return; |
| } |
| } |
| else |
| { |
| Js::EquivalentTypeSet * typeSet = typeValueInfo->GetJsTypeSet(); |
| Assert(typeSet); |
| for (uint16 i = 0; i < typeSet->GetCount(); i++) |
| { |
| type = typeSet->GetType(i); |
| if (Js::DynamicType::Is(type->GetTypeId()) && |
| !type->GetTypeHandler()->IsLocked()) |
| { |
| return; |
| } |
| } |
| } |
| |
| AnalysisAssert(type != nullptr); |
| Js::TypeId typeId = type->GetTypeId(); |
| |
| // Passing false for useVirtual as we would never have a virtual typed array hitting this code path |
| ValueType newValueType = ValueType::FromTypeId(typeId, false); |
| |
| if (newValueType == ValueType::Uninitialized) |
| { |
| switch (typeId) |
| { |
| default: |
| // Can't mark as definite object because it may actually be object-with-array. |
| // Consider: a value type that subsumes object, array, and object-with-array. |
| break; |
| case Js::TypeIds_NativeIntArray: |
| case Js::TypeIds_NativeFloatArray: |
| // Do not mark these values as definite to protect against array conversion |
| break; |
| case Js::TypeIds_Array: |
| // Because array can change type id, we can only make it definite if we are doing array check hoist |
| // so that implicit call will be installed between the array checks. |
| if (!DoArrayCheckHoist() || |
| (currentBlock->loop |
| ? !this->ImplicitCallFlagsAllowOpts(currentBlock->loop) |
| : !this->ImplicitCallFlagsAllowOpts(this->func))) |
| { |
| break; |
| } |
| if (objValueType.IsLikelyArrayOrObjectWithArray()) |
| { |
| // If we have likely no missing values before, keep the likely, because, we haven't proven that |
| // the array really has no missing values |
| if (!objValueType.HasNoMissingValues()) |
| { |
| newValueType = ValueType::GetObject(ObjectType::Array).SetArrayTypeId(typeId); |
| } |
| } |
| else |
| { |
| newValueType = ValueType::GetObject(ObjectType::Array).SetArrayTypeId(typeId); |
| } |
| break; |
| } |
| } |
| if (newValueType != ValueType::Uninitialized) |
| { |
| ChangeValueType(currentBlock, objVal, newValueType, false, true); |
| } |
| } |