Răsfoiți Sursa

cache if objects have no special accessor properties

Michael Holman 8 ani în urmă
părinte
comite
a4a80968be
43 a modificat fișierele cu 899 adăugiri și 436 ștergeri
  1. 1 1
      lib/Backend/JnHelperMethodList.h
  2. 4 40
      lib/Runtime/Base/ScriptContext.cpp
  3. 4 8
      lib/Runtime/Base/ScriptContext.h
  4. 7 36
      lib/Runtime/Base/ThreadContext.cpp
  5. 5 6
      lib/Runtime/Base/ThreadContext.h
  6. 1 0
      lib/Runtime/Language/CMakeLists.txt
  7. 2 0
      lib/Runtime/Language/Chakra.Runtime.Language.vcxproj
  8. 2 0
      lib/Runtime/Language/Chakra.Runtime.Language.vcxproj.filters
  9. 31 29
      lib/Runtime/Language/JavascriptConversion.cpp
  10. 6 3
      lib/Runtime/Language/JavascriptConversion.h
  11. 12 67
      lib/Runtime/Language/JavascriptOperators.cpp
  12. 5 3
      lib/Runtime/Language/JavascriptOperators.h
  13. 361 0
      lib/Runtime/Language/PrototypeChainCache.cpp
  14. 209 0
      lib/Runtime/Language/PrototypeChainCache.h
  15. 4 4
      lib/Runtime/Library/JavascriptDate.cpp
  16. 2 2
      lib/Runtime/Library/JavascriptFunction.cpp
  17. 3 27
      lib/Runtime/Library/JavascriptLibrary.cpp
  18. 5 17
      lib/Runtime/Library/JavascriptLibrary.h
  19. 24 1
      lib/Runtime/Library/JavascriptObject.cpp
  20. 1 0
      lib/Runtime/Library/JavascriptRegularExpression.cpp
  21. 2 2
      lib/Runtime/Library/JavascriptStringObject.cpp
  22. 1 1
      lib/Runtime/Library/JavascriptSymbol.cpp
  23. 2 2
      lib/Runtime/Math/JavascriptMath.cpp
  24. 0 1
      lib/Runtime/PlatformAgnostic/Platform/Windows/UnicodeText.cpp
  25. 2 1
      lib/Runtime/Runtime.h
  26. 7 5
      lib/Runtime/RuntimeCommon.h
  27. 1 1
      lib/Runtime/Types/DeferredTypeHandler.cpp
  28. 1 1
      lib/Runtime/Types/DeferredTypeHandler.h
  29. 69 65
      lib/Runtime/Types/DictionaryTypeHandler.cpp
  30. 7 2
      lib/Runtime/Types/DictionaryTypeHandler.h
  31. 1 1
      lib/Runtime/Types/DynamicObject.cpp
  32. 16 3
      lib/Runtime/Types/DynamicType.cpp
  33. 7 7
      lib/Runtime/Types/ES5ArrayTypeHandler.cpp
  34. 2 2
      lib/Runtime/Types/NullTypeHandler.cpp
  35. 10 14
      lib/Runtime/Types/PathTypeHandler.cpp
  36. 14 2
      lib/Runtime/Types/RecyclableObject.cpp
  37. 2 0
      lib/Runtime/Types/RecyclableObject.h
  38. 10 35
      lib/Runtime/Types/SimpleDictionaryTypeHandler.cpp
  39. 15 41
      lib/Runtime/Types/SimpleTypeHandler.cpp
  40. 31 2
      lib/Runtime/Types/Type.cpp
  41. 4 1
      lib/Runtime/Types/Type.h
  42. 1 0
      lib/Runtime/Types/TypeHandler.cpp
  43. 5 3
      lib/Runtime/Types/TypeHandler.h

+ 1 - 1
lib/Backend/JnHelperMethodList.h

@@ -376,7 +376,7 @@ HELPERCALL(ProbeCurrentStack2, ThreadContext::ProbeCurrentStack2, 0)
 
 HELPERCALL(AdjustSlots, Js::DynamicTypeHandler::AdjustSlots_Jit, 0)
 HELPERCALL(InvalidateProtoCaches, Js::JavascriptOperators::OP_InvalidateProtoCaches, 0)
-HELPERCALL(CheckProtoHasNonWritable, Js::JavascriptOperators::DoCheckIfPrototypeChainHasOnlyWritableDataProperties, 0)
+HELPERCALL(CheckProtoHasNonWritable, Js::JavascriptOperators::CheckIfPrototypeChainHasOnlyWritableDataProperties, 0)
 
 HELPERCALL(GetStringForChar, (Js::JavascriptString * (*)(Js::CharStringCache *, char16))&Js::CharStringCache::GetStringForChar, 0)
 HELPERCALL(GetStringForCharCodePoint, (Js::JavascriptString * (*)(Js::CharStringCache *, codepoint_t))&Js::CharStringCache::GetStringForCharCodePoint, 0)

+ 4 - 40
lib/Runtime/Base/ScriptContext.cpp

@@ -139,7 +139,8 @@ namespace Js
         hasUsedInlineCache(false),
         hasProtoOrStoreFieldInlineCache(false),
         hasIsInstInlineCache(false),
-        registeredPrototypeChainEnsuredToHaveOnlyWritableDataPropertiesScriptContext(nullptr),
+        noSpecialPropertyRegistry(threadContext->GetNoSpecialPropertyRegistry()),
+        onlyWritablePropertyRegistry(threadContext->GetOnlyWritablePropertyRegistry()),
         firstInterpreterFrameReturnAddress(nullptr),
         builtInLibraryFunctions(nullptr),
         isWeakReferenceDictionaryListCleared(false)
@@ -792,14 +793,8 @@ namespace Js
         isWeakReferenceDictionaryListCleared = true;
         this->weakReferenceDictionaryList.Clear(this->GeneralAllocator());
 
-        if (registeredPrototypeChainEnsuredToHaveOnlyWritableDataPropertiesScriptContext != nullptr)
-        {
-            // UnregisterPrototypeChainEnsuredToHaveOnlyWritableDataPropertiesScriptContext may throw, set up the correct state first
-            ScriptContext ** registeredScriptContext = registeredPrototypeChainEnsuredToHaveOnlyWritableDataPropertiesScriptContext;
-            ClearPrototypeChainEnsuredToHaveOnlyWritableDataPropertiesCaches();
-            Assert(registeredPrototypeChainEnsuredToHaveOnlyWritableDataPropertiesScriptContext == nullptr);
-            threadContext->UnregisterPrototypeChainEnsuredToHaveOnlyWritableDataPropertiesScriptContext(registeredScriptContext);
-        }
+        this->noSpecialPropertyRegistry.Clear(true /* Unregister */);
+        this->onlyWritablePropertyRegistry.Clear(true /* Unregister */);
 
 #ifdef ENABLE_SCRIPT_DEBUGGING
         threadContext->ReleaseDebugManager();
@@ -4766,37 +4761,6 @@ ScriptContext::GetJitFuncRangeCache()
 }
 #endif
 
-void ScriptContext::RegisterPrototypeChainEnsuredToHaveOnlyWritableDataPropertiesScriptContext()
-{
-    Assert(!IsClosed());
-
-    if (registeredPrototypeChainEnsuredToHaveOnlyWritableDataPropertiesScriptContext == nullptr)
-    {
-        DoRegisterPrototypeChainEnsuredToHaveOnlyWritableDataPropertiesScriptContext();
-    }
-}
-
-    void ScriptContext::DoRegisterPrototypeChainEnsuredToHaveOnlyWritableDataPropertiesScriptContext()
-    {
-        Assert(!IsClosed());
-        Assert(registeredPrototypeChainEnsuredToHaveOnlyWritableDataPropertiesScriptContext == nullptr);
-
-        // this call may throw OOM
-        registeredPrototypeChainEnsuredToHaveOnlyWritableDataPropertiesScriptContext = threadContext->RegisterPrototypeChainEnsuredToHaveOnlyWritableDataPropertiesScriptContext(this);
-    }
-
-    void ScriptContext::ClearPrototypeChainEnsuredToHaveOnlyWritableDataPropertiesCaches()
-    {
-        Assert(registeredPrototypeChainEnsuredToHaveOnlyWritableDataPropertiesScriptContext != nullptr);
-        if (!isFinalized)
-        {
-            javascriptLibrary->NoPrototypeChainsAreEnsuredToHaveOnlyWritableDataProperties();
-        }
-
-        // Caller will unregister the script context from the thread context
-        registeredPrototypeChainEnsuredToHaveOnlyWritableDataPropertiesScriptContext = nullptr;
-    }
-
     JavascriptString * ScriptContext::GetLastNumberToStringRadix10(double value)
     {
         if (value == lastNumberToStringRadix10)

+ 4 - 8
lib/Runtime/Base/ScriptContext.h

@@ -553,12 +553,15 @@ namespace Js
         InlineCache * GetValueOfInlineCache() const { return valueOfInlineCache;}
         InlineCache * GetToStringInlineCache() const { return toStringInlineCache; }
 
+        NoSpecialPropertyScriptRegistry* GetNoSpecialPropertyRegistry() { return &this->noSpecialPropertyRegistry; }
+        OnlyWritablePropertyScriptRegistry* GetOnlyWritablePropertyRegistry() { return &this->onlyWritablePropertyRegistry; }
     private:
 
         JavascriptFunction* GenerateRootFunction(ParseNodeProg * parseTree, uint sourceIndex, Parser* parser, uint32 grfscr, CompileScriptException * pse, const char16 *rootDisplayName);
 
         typedef void (*EventHandler)(ScriptContext *);
-        ScriptContext ** registeredPrototypeChainEnsuredToHaveOnlyWritableDataPropertiesScriptContext;
+        NoSpecialPropertyScriptRegistry noSpecialPropertyRegistry;
+        OnlyWritablePropertyScriptRegistry onlyWritablePropertyRegistry;
 
         ArenaAllocator generalAllocator;
 #ifdef ENABLE_BASIC_TELEMETRY
@@ -1492,13 +1495,6 @@ private:
         void RegisterConstructorCache(Js::PropertyId propertyId, Js::ConstructorCache* cache);
 #endif
 
-    public:
-        void RegisterPrototypeChainEnsuredToHaveOnlyWritableDataPropertiesScriptContext();
-    private:
-        void DoRegisterPrototypeChainEnsuredToHaveOnlyWritableDataPropertiesScriptContext();
-    public:
-        void ClearPrototypeChainEnsuredToHaveOnlyWritableDataPropertiesCaches();
-
     public:
         JavascriptString * GetLastNumberToStringRadix10(double value);
         void SetLastNumberToStringRadix10(double value, JavascriptString * str);

+ 7 - 36
lib/Runtime/Base/ThreadContext.cpp

@@ -149,7 +149,8 @@ ThreadContext::ThreadContext(AllocationPolicyManager * allocationPolicyManager,
     isInstInlineCacheByFunction(&isInstInlineCacheThreadInfoAllocator, 131),
     registeredInlineCacheCount(0),
     unregisteredInlineCacheCount(0),
-    prototypeChainEnsuredToHaveOnlyWritableDataPropertiesAllocator(_u("TC-ProtoWritableProp"), GetPageAllocator(), Js::Throw::OutOfMemory),
+    noSpecialPropertyRegistry(this->GetPageAllocator()),
+    onlyWritablePropertyRegistry(this->GetPageAllocator()),
     standardUTF8Chars(0),
     standardUnicodeChars(0),
     hasUnhandledException(FALSE),
@@ -550,7 +551,8 @@ ThreadContext::~ThreadContext()
     this->storeFieldInlineCacheByPropId.Reset();
     this->isInstInlineCacheByFunction.Reset();
     this->equivalentTypeCacheEntryPoints.Reset();
-    this->prototypeChainEnsuredToHaveOnlyWritableDataPropertiesScriptContext.Reset();
+    this->noSpecialPropertyRegistry.Reset();
+    this->onlyWritablePropertyRegistry.Reset();
 
     this->registeredInlineCacheCount = 0;
     this->unregisteredInlineCacheCount = 0;
@@ -2484,8 +2486,9 @@ ThreadContext::PreCollectionCallBack(CollectionFlags flags)
     // script contexts with inline caches
     this->ClearScriptContextCaches();
 
-    // Clear up references to types to avoid keep them alive
-    this->ClearPrototypeChainEnsuredToHaveOnlyWritableDataPropertiesCaches();
+    // Clear up references to types to avoid keeping them alive
+    this->noSpecialPropertyRegistry.Clear();
+    this->onlyWritablePropertyRegistry.Clear();
 
     // Clean up unused memory before we start collecting
     this->CleanNoCasePropertyMap();
@@ -3930,38 +3933,6 @@ void ThreadContext::DoInvalidateProtoTypePropertyCaches(const Js::PropertyId pro
         });
 }
 
-Js::ScriptContext **
-ThreadContext::RegisterPrototypeChainEnsuredToHaveOnlyWritableDataPropertiesScriptContext(Js::ScriptContext * scriptContext)
-{
-    return prototypeChainEnsuredToHaveOnlyWritableDataPropertiesScriptContext.PrependNode(&prototypeChainEnsuredToHaveOnlyWritableDataPropertiesAllocator, scriptContext);
-}
-
-void
-ThreadContext::UnregisterPrototypeChainEnsuredToHaveOnlyWritableDataPropertiesScriptContext(Js::ScriptContext ** scriptContext)
-{
-    prototypeChainEnsuredToHaveOnlyWritableDataPropertiesScriptContext.RemoveElement(&prototypeChainEnsuredToHaveOnlyWritableDataPropertiesAllocator, scriptContext);
-}
-
-void
-ThreadContext::ClearPrototypeChainEnsuredToHaveOnlyWritableDataPropertiesCaches()
-{
-    bool hasItem = false;
-    FOREACH_DLISTBASE_ENTRY(Js::ScriptContext *, scriptContext, &prototypeChainEnsuredToHaveOnlyWritableDataPropertiesScriptContext)
-    {
-        scriptContext->ClearPrototypeChainEnsuredToHaveOnlyWritableDataPropertiesCaches();
-        hasItem = true;
-    }
-    NEXT_DLISTBASE_ENTRY;
-
-    if (!hasItem)
-    {
-        return;
-    }
-
-    prototypeChainEnsuredToHaveOnlyWritableDataPropertiesScriptContext.Reset();
-    prototypeChainEnsuredToHaveOnlyWritableDataPropertiesAllocator.Reset();
-}
-
 BOOL ThreadContext::HasPreviousHostScriptContext()
 {
     return hostScriptContextStack->Count() != 0;

+ 5 - 6
lib/Runtime/Base/ThreadContext.h

@@ -735,8 +735,8 @@ private:
 
     Js::IsConcatSpreadableCache isConcatSpreadableCache;
 
-    ArenaAllocator prototypeChainEnsuredToHaveOnlyWritableDataPropertiesAllocator;
-    DListBase<Js::ScriptContext *> prototypeChainEnsuredToHaveOnlyWritableDataPropertiesScriptContext;
+    Js::NoSpecialPropertyThreadRegistry noSpecialPropertyRegistry;
+    Js::OnlyWritablePropertyThreadRegistry onlyWritablePropertyRegistry;
 
     DListBase<CollectCallBack> collectCallBackList;
     CriticalSection csCollectionCallBack;
@@ -891,6 +891,9 @@ public:
     void* GetTridentLoadAddress() const { return tridentLoadAddress;  }
     void SetTridentLoadAddress(void *loadAddress) { tridentLoadAddress = loadAddress; }
 
+    Js::NoSpecialPropertyThreadRegistry* GetNoSpecialPropertyRegistry() { return &this->noSpecialPropertyRegistry; }
+    Js::OnlyWritablePropertyThreadRegistry* GetOnlyWritablePropertyRegistry() { return &this->onlyWritablePropertyRegistry; }
+
 #ifdef ENABLE_DIRECTCALL_TELEMETRY
     DirectCallTelemetry directCallTelemetry;
 #endif
@@ -1391,10 +1394,6 @@ public:
     void InternalInvalidateProtoTypePropertyCaches(const Js::PropertyId propertyId);
     void InvalidateAllProtoTypePropertyCaches();
 
-    Js::ScriptContext ** RegisterPrototypeChainEnsuredToHaveOnlyWritableDataPropertiesScriptContext(Js::ScriptContext * scriptContext);
-    void UnregisterPrototypeChainEnsuredToHaveOnlyWritableDataPropertiesScriptContext(Js::ScriptContext ** scriptContext);
-    void ClearPrototypeChainEnsuredToHaveOnlyWritableDataPropertiesCaches();
-
     BOOL HasUnhandledException() const { return hasUnhandledException; }
     void SetHasUnhandledException() {hasUnhandledException = TRUE; }
     void ResetHasUnhandledException() {hasUnhandledException = FALSE; }

+ 1 - 0
lib/Runtime/Language/CMakeLists.txt

@@ -32,6 +32,7 @@ set(CRL_SOURCE_FILES ${CRL_SOURCE_FILES}
     ModuleNamespace.cpp
     ModuleNamespaceEnumerator.cpp
     ProfilingHelpers.cpp
+    PrototypeChainCache.cpp
     RuntimeLanguagePch.cpp
     SimdBool16x8Operation.cpp
     SimdBool16x8OperationX86X64.cpp

+ 2 - 0
lib/Runtime/Language/Chakra.Runtime.Language.vcxproj

@@ -179,6 +179,7 @@
     <ClCompile Include="$(MSBuildThisFileDirectory)ModuleNamespaceEnumerator.cpp" />
     <ClCompile Include="$(MSBuildThisFileDirectory)WebAssemblySource.cpp" />
     <ClCompile Include="$(MSBuildThisFileDirectory)ConstructorCache.cpp" />
+    <ClCompile Include="$(MSBuildThisFileDirectory)PrototypeChainCache.cpp" />
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="amd64\StackFrame.h">
@@ -226,6 +227,7 @@
     <ClInclude Include="ModuleNamespace.h" />
     <ClInclude Include="ModuleNamespaceEnumerator.h" />
     <ClInclude Include="ProfilingHelpers.h" />
+    <ClInclude Include="PrototypeChainCache.h" />
     <ClInclude Include="SourceDynamicProfileManager.h" />
     <ClInclude Include="ModuleRecordBase.h" />
     <ClInclude Include="SourceTextModuleRecord.h" />

+ 2 - 0
lib/Runtime/Language/Chakra.Runtime.Language.vcxproj.filters

@@ -77,6 +77,7 @@
     <ClCompile Include="$(MSBuildThisFileDirectory)ModuleNamespaceEnumerator.cpp" />
     <ClCompile Include="$(MSBuildThisFileDirectory)WebAssemblySource.cpp" />
     <ClCompile Include="$(MSBuildThisFileDirectory)ConstructorCache.cpp" />
+    <ClCompile Include="$(MSBuildThisFileDirectory)PrototypeChainCache.cpp" />
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="AsmJs.h" />
@@ -150,6 +151,7 @@
     <ClInclude Include="WebAssemblySource.h" />
     <ClInclude Include="ConstructorCache.h" />
     <ClInclude Include="AsmJsArrayBufferViews.h" />
+    <ClInclude Include="PrototypeChainCache.h" />
   </ItemGroup>
   <ItemGroup>
     <MASM Include="$(MSBuildThisFileDirectory)amd64\amd64_Thunks.asm">

+ 31 - 29
lib/Runtime/Language/JavascriptConversion.cpp

@@ -273,7 +273,7 @@ CommonNumber:
         _Out_ const PropertyRecord** propertyRecord,
         _Out_opt_ PropertyString** propString)
     {
-        Var key = JavascriptConversion::ToPrimitive(argument, JavascriptHint::HintString, scriptContext);
+        Var key = JavascriptConversion::ToPrimitive<JavascriptHint::HintString>(argument, scriptContext);
         PropertyString * propertyString = nullptr;
         if (JavascriptSymbol::Is(key))
         {
@@ -309,7 +309,8 @@ CommonNumber:
     //              The behavior of the [[DefaultValue]] internal method is defined by this specification
     //              for all native ECMAScript objects (8.12.9).
     //----------------------------------------------------------------------------
-    Var JavascriptConversion::ToPrimitive(Var aValue, JavascriptHint hint, ScriptContext * requestContext)
+    template <JavascriptHint hint>
+    Var JavascriptConversion::ToPrimitive(_In_ Var aValue, _In_ ScriptContext * requestContext)
     {
         switch (JavascriptOperators::GetTypeId(aValue))
         {
@@ -338,7 +339,7 @@ CommonNumber:
                 ScriptContext * objectScriptContext = stringObject->GetScriptContext();
                 if (objectScriptContext->optimizationOverrides.GetSideEffects() & (hint == JavascriptHint::HintString ? SideEffects_ToString : SideEffects_ValueOf))
                 {
-                    return MethodCallToPrimitive(aValue, hint, requestContext);
+                    return MethodCallToPrimitive<hint>(stringObject, requestContext);
                 }
 
                 return CrossSite::MarshalVar(requestContext, stringObject->Unwrap(), objectScriptContext);
@@ -352,7 +353,7 @@ CommonNumber:
                 {
                     if (objectScriptContext->optimizationOverrides.GetSideEffects() & SideEffects_ToString)
                     {
-                        return MethodCallToPrimitive(aValue, hint, requestContext);
+                        return MethodCallToPrimitive<hint>(numberObject, requestContext);
                     }
                     return JavascriptNumber::ToStringRadix10(numberObject->GetValue(), requestContext);
                 }
@@ -360,7 +361,7 @@ CommonNumber:
                 {
                     if (objectScriptContext->optimizationOverrides.GetSideEffects() & SideEffects_ValueOf)
                     {
-                        return MethodCallToPrimitive(aValue, hint, requestContext);
+                        return MethodCallToPrimitive<hint>(numberObject, requestContext);
                     }
 
                     return CrossSite::MarshalVar(requestContext, numberObject->Unwrap(), objectScriptContext);
@@ -385,7 +386,7 @@ CommonNumber:
                     {
                         // if no Method exists this function falls back to OrdinaryToPrimitive
                         // if IsES6ToPrimitiveEnabled flag is off we also fall back to OrdinaryToPrimitive
-                        return MethodCallToPrimitive(aValue, hint, requestContext);
+                        return MethodCallToPrimitive<hint>(dateObject, requestContext);
                     }
                     return JavascriptNumber::ToVarNoCheck(dateObject->GetTime(), requestContext);
                 }
@@ -395,7 +396,7 @@ CommonNumber:
                     {
                         // if no Method exists this function falls back to OrdinaryToPrimitive
                         // if IsES6ToPrimitiveEnabled flag is off we also fall back to OrdinaryToPrimitive
-                        return MethodCallToPrimitive(aValue, hint, requestContext);
+                        return MethodCallToPrimitive<hint>(dateObject, requestContext);
                     }
                     return JavascriptDate::ToString(dateObject, requestContext);
                 }
@@ -410,7 +411,7 @@ CommonNumber:
         default:
             // if no Method exists this function falls back to OrdinaryToPrimitive
             // if IsES6ToPrimitiveEnabled flag is off we also fall back to OrdinaryToPrimitive
-            return MethodCallToPrimitive(aValue, hint, requestContext);
+            return MethodCallToPrimitive<hint>(RecyclableObject::UnsafeFromVar(aValue), requestContext);
         }
     }
 
@@ -440,11 +441,11 @@ CommonNumber:
         return FALSE;
     }
 
-    Var JavascriptConversion::MethodCallToPrimitive(Var aValue, JavascriptHint hint, ScriptContext * requestContext)
+    template <JavascriptHint hint>
+    Var JavascriptConversion::MethodCallToPrimitive(_In_ RecyclableObject* value, _In_ ScriptContext * requestContext)
     {
         Var result = nullptr;
-        RecyclableObject *const recyclableObject = RecyclableObject::FromVar(aValue);
-        ScriptContext *const scriptContext = recyclableObject->GetScriptContext();
+        ScriptContext *const scriptContext = value->GetScriptContext();
 
         //7.3.9 GetMethod(V, P)
         //  The abstract operation GetMethod is used to get the value of a specific property of an ECMAScript language value when the value of the
@@ -457,11 +458,12 @@ CommonNumber:
         //  5. Return func.
         Var varMethod = nullptr;
 
-        if (!(requestContext->GetConfig()->IsES6ToPrimitiveEnabled()
-            && JavascriptOperators::GetPropertyReference(recyclableObject, PropertyIds::_symbolToPrimitive, &varMethod, requestContext)
-            && !JavascriptOperators::IsUndefinedOrNull(varMethod)))
+        if (!requestContext->GetConfig()->IsES6ToPrimitiveEnabled()
+            || JavascriptOperators::CheckIfObjectAndProtoChainHasNoSpecialProperties(value)
+            || !JavascriptOperators::GetPropertyReference(value, PropertyIds::_symbolToPrimitive, &varMethod, requestContext)
+            || JavascriptOperators::IsUndefinedOrNull(varMethod))
         {
-            return OrdinaryToPrimitive(aValue, hint, requestContext);
+            return OrdinaryToPrimitive<hint>(value, requestContext);
         }
         if (!JavascriptFunction::Is(varMethod))
         {
@@ -493,10 +495,10 @@ CommonNumber:
         result = threadContext->ExecuteImplicitCall(exoticToPrim, ImplicitCall_ToPrimitive, [=]()->Js::Var
         {
             // Stack object should have a pre-op bail on implicit call.  We shouldn't see them here.
-            Assert(!ThreadContext::IsOnStack(recyclableObject));
+            Assert(!ThreadContext::IsOnStack(value));
 
             // Let result be the result of calling the[[Call]] internal method of exoticToPrim, with input as thisArgument and(hint) as argumentsList.
-            return CALL_FUNCTION(threadContext, exoticToPrim, CallInfo(CallFlags_Value, 2), recyclableObject, hintString);
+            return CALL_FUNCTION(threadContext, exoticToPrim, CallInfo(CallFlags_Value, 2), value, hintString);
         });
 
         if (!result)
@@ -521,13 +523,13 @@ CommonNumber:
         }
     }
 
-    Var JavascriptConversion::OrdinaryToPrimitive(Var aValue, JavascriptHint hint, ScriptContext * requestContext)
+    template <JavascriptHint hint>
+    Var JavascriptConversion::OrdinaryToPrimitive(_In_ RecyclableObject* value, _In_ ScriptContext * requestContext)
     {
         Var result;
-        RecyclableObject *const recyclableObject = RecyclableObject::FromVar(aValue);
-        if (!recyclableObject->ToPrimitive(hint, &result, requestContext))
+        if (!value->ToPrimitive(hint, &result, requestContext))
         {
-            ScriptContext *const scriptContext = recyclableObject->GetScriptContext();
+            ScriptContext *const scriptContext = value->GetScriptContext();
 
             int32 hCode;
 
@@ -656,7 +658,7 @@ CommonNumber:
                         JavascriptError::ThrowError(scriptContext, VBSERR_InternalError);
                     }
                     fPrimitiveOnly = true;
-                    aValue = ToPrimitive(aValue, JavascriptHint::HintString, scriptContext);
+                    aValue = ToPrimitive<JavascriptHint::HintString>(aValue, scriptContext);
                 }
             }
         }
@@ -886,7 +888,7 @@ CommonNumber:
                         JavascriptError::ThrowError(scriptContext, VBSERR_OLENoPropOrMethod);
                     }
                     fPrimitiveOnly = true;
-                    aValue = ToPrimitive(aValue, JavascriptHint::HintNumber, scriptContext);
+                    aValue = ToPrimitive<JavascriptHint::HintNumber>(aValue, scriptContext);
                 }
             }
         }
@@ -940,7 +942,7 @@ CommonNumber:
                         JavascriptError::ThrowError(scriptContext, VBSERR_OLENoPropOrMethod);
                     }
                     fPrimitiveOnly = true;
-                    aValue = ToPrimitive(aValue, JavascriptHint::HintNumber, scriptContext);
+                    aValue = ToPrimitive<JavascriptHint::HintNumber>(aValue, scriptContext);
                 }
             }
         }
@@ -1020,7 +1022,7 @@ CommonNumber:
 
         default:
             AssertMsg(JavascriptOperators::IsObject(aValue), "bad type object in conversion ToInteger32");
-            aValue = ToPrimitive(aValue, JavascriptHint::HintNumber, scriptContext);
+            aValue = ToPrimitive<JavascriptHint::HintNumber>(aValue, scriptContext);
         }
 
         switch (JavascriptOperators::GetTypeId(aValue))
@@ -1127,7 +1129,7 @@ CommonNumber:
                         JavascriptError::ThrowError(scriptContext, VBSERR_OLENoPropOrMethod);
                     }
                     fPrimitiveOnly = true;
-                    aValue = ToPrimitive(aValue, JavascriptHint::HintNumber, scriptContext);
+                    aValue = ToPrimitive<JavascriptHint::HintNumber>(aValue, scriptContext);
                 }
             }
         }
@@ -1263,7 +1265,7 @@ CommonNumber:
                         JavascriptError::ThrowError(scriptContext, VBSERR_OLENoPropOrMethod);
                     }
                     fPrimitiveOnly = true;
-                    aValue = ToPrimitive(aValue, JavascriptHint::HintNumber, scriptContext);
+                    aValue = ToPrimitive<JavascriptHint::HintNumber>(aValue, scriptContext);
                 }
             }
         }
@@ -1337,7 +1339,7 @@ CommonNumber:
                         JavascriptError::ThrowError(scriptContext, VBSERR_OLENoPropOrMethod);
                     }
                     fPrimitiveOnly = true;
-                    aValue = ToPrimitive(aValue, JavascriptHint::HintNumber, scriptContext);
+                    aValue = ToPrimitive<JavascriptHint::HintNumber>(aValue, scriptContext);
                 }
             }
         }
@@ -1381,7 +1383,7 @@ CommonNumber:
 
     JavascriptString * JavascriptConversion::ToPrimitiveString(Var aValue, ScriptContext * scriptContext)
     {
-        return ToString(ToPrimitive(aValue, JavascriptHint::None, scriptContext), scriptContext);
+        return ToString(ToPrimitive<JavascriptHint::None>(aValue, scriptContext), scriptContext);
     }
 
     double JavascriptConversion::LongToDouble(__int64 aValue)

+ 6 - 3
lib/Runtime/Language/JavascriptConversion.h

@@ -9,9 +9,12 @@ namespace Js {
     class JavascriptConversion  /* All static */
     {
     public:
-        static Var OrdinaryToPrimitive(Var aValue, JavascriptHint hint, ScriptContext * scriptContext);
-        static Var MethodCallToPrimitive(Var aValue, JavascriptHint hint, ScriptContext * scriptContext);
-        static Var ToPrimitive(Var aValue, JavascriptHint hint, ScriptContext * scriptContext);
+        template <JavascriptHint hint>
+        static Var OrdinaryToPrimitive(_In_ RecyclableObject* value, _In_ ScriptContext * scriptContext);
+        template <JavascriptHint hint>
+        static Var MethodCallToPrimitive(_In_ RecyclableObject* value, _In_ ScriptContext * scriptContext);
+        template <JavascriptHint hint>
+        static Var ToPrimitive(_In_ Var aValue, _In_ ScriptContext * scriptContext);
         static BOOL CanonicalNumericIndexString(JavascriptString *aValue, double *indexValue, ScriptContext * scriptContext);
 
         static void ToPropertyKey(

+ 12 - 67
lib/Runtime/Language/JavascriptOperators.cpp

@@ -150,7 +150,7 @@ namespace Js
 
     IndexType GetIndexType(Var& indexVar, ScriptContext* scriptContext, uint32* index, PropertyRecord const ** propertyRecord, JavascriptString ** propertyNameString, bool createIfNotFound, bool preferJavascriptStringOverPropertyRecord)
     {
-        indexVar = JavascriptConversion::ToPrimitive(indexVar, JavascriptHint::HintString, scriptContext);
+        indexVar = JavascriptConversion::ToPrimitive<JavascriptHint::HintString>(indexVar, scriptContext);
         return GetIndexTypeFromPrimitive(indexVar, scriptContext, index, propertyRecord, propertyNameString, createIfNotFound, preferJavascriptStringOverPropertyRecord);
     }
 
@@ -693,7 +693,7 @@ namespace Js
             case TypeIds_Boolean:
                 break;
             default:
-                aRight = JavascriptConversion::ToPrimitive(aRight, JavascriptHint::HintNumber, scriptContext);
+                aRight = JavascriptConversion::ToPrimitive<JavascriptHint::HintNumber>(aRight, scriptContext);
                 rightType = JavascriptOperators::GetTypeId(aRight);
                 if (rightType != TypeIds_String)
                 {
@@ -716,13 +716,13 @@ namespace Js
         default:
             if (leftFirst)
             {
-                aLeft = JavascriptConversion::ToPrimitive(aLeft, JavascriptHint::HintNumber, scriptContext);
-                aRight = JavascriptConversion::ToPrimitive(aRight, JavascriptHint::HintNumber, scriptContext);
+                aLeft = JavascriptConversion::ToPrimitive<JavascriptHint::HintNumber>(aLeft, scriptContext);
+                aRight = JavascriptConversion::ToPrimitive<JavascriptHint::HintNumber>(aRight, scriptContext);
             }
             else
             {
-                aRight = JavascriptConversion::ToPrimitive(aRight, JavascriptHint::HintNumber, scriptContext);
-                aLeft = JavascriptConversion::ToPrimitive(aLeft, JavascriptHint::HintNumber, scriptContext);
+                aRight = JavascriptConversion::ToPrimitive<JavascriptHint::HintNumber>(aRight, scriptContext);
+                aLeft = JavascriptConversion::ToPrimitive<JavascriptHint::HintNumber>(aLeft, scriptContext);
             }
             //BugFix: When @@ToPrimitive of an object is overridden with a function that returns null/undefined
             //this helper will fall into a inescapable goto loop as the checks for null/undefined were outside of the path
@@ -9891,74 +9891,19 @@ SetElementIHelper_INDEX_TYPE_IS_NUMBER:
         return !StrictEqual(aLeft, aRight, scriptContext);
     }
 
-    bool JavascriptOperators::CheckIfObjectAndPrototypeChainHasOnlyWritableDataProperties(RecyclableObject* object)
+    bool JavascriptOperators::CheckIfObjectAndPrototypeChainHasOnlyWritableDataProperties(_In_ RecyclableObject* object)
     {
-        Assert(object);
-        if (object->GetType()->HasSpecialPrototype())
-        {
-            TypeId typeId = object->GetTypeId();
-            if (typeId == TypeIds_Null)
-            {
-                return true;
-            }
-            if (typeId == TypeIds_Proxy)
-            {
-                return false;
-            }
-        }
-        if (!object->HasOnlyWritableDataProperties())
-        {
-            return false;
-        }
-        return CheckIfPrototypeChainHasOnlyWritableDataProperties(object->GetPrototype());
+        return object->GetLibrary()->GetTypesWithOnlyWritablePropertyProtoChainCache()->Check(object);
     }
 
-    bool JavascriptOperators::CheckIfPrototypeChainHasOnlyWritableDataProperties(RecyclableObject* prototype)
+    bool JavascriptOperators::CheckIfPrototypeChainHasOnlyWritableDataProperties(_In_ RecyclableObject* prototype)
     {
-        Assert(prototype);
-
-        if (prototype->GetType()->AreThisAndPrototypesEnsuredToHaveOnlyWritableDataProperties())
-        {
-            Assert(DoCheckIfPrototypeChainHasOnlyWritableDataProperties(prototype));
-            return true;
-        }
-        return DoCheckIfPrototypeChainHasOnlyWritableDataProperties(prototype);
+        return prototype->GetLibrary()->GetTypesWithOnlyWritablePropertyProtoChainCache()->CheckProtoChain(prototype);
     }
 
-    // Does a quick check to see if the specified object (which should be a prototype object) and all objects in its prototype
-    // chain have only writable data properties (i.e. no accessors or non-writable properties).
-    bool JavascriptOperators::DoCheckIfPrototypeChainHasOnlyWritableDataProperties(RecyclableObject* prototype)
+    bool JavascriptOperators::CheckIfObjectAndProtoChainHasNoSpecialProperties(_In_ RecyclableObject* object)
     {
-        Assert(prototype);
-
-        Type *const originalType = prototype->GetType();
-        ScriptContext *const scriptContext = prototype->GetScriptContext();
-        bool onlyOneScriptContext = true;
-        TypeId typeId;
-        for (; (typeId = prototype->GetTypeId()) != TypeIds_Null; prototype = prototype->GetPrototype())
-        {
-            if (typeId == TypeIds_Proxy)
-            {
-                return false;
-            }
-            if (!prototype->HasOnlyWritableDataProperties())
-            {
-                return false;
-            }
-            if (prototype->GetScriptContext() != scriptContext)
-            {
-                onlyOneScriptContext = false;
-            }
-        }
-
-        if (onlyOneScriptContext)
-        {
-            // See JavascriptLibrary::typesEnsuredToHaveOnlyWritableDataPropertiesInItAndPrototypeChain for a description of
-            // this cache. Technically, we could register all prototypes in the chain but this is good enough for now.
-            originalType->SetAreThisAndPrototypesEnsuredToHaveOnlyWritableDataProperties(true);
-        }
-
-        return true;
+        return object->GetLibrary()->GetTypesWithNoSpecialPropertyProtoChainCache()->Check(object);
     }
 
     // Checks to see if the specified object (which should be a prototype object)

+ 5 - 3
lib/Runtime/Language/JavascriptOperators.h

@@ -600,9 +600,11 @@ namespace Js
         static Var CallGetter(RecyclableObject * const function, Var const object, ScriptContext * const scriptContext);
         static void CallSetter(RecyclableObject * const function, Var const object, Var const value, ScriptContext * const scriptContext);
 
-        static bool CheckIfObjectAndPrototypeChainHasOnlyWritableDataProperties(RecyclableObject* object);
-        static bool CheckIfPrototypeChainHasOnlyWritableDataProperties(RecyclableObject* prototype);
-        static bool DoCheckIfPrototypeChainHasOnlyWritableDataProperties(RecyclableObject* prototype);
+        static bool CheckIfObjectAndPrototypeChainHasOnlyWritableDataProperties(_In_ RecyclableObject* object);
+        static bool CheckIfPrototypeChainHasOnlyWritableDataProperties(_In_ RecyclableObject* prototype);
+
+        static bool CheckIfObjectAndProtoChainHasNoSpecialProperties(_In_ RecyclableObject* object);
+
         static bool CheckIfPrototypeChainContainsProxyObject(RecyclableObject* prototype);
         static void OP_SetComputedNameVar(Var method, Var computedNameVar);
         static void OP_SetHomeObj(Var method, Var homeObj);

+ 361 - 0
lib/Runtime/Language/PrototypeChainCache.cpp

@@ -0,0 +1,361 @@
+//-------------------------------------------------------------------------------------------------------
+// Copyright (C) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
+//-------------------------------------------------------------------------------------------------------
+
+#include "RuntimeLanguagePch.h"
+
+using namespace Js;
+
+template <typename T>
+ThreadCacheRegistry<T>::ThreadCacheRegistry(_In_ PageAllocator* pageAllocator) :
+    arena(_u("PrototypeChainCacheRegistry"), pageAllocator, Throw::OutOfMemory)
+{
+}
+
+template <typename T>
+ScriptCacheRegistry<T>**
+ThreadCacheRegistry<T>::Register(_In_ ScriptCacheRegistry<T>* cache)
+{
+    return this->scriptRegistrations.PrependNode(&this->arena, cache);
+}
+
+template <typename T>
+void
+ThreadCacheRegistry<T>::Unregister(_In_ ScriptCacheRegistry<T>** registration)
+{
+    this->scriptRegistrations.RemoveElement(&this->arena, registration);
+}
+
+template <typename T>
+void
+ThreadCacheRegistry<T>::Reset()
+{
+    this->scriptRegistrations.Reset();
+}
+
+template <typename T>
+void
+ThreadCacheRegistry<T>::Clear()
+{
+    bool hasItem = false;
+    FOREACH_DLISTBASE_ENTRY(ScriptCacheRegistry<T>*, registration, &this->scriptRegistrations)
+    {
+        registration->Clear(false /* unregister */);
+        hasItem = true;
+    }
+    NEXT_DLISTBASE_ENTRY;
+
+    if (!hasItem)
+    {
+        return;
+    }
+
+    this->scriptRegistrations.Reset();
+    this->arena.Reset();
+}
+
+template <typename T>
+ScriptCacheRegistry<T>::ScriptCacheRegistry(_In_ ThreadCacheRegistry<T>* threadRegistry) :
+    threadRegistry(threadRegistry),
+    cache(nullptr),
+    registration(nullptr)
+{
+}
+
+template <typename T>
+void
+AssignCache(_In_ T* cache)
+{
+    Assert(this->cache == nullptr);
+    this->cache = cache;
+}
+
+template <typename T>
+void
+ScriptCacheRegistry<T>::Register()
+{
+    Assert(this->cache != nullptr);
+    if (this->registration == nullptr)
+    {
+        this->registration = this->threadRegistry->Register(this);
+    }
+}
+
+template <typename T>
+void
+ScriptCacheRegistry<T>::Clear(bool unregister)
+{
+    if (this->registration != nullptr)
+    {
+        Assert(this->cache);
+        this->cache->Clear();
+        if (unregister)
+        {
+            this->threadRegistry->Unregister(this->registration);
+        }
+        this->registration = nullptr;
+    }
+}
+
+template <typename T>
+void
+ScriptCacheRegistry<T>::AssignCache(_In_ T* cache)
+{
+    Assert(this->cache == nullptr);
+    this->cache = cache;
+}
+
+template <typename T>
+PrototypeChainCache<T>::PrototypeChainCache(
+    _In_ ScriptContext* scriptContext,
+    _In_ ScriptCacheRegistry<PrototypeChainCache<T>>* scriptRegistry) :
+        scriptContext(scriptContext),
+        cache(scriptContext),
+        scriptRegistry(scriptRegistry)
+{
+    this->types = JsUtil::List<Type*>::New(scriptContext->GetRecycler()); 
+    scriptRegistry->AssignCache(this);
+}
+
+template <typename T>
+void
+PrototypeChainCache<T>::Register(_In_ Type* type)
+{
+    Assert(type);
+    Assert(type->GetScriptContext() == this->scriptContext);
+    Assert(!scriptContext->IsClosed());
+#if DBG
+    this->cache.RegistrationAssert(type);
+#endif
+
+    if (this->types->Count() == 0)
+    {
+        this->scriptRegistry->Register();
+    }
+    this->types->Add(type);
+}
+
+template <typename T>
+bool
+PrototypeChainCache<T>::Check(_In_ RecyclableObject* object)
+{
+    Assert(object);
+    if (object->GetType()->HasSpecialPrototype())
+    {
+        TypeId typeId = object->GetTypeId();
+        if (typeId == TypeIds_Null)
+        {
+            return true;
+        }
+        if (typeId == TypeIds_Proxy)
+        {
+            return false;
+        }
+    }
+
+    if (!T::CheckObject(object))
+    {
+        return false;
+    }
+
+    return CheckProtoChain(object->GetPrototype());
+}
+
+template <typename T>
+bool
+PrototypeChainCache<T>::CheckProtoChain(_In_ RecyclableObject* prototype)
+{
+    Assert(prototype);
+
+    if (T::IsCached(prototype->GetType()))
+    {
+        Assert(CheckProtoChainInternal(prototype));
+        return true;
+    }
+    return CheckProtoChainInternal(prototype);
+}
+
+template <typename T>
+bool
+PrototypeChainCache<T>::CheckProtoChainInternal(RecyclableObject* prototype)
+{
+    Assert(prototype);
+
+    Type *const originalType = prototype->GetType();
+    ScriptContext *const scriptContext = prototype->GetScriptContext();
+
+    bool onlyOneScriptContext = true;
+    TypeId typeId;
+    for (; (typeId = prototype->GetTypeId()) != TypeIds_Null; prototype = prototype->GetPrototype())
+    {
+        if (typeId == TypeIds_Proxy)
+        {
+            return false;
+        }
+        if (prototype->GetScriptContext() != scriptContext)
+        {
+            onlyOneScriptContext = false;
+        }
+        if (!T::CheckObject(prototype))
+        {
+            return false;
+        }
+    }
+
+    if (onlyOneScriptContext)
+    {
+        // See JavascriptLibrary::typesWithNoSpecialPropertiesInItAndPrototypeChain for a description of
+        // this cache. Technically, we could register all prototypes in the chain but this is good enough for now.
+        T::Cache(originalType);
+    }
+
+    return true;
+}
+
+template <typename T>
+void
+PrototypeChainCache<T>::Clear()
+{
+    for (int i = 0; i < this->types->Count(); ++i)
+    {
+        this->cache.ClearType(this->types->Item(i));
+    }
+    this->types->ClearAndZero();
+}
+
+NoSpecialPropertyCache::NoSpecialPropertyCache(_In_ ScriptContext* scriptContext) :
+    scriptContext(scriptContext)
+{
+}
+
+void
+NoSpecialPropertyCache::ClearType(_In_ Type* type)
+{
+    type->SetThisAndPrototypesHaveNoSpecialProperties(false);
+}
+
+bool
+NoSpecialPropertyCache::IsSpecialProperty(PropertyId propertyId)
+{
+    return propertyId == PropertyIds::_symbolToStringTag
+        || propertyId == PropertyIds::_symbolToPrimitive
+        || propertyId == PropertyIds::toString
+        || propertyId == PropertyIds::valueOf;
+}
+
+bool
+NoSpecialPropertyCache::IsSpecialProperty(_In_ PropertyRecord const* propertyRecord)
+{
+    return IsSpecialProperty(propertyRecord->GetPropertyId());
+}
+
+bool
+NoSpecialPropertyCache::IsSpecialProperty(_In_ JavascriptString* propertyString)
+{
+    return BuiltInPropertyRecords::valueOf.Equals(propertyString)
+        || BuiltInPropertyRecords::toString.Equals(propertyString);
+}
+
+// We can handle default initialization of toString/valueOf for Object.prototype,
+// so check if it's that's what is happening
+bool
+NoSpecialPropertyCache::IsDefaultSpecialProperty(
+    _In_ DynamicObject* instance,
+    _In_ JavascriptLibrary* library,
+    PropertyId propertyId)
+{
+    return instance == library->GetObjectPrototype()
+        && ((propertyId == PropertyIds::toString && !library->GetObjectToStringFunction())
+            || (propertyId == PropertyIds::valueOf && !library->GetObjectValueOfFunction()));
+}
+
+bool
+NoSpecialPropertyCache::IsDefaultHandledSpecialProperty(PropertyId propertyId)
+{
+    return propertyId == PropertyIds::toString || propertyId == PropertyIds::valueOf;
+}
+
+bool
+NoSpecialPropertyCache::IsDefaultHandledSpecialProperty(_In_ PropertyRecord const* propertyRecord)
+{
+    return IsDefaultHandledSpecialProperty(propertyRecord->GetPropertyId());
+}
+
+bool
+NoSpecialPropertyCache::IsDefaultHandledSpecialProperty(_In_ JavascriptString* propertyString)
+{
+    return IsSpecialProperty(propertyString);
+}
+
+bool
+NoSpecialPropertyCache::IsCached(_In_ Type* type)
+{
+    return type->ThisAndPrototypesHaveNoSpecialProperties();
+}
+
+void
+NoSpecialPropertyCache::Cache(_In_ Type* type)
+{
+    type->SetThisAndPrototypesHaveNoSpecialProperties(true);
+}
+
+bool
+NoSpecialPropertyCache::CheckObject(_In_ RecyclableObject* object)
+{
+    return !object->HasAnySpecialProperties() && !object->HasDeferredTypeHandler();
+}
+
+#if DBG
+void
+NoSpecialPropertyCache::RegistrationAssert(_In_ Type* type)
+{
+    Assert(type->ThisAndPrototypesHaveNoSpecialProperties());
+}
+#endif
+
+OnlyWritablePropertyCache::OnlyWritablePropertyCache(_In_ ScriptContext* scriptContext) :
+    scriptContext(scriptContext)
+{
+}
+
+void
+OnlyWritablePropertyCache::ClearType(_In_ Type* type)
+{
+    type->SetAreThisAndPrototypesEnsuredToHaveOnlyWritableDataProperties(false);
+}
+
+bool
+OnlyWritablePropertyCache::IsCached(_In_ Type* type)
+{
+    return type->AreThisAndPrototypesEnsuredToHaveOnlyWritableDataProperties();
+}
+
+void
+OnlyWritablePropertyCache::Cache(_In_ Type* type)
+{
+    type->SetAreThisAndPrototypesEnsuredToHaveOnlyWritableDataProperties(true);
+}
+
+bool
+OnlyWritablePropertyCache::CheckObject(_In_ RecyclableObject* object)
+{
+    return object->HasOnlyWritableDataProperties();
+}
+
+#if DBG
+void
+OnlyWritablePropertyCache::RegistrationAssert(_In_ Type* type)
+{
+    Assert(type->AreThisAndPrototypesEnsuredToHaveOnlyWritableDataProperties());
+}
+#endif
+
+template class PrototypeChainCache<NoSpecialPropertyCache>;
+template class PrototypeChainCache<OnlyWritablePropertyCache>;
+
+template class ScriptCacheRegistry<NoSpecialPropertyProtoChainCache>;
+template class ScriptCacheRegistry<OnlyWritablePropertyProtoChainCache>;
+
+template class ThreadCacheRegistry<NoSpecialPropertyProtoChainCache>;
+template class ThreadCacheRegistry<OnlyWritablePropertyProtoChainCache>;

+ 209 - 0
lib/Runtime/Language/PrototypeChainCache.h

@@ -0,0 +1,209 @@
+//-------------------------------------------------------------------------------------------------------
+// Copyright (C) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
+//-------------------------------------------------------------------------------------------------------
+
+#pragma once
+
+namespace Js
+{
+template <typename T> class PrototypeChainCache;
+template <typename T> class ScriptCacheRegistry;
+
+template <typename T>
+class ThreadCacheRegistry
+{
+public:
+    ThreadCacheRegistry(_In_ PageAllocator* pageAllocator);
+    ScriptCacheRegistry<T>** Register(_In_ ScriptCacheRegistry<T>* cache);
+    void Unregister(_In_ ScriptCacheRegistry<T>** cache);
+    void Clear();
+    void Reset();
+
+private:
+    ArenaAllocator arena;
+    DListBase<ScriptCacheRegistry<T>*> scriptRegistrations;
+};
+
+template <typename T>
+class ScriptCacheRegistry
+{
+public:
+    ScriptCacheRegistry(_In_ ThreadCacheRegistry<T>* threadRegistry);
+    void Register();
+    void Clear(bool unregister);
+    void AssignCache(_In_ T* cache);
+private:
+    T* cache;
+    ScriptCacheRegistry<T>** registration;
+    ThreadCacheRegistry<T>* threadRegistry;
+};
+
+template <typename T>
+class PrototypeChainCache
+{
+public:
+    PrototypeChainCache(
+        _In_ ScriptContext* scriptContext,
+        _In_ ScriptCacheRegistry<PrototypeChainCache<T>>* scriptRegistry);
+    void Register(_In_ Type* type);
+    bool Check(_In_ RecyclableObject* object);
+    bool CheckProtoChain(_In_ RecyclableObject* prototype);
+    void Clear();
+
+    template <typename KeyType>
+    void ProcessProperty(
+        _In_ DynamicTypeHandler* typeHandler,
+        PropertyAttributes attributes,
+        _In_ KeyType propertyKey);
+
+private:
+    bool CheckProtoChainInternal(_In_ RecyclableObject* prototype);
+
+    Field(T) cache;
+    Field(JsUtil::List<Type*>*) types;
+    Field(ScriptCacheRegistry<PrototypeChainCache<T>>*) scriptRegistry;
+
+    Field(ScriptContext*) scriptContext;
+};
+
+// This cache contains types ensured to have no @@toPrimitive, @@toStringTag, toString, or valueOf properties in it
+// and in all objects in its prototype chain.
+// The default values of Object.prototype.toString and Object.prototype.valueOf are also permitted.
+class NoSpecialPropertyCache
+{
+public:
+    NoSpecialPropertyCache(_In_ ScriptContext* scriptContext);
+
+    template <typename KeyType>
+    bool ProcessProperty(
+        _In_ DynamicTypeHandler* typeHandler,
+        PropertyAttributes attributes,
+        _In_ KeyType propertyKey);
+
+    static void ClearType(_In_ Type* type);
+
+    static bool IsCached(_In_ Type* type);
+    static void Cache(_In_ Type* type);
+    static bool CheckObject(_In_ RecyclableObject* object);
+
+    static bool IsSpecialProperty(PropertyId propertyId);
+    static bool IsSpecialProperty(_In_ PropertyRecord const* propertyRecord);
+    static bool IsSpecialProperty(_In_ JavascriptString* propertyString);
+
+    static bool IsDefaultSpecialProperty(
+        _In_ DynamicObject* instance,
+        _In_ JavascriptLibrary* library,
+        PropertyId propertyId);
+
+    static bool IsDefaultHandledSpecialProperty(PropertyId propertyId);
+    static bool IsDefaultHandledSpecialProperty(_In_ PropertyRecord const* propertyRecord);
+    static bool IsDefaultHandledSpecialProperty(_In_ JavascriptString* propertyString);
+#if DBG
+    void RegistrationAssert(_In_ Type* type);
+#endif
+
+private:
+    Field(ScriptContext*) scriptContext;
+};
+
+// This cache contains types ensured to have only writable data properties in it and all objects in its prototype chain
+// (i.e., no readonly properties or accessors). Only prototype objects' types are stored in the cache. When something
+// in the script context adds a readonly property or accessor to an object that is used as a prototype object, this
+// list is cleared. The list is also cleared before garbage collection so that it does not keep growing, and so, it can
+// hold strong references to the types.
+//
+// The cache is used by the type-without-property local inline cache. When setting a property on a type that doesn't
+// have the property, to determine whether to promote the object like an object of that type was last promoted, we need
+// to ensure that objects in the prototype chain have not acquired a readonly property or setter (ideally, only for that
+// property ID, but we just check for any such property). This cache is used to avoid doing this many times, especially
+// when the prototype chain is not short.
+//
+// This cache is only used to invalidate the status of types. The type itself contains a boolean indicating whether it
+// and prototypes contain only writable data properties, which is reset upon invalidating the status.
+class OnlyWritablePropertyCache
+{
+public:
+    OnlyWritablePropertyCache(_In_ ScriptContext* scriptContext);
+
+    template <typename KeyType>
+    bool ProcessProperty(
+        _In_ DynamicTypeHandler* typeHandler,
+        PropertyAttributes attributes,
+        _In_ KeyType propertyKey);
+
+    static void ClearType(_In_ Type* type);
+
+    static bool IsCached(_In_ Type* type);
+    static void Cache(_In_ Type* type);
+    static bool CheckObject(_In_ RecyclableObject* object);
+#if DBG
+    void RegistrationAssert(_In_ Type* type);
+#endif
+
+private:
+    Field(ScriptContext*) scriptContext;
+};
+
+typedef PrototypeChainCache<OnlyWritablePropertyCache> OnlyWritablePropertyProtoChainCache;
+typedef PrototypeChainCache<NoSpecialPropertyCache> NoSpecialPropertyProtoChainCache;
+
+typedef ScriptCacheRegistry<NoSpecialPropertyProtoChainCache> NoSpecialPropertyScriptRegistry;
+typedef ScriptCacheRegistry<OnlyWritablePropertyProtoChainCache> OnlyWritablePropertyScriptRegistry;
+
+typedef ThreadCacheRegistry<NoSpecialPropertyProtoChainCache> NoSpecialPropertyThreadRegistry;
+typedef ThreadCacheRegistry<OnlyWritablePropertyProtoChainCache> OnlyWritablePropertyThreadRegistry;
+
+template <typename T>
+template <typename KeyType>
+inline void
+PrototypeChainCache<T>::ProcessProperty(
+    _In_ DynamicTypeHandler* typeHandler,
+    PropertyAttributes attributes,
+    _In_ KeyType propertyKey)
+{
+    if (this->cache.ProcessProperty(typeHandler, attributes, propertyKey))
+    {
+        this->Clear();
+    }
+}
+
+template <typename KeyType>
+inline bool
+NoSpecialPropertyCache::ProcessProperty(
+    _In_ DynamicTypeHandler* typeHandler,
+    PropertyAttributes attributes,
+    _In_ KeyType propertyKey)
+{
+    if (IsSpecialProperty(propertyKey))
+    {
+        typeHandler->SetHasSpecialProperties();
+        if (typeHandler->GetFlags() & DynamicTypeHandler::IsPrototypeFlag)
+        {
+            return true;
+        }
+    }
+    return false;
+}
+
+template <typename KeyType>
+inline bool
+OnlyWritablePropertyCache::ProcessProperty(
+    _In_ DynamicTypeHandler* typeHandler,
+    PropertyAttributes attributes,
+    _In_ KeyType propertyKey)
+{
+    if (!(attributes & PropertyWritable))
+    {
+        typeHandler->ClearHasOnlyWritableDataProperties();
+        if (typeHandler->GetFlags() & DynamicTypeHandler::IsPrototypeFlag)
+        {
+            PropertyId propertyId = DynamicTypeHandler::TMapKey_GetPropertyId(this->scriptContext, propertyKey);
+            this->scriptContext->InvalidateStoreFieldCaches(propertyId);
+            return true;
+        }
+    }
+    return false;
+}
+
+}

+ 4 - 4
lib/Runtime/Library/JavascriptDate.cpp

@@ -172,7 +172,7 @@ namespace Js
             }
             else
             {
-                Var value = JavascriptConversion::ToPrimitive(args[1], Js::JavascriptHint::None, scriptContext);
+                Var value = JavascriptConversion::ToPrimitive<Js::JavascriptHint::None>(args[1], scriptContext);
                 if (JavascriptString::Is(value))
                 {
                     timeValue = ParseHelper(scriptContext, JavascriptString::FromVar(value));
@@ -264,13 +264,13 @@ namespace Js
                     // Date objects, are unique among built-in ECMAScript object in that they treat "default" as being equivalent to "string"
                     // If hint is the string value "string" or the string value "default", then
                     // Let tryFirst be "string".
-                    return JavascriptConversion::OrdinaryToPrimitive(args[0], JavascriptHint::HintString/*tryFirst*/, scriptContext);
+                    return JavascriptConversion::OrdinaryToPrimitive<JavascriptHint::HintString>(RecyclableObject::UnsafeFromVar(args[0]), scriptContext);
                 }
                 // Else if hint is the string value "number", then
                 // Let tryFirst be "number".
                 else if(wcscmp(str, _u("number")) == 0)
                 {
-                    return JavascriptConversion::OrdinaryToPrimitive(args[0], JavascriptHint::HintNumber/*tryFirst*/, scriptContext);
+                    return JavascriptConversion::OrdinaryToPrimitive<JavascriptHint::HintNumber>(RecyclableObject::UnsafeFromVar(args[0]), scriptContext);
                 }
                 //anything else should throw a type error
             }
@@ -1304,7 +1304,7 @@ namespace Js
             return result;
         }
 
-        Var num = JavascriptConversion::ToPrimitive(thisObj, JavascriptHint::HintNumber, scriptContext);
+        Var num = JavascriptConversion::ToPrimitive<JavascriptHint::HintNumber>(thisObj, scriptContext);
         if (JavascriptNumber::Is(num)
             && !NumberUtilities::IsFinite(JavascriptNumber::GetValue(num)))
         {

+ 2 - 2
lib/Runtime/Library/JavascriptFunction.cpp

@@ -47,7 +47,7 @@ namespace Js
             // an object that is already a prototype. If it becomes a prototype and then we attempt to add a property to an object derived from this
             // object, then we will check if this property is writable, and only if it is will we do the fast path for add property.
             // GetScriptContext()->InvalidateStoreFieldCaches(PropertyIds::length);
-            GetLibrary()->NoPrototypeChainsAreEnsuredToHaveOnlyWritableDataProperties();
+            GetLibrary()->GetTypesWithOnlyWritablePropertyProtoChainCache()->Clear();
         }
     }
 
@@ -62,7 +62,7 @@ namespace Js
             // an object that is already a prototype. If it becomes a prototype and then we attempt to add a property to an object derived from this
             // object, then we will check if this property is writable, and only if it is will we do the fast path for add property.
             // GetScriptContext()->InvalidateStoreFieldCaches(PropertyIds::length);
-            GetLibrary()->NoPrototypeChainsAreEnsuredToHaveOnlyWritableDataProperties();
+            GetLibrary()->GetTypesWithOnlyWritablePropertyProtoChainCache()->Clear();
         }
     }
 

+ 3 - 27
lib/Runtime/Library/JavascriptLibrary.cpp

@@ -57,7 +57,7 @@ namespace Js
     SimpleTypeHandler<1> JavascriptLibrary::SharedFunctionWithConfigurableLengthTypeHandler(NO_WRITE_BARRIER_TAG(BuiltInPropertyRecords::length), PropertyConfigurable);
     SimpleTypeHandler<1> JavascriptLibrary::SharedFunctionWithLengthTypeHandler(NO_WRITE_BARRIER_TAG(BuiltInPropertyRecords::length));
     SimpleTypeHandler<2> JavascriptLibrary::SharedFunctionWithLengthAndNameTypeHandler(NO_WRITE_BARRIER_TAG(FunctionWithLengthAndNameTypeDescriptors));
-    SimpleTypeHandler<1> JavascriptLibrary::SharedNamespaceSymbolTypeHandler(NO_WRITE_BARRIER_TAG(ModuleNamespaceTypeDescriptors));
+    SimpleTypeHandler<1> JavascriptLibrary::SharedNamespaceSymbolTypeHandler(NO_WRITE_BARRIER_TAG(ModuleNamespaceTypeDescriptors), PropertyTypesHasSpecialProperties);
     MissingPropertyTypeHandler JavascriptLibrary::MissingPropertyHolderTypeHandler;
 
 
@@ -84,7 +84,8 @@ namespace Js
         this->recycler = scriptContext->GetRecycler();
         this->undeclBlockVarSentinel = RecyclerNew(recycler, UndeclaredBlockVariable, StaticType::New(scriptContext, TypeIds_Null, nullptr, nullptr));
 
-        typesEnsuredToHaveOnlyWritableDataPropertiesInItAndPrototypeChain = RecyclerNew(recycler, JsUtil::List<Type *>, recycler);
+        this->typesWithOnlyWritablePropertyProtoChain = RecyclerNew(recycler, OnlyWritablePropertyProtoChainCache, scriptContext, scriptContext->GetOnlyWritablePropertyRegistry());
+        this->typesWithNoSpecialPropertyProtoChain = RecyclerNew(recycler, NoSpecialPropertyProtoChainCache, scriptContext, scriptContext->GetNoSpecialPropertyRegistry());
 
         // Library is not zero-initialized. memset the memory occupied by builtinFunctions array to 0.
         ClearArray(builtinFunctions, BuiltinFunction::Count);
@@ -3428,31 +3429,6 @@ namespace Js
         return false;
     }
 
-    void JavascriptLibrary::TypeAndPrototypesAreEnsuredToHaveOnlyWritableDataProperties(Type *const type)
-    {
-        Assert(type);
-        Assert(type->GetScriptContext() == scriptContext);
-        Assert(type->AreThisAndPrototypesEnsuredToHaveOnlyWritableDataProperties());
-        Assert(!scriptContext->IsClosed());
-
-        if(typesEnsuredToHaveOnlyWritableDataPropertiesInItAndPrototypeChain->Count() == 0)
-        {
-            scriptContext->RegisterPrototypeChainEnsuredToHaveOnlyWritableDataPropertiesScriptContext();
-        }
-        typesEnsuredToHaveOnlyWritableDataPropertiesInItAndPrototypeChain->Add(type);
-    }
-
-    void JavascriptLibrary::NoPrototypeChainsAreEnsuredToHaveOnlyWritableDataProperties()
-    {
-        for(int i = 0; i < typesEnsuredToHaveOnlyWritableDataPropertiesInItAndPrototypeChain->Count(); ++i)
-        {
-            typesEnsuredToHaveOnlyWritableDataPropertiesInItAndPrototypeChain
-                ->Item(i)
-                ->SetAreThisAndPrototypesEnsuredToHaveOnlyWritableDataProperties(false);
-        }
-        typesEnsuredToHaveOnlyWritableDataPropertiesInItAndPrototypeChain->ClearAndZero();
-    }
-
     bool JavascriptLibrary::ArrayIteratorPrototypeHasUserDefinedNext(ScriptContext *scriptContext)
     {
         Var arrayIteratorPrototypeNext = nullptr;

+ 5 - 17
lib/Runtime/Library/JavascriptLibrary.h

@@ -483,21 +483,8 @@ namespace Js
 
         Field(ModuleRecordList*) moduleRecordList;
 
-        // This list contains types ensured to have only writable data properties in it and all objects in its prototype chain
-        // (i.e., no readonly properties or accessors). Only prototype objects' types are stored in the list. When something
-        // in the script context adds a readonly property or accessor to an object that is used as a prototype object, this
-        // list is cleared. The list is also cleared before garbage collection so that it does not keep growing, and so, it can
-        // hold strong references to the types.
-        //
-        // The cache is used by the type-without-property local inline cache. When setting a property on a type that doesn't
-        // have the property, to determine whether to promote the object like an object of that type was last promoted, we need
-        // to ensure that objects in the prototype chain have not acquired a readonly property or setter (ideally, only for that
-        // property ID, but we just check for any such property). This cache is used to avoid doing this many times, especially
-        // when the prototype chain is not short.
-        //
-        // This list is only used to invalidate the status of types. The type itself contains a boolean indicating whether it
-        // and prototypes contain only writable data properties, which is reset upon invalidating the status.
-        Field(JsUtil::List<Type *> *) typesEnsuredToHaveOnlyWritableDataPropertiesInItAndPrototypeChain;
+        Field(OnlyWritablePropertyProtoChainCache*) typesWithOnlyWritablePropertyProtoChain;
+        Field(NoSpecialPropertyProtoChainCache*) typesWithNoSpecialPropertyProtoChain;
 
         Field(uint64) randSeed0, randSeed1;
         Field(bool) isPRNGSeeded;
@@ -1117,8 +1104,9 @@ namespace Js
         template <> PropertyStringCacheMap* GetPropertyMap<PropertyString>() { return this->propertyStringMap; }
         template <> SymbolCacheMap* GetPropertyMap<JavascriptSymbol>() { return this->symbolMap; }
 
-        void TypeAndPrototypesAreEnsuredToHaveOnlyWritableDataProperties(Type *const type);
-        void NoPrototypeChainsAreEnsuredToHaveOnlyWritableDataProperties();
+
+        Field(OnlyWritablePropertyProtoChainCache*) GetTypesWithOnlyWritablePropertyProtoChainCache() const { return this->typesWithOnlyWritablePropertyProtoChain; }
+        Field(NoSpecialPropertyProtoChainCache*) GetTypesWithNoSpecialPropertyProtoChainCache() const { return this->typesWithNoSpecialPropertyProtoChain; }
 
         static bool IsDefaultArrayValuesFunction(RecyclableObject * function, ScriptContext *scriptContext);
         static bool ArrayIteratorPrototypeHasUserDefinedNext(ScriptContext *scriptContext);

+ 24 - 1
lib/Runtime/Library/JavascriptObject.cpp

@@ -231,6 +231,24 @@ namespace Js
                 obj->RemoveFromPrototype(scriptContext);
             });
 
+            // Examine new prototype chain. If it brings in any special property, we need to invalidate related caches.
+            bool objectAndPrototypeChainHasNoSpecialProperties =
+                JavascriptOperators::CheckIfObjectAndProtoChainHasNoSpecialProperties(newPrototype);
+
+            if (!objectAndPrototypeChainHasNoSpecialProperties
+                || object->GetScriptContext() != newPrototype->GetScriptContext())
+            {
+                // The HaveNoSpecialProperties cache is cleared when a property is added or changed,
+                // but only for types in the same script context. Therefore, if the prototype is in another
+                // context, the object's cache won't be cleared when a property is added or changed on the prototype.
+                // Moreover, an object is added to the cache only when its whole prototype chain is in the same
+                // context.
+                //
+                // Since we don't have a way to find out which objects have a certain object as their prototype,
+                // we clear the cache here instead.
+                object->GetLibrary()->GetTypesWithNoSpecialPropertyProtoChainCache()->Clear();
+            }
+
             // Examine new prototype chain. If it brings in any non-WritableData property, we need to invalidate related caches.
             bool objectAndPrototypeChainHasOnlyWritableDataProperties =
                 JavascriptOperators::CheckIfObjectAndPrototypeChainHasOnlyWritableDataProperties(newPrototype);
@@ -248,7 +266,7 @@ namespace Js
                 // we clear the cache here instead.
 
                 // Invalidate fast prototype chain writable data test flag
-                object->GetLibrary()->NoPrototypeChainsAreEnsuredToHaveOnlyWritableDataProperties();
+                object->GetLibrary()->GetTypesWithOnlyWritablePropertyProtoChainCache()->Clear();
             }
 
             if (!objectAndPrototypeChainHasOnlyWritableDataProperties)
@@ -357,6 +375,11 @@ namespace Js
 
     Var JavascriptObject::GetToStringTagValue(RecyclableObject *thisArg, ScriptContext *scriptContext)
     {
+        if (JavascriptOperators::CheckIfObjectAndProtoChainHasNoSpecialProperties(thisArg))
+        {
+            return nullptr;
+        }
+
         const PropertyId toStringTagId(PropertyIds::_symbolToStringTag);
         PolymorphicInlineCache *cache = scriptContext->GetLibrary()->GetToStringTagCache();
         PropertyValueInfo info;

+ 1 - 0
lib/Runtime/Library/JavascriptRegularExpression.cpp

@@ -19,6 +19,7 @@ namespace Js
     {
         Assert(type->GetTypeId() == TypeIds_RegEx);
         Assert(!this->GetType()->AreThisAndPrototypesEnsuredToHaveOnlyWritableDataProperties());
+        Assert(!this->GetType()->ThisAndPrototypesHaveNoSpecialProperties());
 
 #if ENABLE_REGEX_CONFIG_OPTIONS
         if (REGEX_CONFIG_FLAG(RegexTracing))

+ 2 - 2
lib/Runtime/Library/JavascriptStringObject.cpp

@@ -18,7 +18,7 @@ namespace Js
             // No need to invalidate store field caches for non-writable properties here. Since this type is just being created, it cannot represent
             // an object that is already a prototype. If it becomes a prototype and then we attempt to add a property to an object derived from this
             // object, then we will check if this property is writable, and only if it is will we do the fast path for add property.
-            GetLibrary()->NoPrototypeChainsAreEnsuredToHaveOnlyWritableDataProperties();
+            GetLibrary()->GetTypesWithOnlyWritablePropertyProtoChainCache()->Clear();
         }
     }
 
@@ -33,7 +33,7 @@ namespace Js
             // No need to invalidate store field caches for non-writable properties here. Since this type is just being created, it cannot represent
             // an object that is already a prototype. If it becomes a prototype and then we attempt to add a property to an object derived from this
             // object, then we will check if this property is writable, and only if it is will we do the fast path for add property.
-            GetLibrary()->NoPrototypeChainsAreEnsuredToHaveOnlyWritableDataProperties();
+            GetLibrary()->GetTypesWithOnlyWritablePropertyProtoChainCache()->Clear();
         }
     }
 

+ 1 - 1
lib/Runtime/Library/JavascriptSymbol.cpp

@@ -259,7 +259,7 @@ namespace Js
         TypeId typeId = JavascriptOperators::GetTypeId(right);
         if (typeId != TypeIds_Symbol && typeId != TypeIds_SymbolObject)
         {
-            right = JavascriptConversion::ToPrimitive(right, JavascriptHint::None, requestContext);
+            right = JavascriptConversion::ToPrimitive<JavascriptHint::None>(right, requestContext);
             typeId = JavascriptOperators::GetTypeId(right);
         }
 

+ 2 - 2
lib/Runtime/Math/JavascriptMath.cpp

@@ -371,8 +371,8 @@ StringCommon:
 
         Var JavascriptMath::Add_FullHelper_Wrapper(Var aLeft, Var aRight, ScriptContext* scriptContext, JavascriptNumber* result, bool leftIsDead)
         {
-            Var aLeftToPrim = JavascriptConversion::ToPrimitive(aLeft, JavascriptHint::None, scriptContext);
-            Var aRightToPrim = JavascriptConversion::ToPrimitive(aRight, JavascriptHint::None, scriptContext);
+            Var aLeftToPrim = JavascriptConversion::ToPrimitive<JavascriptHint::None>(aLeft, scriptContext);
+            Var aRightToPrim = JavascriptConversion::ToPrimitive<JavascriptHint::None>(aRight, scriptContext);
             return Add_FullHelper(aLeftToPrim, aRightToPrim, scriptContext, result, leftIsDead);
         }
 

+ 0 - 1
lib/Runtime/PlatformAgnostic/Platform/Windows/UnicodeText.cpp

@@ -9,7 +9,6 @@
 
 #include <windows.h>
 #include "Runtime.h"
-#include "Base/ThreadContext.h"
 #ifdef NTBUILD
 #include "Windows.Globalization.h"
 #else

+ 2 - 1
lib/Runtime/Runtime.h

@@ -227,7 +227,7 @@ namespace Js
     class EntryPointInfo;
     struct LoopHeader;
     class InternalString;
-    /* enum */ struct JavascriptHint;
+    enum class JavascriptHint;
     /* enum */ struct BuiltinFunction;
     class EnterScriptObject;
     class PropertyRecord;
@@ -425,6 +425,7 @@ enum tagDEBUG_EVENT_INFO_TYPE
 
 #include "Base/CharStringCache.h"
 
+#include "Language/PrototypeChainCache.h"
 #include "Library/JavascriptObject.h"
 #include "Library/BuiltInFlags.h"
 #include "Types/DynamicObjectPropertyEnumerator.h"

+ 7 - 5
lib/Runtime/RuntimeCommon.h

@@ -115,14 +115,16 @@ namespace Js
                                                          // (no accessors or non-writable properties)
     #define PropertyTypesWritableDataOnlyDetection 0x20  // Set on each call to DynamicTypeHandler::SetHasOnlyWritableDataProperties.
     #define PropertyTypesInlineSlotCapacityLocked  0x40  // Indicates that the inline slot capacity has been shrunk already and shouldn't be touched again.
-    #define PropertyTypesAll                       (PropertyTypesWritableDataOnly|PropertyTypesWritableDataOnlyDetection|PropertyTypesInlineSlotCapacityLocked)
+    #define PropertyTypesHasSpecialProperties      0x80  // Indicates that @@toStringTag, @@toPrimitive, toString, or valueOf are set
+    #define PropertyTypesAll                       (PropertyTypesHasSpecialProperties|PropertyTypesWritableDataOnly|PropertyTypesWritableDataOnlyDetection|PropertyTypesInlineSlotCapacityLocked)
     typedef unsigned char PropertyTypes;                 // Holds flags that represent general information about the types of properties
                                                          // handled by a type handler.
-    BEGIN_ENUM_UINT(JavascriptHint)
+    enum class JavascriptHint
+    {
         None,                                   // no hint. use the default for that object
-        HintString  = 0x00000001,               // 'string' hint in ToPrimitiveValue()
-        HintNumber  = 0x00000002,               // 'number' hint
-    END_ENUM_UINT()
+        HintString = 0x00000001,               // 'string' hint in ToPrimitiveValue()
+        HintNumber = 0x00000002,               // 'number' hint
+    };
 
     enum DescriptorFlags
     {

+ 1 - 1
lib/Runtime/Types/DeferredTypeHandler.cpp

@@ -111,7 +111,7 @@ namespace Js
         // EnsureSlots before updating the type handler and instance, as EnsureSlots allocates and may throw.
         instance->EnsureSlots(0, newTypeHandler->GetSlotCapacity(), scriptContext, newTypeHandler);
         newTypeHandler->SetFlags(IsPrototypeFlag, this->GetFlags());
-        newTypeHandler->SetPropertyTypes(PropertyTypesWritableDataOnly | PropertyTypesWritableDataOnlyDetection | PropertyTypesInlineSlotCapacityLocked , this->GetPropertyTypes());
+        newTypeHandler->SetPropertyTypes(PropertyTypesWritableDataOnly | PropertyTypesWritableDataOnlyDetection | PropertyTypesInlineSlotCapacityLocked | PropertyTypesHasSpecialProperties, this->GetPropertyTypes());
         if (instance->HasReadOnlyPropertiesInvisibleToTypeHandler())
         {
             newTypeHandler->ClearHasOnlyWritableDataProperties();

+ 1 - 1
lib/Runtime/Types/DeferredTypeHandler.h

@@ -695,7 +695,7 @@ namespace Js
             DeferredTypeHandlerBase* protoTypeHandler = DeferredTypeHandler<initializer, DeferredTypeFilter, true, _inlineSlotCapacity, _offsetOfInlineSlots>::GetDefaultInstance();
             AssertMsg(protoTypeHandler->GetFlags() == (GetFlags() | IsPrototypeFlag), "Why did we change the flags of a DeferredTypeHandler?");
             Assert(this->GetIsInlineSlotCapacityLocked() == protoTypeHandler->GetIsInlineSlotCapacityLocked());
-            protoTypeHandler->SetPropertyTypes(PropertyTypesWritableDataOnly | PropertyTypesWritableDataOnlyDetection, GetPropertyTypes());
+            protoTypeHandler->SetPropertyTypes(PropertyTypesWritableDataOnly | PropertyTypesWritableDataOnlyDetection | PropertyTypesHasSpecialProperties, GetPropertyTypes());
             SetInstanceTypeHandler(instance, protoTypeHandler);
         }
     }

+ 69 - 65
lib/Runtime/Types/DictionaryTypeHandler.cpp

@@ -62,7 +62,7 @@ namespace Js
 #endif
     {
         Assert(typeHandler->GetIsInlineSlotCapacityLocked());
-        CopyPropertyTypes(PropertyTypesWritableDataOnly | PropertyTypesWritableDataOnlyDetection | PropertyTypesInlineSlotCapacityLocked, typeHandler->GetPropertyTypes());
+        CopyPropertyTypes(PropertyTypesWritableDataOnly | PropertyTypesWritableDataOnlyDetection | PropertyTypesInlineSlotCapacityLocked | PropertyTypesHasSpecialProperties, typeHandler->GetPropertyTypes());
     }
 
     template <typename T>
@@ -359,16 +359,16 @@ namespace Js
 
     template <typename T>
     void DictionaryTypeHandlerBase<T>::Add(
-        const PropertyRecord* propertyId,
+        const PropertyRecord* propertyRecord,
         PropertyAttributes attributes,
         ScriptContext *const scriptContext)
     {
-        return Add(propertyId, attributes, true, false, false, scriptContext);
+        return Add(propertyRecord, attributes, true, false, false, scriptContext);
     }
 
     template <typename T>
     void DictionaryTypeHandlerBase<T>::Add(
-        const PropertyRecord* propertyId,
+        const PropertyRecord* propertyRecord,
         PropertyAttributes attributes,
         bool isInitialized, bool isFixed, bool usedAsFixed,
         ScriptContext *const scriptContext)
@@ -379,22 +379,15 @@ namespace Js
 
         DictionaryPropertyDescriptor<T> descriptor(index, attributes);
 #if ENABLE_FIXED_FIELDS
-        Assert((!isFixed && !usedAsFixed) || (!IsInternalPropertyId(propertyId->GetPropertyId()) && this->singletonInstance != nullptr));
+        Assert((!isFixed && !usedAsFixed) || (!IsInternalPropertyId(propertyRecord->GetPropertyId()) && this->singletonInstance != nullptr));
         descriptor.IsInitialized = isInitialized;
         descriptor.IsFixed = isFixed;
         descriptor.UsedAsFixed = usedAsFixed;
 #endif
-        propertyMap->Add(propertyId, descriptor);
+        propertyMap->Add(propertyRecord, descriptor);
 
-        if (!(attributes & PropertyWritable))
-        {
-            this->ClearHasOnlyWritableDataProperties();
-            if(GetFlags() & IsPrototypeFlag)
-            {
-                scriptContext->InvalidateStoreFieldCaches(propertyId->GetPropertyId());
-                scriptContext->GetLibrary()->NoPrototypeChainsAreEnsuredToHaveOnlyWritableDataProperties();
-            }
-        }
+        scriptContext->GetLibrary()->GetTypesWithOnlyWritablePropertyProtoChainCache()->ProcessProperty(this, attributes, propertyRecord);
+        scriptContext->GetLibrary()->GetTypesWithNoSpecialPropertyProtoChainCache()->ProcessProperty(this, attributes, propertyRecord);
     }
 
     template <typename T>
@@ -729,10 +722,13 @@ namespace Js
 
     template <typename T>
     template <bool allowLetConstGlobal>
-    void DictionaryTypeHandlerBase<T>::SetPropertyWithDescriptor(DynamicObject* instance,
-        PropertyRecord const* propertyRecord,
-        DictionaryPropertyDescriptor<T> ** pdescriptor,
-        Var value, PropertyOperationFlags flags, PropertyValueInfo* info)
+    void DictionaryTypeHandlerBase<T>::SetPropertyWithDescriptor(
+        _In_ DynamicObject* instance,
+        _In_ PropertyRecord const* propertyRecord,
+        _Inout_ DictionaryPropertyDescriptor<T> ** pdescriptor,
+        _In_ Var value,
+        _In_ PropertyOperationFlags flags,
+        _Inout_opt_ PropertyValueInfo* info)
     {
         Assert(pdescriptor && *pdescriptor);
         DictionaryPropertyDescriptor<T> * descriptor = *pdescriptor;
@@ -821,6 +817,14 @@ namespace Js
                 *pdescriptor = nullptr;
             }
         }
+        if (NoSpecialPropertyCache::IsDefaultHandledSpecialProperty(propertyId))
+        {
+            this->SetHasSpecialProperties();
+            if (GetFlags() & IsPrototypeFlag)
+            {
+                instance->GetScriptContext()->GetLibrary()->GetTypesWithNoSpecialPropertyProtoChainCache()->Clear();
+            }
+        }
         SetPropertyUpdateSideEffect(instance, propertyId, value, SideEffects_Any);
     }
 
@@ -924,11 +928,20 @@ namespace Js
             "Numeric property names should have been converted to uint or PropertyRecord* ");
 
         ScriptContext* scriptContext = instance->GetScriptContext();
+        JavascriptLibrary* library = scriptContext->GetLibrary();
         DictionaryPropertyDescriptor<T>* descriptor;
         JsUtil::CharacterBuffer<WCHAR> propertyName(propertyNameString->GetString(), propertyNameString->GetLength());
 
         if (propertyMap->TryGetReference(propertyName, &descriptor))
         {
+            if (!this->GetHasSpecialProperties() && NoSpecialPropertyCache::IsDefaultHandledSpecialProperty(propertyNameString))
+            {
+                this->SetHasSpecialProperties();
+                if (GetFlags() & IsPrototypeFlag)
+                {
+                    library->GetTypesWithNoSpecialPropertyProtoChainCache()->Clear();
+                }
+            }
 #if ENABLE_FIXED_FIELDS
             Assert(descriptor->SanityCheckFixedBits());
 #endif
@@ -945,7 +958,7 @@ namespace Js
                 return false;
             }
 
-            Var undefined = scriptContext->GetLibrary()->GetUndefined();
+            Var undefined = library->GetUndefined();
 
             if (descriptor->HasNonLetConstGlobal())
             {
@@ -1013,6 +1026,14 @@ namespace Js
         PropertyRecord const* propertyRecord = scriptContext->GetPropertyName(propertyId);
         if (propertyMap->TryGetReference(propertyRecord, &descriptor))
         {
+            if (!this->GetHasSpecialProperties() && NoSpecialPropertyCache::IsDefaultHandledSpecialProperty(propertyId))
+            {
+                this->SetHasSpecialProperties();
+                if (GetFlags() & IsPrototypeFlag)
+                {
+                    scriptContext->GetLibrary()->GetTypesWithNoSpecialPropertyProtoChainCache()->Clear();
+                }
+            }
 #if ENABLE_FIXED_FIELDS
             Assert(descriptor->SanityCheckFixedBits());
 #endif
@@ -1317,12 +1338,8 @@ namespace Js
             else
             {
                 descriptor->Attributes &= (~PropertyWritable);
-                this->ClearHasOnlyWritableDataProperties();
-                if(GetFlags() & IsPrototypeFlag)
-                {
-                    scriptContext->InvalidateStoreFieldCaches(propertyId);
-                    instance->GetLibrary()->NoPrototypeChainsAreEnsuredToHaveOnlyWritableDataProperties();
-                }
+
+                instance->GetLibrary()->GetTypesWithOnlyWritablePropertyProtoChainCache()->ProcessProperty(this, descriptor->Attributes, propertyId);
             }
             instance->ChangeType();
             return true;
@@ -1465,7 +1482,7 @@ namespace Js
         if(GetFlags() & IsPrototypeFlag)
         {
             InvalidateStoreFieldCachesForAllProperties(instance->GetScriptContext());
-            instance->GetLibrary()->NoPrototypeChainsAreEnsuredToHaveOnlyWritableDataProperties();
+            instance->GetLibrary()->GetTypesWithOnlyWritablePropertyProtoChainCache()->Clear();
         }
 
         return true;
@@ -1713,12 +1730,9 @@ namespace Js
                 SetSlotUnchecked(instance, descriptor->GetSetterPropertyIndex(), setter);
             }
             instance->ChangeType();
-            this->ClearHasOnlyWritableDataProperties();
-            if(GetFlags() & IsPrototypeFlag)
-            {
-                scriptContext->InvalidateStoreFieldCaches(propertyId);
-                library->NoPrototypeChainsAreEnsuredToHaveOnlyWritableDataProperties();
-            }
+            library->GetTypesWithOnlyWritablePropertyProtoChainCache()->ProcessProperty(this, PropertyNone, propertyRecord);
+            library->GetTypesWithNoSpecialPropertyProtoChainCache()->ProcessProperty(this, PropertyNone, propertyRecord);
+
             SetPropertyUpdateSideEffect(instance, propertyId, nullptr, SideEffects_Any);
 
             // Let's make sure we always have a getter and a setter
@@ -1766,12 +1780,10 @@ namespace Js
 
         SetSlotUnchecked(instance, newDescriptor.GetGetterPropertyIndex(), getter);
         SetSlotUnchecked(instance, newDescriptor.GetSetterPropertyIndex(), setter);
-        this->ClearHasOnlyWritableDataProperties();
-        if(GetFlags() & IsPrototypeFlag)
-        {
-            scriptContext->InvalidateStoreFieldCaches(propertyId);
-            library->NoPrototypeChainsAreEnsuredToHaveOnlyWritableDataProperties();
-        }
+
+        library->GetTypesWithOnlyWritablePropertyProtoChainCache()->ProcessProperty(this, PropertyNone, propertyRecord);
+        library->GetTypesWithNoSpecialPropertyProtoChainCache()->ProcessProperty(this, PropertyNone, propertyRecord);
+
         SetPropertyUpdateSideEffect(instance, propertyId, nullptr, SideEffects_Any);
 
         // Let's make sure we always have a getter and a setter
@@ -1936,15 +1948,7 @@ namespace Js
                 {
                     instance->SetHasNoEnumerableProperties(false);
                 }
-                if (!(descriptor->Attributes & PropertyWritable))
-                {
-                    this->ClearHasOnlyWritableDataProperties();
-                    if (GetFlags() & IsPrototypeFlag)
-                    {
-                        scriptContext->InvalidateStoreFieldCaches(propertyId);
-                        instance->GetLibrary()->NoPrototypeChainsAreEnsuredToHaveOnlyWritableDataProperties();
-                    }
-                }
+                instance->GetLibrary()->GetTypesWithOnlyWritablePropertyProtoChainCache()->ProcessProperty(this, descriptor->Attributes, propertyId);
             }
 
             SetPropertyUpdateSideEffect(instance, propertyId, value, possibleSideEffects);
@@ -2003,15 +2007,7 @@ namespace Js
                 instance->SetHasNoEnumerableProperties(false);
             }
 
-            if (!(descriptor->Attributes & PropertyWritable))
-            {
-                this->ClearHasOnlyWritableDataProperties();
-                if(GetFlags() & IsPrototypeFlag)
-                {
-                    scriptContext->InvalidateStoreFieldCaches(propertyId);
-                    instance->GetLibrary()->NoPrototypeChainsAreEnsuredToHaveOnlyWritableDataProperties();
-                }
-            }
+            instance->GetLibrary()->GetTypesWithOnlyWritablePropertyProtoChainCache()->ProcessProperty(this, descriptor->Attributes, propertyId);
 
             return true;
         }
@@ -2091,7 +2087,7 @@ namespace Js
         // Any new type handler we expect to see here should have inline slot capacity locked.  If this were to change, we would need
         // to update our shrinking logic (see PathTypeHandlerBase::ShrinkSlotAndInlineSlotCapacity).
         Assert(newTypeHandler->GetIsInlineSlotCapacityLocked());
-        newTypeHandler->SetPropertyTypes(PropertyTypesWritableDataOnly | PropertyTypesWritableDataOnlyDetection,  this->GetPropertyTypes());
+        newTypeHandler->SetPropertyTypes(PropertyTypesWritableDataOnly | PropertyTypesWritableDataOnlyDetection | PropertyTypesHasSpecialProperties, this->GetPropertyTypes());
         newTypeHandler->SetInstanceTypeHandler(instance);
 
 #if ENABLE_FIXED_FIELDS
@@ -2140,7 +2136,7 @@ namespace Js
         AnalysisAssert(instance);
         ScriptContext* scriptContext = instance->GetScriptContext();
         bool isForce = (flags & PropertyOperation_Force) != 0;
-
+        PropertyId propertyId = propertyRecord->GetPropertyId();
 #if DBG
         DictionaryPropertyDescriptor<T>* descriptor;
         Assert(!propertyMap->TryGetReference(propertyRecord, &descriptor));
@@ -2179,7 +2175,7 @@ namespace Js
         if ((flags & PropertyOperation_PreInit) == 0)
         {
             newDescriptor.IsInitialized = true;
-            if (localSingletonInstance == instance && !IsInternalPropertyId(propertyRecord->GetPropertyId()) &&
+            if (localSingletonInstance == instance && !IsInternalPropertyId(propertyId) &&
                 (flags & (PropertyOperation_NonFixedValue | PropertyOperation_SpecialValue)) == 0)
             {
                 Assert(value != nullptr);
@@ -2196,14 +2192,22 @@ namespace Js
         {
             instance->SetHasNoEnumerableProperties(false);
         }
+        JavascriptLibrary* library = scriptContext->GetLibrary();
 
-        if (!(attributes & PropertyWritable))
+        library->GetTypesWithOnlyWritablePropertyProtoChainCache()->ProcessProperty(this, attributes, propertyId);
+        if (NoSpecialPropertyCache::IsSpecialProperty(propertyId) && !this->GetHasSpecialProperties())
         {
-            this->ClearHasOnlyWritableDataProperties();
-            if(GetFlags() & IsPrototypeFlag)
+            if (!NoSpecialPropertyCache::IsDefaultSpecialProperty(instance, library, propertyId))
             {
-                instance->GetScriptContext()->InvalidateStoreFieldCaches(propertyRecord->GetPropertyId());
-                instance->GetLibrary()->NoPrototypeChainsAreEnsuredToHaveOnlyWritableDataProperties();
+                this->SetHasSpecialProperties();
+                if (GetFlags() & IsPrototypeFlag)
+                {
+                    library->GetTypesWithNoSpecialPropertyProtoChainCache()->Clear();
+                }
+            }
+            else
+            {
+                PropertyValueInfo::SetNoCache(info, instance);
             }
         }
 

+ 7 - 2
lib/Runtime/Types/DictionaryTypeHandler.h

@@ -248,8 +248,13 @@ namespace Js
         template<bool allowLetConstGlobal>
         inline DescriptorFlags GetSetterFromDescriptor(DynamicObject* instance, DictionaryPropertyDescriptor<T> * descriptor, Var* setterValue, PropertyValueInfo* info);
         template <bool allowLetConstGlobal>
-        inline void SetPropertyWithDescriptor(DynamicObject* instance, PropertyRecord const* propertyRecord, DictionaryPropertyDescriptor<T> ** pdescriptor,
-            Var value, PropertyOperationFlags flags, PropertyValueInfo* info);
+        inline void SetPropertyWithDescriptor(
+            _In_ DynamicObject* instance,
+            _In_ PropertyRecord const* propertyRecord,
+            _Inout_ DictionaryPropertyDescriptor<T> ** pdescriptor,
+            _In_ Var value,
+            _In_ PropertyOperationFlags flags,
+            _Inout_opt_ PropertyValueInfo* info);
 
     protected:
         virtual BOOL FreezeImpl(DynamicObject* instance, bool isConvertedType) override;

+ 1 - 1
lib/Runtime/Types/DynamicObject.cpp

@@ -322,7 +322,7 @@ namespace Js
 
             // If this object is used as a prototype, the has-only-writable-data-properties-in-prototype-chain cache needs to be
             // invalidated here since the type handler of 'objectArray' is not marked as being used as a prototype
-            GetType()->GetLibrary()->NoPrototypeChainsAreEnsuredToHaveOnlyWritableDataProperties();
+            GetType()->GetLibrary()->GetTypesWithOnlyWritablePropertyProtoChainCache()->Clear();
         }
     }
 

+ 16 - 3
lib/Runtime/Types/DynamicType.cpp

@@ -399,9 +399,22 @@ namespace Js
     BOOL DynamicObject::ToPrimitiveImpl(Var* result, ScriptContext * requestContext)
     {
         CompileAssert(propertyId == PropertyIds::valueOf || propertyId == PropertyIds::toString);
-        InlineCache * inlineCache = propertyId == PropertyIds::valueOf ? requestContext->GetValueOfInlineCache() : requestContext->GetToStringInlineCache();
-        // Use per script context inline cache for valueOf and toString
-        Var aValue = JavascriptOperators::PatchGetValueUsingSpecifiedInlineCache(inlineCache, this, this, propertyId, requestContext);
+        Var aValue = nullptr;
+        if (JavascriptOperators::CheckIfObjectAndProtoChainHasNoSpecialProperties(this))
+        {
+            if (this->GetPrototype() == this->GetLibrary()->GetObjectPrototype())
+            {
+                aValue = (propertyId == PropertyIds::valueOf)
+                    ? requestContext->GetLibrary()->GetObjectValueOfFunction()
+                    : requestContext->GetLibrary()->GetObjectToStringFunction();
+            }
+        }
+        if(!aValue)
+        {
+            InlineCache * inlineCache = propertyId == PropertyIds::valueOf ? requestContext->GetValueOfInlineCache() : requestContext->GetToStringInlineCache();
+            // Use per script context inline cache for valueOf and toString
+            aValue = JavascriptOperators::PatchGetValueUsingSpecifiedInlineCache(inlineCache, this, this, propertyId, requestContext);
+        }
 
         // Fast path to the default valueOf/toString implementation
         if (propertyId == PropertyIds::valueOf)

+ 7 - 7
lib/Runtime/Types/ES5ArrayTypeHandler.cpp

@@ -455,7 +455,7 @@ namespace Js
             this->ClearHasOnlyWritableDataProperties();
             if(this->GetFlags() & this->IsPrototypeFlag)
             {
-                instance->GetLibrary()->NoPrototypeChainsAreEnsuredToHaveOnlyWritableDataProperties();
+                instance->GetLibrary()->GetTypesWithOnlyWritablePropertyProtoChainCache()->Clear();
             }
         }
 
@@ -516,7 +516,7 @@ namespace Js
                 this->ClearHasOnlyWritableDataProperties();
                 if(this->GetFlags() & this->IsPrototypeFlag)
                 {
-                    instance->GetLibrary()->NoPrototypeChainsAreEnsuredToHaveOnlyWritableDataProperties();
+                    instance->GetLibrary()->GetTypesWithOnlyWritablePropertyProtoChainCache()->Clear();
                 }
             }
             return true;
@@ -532,7 +532,7 @@ namespace Js
                 this->ClearHasOnlyWritableDataProperties();
                 if(this->GetFlags() & this->IsPrototypeFlag)
                 {
-                    instance->GetLibrary()->NoPrototypeChainsAreEnsuredToHaveOnlyWritableDataProperties();
+                    instance->GetLibrary()->GetTypesWithOnlyWritablePropertyProtoChainCache()->Clear();
                 }
             }
             return true;
@@ -594,7 +594,7 @@ namespace Js
         this->ClearHasOnlyWritableDataProperties();
         if(this->GetFlags() & this->IsPrototypeFlag)
         {
-            instance->GetLibrary()->NoPrototypeChainsAreEnsuredToHaveOnlyWritableDataProperties();
+            instance->GetLibrary()->GetTypesWithOnlyWritablePropertyProtoChainCache()->Clear();
         }
         return true;
     }
@@ -1033,7 +1033,7 @@ namespace Js
                         this->ClearHasOnlyWritableDataProperties();
                         if(this->GetFlags() & this->IsPrototypeFlag)
                         {
-                            instance->GetLibrary()->NoPrototypeChainsAreEnsuredToHaveOnlyWritableDataProperties();
+                            instance->GetLibrary()->GetTypesWithOnlyWritablePropertyProtoChainCache()->Clear();
                         }
                     }
                 }
@@ -1063,7 +1063,7 @@ namespace Js
                         this->ClearHasOnlyWritableDataProperties();
                         if(this->GetFlags() & this->IsPrototypeFlag)
                         {
-                            instance->GetLibrary()->NoPrototypeChainsAreEnsuredToHaveOnlyWritableDataProperties();
+                            instance->GetLibrary()->GetTypesWithOnlyWritablePropertyProtoChainCache()->Clear();
                         }
                     }
                 }
@@ -1127,7 +1127,7 @@ namespace Js
             SetLengthWritable(value ? true : false);
             if(!value && this->GetFlags() & this->IsPrototypeFlag)
             {
-                instance->GetLibrary()->NoPrototypeChainsAreEnsuredToHaveOnlyWritableDataProperties();
+                instance->GetLibrary()->GetTypesWithOnlyWritablePropertyProtoChainCache()->Clear();
             }
             return true;
         }

+ 2 - 2
lib/Runtime/Types/NullTypeHandler.cpp

@@ -237,7 +237,7 @@ namespace Js
         instance->EnsureSlots(0, newTypeHandler->GetSlotCapacity(), scriptContext, newTypeHandler);
         Assert(((this->GetFlags() & IsPrototypeFlag) != 0) == this->isPrototype);
         newTypeHandler->SetFlags(IsPrototypeFlag, this->GetFlags());
-        newTypeHandler->SetPropertyTypes(PropertyTypesWritableDataOnly | PropertyTypesWritableDataOnlyDetection | PropertyTypesInlineSlotCapacityLocked, this->GetPropertyTypes());
+        newTypeHandler->SetPropertyTypes(PropertyTypesWritableDataOnly | PropertyTypesWritableDataOnlyDetection | PropertyTypesInlineSlotCapacityLocked | PropertyTypesHasSpecialProperties, this->GetPropertyTypes());
         if (instance->HasReadOnlyPropertiesInvisibleToTypeHandler())
         {
             newTypeHandler->ClearHasOnlyWritableDataProperties();
@@ -332,7 +332,7 @@ namespace Js
             NullTypeHandler<true>* protoTypeHandler = NullTypeHandler<true>::GetDefaultInstance();
             AssertMsg(protoTypeHandler->GetFlags() == (GetFlags() | IsPrototypeFlag), "Why did we change the flags of a NullTypeHandler?");
             Assert(this->GetIsInlineSlotCapacityLocked() == protoTypeHandler->GetIsInlineSlotCapacityLocked());
-            protoTypeHandler->SetPropertyTypes(PropertyTypesWritableDataOnly | PropertyTypesWritableDataOnlyDetection, GetPropertyTypes());
+            protoTypeHandler->SetPropertyTypes(PropertyTypesWritableDataOnly | PropertyTypesWritableDataOnlyDetection | PropertyTypesHasSpecialProperties, GetPropertyTypes());
             SetInstanceTypeHandler(instance, protoTypeHandler);
         }
     }

+ 10 - 14
lib/Runtime/Types/PathTypeHandler.cpp

@@ -1498,7 +1498,7 @@ namespace Js
         // Any new type handler we expect to see here should have inline slot capacity locked.  If this were to change, we would need
         // to update our shrinking logic (see ShrinkSlotAndInlineSlotCapacity).
         Assert(newTypeHandler->GetIsInlineSlotCapacityLocked());
-        newTypeHandler->SetPropertyTypes(PropertyTypesWritableDataOnly | PropertyTypesWritableDataOnlyDetection, oldTypeHandler->GetPropertyTypes());
+        newTypeHandler->SetPropertyTypes(PropertyTypesWritableDataOnly | PropertyTypesWritableDataOnlyDetection | PropertyTypesHasSpecialProperties, oldTypeHandler->GetPropertyTypes());
         newTypeHandler->SetInstanceTypeHandler(instance);
 
 #if ENABLE_FIXED_FIELDS
@@ -1732,7 +1732,7 @@ namespace Js
         // Any new type handler we expect to see here should have inline slot capacity locked.  If this were to change, we would need
         // to update our shrinking logic (see ShrinkSlotAndInlineSlotCapacity).
         Assert(newTypeHandler->GetIsInlineSlotCapacityLocked());
-        newTypeHandler->SetPropertyTypes(PropertyTypesWritableDataOnly | PropertyTypesWritableDataOnlyDetection, oldTypeHandler->GetPropertyTypes());
+        newTypeHandler->SetPropertyTypes(PropertyTypesWritableDataOnly | PropertyTypesWritableDataOnlyDetection | PropertyTypesHasSpecialProperties, oldTypeHandler->GetPropertyTypes());
         newTypeHandler->SetInstanceTypeHandler(instance);
 
 #if ENABLE_FIXED_FIELDS
@@ -1916,6 +1916,7 @@ namespace Js
         Assert(propertyIndex != nullptr);
         Assert(isObjectLiteral || instance != nullptr);
 
+        JavascriptLibrary* library = scriptContext->GetLibrary();
         Recycler* recycler = scriptContext->GetRecycler();
         PropertyIndex index;
         DynamicType * nextType;
@@ -2076,7 +2077,7 @@ namespace Js
             }
             if (!markTypeAsShared) nextPath->SetMayBecomeShared();
             Assert(nextPath->GetHasOnlyWritableDataProperties());
-            nextPath->CopyPropertyTypes(PropertyTypesWritableDataOnly | PropertyTypesWritableDataOnlyDetection, GetPropertyTypes());
+            nextPath->CopyPropertyTypes(PropertyTypesWritableDataOnly | PropertyTypesWritableDataOnlyDetection | PropertyTypesHasSpecialProperties, GetPropertyTypes());
             nextPath->SetPropertyTypes(PropertyTypesInlineSlotCapacityLocked, GetPropertyTypes());
 
 #if ENABLE_FIXED_FIELDS
@@ -2161,16 +2162,11 @@ namespace Js
         nextPath->SetFlags(IsPrototypeFlag, this->GetFlags());
         Assert(this->GetHasOnlyWritableDataProperties() == nextPath->GetHasOnlyWritableDataProperties() || !(key.GetAttributes() & ObjectSlotAttr_Writable) || (key.GetAttributes() & ObjectSlotAttr_Accessor));
         Assert(this->GetIsInlineSlotCapacityLocked() == nextPath->GetIsInlineSlotCapacityLocked());
-        nextPath->SetPropertyTypes(PropertyTypesWritableDataOnlyDetection, this->GetPropertyTypes());
-        if (!(key.GetAttributes() & ObjectSlotAttr_Writable) || (key.GetAttributes() & ObjectSlotAttr_Accessor))
-        {
-            nextPath->ClearHasOnlyWritableDataProperties();
-            if (nextPath->GetFlags() & IsPrototypeFlag)
-            {
-                scriptContext->InvalidateStoreFieldCaches(key.GetPropertyId());
-                instance->GetLibrary()->NoPrototypeChainsAreEnsuredToHaveOnlyWritableDataProperties();
-            }
-        }
+        nextPath->SetPropertyTypes(PropertyTypesWritableDataOnlyDetection | PropertyTypesHasSpecialProperties, this->GetPropertyTypes());
+
+        PropertyAttributes isWritableAttribute = ((key.GetAttributes() & ObjectSlotAttr_Writable) && !(key.GetAttributes() & ObjectSlotAttr_Accessor)) ? PropertyWritable : PropertyNone;
+        library->GetTypesWithOnlyWritablePropertyProtoChainCache()->ProcessProperty(nextPath, isWritableAttribute, propertyRecord);
+        library->GetTypesWithNoSpecialPropertyProtoChainCache()->ProcessProperty(nextPath, isWritableAttribute, propertyRecord);
 
         (*propertyIndex) = index;
 
@@ -2583,7 +2579,7 @@ namespace Js
                     false);
         }
         clonedTypeHandler->SetMayBecomeShared();
-        clonedTypeHandler->CopyPropertyTypes(PropertyTypesWritableDataOnly | PropertyTypesWritableDataOnlyDetection, this->GetPropertyTypes());
+        clonedTypeHandler->CopyPropertyTypes(PropertyTypesWritableDataOnly | PropertyTypesWritableDataOnlyDetection | PropertyTypesHasSpecialProperties, this->GetPropertyTypes());
         
         return clonedTypeHandler;
     }

+ 14 - 2
lib/Runtime/Types/RecyclableObject.cpp

@@ -197,6 +197,18 @@ namespace Js
         return true;
     }
 
+    bool RecyclableObject::HasAnySpecialProperties()
+    {
+        if (DynamicType::Is(this->GetTypeId()))
+        {
+            DynamicObject* obj = DynamicObject::UnsafeFromVar(this);
+            return obj->GetTypeHandler()->GetHasSpecialProperties() ||
+                (obj->HasObjectArray() && obj->GetObjectArrayOrFlagsAsArray()->HasAnySpecialProperties());
+        }
+
+        return true;
+    }
+
     void RecyclableObject::ClearWritableDataOnlyDetectionBit()
     {
         if (DynamicType::Is(this->GetTypeId()))
@@ -750,12 +762,12 @@ namespace Js
         }
 
     RedoLeft:
-        aLeft = JavascriptConversion::ToPrimitive(aLeft, JavascriptHint::None, requestContext);
+        aLeft = JavascriptConversion::ToPrimitive<JavascriptHint::None>(aLeft, requestContext);
         leftType = JavascriptOperators::GetTypeId(aLeft);
         redoCount++;
         goto Redo;
     RedoRight:
-        aRight = JavascriptConversion::ToPrimitive(aRight, JavascriptHint::None, requestContext);
+        aRight = JavascriptConversion::ToPrimitive<JavascriptHint::None>(aRight, requestContext);
         rightType = JavascriptOperators::GetTypeId(aRight);
         redoCount++;
         goto Redo;

+ 2 - 0
lib/Runtime/Types/RecyclableObject.h

@@ -270,6 +270,8 @@ namespace Js {
         // (i.e. no accessors or non-writable properties)?
         bool HasOnlyWritableDataProperties();
 
+        bool HasAnySpecialProperties();
+
         void ClearWritableDataOnlyDetectionBit();
         bool IsWritableDataOnlyDetectionBitSet();
 

+ 10 - 35
lib/Runtime/Types/SimpleDictionaryTypeHandler.cpp

@@ -481,7 +481,7 @@ namespace Js
         // Any new type handler we expect to see here should have inline slot capacity locked.  If this were to change, we would need
         // to update our shrinking logic (see PathTypeHandlerBase::ShrinkSlotAndInlineSlotCapacity).
         Assert(newTypeHandler->GetIsInlineSlotCapacityLocked());
-        newTypeHandler->SetPropertyTypes(PropertyTypesWritableDataOnly | PropertyTypesWritableDataOnlyDetection, this->GetPropertyTypes());
+        newTypeHandler->SetPropertyTypes(PropertyTypesWritableDataOnly | PropertyTypesWritableDataOnlyDetection | PropertyTypesHasSpecialProperties, this->GetPropertyTypes());
         newTypeHandler->SetInstanceTypeHandler(instance);
 #if ENABLE_FIXED_FIELDS
         // We assumed that we don't need to transfer used as fixed bits unless we are a prototype, which is only valid if we also changed the type.
@@ -1011,6 +1011,7 @@ namespace Js
         bool isInitialized, bool isFixed, bool usedAsFixed,
         ScriptContext *const scriptContext)
     {
+        JavascriptLibrary* library = scriptContext->GetLibrary();
         //
         // For a function with same named parameters,
         // property id Constants::NoProperty will be passed for all the dups except the last one
@@ -1030,16 +1031,9 @@ namespace Js
             descriptor.usedAsFixed = usedAsFixed;
 #endif
             propertyMap->Add(TMapKey_ConvertKey<TMapKey>(scriptContext, propertyKey), descriptor);
-        }
 
-        if (!(attributes & PropertyWritable))
-        {
-            this->ClearHasOnlyWritableDataProperties();
-            if (GetFlags() & IsPrototypeFlag)
-            {
-                scriptContext->InvalidateStoreFieldCaches(TMapKey_GetPropertyId(scriptContext, propertyKey));
-                scriptContext->GetLibrary()->NoPrototypeChainsAreEnsuredToHaveOnlyWritableDataProperties();
-            }
+            library->GetTypesWithNoSpecialPropertyProtoChainCache()->ProcessProperty(this, attributes, propertyKey);
+            library->GetTypesWithOnlyWritablePropertyProtoChainCache()->ProcessProperty(this, attributes, propertyKey);
         }
     }
 
@@ -2006,12 +2000,8 @@ namespace Js
                 // Clearing the attribute may have changed the type handler, so make sure
                 // we access the current one.
                 DynamicTypeHandler *const typeHandler = GetCurrentTypeHandler(instance);
-                typeHandler->ClearHasOnlyWritableDataProperties();
-                if(typeHandler->GetFlags() & IsPrototypeFlag)
-                {
-                    instance->GetScriptContext()->InvalidateStoreFieldCaches(propertyId);
-                    instance->GetLibrary()->NoPrototypeChainsAreEnsuredToHaveOnlyWritableDataProperties();
-                }
+
+                instance->GetLibrary()->GetTypesWithOnlyWritablePropertyProtoChainCache()->ProcessProperty(typeHandler, PropertyNone, propertyId);
             }
         }
         return true;
@@ -2196,7 +2186,7 @@ namespace Js
         if (GetFlags() & IsPrototypeFlag)
         {
             InvalidateStoreFieldCachesForAllProperties(instance->GetScriptContext());
-            instance->GetLibrary()->NoPrototypeChainsAreEnsuredToHaveOnlyWritableDataProperties();
+            instance->GetLibrary()->GetTypesWithOnlyWritablePropertyProtoChainCache()->Clear();
         }
 
         return TRUE;
@@ -2477,15 +2467,8 @@ namespace Js
                 instance->SetHasNoEnumerableProperties(false);
             }
 
-            if (!(descriptor->Attributes & PropertyWritable))
-            {
-                this->ClearHasOnlyWritableDataProperties();
-                if(GetFlags() & IsPrototypeFlag)
-                {
-                    instance->GetScriptContext()->InvalidateStoreFieldCaches(propertyId);
-                    instance->GetLibrary()->NoPrototypeChainsAreEnsuredToHaveOnlyWritableDataProperties();
-                }
-            }
+            instance->GetLibrary()->GetTypesWithOnlyWritablePropertyProtoChainCache()->ProcessProperty(this, descriptor->Attributes, propertyId);
+
             SetPropertyUpdateSideEffect(instance, propertyId, value, possibleSideEffects);
             return true;
         }
@@ -2597,15 +2580,7 @@ namespace Js
                     instance->SetHasNoEnumerableProperties(false);
                 }
 
-                if (!(descriptor->Attributes & PropertyWritable))
-                {
-                    this->ClearHasOnlyWritableDataProperties();
-                    if(GetFlags() & IsPrototypeFlag)
-                    {
-                        instance->GetScriptContext()->InvalidateStoreFieldCaches(propertyId);
-                        instance->GetLibrary()->NoPrototypeChainsAreEnsuredToHaveOnlyWritableDataProperties();
-                    }
-                }
+                instance->GetLibrary()->GetTypesWithOnlyWritablePropertyProtoChainCache()->ProcessProperty(this, descriptor->Attributes, propertyId);
 
                 return true;
             }

+ 15 - 41
lib/Runtime/Types/SimpleTypeHandler.cpp

@@ -39,8 +39,8 @@ namespace Js
         NoWriteBarrierSet(descriptors[0].Id, id); // Used to init from global static BuiltInPropertyId
         descriptors[0].Attributes = attributes;
 
-        Assert((propertyTypes & (PropertyTypesAll & ~PropertyTypesWritableDataOnly)) == 0);
-        SetPropertyTypes(PropertyTypesWritableDataOnly, propertyTypes);
+        Assert((propertyTypes & (PropertyTypesAll & ~(PropertyTypesWritableDataOnly | PropertyTypesHasSpecialProperties))) == 0);
+        SetPropertyTypes(PropertyTypesWritableDataOnly | PropertyTypesHasSpecialProperties, propertyTypes);
         SetIsInlineSlotCapacityLocked();
     }
 
@@ -56,8 +56,8 @@ namespace Js
             NoWriteBarrierSet(descriptors[i].Id, SharedFunctionPropertyDescriptors[i].Id);
             descriptors[i].Attributes = SharedFunctionPropertyDescriptors[i].Attributes;
         }
-        Assert((propertyTypes & (PropertyTypesAll & ~PropertyTypesWritableDataOnly)) == 0);
-        SetPropertyTypes(PropertyTypesWritableDataOnly, propertyTypes);
+        Assert((propertyTypes & (PropertyTypesAll & ~(PropertyTypesWritableDataOnly | PropertyTypesHasSpecialProperties))) == 0);
+        SetPropertyTypes(PropertyTypesWritableDataOnly | PropertyTypesHasSpecialProperties, propertyTypes);
         SetIsInlineSlotCapacityLocked();
     }
 
@@ -97,7 +97,7 @@ namespace Js
 
         newTypeHandler->SetFlags(IsPrototypeFlag | HasKnownSlot0Flag, this->GetFlags());
         Assert(newTypeHandler->GetIsInlineSlotCapacityLocked());
-        newTypeHandler->SetPropertyTypes(PropertyTypesWritableDataOnly | PropertyTypesWritableDataOnlyDetection, this->GetPropertyTypes());
+        newTypeHandler->SetPropertyTypes(PropertyTypesWritableDataOnly | PropertyTypesWritableDataOnlyDetection | PropertyTypesHasSpecialProperties, this->GetPropertyTypes());
         newTypeHandler->SetInstanceTypeHandler(instance);
 
         return newTypeHandler;
@@ -143,7 +143,7 @@ namespace Js
         // inline slot capacity, or if we want to allow shrinking of the SimpleTypeHandler's inline slot capacity.
         Assert(!newTypeHandler->IsPathTypeHandler());
         Assert(newTypeHandler->GetIsInlineSlotCapacityLocked());
-        newTypeHandler->SetPropertyTypes(PropertyTypesWritableDataOnly | PropertyTypesWritableDataOnlyDetection, this->GetPropertyTypes());
+        newTypeHandler->SetPropertyTypes(PropertyTypesWritableDataOnly | PropertyTypesWritableDataOnlyDetection | PropertyTypesHasSpecialProperties, this->GetPropertyTypes());
         newTypeHandler->SetInstanceTypeHandler(instance);
 
 #if ENABLE_FIXED_FIELDS && DBG
@@ -203,7 +203,7 @@ namespace Js
             newTypeHandler->ShareTypeHandler(scriptContext);
         }
         newTypeHandler->SetFlags(IsPrototypeFlag | HasKnownSlot0Flag, this->GetFlags());
-        newTypeHandler->SetPropertyTypes(PropertyTypesWritableDataOnly | PropertyTypesWritableDataOnlyDetection, this->GetPropertyTypes());
+        newTypeHandler->SetPropertyTypes(PropertyTypesWritableDataOnly | PropertyTypesWritableDataOnlyDetection | PropertyTypesHasSpecialProperties, this->GetPropertyTypes());
         newTypeHandler->SetInstanceTypeHandler(instance, false);
         if (newTypeHandler->GetPredecessorType())
         {
@@ -789,12 +789,7 @@ namespace Js
                 // Clearing the attribute may have changed the type handler, so make sure
                 // we access the current one.
                 DynamicTypeHandler* const typeHandler = GetCurrentTypeHandler(instance);
-                typeHandler->ClearHasOnlyWritableDataProperties();
-                if (typeHandler->GetFlags() & IsPrototypeFlag)
-                {
-                    instance->GetScriptContext()->InvalidateStoreFieldCaches(propertyId);
-                    instance->GetLibrary()->NoPrototypeChainsAreEnsuredToHaveOnlyWritableDataProperties();
-                }
+                instance->GetLibrary()->GetTypesWithOnlyWritablePropertyProtoChainCache()->ProcessProperty(typeHandler, PropertyNone, propertyId);
             }
         }
         return true;
@@ -993,15 +988,7 @@ namespace Js
                 {
                     instance->SetHasNoEnumerableProperties(false);
                 }
-                if (!(attributes & PropertyWritable))
-                {
-                    typeHandler->ClearHasOnlyWritableDataProperties();
-                    if (typeHandler->GetFlags() & IsPrototypeFlag)
-                    {
-                        instance->GetScriptContext()->InvalidateStoreFieldCaches(propertyId);
-                        instance->GetLibrary()->NoPrototypeChainsAreEnsuredToHaveOnlyWritableDataProperties();
-                    }
-                }
+                instance->GetLibrary()->GetTypesWithOnlyWritablePropertyProtoChainCache()->ProcessProperty(typeHandler, attributes, propertyId);
             }
             SetSlotUnchecked(instance, index, value);
             PropertyValueInfo::Set(info, instance, static_cast<PropertyIndex>(index), descriptors[index].Attributes);
@@ -1037,15 +1024,7 @@ namespace Js
                 {
                     instance->SetHasNoEnumerableProperties(false);
                 }
-                if (!(descriptors[i].Attributes & PropertyWritable))
-                {
-                    this->ClearHasOnlyWritableDataProperties();
-                    if (GetFlags() & IsPrototypeFlag)
-                    {
-                        instance->GetScriptContext()->InvalidateStoreFieldCaches(propertyId);
-                        instance->GetLibrary()->NoPrototypeChainsAreEnsuredToHaveOnlyWritableDataProperties();
-                    }
-                }
+                instance->GetLibrary()->GetTypesWithOnlyWritablePropertyProtoChainCache()->ProcessProperty(this, descriptors[i].Attributes, propertyId);
                 return true;
             }
         }
@@ -1078,7 +1057,7 @@ namespace Js
     BOOL SimpleTypeHandler<size>::AddProperty(DynamicObject* instance, PropertyId propertyId, Var value, PropertyAttributes attributes, PropertyValueInfo* info, PropertyOperationFlags flags, SideEffects possibleSideEffects)
     {
         ScriptContext* scriptContext = instance->GetScriptContext();
-
+        JavascriptLibrary* library = scriptContext->GetLibrary();
 #if DBG
         PropertyIndex index;
         uint32 indexVal;
@@ -1106,15 +1085,10 @@ namespace Js
         {
             instance->SetHasNoEnumerableProperties(false);
         }
-        if (!(attributes & PropertyWritable))
-        {
-            this->ClearHasOnlyWritableDataProperties();
-            if (GetFlags() & IsPrototypeFlag)
-            {
-                instance->GetScriptContext()->InvalidateStoreFieldCaches(propertyId);
-                instance->GetLibrary()->NoPrototypeChainsAreEnsuredToHaveOnlyWritableDataProperties();
-            }
-        }
+
+        library->GetTypesWithOnlyWritablePropertyProtoChainCache()->ProcessProperty(this, attributes, propertyId);
+        library->GetTypesWithNoSpecialPropertyProtoChainCache()->ProcessProperty(this, attributes, propertyId);
+
         SetSlotUnchecked(instance, propertyCount, value);
         PropertyValueInfo::Set(info, instance, static_cast<PropertyIndex>(propertyCount), attributes);
         propertyCount++;

+ 31 - 2
lib/Runtime/Types/Type.cpp

@@ -58,10 +58,14 @@ namespace Js
         // for that property ID must be cleared on this new type after the type property cache is copied. Also, types are not
         // changed consistently to use this copy constructor, so those would need to be fixed as well.
 
-        if(type->AreThisAndPrototypesEnsuredToHaveOnlyWritableDataProperties())
+        if (type->AreThisAndPrototypesEnsuredToHaveOnlyWritableDataProperties())
         {
             SetAreThisAndPrototypesEnsuredToHaveOnlyWritableDataProperties(true);
         }
+        if(type->ThisAndPrototypesHaveNoSpecialProperties())
+        {
+            SetThisAndPrototypesHaveNoSpecialProperties(true);
+        }
         if(type->IsFalsy())
         {
             SetIsFalsy(true);
@@ -105,7 +109,7 @@ namespace Js
             }
 
             flags |= TypeFlagMask_AreThisAndPrototypesEnsuredToHaveOnlyWritableDataProperties;
-            javascriptLibrary->TypeAndPrototypesAreEnsuredToHaveOnlyWritableDataProperties(this);
+            javascriptLibrary->GetTypesWithOnlyWritablePropertyProtoChainCache()->Register(this);
         }
         else
         {
@@ -118,6 +122,31 @@ namespace Js
         return flags & TypeFlagMask_AreThisAndPrototypesEnsuredToHaveOnlyWritableDataProperties;
     }
 
+    void Type::SetThisAndPrototypesHaveNoSpecialProperties(const bool truth)
+    {
+        if (truth)
+        {
+            if (GetScriptContext()->IsClosed())
+            {
+                // The cache is disabled after the script context is closed, to avoid issues between being closed and being deleted,
+                // where the cache of these types in JavascriptLibrary may be reclaimed at any point
+                return;
+            }
+
+            flags |= TypeFlagMask_ThisAndPrototypesHaveNoSpecialProperties;
+            javascriptLibrary->GetTypesWithNoSpecialPropertyProtoChainCache()->Register(this);
+        }
+        else
+        {
+            flags &= ~TypeFlagMask_ThisAndPrototypesHaveNoSpecialProperties;
+        }
+    }
+
+    BOOL Type::ThisAndPrototypesHaveNoSpecialProperties() const
+    {
+        return flags & TypeFlagMask_ThisAndPrototypesHaveNoSpecialProperties;
+    }
+
     void Type::SetIsFalsy(const bool truth)
     {
         if (truth)

+ 4 - 1
lib/Runtime/Types/Type.h

@@ -12,7 +12,7 @@ enum TypeFlagMask : uint8
     TypeFlagMask_HasSpecialPrototype                                               = 0x04,
     TypeFlagMask_EngineExternal                                                    = 0x08,
     TypeFlagMask_SkipsPrototype                                                    = 0x10,
-    TypeFlagMask_RESERVED                                                          = 0x20,
+    TypeFlagMask_ThisAndPrototypesHaveNoSpecialProperties                          = 0x20,
     TypeFlagMask_JsrtExternal                                                      = 0x40,
     TypeFlagMask_HasBeenCached                                                     = 0x80
 };
@@ -60,6 +60,9 @@ namespace Js
         BOOL AreThisAndPrototypesEnsuredToHaveOnlyWritableDataProperties() const;
         void SetAreThisAndPrototypesEnsuredToHaveOnlyWritableDataProperties(const bool truth);
 
+        BOOL ThisAndPrototypesHaveNoSpecialProperties() const;
+        void SetThisAndPrototypesHaveNoSpecialProperties(const bool truth);
+
         inline BOOL IsExternal() const
         {
 #ifdef NTBUILD

+ 1 - 0
lib/Runtime/Types/TypeHandler.cpp

@@ -842,6 +842,7 @@ namespace Js
         Output::Print(_u("%*spropertyTypes: 0x%02x "), fieldIndent, padding, this->propertyTypes);
         if (this->propertyTypes & PropertyTypesReserved) Output::Print(_u("PropertyTypesReserved "));
         if (this->propertyTypes & PropertyTypesWritableDataOnly) Output::Print(_u("PropertyTypesWritableDataOnly "));
+        if (this->propertyTypes & PropertyTypesHasSpecialProperties) Output::Print(_u("PropertyTypesHasSpecialProperties "));
         if (this->propertyTypes & PropertyTypesWritableDataOnlyDetection) Output::Print(_u("PropertyTypesWritableDataOnlyDetection "));
         if (this->propertyTypes & PropertyTypesInlineSlotCapacityLocked) Output::Print(_u("PropertyTypesInlineSlotCapacityLocked "));
         Output::Print(_u("\n"));

+ 5 - 3
lib/Runtime/Types/TypeHandler.h

@@ -392,6 +392,8 @@ namespace Js
 
         PropertyTypes GetPropertyTypes() { Assert((propertyTypes & PropertyTypesReserved) != 0); return propertyTypes; }
         bool GetHasOnlyWritableDataProperties() { return (GetPropertyTypes() & PropertyTypesWritableDataOnly) == PropertyTypesWritableDataOnly; }
+        bool GetHasSpecialProperties() { return (GetPropertyTypes() & PropertyTypesHasSpecialProperties) == PropertyTypesHasSpecialProperties; }
+        void SetHasSpecialProperties() { propertyTypes |= PropertyTypesHasSpecialProperties; }
         // Do not use this method.  It's here only for the __proto__ performance workaround.
         void SetHasOnlyWritableDataProperties() { SetHasOnlyWritableDataProperties(true); }
         void ClearHasOnlyWritableDataProperties() { SetHasOnlyWritableDataProperties(false); };
@@ -618,12 +620,12 @@ namespace Js
 
         virtual BigPropertyIndex PropertyIndexToPropertyEnumeration(BigPropertyIndex index) const { return index; }
 
+        static PropertyId TMapKey_GetPropertyId(ScriptContext* scriptContext, const PropertyId key);
+        static PropertyId TMapKey_GetPropertyId(ScriptContext* scriptContext, const PropertyRecord* key);
+        static PropertyId TMapKey_GetPropertyId(ScriptContext* scriptContext, JavascriptString* key);
     protected:
         void SetPropertyUpdateSideEffect(DynamicObject* instance, PropertyId propertyId, Var value, SideEffects possibleSideEffects);
         void SetPropertyUpdateSideEffect(DynamicObject* instance, JsUtil::CharacterBuffer<WCHAR> const& propertyName, Var value, SideEffects possibleSideEffects);
-        PropertyId TMapKey_GetPropertyId(ScriptContext* scriptContext, const PropertyId key);
-        PropertyId TMapKey_GetPropertyId(ScriptContext* scriptContext, const PropertyRecord* key);
-        PropertyId TMapKey_GetPropertyId(ScriptContext* scriptContext, JavascriptString* key);
         bool VerifyIsExtensible(ScriptContext* scriptContext, bool alwaysThrow);
 
         void SetOffsetOfInlineSlots(const uint16 offsetOfInlineSlots) { this->offsetOfInlineSlots = offsetOfInlineSlots; }