diff --git a/lib/Backend/Lower.cpp b/lib/Backend/Lower.cpp index a4486bf8bef..6a42fc5ffb3 100644 --- a/lib/Backend/Lower.cpp +++ b/lib/Backend/Lower.cpp @@ -1533,6 +1533,10 @@ Lowerer::LowerRange(IR::Instr *instrStart, IR::Instr *instrEnd, bool defaultDoFa break; case Js::OpCode::IsInst: +#ifdef ENABLE_TEST_HOOKS + // Don't use the fast path if we are in this phase + if (!PHASE_TRACE1(Js::IsInstInlineCacheInvalidationPhase)) +#endif this->GenerateFastIsInst(instr); instrPrev = this->LowerIsInst(instr, IR::HelperScrObj_OP_IsInst); break; diff --git a/lib/Common/ConfigFlagsList.h b/lib/Common/ConfigFlagsList.h index 1777127109c..1dd29997cc6 100644 --- a/lib/Common/ConfigFlagsList.h +++ b/lib/Common/ConfigFlagsList.h @@ -1,6 +1,6 @@ //------------------------------------------------------------------------------------------------------- // Copyright (C) Microsoft Corporation and contributors. All rights reserved. -// Copyright (c) 2021 ChakraCore Project Contributors. All rights reserved. +// Copyright (c) ChakraCore Project Contributors. All rights reserved. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. //------------------------------------------------------------------------------------------------------- @@ -331,6 +331,7 @@ PHASE(All) PHASE(RegexQc) PHASE(RegexOptBT) PHASE(InlineCache) + PHASE(IsInstInlineCacheInvalidation) PHASE(PolymorphicInlineCache) PHASE(MissingPropertyCache) PHASE(PropertyCache) // Trace caching of property lookups using PropertyString and JavascriptSymbol diff --git a/lib/Runtime/Base/ThreadContext.h b/lib/Runtime/Base/ThreadContext.h index ec823a39986..b0b7f8df990 100644 --- a/lib/Runtime/Base/ThreadContext.h +++ b/lib/Runtime/Base/ThreadContext.h @@ -1,5 +1,6 @@ //------------------------------------------------------------------------------------------------------- // Copyright (C) Microsoft. All rights reserved. +// Copyright (c) ChakraCore Project Contributors. All rights reserved. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. //------------------------------------------------------------------------------------------------------- #pragma once @@ -1384,6 +1385,14 @@ class ThreadContext sealed : #endif public: + template + void MapIsInstInlineCaches(Fn fn) const + { + isInstInlineCacheByFunction.Map([fn](const Js::Var function, Js::IsInstInlineCache* inlineCacheList) { + fn(function, inlineCacheList); + }); + } + void InvalidateIsInstInlineCachesForFunction(Js::Var function); void InvalidateAllIsInstInlineCaches(); bool AreAllIsInstInlineCachesInvalidated() const; diff --git a/lib/Runtime/Library/JavascriptFunction.cpp b/lib/Runtime/Library/JavascriptFunction.cpp index f92a56be862..a64ad0a36d9 100644 --- a/lib/Runtime/Library/JavascriptFunction.cpp +++ b/lib/Runtime/Library/JavascriptFunction.cpp @@ -1,5 +1,6 @@ //------------------------------------------------------------------------------------------------------- // Copyright (C) Microsoft. All rights reserved. +// Copyright (c) ChakraCore Project Contributors. All rights reserved. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. //------------------------------------------------------------------------------------------------------- #include "RuntimeLibraryPch.h" @@ -3389,8 +3390,16 @@ void __cdecl _alloca_probe_16() Assert(function != nullptr); if (inlineCache->TryGetResult(instance, function, &javascriptResult)) { +#ifdef ENABLE_TEST_HOOKS + if (PHASE_TRACE1(Js::IsInstInlineCacheInvalidationPhase)) + Output::Print(_u("CacheHit")); +#endif return javascriptResult == scriptContext->GetLibrary()->GetTrue(); } +#ifdef ENABLE_TEST_HOOKS + if (PHASE_TRACE1(Js::IsInstInlineCacheInvalidationPhase)) + Output::Print(_u("NoCacheHit")); +#endif } // If we are here, then me must have missed the cache. This may be because: diff --git a/lib/Runtime/Library/JavascriptObject.cpp b/lib/Runtime/Library/JavascriptObject.cpp index b38b535db43..57a422ad7c4 100644 --- a/lib/Runtime/Library/JavascriptObject.cpp +++ b/lib/Runtime/Library/JavascriptObject.cpp @@ -1,6 +1,6 @@ //------------------------------------------------------------------------------------------------------- // Copyright (C) Microsoft. All rights reserved. -// Copyright (c) 2021 ChakraCore Project Contributors. All rights reserved. +// Copyright (c) ChakraCore Project Contributors. All rights reserved. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. //------------------------------------------------------------------------------------------------------- #include "RuntimeLibraryPch.h" @@ -247,6 +247,43 @@ BOOL JavascriptObject::ChangePrototype(RecyclableObject* object, RecyclableObjec if (isInvalidationOfInlineCacheNeeded) { + // Invalidate the "instanceof" cache + ThreadContext* threadContext = scriptContext->GetThreadContext(); + threadContext->MapIsInstInlineCaches([threadContext, object](const Js::Var function, Js::IsInstInlineCache* inlineCacheList) { + Assert(inlineCacheList != nullptr); + + Js::IsInstInlineCache* curInlineCache; + Js::IsInstInlineCache* nextInlineCache; + for (curInlineCache = inlineCacheList; curInlineCache != nullptr; curInlineCache = nextInlineCache) + { + // Stash away the next cache before we potentially zero out current one + nextInlineCache = curInlineCache->next; + + // `type` might be null (See https://github.com/chakra-core/ChakraCore/blob/0cfe82d202c0298f4bbde06070f8dbf9099946c8/lib/Runtime/Library/JavascriptFunction.cpp#L3365-L3367) + if (curInlineCache->type == nullptr) + continue; + + bool clearCurrentCache = curInlineCache->type == object->GetType(); + if (!clearCurrentCache) { + // Check if function prototype contains old prototype + JavascriptOperators::MapObjectAndPrototypes(curInlineCache->type->GetPrototype(), [&](RecyclableObject* obj) + { + if (object->GetType() == obj->GetType()) + clearCurrentCache = true; + }); + } + + if (clearCurrentCache) + { + // Fix cache list + // Deletes empty entries + threadContext->UnregisterIsInstInlineCache(curInlineCache, function); + // Actually invalidate current cache + memset(curInlineCache, 0, sizeof(Js::IsInstInlineCache)); + } + } + }); + bool allProtoCachesInvalidated = false; JavascriptOperators::MapObjectAndPrototypes(newPrototype, [&](RecyclableObject* obj) diff --git a/test/InlineCaches/isinstanceof.baseline b/test/InlineCaches/isinstanceof.baseline new file mode 100644 index 00000000000..5297517ae33 --- /dev/null +++ b/test/InlineCaches/isinstanceof.baseline @@ -0,0 +1,15 @@ +NoCacheHit:Box:Component: false +NoCacheHit:Component:ComponentBase: true +NoCacheHit:Health:Component: true +NoCacheHit:Shape:ShapeBase: true +NoCacheHit:ShapeBase:ShapeBaseBase: true + -> Change prototype +NoCacheHitNoCacheHit:Box:Component: true true +CacheHitNoCacheHit:Component:ComponentBase: true true +CacheHitNoCacheHit:Health:Component: true true +NoCacheHitNoCacheHit:Shape:ShapeBase: false false +CacheHitNoCacheHit:ShapeBase:ShapeBaseBase: true true +==================== +NoCacheHit:Box:Circle false + -> Change prototype +CacheHitNoCacheHit:Box:Circle false false diff --git a/test/InlineCaches/isinstanceof.js b/test/InlineCaches/isinstanceof.js new file mode 100644 index 00000000000..51fcfc59bbc --- /dev/null +++ b/test/InlineCaches/isinstanceof.js @@ -0,0 +1,96 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Copyright (c) ChakraCore Project Contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +// @ts-check + +function inherit(child, parent) { + child.prototype = Object.create(parent.prototype); + child.prototype.constructor = child; +} + +// #region setup "classes" +function ComponentBase() { } +function Component() { } +inherit(Component, ComponentBase); + +function Health() { } +inherit(Health, Component); + +function ShapeBaseBase() { } +function ShapeBase() { } +inherit(ShapeBase, ShapeBaseBase); +function Shape() { } +inherit(Shape, ShapeBase); + +function Box() { } +inherit(Box, Shape); + +function Circle() { } +inherit(Circle, Shape); +// #endregion + +// #region check functions +function checkA(a, b) { + return a instanceof b; +} + +function checkB(a, b) { + return a instanceof b; +} + +function checkC(a, b) { + return a instanceof b; +} + +function checkD(a, b) { + return a instanceof b; +} + +function checkE(a, b) { + return a instanceof b; +} +// #endregion + +console.log(':Box:Component:', checkA(Box.prototype, Component)); + +console.log(':Component:ComponentBase:', checkB(Component.prototype, ComponentBase)); + +console.log(':Health:Component:', checkC(Health.prototype, Component)); + +console.log(':Shape:ShapeBase:', checkD(Shape.prototype, ShapeBase)); + +console.log(':ShapeBase:ShapeBaseBase:', checkE(ShapeBase.prototype, ShapeBaseBase)); + +console.log(" -> Change prototype"); +Object.setPrototypeOf(Shape.prototype, Component.prototype); + +// Box inherits Shape (changed) -> should invalidate cache (NoCacheHit) +console.log(':Box:Component:', checkA(Box.prototype, Component), Box.prototype instanceof Component); + +// New prototype chain remains valid (CacheHit) +console.log(':Component:ComponentBase:', checkB(Component.prototype, ComponentBase), Component.prototype instanceof ComponentBase); + +// Seperate prototype chain remains valid (CacheHit) +console.log(':Health:Component:', checkC(Health.prototype, Component), Health.prototype instanceof Component); + +// We changed that! (NoCacheHit) +console.log(':Shape:ShapeBase:', checkD(Shape.prototype, ShapeBase), Shape.prototype instanceof ShapeBase); + +// Old prototype chain remains valid (CacheHit) +console.log(':ShapeBase:ShapeBaseBase:', checkE(ShapeBase.prototype, ShapeBaseBase), ShapeBase.prototype instanceof ShapeBaseBase); + + +console.log("===================="); + + +console.log(':Box:Circle', checkA(Box.prototype, Circle)); + +console.log(" -> Change prototype"); +Object.setPrototypeOf(Circle.prototype, Shape.prototype); + +// Modifying the function (constructor) does not invalidate the cache +console.log(':Box:Circle', checkA(Box.prototype, Circle), Box.prototype instanceof Circle); + diff --git a/test/InlineCaches/rlexe.xml b/test/InlineCaches/rlexe.xml index 9a684a2f88e..7b4bddafddd 100644 --- a/test/InlineCaches/rlexe.xml +++ b/test/InlineCaches/rlexe.xml @@ -137,4 +137,11 @@ bug_vso_os_1206083.baseline + + + isinstanceof.js + -trace:IsInstInlineCacheInvalidation + isinstanceof.baseline + +