| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451 |
- //-------------------------------------------------------------------------------------------------------
- // Copyright (C) Microsoft. All rights reserved.
- // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
- //-------------------------------------------------------------------------------------------------------
- #include "RuntimeLibraryPch.h"
- namespace Js
- {
- JavascriptWeakMap::JavascriptWeakMap(DynamicType* type)
- : DynamicObject(type),
- keySet(type->GetScriptContext()->GetRecycler())
- {
- }
- bool JavascriptWeakMap::Is(Var aValue)
- {
- return JavascriptOperators::GetTypeId(aValue) == TypeIds_WeakMap;
- }
- JavascriptWeakMap* JavascriptWeakMap::FromVar(Var aValue)
- {
- AssertOrFailFastMsg(Is(aValue), "Ensure var is actually a 'JavascriptWeakMap'");
- return static_cast<JavascriptWeakMap *>(aValue);
- }
- JavascriptWeakMap* JavascriptWeakMap::UnsafeFromVar(Var aValue)
- {
- AssertMsg(Is(aValue), "Ensure var is actually a 'JavascriptWeakMap'");
- return static_cast<JavascriptWeakMap *>(RecyclableObject::UnsafeFromVar(aValue));
- }
- JavascriptWeakMap::WeakMapKeyMap* JavascriptWeakMap::GetWeakMapKeyMapFromKey(RecyclableObject* key) const
- {
- AssertOrFailFast(DynamicType::Is(key->GetTypeId()) || JavascriptOperators::GetTypeId(key) == TypeIds_HostDispatch);
- Var weakMapKeyData = nullptr;
- if (!key->GetInternalProperty(key, InternalPropertyIds::WeakMapKeyMap, &weakMapKeyData, nullptr, key->GetScriptContext()))
- {
- return nullptr;
- }
- if (key->GetScriptContext()->GetLibrary()->GetUndefined() == weakMapKeyData)
- {
- return nullptr;
- }
- return static_cast<WeakMapKeyMap*>(weakMapKeyData);
- }
- JavascriptWeakMap::WeakMapKeyMap* JavascriptWeakMap::AddWeakMapKeyMapToKey(RecyclableObject* key)
- {
- AssertOrFailFast(DynamicType::Is(key->GetTypeId()) || JavascriptOperators::GetTypeId(key) == TypeIds_HostDispatch);
- // The internal property may exist on an object that has had DynamicObject::ResetObject called on itself.
- // In that case the value stored in the property slot should be null.
- DebugOnly(Var unused = nullptr);
- Assert(!key->GetInternalProperty(key, InternalPropertyIds::WeakMapKeyMap, &unused, nullptr, key->GetScriptContext()) || unused == nullptr);
- WeakMapKeyMap* weakMapKeyData = RecyclerNew(GetScriptContext()->GetRecycler(), WeakMapKeyMap, GetScriptContext()->GetRecycler());
- BOOL success = key->SetInternalProperty(InternalPropertyIds::WeakMapKeyMap, weakMapKeyData, PropertyOperation_Force, nullptr);
- Assert(success);
- return weakMapKeyData;
- }
- bool JavascriptWeakMap::KeyMapGet(WeakMapKeyMap* map, Var* value) const
- {
- if (map->ContainsKey(GetWeakMapId()))
- {
- *value = map->Item(GetWeakMapId());
- return true;
- }
- return false;
- }
- Var JavascriptWeakMap::NewInstance(RecyclableObject* function, CallInfo callInfo, ...)
- {
- PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
- ARGUMENTS(args, callInfo);
- ScriptContext* scriptContext = function->GetScriptContext();
- JavascriptLibrary* library = scriptContext->GetLibrary();
- Var newTarget = args.GetNewTarget();
- bool isCtorSuperCall = JavascriptOperators::GetAndAssertIsConstructorSuperCall(args);
- CHAKRATEL_LANGSTATS_INC_LANGFEATURECOUNT(ES6, WeakMap, scriptContext);
- JavascriptWeakMap* weakMapObject = nullptr;
- if (callInfo.Flags & CallFlags_New)
- {
- weakMapObject = library->CreateWeakMap();
- }
- else
- {
- JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("WeakMap"), _u("WeakMap"));
- }
- Assert(weakMapObject != nullptr);
- Var iterable = (args.Info.Count > 1) ? args[1] : library->GetUndefined();
- RecyclableObject* iter = nullptr;
- RecyclableObject* adder = nullptr;
- if (JavascriptConversion::CheckObjectCoercible(iterable, scriptContext))
- {
- iter = JavascriptOperators::GetIterator(iterable, scriptContext);
- Var adderVar = JavascriptOperators::GetProperty(weakMapObject, PropertyIds::set, scriptContext);
- if (!JavascriptConversion::IsCallable(adderVar))
- {
- JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedFunction);
- }
- adder = RecyclableObject::FromVar(adderVar);
- }
- if (iter != nullptr)
- {
- Var undefined = library->GetUndefined();
- JavascriptOperators::DoIteratorStepAndValue(iter, scriptContext, [&](Var nextItem) {
- if (!JavascriptOperators::IsObject(nextItem))
- {
- JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedObject);
- }
- RecyclableObject* obj = RecyclableObject::FromVar(nextItem);
- Var key = nullptr, value = nullptr;
- if (!JavascriptOperators::GetItem(obj, 0u, &key, scriptContext))
- {
- key = undefined;
- }
- if (!JavascriptOperators::GetItem(obj, 1u, &value, scriptContext))
- {
- value = undefined;
- }
- CALL_FUNCTION(scriptContext->GetThreadContext(), adder, CallInfo(CallFlags_Value, 3), weakMapObject, key, value);
- });
- }
- #if ENABLE_TTD
- //TODO: right now we always GC before snapshots (assuming we have a weak collection)
- // may want to optimize this and use a notify here that we have a weak container -- also update post inflate and post snap
- #endif
- return isCtorSuperCall ?
- JavascriptOperators::OrdinaryCreateFromConstructor(RecyclableObject::FromVar(newTarget), weakMapObject, nullptr, scriptContext) :
- weakMapObject;
- }
- Var JavascriptWeakMap::EntryDelete(RecyclableObject* function, CallInfo callInfo, ...)
- {
- PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
- ARGUMENTS(args, callInfo);
- ScriptContext* scriptContext = function->GetScriptContext();
- if (!JavascriptWeakMap::Is(args[0]))
- {
- JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("WeakMap.prototype.delete"), _u("WeakMap"));
- }
- JavascriptWeakMap* weakMap = JavascriptWeakMap::FromVar(args[0]);
- Var key = (args.Info.Count > 1) ? args[1] : scriptContext->GetLibrary()->GetUndefined();
- bool didDelete = false;
- if (JavascriptOperators::IsObject(key))
- {
- RecyclableObject* keyObj = RecyclableObject::FromVar(key);
- didDelete = weakMap->Delete(keyObj);
- }
- #if ENABLE_TTD
- if(scriptContext->IsTTDRecordOrReplayModeEnabled())
- {
- if(scriptContext->IsTTDRecordModeEnabled())
- {
- function->GetScriptContext()->GetThreadContext()->TTDLog->RecordWeakCollectionContainsEvent(didDelete);
- }
- else
- {
- didDelete = function->GetScriptContext()->GetThreadContext()->TTDLog->ReplayWeakCollectionContainsEvent();
- }
- }
- #endif
- return scriptContext->GetLibrary()->CreateBoolean(didDelete);
- }
- Var JavascriptWeakMap::EntryGet(RecyclableObject* function, CallInfo callInfo, ...)
- {
- PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
- ARGUMENTS(args, callInfo);
- ScriptContext* scriptContext = function->GetScriptContext();
- if (!JavascriptWeakMap::Is(args[0]))
- {
- JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("WeakMap.prototype.get"), _u("WeakMap"));
- }
- JavascriptWeakMap* weakMap = JavascriptWeakMap::FromVar(args[0]);
- Var key = (args.Info.Count > 1) ? args[1] : scriptContext->GetLibrary()->GetUndefined();
- bool loaded = false;
- Var value = nullptr;
- if (JavascriptOperators::IsObject(key))
- {
- RecyclableObject* keyObj = RecyclableObject::FromVar(key);
- loaded = weakMap->Get(keyObj, &value);
- }
- #if ENABLE_TTD
- if(scriptContext->IsTTDRecordOrReplayModeEnabled())
- {
- if(scriptContext->IsTTDRecordModeEnabled())
- {
- function->GetScriptContext()->GetThreadContext()->TTDLog->RecordWeakCollectionContainsEvent(loaded);
- }
- else
- {
- loaded = function->GetScriptContext()->GetThreadContext()->TTDLog->ReplayWeakCollectionContainsEvent();
- }
- }
- #endif
- return loaded ? value : scriptContext->GetLibrary()->GetUndefined();
- }
- Var JavascriptWeakMap::EntryHas(RecyclableObject* function, CallInfo callInfo, ...)
- {
- PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
- ARGUMENTS(args, callInfo);
- ScriptContext* scriptContext = function->GetScriptContext();
- if (!JavascriptWeakMap::Is(args[0]))
- {
- JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("WeakMap.prototype.has"), _u("WeakMap"));
- }
- JavascriptWeakMap* weakMap = JavascriptWeakMap::FromVar(args[0]);
- Var key = (args.Info.Count > 1) ? args[1] : scriptContext->GetLibrary()->GetUndefined();
- bool hasValue = false;
- if (JavascriptOperators::IsObject(key))
- {
- RecyclableObject* keyObj = RecyclableObject::FromVar(key);
- hasValue = weakMap->Has(keyObj);
- }
- #if ENABLE_TTD
- if(scriptContext->IsTTDRecordOrReplayModeEnabled())
- {
- if(scriptContext->IsTTDRecordModeEnabled())
- {
- function->GetScriptContext()->GetThreadContext()->TTDLog->RecordWeakCollectionContainsEvent(hasValue);
- }
- else
- {
- hasValue = function->GetScriptContext()->GetThreadContext()->TTDLog->ReplayWeakCollectionContainsEvent();
- }
- }
- #endif
- return scriptContext->GetLibrary()->CreateBoolean(hasValue);
- }
- Var JavascriptWeakMap::EntrySet(RecyclableObject* function, CallInfo callInfo, ...)
- {
- PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
- ARGUMENTS(args, callInfo);
- ScriptContext* scriptContext = function->GetScriptContext();
- if (!JavascriptWeakMap::Is(args[0]))
- {
- JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("WeakMap.prototype.set"), _u("WeakMap"));
- }
- JavascriptWeakMap* weakMap = JavascriptWeakMap::FromVar(args[0]);
- Var key = (args.Info.Count > 1) ? args[1] : scriptContext->GetLibrary()->GetUndefined();
- Var value = (args.Info.Count > 2) ? args[2] : scriptContext->GetLibrary()->GetUndefined();
- if (!JavascriptOperators::IsObject(key))
- {
- JavascriptError::ThrowTypeError(scriptContext, JSERR_WeakMapSetKeyNotAnObject, _u("WeakMap.prototype.set"));
- }
- RecyclableObject* keyObj = RecyclableObject::FromVar(key);
- #if ENABLE_TTD
- //In replay we need to pin the object (and will release at snapshot points) -- in record we don't need to do anything
- if(scriptContext->IsTTDReplayModeEnabled())
- {
- scriptContext->TTDContextInfo->TTDWeakReferencePinSet->AddNew(keyObj);
- }
- #endif
- weakMap->Set(keyObj, value);
- return weakMap;
- }
- void JavascriptWeakMap::Clear()
- {
- keySet.Map([&](RecyclableObject* key, bool value, const RecyclerWeakReference<RecyclableObject>* weakRef) {
- WeakMapKeyMap* keyMap = GetWeakMapKeyMapFromKey(key);
- // It may be the case that a CEO has been reset and the keyMap is now null.
- // Just ignore it in this case, the keyMap has already been collected.
- if (keyMap != nullptr)
- {
- // It may also be the case that a CEO has been reset and then added to a separate WeakMap,
- // creating a new WeakMapKeyMap on the CEO. In this case GetWeakMapId() may not be in the
- // keyMap, so don't assert successful removal here.
- keyMap->Remove(GetWeakMapId());
- }
- });
- keySet.Clear();
- }
- bool JavascriptWeakMap::Delete(RecyclableObject* key)
- {
- WeakMapKeyMap* keyMap = GetWeakMapKeyMapFromKey(key);
- if (keyMap != nullptr)
- {
- bool unused = false;
- bool inSet = keySet.TryGetValueAndRemove(key, &unused);
- bool inData = keyMap->Remove(GetWeakMapId());
- Assert(inSet == inData);
- return inData;
- }
- return false;
- }
- bool JavascriptWeakMap::Get(RecyclableObject* key, Var* value) const
- {
- WeakMapKeyMap* keyMap = GetWeakMapKeyMapFromKey(key);
- if (keyMap != nullptr)
- {
- return KeyMapGet(keyMap, value);
- }
- return false;
- }
- bool JavascriptWeakMap::Has(RecyclableObject* key) const
- {
- WeakMapKeyMap* keyMap = GetWeakMapKeyMapFromKey(key);
- if (keyMap != nullptr)
- {
- return keyMap->ContainsKey(GetWeakMapId());
- }
- return false;
- }
- void JavascriptWeakMap::Set(RecyclableObject* key, Var value)
- {
- WeakMapKeyMap* keyMap = GetWeakMapKeyMapFromKey(key);
- if (keyMap == nullptr)
- {
- keyMap = AddWeakMapKeyMapToKey(key);
- }
- keyMap->Item(GetWeakMapId(), value);
- keySet.Item(key, true);
- }
- BOOL JavascriptWeakMap::GetDiagTypeString(StringBuilder<ArenaAllocator>* stringBuilder, ScriptContext* requestContext)
- {
- stringBuilder->AppendCppLiteral(_u("WeakMap"));
- return TRUE;
- }
- #if ENABLE_TTD
- void JavascriptWeakMap::MarkVisitKindSpecificPtrs(TTD::SnapshotExtractor* extractor)
- {
- //All weak things should be reachable from another root so no need to mark but do need to repopulate the pin sets if in replay mode
- Js::ScriptContext* scriptContext = this->GetScriptContext();
- if(scriptContext->IsTTDReplayModeEnabled())
- {
- this->Map([&](RecyclableObject* key, Js::Var value)
- {
- scriptContext->TTDContextInfo->TTDWeakReferencePinSet->AddNew(key);
- });
- }
- //Keys are weak so are always reachable from somewhere else but values are not so we must walk them
- this->Map([&](RecyclableObject* key, Js::Var value)
- {
- extractor->MarkVisitVar(value);
- });
- }
- TTD::NSSnapObjects::SnapObjectType JavascriptWeakMap::GetSnapTag_TTD() const
- {
- return TTD::NSSnapObjects::SnapObjectType::SnapMapObject;
- }
- void JavascriptWeakMap::ExtractSnapObjectDataInto(TTD::NSSnapObjects::SnapObject* objData, TTD::SlabAllocator& alloc)
- {
- TTD::NSSnapObjects::SnapMapInfo* smi = alloc.SlabAllocateStruct<TTD::NSSnapObjects::SnapMapInfo>();
- uint32 mapCountEst = this->Size() * 2;
- smi->MapSize = 0;
- smi->MapKeyValueArray = alloc.SlabReserveArraySpace<TTD::TTDVar>(mapCountEst + 1); //always reserve at least 1 element
- this->Map([&](RecyclableObject* key, Js::Var value)
- {
- AssertMsg(smi->MapSize + 1 < mapCountEst, "We are writing junk");
- smi->MapKeyValueArray[smi->MapSize] = key;
- smi->MapKeyValueArray[smi->MapSize + 1] = value;
- smi->MapSize += 2;
- });
- if(smi->MapSize == 0)
- {
- smi->MapKeyValueArray = nullptr;
- alloc.SlabAbortArraySpace<TTD::TTDVar>(mapCountEst + 1);
- }
- else
- {
- alloc.SlabCommitArraySpace<TTD::TTDVar>(smi->MapSize, mapCountEst + 1);
- }
- TTD::NSSnapObjects::StdExtractSetKindSpecificInfo<TTD::NSSnapObjects::SnapMapInfo*, TTD::NSSnapObjects::SnapObjectType::SnapMapObject>(objData, smi);
- }
- #endif
- }
|