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

Type sharing for __proto__ #1489

Merged
merged 1 commit into from
Sep 15, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/Common/ConfigFlagsList.h
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ PHASE(All)
PHASE(StackFramesEvent)
#endif
PHASE(PerfHint)
PHASE(TypeShareForChangePrototype)
PHASE(DeferSourceLoad)
PHASE(ObjectMutationBreakpoint)
#undef PHASE
Expand Down
3 changes: 2 additions & 1 deletion lib/Jsrt/JsrtExternalObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ JsrtExternalType::JsrtExternalType(Js::ScriptContext* scriptContext, JsFinalizeC
true)
, jsFinalizeCallback(finalizeCallback)
{
this->flags |= TypeFlagMask_JsrtExternal;
}

JsrtExternalObject::JsrtExternalObject(JsrtExternalType * type, void *data) :
slot(data),
Js::DynamicObject(type)
Js::DynamicObject(type, false/* initSlots*/)
{
}

Expand Down
23 changes: 13 additions & 10 deletions lib/Runtime/InternalPropertyList.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@
// They become nameless compile time known PropertyRecords, stored as static
// fields on the InternalPropertyRecords class.

INTERNALPROPERTY(TypeOfPrototypeObject) // Used to store the type of the prototype object in the prototype objects slots
INTERNALPROPERTY(NonExtensibleType) // Used to store shared non-extensible type in PathTypeHandler::propertySuccessors map.
INTERNALPROPERTY(SealedType) // Used to store shared sealed type in PathTypeHandler::propertySuccessors map.
INTERNALPROPERTY(FrozenType) // Used to store shared frozen type in PathTypeHandler::propertySuccessors map.
INTERNALPROPERTY(StackTrace) // Stack trace object for Error.stack generation
INTERNALPROPERTY(StackTraceCache) // Cache of Error.stack string
INTERNALPROPERTY(WeakMapKeyMap) // WeakMap data stored on WeakMap key objects
INTERNALPROPERTY(HiddenObject) // Used to store hidden data for JS library code (Intl as an example will use this)
INTERNALPROPERTY(RevocableProxy) // Internal slot for [[RevokableProxy]] for revocable proxy in ES6
INTERNALPROPERTY(MutationBp) // Used to store strong reference to the mutation breakpoint object
INTERNALPROPERTY(TypeOfPrototypeObjectInlined) // Used to store the type of the prototype object in the prototype objects slots. Only DynamicTypes having TypeIds_Object are saved in this slot.
// Used to store the type of the prototype object in the prototype objects slots. Everything else (except ExternalType) are stored in this slot as Dictionary.
// Key in the Dictionary is combination of Type and TypeId and value is dynamicType object.
INTERNALPROPERTY(TypeOfPrototypeObjectDictionary)
INTERNALPROPERTY(NonExtensibleType) // Used to store shared non-extensible type in PathTypeHandler::propertySuccessors map.
INTERNALPROPERTY(SealedType) // Used to store shared sealed type in PathTypeHandler::propertySuccessors map.
INTERNALPROPERTY(FrozenType) // Used to store shared frozen type in PathTypeHandler::propertySuccessors map.
INTERNALPROPERTY(StackTrace) // Stack trace object for Error.stack generation
INTERNALPROPERTY(StackTraceCache) // Cache of Error.stack string
INTERNALPROPERTY(WeakMapKeyMap) // WeakMap data stored on WeakMap key objects
INTERNALPROPERTY(HiddenObject) // Used to store hidden data for JS library code (Intl as an example will use this)
INTERNALPROPERTY(RevocableProxy) // Internal slot for [[RevokableProxy]] for revocable proxy in ES6
INTERNALPROPERTY(MutationBp) // Used to store strong reference to the mutation breakpoint object
#undef INTERNALPROPERTY
810 changes: 405 additions & 405 deletions lib/Runtime/Library/InJavascript/Intl.js.bc.32b.h

Large diffs are not rendered by default.

808 changes: 404 additions & 404 deletions lib/Runtime/Library/InJavascript/Intl.js.bc.64b.h

Large diffs are not rendered by default.

794 changes: 397 additions & 397 deletions lib/Runtime/Library/InJavascript/Intl.js.nojit.bc.32b.h

Large diffs are not rendered by default.

808 changes: 404 additions & 404 deletions lib/Runtime/Library/InJavascript/Intl.js.nojit.bc.64b.h

Large diffs are not rendered by default.

79 changes: 77 additions & 2 deletions lib/Runtime/Library/JavascriptLibrary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6217,8 +6217,16 @@ namespace Js

DynamicType* dynamicType = nullptr;
const bool useCache = prototype->GetScriptContext() == this->scriptContext;
#if DBG
DynamicType* oldCachedType = nullptr;
char16 reason[1024];
swprintf_s(reason, 1024, _u("Cache not populated."));
#endif
// Always use `TypeOfPrototypeObjectInlined` because we are creating DynamicType of TypeIds_Object
AssertMsg(typeId == TypeIds_Object, "CreateObjectType() is used to create other objects. Please update cacheSlot for protoObjectCache first.");

if (useCache &&
prototype->GetInternalProperty(prototype, Js::InternalPropertyIds::TypeOfPrototypeObject, (Js::Var*) &dynamicType, nullptr, this->scriptContext))
prototype->GetInternalProperty(prototype, Js::InternalPropertyIds::TypeOfPrototypeObjectInlined, (Js::Var*) &dynamicType, nullptr, this->scriptContext))
{
//If the prototype is externalObject, then ExternalObject::Reinitialize can set all the properties to undefined in navigation scenario.
//Check to make sure dynamicType which is stored as a Js::Var is not undefined.
Expand All @@ -6237,17 +6245,84 @@ namespace Js
))
{
Assert(dynamicType->GetIsShared());

if (PHASE_TRACE1(TypeShareForChangePrototypePhase))
{
#if DBG
if (PHASE_VERBOSE_TRACE1(TypeShareForChangePrototypePhase))
{
Output::Print(_u("TypeSharing: Reusing prototype [0x%p] object's InlineSlot cache 0x%p in CreateObject.\n"), prototype, dynamicType);
}
else
{
#endif
Output::Print(_u("TypeSharing: Reusing prototype object's InlineSlot cache in __proto__.\n"));
#if DBG
}
#endif
Output::Flush();
}

return dynamicType;
}
#if DBG
if (PHASE_VERBOSE_TRACE1(TypeShareForChangePrototypePhase))
{
if (dynamicTypeHandler->IsObjectHeaderInlinedTypeHandler() != useObjectHeaderInlining)
{
swprintf_s(reason, 1024, _u("useObjectHeaderInlining mismatch."));
}
else
{
uint16 cachedCapacity = dynamicTypeHandler->GetInlineSlotCapacity();
uint16 requiredCapacity = useObjectHeaderInlining
? DynamicTypeHandler::RoundUpObjectHeaderInlinedInlineSlotCapacity(requestedInlineSlotCapacity)
: DynamicTypeHandler::RoundUpInlineSlotCapacity(requestedInlineSlotCapacity);

swprintf_s(reason, 1024, _u("inlineSlotCapacity mismatch. Required = %d, Cached = %d"), requiredCapacity, cachedCapacity);
}
}
#endif
}

}

#if DBG
if (PHASE_VERBOSE_TRACE1(TypeShareForChangePrototypePhase))
{
if (dynamicType == nullptr)
{
swprintf_s(reason, 1024, _u("cached type was null"));
}
else if ((Js::Var)dynamicType == this->GetUndefined())
{
swprintf_s(reason, 1024, _u("cached type was undefined"));
}
}
oldCachedType = dynamicType;
#endif
SimplePathTypeHandler* typeHandler = SimplePathTypeHandler::New(scriptContext, this->GetRootPath(), 0, requestedInlineSlotCapacity, offsetOfInlineSlots, true, true);
dynamicType = DynamicType::New(scriptContext, typeId, prototype, RecyclableObject::DefaultEntryPoint, typeHandler, true, true);

if (useCache)
{
prototype->SetInternalProperty(Js::InternalPropertyIds::TypeOfPrototypeObject, (Var)dynamicType, PropertyOperationFlags::PropertyOperation_Force, nullptr);
prototype->SetInternalProperty(Js::InternalPropertyIds::TypeOfPrototypeObjectInlined, (Var)dynamicType, PropertyOperationFlags::PropertyOperation_Force, nullptr);
if (PHASE_TRACE1(TypeShareForChangePrototypePhase))
{
#if DBG
if (PHASE_VERBOSE_TRACE1(TypeShareForChangePrototypePhase))
{
Output::Print(_u("TypeSharing: Updating prototype [0x%p] object's InlineSlot cache from 0x%p to 0x%p in CreateObject. Reason = %s\n"), prototype, oldCachedType, dynamicType, reason);
}
else
{
#endif
Output::Print(_u("TypeSharing: Updating prototype object's InlineSlot cache in CreateObject.\n"));
#if DBG
}
#endif
Output::Flush();
}
}

return dynamicType;
Expand Down
151 changes: 150 additions & 1 deletion lib/Runtime/Types/PathTypeHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1376,7 +1376,156 @@ namespace Js

void PathTypeHandlerBase::SetPrototype(DynamicObject* instance, RecyclableObject* newPrototype)
{
ConvertToSimpleDictionaryType(instance, GetPathLength())->SetPrototype(instance, newPrototype);
// No typesharing for ExternalType
if (instance->GetType()->IsExternal())
{
ConvertToSimpleDictionaryType(instance, GetPathLength())->SetPrototype(instance, newPrototype);
return;
}

const bool useObjectHeaderInlining = IsObjectHeaderInlined(this->GetOffsetOfInlineSlots());
uint16 requestedInlineSlotCapacity = this->GetInlineSlotCapacity();
uint16 roundedInlineSlotCapacity = (useObjectHeaderInlining ?
DynamicTypeHandler::RoundUpObjectHeaderInlinedInlineSlotCapacity(requestedInlineSlotCapacity) :
DynamicTypeHandler::RoundUpInlineSlotCapacity(requestedInlineSlotCapacity));
ScriptContext* scriptContext = instance->GetScriptContext();
DynamicType* cachedDynamicType = nullptr;
BOOL isJsrtExternalType = instance->GetType()->IsJsrtExternal();
DynamicType* oldType = isJsrtExternalType ? 0 : instance->GetDynamicType();

bool useCache = instance->GetScriptContext() == newPrototype->GetScriptContext();

TypeTransitionMap * oldTypeToPromotedTypeMap = nullptr;
#if DBG
DynamicType * oldCachedType = nullptr;
char16 reason[1024];
swprintf_s(reason, 1024, _u("Cache not populated."));
#endif
AssertMsg(isJsrtExternalType || typeid(DynamicType) == typeid(*oldType), "PathTypeHandler is used either by JsrtExternalType or DynamicType");

if (useCache && newPrototype->GetInternalProperty(newPrototype, Js::InternalPropertyIds::TypeOfPrototypeObjectDictionary, (Js::Var*)&oldTypeToPromotedTypeMap, nullptr, scriptContext))
{
Assert(oldTypeToPromotedTypeMap && (Js::Var)oldTypeToPromotedTypeMap != scriptContext->GetLibrary()->GetUndefined());
oldTypeToPromotedTypeMap = reinterpret_cast<TypeTransitionMap*>(oldTypeToPromotedTypeMap);

if (oldTypeToPromotedTypeMap->TryGetValue(reinterpret_cast<uintptr_t>(oldType), &cachedDynamicType))
{
#if DBG
oldCachedType = cachedDynamicType;
#endif
DynamicTypeHandler *const cachedDynamicTypeHandler = cachedDynamicType->GetTypeHandler();
if (cachedDynamicTypeHandler->GetOffsetOfInlineSlots() != GetOffsetOfInlineSlots())
{
cachedDynamicType = nullptr;
#if DBG
swprintf_s(reason, 1024, _u("OffsetOfInlineSlot mismatch. Required = %d, Cached = %d"), this->GetOffsetOfInlineSlots(), cachedDynamicTypeHandler->GetOffsetOfInlineSlots());
#endif
}
else if (cachedDynamicTypeHandler->GetInlineSlotCapacity() != roundedInlineSlotCapacity)
{
Assert(cachedDynamicTypeHandler->GetInlineSlotCapacity() >= roundedInlineSlotCapacity);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also confirm that it's safe to shrink the capacity in this way by verifying that the total number of properties <= the current inlineSlotCapacity?

Assert(cachedDynamicTypeHandler->GetInlineSlotCapacity() >= GetPropertyCount());
cachedDynamicTypeHandler->ShrinkSlotAndInlineSlotCapacity();
}
}
}
else
{
Assert(!oldTypeToPromotedTypeMap || (Js::Var)oldTypeToPromotedTypeMap == scriptContext->GetLibrary()->GetUndefined());
oldTypeToPromotedTypeMap = nullptr;
}

if (cachedDynamicType == nullptr)
{
SimplePathTypeHandler* newTypeHandler = SimplePathTypeHandler::New(scriptContext, scriptContext->GetLibrary()->GetRootPath(), 0, this->GetInlineSlotCapacity(), this->GetOffsetOfInlineSlots(), true, true);

cachedDynamicType = instance->DuplicateType();
cachedDynamicType->typeHandler = newTypeHandler;

// Make type locked, shared only if we are using cache
if (useCache)
{
cachedDynamicType->LockType();
cachedDynamicType->ShareType();
}

// Promote type based on existing properties to get new type which will be cached and shared
for (PropertyIndex i = 0; i < GetPropertyCount(); i++)
{
PathTypeHandlerBase * pathTypeHandler = (PathTypeHandlerBase*)cachedDynamicType->GetTypeHandler();
Js::PropertyId propertyId = GetPropertyId(scriptContext, i);

PropertyIndex propertyIndex = GetPropertyIndex(propertyId);
cachedDynamicType = pathTypeHandler->PromoteType<true>(cachedDynamicType, scriptContext->GetPropertyName(propertyId), true, scriptContext, nullptr, &propertyIndex);
}

if (useCache)
{
if (oldTypeToPromotedTypeMap == nullptr)
{
oldTypeToPromotedTypeMap = RecyclerNew(instance->GetRecycler(), TypeTransitionMap, instance->GetRecycler(), 2);
newPrototype->SetInternalProperty(Js::InternalPropertyIds::TypeOfPrototypeObjectDictionary, (Var)oldTypeToPromotedTypeMap, PropertyOperationFlags::PropertyOperation_Force, nullptr);
}

// oldType is kind of weakReference here
oldTypeToPromotedTypeMap->Item(reinterpret_cast<uintptr_t>(oldType), cachedDynamicType);

if (PHASE_TRACE1(TypeShareForChangePrototypePhase))
{
#if DBG
if (PHASE_VERBOSE_TRACE1(TypeShareForChangePrototypePhase))
{
Output::Print(_u("TypeSharing: Updating prototype [0x%p] object's DictionarySlot in __proto__. Adding (key = 0x%p, value = 0x%p) in map = 0x%p. Reason = %s\n"), newPrototype, oldType, cachedDynamicType, oldTypeToPromotedTypeMap, reason);
}
else
{
#endif
Output::Print(_u("TypeSharing: Updating prototype object's DictionarySlot cache in __proto__.\n"));
#if DBG
}
#endif
Output::Flush();
}

}
else
{
if (PHASE_TRACE1(TypeShareForChangePrototypePhase) || PHASE_VERBOSE_TRACE1(TypeShareForChangePrototypePhase))
{
Output::Print(_u("TypeSharing: No Typesharing because instance and newPrototype are from different scriptContext.\n"));
Output::Flush();
}
}
}
else
{
Assert(cachedDynamicType->GetIsShared());
if (PHASE_TRACE1(TypeShareForChangePrototypePhase))
{
#if DBG
if (PHASE_VERBOSE_TRACE1(TypeShareForChangePrototypePhase))
{

Output::Print(_u("TypeSharing: Reusing prototype [0x%p] object's DictionarySlot (key = 0x%p, value = 0x%p) from map = 0x%p in __proto__.\n"), newPrototype, oldType, cachedDynamicType, oldTypeToPromotedTypeMap);
}
else
{
#endif
Output::Print(_u("TypeSharing: Reusing prototype object's DictionarySlot cache in __proto__.\n"));
#if DBG
}
#endif
Output::Flush();
}
}


// Make sure the offsetOfInlineSlots and inlineSlotCapacity matches with currentTypeHandler
Assert(cachedDynamicType->typeHandler->GetOffsetOfInlineSlots() == GetOffsetOfInlineSlots());
Assert(cachedDynamicType->typeHandler->GetInlineSlotCapacity() == roundedInlineSlotCapacity);

cachedDynamicType->SetPrototype(newPrototype);
instance->ReplaceType(cachedDynamicType);
}

void PathTypeHandlerBase::SetIsPrototype(DynamicObject* instance)
Expand Down
1 change: 1 addition & 0 deletions lib/Runtime/Types/PathTypeHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ namespace Js

public:
DEFINE_GETCPPNAME();
typedef JsUtil::BaseDictionary<uintptr_t, DynamicType *, Recycler, PowerOf2SizePolicy> TypeTransitionMap;

protected:
PathTypeHandlerBase(TypePath* typePath, uint16 pathLength, const PropertyIndex slotCapacity, uint16 inlineSlotCapacity, uint16 offsetOfInlineSlots, bool isLocked = false, bool isShared = false, DynamicType* predecessorType = nullptr);
Expand Down
2 changes: 2 additions & 0 deletions lib/Runtime/Types/Type.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ enum TypeFlagMask : uint8
TypeFlagMask_External = 0x08,
TypeFlagMask_SkipsPrototype = 0x10,
TypeFlagMask_CanHaveInterceptors = 0x20,
TypeFlagMask_JsrtExternal = 0x40
};
ENUM_CLASS_HELPERS(TypeFlagMask, uint8);

Expand Down Expand Up @@ -59,6 +60,7 @@ namespace Js
void SetAreThisAndPrototypesEnsuredToHaveOnlyWritableDataProperties(const bool truth);

inline BOOL IsExternal() const { return (this->flags & TypeFlagMask_External) != 0; }
inline BOOL IsJsrtExternal() const { return (this->flags & TypeFlagMask_JsrtExternal) != 0; }
inline BOOL SkipsPrototype() const { return (this->flags & TypeFlagMask_SkipsPrototype) != 0 ; }
inline BOOL CanHaveInterceptors() const { return (this->flags & TypeFlagMask_CanHaveInterceptors) != 0; }
inline BOOL IsFalsy() const { return flags & TypeFlagMask_IsFalsy; }
Expand Down
28 changes: 28 additions & 0 deletions test/Prototypes/ChangePrototype.baseline
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
TypeSharing: Updating prototype object's InlineSlot cache in CreateObject.
TypeSharing: Updating prototype object's InlineSlot cache in CreateObject.
TypeSharing: Updating prototype object's DictionarySlot cache in __proto__.
obj1.protoProp = 1
TypeSharing: Updating prototype object's DictionarySlot cache in __proto__.
obj3.protoProp = 1
TypeSharing: Updating prototype object's DictionarySlot cache in __proto__.
obj2.protoProp = 1
TypeSharing: Updating prototype object's DictionarySlot cache in __proto__.
date1.protoProp = 1
TypeSharing: Updating prototype object's DictionarySlot cache in __proto__.
date2.protoProp = 1
TypeSharing: Updating prototype object's DictionarySlot cache in __proto__.
re.protoProp = 1
TypeSharing: Updating prototype object's DictionarySlot cache in __proto__.
buff.protoProp = 1
TypeSharing: Updating prototype object's DictionarySlot cache in __proto__.
i8.protoProp = 1
TypeSharing: Updating prototype object's DictionarySlot cache in __proto__.
i16.protoProp = 1
TypeSharing: Updating prototype object's DictionarySlot cache in __proto__.
i8_custom.protoProp = 1
done
TypeSharing: Updating prototype object's InlineSlot cache in CreateObject.
Changing __proto__
TypeSharing: Updating prototype object's DictionarySlot cache in __proto__.
Changing __proto__
TypeSharing: Reusing prototype object's DictionarySlot cache in __proto__.
Loading