blob: b2ca12d14d551cbd51ec2784220d1cb6569ab5de [file]
//-------------------------------------------------------------------------------------------------------
// 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
{
typedef void* WeakMapId;
class JavascriptWeakMap : public DynamicObject
{
private:
// WeakMapKeyMap is the data that is kept alive by the key object itself so
// that the lifetime of the WeakMap mapping from key to value is tied to the
// lifetime of the key object. The WeakMap itself contains only a weak
// reference to this data for the purpose of removing all references in the
// Clear() and Finalize() methods, as well as enumeration in the debugger.
//
// Currently the WeakMapKeyMap object is stored in an internal property slot
// on the key object. This is a problem though because making an object the
// key of a WeakMap will then change the object's type and invalidate
// caching and JIT assumptions.
//
// One alternative idea to using an internal property slot to hold the
// WeakMapKeyMap on the key object is to use the arrayOrFlags field on
// DynamicObject. E.g. subclass JavascriptArray with a version that is
// both an array and has the extra data on it, allowing it to be placed in
// the arrayOrFlags field. There are problems with this approach though:
//
// 1. arrayOrFlags is tied to sensitive performance optimizations and
// changing it will be met with difficulty in trying to maintain
// current benchmark performance
//
// 2. Not all objects are DynamicObject, so there are still keys that
// will not have the arrayOrFlags field (e.g. HostDispatch). Perhaps
// such objects should not be permitted to be keys on WeakMap?
//
// Regardless of this idea, the ideal would be to have InternalPropertyIds
// not affect the type of an object. That is, ideally we would have a way
// to add and remove InternalPropertyIds from an object without affecting
// its type and therefore without invalidating cache and JIT assumptions.
//
typedef JsUtil::BaseDictionary<WeakMapId, Var, Recycler, PowerOf2SizePolicy, RecyclerPointerComparer> WeakMapKeyMap;
typedef JsUtil::WeaklyReferencedKeyDictionary<DynamicObject, bool, RecyclerPointerComparer<const DynamicObject*>> KeySet;
Field(KeySet) keySet;
WeakMapKeyMap* GetWeakMapKeyMapFromKey(DynamicObject* key) const;
WeakMapKeyMap* AddWeakMapKeyMapToKey(DynamicObject* key);
WeakMapId GetWeakMapId() const { return (void*)(((uintptr_t)this) | 1); }
static JavascriptWeakMap* GetWeakMapFromId(WeakMapId id) { return reinterpret_cast<JavascriptWeakMap*>((uintptr_t)id & (~1)); }
bool KeyMapGet(WeakMapKeyMap* map, Var* value) const;
DEFINE_VTABLE_CTOR_MEMBER_INIT(JavascriptWeakMap, DynamicObject, keySet);
DEFINE_MARSHAL_OBJECT_TO_SCRIPT_CONTEXT(JavascriptWeakMap);
public:
JavascriptWeakMap(DynamicType* type);
static bool Is(Var aValue);
static JavascriptWeakMap* FromVar(Var aValue);
void Clear();
bool Delete(DynamicObject* key);
bool Get(DynamicObject* key, Var* value) const;
bool Has(DynamicObject* key) const;
void Set(DynamicObject* key, Var value);
virtual void Finalize(bool isShutdown) override { Clear(); }
virtual void Dispose(bool isShutdown) override { }
virtual BOOL GetDiagTypeString(StringBuilder<ArenaAllocator>* stringBuilder, ScriptContext* requestContext) override;
class EntryInfo
{
public:
static FunctionInfo NewInstance;
static FunctionInfo Delete;
static FunctionInfo Get;
static FunctionInfo Has;
static FunctionInfo Set;
};
static Var NewInstance(RecyclableObject* function, CallInfo callInfo, ...);
static Var EntryDelete(RecyclableObject* function, CallInfo callInfo, ...);
static Var EntryGet(RecyclableObject* function, CallInfo callInfo, ...);
static Var EntryHas(RecyclableObject* function, CallInfo callInfo, ...);
static Var EntrySet(RecyclableObject* function, CallInfo callInfo, ...);
public:
// For diagnostics and heap enum provide size and allow enumeration of key value pairs
int Size() { keySet.Clean(); return keySet.Count(); }
template <typename Fn>
void Map(Fn fn)
{
return keySet.Map([&](DynamicObject* key, bool, const RecyclerWeakReference<DynamicObject>*)
{
Var value = nullptr;
WeakMapKeyMap* keyMap = GetWeakMapKeyMapFromKey(key);
// It may be the case that a CustomExternalObject (CEO) was reset, removing its WeakMapKeyMap.
// In this case it can still be in the keySet. The keyMap may be null because of the reset,
// but it could be reinstated if the CEO was added to another WeakMap. Thus it could also be the case that
// this WeakMap's ID is not in the WeakMapKeyMap returned from the key object. Ignore the
// CEO key object in these two cases.
if (keyMap != nullptr && keyMap->ContainsKey(GetWeakMapId()))
{
KeyMapGet(keyMap, &value);
fn(key, value);
}
});
}
#if ENABLE_TTD
public:
virtual void MarkVisitKindSpecificPtrs(TTD::SnapshotExtractor* extractor) override;
virtual TTD::NSSnapObjects::SnapObjectType GetSnapTag_TTD() const override;
virtual void ExtractSnapObjectDataInto(TTD::NSSnapObjects::SnapObject* objData, TTD::SlabAllocator& alloc) override;
#endif
};
}