Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Invalidate IsInstInlineCache in Object.setPrototypeOf #6858

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
4 changes: 4 additions & 0 deletions lib/Backend/Lower.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 2 additions & 1 deletion lib/Common/ConfigFlagsList.h
Original file line number Diff line number Diff line change
@@ -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.
//-------------------------------------------------------------------------------------------------------

Expand Down Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions lib/Runtime/Base/ThreadContext.h
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -1384,6 +1385,14 @@ class ThreadContext sealed :
#endif

public:
template<class Fn>
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;
Expand Down
9 changes: 9 additions & 0 deletions lib/Runtime/Library/JavascriptFunction.cpp
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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:
Expand Down
39 changes: 38 additions & 1 deletion lib/Runtime/Library/JavascriptObject.cpp
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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<true>(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<true>(newPrototype, [&](RecyclableObject* obj)
Expand Down
15 changes: 15 additions & 0 deletions test/InlineCaches/isinstanceof.baseline
Original file line number Diff line number Diff line change
@@ -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
96 changes: 96 additions & 0 deletions test/InlineCaches/isinstanceof.js
Original file line number Diff line number Diff line change
@@ -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);

7 changes: 7 additions & 0 deletions test/InlineCaches/rlexe.xml
Original file line number Diff line number Diff line change
Expand Up @@ -137,4 +137,11 @@
<baseline>bug_vso_os_1206083.baseline</baseline>
</default>
</test>
<test>
<default>
<files>isinstanceof.js</files>
<compile-flags>-trace:IsInstInlineCacheInvalidation</compile-flags>
<baseline>isinstanceof.baseline</baseline>
</default>
</test>
</regress-exe>