diff --git a/lib/Backend/Lower.cpp b/lib/Backend/Lower.cpp index d3f6df952da..8e4e9ef9168 100644 --- a/lib/Backend/Lower.cpp +++ b/lib/Backend/Lower.cpp @@ -15123,14 +15123,14 @@ Lowerer::GenerateFastElemICommon( IR::RegOpnd *indexOpnd = indirOpnd->GetIndexOpnd(); if (indexOpnd) { - if (indexOpnd->GetValueType().IsString()) + if (indexOpnd->GetValueType().IsLikelyString()) { if (!baseOpnd->GetValueType().IsLikelyOptimizedTypedArray()) { // If profile data says that it's a typed array - do not generate the property string fast path as the src. could be a temp and that would cause a bug. *pIsTypedArrayElement = false; *pIsStringIndex = true; - return m_lowererMD.GenerateFastElemIStringIndexCommon(instr, isStore, indirOpnd, labelHelper); + return GenerateFastElemIStringIndexCommon(instr, isStore, indirOpnd, labelHelper); } else { @@ -15157,6 +15157,110 @@ Lowerer::GenerateFastElemICommon( indirOpndOverflowed); } +void +Lowerer::GenerateDynamicLoadPolymorphicInlineCacheSlot(IR::Instr * instrInsert, IR::RegOpnd * inlineCacheOpnd, IR::Opnd * objectTypeOpnd) +{ + // Generates: + // MOV opndOffset, objectTypeOpnd + // SHR opndOffset, PolymorphicInlineCacheShift + // MOVZX cacheIndexOpnd, inlineCacheOpnd->size + // DEC cacheIndexOpnd + // AND opndOffset, cacheIndexOpnd + // SHL opndOffset, Math::Log2(sizeof(Js::InlineCache)) + // MOV inlineCacheOpnd, inlineCacheOpnd->inlineCaches + // LEA inlineCacheOpnd, [inlineCacheOpnd + opndOffset] + + IntConstType rightShiftAmount = PolymorphicInlineCacheShift; + IntConstType leftShiftAmount = Math::Log2(sizeof(Js::InlineCache)); + Assert(rightShiftAmount > leftShiftAmount); + IR::RegOpnd * opndOffset = IR::RegOpnd::New(TyMachPtr, m_func); + InsertShift(Js::OpCode::ShrU_A, false, opndOffset, objectTypeOpnd, IR::IntConstOpnd::New(rightShiftAmount, TyUint8, m_func, true), instrInsert); + + IR::RegOpnd * cacheIndexOpnd = IR::RegOpnd::New(TyMachPtr, m_func); + InsertMove(cacheIndexOpnd, IR::IndirOpnd::New(inlineCacheOpnd, Js::PolymorphicInlineCache::GetOffsetOfSize(), TyUint16, m_func), instrInsert); + InsertSub(false, cacheIndexOpnd, cacheIndexOpnd, IR::IntConstOpnd::New(1, TyMachPtr, m_func), instrInsert); + InsertAnd(opndOffset, opndOffset, cacheIndexOpnd, instrInsert); + InsertShift(Js::OpCode::Shl_A, false, opndOffset, opndOffset, IR::IntConstOpnd::New(leftShiftAmount, TyUint8, m_func), instrInsert); + InsertMove(inlineCacheOpnd, IR::IndirOpnd::New(inlineCacheOpnd, Js::PolymorphicInlineCache::GetOffsetOfInlineCaches(), TyMachPtr, m_func), instrInsert); + InsertLea(inlineCacheOpnd, IR::IndirOpnd::New(inlineCacheOpnd, opndOffset, TyMachPtr, m_func), instrInsert); +} + +IR::IndirOpnd * +Lowerer::GenerateFastElemIStringIndexCommon(IR::Instr * instrInsert, bool isStore, IR::IndirOpnd * indirOpnd, IR::LabelInstr * labelHelper) +{ + IR::RegOpnd *indexOpnd = indirOpnd->GetIndexOpnd(); + IR::RegOpnd *baseOpnd = indirOpnd->GetBaseOpnd(); + Assert(baseOpnd != nullptr); + Assert(indexOpnd->GetValueType().IsLikelyString()); + + // Generates: + // StringTest(indexOpnd, $helper) ; verify index is string type + // CMP indexOpnd, PropertyString::`vtable' ; verify index is property string + // JNE $helper + // MOV inlineCacheOpnd, index->inlineCache + // GenerateObjectTest(baseOpnd, $helper) ; verify base is an object + // MOV objectTypeOpnd, baseOpnd->type + // GenerateDynamicLoadPolymorphicInlineCacheSlot(inlineCacheOpnd, objectTypeOpnd) ; loads inline cache for given type + // LocalInlineCacheCheck(objectTypeOpnd, inlineCacheOpnd, $notInlineSlots) ; check for type in local inline slots, jump to $notInlineSlotsLabel on failure + // MOV opndSlotArray, baseOpnd + // JMP slotArrayLoadedLabel + // $notInlineSlotsLabel + // opndTaggedType = GenerateLoadTaggedType(objectTypeOpnd) ; load objectTypeOpnd with InlineCacheAuxSlotTypeTag into opndTaggedType + // LocalInlineCacheCheck(opndTaggedType, inlineCacheOpnd, $helper) ; check for type in local aux slots, jump to $helper on failure + // MOV opndSlotArray, baseOpnd->auxSlots ; load the aux slot array + // $slotArrayLoadedLabel + // MOV opndSlotIndex, inlineCacheOpnd->u.local.slotIndex ; load the cached slot offset or index + // INC indexOpnd->hitRate + + GenerateStringTest(indexOpnd, instrInsert, labelHelper); + + InsertCompareBranch( + IR::IndirOpnd::New(indexOpnd, 0, TyMachPtr, m_func), + LoadVTableValueOpnd(instrInsert, VTableValue::VtablePropertyString), + Js::OpCode::BrNeq_A, labelHelper, instrInsert); + + m_lowererMD.GenerateObjectTest(baseOpnd, instrInsert, labelHelper); + + IR::RegOpnd * objectTypeOpnd = IR::RegOpnd::New(TyMachPtr, this->m_func); + InsertMove(objectTypeOpnd, IR::IndirOpnd::New(baseOpnd, Js::RecyclableObject::GetOffsetOfType(), TyMachPtr, m_func), instrInsert); + + const uint32 inlineCacheOffset = isStore ? Js::PropertyString::GetOffsetOfStElemInlineCache() : Js::PropertyString::GetOffsetOfLdElemInlineCache(); + + IR::RegOpnd * inlineCacheOpnd = IR::RegOpnd::New(TyMachPtr, m_func); + InsertMove(inlineCacheOpnd, IR::IndirOpnd::New(indexOpnd, inlineCacheOffset, TyMachPtr, m_func), instrInsert); + + GenerateDynamicLoadPolymorphicInlineCacheSlot(instrInsert, inlineCacheOpnd, objectTypeOpnd); + + IR::LabelInstr * notInlineSlotsLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func); + IR::LabelInstr * slotArrayLoadedLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func); + + m_lowererMD.GenerateLocalInlineCacheCheck(instrInsert, objectTypeOpnd, inlineCacheOpnd, notInlineSlotsLabel); + + IR::RegOpnd * opndSlotArray = IR::RegOpnd::New(TyMachReg, instrInsert->m_func); + InsertMove(opndSlotArray, baseOpnd, instrInsert); + InsertBranch(Js::OpCode::Br, slotArrayLoadedLabel, instrInsert); + + instrInsert->InsertBefore(notInlineSlotsLabel); + IR::RegOpnd * opndTaggedType = IR::RegOpnd::New(TyMachReg, this->m_func); + m_lowererMD.GenerateLoadTaggedType(instrInsert, objectTypeOpnd, opndTaggedType); + m_lowererMD.GenerateLocalInlineCacheCheck(instrInsert, opndTaggedType, inlineCacheOpnd, labelHelper); + + IR::IndirOpnd * opndIndir = IR::IndirOpnd::New(baseOpnd, Js::DynamicObject::GetOffsetOfAuxSlots(), TyMachReg, instrInsert->m_func); + InsertMove(opndSlotArray, opndIndir, instrInsert); + + instrInsert->InsertBefore(slotArrayLoadedLabel); + + IR::RegOpnd * opndSlotIndex = IR::RegOpnd::New(TyMachReg, instrInsert->m_func); + InsertMove(opndSlotIndex, IR::IndirOpnd::New(inlineCacheOpnd, (int32)offsetof(Js::InlineCache, u.local.slotIndex), TyUint16, instrInsert->m_func), instrInsert); + + IR::IndirOpnd * hitRateOpnd = IR::IndirOpnd::New(indexOpnd, Js::PropertyString::GetOffsetOfHitRate(), TyInt32, m_func); + IR::IntConstOpnd * incOpnd = IR::IntConstOpnd::New(1, TyInt32, instrInsert->m_func); + InsertAdd(false, hitRateOpnd, hitRateOpnd, incOpnd, instrInsert); + + // return [opndSlotArray + opndSlotIndex * PtrSize] + return IR::IndirOpnd::New(opndSlotArray, opndSlotIndex, m_lowererMD.GetDefaultIndirScale(), TyMachReg, instrInsert->m_func); +} + IR::IndirOpnd * Lowerer::GenerateFastElemIIntIndexCommon( IR::Instr * instr, @@ -17924,63 +18028,73 @@ Lowerer::GenerateFastInlineHasOwnProperty(IR::Instr * instr) return; } - // fast path case where hasOwnProperty is being called using a property name loaded via a for-in loop - bool generateForInFastpath = argsOpnd[1]->GetValueType().IsString() - && argsOpnd[1]->AsRegOpnd()->m_sym->m_isSingleDef - && (argsOpnd[1]->AsRegOpnd()->m_sym->m_instrDef->m_opcode == Js::OpCode::BrOnEmpty - || argsOpnd[1]->AsRegOpnd()->m_sym->m_instrDef->m_opcode == Js::OpCode::BrOnNotEmpty); - IR::RegOpnd * thisObj = argsOpnd[0]->AsRegOpnd(); IR::RegOpnd * propOpnd = argsOpnd[1]->AsRegOpnd(); + // fast path case where hasOwnProperty is being called using a property name loaded via a for-in loop + bool generateForInFastpath = propOpnd->GetValueType().IsString() + && propOpnd->m_sym->m_isSingleDef + && (propOpnd->m_sym->m_instrDef->m_opcode == Js::OpCode::BrOnEmpty + || propOpnd->m_sym->m_instrDef->m_opcode == Js::OpCode::BrOnNotEmpty); + IR::LabelInstr * doneLabel = InsertLabel(false, instr->m_next); IR::LabelInstr * labelHelper = InsertLabel(true, instr); - IR::LabelInstr * cacheMissLabel = generateForInFastpath ? InsertLabel(true, labelHelper) : labelHelper; + IR::LabelInstr * cacheMissLabel = generateForInFastpath ? IR::LabelInstr::New(Js::OpCode::Label, m_func, true) : labelHelper; - IR::Instr * insertInstr = cacheMissLabel; + IR::Instr * insertInstr = labelHelper; - // TEST indexOpnd, AtomTag + // GenerateObjectTest(propOpnd, $labelHelper) // CMP indexOpnd, PropertyString::`vtable' // JNE $helper - // MOV propertyCacheOpnd, propOpnd->propCache - // TEST thisObj, AtomTag - // JNE $labelHelper + // GenerateObjectTest(thisObj, $labelHelper) + // MOV inlineCacheOpnd, propOpnd->lsElemInlineCache // MOV objectTypeOpnd, thisObj->type - // CMP propertyCacheOpnd->type, objectTypeOpnd - // JNE $cacheMissLabel + // GenerateDynamicLoadPolymorphicInlineCacheSlot(inlineCacheOpnd, objectTypeOpnd) ; loads inline cache for given type + // GenerateLocalInlineCacheCheck(objectTypeOpnd, inlineCacheOpnd, $notInlineSlotsLabel) ; check for type in inline slots, jump to $notInlineSlotsLabel on failure + // MOV dst, ValueTrue + // JMP $done + // $notInlineSlotsLabel: + // GenerateLoadTaggedType(objectTypeOpnd, opndTaggedType) + // GenerateLocalInlineCacheCheck(opndTaggedType, inlineCacheOpnd, $cacheMissLabel) ; check for type in aux slot, jump to $cacheMissLabel on failure // MOV dst, ValueTrue // JMP $done - if (!propOpnd->IsNotTaggedValue()) - { - m_lowererMD.GenerateObjectTest(propOpnd, insertInstr, labelHelper); - } + m_lowererMD.GenerateObjectTest(propOpnd, insertInstr, labelHelper); InsertCompareBranch(IR::IndirOpnd::New(propOpnd, 0, TyMachPtr, m_func), LoadVTableValueOpnd(insertInstr, VTableValue::VtablePropertyString), Js::OpCode::BrNeq_A, labelHelper, insertInstr); - IR::RegOpnd * propertyCacheOpnd = IR::RegOpnd::New(TyMachPtr, m_func); - InsertMove(propertyCacheOpnd, IR::IndirOpnd::New(propOpnd, Js::PropertyString::GetOffsetOfPropertyCache(), TyMachPtr, m_func), insertInstr); + m_lowererMD.GenerateObjectTest(thisObj, insertInstr, labelHelper); - if (!thisObj->IsNotTaggedValue()) - { - m_lowererMD.GenerateObjectTest(thisObj, insertInstr, labelHelper); - } + IR::RegOpnd * inlineCacheOpnd = IR::RegOpnd::New(TyMachPtr, m_func); + InsertMove(inlineCacheOpnd, IR::IndirOpnd::New(propOpnd, Js::PropertyString::GetOffsetOfLdElemInlineCache(), TyMachPtr, m_func), insertInstr); IR::RegOpnd * objectTypeOpnd = IR::RegOpnd::New(TyMachPtr, m_func); InsertMove(objectTypeOpnd, IR::IndirOpnd::New(thisObj, Js::RecyclableObject::GetOffsetOfType(), TyMachPtr, m_func), insertInstr); - InsertCompareBranch(IR::IndirOpnd::New(propertyCacheOpnd, (int32)offsetof(Js::PropertyCache, type), TyMachPtr, m_func), objectTypeOpnd, Js::OpCode::BrNeq_A, cacheMissLabel, insertInstr); + GenerateDynamicLoadPolymorphicInlineCacheSlot(insertInstr, inlineCacheOpnd, objectTypeOpnd); + + IR::LabelInstr * notInlineSlotsLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func); + m_lowererMD.GenerateLocalInlineCacheCheck(insertInstr, objectTypeOpnd, inlineCacheOpnd, notInlineSlotsLabel); InsertMove(instr->GetDst(), LoadLibraryValueOpnd(instr, LibraryValue::ValueTrue), insertInstr); InsertBranch(Js::OpCode::Br, doneLabel, insertInstr); + insertInstr->InsertBefore(notInlineSlotsLabel); + IR::RegOpnd * opndTaggedType = IR::RegOpnd::New(TyMachReg, m_func); + m_lowererMD.GenerateLoadTaggedType(insertInstr, objectTypeOpnd, opndTaggedType); + m_lowererMD.GenerateLocalInlineCacheCheck(insertInstr, opndTaggedType, inlineCacheOpnd, cacheMissLabel); + InsertMove(instr->GetDst(), LoadLibraryValueOpnd(instr, LibraryValue::ValueTrue), insertInstr); + InsertBranch(Js::OpCode::Br, doneLabel, insertInstr); + if (!generateForInFastpath) { RelocateCallDirectToHelperPath(tmpInstr, labelHelper); return; } + insertInstr->InsertBefore(cacheMissLabel); + // CMP forInEnumeratorOpnd->canUseJitFastPath, 0 // JEQ $labelHelper // MOV cachedDataTypeOpnd, forInEnumeratorOpnd->enumeratorInitialType diff --git a/lib/Backend/Lower.h b/lib/Backend/Lower.h index ff4035a828f..abb9897827a 100644 --- a/lib/Backend/Lower.h +++ b/lib/Backend/Lower.h @@ -289,6 +289,7 @@ class Lowerer bool GenerateFastBrBool(IR::BranchInstr *const instr); bool GenerateFastStringCheck(IR::Instr *instr, IR::RegOpnd *srcReg1, IR::RegOpnd *srcReg2, bool isEqual, bool isStrict, IR::LabelInstr *labelHelper, IR::LabelInstr *labelBranchSuccess, IR::LabelInstr *labelBranchFail); bool GenerateFastBrOrCmString(IR::Instr* instr); + void GenerateDynamicLoadPolymorphicInlineCacheSlot(IR::Instr * instrInsert, IR::RegOpnd * inlineCacheOpnd, IR::Opnd * objectTypeOpnd); static IR::Instr *LoadFloatFromNonReg(IR::Opnd * opndOrig, IR::Opnd * regOpnd, IR::Instr * instrInsert); void LoadInt32FromUntaggedVar(IR::Instr *const instrLoad); bool GetValueFromIndirOpnd(IR::IndirOpnd *indirOpnd, IR::Opnd **pValueOpnd, IntConstType *pValue); @@ -424,6 +425,7 @@ class Lowerer IR::LabelInstr *bailOutLabelInstr = nullptr, bool * indirOpndOverflowed = nullptr); + IR::IndirOpnd * GenerateFastElemIStringIndexCommon(IR::Instr * ldElem, bool isStore, IR::IndirOpnd * indirOpnd, IR::LabelInstr * labelHelper); bool GenerateFastLdElemI(IR::Instr *& ldElem, bool *instrIsInHelperBlockRef); bool GenerateFastStElemI(IR::Instr *& StElem, bool *instrIsInHelperBlockRef); bool GenerateFastLdLen(IR::Instr *ldLen, bool *instrIsInHelperBlockRef); diff --git a/lib/Backend/LowerMDShared.cpp b/lib/Backend/LowerMDShared.cpp index 4b8adfb3da1..a307dd01f69 100644 --- a/lib/Backend/LowerMDShared.cpp +++ b/lib/Backend/LowerMDShared.cpp @@ -4914,113 +4914,6 @@ IR::RegOpnd *LowererMD::LoadNonnegativeIndex( return indexOpnd; } -IR::IndirOpnd * -LowererMD::GenerateFastElemIStringIndexCommon(IR::Instr * instrInsert, bool isStore, IR::IndirOpnd * indirOpnd, IR::LabelInstr * labelHelper) -{ - IR::RegOpnd *indexOpnd = indirOpnd->GetIndexOpnd(); - IR::RegOpnd *baseOpnd = indirOpnd->GetBaseOpnd(); - Assert(baseOpnd != nullptr); - Assert(indexOpnd->GetValueType().IsLikelyString()); - - // Generates: - // CMP indexOpnd, PropertyString::`vtable' -- check if index is property string - // JNE $helper - // MOV propertyCacheOpnd, index->propCache - // TEST baseOpnd, AtomTag -- check base not tagged int - // JNE $helper - // MOV objectTypeOpnd, baseOpnd->type - // CMP [propertyCacheOpnd->type], objectTypeOpnd -- check if object type match the cache - // JNE $helper - // CMP [propertyCacheOpnd->isInlineSlot,1] -- check if it is inline slots - // JEQ $inlineSlot - // MOV slotOpnd, [baseOpnd->slot] -- load the aux slot - // JMP $afterLabel - // $inlineSlot: - // MOV slotOpnd, baseOpnd -- use the object as start of the slot offset - // $afterLabel: - // MOVZXW offsetOpnd, [propertyCacheOpnd->dataSlotIndex] -- load the slot index - // - - // CMP indexOpnd, PropertyString::`vtable' -- check if index is property string - // JNE $helper - - this->m_lowerer->InsertCompareBranch( - IR::IndirOpnd::New(indexOpnd, 0, TyMachPtr, this->m_func), - m_lowerer->LoadVTableValueOpnd(instrInsert, VTableValue::VtablePropertyString), - Js::OpCode::BrNeq_A, labelHelper, instrInsert); - - // MOV propertyCacheOpnd, indexOpnd->propCache - IR::RegOpnd * propertyCacheOpnd = IR::RegOpnd::New(TyMachPtr, this->m_func); - IR::Instr * loadPropertyCacheInstr = IR::Instr::New(Js::OpCode::MOV, propertyCacheOpnd, - IR::IndirOpnd::New(indexOpnd, Js::PropertyString::GetOffsetOfPropertyCache(), TyMachPtr, - this->m_func), this->m_func); - instrInsert->InsertBefore(loadPropertyCacheInstr); - - // TEST baseOpnd, AtomTag -- check base not tagged int - // JNE $helper - - if(!baseOpnd->IsNotTaggedValue()) - { - GenerateObjectTest(baseOpnd, instrInsert, labelHelper); - } - - // MOV s2, baseOpnd->type - // CMP [propertyCacheOpnd->type], s2 -- check if object type match the cache - // JNE $helper - IR::RegOpnd * objectTypeOpnd = IR::RegOpnd::New(TyMachPtr, this->m_func); - IR::Instr * loadObjectTypeInstr = IR::Instr::New(Js::OpCode::MOV, - objectTypeOpnd, IR::IndirOpnd::New(baseOpnd, Js::RecyclableObject::GetOffsetOfType(), TyMachPtr, this->m_func), - this->m_func); - instrInsert->InsertBefore(loadObjectTypeInstr); - - IR::Instr * checkTypeInstr = IR::Instr::New(Js::OpCode::CMP, this->m_func); - checkTypeInstr->SetSrc1(IR::IndirOpnd::New(propertyCacheOpnd, (int32)offsetof(Js::PropertyCache, type), TyMachPtr, this->m_func)); - checkTypeInstr->SetSrc2(objectTypeOpnd); - instrInsert->InsertBefore(checkTypeInstr); - instrInsert->InsertBefore(IR::BranchInstr::New(Js::OpCode::JNE, labelHelper, this->m_func)); - - if (isStore) - { - IR::IndirOpnd* isStoreEnabledOpnd = IR::IndirOpnd::New(propertyCacheOpnd, (int32)offsetof(Js::PropertyCache, isStoreFieldEnabled), TyInt8, this->m_func); - IR::IntConstOpnd* zeroOpnd = IR::IntConstOpnd::New(0, TyInt8, this->m_func, /* dontEncode = */ true); - this->m_lowerer->InsertCompareBranch(isStoreEnabledOpnd, zeroOpnd, Js::OpCode::BrEq_A, labelHelper, instrInsert); - } - - // CMP [propertyCacheOpnd->isInlineSlot,1] -- check if it is inline slots - // JEQ $inlineSlot - IR::Instr * inlineSlotTestInstr = IR::Instr::New(Js::OpCode::CMP, this->m_func); - inlineSlotTestInstr->SetSrc1(IR::IndirOpnd::New(propertyCacheOpnd, (int32)offsetof(Js::PropertyCache, isInlineSlot), TyInt8, this->m_func)); - inlineSlotTestInstr->SetSrc2(IR::IntConstOpnd::New(1, TyInt8, this->m_func)); - instrInsert->InsertBefore(inlineSlotTestInstr); - - IR::LabelInstr * isInlineSlotLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func); - instrInsert->InsertBefore(IR::BranchInstr::New(Js::OpCode::JEQ, isInlineSlotLabel, this->m_func)); - - // MOV slotOpnd, [baseOpnd->slot] -- load the aux slot - // JMP $afterLabel - IR::RegOpnd * slotOpnd = IR::RegOpnd::New(TyMachPtr, this->m_func); - instrInsert->InsertBefore(IR::Instr::New(Js::OpCode::MOV, slotOpnd, - IR::IndirOpnd::New(baseOpnd, Js::DynamicObject::GetOffsetOfAuxSlots(), TyMachPtr, this->m_func), this->m_func)); - - IR::LabelInstr * afterLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func); - instrInsert->InsertBefore(IR::BranchInstr::New(Js::OpCode::JMP, afterLabel, this->m_func)); - - // $inlineSlot: - // MOV slotOpnd, baseOpnd -- use the object as start of the slot offset - instrInsert->InsertBefore(isInlineSlotLabel); - instrInsert->InsertBefore(IR::Instr::New(Js::OpCode::MOV, slotOpnd, baseOpnd, this->m_func)); - - // $afterLabel: - // MOVZXW offsetOpnd, [propertyCacheOpnd->dataSlotIndex] -- load the slot index - instrInsert->InsertBefore(afterLabel); - IR::RegOpnd * offsetOpnd = IR::RegOpnd::New(TyInt32, this->m_func); - instrInsert->InsertBefore(IR::Instr::New(Js::OpCode::MOVZXW, offsetOpnd, - IR::IndirOpnd::New(propertyCacheOpnd, (int32)offsetof(Js::PropertyCache, dataSlotIndex), TyUint16, this->m_func), this->m_func)); - - // return [slotOpnd + offsetOpnd * PtrSize] - return IR::IndirOpnd::New(slotOpnd, offsetOpnd, this->GetDefaultIndirScale(), TyVar, this->m_func); -} - // Inlines fast-path for int Mul/Add or int Mul/Sub. If not int, call MulAdd/MulSub helper bool LowererMD::TryGenerateFastMulAdd(IR::Instr * instrAdd, IR::Instr ** pInstrPrev) { diff --git a/lib/Backend/LowerMDShared.h b/lib/Backend/LowerMDShared.h index 70fbe3845d9..244b43ea1c7 100644 --- a/lib/Backend/LowerMDShared.h +++ b/lib/Backend/LowerMDShared.h @@ -161,7 +161,6 @@ class LowererMD void GenerateCheckForArgumentsLength(IR::Instr* ldElem, IR::LabelInstr* labelCreateHeapArgs, IR::Opnd* actualParamOpnd, IR::Opnd* valueOpnd, Js::OpCode); IR::RegOpnd * LoadNonnegativeIndex(IR::RegOpnd *indexOpnd, const bool skipNegativeCheck, IR::LabelInstr *const notTaggedIntLabel, IR::LabelInstr *const negativeLabel, IR::Instr *const insertBeforeInstr); IR::RegOpnd * GenerateUntagVar(IR::RegOpnd * opnd, IR::LabelInstr * labelFail, IR::Instr * insertBeforeInstr, bool generateTagCheck = true); - IR::IndirOpnd * GenerateFastElemIStringIndexCommon(IR::Instr * ldElem, bool isStore, IR::IndirOpnd * indirOpnd, IR::LabelInstr * labelHelper); bool GenerateFastLdMethodFromFlags(IR::Instr * instrLdFld); IR::Instr * GenerateFastScopedLdFld(IR::Instr * instrLdFld); IR::Instr * GenerateFastScopedStFld(IR::Instr * instrStFld); diff --git a/lib/Backend/arm/LowerMD.cpp b/lib/Backend/arm/LowerMD.cpp index 71bd51a4dcd..70b8f46d434 100644 --- a/lib/Backend/arm/LowerMD.cpp +++ b/lib/Backend/arm/LowerMD.cpp @@ -5086,121 +5086,6 @@ LowererMD::GenerateStFldFromLocalInlineCache( instrStFld->InsertBefore(instr); } -IR::IndirOpnd * -LowererMD::GenerateFastElemIStringIndexCommon( - IR::Instr * instrInsert, - bool isStore, - IR::IndirOpnd *indirOpnd, - IR::LabelInstr * labelHelper -) -{ - // Generates: - // CMP indexOpnd, PropertyString::`vtable' -- check if index is property string - // BNE $helper - // LDR propertyCacheOpnd, index->propCache - // TST baseOpnd, AtomTag -- check base not tagged int - // BNE $helper - // LDR objectTypeOpnd, baseOpnd->type - // CMP [propertyCacheOpnd->type], objectTypeOpnd -- check if object type match the cache - // BNE $helper - // CMP [propertyCacheOpnd->isInlineSlot,1] -- check if it is inline slots - // BEQ $inlineSlot - // LDR slotOpnd, [baseOpnd->slot] -- load the aux slot - // B $afterLabel - // $inlineSlot: - // LDR slotOpnd, baseOpnd -- use the object as start of the slot offset - // $afterLabel: - // LDR offsetOpnd, [propertyCacheOpnd->dataSlotIndex] -- load the slot index - // - - IR::RegOpnd *indexOpnd = indirOpnd->GetIndexOpnd(); - IR::RegOpnd *baseOpnd = indirOpnd->GetBaseOpnd(); - Assert(baseOpnd != nullptr); - Assert(indexOpnd->GetValueType().IsString()); - - // CMP [indexOpnd], PropertyString::`vtable' -- check if index is property string - // BNE $helper - IR::Instr * checkVtableInstr = IR::Instr::New(Js::OpCode::CMP, this->m_func); - checkVtableInstr->SetSrc1(IR::IndirOpnd::New(indexOpnd, (int32)0, TyMachPtr, this->m_func)); - checkVtableInstr->SetSrc2(m_lowerer->LoadVTableValueOpnd(instrInsert, VTableValue::VtablePropertyString)); - instrInsert->InsertBefore(checkVtableInstr); - LegalizeMD::LegalizeInstr(checkVtableInstr, false); - - instrInsert->InsertBefore(IR::BranchInstr::New(Js::OpCode::BNE, labelHelper, this->m_func)); - - // LDR propertyCacheOpnd, index->propCache - IR::RegOpnd * propertyCacheOpnd = IR::RegOpnd::New(TyMachPtr, this->m_func); - IR::Instr * loadPropertyCacheInstr = IR::Instr::New(Js::OpCode::LDR, propertyCacheOpnd, - IR::IndirOpnd::New(indexOpnd, Js::PropertyString::GetOffsetOfPropertyCache(), TyMachReg, this->m_func), this->m_func); - instrInsert->InsertBefore(loadPropertyCacheInstr); - - // TST baseOpnd, AtomTag -- check base not tagged int - // BNE $helper - if (!(baseOpnd->m_sym->m_isNotInt || baseOpnd->GetValueType().IsNotInt())) - { - GenerateObjectTest(baseOpnd, instrInsert, labelHelper); - } - - // LDR s2, baseOpnd->type - // CMP [propertyCacheOpnd->type], s2 -- check if object type match the cache - // BNE $helper - - IR::RegOpnd * objectTypeOpnd = IR::RegOpnd::New(TyMachPtr, this->m_func); - IR::Instr * loadObjectTypeInstr = IR::Instr::New(Js::OpCode::LDR, - objectTypeOpnd, IR::IndirOpnd::New(baseOpnd, Js::RecyclableObject::GetOffsetOfType(), TyMachPtr, this->m_func), - this->m_func); - instrInsert->InsertBefore(loadObjectTypeInstr); - - IR::Instr * checkTypeInstr = IR::Instr::New(Js::OpCode::CMP, this->m_func); - checkTypeInstr->SetSrc1(IR::IndirOpnd::New(propertyCacheOpnd, (int32)offsetof(Js::PropertyCache, type), TyMachPtr, this->m_func)); - checkTypeInstr->SetSrc2(objectTypeOpnd); - instrInsert->InsertBefore(checkTypeInstr); - LegalizeMD::LegalizeInstr(checkTypeInstr, false); - instrInsert->InsertBefore(IR::BranchInstr::New(Js::OpCode::BNE, labelHelper, this->m_func)); - - if (isStore) - { - IR::IndirOpnd* isStoreEnabledOpnd = IR::IndirOpnd::New(propertyCacheOpnd, (int32)offsetof(Js::PropertyCache, isStoreFieldEnabled), TyInt8, this->m_func); - IR::IntConstOpnd* zeroOpnd = IR::IntConstOpnd::New(0, TyInt8, this->m_func, /* dontEncode = */ true); - this->m_lowerer->InsertCompareBranch(isStoreEnabledOpnd, zeroOpnd, Js::OpCode::BrEq_A, labelHelper, instrInsert); - } - - // CMP [propertyCacheOpnd->isInlineSlot,1] -- check if it is inline slots - // BEQ $inlineSlot - IR::Instr * inlineSlotTestInstr = IR::Instr::New(Js::OpCode::CMP, this->m_func); - inlineSlotTestInstr->SetSrc1(IR::IndirOpnd::New(propertyCacheOpnd, (int32)offsetof(Js::PropertyCache, isInlineSlot), TyInt8, this->m_func)); - inlineSlotTestInstr->SetSrc2(IR::IntConstOpnd::New(1, TyInt8, this->m_func)); - instrInsert->InsertBefore(inlineSlotTestInstr); - LegalizeMD::LegalizeInstr(inlineSlotTestInstr, false); - - IR::LabelInstr * isInlineSlotLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func); - instrInsert->InsertBefore(IR::BranchInstr::New(Js::OpCode::BEQ, isInlineSlotLabel, this->m_func)); - - // LDR slotOpnd, [baseOpnd->slot] -- load the aux slot - // B $afterLabel - IR::RegOpnd * slotOpnd = IR::RegOpnd::New(TyMachPtr, this->m_func); - instrInsert->InsertBefore(IR::Instr::New(Js::OpCode::LDR, slotOpnd, - IR::IndirOpnd::New(baseOpnd, Js::DynamicObject::GetOffsetOfAuxSlots(), TyMachPtr, this->m_func), this->m_func)); - - IR::LabelInstr * afterLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func); - instrInsert->InsertBefore(IR::BranchInstr::New(Js::OpCode::B, afterLabel, this->m_func)); - - // $inlineSlot: - // LDR slotOpnd, baseOpnd -- use the object as start of the slot offset - instrInsert->InsertBefore(isInlineSlotLabel); - instrInsert->InsertBefore(IR::Instr::New(Js::OpCode::LDR, slotOpnd, baseOpnd, this->m_func)); - - // $afterLabel: - // LDR offsetOpnd, [propertyCacheOpnd->dataSlotIndex] -- load the slot index - instrInsert->InsertBefore(afterLabel); - IR::RegOpnd * offsetOpnd = IR::RegOpnd::New(TyInt32, this->m_func); - instrInsert->InsertBefore(IR::Instr::New(Js::OpCode::LDR, offsetOpnd, - IR::IndirOpnd::New(propertyCacheOpnd, (int32)offsetof(Js::PropertyCache, dataSlotIndex), TyUint16, this->m_func), this->m_func)); - - // return [slotOpnd + offsetOpnd * PtrSize] - return IR::IndirOpnd::New(slotOpnd, offsetOpnd, this->GetDefaultIndirScale(), TyVar, this->m_func); -} - IR::Opnd * LowererMD::CreateStackArgumentsSlotOpnd() { diff --git a/lib/Backend/arm/LowerMD.h b/lib/Backend/arm/LowerMD.h index d00972e31bd..b26315bf0ca 100644 --- a/lib/Backend/arm/LowerMD.h +++ b/lib/Backend/arm/LowerMD.h @@ -118,7 +118,6 @@ class LowererMD bool GenerateFastShiftLeft(IR::Instr * instrShift); bool GenerateFastShiftRight(IR::Instr * instrShift); void GenerateFastBrS(IR::BranchInstr *brInstr); - IR::IndirOpnd * GenerateFastElemIStringIndexCommon(IR::Instr * instr, bool isStore, IR::IndirOpnd *indirOpnd, IR::LabelInstr * labelHelper); void GenerateFastInlineBuiltInCall(IR::Instr* instr, IR::JnHelperMethod helperMethod); void HelperCallForAsmMathBuiltin(IR::Instr* instr, IR::JnHelperMethod helperMethodFloat, IR::JnHelperMethod helperMethodDouble) { Assert(UNREACHED); } // only for asm.js IR::Opnd * CreateStackArgumentsSlotOpnd(); diff --git a/lib/Backend/arm64/LowerMD.h b/lib/Backend/arm64/LowerMD.h index cf11a573099..d8a43750b7c 100644 --- a/lib/Backend/arm64/LowerMD.h +++ b/lib/Backend/arm64/LowerMD.h @@ -110,7 +110,6 @@ class LowererMD bool GenerateFastShiftLeft(IR::Instr * instrShift) { __debugbreak(); return 0; } bool GenerateFastShiftRight(IR::Instr * instrShift) { __debugbreak(); return 0; } void GenerateFastBrS(IR::BranchInstr *brInstr) { __debugbreak(); } - IR::IndirOpnd * GenerateFastElemIStringIndexCommon(IR::Instr * instr, bool isStore, IR::IndirOpnd *indirOpnd, IR::LabelInstr * labelHelper) { __debugbreak(); return 0; } void GenerateFastInlineBuiltInCall(IR::Instr* instr, IR::JnHelperMethod helperMethod) { __debugbreak(); } void HelperCallForAsmMathBuiltin(IR::Instr* instr, IR::JnHelperMethod helperMethodFloat, IR::JnHelperMethod helperMethodDouble) { __debugbreak(); } IR::Opnd * CreateStackArgumentsSlotOpnd() { __debugbreak(); return 0; } diff --git a/lib/Common/ConfigFlagsList.h b/lib/Common/ConfigFlagsList.h index a3996946dbd..e676a8efb09 100644 --- a/lib/Common/ConfigFlagsList.h +++ b/lib/Common/ConfigFlagsList.h @@ -295,6 +295,7 @@ PHASE(All) PHASE(InlineCache) PHASE(PolymorphicInlineCache) PHASE(MissingPropertyCache) + PHASE(PropertyStringCache) PHASE(CloneCacheInCollision) PHASE(ConstructorCache) PHASE(InlineCandidate) @@ -444,6 +445,9 @@ PHASE(All) #define DEFAULT_CONFIG_RecursiveInlineDepthMax (8) // Maximum inline depth for recursive calls #define DEFAULT_CONFIG_RecursiveInlineDepthMin (2) // Minimum inline depth for recursive call #define DEFAULT_CONFIG_InlineInLoopBodyScaleDownFactor (4) +#define DEFAULT_CONFIG_StringCacheMissPenalty (10) +#define DEFAULT_CONFIG_StringCacheMissThreshold (-100) +#define DEFAULT_CONFIG_StringCacheMissReset (-5000) #define DEFAULT_CONFIG_CloneInlinedPolymorphicCaches (true) #define DEFAULT_CONFIG_HighPrecisionDate (false) @@ -903,6 +907,9 @@ FLAGNR(Boolean, ConsoleExitPause , "Pause on exit when a console window is #endif FLAGNR(Number, ConstructorInlineThreshold , "Maximum size in bytecodes of a constructor inline candidate with monomorphic field access", DEFAULT_CONFIG_ConstructorInlineThreshold) FLAGNR(Number, ConstructorCallsRequiredToFinalizeCachedType, "Number of calls to a constructor required before the type cached in the constructor cache is finalized", DEFAULT_CONFIG_ConstructorCallsRequiredToFinalizeCachedType) +FLAGNR(Number, StringCacheMissPenalty, "Number of string cache hits per miss needed to be worth using cache", DEFAULT_CONFIG_StringCacheMissPenalty) +FLAGNR(Number, StringCacheMissThreshold, "Point at which we disable string property cache", DEFAULT_CONFIG_StringCacheMissThreshold) +FLAGNR(Number, StringCacheMissReset, "Point at which we try to start using string cache after giving up", DEFAULT_CONFIG_StringCacheMissReset) #ifdef SECURITY_TESTING FLAGNR(Boolean, CrashOnException , "Removes the top-level exception handler, allowing jc.exe to crash on an unhandled exception. No effect on IE. (default: false)", false) #endif diff --git a/lib/Common/Memory/ArenaAllocator.h b/lib/Common/Memory/ArenaAllocator.h index 18830c33dfa..f2dededa3d1 100644 --- a/lib/Common/Memory/ArenaAllocator.h +++ b/lib/Common/Memory/ArenaAllocator.h @@ -570,6 +570,7 @@ class NoRecoverMemoryArenaAllocator : public ArenaAllocator }; #define InlineCacheAuxSlotTypeTag 4 +#define MinPropertyStringInlineCacheSize 1 #define MinPolymorphicInlineCacheSize 4 #define MaxPolymorphicInlineCacheSize 32 diff --git a/lib/Runtime/Base/FunctionBody.cpp b/lib/Runtime/Base/FunctionBody.cpp index 5b1cc7bfbf3..033c51cc20b 100644 --- a/lib/Runtime/Base/FunctionBody.cpp +++ b/lib/Runtime/Base/FunctionBody.cpp @@ -6087,12 +6087,12 @@ namespace Js #endif ) { - PolymorphicInlineCache * polymorphicInlineCache = CreatePolymorphicInlineCache(index, PolymorphicInlineCache::GetInitialSize()); + PolymorphicInlineCache * polymorphicInlineCache = CreatePolymorphicInlineCache(index, MinPolymorphicInlineCacheSize); #ifdef ENABLE_DEBUG_CONFIG_OPTIONS if (PHASE_VERBOSE_TRACE1(Js::PolymorphicInlineCachePhase)) { this->DumpFullFunctionName(); - Output::Print(_u(": New PIC, index = %d, size = %d\n"), index, PolymorphicInlineCache::GetInitialSize()); + Output::Print(_u(": New PIC, index = %d, size = %d\n"), index, MinPolymorphicInlineCacheSize); } #endif @@ -6101,7 +6101,7 @@ namespace Js #endif PHASE_PRINT_INTRUSIVE_TESTTRACE1( Js::PolymorphicInlineCachePhase, - _u("TestTrace PIC: New, Function %s (%s), 0x%x, index = %d, size = %d\n"), this->GetDisplayName(), this->GetDebugNumberSet(debugStringBuffer), polymorphicInlineCache, index, PolymorphicInlineCache::GetInitialSize()); + _u("TestTrace PIC: New, Function %s (%s), 0x%x, index = %d, size = %d\n"), this->GetDisplayName(), this->GetDebugNumberSet(debugStringBuffer), polymorphicInlineCache, index, MinPolymorphicInlineCacheSize); uint indexInPolymorphicCache = polymorphicInlineCache->GetInlineCacheIndexForType(inlineCache->GetType()); inlineCache->CopyTo(propertyId, m_scriptContext, &(polymorphicInlineCache->GetInlineCaches()[indexInPolymorphicCache])); @@ -6150,7 +6150,7 @@ namespace Js PolymorphicInlineCache * FunctionBody::CreatePolymorphicInlineCache(uint index, uint16 size) { Recycler * recycler = this->m_scriptContext->GetRecycler(); - PolymorphicInlineCache * newPolymorphicInlineCache = PolymorphicInlineCache::New(size, this); + PolymorphicInlineCache * newPolymorphicInlineCache = FunctionBodyPolymorphicInlineCache::New(size, this); this->polymorphicInlineCaches.SetInlineCache(recycler, this, index, newPolymorphicInlineCache); return newPolymorphicInlineCache; } diff --git a/lib/Runtime/Base/FunctionBody.h b/lib/Runtime/Base/FunctionBody.h index af14d150421..14e0d79350e 100644 --- a/lib/Runtime/Base/FunctionBody.h +++ b/lib/Runtime/Base/FunctionBody.h @@ -33,7 +33,7 @@ namespace Js struct PropertyIdOnRegSlotsContainer; struct InlineCache; - struct PolymorphicInlineCache; + class PolymorphicInlineCache; struct IsInstInlineCache; class ScopeObjectChain; class EntryPointInfo; @@ -3150,8 +3150,8 @@ namespace Js PolymorphicCallSiteInfo * GetPolymorphicCallSiteInfoHead() { return static_cast(this->GetAuxPtr(AuxPointerType::PolymorphicCallSiteInfoHead)); } #endif - PolymorphicInlineCache * GetPolymorphicInlineCachesHead() { return static_cast(this->GetAuxPtr(AuxPointerType::PolymorphicInlineCachesHead)); } - void SetPolymorphicInlineCachesHead(PolymorphicInlineCache * cache) { this->SetAuxPtr(AuxPointerType::PolymorphicInlineCachesHead, cache); } + FunctionBodyPolymorphicInlineCache * GetPolymorphicInlineCachesHead() { return static_cast(this->GetAuxPtr(AuxPointerType::PolymorphicInlineCachesHead)); } + void SetPolymorphicInlineCachesHead(FunctionBodyPolymorphicInlineCache * cache) { this->SetAuxPtr(AuxPointerType::PolymorphicInlineCachesHead, cache); } bool PolyInliningUsingFixedMethodsAllowedByConfigFlags(FunctionBody* topFunctionBody) { diff --git a/lib/Runtime/Base/ScriptContext.cpp b/lib/Runtime/Base/ScriptContext.cpp index ecf85856540..0014d4f851e 100644 --- a/lib/Runtime/Base/ScriptContext.cpp +++ b/lib/Runtime/Base/ScriptContext.cpp @@ -256,8 +256,6 @@ namespace Js memset(byteCodeHistogram, 0, sizeof(byteCodeHistogram)); #endif - memset(propertyStrings, 0, sizeof(PropertyStringMap*)* 80); - #if DBG || defined(RUNTIME_DATA_COLLECTION) this->allocId = threadContext->GetScriptContextCount(); #endif @@ -797,12 +795,12 @@ namespace Js return NULL; } const uint i = PropertyStringMap::PStrMapIndex(ch1); - if (propertyStrings[i] == NULL) + if (this->Cache()->propertyStrings[i] == NULL) { return NULL; } const uint j = PropertyStringMap::PStrMapIndex(ch2); - return propertyStrings[i]->strLen2[j]; + return this->Cache()->propertyStrings[i]->strLen2[j]; } void ScriptContext::FindPropertyRecord(JavascriptString *pstName, PropertyRecord const ** propertyRecord) @@ -1205,6 +1203,7 @@ namespace Js #endif #endif + ClearArray(this->Cache()->propertyStrings, 80); this->Cache()->dynamicRegexMap = RegexPatternMruMap::New( recycler, @@ -1546,8 +1545,8 @@ namespace Js void ScriptContext::InitPropertyStringMap(int i) { - propertyStrings[i] = AnewStruct(GeneralAllocator(), PropertyStringMap); - memset(propertyStrings[i]->strLen2, 0, sizeof(PropertyString*)* 80); + this->Cache()->propertyStrings[i] = RecyclerNewStruct(GetRecycler(), PropertyStringMap); + ClearArray(this->Cache()->propertyStrings[i]->strLen2, 80); } void ScriptContext::TrackPid(const PropertyRecord* propertyRecord) @@ -1593,17 +1592,17 @@ namespace Js { const char16* buf = propString->GetBuffer(); const uint i = PropertyStringMap::PStrMapIndex(buf[0]); - if (propertyStrings[i] == NULL) + if (this->Cache()->propertyStrings[i] == NULL) { InitPropertyStringMap(i); } const uint j = PropertyStringMap::PStrMapIndex(buf[1]); - if (propertyStrings[i]->strLen2[j] == NULL && !isClosed) + if (this->Cache()->propertyStrings[i]->strLen2[j] == NULL && !isClosed) { - propertyStrings[i]->strLen2[j] = GetLibrary()->CreatePropertyString(propString, this->GeneralAllocator()); + this->Cache()->propertyStrings[i]->strLen2[j] = GetLibrary()->CreatePropertyString(propString); this->TrackPid(propString); } - return propertyStrings[i]->strLen2[j]; + return this->Cache()->propertyStrings[i]->strLen2[j]; } PropertyString* ScriptContext::CachePropertyString2(const PropertyRecord* propString) @@ -1665,10 +1664,28 @@ namespace Js } if (string) { - PropertyCache const* cache = string->GetPropertyCache(); - if (cache->type == type) + PolymorphicInlineCache * cache = string->GetLdElemInlineCache(); + PropertyCacheOperationInfo info; + if (cache->PretendTryGetProperty(type, &info)) { - string->ClearPropertyCache(); +#ifdef ENABLE_DEBUG_CONFIG_OPTIONS + if (PHASE_TRACE1(PropertyStringCachePhase)) + { + Output::Print(_u("PropertyString '%s' : Invalidating LdElem cache for type %p\n"), string->GetString(), type); + } +#endif + cache->GetInlineCaches()[cache->GetInlineCacheIndexForType(type)].Clear(); + } + cache = string->GetStElemInlineCache(); + if (cache->PretendTrySetProperty(type, type, &info)) + { +#ifdef ENABLE_DEBUG_CONFIG_OPTIONS + if (PHASE_TRACE1(PropertyStringCachePhase)) + { + Output::Print(_u("PropertyString '%s' : Invalidating StElem cache for type %p\n"), string->GetString(), type); + } +#endif + cache->GetInlineCaches()[cache->GetInlineCacheIndexForType(type)].Clear(); } } } @@ -5566,26 +5583,8 @@ void ScriptContext::RegisterPrototypeChainEnsuredToHaveOnlyWritableDataPropertie if (PHASE_STATS1(Js::PolymorphicInlineCachePhase)) { Output::Print(_u("%s,%s,%s,%s,%s,%s,%s,%s,%s\n"), _u("Function"), _u("Property"), _u("Kind"), _u("Accesses"), _u("Misses"), _u("Miss Rate"), _u("Collisions"), _u("Collision Rate"), _u("Slot Count")); - cacheDataMap->Map([this](Js::PolymorphicInlineCache const *cache, CacheData *data) { - char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE]; - uint total = data->hits + data->misses; - char16 const *propName = this->threadContext->GetPropertyName(data->propertyId)->GetBuffer(); - - wchar funcName[1024]; - - swprintf_s(funcName, _u("%s (%s)"), cache->functionBody->GetExternalDisplayName(), cache->functionBody->GetDebugNumberSet(debugStringBuffer)); - - Output::Print(_u("%s,%s,%s,%d,%d,%f,%d,%f,%d\n"), - funcName, - propName, - data->isGetCache ? _u("get") : _u("set"), - total, - data->misses, - static_cast(data->misses) / total, - data->collisions, - static_cast(data->collisions) / total, - cache->GetSize() - ); + cacheDataMap->Map([this](Js::PolymorphicInlineCache const *cache, InlineCacheData *data) { + cache->PrintStats(data); }); } #endif @@ -5756,10 +5755,10 @@ void ScriptContext::RegisterPrototypeChainEnsuredToHaveOnlyWritableDataPropertie BindReference(cacheDataMap); } - CacheData *data = NULL; + InlineCacheData *data = NULL; if (!cacheDataMap->TryGetValue(cache, &data)) { - data = Anew(GeneralAllocator(), CacheData); + data = Anew(GeneralAllocator(), InlineCacheData); cacheDataMap->Item(cache, data); data->isGetCache = isGetter; data->propertyId = propertyId; diff --git a/lib/Runtime/Base/ScriptContext.h b/lib/Runtime/Base/ScriptContext.h index e84237076c0..ba5d6327cbd 100644 --- a/lib/Runtime/Base/ScriptContext.h +++ b/lib/Runtime/Base/ScriptContext.h @@ -122,6 +122,22 @@ enum LoadScriptFlag LoadScriptFlag_ExternalArrayBuffer = 0x100 // for ExternalArrayBuffer }; +#ifdef INLINE_CACHE_STATS +// Used to store inline cache stats + +struct InlineCacheData +{ + uint hits; + uint misses; + uint collisions; + bool isGetCache; + Js::PropertyId propertyId; + + InlineCacheData() : hits(0), misses(0), collisions(0), isGetCache(false), propertyId(Js::PropertyIds::_none) { } +}; + +#endif + class HostScriptContext { public: @@ -355,18 +371,6 @@ namespace Js #endif }; - struct PropertyStringMap - { - PropertyString* strLen2[80]; - - inline static uint PStrMapIndex(char16 ch) - { - Assert(ch >= '0' && ch <= 'z'); - return ch - '0'; - } - }; - - /* * This class caches jitted func address ranges. * This is to facilitate WER scenarios to use this cache for checking native addresses. @@ -530,7 +534,6 @@ namespace Js InlineCache * GetToStringInlineCache() const { return toStringInlineCache; } private: - PropertyStringMap* propertyStrings[80]; JavascriptFunction* GenerateRootFunction(ParseNodePtr parseTree, uint sourceIndex, Parser* parser, uint32 grfscr, CompileScriptException * pse, const char16 *rootDisplayName); @@ -705,23 +708,9 @@ namespace Js #endif #ifdef INLINE_CACHE_STATS - // Used to store inline cache stats - - struct CacheData - { - uint hits; - uint misses; - uint collisions; - bool isGetCache; - Js::PropertyId propertyId; - - CacheData() : hits(0), misses(0), collisions(0), isGetCache(false), propertyId(Js::PropertyIds::_none) { } - }; - // This is a strongly referenced dictionary, since we want to know hit rates for dead caches. - typedef JsUtil::BaseDictionary CacheDataMap; + typedef JsUtil::BaseDictionary CacheDataMap; CacheDataMap *cacheDataMap; - void LogCacheUsage(Js::PolymorphicInlineCache *cache, bool isGet, Js::PropertyId propertyId, bool hit, bool collision); #endif diff --git a/lib/Runtime/Language/CacheOperators.inl b/lib/Runtime/Language/CacheOperators.inl index 1461f443a76..80bf0995db0 100644 --- a/lib/Runtime/Language/CacheOperators.inl +++ b/lib/Runtime/Language/CacheOperators.inl @@ -434,16 +434,24 @@ namespace Js // Don't resize a polymorphic inline cache from full JIT because it currently doesn't rejit to use the new // polymorphic inline cache. Once resized, bailouts would populate only the new set of caches and full JIT would // continue to use to old set of caches. - Assert(!info->AllowResizingPolymorphicInlineCache() || info->GetFunctionBody()); + Assert(!info->AllowResizingPolymorphicInlineCache() || info->GetFunctionBody() || info->GetPropertyString()); if(((includeTypePropertyCache && !createTypePropertyCache) || info->AllowResizingPolymorphicInlineCache()) && polymorphicInlineCache->HasDifferentType(isProto, type, typeWithoutProperty)) { if(info->AllowResizingPolymorphicInlineCache() && polymorphicInlineCache->CanAllocateBigger()) { - polymorphicInlineCache = - info->GetFunctionBody()->CreateBiggerPolymorphicInlineCache( - info->GetInlineCacheIndex(), - propertyId); + if (info->GetFunctionBody()) + { + polymorphicInlineCache = + info->GetFunctionBody()->CreateBiggerPolymorphicInlineCache( + info->GetInlineCacheIndex(), + propertyId); + } + else + { + Assert(!info->GetFunctionBody()); + polymorphicInlineCache = info->GetPropertyString()->CreateBiggerPolymorphicInlineCache(IsRead); + } } if(includeTypePropertyCache) { diff --git a/lib/Runtime/Language/InlineCache.cpp b/lib/Runtime/Language/InlineCache.cpp index 0946caf7ccc..8362854bf64 100644 --- a/lib/Runtime/Language/InlineCache.cpp +++ b/lib/Runtime/Language/InlineCache.cpp @@ -203,7 +203,7 @@ namespace Js #endif } - bool InlineCache::PretendTryGetProperty(Type *const type, PropertyCacheOperationInfo * operationInfo) + bool InlineCache::PretendTryGetProperty(Type *const type, PropertyCacheOperationInfo * operationInfo) const { if (type == u.local.type) { @@ -254,7 +254,7 @@ namespace Js return false; } - bool InlineCache::PretendTrySetProperty(Type *const type, Type *const oldType, PropertyCacheOperationInfo * operationInfo) + bool InlineCache::PretendTrySetProperty(Type *const type, Type *const oldType, PropertyCacheOperationInfo * operationInfo) const { if (oldType == u.local.typeWithoutProperty) { @@ -600,29 +600,6 @@ namespace Js } #endif - PolymorphicInlineCache * PolymorphicInlineCache::New(uint16 size, FunctionBody * functionBody) - { - ScriptContext * scriptContext = functionBody->GetScriptContext(); - InlineCache * inlineCaches = AllocatorNewArrayZ(InlineCacheAllocator, scriptContext->GetInlineCacheAllocator(), InlineCache, size); -#ifdef POLY_INLINE_CACHE_SIZE_STATS - scriptContext->GetInlineCacheAllocator()->LogPolyCacheAlloc(size * sizeof(InlineCache)); -#endif - PolymorphicInlineCache * polymorphicInlineCache = RecyclerNewFinalizedLeaf(scriptContext->GetRecycler(), PolymorphicInlineCache, inlineCaches, size, functionBody); - - // Insert the cache into finalization list. We maintain this linked list of polymorphic caches because when we allocate - // a larger cache, the old one might still be used by some code on the stack. Consequently, we can't release - // the inline cache array back to the arena allocator. The list is leaf-allocated and so does not keep the - // old caches alive. As soon as they are collectible, their finalizer releases the inline cache array to the arena. - polymorphicInlineCache->prev = nullptr; - polymorphicInlineCache->next = functionBody->GetPolymorphicInlineCachesHead(); - if (polymorphicInlineCache->next) - { - polymorphicInlineCache->next->prev = polymorphicInlineCache; - } - functionBody->SetPolymorphicInlineCachesHead(polymorphicInlineCache); - - return polymorphicInlineCache; - } template bool PolymorphicInlineCache::HasDifferentType( @@ -697,7 +674,7 @@ namespace Js #endif if (!PHASE_OFF1(Js::CloneCacheInCollisionPhase)) { - if (!inlineCaches[inlineCacheIndex].IsEmpty() && !inlineCaches[inlineCacheIndex].NeedsToBeRegisteredForStoreFieldInvalidation()) + if (!inlineCaches[inlineCacheIndex].IsEmpty() && !inlineCaches[inlineCacheIndex].NeedsToBeRegisteredForStoreFieldInvalidation() && GetSize() != 1) { if (inlineCaches[inlineCacheIndex].IsLocal()) { @@ -771,7 +748,7 @@ namespace Js #endif if (!PHASE_OFF1(Js::CloneCacheInCollisionPhase)) { - if (!inlineCaches[inlineCacheIndex].IsEmpty() && !inlineCaches[inlineCacheIndex].NeedsToBeRegisteredForStoreFieldInvalidation()) + if (!inlineCaches[inlineCacheIndex].IsEmpty() && !inlineCaches[inlineCacheIndex].NeedsToBeRegisteredForStoreFieldInvalidation() && GetSize() != 1) { if (inlineCaches[inlineCacheIndex].IsLocal()) { @@ -822,7 +799,7 @@ namespace Js #endif if (!PHASE_OFF1(Js::CloneCacheInCollisionPhase)) { - if (!inlineCaches[inlineCacheIndex].IsEmpty() && !inlineCaches[inlineCacheIndex].NeedsToBeRegisteredForStoreFieldInvalidation()) + if (!inlineCaches[inlineCacheIndex].IsEmpty() && !inlineCaches[inlineCacheIndex].NeedsToBeRegisteredForStoreFieldInvalidation() && GetSize() != 1) { if (inlineCaches[inlineCacheIndex].IsLocal()) { @@ -891,7 +868,7 @@ namespace Js // This might lead to collision in the new cache. We need to try to resolve that collision. if (!PHASE_OFF1(Js::CloneCacheInCollisionPhase)) { - if (!clone->inlineCaches[inlineCacheIndex].IsEmpty() && !clone->inlineCaches[inlineCacheIndex].NeedsToBeRegisteredForStoreFieldInvalidation()) + if (!clone->inlineCaches[inlineCacheIndex].IsEmpty() && !clone->inlineCaches[inlineCacheIndex].NeedsToBeRegisteredForStoreFieldInvalidation() && GetSize() != 1) { if (clone->inlineCaches[inlineCacheIndex].IsLocal()) { @@ -928,6 +905,218 @@ namespace Js } } #endif + + FunctionBodyPolymorphicInlineCache * FunctionBodyPolymorphicInlineCache::New(uint16 size, FunctionBody * functionBody) + { + ScriptContext * scriptContext = functionBody->GetScriptContext(); + InlineCache * inlineCaches = AllocatorNewArrayZ(InlineCacheAllocator, scriptContext->GetInlineCacheAllocator(), InlineCache, size); +#ifdef POLY_INLINE_CACHE_SIZE_STATS + scriptContext->GetInlineCacheAllocator()->LogPolyCacheAlloc(size * sizeof(InlineCache)); +#endif + FunctionBodyPolymorphicInlineCache * polymorphicInlineCache = RecyclerNewFinalizedLeaf(scriptContext->GetRecycler(), FunctionBodyPolymorphicInlineCache, inlineCaches, size, functionBody); + + polymorphicInlineCache->prev = nullptr; + polymorphicInlineCache->next = polymorphicInlineCache->functionBody->GetPolymorphicInlineCachesHead(); + if (polymorphicInlineCache->next) + { + polymorphicInlineCache->next->prev = polymorphicInlineCache; + } + polymorphicInlineCache->functionBody->SetPolymorphicInlineCachesHead(polymorphicInlineCache); + + return polymorphicInlineCache; + } + + void FunctionBodyPolymorphicInlineCache::Finalize(bool isShutdown) + { + if (size == 0) + { + // Already finalized + Assert(!inlineCaches && !prev && !next); + return; + } + + uint unregisteredInlineCacheCount = 0; + + Assert(inlineCaches && size > 0); + + // If we're not shutting down (as in closing the script context), we need to remove our inline caches from + // thread context's invalidation lists, and release memory back to the arena. During script context shutdown, + // we leave everything in place, because the inline cache arena will stay alive until script context is destroyed + // (as in destructor has been called) and thus the invalidation lists are safe to keep references to caches from this + // script context. We will, however, zero all inline caches so that we don't have to process them on subsequent + // collections, which may still happen from other script contexts. + if (isShutdown) + { +#if DBG + for (int i = 0; i < size; i++) + { + inlineCaches[i].Clear(); + } +#else + memset(inlineCaches, 0, size * sizeof(InlineCache)); +#endif + } + else + { + for (int i = 0; i < size; i++) + { + if (inlineCaches[i].RemoveFromInvalidationList()) + { + unregisteredInlineCacheCount++; + } + } + + AllocatorDeleteArray(InlineCacheAllocator, this->functionBody->GetScriptContext()->GetInlineCacheAllocator(), size, inlineCaches); +#ifdef POLY_INLINE_CACHE_SIZE_STATS + this->functionBody->GetScriptContext()->GetInlineCacheAllocator()->LogPolyCacheFree(size * sizeof(InlineCache)); +#endif + } + + // Remove this PolymorphicInlineCache from the list + if (this == this->functionBody->GetPolymorphicInlineCachesHead()) + { + Assert(!prev); + if (next) + { + Assert(next->prev == this); + next->prev = nullptr; + } + this->functionBody->SetPolymorphicInlineCachesHead(next); + } + else + { + if (prev) + { + Assert(prev->next == this); + prev->next = next; + } + if (next) + { + Assert(next->prev == this); + next->prev = prev; + } + } + prev = next = nullptr; + inlineCaches = nullptr; + size = 0; + if (unregisteredInlineCacheCount > 0) + { + this->functionBody->GetScriptContext()->GetThreadContext()->NotifyInlineCacheBatchUnregistered(unregisteredInlineCacheCount); + } + } + + ScriptContextPolymorphicInlineCache * ScriptContextPolymorphicInlineCache::New(uint16 size, JavascriptLibrary* javascriptLibrary) + { + ScriptContext * scriptContext = javascriptLibrary->GetScriptContext(); + InlineCache * inlineCaches = AllocatorNewArrayZ(InlineCacheAllocator, scriptContext->GetInlineCacheAllocator(), InlineCache, size); +#ifdef POLY_INLINE_CACHE_SIZE_STATS + scriptContext->GetInlineCacheAllocator()->LogPolyCacheAlloc(size * sizeof(InlineCache)); +#endif + ScriptContextPolymorphicInlineCache * polymorphicInlineCache = RecyclerNewFinalized(scriptContext->GetRecycler(), ScriptContextPolymorphicInlineCache, inlineCaches, size, javascriptLibrary); + + return polymorphicInlineCache; + } + + void ScriptContextPolymorphicInlineCache::Finalize(bool isShutdown) + { + if (size == 0) + { + // Already finalized + Assert(!inlineCaches); + return; + } + + uint unregisteredInlineCacheCount = 0; + + Assert(inlineCaches && size > 0); + + // If we're not shutting down (as in closing the script context), we need to remove our inline caches from + // thread context's invalidation lists, and release memory back to the arena. During script context shutdown, + // we leave everything in place, because the inline cache arena will stay alive until script context is destroyed + // (as in destructor has been called) and thus the invalidation lists are safe to keep references to caches from this + // script context. We will, however, zero all inline caches so that we don't have to process them on subsequent + // collections, which may still happen from other script contexts. + if (isShutdown) + { +#if DBG + for (int i = 0; i < size; i++) + { + inlineCaches[i].Clear(); + } +#else + memset(inlineCaches, 0, size * sizeof(InlineCache)); +#endif + } + else + { + for (int i = 0; i < size; i++) + { + if (inlineCaches[i].RemoveFromInvalidationList()) + { + unregisteredInlineCacheCount++; + } + } + AllocatorDeleteArray(InlineCacheAllocator, this->javascriptLibrary->scriptContext->GetInlineCacheAllocator(), size, inlineCaches); +#ifdef POLY_INLINE_CACHE_SIZE_STATS + this->javascriptLibrary->scriptContext->GetInlineCacheAllocator()->LogPolyCacheFree(size * sizeof(InlineCache)); +#endif + } + + inlineCaches = nullptr; + size = 0; + if (unregisteredInlineCacheCount > 0) + { + this->javascriptLibrary->scriptContext->GetThreadContext()->NotifyInlineCacheBatchUnregistered(unregisteredInlineCacheCount); + } + } + +#ifdef INLINE_CACHE_STATS + void FunctionBodyPolymorphicInlineCache::PrintStats(InlineCacheData *data) const + { + char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE]; + wchar funcName[1024]; + uint total = data->hits + data->misses; + char16 const *propName = this->functionBody->GetScriptContext()->GetThreadContext()->GetPropertyName(data->propertyId)->GetBuffer(); + swprintf_s(funcName, _u("%s (%s)"), this->functionBody->GetExternalDisplayName(), this->functionBody->GetDebugNumberSet(debugStringBuffer)); + + Output::Print(_u("%s,%s,%s,%d,%d,%f,%d,%f,%d\n"), + funcName, + propName, + data->isGetCache ? _u("get") : _u("set"), + total, + data->misses, + static_cast(data->misses) / total, + data->collisions, + static_cast(data->collisions) / total, + GetSize() + ); + } + ScriptContext* FunctionBodyPolymorphicInlineCache::GetScriptContext() const + { + return this->functionBody->GetScriptContext(); + } + + void ScriptContextPolymorphicInlineCache::PrintStats(InlineCacheData *data) const + { + uint total = data->hits + data->misses; + + Output::Print(_u("ScriptContext,%s,%s,%d,%d,%f,%d,%f,%d\n"), + data->isGetCache ? _u("get") : _u("set"), + total, + data->misses, + static_cast(data->misses) / total, + data->collisions, + static_cast(data->collisions) / total, + GetSize() + ); + } + + ScriptContext* ScriptContextPolymorphicInlineCache::GetScriptContext() const + { + return this->javascriptLibrary->scriptContext; + } +#endif + #if ENABLE_NATIVE_CODEGEN EquivalentTypeSet::EquivalentTypeSet(RecyclerJITTypeHolder * types, uint16 count) @@ -1217,7 +1406,7 @@ namespace Js this->Set(instanceType, function, result); } } - + /* static */ uint32 IsInstInlineCache::OffsetOfFunction() { diff --git a/lib/Runtime/Language/InlineCache.h b/lib/Runtime/Language/InlineCache.h index 68d391368d3..1c9b34957be 100644 --- a/lib/Runtime/Language/InlineCache.h +++ b/lib/Runtime/Language/InlineCache.h @@ -17,9 +17,9 @@ #define PolymorphicInlineCacheShift 6 // On 64 bit architectures, the least 6 significant bits of a DynamicTypePointer is 0 #endif -// TODO: OOP JIT, move equiv set to backend? // forward decl class JITType; +struct InlineCacheData; template class JITTypeHolderBase; typedef JITTypeHolderBase JITTypeHolder; typedef JITTypeHolderBase RecyclerJITTypeHolder; @@ -310,8 +310,8 @@ namespace Js PropertyCacheOperationInfo *const operationInfo, const PropertyOperationFlags propertyOperationFlags = PropertyOperation_None); - bool PretendTryGetProperty(Type *const type, PropertyCacheOperationInfo * operationInfo); - bool PretendTrySetProperty(Type *const type, Type *const oldType, PropertyCacheOperationInfo * operationInfo); + bool PretendTryGetProperty(Type *const type, PropertyCacheOperationInfo * operationInfo) const; + bool PretendTrySetProperty(Type *const type, Type *const oldType, PropertyCacheOperationInfo * operationInfo) const; void Clear(); template @@ -364,8 +364,7 @@ namespace Js CompileAssert(sizeof(InlineCache) == sizeof(InlineCacheAllocator::CacheLayout)); CompileAssert(offsetof(InlineCache, invalidationListSlotPtr) == offsetof(InlineCacheAllocator::CacheLayout, strongRef)); - struct JitTimePolymorphicInlineCache; - struct PolymorphicInlineCache sealed : public FinalizableObject + class PolymorphicInlineCache _ABSTRACT : public FinalizableObject { #ifdef INLINE_CACHE_STATS friend class Js::ScriptContext; @@ -374,33 +373,21 @@ namespace Js public: static const bool IsPolymorphic = true; - private: + protected: FieldNoBarrier(InlineCache *) inlineCaches; - Field(FunctionBody *) functionBody; Field(uint16) size; Field(bool) ignoreForEquivalentObjTypeSpec; Field(bool) cloneForJitTimeUse; Field(int32) inlineCachesFillInfo; - // DList chaining all polymorphic inline caches of a FunctionBody together. - // Since PolymorphicInlineCache is a leaf object, these references do not keep - // the polymorphic inline caches alive. When a PolymorphicInlineCache is finalized, - // it removes itself from the list and deletes its inline cache array. - Field(PolymorphicInlineCache *) next; - Field(PolymorphicInlineCache *) prev; - - PolymorphicInlineCache(InlineCache * inlineCaches, uint16 size, FunctionBody * functionBody) - : inlineCaches(inlineCaches), functionBody(functionBody), size(size), ignoreForEquivalentObjTypeSpec(false), cloneForJitTimeUse(true), inlineCachesFillInfo(0), next(nullptr), prev(nullptr) + PolymorphicInlineCache(InlineCache * inlineCaches, uint16 size) + : inlineCaches(inlineCaches), size(size), ignoreForEquivalentObjTypeSpec(false), cloneForJitTimeUse(true), inlineCachesFillInfo(0) { - Assert((size == 0 && inlineCaches == nullptr) || - (inlineCaches != nullptr && size >= MinPolymorphicInlineCacheSize && size <= MaxPolymorphicInlineCacheSize)); } public: - static PolymorphicInlineCache * New(uint16 size, FunctionBody * functionBody); - static uint16 GetInitialSize() { return MinPolymorphicInlineCacheSize; } bool CanAllocateBigger() { return GetSize() < MaxPolymorphicInlineCacheSize; } static uint16 GetNextSize(uint16 currentSize) { @@ -408,6 +395,11 @@ namespace Js { return 0; } + else if (currentSize == MinPropertyStringInlineCacheSize) + { + CompileAssert(MinPropertyStringInlineCacheSize < MinPolymorphicInlineCacheSize); + return MinPolymorphicInlineCacheSize; + } else { Assert(currentSize >= MinPolymorphicInlineCacheSize && currentSize <= (MaxPolymorphicInlineCacheSize / 2)); @@ -421,7 +413,6 @@ namespace Js InlineCache * GetInlineCaches() const { return inlineCaches; } uint16 GetSize() const { return size; } - PolymorphicInlineCache * GetNext() { return next; } bool GetIgnoreForEquivalentObjTypeSpec() const { return this->ignoreForEquivalentObjTypeSpec; } void SetIgnoreForEquivalentObjTypeSpec(bool value) { this->ignoreForEquivalentObjTypeSpec = value; } bool GetCloneForJitTimeUse() const { return this->cloneForJitTimeUse; } @@ -429,8 +420,8 @@ namespace Js uint32 GetInlineCachesFillInfo() { return this->inlineCachesFillInfo; } void UpdateInlineCachesFillInfo(uint32 index, bool set); bool IsFull(); + void Clear(Type * type); - virtual void Finalize(bool isShutdown) override; virtual void Dispose(bool isShutdown) override { }; virtual void Mark(Recycler *recycler) override { AssertMsg(false, "Mark called on object that isn't TrackableObject"); } @@ -507,6 +498,14 @@ namespace Js return (((size_t)type) >> PolymorphicInlineCacheShift) & (GetSize() - 1); } +#ifdef INLINE_CACHE_STATS + virtual void PrintStats(InlineCacheData *data) const = 0; + virtual ScriptContext* GetScriptContext() const = 0; +#endif + + static uint32 GetOffsetOfSize() { return offsetof(Js::PolymorphicInlineCache, size); } + static uint32 GetOffsetOfInlineCaches() { return offsetof(Js::PolymorphicInlineCache, inlineCaches); } + private: uint GetNextInlineCacheIndex(uint index) const { @@ -540,6 +539,62 @@ namespace Js #endif }; + + class FunctionBodyPolymorphicInlineCache sealed : public PolymorphicInlineCache + { + private: + FunctionBody * functionBody; + + // DList chaining all polymorphic inline caches of a FunctionBody together. + // Since PolymorphicInlineCache is a leaf object, these references do not keep + // the polymorphic inline caches alive. When a PolymorphicInlineCache is finalized, + // it removes itself from the list and deletes its inline cache array. + // We maintain this linked list of polymorphic caches because when we allocate a larger cache, + // the old one might still be used by some code on the stack. Consequently, we can't release + // the inline cache array back to the arena allocator. + FunctionBodyPolymorphicInlineCache * next; + FunctionBodyPolymorphicInlineCache * prev; + + FunctionBodyPolymorphicInlineCache(InlineCache * inlineCaches, uint16 size, FunctionBody * functionBody) + : PolymorphicInlineCache(inlineCaches, size), functionBody(functionBody), next(nullptr), prev(nullptr) + { + Assert((size == 0 && inlineCaches == nullptr) || + (inlineCaches != nullptr && size >= MinPolymorphicInlineCacheSize && size <= MaxPolymorphicInlineCacheSize)); + } + public: + static FunctionBodyPolymorphicInlineCache * New(uint16 size, FunctionBody * functionBody); + +#ifdef INLINE_CACHE_STATS + virtual void PrintStats(InlineCacheData *data) const override; + virtual ScriptContext* GetScriptContext() const override; +#endif + + virtual void Finalize(bool isShutdown) override; + }; + + class ScriptContextPolymorphicInlineCache sealed : public PolymorphicInlineCache + { + private: + Field(JavascriptLibrary*) javascriptLibrary; + + ScriptContextPolymorphicInlineCache(InlineCache * inlineCaches, uint16 size, JavascriptLibrary * javascriptLibrary) + : PolymorphicInlineCache(inlineCaches, size), javascriptLibrary(javascriptLibrary) + { + Assert((size == 0 && inlineCaches == nullptr) || + (inlineCaches != nullptr && size >= MinPropertyStringInlineCacheSize && size <= MaxPolymorphicInlineCacheSize)); + } + + public: + static ScriptContextPolymorphicInlineCache * New(uint16 size, JavascriptLibrary * javascriptLibrary); + +#ifdef INLINE_CACHE_STATS + virtual void PrintStats(InlineCacheData *data) const override; + virtual ScriptContext* GetScriptContext() const override; +#endif + + virtual void Finalize(bool isShutdown) override; + }; + #if ENABLE_NATIVE_CODEGEN class EquivalentTypeSet { diff --git a/lib/Runtime/Language/InlineCache.inl b/lib/Runtime/Language/InlineCache.inl index a5fce755ddf..bdd49334d54 100644 --- a/lib/Runtime/Language/InlineCache.inl +++ b/lib/Runtime/Language/InlineCache.inl @@ -560,7 +560,7 @@ namespace Js if (PHASE_STATS1(Js::PolymorphicInlineCachePhase)) { bool collision = !result && !isEmpty; - this->functionBody->GetScriptContext()->LogCacheUsage(this, /*isGet*/ true, propertyId, result, collision); + GetScriptContext()->LogCacheUsage(this, /*isGet*/ true, propertyId, result, collision); } #endif @@ -620,7 +620,7 @@ namespace Js if (PHASE_STATS1(Js::PolymorphicInlineCachePhase)) { bool collision = !result && !isEmpty; - this->functionBody->GetScriptContext()->LogCacheUsage(this, /*isGet*/ false, propertyId, result, collision); + GetScriptContext()->LogCacheUsage(this, /*isGet*/ false, propertyId, result, collision); } #endif diff --git a/lib/Runtime/Language/JavascriptOperators.cpp b/lib/Runtime/Language/JavascriptOperators.cpp index 205c1dda12a..36c64d1b5f9 100644 --- a/lib/Runtime/Language/JavascriptOperators.cpp +++ b/lib/Runtime/Language/JavascriptOperators.cpp @@ -1084,13 +1084,34 @@ namespace Js PropertyString *propString = requestContext->TryGetPropertyString(propertyId); if (propString != nullptr) { - const PropertyCache *propCache = propString->GetPropertyCache(); - if (object->GetType() == propCache->type) + PropertyCacheOperationInfo info; + if (propString->GetLdElemInlineCache()->PretendTryGetProperty(object->GetType(), &info)) { - // The type cached for the property was the same as the type of this object - // (i.e. obj in obj.hasOwnProperty), so we know the answer is "true". - Assert(TRUE == (object && object->HasOwnProperty(propertyId))); // sanity check on the fastpath result - return TRUE; + switch (info.cacheType) + { + case CacheType_Local: + Assert(object->HasOwnProperty(propertyId)); + return TRUE; + case CacheType_Proto: + Assert(!object->HasOwnProperty(propertyId)); + return FALSE; + default: + break; + } + } + if (propString->GetStElemInlineCache()->PretendTrySetProperty(object->GetType(), object->GetType(), &info)) + { + switch (info.cacheType) + { + case CacheType_Local: + Assert(object->HasOwnProperty(propertyId)); + return TRUE; + case CacheType_LocalWithoutProperty: + Assert(!object->HasOwnProperty(propertyId)); + return FALSE; + default: + break; + } } } @@ -1772,33 +1793,18 @@ namespace Js } template - BOOL JavascriptOperators::GetPropertyWPCache(Var instance, RecyclableObject* propertyObject, PropertyKeyType propertyKey, Var* value, ScriptContext* requestContext, PropertyString * propertyString) + BOOL JavascriptOperators::GetPropertyWPCache(Var instance, RecyclableObject* propertyObject, PropertyKeyType propertyKey, Var* value, ScriptContext* requestContext, _Inout_ PropertyValueInfo * info) { - if (TaggedNumber::Is(instance)) - { - propertyString = NULL; - } - PropertyValueInfo info; RecyclableObject* object = propertyObject; while (JavascriptOperators::GetTypeId(object) != TypeIds_Null) { - PropertyQueryFlags result = object->GetPropertyQuery(instance, propertyKey, value, &info, requestContext); + PropertyQueryFlags result = object->GetPropertyQuery(instance, propertyKey, value, info, requestContext); if (result != Property_NotFound) { - if (propertyString != NULL) + if (value && !WithScopeObject::Is(object) && info->GetPropertyString()) { - uint16 slotIndex = info.GetPropertyIndex(); - if (slotIndex != Constants::NoSlot && - info.GetInstance() == object && - info.IsWritable() && !object->CanHaveInterceptors() && - requestContext == object->GetScriptContext() && - ((info.GetFlags() & (InlineCacheGetterFlag | InlineCacheSetterFlag)) == 0)) - { - uint16 inlineOrAuxSlotIndex; - bool isInlineSlot; - DynamicObject::FromVar(info.GetInstance())->GetTypeHandler()->PropertyIndexToInlineOrAuxSlotIndex(slotIndex, &inlineOrAuxSlotIndex, &isInlineSlot); - propertyString->UpdateCache(info.GetInstance()->GetType(), inlineOrAuxSlotIndex, isInlineSlot, info.IsStoreFieldCacheEnabled()); - } + PropertyId propertyId = info->GetPropertyString()->GetPropertyRecord()->GetPropertyId(); + CacheOperators::CachePropertyRead(instance, object, false, propertyId, false, info, requestContext); } return JavascriptConversion::PropertyQueryFlagsToBoolean(result); } @@ -1808,6 +1814,12 @@ namespace Js } object = JavascriptOperators::GetPrototypeNoTrap(object); } + if (!PHASE_OFF1(MissingPropertyCachePhase) && info->GetPropertyString() && DynamicObject::Is(instance) && ((DynamicObject*)instance)->GetDynamicType()->GetTypeHandler()->IsPathTypeHandler()) + { + PropertyValueInfo::Set(info, requestContext->GetLibrary()->GetMissingPropertyHolder(), 0); + CacheOperators::CachePropertyRead(instance, requestContext->GetLibrary()->GetMissingPropertyHolder(), false, info->GetPropertyString()->GetPropertyRecord()->GetPropertyId(), true, info, requestContext); + } + *value = requestContext->GetMissingPropertyResult(); return FALSE; } @@ -2161,7 +2173,7 @@ namespace Js } template - BOOL JavascriptOperators::SetPropertyWPCache(Var receiver, RecyclableObject* object, PropertyKeyType propertyKey, Var newValue, ScriptContext* requestContext, PropertyString * propertyString, PropertyOperationFlags propertyOperationFlags) + BOOL JavascriptOperators::SetPropertyWPCache(Var receiver, RecyclableObject* object, PropertyKeyType propertyKey, Var newValue, ScriptContext* requestContext, PropertyOperationFlags propertyOperationFlags, _Inout_ PropertyValueInfo * info) { if (receiver) { @@ -2169,7 +2181,7 @@ namespace Js Assert(!TaggedNumber::Is(receiver)); Var setterValueOrProxy = nullptr; DescriptorFlags flags = None; - if (JavascriptOperators::CheckPrototypesForAccessorOrNonWritableProperty(object, propertyKey, &setterValueOrProxy, &flags, NULL, requestContext)) + if (JavascriptOperators::CheckPrototypesForAccessorOrNonWritableProperty(object, propertyKey, &setterValueOrProxy, &flags, info, requestContext)) { if ((flags & Accessor) == Accessor) { @@ -2179,8 +2191,13 @@ namespace Js } if (setterValueOrProxy) { + if (!WithScopeObject::Is(receiver) && info->GetPropertyString()) + { + CacheOperators::CachePropertyWrite(RecyclableObject::FromVar(receiver), false, object->GetType(), info->GetPropertyString()->GetPropertyRecord()->GetPropertyId(), info, requestContext); + } receiver = (RecyclableObject::FromVar(receiver))->GetThisObjectOrUnWrap(); RecyclableObject* func = RecyclableObject::FromVar(setterValueOrProxy); + JavascriptOperators::CallSetter(func, receiver, newValue, requestContext); } return TRUE; @@ -2190,8 +2207,13 @@ namespace Js Assert(JavascriptProxy::Is(setterValueOrProxy)); JavascriptProxy* proxy = JavascriptProxy::FromVar(setterValueOrProxy); auto fn = [&](RecyclableObject* target) -> BOOL { - return JavascriptOperators::SetPropertyWPCache(receiver, target, propertyKey, newValue, requestContext, propertyString, propertyOperationFlags); + return JavascriptOperators::SetPropertyWPCache(receiver, target, propertyKey, newValue, requestContext, propertyOperationFlags, info); }; + if (info->GetPropertyString()) + { + PropertyValueInfo::SetNoCache(info, proxy); + PropertyValueInfo::DisablePrototypeCache(info, proxy); + } return proxy->SetPropertyTrap(receiver, JavascriptProxy::SetPropertyTrapKind::SetPropertyWPCacheKind, propertyKey, newValue, requestContext); } else @@ -2221,23 +2243,18 @@ namespace Js } } + Type *typeWithoutProperty = object->GetType(); // in 9.1.9, step 5, we should return false if receiver is not object, and that will happen in default RecyclableObject operation anyhow. - PropertyValueInfo info; - if (receiverObject->SetProperty(propertyKey, newValue, propertyOperationFlags, &info)) + if (receiverObject->SetProperty(propertyKey, newValue, propertyOperationFlags, info)) { - if (propertyString != NULL) + if (!JavascriptProxy::Is(receiver) && info->GetPropertyString() && info->GetFlags() != InlineCacheSetterFlag && !object->CanHaveInterceptors()) { - uint16 slotIndex = info.GetPropertyIndex(); - if (slotIndex != Constants::NoSlot && - info.GetInstance() == receiverObject && - !object->CanHaveInterceptors() && - requestContext == receiverObject->GetScriptContext() && - (info.GetFlags() != InlineCacheSetterFlag)) + CacheOperators::CachePropertyWrite(RecyclableObject::FromVar(receiver), false, typeWithoutProperty, info->GetPropertyString()->GetPropertyRecord()->GetPropertyId(), info, requestContext); + + if (info->GetInstance() == receiverObject) { - uint16 inlineOrAuxSlotIndex; - bool isInlineSlot; - DynamicObject::FromVar(info.GetInstance())->GetTypeHandler()->PropertyIndexToInlineOrAuxSlotIndex(info.GetPropertyIndex(), &inlineOrAuxSlotIndex, &isInlineSlot); - propertyString->UpdateCache(info.GetInstance()->GetType(), inlineOrAuxSlotIndex, isInlineSlot, info.IsStoreFieldCacheEnabled()); + PropertyValueInfo::SetCacheInfo(info, info->GetPropertyString(), info->GetPropertyString()->GetLdElemInlineCache(), info->AllowResizingPolymorphicInlineCache()); + CacheOperators::CachePropertyRead(object, receiverObject, false, info->GetPropertyString()->GetPropertyRecord()->GetPropertyId(), false, info, requestContext); } } return TRUE; @@ -3716,38 +3733,17 @@ namespace Js if (VirtualTableInfo::HasVirtualTable(temp)) { PropertyString * propertyString = (PropertyString*)temp; - PropertyCache const *cache = propertyString->GetPropertyCache(); RecyclableObject* object = nullptr; if (FALSE == JavascriptOperators::GetPropertyObject(instance, scriptContext, &object)) { JavascriptError::ThrowTypeError(scriptContext, JSERR_Property_CannotGet_NullOrUndefined, JavascriptString::FromVar(index)->GetSz()); } - if (object->GetType() == cache->type) - { -#if DBG_DUMP - scriptContext->forinCache++; -#endif - Assert(object->GetScriptContext() == scriptContext); - Var value; - if (cache->isInlineSlot) - { - value = DynamicObject::FromVar(object)->GetInlineSlot(cache->dataSlotIndex); - } - else - { - value = DynamicObject::FromVar(object)->GetAuxSlot(cache->dataSlotIndex); - } - Assert(!CrossSite::NeedMarshalVar(value, scriptContext)); - Assert(value == JavascriptOperators::GetProperty(object, propertyString->GetPropertyRecord()->GetPropertyId(), scriptContext) - || value == JavascriptOperators::GetRootProperty(object, propertyString->GetPropertyRecord()->GetPropertyId(), scriptContext)); - return value; - } -#if DBG_DUMP - scriptContext->forinNoCache++; -#endif + PropertyRecord const * propertyRecord = propertyString->GetPropertyRecord(); + const PropertyId propId = propertyRecord->GetPropertyId(); Var value; + if (propertyRecord->IsNumeric()) { if (JavascriptOperators::GetItem(instance, object, propertyRecord->GetNumericValue(), &value, scriptContext)) @@ -3757,13 +3753,47 @@ namespace Js } else { - if (JavascriptOperators::GetPropertyWPCache(instance, object, propertyRecord->GetPropertyId(), &value, scriptContext, propertyString)) + PropertyValueInfo info; + if (propertyString->ShouldUseCache()) + { + PropertyValueInfo::SetCacheInfo(&info, propertyString, propertyString->GetLdElemInlineCache(), true); + if (CacheOperators::TryGetProperty( + instance, false, object, propId, &value, scriptContext, nullptr, &info)) + { + propertyString->LogCacheHit(); +#ifdef ENABLE_DEBUG_CONFIG_OPTIONS + if (PHASE_TRACE1(PropertyStringCachePhase)) + { + Output::Print(_u("PropertyCache: GetElem cache hit for '%s': type %p\n"), propertyString->GetString(), object->GetType()); + } +#endif + return value; + } + } + propertyString->LogCacheMiss(); +#ifdef ENABLE_DEBUG_CONFIG_OPTIONS + if (PHASE_TRACE1(PropertyStringCachePhase)) + { + Output::Print(_u("PropertyCache: GetElem cache miss for '%s': type %p, index %d\n"), + propertyString->GetString(), + object->GetType(), + propertyString->GetLdElemInlineCache()->GetInlineCacheIndexForType(object->GetType())); + propertyString->DumpCache(true); + } +#endif + if (JavascriptOperators::GetPropertyWPCache(instance, object, propertyRecord->GetPropertyId(), &value, scriptContext, &info)) { return value; } } return scriptContext->GetLibrary()->GetUndefined(); } +#ifdef ENABLE_DEBUG_CONFIG_OPTIONS + if (PHASE_TRACE1(PropertyStringCachePhase)) + { + Output::Print(_u("PropertyCache: GetElem No property string for '%s'\n"), temp->GetString()); + } +#endif #if DBG_DUMP scriptContext->forinNoCache++; #endif @@ -3803,7 +3833,8 @@ namespace Js } else if (indexType == IndexType_JavascriptString) { - if (JavascriptOperators::GetPropertyWPCache(receiver, object, propertyNameString, &value, scriptContext, nullptr)) + PropertyValueInfo info; + if (JavascriptOperators::GetPropertyWPCache(receiver, object, propertyNameString, &value, scriptContext, &info)) { return value; } @@ -3820,7 +3851,8 @@ namespace Js if (propertyRecord != nullptr) { - if (JavascriptOperators::GetPropertyWPCache(receiver, object, propertyRecord->GetPropertyId(), &value, scriptContext, nullptr)) + PropertyValueInfo info; + if (JavascriptOperators::GetPropertyWPCache(receiver, object, propertyRecord->GetPropertyId(), &value, scriptContext, &info)) { return value; } @@ -4394,6 +4426,7 @@ namespace Js uint32 indexVal = 0; PropertyRecord const * propertyRecord = nullptr; JavascriptString * propertyNameString = nullptr; + PropertyValueInfo propertyValueInfo; if (TaggedNumber::Is(receiver)) { @@ -4415,30 +4448,8 @@ namespace Js propertyString = (PropertyString *)JavascriptString::FromVar(index); Assert(propertyString->GetScriptContext() == scriptContext); - - PropertyCache const * cache = propertyString->GetPropertyCache(); - if (receiver == object && object->GetType() == cache->type && cache->isStoreFieldEnabled) - { -#if DBG - propertyRecord = propertyString->GetPropertyRecord(); -#endif -#if DBG_DUMP - scriptContext->forinCache++; -#endif - Assert(object->GetScriptContext() == scriptContext); - Assert(!CrossSite::NeedMarshalVar(value, scriptContext)); - if (cache->isInlineSlot) - { - DynamicObject::FromVar(object)->SetInlineSlot(SetSlotArguments(propertyRecord->GetPropertyId(), cache->dataSlotIndex, value)); - } - else - { - DynamicObject::FromVar(object)->SetAuxSlot(SetSlotArguments(propertyRecord->GetPropertyId(), cache->dataSlotIndex, value)); - } - return true; - } - propertyRecord = propertyString->GetPropertyRecord(); + if (propertyRecord->IsNumeric()) { indexType = IndexType_Number; @@ -4446,6 +4457,43 @@ namespace Js } else { + if (receiver == object) + { + if (propertyString->ShouldUseCache()) + { + PropertyValueInfo::SetCacheInfo(&propertyValueInfo, propertyString, propertyString->GetStElemInlineCache(), true); + if (CacheOperators::TrySetProperty( + object, + false, + propertyRecord->GetPropertyId(), + value, + scriptContext, + flags, + nullptr, + &propertyValueInfo)) + { +#ifdef ENABLE_DEBUG_CONFIG_OPTIONS + if (PHASE_TRACE1(PropertyStringCachePhase)) + { + Output::Print(_u("PropertyCache: SetElem cache hit for '%s': type %p\n"), propertyString->GetString(), object->GetType()); + } +#endif + propertyString->LogCacheHit(); + return true; + } + } + propertyString->LogCacheMiss(); +#ifdef ENABLE_DEBUG_CONFIG_OPTIONS + if (PHASE_TRACE1(PropertyStringCachePhase)) + { + Output::Print(_u("PropertyCache: SetElem cache miss for '%s': type %p, index %d\n"), + propertyString->GetString(), + object->GetType(), + propertyString->GetStElemInlineCache()->GetInlineCacheIndexForType(object->GetType())); + propertyString->DumpCache(false); + } +#endif + } indexType = IndexType_PropertyId; } @@ -4487,11 +4535,17 @@ namespace Js // Follow SetProperty convention for Infinity return JavascriptOperators::SetProperty(receiver, object, PropertyIds::Infinity, value, scriptContext, flags); } - - return JavascriptOperators::SetPropertyWPCache(receiver, object, propertyNameString, value, scriptContext, nullptr, flags); +#ifdef ENABLE_DEBUG_CONFIG_OPTIONS + if (PHASE_TRACE1(PropertyStringCachePhase)) + { + Output::Print(_u("PropertyCache: SetElem No property string for '%s'\n"), propertyNameString->GetString()); + } +#endif + return SetPropertyWPCache(receiver, object, propertyNameString, value, scriptContext, flags, &propertyValueInfo); } - else if (indexType == IndexType_PropertyId) + else { + Assert(indexType == IndexType_PropertyId); Assert(propertyRecord); PropertyId propId = propertyRecord->GetPropertyId(); if (propId == PropertyIds::NaN || propId == PropertyIds::Infinity) @@ -4501,9 +4555,8 @@ namespace Js // Note that "-Infinity" does not qualify as property name, so we don't have to take care of it. return JavascriptOperators::SetProperty(receiver, object, propId, value, scriptContext, flags); } + return SetPropertyWPCache(receiver, object, propId, value, scriptContext, flags, &propertyValueInfo); } - - return JavascriptOperators::SetPropertyWPCache(receiver, object, propertyRecord->GetPropertyId(), value, scriptContext, propertyString, flags); } BOOL JavascriptOperators::OP_SetNativeIntElementI( @@ -9449,85 +9502,6 @@ namespace Js throw ScriptAbortException(); } - void PolymorphicInlineCache::Finalize(bool isShutdown) - { - if (size == 0) - { - // Already finalized - Assert(!inlineCaches && !prev && !next); - return; - } - - uint unregisteredInlineCacheCount = 0; - - Assert(inlineCaches && size > 0); - - // If we're not shutting down (as in closing the script context), we need to remove our inline caches from - // thread context's invalidation lists, and release memory back to the arena. During script context shutdown, - // we leave everything in place, because the inline cache arena will stay alive until script context is destroyed - // (as in destructor has been called) and thus the invalidation lists are safe to keep references to caches from this - // script context. We will, however, zero all inline caches so that we don't have to process them on subsequent - // collections, which may still happen from other script contexts. - if (isShutdown) - { -#if DBG - for (int i = 0; i < size; i++) - { - inlineCaches[i].Clear(); - } -#else - memset(inlineCaches, 0, size * sizeof(InlineCache)); -#endif - } - else - { - for (int i = 0; i < size; i++) - { - if (inlineCaches[i].RemoveFromInvalidationList()) - { - unregisteredInlineCacheCount++; - } - } - - AllocatorDeleteArray(InlineCacheAllocator, functionBody->GetScriptContext()->GetInlineCacheAllocator(), size, inlineCaches); -#ifdef POLY_INLINE_CACHE_SIZE_STATS - functionBody->GetScriptContext()->GetInlineCacheAllocator()->LogPolyCacheFree(size * sizeof(InlineCache)); -#endif - } - - // Remove this PolymorphicInlineCache from the list - if (this == functionBody->GetPolymorphicInlineCachesHead()) - { - Assert(!prev); - if (next) - { - Assert(next->prev == this); - next->prev = nullptr; - } - functionBody->SetPolymorphicInlineCachesHead(next); - } - else - { - if (prev) - { - Assert(prev->next == this); - prev->next = next; - } - if (next) - { - Assert(next->prev == this); - next->prev = prev; - } - } - prev = next = nullptr; - inlineCaches = nullptr; - size = 0; - if (unregisteredInlineCacheCount > 0) - { - functionBody->GetScriptContext()->GetThreadContext()->NotifyInlineCacheBatchUnregistered(unregisteredInlineCacheCount); - } - } - JavascriptString * JavascriptOperators::Concat3(Var aLeft, Var aCenter, Var aRight, ScriptContext * scriptContext) { // Make sure we do the conversion in order from left to right diff --git a/lib/Runtime/Language/JavascriptOperators.h b/lib/Runtime/Language/JavascriptOperators.h index 53983a8404f..f0ea401540d 100644 --- a/lib/Runtime/Language/JavascriptOperators.h +++ b/lib/Runtime/Language/JavascriptOperators.h @@ -174,7 +174,7 @@ namespace Js static BOOL HasProxyOrPrototypeInlineCacheProperty(RecyclableObject* instance, PropertyId propertyId); static BOOL HasProxyInPrototypeChain(RecyclableObject* instance); template - static BOOL GetPropertyWPCache(Var instance, RecyclableObject* propertyObject, PropertyKeyType propertyKey, Var* value, ScriptContext* requestContext, PropertyString * propertyString); + static BOOL GetPropertyWPCache(Var instance, RecyclableObject* propertyObject, PropertyKeyType propertyKey, Var* value, ScriptContext* requestContext, _Inout_ PropertyValueInfo * info); static BOOL GetPropertyUnscopable(Var instance, RecyclableObject* propertyObject, PropertyId propertyId, Var* value, ScriptContext* requestContext, PropertyValueInfo* info=NULL); static Var GetProperty(RecyclableObject* instance, PropertyId propertyId, ScriptContext* requestContext, PropertyValueInfo* info = NULL); static BOOL GetProperty(RecyclableObject* instance, PropertyId propertyId, Var* value, ScriptContext* requestContext, PropertyValueInfo* info = NULL); @@ -188,7 +188,7 @@ namespace Js static BOOL GetPropertyReference(Var instance, RecyclableObject* propertyObject, PropertyId propertyId, Var* value,ScriptContext* requestContext, PropertyValueInfo* info = NULL); static BOOL GetRootPropertyReference(RecyclableObject* instance, PropertyId propertyId, Var* value,ScriptContext* requestContext, PropertyValueInfo* info = NULL); template - static BOOL SetPropertyWPCache(Var instance, RecyclableObject* object, PropertyKeyType propertyKey, Var newValue, ScriptContext* requestContext, PropertyString * propertyString, PropertyOperationFlags flags); + static BOOL SetPropertyWPCache(Var instance, RecyclableObject* object, PropertyKeyType propertyKey, Var newValue, ScriptContext* requestContext, PropertyOperationFlags flags, _Inout_ PropertyValueInfo * info); static BOOL SetPropertyUnscopable(Var instance, RecyclableObject* receiver, PropertyId propertyId, Var newValue, PropertyValueInfo * info, ScriptContext* requestContext, PropertyOperationFlags flags = PropertyOperation_None); static BOOL SetProperty(Var instance, RecyclableObject* object, PropertyId propertyId, Var newValue, ScriptContext* requestContext, PropertyOperationFlags flags = PropertyOperation_None); static BOOL SetProperty(Var instance, RecyclableObject* receiver, PropertyId propertyId, Var newValue, PropertyValueInfo * info, ScriptContext* requestContext, PropertyOperationFlags flags = PropertyOperation_None); diff --git a/lib/Runtime/Library/JavascriptLibrary.cpp b/lib/Runtime/Library/JavascriptLibrary.cpp index 733de78943a..9ea5eea7ec9 100644 --- a/lib/Runtime/Library/JavascriptLibrary.cpp +++ b/lib/Runtime/Library/JavascriptLibrary.cpp @@ -6786,12 +6786,6 @@ namespace Js return PropertyString::New(stringTypeStatic, propertyRecord, this->GetRecycler()); } - PropertyString* JavascriptLibrary::CreatePropertyString(const Js::PropertyRecord* propertyRecord, ArenaAllocator *arena) - { - AssertMsg(stringTypeStatic, "Where's stringTypeStatic?"); - return PropertyString::New(stringTypeStatic, propertyRecord, arena); - } - JavascriptVariantDate* JavascriptLibrary::CreateVariantDate(const double value) { AssertMsg(variantDateType, "Where's variantDateType?"); diff --git a/lib/Runtime/Library/JavascriptLibrary.h b/lib/Runtime/Library/JavascriptLibrary.h index 5709827943f..f851511fb67 100644 --- a/lib/Runtime/Library/JavascriptLibrary.h +++ b/lib/Runtime/Library/JavascriptLibrary.h @@ -52,8 +52,20 @@ namespace Js Field(int) validPropStrings; }; + struct PropertyStringMap + { + Field(PropertyString*) strLen2[80]; + + inline static uint PStrMapIndex(char16 ch) + { + Assert(ch >= '0' && ch <= 'z'); + return ch - '0'; + } + }; + struct Cache { + Field(PropertyStringMap*) propertyStrings[80]; Field(JavascriptString *) lastNumberToStringRadix10String; Field(EnumeratedObjectCache) enumObjCache; Field(JavascriptString *) lastUtcTimeFromStrString; @@ -1064,7 +1076,6 @@ namespace Js template<> JavascriptString* CreateStringFromCppLiteral(const char16 (&value)[1]) const; // Specialization for empty string template<> JavascriptString* CreateStringFromCppLiteral(const char16 (&value)[2]) const; // Specialization for single-char strings PropertyString* CreatePropertyString(const Js::PropertyRecord* propertyRecord); - PropertyString* CreatePropertyString(const Js::PropertyRecord* propertyRecord, ArenaAllocator *arena); JavascriptVariantDate* CreateVariantDate(const double value); diff --git a/lib/Runtime/Library/JavascriptProxy.cpp b/lib/Runtime/Library/JavascriptProxy.cpp index c051fabee77..d6029f3ea58 100644 --- a/lib/Runtime/Library/JavascriptProxy.cpp +++ b/lib/Runtime/Library/JavascriptProxy.cpp @@ -557,7 +557,7 @@ namespace Js PropertyValueInfo::SetNoCache(info, this); PropertyValueInfo::DisablePrototypeCache(info, this); // We can't cache prototype property either auto fn = [&](RecyclableObject* object)-> BOOL { - return JavascriptOperators::GetPropertyWPCache(originalInstance, object, propertyNameString, value, requestContext, nullptr); + return JavascriptOperators::GetPropertyWPCache(originalInstance, object, propertyNameString, value, requestContext, info); }; auto getPropertyId = [&]()->PropertyId{ const PropertyRecord* propertyRecord; @@ -1838,13 +1838,8 @@ namespace Js } case SetPropertyTrapKind::SetPropertyWPCacheKind: { - Var name = GetName(requestContext, propertyId); - if (!JavascriptString::Is(name) || !VirtualTableInfo::HasVirtualTable(JavascriptString::FromVar(name))) - { - name = nullptr; - } - return JavascriptOperators::SetPropertyWPCache(receiver, targetObj, propertyId, newValue, requestContext, - static_cast(name), PropertyOperationFlags::PropertyOperation_None); + PropertyValueInfo propertyValueInfo; + return JavascriptOperators::SetPropertyWPCache(receiver, targetObj, propertyId, newValue, requestContext, PropertyOperationFlags::PropertyOperation_None, &propertyValueInfo); } default: Assert(FALSE); diff --git a/lib/Runtime/Library/PropertyString.cpp b/lib/Runtime/Library/PropertyString.cpp index db3cadd1c6c..c25d4e6bae6 100644 --- a/lib/Runtime/Library/PropertyString.cpp +++ b/lib/Runtime/Library/PropertyString.cpp @@ -9,71 +9,82 @@ namespace Js DEFINE_RECYCLER_TRACKER_PERF_COUNTER(PropertyString); DEFINE_RECYCLER_TRACKER_WEAKREF_PERF_COUNTER(PropertyString); - PropertyString::PropertyString(StaticType* type, const Js::PropertyRecord* propertyRecord) - : JavascriptString(type, propertyRecord->GetLength(), propertyRecord->GetBuffer()), m_propertyRecord(propertyRecord) + PropertyString::PropertyString(StaticType* type, const Js::PropertyRecord* propertyRecord) : + JavascriptString(type, propertyRecord->GetLength(), propertyRecord->GetBuffer()), + propertyRecord(propertyRecord), + ldElemInlineCache(nullptr), + stElemInlineCache(nullptr), + hitRate(0) { } - PropertyString* PropertyString::New(StaticType* type, const Js::PropertyRecord* propertyRecord, ArenaAllocator *arena) + PropertyString* PropertyString::New(StaticType* type, const Js::PropertyRecord* propertyRecord, Recycler *recycler) { - PropertyString * propertyString = (PropertyString *)Anew(arena, ArenaAllocPropertyString, type, propertyRecord); - propertyString->propCache = AllocatorNewStructZ(InlineCacheAllocator, type->GetScriptContext()->GetInlineCacheAllocator(), PropertyCache); + PropertyString * propertyString = RecyclerNewZ(recycler, PropertyString, type, propertyRecord); + // TODO: in future, might be worth putting these inline to avoid extra allocations. PIC copy API needs to be updated to support this though + propertyString->ldElemInlineCache = ScriptContextPolymorphicInlineCache::New(MinPropertyStringInlineCacheSize, type->GetLibrary()); + propertyString->stElemInlineCache = ScriptContextPolymorphicInlineCache::New(MinPropertyStringInlineCacheSize, type->GetLibrary()); return propertyString; } + PolymorphicInlineCache * PropertyString::GetLdElemInlineCache() const + { + return this->ldElemInlineCache; + } - PropertyString* PropertyString::New(StaticType* type, const Js::PropertyRecord* propertyRecord, Recycler *recycler) + PolymorphicInlineCache * PropertyString::GetStElemInlineCache() const { - PropertyString * propertyString = RecyclerNewPlusZ(recycler, sizeof(PropertyCache), PropertyString, type, propertyRecord); - propertyString->propCache = (PropertyCache*)(propertyString + 1); - return propertyString; + return this->stElemInlineCache; } - PropertyCache const * PropertyString::GetPropertyCache() const + void const * PropertyString::GetOriginalStringReference() { - Assert(!propCache->type || propCache->type->GetScriptContext() == this->GetScriptContext()); - return propCache; + // Property record is the allocation containing the string buffer + return this->propertyRecord; } - void PropertyString::ClearPropertyCache() + bool PropertyString::ShouldUseCache() const { - this->propCache->type = nullptr; + return this->hitRate > (int)CONFIG_FLAG(StringCacheMissThreshold); } - void const * PropertyString::GetOriginalStringReference() + + void PropertyString::LogCacheMiss() { - // Property record is the allocation containing the string buffer - return this->m_propertyRecord; + this->hitRate -= (int)CONFIG_FLAG(StringCacheMissPenalty); + if (this->hitRate < (int)CONFIG_FLAG(StringCacheMissReset)) + { + this->hitRate = 0; + } } RecyclableObject * PropertyString::CloneToScriptContext(ScriptContext* requestContext) { - return requestContext->GetLibrary()->CreatePropertyString(this->m_propertyRecord); + return requestContext->GetLibrary()->CreatePropertyString(this->propertyRecord); } - void PropertyString::UpdateCache(Type * type, uint16 dataSlotIndex, bool isInlineSlot, bool isStoreFieldEnabled) + PolymorphicInlineCache * PropertyString::CreateBiggerPolymorphicInlineCache(bool isLdElem) { - Assert(type); - - if (type->GetScriptContext() != this->GetScriptContext()) + PolymorphicInlineCache * polymorphicInlineCache = isLdElem ? GetLdElemInlineCache() : GetStElemInlineCache(); + Assert(polymorphicInlineCache && polymorphicInlineCache->CanAllocateBigger()); + uint16 polymorphicInlineCacheSize = polymorphicInlineCache->GetSize(); + uint16 newPolymorphicInlineCacheSize = PolymorphicInlineCache::GetNextSize(polymorphicInlineCacheSize); + Assert(newPolymorphicInlineCacheSize > polymorphicInlineCacheSize); + PolymorphicInlineCache * newPolymorphicInlineCache = ScriptContextPolymorphicInlineCache::New(newPolymorphicInlineCacheSize, GetLibrary()); + polymorphicInlineCache->CopyTo(this->propertyRecord->GetPropertyId(), GetScriptContext(), newPolymorphicInlineCache); + if (isLdElem) { - return; + this->ldElemInlineCache = newPolymorphicInlineCache; } - - if (this->IsArenaAllocPropertyString()) + else { - this->GetScriptContext()->SetHasUsedInlineCache(true); + this->stElemInlineCache = newPolymorphicInlineCache; } - - type->SetHasBeenCached(); - this->propCache->type = type; - this->propCache->preventdataSlotIndexFalseRef = 1; - this->propCache->dataSlotIndex = dataSlotIndex; - this->propCache->preventFlagsFalseRef = 1; - this->propCache->isInlineSlot = isInlineSlot; - this->propCache->isStoreFieldEnabled = isStoreFieldEnabled; +#ifdef ENABLE_DEBUG_CONFIG_OPTIONS + if (PHASE_VERBOSE_TRACE1(Js::PolymorphicInlineCachePhase) || PHASE_TRACE1(PropertyStringCachePhase)) + { + Output::Print(_u("PropertyString '%s' : Bigger PIC, oldSize = %d, newSize = %d\n"), GetString(), polymorphicInlineCacheSize, newPolymorphicInlineCacheSize); + } +#endif + return newPolymorphicInlineCache; } - - ArenaAllocPropertyString::ArenaAllocPropertyString(StaticType* type, const Js::PropertyRecord* propertyRecord) - :PropertyString(type, propertyRecord) - {} } diff --git a/lib/Runtime/Library/PropertyString.h b/lib/Runtime/Library/PropertyString.h index d44d2bcba96..e03f930cebe 100644 --- a/lib/Runtime/Library/PropertyString.h +++ b/lib/Runtime/Library/PropertyString.h @@ -6,61 +6,52 @@ namespace Js { - struct PropertyCache - { - Field(Type*) type; - union - { - struct - { - uint16 preventdataSlotIndexFalseRef; - uint16 dataSlotIndex; - }; - intptr_t ptrSlot1; - }; - union - { - struct - { - uint16 preventFlagsFalseRef; - bool isInlineSlot; - bool isStoreFieldEnabled; - }; - intptr_t ptrSlot2; - }; - intptr_t blank; - }; - - CompileAssert(sizeof(PropertyCache) == sizeof(InlineCacheAllocator::CacheLayout)); - CompileAssert(offsetof(PropertyCache, blank) == offsetof(InlineCacheAllocator::CacheLayout, strongRef)); - class PropertyString : public JavascriptString { protected: - Field(PropertyCache*) propCache; - Field(const Js::PropertyRecord*) m_propertyRecord; + Field(int) hitRate; + Field(PolymorphicInlineCache*) ldElemInlineCache; + Field(PolymorphicInlineCache*) stElemInlineCache; + Field(const Js::PropertyRecord*) propertyRecord; + DEFINE_VTABLE_CTOR(PropertyString, JavascriptString); DECLARE_CONCRETE_STRING_CLASS; PropertyString(StaticType* type, const Js::PropertyRecord* propertyRecord); public: - PropertyCache const * GetPropertyCache() const; - void ClearPropertyCache(); - Js::PropertyRecord const * GetPropertyRecord() const { return m_propertyRecord; } + PolymorphicInlineCache * GetLdElemInlineCache() const; + PolymorphicInlineCache * GetStElemInlineCache() const; + Js::PropertyRecord const * GetPropertyRecord() const { return this->propertyRecord; } + PolymorphicInlineCache * CreateBiggerPolymorphicInlineCache(bool isLdElem); + void LogCacheMiss(); + int GetHitRate() const { return this->hitRate; }; + void LogCacheHit() { ++this->hitRate; }; + bool ShouldUseCache() const; + static PropertyString* New(StaticType* type, const Js::PropertyRecord* propertyRecord, Recycler *recycler); - static PropertyString* New(StaticType* type, const Js::PropertyRecord* propertyRecord, ArenaAllocator *arena); - void UpdateCache(Type * type, uint16 dataSlotIndex, bool isInlineSlot, bool isStoreFieldEnabled); - void ClearCache() { propCache->type = nullptr; } virtual void const * GetOriginalStringReference() override; virtual RecyclableObject * CloneToScriptContext(ScriptContext* requestContext) override; - virtual bool IsArenaAllocPropertyString() { return false; } - static uint32 GetOffsetOfPropertyCache() { return offsetof(PropertyString, propCache); } + static uint32 GetOffsetOfLdElemInlineCache() { return offsetof(PropertyString, ldElemInlineCache); } + static uint32 GetOffsetOfStElemInlineCache() { return offsetof(PropertyString, stElemInlineCache); } + static uint32 GetOffsetOfHitRate() { return offsetof(PropertyString, hitRate); } +#if ENABLE_DEBUG_CONFIG_OPTIONS + void DumpCache(bool ldElemCache) + { + PolymorphicInlineCache * cache = ldElemCache ? GetLdElemInlineCache() : GetStElemInlineCache(); + Output::Print(_u("PropertyCache HitRate: %i; types: "), this->hitRate); + for (uint i = 0; i < cache->GetSize(); ++i) + { + Output::Print(_u("%p,"), cache->GetInlineCaches()[i].GetType()); + } + Output::Print(_u("\n")); + } +#endif #if ENABLE_TTD //Get the associated property id for this string if there is on (e.g. it is a propertystring otherwise return Js::PropertyIds::_none) - virtual Js::PropertyId TryGetAssociatedPropertyId() const override { return this->m_propertyRecord->GetPropertyId(); } + virtual Js::PropertyId TryGetAssociatedPropertyId() const override { return this->propertyRecord->GetPropertyId(); } #endif public: @@ -69,13 +60,4 @@ namespace Js return VTableValue::VtablePropertyString; } }; - - class ArenaAllocPropertyString sealed : public PropertyString - { - friend PropertyString; - protected: - ArenaAllocPropertyString(StaticType* type, const Js::PropertyRecord* propertyRecord); - public: - virtual bool IsArenaAllocPropertyString() override { return true; } - }; } diff --git a/lib/Runtime/Library/RuntimeLibraryPch.h b/lib/Runtime/Library/RuntimeLibraryPch.h index 053bd0a23a7..90c31b371af 100644 --- a/lib/Runtime/Library/RuntimeLibraryPch.h +++ b/lib/Runtime/Library/RuntimeLibraryPch.h @@ -87,7 +87,7 @@ #include "Library/WebAssemblyInstance.h" #include "Language/JavascriptStackWalker.h" - +#include "Language/CacheOperators.h" // .inl files #include "Library/JavascriptString.inl" #include "Library/ConcatString.inl" diff --git a/lib/Runtime/Runtime.h b/lib/Runtime/Runtime.h index 3ba7fb6e0c0..115a6d2e70f 100644 --- a/lib/Runtime/Runtime.h +++ b/lib/Runtime/Runtime.h @@ -75,7 +75,7 @@ namespace Js struct CallInfo; struct InlineeCallInfo; struct InlineCache; - struct PolymorphicInlineCache; + class PolymorphicInlineCache; struct Arguments; class StringDictionaryWrapper; struct ByteCodeDumper; diff --git a/lib/Runtime/Types/DeferredTypeHandler.h b/lib/Runtime/Types/DeferredTypeHandler.h index 0e35354651b..9a7f4b521b3 100644 --- a/lib/Runtime/Types/DeferredTypeHandler.h +++ b/lib/Runtime/Types/DeferredTypeHandler.h @@ -85,7 +85,7 @@ namespace Js virtual PropertyId GetPropertyId(ScriptContext* scriptContext, PropertyIndex index) override; virtual PropertyId GetPropertyId(ScriptContext* scriptContext, BigPropertyIndex index) override; virtual BOOL FindNextProperty(ScriptContext* scriptContext, PropertyIndex& index, JavascriptString** propertyString, - PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags) override; + PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags, DynamicObject* instance, PropertyValueInfo* info) override; virtual PropertyIndex GetPropertyIndex(PropertyRecord const* propertyRecord) override; virtual bool GetPropertyEquivalenceInfo(PropertyRecord const* propertyRecord, PropertyEquivalenceInfo& info) override; virtual bool IsObjTypeSpecEquivalent(const Type* type, const TypeEquivalenceRecord& record, uint& failedPropertyIndex) override; @@ -164,7 +164,7 @@ namespace Js template BOOL DeferredTypeHandler::FindNextProperty(ScriptContext* scriptContext, PropertyIndex& index, - __out JavascriptString** propertyString, __out PropertyId* propertyId, __out_opt PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags) + __out JavascriptString** propertyString, __out PropertyId* propertyId, __out_opt PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags, DynamicObject* instance, PropertyValueInfo* info) { Assert(false); return FALSE; diff --git a/lib/Runtime/Types/DictionaryTypeHandler.cpp b/lib/Runtime/Types/DictionaryTypeHandler.cpp index a57cde803c5..227ded452b7 100644 --- a/lib/Runtime/Types/DictionaryTypeHandler.cpp +++ b/lib/Runtime/Types/DictionaryTypeHandler.cpp @@ -96,7 +96,7 @@ namespace Js template BOOL DictionaryTypeHandlerBase::FindNextProperty(ScriptContext* scriptContext, PropertyIndex& index, JavascriptString** propertyStringName, - PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags) + PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags, DynamicObject* instance, PropertyValueInfo* info) { Assert(propertyStringName); Assert(propertyId); @@ -130,30 +130,33 @@ namespace Js T dataSlot = descriptor.template GetDataPropertyIndex(); if (dataSlot != NoSlots && (attribs & PropertyWritable)) { - uint16 inlineOrAuxSlotIndex; - bool isInlineSlot; - PropertyIndexToInlineOrAuxSlotIndex(dataSlot, &inlineOrAuxSlotIndex, &isInlineSlot); - - propertyString->UpdateCache(type, inlineOrAuxSlotIndex, isInlineSlot, descriptor.IsInitialized && !descriptor.IsFixed); + PropertyValueInfo::SetCacheInfo(info, propertyString, propertyString->GetLdElemInlineCache(), false); + SetPropertyValueInfo(info, instance, dataSlot, descriptor.Attributes); + if (!descriptor.IsInitialized || descriptor.IsFixed) + { + PropertyValueInfo::DisableStoreFieldCache(info); + } + if (descriptor.Attributes & PropertyDeleted) + { + // letconst shadowing a deleted property. don't bother to cache + PropertyValueInfo::SetNoCache(info, instance); + } } else { -#ifdef DEBUG - PropertyCache const* cache = propertyString->GetPropertyCache(); - Assert(!cache || cache->type != type); -#endif + PropertyValueInfo::SetNoCache(info, instance); } - return TRUE; } } + PropertyValueInfo::SetNoCache(info, instance); return FALSE; } template <> BOOL DictionaryTypeHandlerBase::FindNextProperty(ScriptContext* scriptContext, PropertyIndex& index, JavascriptString** propertyString, - PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags) + PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags, DynamicObject* instance, PropertyValueInfo* info) { Assert(false); Throw::InternalError(); @@ -161,18 +164,18 @@ namespace Js template BOOL DictionaryTypeHandlerBase::FindNextProperty(ScriptContext* scriptContext, BigPropertyIndex& index, JavascriptString** propertyString, - PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags) + PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags, DynamicObject* instance, PropertyValueInfo* info) { PropertyIndex local = (PropertyIndex)index; Assert(index <= Constants::UShortMaxValue || index == Constants::NoBigSlot); - BOOL result = this->FindNextProperty(scriptContext, local, propertyString, propertyId, attributes, type, typeToEnumerate, flags); + BOOL result = this->FindNextProperty(scriptContext, local, propertyString, propertyId, attributes, type, typeToEnumerate, flags, instance, info); index = local; return result; } template <> BOOL DictionaryTypeHandlerBase::FindNextProperty(ScriptContext* scriptContext, BigPropertyIndex& index, JavascriptString** propertyStringName, - PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags) + PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags, DynamicObject* instance, PropertyValueInfo* info) { Assert(propertyStringName); Assert(propertyId); diff --git a/lib/Runtime/Types/DictionaryTypeHandler.h b/lib/Runtime/Types/DictionaryTypeHandler.h index 4341f8ccd91..2e4974c6f99 100644 --- a/lib/Runtime/Types/DictionaryTypeHandler.h +++ b/lib/Runtime/Types/DictionaryTypeHandler.h @@ -73,9 +73,9 @@ namespace Js virtual PropertyId GetPropertyId(ScriptContext* scriptContext, BigPropertyIndex index) override; virtual BOOL FindNextProperty(ScriptContext* scriptContext, PropertyIndex& index, JavascriptString** propertyString, - PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags) override; + PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags, DynamicObject* instance, PropertyValueInfo* info) override; virtual BOOL FindNextProperty(ScriptContext* scriptContext, BigPropertyIndex& index, JavascriptString** propertyString, - PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags) override; + PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags, DynamicObject* instance, PropertyValueInfo* info) override; virtual PropertyIndex GetPropertyIndex(PropertyRecord const* propertyRecord) override; virtual bool GetPropertyEquivalenceInfo(PropertyRecord const* propertyRecord, PropertyEquivalenceInfo& info) override; diff --git a/lib/Runtime/Types/DynamicObject.cpp b/lib/Runtime/Types/DynamicObject.cpp index 5ec67b9249a..9f16a4970fa 100644 --- a/lib/Runtime/Types/DynamicObject.cpp +++ b/lib/Runtime/Types/DynamicObject.cpp @@ -395,7 +395,7 @@ namespace Js BOOL DynamicObject::FindNextProperty(BigPropertyIndex& index, JavascriptString** propertyString, PropertyId* propertyId, PropertyAttributes* attributes, - DynamicType *typeToEnumerate, EnumeratorFlags flags, ScriptContext * requestContext) const + DynamicType *typeToEnumerate, EnumeratorFlags flags, ScriptContext * requestContext, PropertyValueInfo * info) { if(index == Constants::NoBigSlot) { @@ -418,7 +418,7 @@ namespace Js } else if(this->GetScriptContext()->ShouldPerformRecordAction()) { - BOOL res = this->GetTypeHandler()->FindNextProperty(requestContext, index, propertyString, propertyId, attributes, this->GetType(), typeToEnumerate, flags); + BOOL res = this->GetTypeHandler()->FindNextProperty(requestContext, index, propertyString, propertyId, attributes, this->GetType(), typeToEnumerate, flags, this, info); PropertyAttributes tmpAttributes = (attributes != nullptr) ? *attributes : PropertyNone; this->GetScriptContext()->GetThreadContext()->TTDLog->RecordPropertyEnumEvent(res, *propertyId, tmpAttributes, *propertyString); @@ -426,10 +426,10 @@ namespace Js } else { - return this->GetTypeHandler()->FindNextProperty(requestContext, index, propertyString, propertyId, attributes, this->GetType(), typeToEnumerate, flags); + return this->GetTypeHandler()->FindNextProperty(requestContext, index, propertyString, propertyId, attributes, this->GetType(), typeToEnumerate, flags, this, info); } #else - return this->GetTypeHandler()->FindNextProperty(requestContext, index, propertyString, propertyId, attributes, this->GetType(), typeToEnumerate, flags); + return this->GetTypeHandler()->FindNextProperty(requestContext, index, propertyString, propertyId, attributes, this->GetType(), typeToEnumerate, flags, this, info); #endif } diff --git a/lib/Runtime/Types/DynamicObject.h b/lib/Runtime/Types/DynamicObject.h index 70fbdc3ebaf..8b15dd1ca2c 100644 --- a/lib/Runtime/Types/DynamicObject.h +++ b/lib/Runtime/Types/DynamicObject.h @@ -284,7 +284,7 @@ namespace Js void ChangeTypeIf(const Type* oldType); BOOL FindNextProperty(BigPropertyIndex& index, JavascriptString** propertyString, PropertyId* propertyId, PropertyAttributes* attributes, - DynamicType *typeToEnumerate, EnumeratorFlags flags, ScriptContext * requestContext) const; + DynamicType *typeToEnumerate, EnumeratorFlags flags, ScriptContext * requestContext, PropertyValueInfo * info); virtual BOOL HasDeferredTypeHandler() const sealed; static DWORD GetOffsetOfAuxSlots(); diff --git a/lib/Runtime/Types/DynamicObjectPropertyEnumerator.cpp b/lib/Runtime/Types/DynamicObjectPropertyEnumerator.cpp index 1b44ca77ede..12b46283d16 100644 --- a/lib/Runtime/Types/DynamicObjectPropertyEnumerator.cpp +++ b/lib/Runtime/Types/DynamicObjectPropertyEnumerator.cpp @@ -234,11 +234,14 @@ namespace Js JavascriptString* propertyString = nullptr; BigPropertyIndex newIndex = this->objectIndex; + PropertyValueInfo info; + RecyclableObject * startingObject = this->object; do { newIndex++; - if (!object->FindNextProperty(newIndex, &propertyString, &propertyId, attributes, - GetTypeToEnumerate(), flags, this->scriptContext) + PropertyValueInfo::ClearCacheInfo(&info); + if (!this->object->FindNextProperty(newIndex, &propertyString, &propertyId, attributes, + GetTypeToEnumerate(), flags, this->scriptContext, &info) || (GetSnapShotSemantics() && newIndex >= initialPropertyCount)) { // No more properties @@ -248,6 +251,15 @@ namespace Js } } while (Js::IsInternalPropertyId(propertyId)); + if (info.GetPropertyString() != nullptr && info.GetPropertyString()->ShouldUseCache()) + { + CacheOperators::CachePropertyRead(startingObject, this->object, false, propertyId, false, &info, scriptContext); + if (info.IsStoreFieldCacheEnabled() && info.IsWritable() && ((info.GetFlags() & (InlineCacheGetterFlag | InlineCacheSetterFlag)) == 0)) + { + PropertyValueInfo::SetCacheInfo(&info, info.GetPropertyString(), info.GetPropertyString()->GetStElemInlineCache(), info.AllowResizingPolymorphicInlineCache()); + CacheOperators::CachePropertyWrite(this->object, false, this->object->GetType(), propertyId, &info, scriptContext); + } + } this->objectIndex = newIndex; return propertyString; } diff --git a/lib/Runtime/Types/DynamicType.cpp b/lib/Runtime/Types/DynamicType.cpp index 6c69a71b717..2c50882a393 100644 --- a/lib/Runtime/Types/DynamicType.cpp +++ b/lib/Runtime/Types/DynamicType.cpp @@ -56,7 +56,8 @@ namespace Js PropertyIndex propertyIndex = (PropertyIndex)-1; JavascriptString* propertyString = nullptr; PropertyId propertyId = Constants::NoProperty; - Assert(!this->GetTypeHandler()->FindNextProperty(this->GetScriptContext(), propertyIndex, &propertyString, &propertyId, nullptr, this, this, EnumeratorFlags::None)); + PropertyValueInfo info; + Assert(!this->GetTypeHandler()->FindNextProperty(this->GetScriptContext(), propertyIndex, &propertyString, &propertyId, nullptr, this, this, EnumeratorFlags::None, nullptr, &info)); #endif this->hasNoEnumerableProperties = true; diff --git a/lib/Runtime/Types/MissingPropertyTypeHandler.cpp b/lib/Runtime/Types/MissingPropertyTypeHandler.cpp index f85b92b4e37..75d59b1d0bb 100644 --- a/lib/Runtime/Types/MissingPropertyTypeHandler.cpp +++ b/lib/Runtime/Types/MissingPropertyTypeHandler.cpp @@ -26,7 +26,7 @@ namespace Js } BOOL MissingPropertyTypeHandler::FindNextProperty(ScriptContext* scriptContext, PropertyIndex& index, JavascriptString** propertyStringName, - PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags) + PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags, DynamicObject* instance, PropertyValueInfo* info) { return FALSE; } diff --git a/lib/Runtime/Types/MissingPropertyTypeHandler.h b/lib/Runtime/Types/MissingPropertyTypeHandler.h index e97ca9a9592..0bba5f69174 100644 --- a/lib/Runtime/Types/MissingPropertyTypeHandler.h +++ b/lib/Runtime/Types/MissingPropertyTypeHandler.h @@ -24,7 +24,7 @@ namespace Js virtual PropertyId GetPropertyId(ScriptContext* scriptContext, PropertyIndex index) override; virtual PropertyId GetPropertyId(ScriptContext* scriptContext, BigPropertyIndex index) override; virtual BOOL FindNextProperty(ScriptContext* scriptContext, PropertyIndex& index, JavascriptString** propertyString, - PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags) override; + PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags, DynamicObject* instance, PropertyValueInfo* info) override; virtual PropertyIndex GetPropertyIndex(PropertyRecord const* propertyRecord) override; virtual bool GetPropertyEquivalenceInfo(PropertyRecord const* propertyRecord, PropertyEquivalenceInfo& info) override; virtual bool IsObjTypeSpecEquivalent(const Type* type, const TypeEquivalenceRecord& record, uint& failedPropertyIndex) override; diff --git a/lib/Runtime/Types/NullTypeHandler.cpp b/lib/Runtime/Types/NullTypeHandler.cpp index 6a79e747ffe..89c6aa24539 100644 --- a/lib/Runtime/Types/NullTypeHandler.cpp +++ b/lib/Runtime/Types/NullTypeHandler.cpp @@ -27,7 +27,7 @@ namespace Js BOOL NullTypeHandlerBase::FindNextProperty(ScriptContext* scriptContext, PropertyIndex& index, JavascriptString** propertyString, PropertyId* propertyId, - PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags) + PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags, DynamicObject* instance, PropertyValueInfo* info) { Assert(propertyString); Assert(propertyId); diff --git a/lib/Runtime/Types/NullTypeHandler.h b/lib/Runtime/Types/NullTypeHandler.h index 5b648ea4b18..a80becb4008 100644 --- a/lib/Runtime/Types/NullTypeHandler.h +++ b/lib/Runtime/Types/NullTypeHandler.h @@ -28,7 +28,7 @@ namespace Js virtual PropertyId GetPropertyId(ScriptContext* scriptContext, PropertyIndex index) override; virtual PropertyId GetPropertyId(ScriptContext* scriptContext, BigPropertyIndex index) override; virtual BOOL FindNextProperty(ScriptContext* scriptContext, PropertyIndex& index, JavascriptString** propertyString, - PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags) override; + PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags, DynamicObject* instance, PropertyValueInfo* info) override; virtual PropertyIndex GetPropertyIndex(PropertyRecord const* propertyRecord) override; virtual bool GetPropertyEquivalenceInfo(PropertyRecord const* propertyRecord, PropertyEquivalenceInfo& info) override; virtual bool IsObjTypeSpecEquivalent(const Type* type, const TypeEquivalenceRecord& record, uint& failedPropertyIndex) override; diff --git a/lib/Runtime/Types/PathTypeHandler.cpp b/lib/Runtime/Types/PathTypeHandler.cpp index a1d241fa77a..bcabb3ad912 100644 --- a/lib/Runtime/Types/PathTypeHandler.cpp +++ b/lib/Runtime/Types/PathTypeHandler.cpp @@ -47,7 +47,7 @@ namespace Js } BOOL PathTypeHandlerBase::FindNextProperty(ScriptContext* scriptContext, PropertyIndex& index, JavascriptString** propertyStringName, PropertyId* propertyId, - PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags) + PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags, DynamicObject* instance, PropertyValueInfo* info) { Assert(propertyStringName); Assert(propertyId); @@ -74,16 +74,15 @@ namespace Js PropertyString* propertyString = scriptContext->GetPropertyString(*propertyId); *propertyStringName = propertyString; - uint16 inlineOrAuxSlotIndex; - bool isInlineSlot; - PropertyIndexToInlineOrAuxSlotIndex(index, &inlineOrAuxSlotIndex, &isInlineSlot); - - propertyString->UpdateCache(type, inlineOrAuxSlotIndex, isInlineSlot, - !FixPropsOnPathTypes() || (this->GetPathLength() < this->typePath->GetMaxInitializedLength() && !this->typePath->GetIsFixedFieldAt(index, this->GetPathLength()))); - + PropertyValueInfo::SetCacheInfo(info, propertyString, propertyString->GetLdElemInlineCache(), false); + PropertyValueInfo::Set(info, instance, index); + if (FixPropsOnPathTypes() && (index >= this->typePath->GetMaxInitializedLength() || this->typePath->GetIsFixedFieldAt(index, GetPathLength()))) + { + PropertyValueInfo::DisableStoreFieldCache(info); + } return TRUE; } - + PropertyValueInfo::SetNoCache(info, instance); return FALSE; } @@ -99,13 +98,15 @@ namespace Js PathTypeHandlerBase* pathTypeToEnumerate = (PathTypeHandlerBase*)typeHandlerToEnumerate; - BOOL found = pathTypeToEnumerate->FindNextProperty(scriptContext, index, propertyStringName, propertyId, attributes, typeToEnumerate, typeToEnumerate, flags); + BOOL found = pathTypeToEnumerate->FindNextProperty(scriptContext, index, propertyStringName, propertyId, attributes, typeToEnumerate, typeToEnumerate, flags, instance, info); // We got a property from previous type, but this property may have been deleted if (found == TRUE && this->GetPropertyIndex(*propertyId) == Js::Constants::NoSlot) { + PropertyValueInfo::SetNoCache(info, instance); return FALSE; } + PropertyValueInfo::SetNoCache(info, instance); return found; } diff --git a/lib/Runtime/Types/PathTypeHandler.h b/lib/Runtime/Types/PathTypeHandler.h index 73fe45ff57d..04f37aa4d0d 100644 --- a/lib/Runtime/Types/PathTypeHandler.h +++ b/lib/Runtime/Types/PathTypeHandler.h @@ -45,7 +45,7 @@ namespace Js virtual bool IsObjTypeSpecEquivalent(const Type* type, const TypeEquivalenceRecord& record, uint& failedPropertyIndex) override; virtual bool IsObjTypeSpecEquivalent(const Type* type, const EquivalentPropertyEntry* entry) override; virtual BOOL FindNextProperty(ScriptContext* scriptContext, PropertyIndex& index, JavascriptString** propertyString, - PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags) override; + PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags, DynamicObject* instance, PropertyValueInfo* info) override; virtual BOOL HasProperty(DynamicObject* instance, PropertyId propertyId, __out_opt bool *noRedecl = nullptr) override; virtual BOOL HasProperty(DynamicObject* instance, JavascriptString* propertyNameString) override; virtual BOOL GetProperty(DynamicObject* instance, Var originalInstance, PropertyId propertyId, Var* value, PropertyValueInfo* info, ScriptContext* requestContext) override; diff --git a/lib/Runtime/Types/RecyclableObject.cpp b/lib/Runtime/Types/RecyclableObject.cpp index 6fd8981f1db..e557d718f62 100644 --- a/lib/Runtime/Types/RecyclableObject.cpp +++ b/lib/Runtime/Types/RecyclableObject.cpp @@ -22,6 +22,23 @@ namespace Js info->allowResizingPolymorphicInlineCache = false; } + void PropertyValueInfo::SetCacheInfo( + PropertyValueInfo* info, + PropertyString *const propertyString, + PolymorphicInlineCache *const polymorphicInlineCache, + bool allowResizing) + { + Assert(info); + Assert(polymorphicInlineCache); + + info->functionBody = nullptr; + info->propertyString = propertyString; + info->inlineCache = nullptr; + info->polymorphicInlineCache = polymorphicInlineCache; + info->inlineCacheIndex = Js::Constants::NoInlineCacheIndex; + info->allowResizingPolymorphicInlineCache = allowResizing; + } + void PropertyValueInfo::SetCacheInfo( PropertyValueInfo* info, FunctionBody *const functionBody, diff --git a/lib/Runtime/Types/RecyclableObject.h b/lib/Runtime/Types/RecyclableObject.h index 3da02d0960b..89013da568f 100644 --- a/lib/Runtime/Types/RecyclableObject.h +++ b/lib/Runtime/Types/RecyclableObject.h @@ -24,9 +24,11 @@ namespace Js { InlineCacheFlags flags; CacheInfoFlag cacheInfoFlag; InlineCache* inlineCache; - PolymorphicInlineCache* polymorphicInlineCache; + PolymorphicInlineCache * polymorphicInlineCache; FunctionBody * functionBody; + PropertyString * propertyString; uint inlineCacheIndex; + bool isFunctionPIC; bool allowResizingPolymorphicInlineCache; void Set(RecyclableObject* instance, PropertyIndex propertyIndex, PropertyAttributes attributes, InlineCacheFlags flags) @@ -44,9 +46,7 @@ namespace Js { public: PropertyValueInfo() : m_instance(NULL), m_propertyIndex(Constants::NoSlot), m_attributes(PropertyNone), flags(InlineCacheNoFlags), - cacheInfoFlag(CacheInfoFlag::defaultInfoFlags), inlineCache(NULL), polymorphicInlineCache(NULL), functionBody(NULL), - inlineCacheIndex(Constants::NoInlineCacheIndex), - allowResizingPolymorphicInlineCache(true) + cacheInfoFlag(CacheInfoFlag::defaultInfoFlags), inlineCache(nullptr), polymorphicInlineCache(nullptr), propertyString(nullptr), functionBody(nullptr), inlineCacheIndex(Constants::NoInlineCacheIndex), allowResizingPolymorphicInlineCache(true) { } @@ -72,24 +72,30 @@ namespace Js { static void SetCacheInfo(PropertyValueInfo* info, InlineCache *const inlineCache); static void SetCacheInfo(PropertyValueInfo* info, FunctionBody *const functionBody, InlineCache *const inlineCache, const InlineCacheIndex inlineCacheIndex, const bool allowResizingPolymorphicInlineCache); static void SetCacheInfo(PropertyValueInfo* info, FunctionBody *const functionBody, PolymorphicInlineCache *const polymorphicInlineCache, const InlineCacheIndex inlineCacheIndex, const bool allowResizingPolymorphicInlineCache); + static void SetCacheInfo(PropertyValueInfo* info, PropertyString *const propertyString, PolymorphicInlineCache *const polymorphicInlineCache, bool allowResizing); static void ClearCacheInfo(PropertyValueInfo* info); - inline InlineCache * GetInlineCache() const + InlineCache * GetInlineCache() const { return this->inlineCache; } - inline PolymorphicInlineCache * GetPolymorphicInlineCache() const + PolymorphicInlineCache * GetPolymorphicInlineCache() const { return this->polymorphicInlineCache; } - inline FunctionBody * GetFunctionBody() const + FunctionBody * GetFunctionBody() const { return this->functionBody; } - inline uint GetInlineCacheIndex() const + PropertyString * GetPropertyString() const + { + return this->propertyString; + } + + uint GetInlineCacheIndex() const { return this->inlineCacheIndex; } diff --git a/lib/Runtime/Types/SimpleDictionaryTypeHandler.cpp b/lib/Runtime/Types/SimpleDictionaryTypeHandler.cpp index a37b1fc9f87..5ccedb20298 100644 --- a/lib/Runtime/Types/SimpleDictionaryTypeHandler.cpp +++ b/lib/Runtime/Types/SimpleDictionaryTypeHandler.cpp @@ -608,7 +608,7 @@ namespace Js template BOOL SimpleDictionaryTypeHandlerBase::FindNextProperty(ScriptContext* scriptContext, PropertyIndex& index, JavascriptString** propertyStringName, - PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags) + PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags, DynamicObject* instance, PropertyValueInfo* info) { Assert(propertyStringName); Assert(propertyId); @@ -640,23 +640,22 @@ namespace Js *propertyStringName = propertyString; if (descriptor.Attributes & PropertyWritable) { - uint16 inlineOrAuxSlotIndex; - bool isInlineSlot; - PropertyIndexToInlineOrAuxSlotIndex(descriptor.propertyIndex, &inlineOrAuxSlotIndex, &isInlineSlot); + PropertyValueInfo::SetCacheInfo(info, propertyString, propertyString->GetLdElemInlineCache(), false); + SetPropertyValueInfo(info, instance, descriptor.propertyIndex, descriptor.Attributes); - propertyString->UpdateCache(type, inlineOrAuxSlotIndex, isInlineSlot, descriptor.isInitialized && !descriptor.isFixed); + if (descriptor.isFixed) + { + PropertyValueInfo::DisableStoreFieldCache(info); + } } else { -#ifdef DEBUG - PropertyCache const* cache = propertyString->GetPropertyCache(); - Assert(!cache || cache->type != type); -#endif + PropertyValueInfo::SetNoCache(info, instance); } - return TRUE; } } + PropertyValueInfo::SetNoCache(info, instance); return FALSE; } @@ -674,7 +673,9 @@ namespace Js attributes, typeToEnumerate, typeToEnumerate, - flags); + flags, + instance, + info); ++index) { SimpleDictionaryPropertyDescriptor descriptor; @@ -701,33 +702,11 @@ namespace Js { *attributes = descriptor.Attributes; } - - if(descriptor.Attributes & PropertyWritable) - { - uint16 inlineOrAuxSlotIndex; - bool isInlineSlot; - PropertyIndexToInlineOrAuxSlotIndex(descriptor.propertyIndex, &inlineOrAuxSlotIndex, &isInlineSlot); - if (VirtualTableInfo::HasVirtualTable(*propertyStringName)) - { - PropertyString* propertyString = (PropertyString*)(*propertyStringName); - propertyString->UpdateCache(type, inlineOrAuxSlotIndex, isInlineSlot, descriptor.isInitialized && !descriptor.isFixed); - } - } - else - { -#ifdef DEBUG - if (VirtualTableInfo::HasVirtualTable(*propertyStringName)) - { - PropertyString* propertyString = (PropertyString*)(*propertyStringName); - PropertyCache const* cache = propertyString->GetPropertyCache(); - Assert(!cache || cache->type != type); - } -#endif - } - + PropertyValueInfo::SetNoCache(info, instance); return TRUE; } } + PropertyValueInfo::SetNoCache(info, instance); return FALSE; } @@ -739,7 +718,7 @@ namespace Js #define DefineUnusedSpecialization_FindNextProperty_BigPropertyIndex(T, S) \ - template <> BOOL SimpleDictionaryTypeHandlerBase::FindNextProperty(ScriptContext* scriptContext, PropertyIndex& index, JavascriptString** propertyString, PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags) { Throw::InternalError(); } + template <> BOOL SimpleDictionaryTypeHandlerBase::FindNextProperty(ScriptContext* scriptContext, PropertyIndex& index, JavascriptString** propertyString, PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags, DynamicObject* instance, PropertyValueInfo* info) { Throw::InternalError(); } DefineUnusedSpecialization_FindNextProperty_BigPropertyIndex(const PropertyRecord*, false) DefineUnusedSpecialization_FindNextProperty_BigPropertyIndex(const PropertyRecord*, true) @@ -750,18 +729,18 @@ namespace Js template BOOL SimpleDictionaryTypeHandlerBase::FindNextProperty(ScriptContext* scriptContext, BigPropertyIndex& index, JavascriptString** propertyString, - PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags) + PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags, DynamicObject* instance, PropertyValueInfo* info) { PropertyIndex local = (PropertyIndex)index; Assert(index <= Constants::UShortMaxValue || index == Constants::NoBigSlot); - BOOL result = this->FindNextProperty(scriptContext, local, propertyString, propertyId, attributes, type, typeToEnumerate, flags); + BOOL result = this->FindNextProperty(scriptContext, local, propertyString, propertyId, attributes, type, typeToEnumerate, flags, instance, info); index = local; return result; } template inline BOOL SimpleDictionaryTypeHandlerBase::FindNextProperty_BigPropertyIndex(ScriptContext* scriptContext, TPropertyIndex& index, - JavascriptString** propertyStringName, PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags) + JavascriptString** propertyStringName, PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags, DynamicObject* instance, PropertyValueInfo* info) { Assert(propertyStringName); Assert(propertyId); @@ -811,7 +790,9 @@ namespace Js attributes, typeToEnumerate, typeToEnumerate, - flags); + flags, + instance, + info); ++index) { SimpleDictionaryPropertyDescriptor descriptor; @@ -838,14 +819,6 @@ namespace Js *attributes = descriptor.Attributes; } -#ifdef DEBUG - if (VirtualTableInfo::HasVirtualTable(*propertyStringName)) - { - PropertyCache const* cache = ((PropertyString*)(*propertyStringName))->GetPropertyCache(); - Assert(!cache || cache->type != type); - } -#endif - return TRUE; } } @@ -855,30 +828,30 @@ namespace Js template <> BOOL SimpleDictionaryTypeHandlerBase::FindNextProperty(ScriptContext* scriptContext, BigPropertyIndex& index, JavascriptString** propertyString, - PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags) + PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags, DynamicObject* instance, PropertyValueInfo* info) { - return this->FindNextProperty_BigPropertyIndex(scriptContext, index, propertyString, propertyId, attributes, type, typeToEnumerate, flags); + return this->FindNextProperty_BigPropertyIndex(scriptContext, index, propertyString, propertyId, attributes, type, typeToEnumerate, flags, instance, info); } template <> BOOL SimpleDictionaryTypeHandlerBase::FindNextProperty(ScriptContext* scriptContext, BigPropertyIndex& index, JavascriptString** propertyString, - PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags) + PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags, DynamicObject* instance, PropertyValueInfo* info) { - return this->FindNextProperty_BigPropertyIndex(scriptContext, index, propertyString, propertyId, attributes, type, typeToEnumerate, flags); + return this->FindNextProperty_BigPropertyIndex(scriptContext, index, propertyString, propertyId, attributes, type, typeToEnumerate, flags, instance, info); } template <> BOOL SimpleDictionaryTypeHandlerBase::FindNextProperty(ScriptContext* scriptContext, BigPropertyIndex& index, JavascriptString** propertyString, - PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags) + PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags, DynamicObject* instance, PropertyValueInfo* info) { - return this->FindNextProperty_BigPropertyIndex(scriptContext, index, propertyString, propertyId, attributes, type, typeToEnumerate, flags); + return this->FindNextProperty_BigPropertyIndex(scriptContext, index, propertyString, propertyId, attributes, type, typeToEnumerate, flags, instance, info); } template <> BOOL SimpleDictionaryTypeHandlerBase::FindNextProperty(ScriptContext* scriptContext, BigPropertyIndex& index, JavascriptString** propertyString, - PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags) + PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags, DynamicObject* instance, PropertyValueInfo* info) { - return this->FindNextProperty_BigPropertyIndex(scriptContext, index, propertyString, propertyId, attributes, type, typeToEnumerate, flags); + return this->FindNextProperty_BigPropertyIndex(scriptContext, index, propertyString, propertyId, attributes, type, typeToEnumerate, flags, instance, info); } template diff --git a/lib/Runtime/Types/SimpleDictionaryTypeHandler.h b/lib/Runtime/Types/SimpleDictionaryTypeHandler.h index 40d8df9d747..4881d7e2e07 100644 --- a/lib/Runtime/Types/SimpleDictionaryTypeHandler.h +++ b/lib/Runtime/Types/SimpleDictionaryTypeHandler.h @@ -120,9 +120,9 @@ namespace Js virtual bool IsObjTypeSpecEquivalent(const Type* type, const EquivalentPropertyEntry* entry) override; virtual BOOL FindNextProperty(ScriptContext* scriptContext, PropertyIndex& index, JavascriptString** propertyString, - PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags) override; + PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags, DynamicObject* instance, PropertyValueInfo* info) override; virtual BOOL FindNextProperty(ScriptContext* scriptContext, BigPropertyIndex& index, JavascriptString** propertyString, - PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flagse) override; + PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags, DynamicObject* instance, PropertyValueInfo* info) override; virtual BOOL HasProperty(DynamicObject* instance, PropertyId propertyId, bool *noRedecl = nullptr) override; virtual BOOL HasProperty(DynamicObject* instance, JavascriptString* propertyNameString) override; @@ -295,7 +295,7 @@ namespace Js // This was added to work around not being able to specify partial template specialization of member function. BOOL FindNextProperty_BigPropertyIndex(ScriptContext* scriptContext, TPropertyIndex& index, JavascriptString** propertyString, - PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags); + PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags, DynamicObject* instance, PropertyValueInfo* info); template static DynamicType* InternalCreateTypeForNewScObject(ScriptContext* scriptContext, DynamicType* type, const Js::PropertyIdArray *propIds, bool shareType); diff --git a/lib/Runtime/Types/SimpleTypeHandler.cpp b/lib/Runtime/Types/SimpleTypeHandler.cpp index 8d09a2be9bd..6ce41145ad0 100644 --- a/lib/Runtime/Types/SimpleTypeHandler.cpp +++ b/lib/Runtime/Types/SimpleTypeHandler.cpp @@ -196,7 +196,7 @@ namespace Js template BOOL SimpleTypeHandler::FindNextProperty(ScriptContext* scriptContext, PropertyIndex& index, JavascriptString** propertyStringName, - PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags) + PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags, DynamicObject* instance, PropertyValueInfo* info) { Assert(propertyStringName); Assert(propertyId); @@ -221,27 +221,22 @@ namespace Js } *propertyId = propertyRecord->GetPropertyId(); - PropertyString* propertyString = scriptContext->GetPropertyString(*propertyId); - *propertyStringName = propertyString; - if (attribs & PropertyWritable) - { - uint16 inlineOrAuxSlotIndex; - bool isInlineSlot; - PropertyIndexToInlineOrAuxSlotIndex(index, &inlineOrAuxSlotIndex, &isInlineSlot); + PropertyString * propStr = scriptContext->GetPropertyString(*propertyId); + *propertyStringName = propStr; - propertyString->UpdateCache(type, inlineOrAuxSlotIndex, isInlineSlot, true); + PropertyValueInfo::SetCacheInfo(info, propStr, propStr->GetLdElemInlineCache(), false); + if ((attribs & PropertyWritable) == PropertyWritable) + { + PropertyValueInfo::Set(info, instance, index, attribs); } else { -#ifdef DEBUG - PropertyCache const* cache = propertyString->GetPropertyCache(); - Assert(!cache || cache->type != type); -#endif + PropertyValueInfo::SetNoCache(info, instance); } - return TRUE; } } + PropertyValueInfo::SetNoCache(info, instance); return FALSE; } diff --git a/lib/Runtime/Types/SimpleTypeHandler.h b/lib/Runtime/Types/SimpleTypeHandler.h index d9747eebe9d..5bc4a54882b 100644 --- a/lib/Runtime/Types/SimpleTypeHandler.h +++ b/lib/Runtime/Types/SimpleTypeHandler.h @@ -34,7 +34,7 @@ namespace Js virtual PropertyId GetPropertyId(ScriptContext* scriptContext, PropertyIndex index) override; virtual PropertyId GetPropertyId(ScriptContext* scriptContext, BigPropertyIndex index) override; virtual BOOL FindNextProperty(ScriptContext* scriptContext, PropertyIndex& index, JavascriptString** propertyString, - PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags) override; + PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags, DynamicObject* instance, PropertyValueInfo* info) override; virtual PropertyIndex GetPropertyIndex(PropertyRecord const* propertyRecord) override; virtual bool GetPropertyEquivalenceInfo(PropertyRecord const* propertyRecord, PropertyEquivalenceInfo& info) override; virtual bool IsObjTypeSpecEquivalent(const Type* type, const TypeEquivalenceRecord& record, uint& failedPropertyIndex) override; diff --git a/lib/Runtime/Types/TypeHandler.cpp b/lib/Runtime/Types/TypeHandler.cpp index 410a8a1cd06..6ac101a0116 100644 --- a/lib/Runtime/Types/TypeHandler.cpp +++ b/lib/Runtime/Types/TypeHandler.cpp @@ -250,14 +250,14 @@ namespace Js BOOL DynamicTypeHandler::FindNextProperty(ScriptContext* scriptContext, BigPropertyIndex& index, JavascriptString** propertyString, - PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags) + PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags, DynamicObject* instance, PropertyValueInfo* info) { // Type handlers that support big property indexes override this function, so if we're here then this type handler does // not support big property indexes. Forward the call to the small property index version. Assert(GetSlotCapacity() <= PropertyIndexRanges::MaxValue); PropertyIndex smallIndex = static_cast(index); Assert(static_cast(smallIndex) == index); - const BOOL found = FindNextProperty(scriptContext, smallIndex, propertyString, propertyId, attributes, type, typeToEnumerate, flags); + const BOOL found = FindNextProperty(scriptContext, smallIndex, propertyString, propertyId, attributes, type, typeToEnumerate, flags, instance, info); index = smallIndex; return found; } diff --git a/lib/Runtime/Types/TypeHandler.h b/lib/Runtime/Types/TypeHandler.h index a5e212c64c4..dd360fce30f 100644 --- a/lib/Runtime/Types/TypeHandler.h +++ b/lib/Runtime/Types/TypeHandler.h @@ -402,9 +402,9 @@ namespace Js virtual PropertyId GetPropertyId(ScriptContext* scriptContext, PropertyIndex index) = 0; virtual PropertyId GetPropertyId(ScriptContext* scriptContext, BigPropertyIndex index) = 0; virtual BOOL FindNextProperty(ScriptContext* scriptContext, PropertyIndex& index, JavascriptString** propertyString, - PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags) = 0; + PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags, DynamicObject* instance, PropertyValueInfo* info) = 0; virtual BOOL FindNextProperty(ScriptContext* scriptContext, BigPropertyIndex& index, JavascriptString** propertyString, - PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags); + PropertyId* propertyId, PropertyAttributes* attributes, Type* type, DynamicType *typeToEnumerate, EnumeratorFlags flags, DynamicObject* instance, PropertyValueInfo* info); virtual PropertyIndex GetPropertyIndex(PropertyRecord const* propertyRecord) = 0; virtual bool GetPropertyEquivalenceInfo(PropertyRecord const* propertyRecord, PropertyEquivalenceInfo& info) = 0; virtual bool IsObjTypeSpecEquivalent(const Type* type, const Js::TypeEquivalenceRecord& record, uint& failedPropertyIndex) = 0;