diff --git a/lib/Backend/JITRecyclableObject.h b/lib/Backend/JITRecyclableObject.h index 6b1c9a82fd4..ebab1143f43 100644 --- a/lib/Backend/JITRecyclableObject.h +++ b/lib/Backend/JITRecyclableObject.h @@ -38,7 +38,7 @@ class JITJavascriptString : JITRecyclableObject return m_charLength; } - static bool Equals(Js::Var aLeft, Js::Var aRight) + static bool Equals(JITJavascriptString* aLeft, JITJavascriptString* aRight) { return Js::JavascriptStringHelpers::Equals(aLeft, aRight); } diff --git a/lib/Runtime/Language/JavascriptConversion.cpp b/lib/Runtime/Language/JavascriptConversion.cpp index ae221cb6311..e01b2cc31db 100644 --- a/lib/Runtime/Language/JavascriptConversion.cpp +++ b/lib/Runtime/Language/JavascriptConversion.cpp @@ -54,14 +54,19 @@ namespace Js template bool JavascriptConversion::SameValueCommon(Var aLeft, Var aRight) { + if (aLeft == aRight) + { + return true; + } + TypeId leftType = JavascriptOperators::GetTypeId(aLeft); - TypeId rightType = JavascriptOperators::GetTypeId(aRight); if (JavascriptOperators::IsUndefinedOrNullType(leftType)) { - return leftType == rightType; + return false; } + TypeId rightType = JavascriptOperators::GetTypeId(aRight); double dblLeft, dblRight; switch (leftType) @@ -70,7 +75,7 @@ namespace Js switch (rightType) { case TypeIds_Integer: - return aLeft == aRight; + return false; case TypeIds_Number: dblLeft = TaggedInt::ToDouble(aLeft); dblRight = JavascriptNumber::GetValue(aRight); @@ -181,17 +186,12 @@ namespace Js } break; case TypeIds_Boolean: - switch (rightType) - { - case TypeIds_Boolean: - return aLeft == aRight; - } - break; + return false; case TypeIds_String: switch (rightType) { case TypeIds_String: - return JavascriptString::Equals(aLeft, aRight); + return JavascriptString::Equals(JavascriptString::UnsafeFromVar(aLeft), JavascriptString::UnsafeFromVar(aRight)); } break; case TypeIds_Symbol: @@ -208,7 +208,7 @@ namespace Js default: break; } - return aLeft == aRight; + return false; } template bool JavascriptConversion::SameValueCommon(Var aLeft, Var aRight); @@ -1521,4 +1521,5 @@ namespace Js return NumberUtilities::TryToInt64(length); } + } // namespace Js diff --git a/lib/Runtime/Language/JavascriptConversion.h b/lib/Runtime/Language/JavascriptConversion.h index 576eec04b25..3e2305e53af 100644 --- a/lib/Runtime/Language/JavascriptConversion.h +++ b/lib/Runtime/Language/JavascriptConversion.h @@ -92,7 +92,17 @@ namespace Js { static double LongToDouble(__int64 aValue); static double ULongToDouble(unsigned __int64 aValue); + template + static Var TryCanonicalizeAsTaggedInt(Var value); + template + static Var TryCanonicalizeAsTaggedInt(Var value, TypeId typeId); + template + static Var TryCanonicalizeAsSimpleVar(Var value); + private: + template + static Var TryCanonicalizeIntHelper(T val); + static BOOL ToInt32Finite(double value, int32* result); template static bool SameValueCommon(Var aValue, Var bValue); diff --git a/lib/Runtime/Language/JavascriptConversion.inl b/lib/Runtime/Language/JavascriptConversion.inl index 1e4097e3ca3..62dbc9dcc13 100644 --- a/lib/Runtime/Language/JavascriptConversion.inl +++ b/lib/Runtime/Language/JavascriptConversion.inl @@ -203,4 +203,109 @@ namespace Js { return SameValueCommon(aValue, bValue); } + template + inline Var JavascriptConversion::TryCanonicalizeIntHelper(T val) + { + if (TaggedInt::IsOverflow(val)) + { + return nullptr; + } + + if (!allowNegOne && val == -1) + { + return nullptr; + } + + return TaggedInt::ToVarUnchecked((int)val); + } + + template + inline Var JavascriptConversion::TryCanonicalizeAsTaggedInt(Var value, TypeId typeId) + { + switch (typeId) + { + case TypeIds_Integer: + return (allowNegOne || value != TaggedInt::ToVarUnchecked(-1)) + ? value + : nullptr; + + case TypeIds_Number: + { + double doubleVal = JavascriptNumber::GetValue(value); + int32 intVal = 0; + + if (!JavascriptNumber::TryGetInt32Value(doubleVal, &intVal)) + { + return nullptr; + } + return TryCanonicalizeIntHelper(intVal); + } + case TypeIds_Int64Number: + { + if (!allowLossyConversion) + { + return nullptr; + } + int64 int64Val = JavascriptInt64Number::UnsafeFromVar(value)->GetValue(); + + return TryCanonicalizeIntHelper(int64Val); + + } + case TypeIds_UInt64Number: + { + if (!allowLossyConversion) + { + return nullptr; + } + uint64 uint64Val = JavascriptUInt64Number::UnsafeFromVar(value)->GetValue(); + + return TryCanonicalizeIntHelper(uint64Val); + } + default: + return nullptr; + } + } + + template + inline Var JavascriptConversion::TryCanonicalizeAsTaggedInt(Var value) + { + TypeId typeId = JavascriptOperators::GetTypeId(value); + return TryCanonicalizeAsTaggedInt(value, typeId); + } + + // Lossy conversion means values are StrictEqual equivalent, + // but we cannot reconstruct the original value after canonicalization + // (e.g. -0 or an Int64Number object) + template + inline Var JavascriptConversion::TryCanonicalizeAsSimpleVar(Var value) + { + TypeId typeId = JavascriptOperators::GetTypeId(value); + switch (typeId) + { + case TypeIds_Integer: + case TypeIds_Number: + case TypeIds_Int64Number: + case TypeIds_UInt64Number: + { + Var taggedInt = TryCanonicalizeAsTaggedInt(value, typeId); + if (taggedInt) + { + return taggedInt; + } + +#if FLOATVAR + return value; +#else + return nullptr; +#endif + } + case TypeIds_String: + case TypeIds_Symbol: + return nullptr; + + default: + return value; + } + } + } // namespace Js diff --git a/lib/Runtime/Language/JavascriptOperators.cpp b/lib/Runtime/Language/JavascriptOperators.cpp index 9c7b592f8d6..df6690b6a7e 100644 --- a/lib/Runtime/Language/JavascriptOperators.cpp +++ b/lib/Runtime/Language/JavascriptOperators.cpp @@ -746,14 +746,14 @@ namespace Js return dblLeft < dblRight; } - BOOL JavascriptOperators::StrictEqualString(Var aLeft, Var aRight) + BOOL JavascriptOperators::StrictEqualString(Var aLeft, JavascriptString* aRight) { - Assert(JavascriptOperators::GetTypeId(aRight) == TypeIds_String); - - if (JavascriptOperators::GetTypeId(aLeft) != TypeIds_String) + JavascriptString* leftStr = TryFromVar(aLeft); + if (!leftStr) + { return false; - - return JavascriptString::Equals(aLeft, aRight); + } + return JavascriptString::Equals(leftStr, aRight); } BOOL JavascriptOperators::StrictEqualEmptyString(Var aLeft) @@ -785,7 +785,7 @@ namespace Js switch (rightType) { case TypeIds_String: - return JavascriptString::Equals(aLeft, aRight); + return JavascriptString::Equals(JavascriptString::UnsafeFromVar(aLeft), JavascriptString::UnsafeFromVar(aRight)); } return FALSE; case TypeIds_Integer: @@ -5188,7 +5188,7 @@ namespace Js return JavascriptBoolean::ToVar(JavascriptOperators::StrictEqual(a, b, scriptContext), scriptContext); } - Var JavascriptOperators::OP_CmSrEq_String(Var a, Var b, ScriptContext *scriptContext) + Var JavascriptOperators::OP_CmSrEq_String(Var a, JavascriptString* b, ScriptContext *scriptContext) { return JavascriptBoolean::ToVar(JavascriptOperators::StrictEqualString(a, b), scriptContext); } diff --git a/lib/Runtime/Language/JavascriptOperators.h b/lib/Runtime/Language/JavascriptOperators.h index ee8e5a53856..1d59e0cd289 100644 --- a/lib/Runtime/Language/JavascriptOperators.h +++ b/lib/Runtime/Language/JavascriptOperators.h @@ -150,7 +150,7 @@ namespace Js static BOOL LessEqual(Var aLeft, Var aRight,ScriptContext* scriptContext); static BOOL NotEqual(Var aLeft, Var aRight,ScriptContext* scriptContext); static BOOL StrictEqual(Var aLeft, Var aRight,ScriptContext* scriptContext); - static BOOL StrictEqualString(Var aLeft, Var aRight); + static BOOL StrictEqualString(Var aLeft, JavascriptString* aRight); static BOOL StrictEqualEmptyString(Var aLeft); static BOOL NotStrictEqual(Var aLeft, Var aRight,ScriptContext* scriptContext); @@ -400,7 +400,7 @@ namespace Js static Var OP_CmEq_A(Js::Var a,Js::Var b,ScriptContext* scriptContext); static Var OP_CmNeq_A(Js::Var a,Js::Var b,ScriptContext* scriptContext); static Var OP_CmSrEq_A(Js::Var a,Js::Var b,ScriptContext* scriptContext); - static Var OP_CmSrEq_String(Var a, Var b, ScriptContext *scriptContext); + static Var OP_CmSrEq_String(Var a, JavascriptString* b, ScriptContext *scriptContext); static Var OP_CmSrEq_EmptyString(Var a, ScriptContext *scriptContext); static Var OP_CmSrNeq_A(Js::Var a,Js::Var b,ScriptContext* scriptContext); static Var OP_CmLt_A(Js::Var a,Js::Var b,ScriptContext* scriptContext); diff --git a/lib/Runtime/Library/JavascriptLibrary.cpp b/lib/Runtime/Library/JavascriptLibrary.cpp index a3f774d95dd..d64656c0419 100644 --- a/lib/Runtime/Library/JavascriptLibrary.cpp +++ b/lib/Runtime/Library/JavascriptLibrary.cpp @@ -5432,7 +5432,7 @@ namespace Js AssertOrFailFast(hasRight); // If the strings at this index are not equal, the callsite objects are not equal. - if (!Js::JavascriptString::Equals(varLeft, varRight)) + if (!Js::JavascriptString::Equals(JavascriptString::FromVar(varLeft), JavascriptString::FromVar(varRight))) { return false; } diff --git a/lib/Runtime/Library/JavascriptMap.cpp b/lib/Runtime/Library/JavascriptMap.cpp index fa944e09319..66b80dbff1e 100644 --- a/lib/Runtime/Library/JavascriptMap.cpp +++ b/lib/Runtime/Library/JavascriptMap.cpp @@ -6,462 +6,694 @@ namespace Js { - JavascriptMap::JavascriptMap(DynamicType* type) - : DynamicObject(type) - { - } +JavascriptMap::JavascriptMap(DynamicType* type) + : DynamicObject(type) +{ +} - JavascriptMap* JavascriptMap::New(ScriptContext* scriptContext) - { - JavascriptMap* map = scriptContext->GetLibrary()->CreateMap(); - map->map = RecyclerNew(scriptContext->GetRecycler(), MapDataMap, scriptContext->GetRecycler()); +JavascriptMap* JavascriptMap::New(ScriptContext* scriptContext) +{ + JavascriptMap* map = scriptContext->GetLibrary()->CreateMap(); - return map; - } + return map; +} - bool JavascriptMap::Is(Var aValue) - { - return JavascriptOperators::GetTypeId(aValue) == TypeIds_Map; - } +bool JavascriptMap::Is(Var aValue) +{ + return JavascriptOperators::GetTypeId(aValue) == TypeIds_Map; +} - JavascriptMap* JavascriptMap::FromVar(Var aValue) - { - AssertOrFailFastMsg(Is(aValue), "Ensure var is actually a 'JavascriptMap'"); +JavascriptMap* JavascriptMap::FromVar(Var aValue) +{ + AssertOrFailFastMsg(Is(aValue), "Ensure var is actually a 'JavascriptMap'"); - return static_cast(aValue); - } + return static_cast(aValue); +} - JavascriptMap* JavascriptMap::UnsafeFromVar(Var aValue) - { - AssertMsg(Is(aValue), "Ensure var is actually a 'JavascriptMap'"); +JavascriptMap* JavascriptMap::UnsafeFromVar(Var aValue) +{ + AssertMsg(Is(aValue), "Ensure var is actually a 'JavascriptMap'"); - return static_cast(aValue); - } + return static_cast(aValue); +} - JavascriptMap::MapDataList::Iterator JavascriptMap::GetIterator() - { - return list.GetIterator(); - } +JavascriptMap::MapDataList::Iterator JavascriptMap::GetIterator() +{ + return list.GetIterator(); +} - Var JavascriptMap::NewInstance(RecyclableObject* function, CallInfo callInfo, ...) - { - PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); +Var JavascriptMap::NewInstance(RecyclableObject* function, CallInfo callInfo, ...) +{ + PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); - ARGUMENTS(args, callInfo); - ScriptContext* scriptContext = function->GetScriptContext(); - JavascriptLibrary* library = scriptContext->GetLibrary(); - AUTO_TAG_NATIVE_LIBRARY_ENTRY(function, callInfo, _u("Map")); + ARGUMENTS(args, callInfo); + ScriptContext* scriptContext = function->GetScriptContext(); + JavascriptLibrary* library = scriptContext->GetLibrary(); + AUTO_TAG_NATIVE_LIBRARY_ENTRY(function, callInfo, _u("Map")); - Var newTarget = args.GetNewTarget(); - bool isCtorSuperCall = JavascriptOperators::GetAndAssertIsConstructorSuperCall(args); - CHAKRATEL_LANGSTATS_INC_DATACOUNT(ES6_Map); + Var newTarget = args.GetNewTarget(); + bool isCtorSuperCall = JavascriptOperators::GetAndAssertIsConstructorSuperCall(args); + CHAKRATEL_LANGSTATS_INC_DATACOUNT(ES6_Map); - JavascriptMap* mapObject = nullptr; + JavascriptMap* mapObject = nullptr; - if (callInfo.Flags & CallFlags_New) - { - mapObject = library->CreateMap(); - } - else - { - JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Map"), _u("Map")); - } - Assert(mapObject != nullptr); + if (callInfo.Flags & CallFlags_New) + { + mapObject = library->CreateMap(); + } + else + { + JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Map"), _u("Map")); + } + Assert(mapObject != nullptr); + + Var iterable = (args.Info.Count > 1) ? args[1] : library->GetUndefined(); - Var iterable = (args.Info.Count > 1) ? args[1] : library->GetUndefined(); + // REVIEW: This condition seems impossible? + if (mapObject->kind != MapKind::EmptyMap) + { + Assert(UNREACHED); + JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_ObjectIsAlreadyInitialized, _u("Map"), _u("Map")); + } - if (mapObject->map != nullptr) + RecyclableObject* iter = nullptr; + RecyclableObject* adder = nullptr; + + if (JavascriptConversion::CheckObjectCoercible(iterable, scriptContext)) + { + iter = JavascriptOperators::GetIterator(iterable, scriptContext); + Var adderVar = JavascriptOperators::GetPropertyNoCache(mapObject, PropertyIds::set, scriptContext); + if (!JavascriptConversion::IsCallable(adderVar)) { - JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_ObjectIsAlreadyInitialized, _u("Map"), _u("Map")); + JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedFunction); } + adder = RecyclableObject::FromVar(adderVar); + } - /* Ensure mapObject->map is created before trying to fetch the adder function. If Map.prototype.set has - its getter set to another Map method (such as Map.prototype.get) and we try to get the function before - the map is initialized, it will cause a null dereference. See github#2747 */ - mapObject->map = RecyclerNew(scriptContext->GetRecycler(), MapDataMap, scriptContext->GetRecycler()); - - RecyclableObject* iter = nullptr; - RecyclableObject* adder = nullptr; + if (iter != nullptr) + { + Var undefined = library->GetUndefined(); - if (JavascriptConversion::CheckObjectCoercible(iterable, scriptContext)) - { - iter = JavascriptOperators::GetIterator(iterable, scriptContext); - Var adderVar = JavascriptOperators::GetPropertyNoCache(mapObject, PropertyIds::set, scriptContext); - if (!JavascriptConversion::IsCallable(adderVar)) + JavascriptOperators::DoIteratorStepAndValue(iter, scriptContext, [&](Var nextItem) { + if (!JavascriptOperators::IsObject(nextItem)) { - JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedFunction); + JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedObject); } - adder = RecyclableObject::FromVar(adderVar); - } - if (iter != nullptr) - { - Var undefined = library->GetUndefined(); + RecyclableObject* obj = RecyclableObject::FromVar(nextItem); - JavascriptOperators::DoIteratorStepAndValue(iter, scriptContext, [&](Var nextItem) { - if (!JavascriptOperators::IsObject(nextItem)) - { - JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedObject); - } + Var key = nullptr, value = nullptr; - RecyclableObject* obj = RecyclableObject::FromVar(nextItem); + if (!JavascriptOperators::GetItem(obj, 0u, &key, scriptContext)) + { + key = undefined; + } - Var key = nullptr, value = nullptr; + if (!JavascriptOperators::GetItem(obj, 1u, &value, scriptContext)) + { + value = undefined; + } - if (!JavascriptOperators::GetItem(obj, 0u, &key, scriptContext)) - { - key = undefined; - } + // CONSIDER: if adder is the default built-in, fast path it and skip the JS call? + CALL_FUNCTION(scriptContext->GetThreadContext(), adder, CallInfo(CallFlags_Value, 3), mapObject, key, value); + }); + } - if (!JavascriptOperators::GetItem(obj, 1u, &value, scriptContext)) - { - value = undefined; - } + return isCtorSuperCall ? + JavascriptOperators::OrdinaryCreateFromConstructor(RecyclableObject::FromVar(newTarget), mapObject, nullptr, scriptContext) : + mapObject; +} - // CONSIDER: if adder is the default built-in, fast path it and skip the JS call? - CALL_FUNCTION(scriptContext->GetThreadContext(), adder, CallInfo(CallFlags_Value, 3), mapObject, key, value); - }); - } +Var JavascriptMap::EntryClear(RecyclableObject* function, CallInfo callInfo, ...) +{ + PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); - return isCtorSuperCall ? - JavascriptOperators::OrdinaryCreateFromConstructor(RecyclableObject::FromVar(newTarget), mapObject, nullptr, scriptContext) : - mapObject; - } + ARGUMENTS(args, callInfo); + ScriptContext* scriptContext = function->GetScriptContext(); - Var JavascriptMap::EntryClear(RecyclableObject* function, CallInfo callInfo, ...) + JavascriptMap* map = JavascriptOperators::TryFromVar(args[0]); + if (map == nullptr) { - PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); - - ARGUMENTS(args, callInfo); - ScriptContext* scriptContext = function->GetScriptContext(); + JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Map.prototype.clear"), _u("Map")); + } - if (!JavascriptMap::Is(args[0])) - { - JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Map.prototype.clear"), _u("Map")); - } + map->Clear(); - JavascriptMap* map = JavascriptMap::FromVar(args[0]); + return scriptContext->GetLibrary()->GetUndefined(); +} - map->Clear(); +Var JavascriptMap::EntryDelete(RecyclableObject* function, CallInfo callInfo, ...) +{ + PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); - return scriptContext->GetLibrary()->GetUndefined(); - } + ARGUMENTS(args, callInfo); + ScriptContext* scriptContext = function->GetScriptContext(); - Var JavascriptMap::EntryDelete(RecyclableObject* function, CallInfo callInfo, ...) + JavascriptMap* map = JavascriptOperators::TryFromVar(args[0]); + if (map == nullptr) { - PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); + JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Map.prototype.delete"), _u("Map")); + } - ARGUMENTS(args, callInfo); - ScriptContext* scriptContext = function->GetScriptContext(); + Var key = (args.Info.Count > 1) ? args[1] : scriptContext->GetLibrary()->GetUndefined(); - if (!JavascriptMap::Is(args[0])) - { - JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Map.prototype.delete"), _u("Map")); - } + bool didDelete = map->Delete(key); - JavascriptMap* map = JavascriptMap::FromVar(args[0]); + return scriptContext->GetLibrary()->CreateBoolean(didDelete); +} - Var key = (args.Info.Count > 1) ? args[1] : scriptContext->GetLibrary()->GetUndefined(); +Var JavascriptMap::EntryForEach(RecyclableObject* function, CallInfo callInfo, ...) +{ + PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); - bool didDelete = map->Delete(key); + ARGUMENTS(args, callInfo); + ScriptContext* scriptContext = function->GetScriptContext(); + AUTO_TAG_NATIVE_LIBRARY_ENTRY(function, callInfo, _u("Map.prototype.forEach")); - return scriptContext->GetLibrary()->CreateBoolean(didDelete); + JavascriptMap* map = JavascriptOperators::TryFromVar(args[0]); + if (map == nullptr) + { + JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Map.prototype.forEach"), _u("Map")); } - Var JavascriptMap::EntryForEach(RecyclableObject* function, CallInfo callInfo, ...) + if (args.Info.Count < 2 || !JavascriptConversion::IsCallable(args[1])) { - PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); + JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_NeedFunction, _u("Map.prototype.forEach")); + } + RecyclableObject* callBackFn = RecyclableObject::FromVar(args[1]); - ARGUMENTS(args, callInfo); - ScriptContext* scriptContext = function->GetScriptContext(); - AUTO_TAG_NATIVE_LIBRARY_ENTRY(function, callInfo, _u("Map.prototype.forEach")); + Var thisArg = (args.Info.Count > 2) ? args[2] : scriptContext->GetLibrary()->GetUndefined(); - if (!JavascriptMap::Is(args[0])) - { - JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Map.prototype.forEach"), _u("Map")); - } + auto iterator = map->GetIterator(); - JavascriptMap* map = JavascriptMap::FromVar(args[0]); + while (iterator.Next()) + { + Var key = iterator.Current().Key(); + Var value = iterator.Current().Value(); - if (args.Info.Count < 2 || !JavascriptConversion::IsCallable(args[1])) - { - JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_NeedFunction, _u("Map.prototype.forEach")); - } - RecyclableObject* callBackFn = RecyclableObject::FromVar(args[1]); + CALL_FUNCTION(scriptContext->GetThreadContext(), callBackFn, CallInfo(CallFlags_Value, 4), thisArg, value, key, map); + } - Var thisArg = (args.Info.Count > 2) ? args[2] : scriptContext->GetLibrary()->GetUndefined(); + return scriptContext->GetLibrary()->GetUndefined(); +} - auto iterator = map->GetIterator(); +Var JavascriptMap::EntryGet(RecyclableObject* function, CallInfo callInfo, ...) +{ + PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); - while (iterator.Next()) - { - Var key = iterator.Current().Key(); - Var value = iterator.Current().Value(); + ARGUMENTS(args, callInfo); + ScriptContext* scriptContext = function->GetScriptContext(); - CALL_FUNCTION(scriptContext->GetThreadContext(), callBackFn, CallInfo(CallFlags_Value, 4), thisArg, value, key, map); - } + JavascriptMap* map = JavascriptOperators::TryFromVar(args[0]); + if (map == nullptr) + { + JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Map.prototype.get"), _u("Map")); + } - return scriptContext->GetLibrary()->GetUndefined(); + Var key = (args.Info.Count > 1) ? args[1] : scriptContext->GetLibrary()->GetUndefined(); + Var value = nullptr; + + if (map->Get(key, &value)) + { + return value; } - Var JavascriptMap::EntryGet(RecyclableObject* function, CallInfo callInfo, ...) + return scriptContext->GetLibrary()->GetUndefined(); +} + +Var JavascriptMap::EntryHas(RecyclableObject* function, CallInfo callInfo, ...) +{ + PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); + + ARGUMENTS(args, callInfo); + ScriptContext* scriptContext = function->GetScriptContext(); + + JavascriptMap* map = JavascriptOperators::TryFromVar(args[0]); + if (map == nullptr) { - PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); + JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Map.prototype.has"), _u("Map")); + } - ARGUMENTS(args, callInfo); - ScriptContext* scriptContext = function->GetScriptContext(); + Var key = (args.Info.Count > 1) ? args[1] : scriptContext->GetLibrary()->GetUndefined(); - if (!JavascriptMap::Is(args[0])) - { - JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Map.prototype.get"), _u("Map")); - } + bool hasValue = map->Has(key); - JavascriptMap* map = JavascriptMap::FromVar(args[0]); + return scriptContext->GetLibrary()->CreateBoolean(hasValue); +} - Var key = (args.Info.Count > 1) ? args[1] : scriptContext->GetLibrary()->GetUndefined(); - Var value = nullptr; +Var JavascriptMap::EntrySet(RecyclableObject* function, CallInfo callInfo, ...) +{ + PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); - if (map->Get(key, &value)) - { - return value; - } + ARGUMENTS(args, callInfo); + ScriptContext* scriptContext = function->GetScriptContext(); - return scriptContext->GetLibrary()->GetUndefined(); + JavascriptMap* map = JavascriptOperators::TryFromVar(args[0]); + if (map == nullptr) + { + JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Map.prototype.set"), _u("Map")); } - Var JavascriptMap::EntryHas(RecyclableObject* function, CallInfo callInfo, ...) - { - PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); + Var key = (args.Info.Count > 1) ? args[1] : scriptContext->GetLibrary()->GetUndefined(); + Var value = (args.Info.Count > 2) ? args[2] : scriptContext->GetLibrary()->GetUndefined(); - ARGUMENTS(args, callInfo); - ScriptContext* scriptContext = function->GetScriptContext(); + if (JavascriptNumber::Is(key) && JavascriptNumber::IsNegZero(JavascriptNumber::GetValue(key))) + { + // Normalize -0 to +0 + key = JavascriptNumber::New(0.0, scriptContext); + } - if (!JavascriptMap::Is(args[0])) - { - JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Map.prototype.has"), _u("Map")); - } + map->Set(key, value); - JavascriptMap* map = JavascriptMap::FromVar(args[0]); + return map; +} - Var key = (args.Info.Count > 1) ? args[1] : scriptContext->GetLibrary()->GetUndefined(); +Var JavascriptMap::EntrySizeGetter(RecyclableObject* function, CallInfo callInfo, ...) +{ + PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); - bool hasValue = map->Has(key); + ARGUMENTS(args, callInfo); + ScriptContext* scriptContext = function->GetScriptContext(); - return scriptContext->GetLibrary()->CreateBoolean(hasValue); + JavascriptMap* map = JavascriptOperators::TryFromVar(args[0]); + if (map == nullptr) + { + JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Map.prototype.size"), _u("Map")); } - Var JavascriptMap::EntrySet(RecyclableObject* function, CallInfo callInfo, ...) - { - PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); + int size = map->Size(); - ARGUMENTS(args, callInfo); - ScriptContext* scriptContext = function->GetScriptContext(); + return JavascriptNumber::ToVar(size, scriptContext); +} - if (!JavascriptMap::Is(args[0])) - { - JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Map.prototype.set"), _u("Map")); - } +Var JavascriptMap::EntryEntries(RecyclableObject* function, CallInfo callInfo, ...) +{ + PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); - JavascriptMap* map = JavascriptMap::FromVar(args[0]); + ARGUMENTS(args, callInfo); + ScriptContext* scriptContext = function->GetScriptContext(); - Var key = (args.Info.Count > 1) ? args[1] : scriptContext->GetLibrary()->GetUndefined(); - Var value = (args.Info.Count > 2) ? args[2] : scriptContext->GetLibrary()->GetUndefined(); + JavascriptMap* map = JavascriptOperators::TryFromVar(args[0]); + if (map == nullptr) + { + JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Map.prototype.entries"), _u("Map")); + } - if (JavascriptNumber::Is(key) && JavascriptNumber::IsNegZero(JavascriptNumber::GetValue(key))) - { - // Normalize -0 to +0 - key = JavascriptNumber::New(0.0, scriptContext); - } + return scriptContext->GetLibrary()->CreateMapIterator(map, JavascriptMapIteratorKind::KeyAndValue); +} - map->Set(key, value); +Var JavascriptMap::EntryKeys(RecyclableObject* function, CallInfo callInfo, ...) +{ + PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); - return map; - } + ARGUMENTS(args, callInfo); + ScriptContext* scriptContext = function->GetScriptContext(); - Var JavascriptMap::EntrySizeGetter(RecyclableObject* function, CallInfo callInfo, ...) + JavascriptMap* map = JavascriptOperators::TryFromVar(args[0]); + if (map == nullptr) { - PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); - - ARGUMENTS(args, callInfo); - ScriptContext* scriptContext = function->GetScriptContext(); + JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Map.prototype.keys"), _u("Map")); + } - if (!JavascriptMap::Is(args[0])) - { - JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Map.prototype.size"), _u("Map")); - } + return scriptContext->GetLibrary()->CreateMapIterator(map, JavascriptMapIteratorKind::Key); +} - JavascriptMap* map = JavascriptMap::FromVar(args[0]); +Var JavascriptMap::EntryValues(RecyclableObject* function, CallInfo callInfo, ...) +{ + PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); - int size = map->Size(); + ARGUMENTS(args, callInfo); + ScriptContext* scriptContext = function->GetScriptContext(); - return JavascriptNumber::ToVar(size, scriptContext); + JavascriptMap* map = JavascriptOperators::TryFromVar(args[0]); + if (map == nullptr) + { + JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Map.prototype.values"), _u("Map")); } + return scriptContext->GetLibrary()->CreateMapIterator(map, JavascriptMapIteratorKind::Value); +} - Var JavascriptMap::EntryEntries(RecyclableObject* function, CallInfo callInfo, ...) - { - PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); +void +JavascriptMap::Clear() +{ + JS_REENTRANCY_LOCK(jsReentLock, this->GetScriptContext()->GetThreadContext()); - ARGUMENTS(args, callInfo); - ScriptContext* scriptContext = function->GetScriptContext(); + list.Clear(); - if (!JavascriptMap::Is(args[0])) - { - JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Map.prototype.entries"), _u("Map")); - } + switch (this->kind) + { + case MapKind::EmptyMap: + return; + case MapKind::SimpleVarMap: + this->u.simpleVarMap->Clear(); + return; + case MapKind::ComplexVarMap: + this->u.complexVarMap->Clear(); + return; + default: + Assume(UNREACHED); + } +} - JavascriptMap* map = JavascriptMap::FromVar(args[0]); +template +bool +JavascriptMap::DeleteFromVarMap(Var value) +{ + Assert(this->kind == (isComplex ? MapKind::ComplexVarMap : MapKind::SimpleVarMap)); - return scriptContext->GetLibrary()->CreateMapIterator(map, JavascriptMapIteratorKind::KeyAndValue); + MapDataNode * node = nullptr; + if (isComplex + ? !this->u.complexVarMap->TryGetValueAndRemove(value, &node) + : !this->u.simpleVarMap->TryGetValueAndRemove(value, &node)) + { + return false; } - Var JavascriptMap::EntryKeys(RecyclableObject* function, CallInfo callInfo, ...) + this->list.Remove(node); + return true; +} + +bool +JavascriptMap::DeleteFromSimpleVarMap(Var value) +{ + Assert(this->kind == MapKind::SimpleVarMap); + + Var simpleVar = JavascriptConversion::TryCanonicalizeAsSimpleVar(value); + if (!simpleVar) { - PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); + return false; + } - ARGUMENTS(args, callInfo); - ScriptContext* scriptContext = function->GetScriptContext(); + return this->DeleteFromVarMap(simpleVar); +} - if (!JavascriptMap::Is(args[0])) - { - JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Map.prototype.keys"), _u("Map")); - } +bool +JavascriptMap::Delete(Var key) +{ + JS_REENTRANCY_LOCK(jsReentLock, this->GetScriptContext()->GetThreadContext()); - JavascriptMap* map = JavascriptMap::FromVar(args[0]); + switch (this->kind) + { + case MapKind::EmptyMap: + return false; - return scriptContext->GetLibrary()->CreateMapIterator(map, JavascriptMapIteratorKind::Key); + case MapKind::SimpleVarMap: + return this->DeleteFromSimpleVarMap(key); + case MapKind::ComplexVarMap: + return this->DeleteFromVarMap(key); + default: + Assume(UNREACHED); + return false; } +} - Var JavascriptMap::EntryValues(RecyclableObject* function, CallInfo callInfo, ...) - { - PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); +bool +JavascriptMap::Get(Var key, Var* value) +{ + JS_REENTRANCY_LOCK(jsReentLock, this->GetScriptContext()->GetThreadContext()); - ARGUMENTS(args, callInfo); - ScriptContext* scriptContext = function->GetScriptContext(); + switch (this->kind) + { + case MapKind::EmptyMap: + return false; - if (!JavascriptMap::Is(args[0])) + case MapKind::SimpleVarMap: + { + // First check if the key is in the map + MapDataNode* node = nullptr; + if (this->u.simpleVarMap->TryGetValue(key, &node)) { - JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Map.prototype.values"), _u("Map")); + *value = node->data.Value(); + return true; + } + // If the key isn't in the map, check if the canonical value is + Var simpleVar = JavascriptConversion::TryCanonicalizeAsSimpleVar(key); + // If the simple var is the same as the original key, we know it isn't in the map + if (!simpleVar || simpleVar == key) + { + return false; } - JavascriptMap* map = JavascriptMap::FromVar(args[0]); - return scriptContext->GetLibrary()->CreateMapIterator(map, JavascriptMapIteratorKind::Value); - } - - void JavascriptMap::Clear() - { - list.Clear(); - map->Clear(); + if (!this->u.simpleVarMap->TryGetValue(simpleVar, &node)) + { + return false; + } + *value = node->data.Value(); + return true; } - - bool JavascriptMap::Delete(Var key) + case MapKind::ComplexVarMap: { - if (map->ContainsKey(key)) + MapDataNode * node = nullptr; + if (!this->u.complexVarMap->TryGetValue(key, &node)) { - MapDataNode* node = map->Item(key); - list.Remove(node); - return map->Remove(key); + return false; } + *value = node->data.Value(); + return true; + } + default: + Assume(UNREACHED); return false; } +} + + +bool +JavascriptMap::Has(Var key) +{ + JS_REENTRANCY_LOCK(jsReentLock, this->GetScriptContext()->GetThreadContext()); - bool JavascriptMap::Get(Var key, Var* value) + switch (this->kind) { - if (map->ContainsKey(key)) + case MapKind::EmptyMap: + return false; + + case MapKind::SimpleVarMap: + { + // First check if the key is in the map + if (this->u.simpleVarMap->ContainsKey(key)) { - MapDataNode* node = map->Item(key); - *value = node->data.Value(); return true; } + // If the key isn't in the map, check if the canonical value is + Var simpleVar = JavascriptConversion::TryCanonicalizeAsSimpleVar(key); + // If the simple var is the same as the original key, we know it isn't in the map + if (!simpleVar || simpleVar == key) + { + return false; + } + + return this->u.simpleVarMap->ContainsKey(simpleVar); + } + case MapKind::ComplexVarMap: + return this->u.complexVarMap->ContainsKey(key); + + default: + Assume(UNREACHED); return false; } +} + +void +JavascriptMap::PromoteToComplexVarMap() +{ + AssertOrFailFast(this->kind == MapKind::SimpleVarMap); - bool JavascriptMap::Has(Var key) + uint newMapSize = this->u.simpleVarMap->Count() + 1; + ComplexVarDataMap* newMap = RecyclerNew(this->GetRecycler(), ComplexVarDataMap, this->GetRecycler(), newMapSize); + + JavascriptMap::MapDataList::Iterator iter = this->list.GetIterator(); + // TODO: we can use a more efficient Iterator, since we know there will be no side effects + while (iter.Next()) { - return map->ContainsKey(key); + newMap->Add(iter.Current().Key(), iter.CurrentNode()); } - void JavascriptMap::Set(Var key, Var value) + this->kind = MapKind::ComplexVarMap; + this->u.complexVarMap = newMap; +} + +void +JavascriptMap::SetOnEmptyMap(Var key, Var value) +{ + Assert(this->kind == MapKind::EmptyMap); + Var simpleVar = JavascriptConversion::TryCanonicalizeAsSimpleVar(key); + if (simpleVar) { - if (map->ContainsKey(key)) - { - MapDataNode* node = map->Item(key); - node->data = MapDataKeyValuePair(key, value); - } - else - { - MapDataKeyValuePair pair(key, value); - MapDataNode* node = list.Append(pair, GetScriptContext()->GetRecycler()); - map->Add(key, node); - } + SimpleVarDataMap* newSimpleMap = RecyclerNew(this->GetRecycler(), SimpleVarDataMap, this->GetRecycler()); + MapDataKeyValuePair simplePair(simpleVar, value); + + MapDataNode* node = this->list.Append(simplePair, this->GetRecycler()); + + newSimpleMap->Add(simpleVar, node); + + this->u.simpleVarMap = newSimpleMap; + this->kind = MapKind::SimpleVarMap; + return; } - int JavascriptMap::Size() + ComplexVarDataMap* newComplexSet = RecyclerNew(this->GetRecycler(), ComplexVarDataMap, this->GetRecycler()); + MapDataKeyValuePair complexPair(key, value); + + MapDataNode* node = this->list.Append(complexPair, this->GetRecycler()); + + newComplexSet->Add(key, node); + + this->u.complexVarMap = newComplexSet; + this->kind = MapKind::ComplexVarMap; +} + +bool +JavascriptMap::TrySetOnSimpleVarMap(Var key, Var value) +{ + Assert(this->kind == MapKind::SimpleVarMap); + + Var simpleVar = JavascriptConversion::TryCanonicalizeAsSimpleVar(key); + if (!simpleVar) { - return map->Count(); + return false; } - BOOL JavascriptMap::GetDiagTypeString(StringBuilder* stringBuilder, ScriptContext* requestContext) + MapDataNode* node = nullptr; + if (this->u.simpleVarMap->TryGetValue(simpleVar, &node)) { - stringBuilder->AppendCppLiteral(_u("Map")); - return TRUE; + node->data = MapDataKeyValuePair(simpleVar, value); + return true; } - Var JavascriptMap::EntryGetterSymbolSpecies(RecyclableObject* function, CallInfo callInfo, ...) - { - ARGUMENTS(args, callInfo); + MapDataKeyValuePair pair(simpleVar, value); + MapDataNode* newNode = this->list.Append(pair, this->GetRecycler()); + this->u.simpleVarMap->Add(simpleVar, newNode); + return true; +} - Assert(args.Info.Count > 0); +void +JavascriptMap::SetOnComplexVarMap(Var key, Var value) +{ + Assert(this->kind == MapKind::ComplexVarMap); - return args[0]; + MapDataNode* node = nullptr; + if (this->u.complexVarMap->TryGetValue(key, &node)) + { + node->data = MapDataKeyValuePair(key, value); + return; } -#if ENABLE_TTD - void JavascriptMap::MarkVisitKindSpecificPtrs(TTD::SnapshotExtractor* extractor) + MapDataKeyValuePair pair(key, value); + MapDataNode* newNode = this->list.Append(pair, this->GetRecycler()); + this->u.complexVarMap->Add(key, newNode); +} + +void +JavascriptMap::Set(Var key, Var value) +{ + JS_REENTRANCY_LOCK(jsReentLock, this->GetScriptContext()->GetThreadContext()); + + switch (this->kind) { - auto iterator = GetIterator(); - while(iterator.Next()) + case MapKind::EmptyMap: + this->SetOnEmptyMap(key, value); + return; + + case MapKind::SimpleVarMap: + if (this->TrySetOnSimpleVarMap(key, value)) { - extractor->MarkVisitVar(iterator.Current().Key()); - extractor->MarkVisitVar(iterator.Current().Value()); + return; } + this->PromoteToComplexVarMap(); + return this->Set(key, value); + + case MapKind::ComplexVarMap: + this->SetOnComplexVarMap(key, value); + return; + + default: + Assume(UNREACHED); } +} + +int JavascriptMap::Size() +{ + JS_REENTRANCY_LOCK(jsReentLock, this->GetScriptContext()->GetThreadContext()); - TTD::NSSnapObjects::SnapObjectType JavascriptMap::GetSnapTag_TTD() const + switch (this->kind) { - return TTD::NSSnapObjects::SnapObjectType::SnapMapObject; + case MapKind::EmptyMap: + return 0; + + case MapKind::SimpleVarMap: + return this->u.simpleVarMap->Count(); + + case MapKind::ComplexVarMap: + return this->u.complexVarMap->Count(); + + default: + Assume(UNREACHED); + return 0; } +} - void JavascriptMap::ExtractSnapObjectDataInto(TTD::NSSnapObjects::SnapObject* objData, TTD::SlabAllocator& alloc) - { - TTD::NSSnapObjects::SnapMapInfo* smi = alloc.SlabAllocateStruct(); - smi->MapSize = 0; +BOOL JavascriptMap::GetDiagTypeString(StringBuilder* stringBuilder, ScriptContext* requestContext) +{ + stringBuilder->AppendCppLiteral(_u("Map")); + return TRUE; +} - if(this->Size() == 0) - { - smi->MapKeyValueArray = nullptr; - } - else - { - smi->MapKeyValueArray = alloc.SlabAllocateArray(this->Size() * 2); +Var JavascriptMap::EntryGetterSymbolSpecies(RecyclableObject* function, CallInfo callInfo, ...) +{ + ARGUMENTS(args, callInfo); - auto iter = this->GetIterator(); - while(iter.Next()) - { - smi->MapKeyValueArray[smi->MapSize] = iter.Current().Key(); - smi->MapKeyValueArray[smi->MapSize + 1] = iter.Current().Value(); - smi->MapSize += 2; - } - } + Assert(args.Info.Count > 0); + + return args[0]; +} - TTD::NSSnapObjects::StdExtractSetKindSpecificInfo(objData, smi); +#if ENABLE_TTD +void JavascriptMap::MarkVisitKindSpecificPtrs(TTD::SnapshotExtractor* extractor) +{ + auto iterator = GetIterator(); + while(iterator.Next()) + { + extractor->MarkVisitVar(iterator.Current().Key()); + extractor->MarkVisitVar(iterator.Current().Value()); } +} - JavascriptMap* JavascriptMap::CreateForSnapshotRestore(ScriptContext* ctx) +TTD::NSSnapObjects::SnapObjectType JavascriptMap::GetSnapTag_TTD() const +{ + return TTD::NSSnapObjects::SnapObjectType::SnapMapObject; +} + +void JavascriptMap::ExtractSnapObjectDataInto(TTD::NSSnapObjects::SnapObject* objData, TTD::SlabAllocator& alloc) +{ + TTD::NSSnapObjects::SnapMapInfo* smi = alloc.SlabAllocateStruct(); + smi->MapSize = 0; + + if(this->Size() == 0) + { + smi->MapKeyValueArray = nullptr; + } + else { - JavascriptMap* res = ctx->GetLibrary()->CreateMap(); - res->map = RecyclerNew(ctx->GetRecycler(), MapDataMap, ctx->GetRecycler()); + smi->MapKeyValueArray = alloc.SlabAllocateArray(this->Size() * 2); - return res; + auto iter = this->GetIterator(); + while(iter.Next()) + { + smi->MapKeyValueArray[smi->MapSize] = iter.Current().Key(); + smi->MapKeyValueArray[smi->MapSize + 1] = iter.Current().Value(); + smi->MapSize += 2; + } } + + TTD::NSSnapObjects::StdExtractSetKindSpecificInfo(objData, smi); +} + +JavascriptMap* JavascriptMap::CreateForSnapshotRestore(ScriptContext* ctx) +{ + JavascriptMap* res = ctx->GetLibrary()->CreateMap(); + + return res; +} #endif } diff --git a/lib/Runtime/Library/JavascriptMap.h b/lib/Runtime/Library/JavascriptMap.h index 6bab5311e03..a3ebb6c6ddf 100644 --- a/lib/Runtime/Library/JavascriptMap.h +++ b/lib/Runtime/Library/JavascriptMap.h @@ -12,15 +12,49 @@ namespace Js typedef JsUtil::KeyValuePair MapDataKeyValuePair; typedef MapOrSetDataNode MapDataNode; typedef MapOrSetDataList MapDataList; - typedef JsUtil::BaseDictionary MapDataMap; + typedef JsUtil::BaseDictionary SimpleVarDataMap; + typedef JsUtil::BaseDictionary ComplexVarDataMap; private: + enum class MapKind : uint8 + { + // An EmptyMap is a map containing no elements + EmptyMap, + // A SimpleVarMap is a map containing only Vars which are comparable by pointer, and don't require + // value comparison + // + // Addition of a Var that is not comparable by pointer value causes the set to be promoted to a ComplexVarSet + SimpleVarMap, + // A ComplexVarMap is a map containing Vars for which we must inspect the values to do a comparison + // This includes Strings, Symbols, and (sometimes) JavascriptNumbers + ComplexVarMap + }; + Field(MapDataList) list; - Field(MapDataMap*) map; + + union MapUnion + { + Field(SimpleVarDataMap*) simpleVarMap; + Field(ComplexVarDataMap*) complexVarMap; + MapUnion() {} + }; + + Field(MapUnion) u; + + Field(MapKind) kind = MapKind::EmptyMap; DEFINE_VTABLE_CTOR_MEMBER_INIT(JavascriptMap, DynamicObject, list); DEFINE_MARSHAL_OBJECT_TO_SCRIPT_CONTEXT(JavascriptMap); + template + bool DeleteFromVarMap(Var value); + bool DeleteFromSimpleVarMap(Var value); + + void SetOnEmptyMap(Var key, Var value); + bool TrySetOnSimpleVarMap(Var key, Var value); + void SetOnComplexVarMap(Var key, Var value); + + void PromoteToComplexVarMap(); public: JavascriptMap(DynamicType* type); @@ -31,10 +65,14 @@ namespace Js static JavascriptMap* UnsafeFromVar(Var aValue); void Clear(); + bool Delete(Var key); + bool Get(Var key, Var* value); bool Has(Var key); + void Set(Var key, Var value); + int Size(); MapDataList::Iterator GetIterator(); diff --git a/lib/Runtime/Library/JavascriptSet.cpp b/lib/Runtime/Library/JavascriptSet.cpp index 8b53c04fe2a..f1b4e2ada38 100644 --- a/lib/Runtime/Library/JavascriptSet.cpp +++ b/lib/Runtime/Library/JavascriptSet.cpp @@ -6,379 +6,676 @@ namespace Js { - JavascriptSet::JavascriptSet(DynamicType* type) - : DynamicObject(type) +JavascriptSet::JavascriptSet(DynamicType* type) + : DynamicObject(type) +{ +} + +JavascriptSet* JavascriptSet::New(ScriptContext* scriptContext) +{ + JavascriptSet* set = scriptContext->GetLibrary()->CreateSet(); + + return set; +} + +bool JavascriptSet::Is(Var aValue) +{ + return JavascriptOperators::GetTypeId(aValue) == TypeIds_Set; +} + +JavascriptSet* JavascriptSet::FromVar(Var aValue) +{ + AssertOrFailFastMsg(Is(aValue), "Ensure var is actually a 'JavascriptSet'"); + + return static_cast(aValue); +} + +JavascriptSet* JavascriptSet::UnsafeFromVar(Var aValue) +{ + AssertMsg(Is(aValue), "Ensure var is actually a 'JavascriptSet'"); + + return static_cast(aValue); +} + +JavascriptSet::SetDataList::Iterator JavascriptSet::GetIterator() +{ + return this->list.GetIterator(); +} + +Var JavascriptSet::NewInstance(RecyclableObject* function, CallInfo callInfo, ...) +{ + PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); + + ARGUMENTS(args, callInfo); + ScriptContext* scriptContext = function->GetScriptContext(); + JavascriptLibrary* library = scriptContext->GetLibrary(); + AUTO_TAG_NATIVE_LIBRARY_ENTRY(function, callInfo, _u("Set")); + + Var newTarget = args.GetNewTarget(); + bool isCtorSuperCall = JavascriptOperators::GetAndAssertIsConstructorSuperCall(args); + CHAKRATEL_LANGSTATS_INC_DATACOUNT(ES6_Set); + + JavascriptSet* setObject = nullptr; + + if (callInfo.Flags & CallFlags_New) { + setObject = library->CreateSet(); } - - JavascriptSet* JavascriptSet::New(ScriptContext* scriptContext) + else { - JavascriptSet* set = scriptContext->GetLibrary()->CreateSet(); - set->set = RecyclerNew(scriptContext->GetRecycler(), SetDataSet, scriptContext->GetRecycler()); - - return set; + JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Set"), _u("Set")); } + Assert(setObject != nullptr); - bool JavascriptSet::Is(Var aValue) + Var iterable = (args.Info.Count > 1) ? args[1] : library->GetUndefined(); + + // REVIEW: This condition seems impossible? + if (setObject->kind != SetKind::EmptySet) { - return JavascriptOperators::GetTypeId(aValue) == TypeIds_Set; + Assert(UNREACHED); + JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_ObjectIsAlreadyInitialized, _u("Set"), _u("Set")); } - JavascriptSet* JavascriptSet::FromVar(Var aValue) - { - AssertOrFailFastMsg(Is(aValue), "Ensure var is actually a 'JavascriptSet'"); + RecyclableObject* iter = nullptr; + RecyclableObject* adder = nullptr; - return static_cast(aValue); + if (JavascriptConversion::CheckObjectCoercible(iterable, scriptContext)) + { + iter = JavascriptOperators::GetIterator(iterable, scriptContext); + Var adderVar = JavascriptOperators::GetPropertyNoCache(setObject, PropertyIds::add, scriptContext); + if (!JavascriptConversion::IsCallable(adderVar)) + { + JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedFunction); + } + adder = RecyclableObject::FromVar(adderVar); } - JavascriptSet* JavascriptSet::UnsafeFromVar(Var aValue) + if (iter != nullptr) { - AssertMsg(Is(aValue), "Ensure var is actually a 'JavascriptSet'"); + JavascriptOperators::DoIteratorStepAndValue(iter, scriptContext, [&](Var nextItem) { + CALL_FUNCTION(scriptContext->GetThreadContext(), adder, CallInfo(CallFlags_Value, 2), setObject, nextItem); + }); + } + + return isCtorSuperCall ? + JavascriptOperators::OrdinaryCreateFromConstructor(RecyclableObject::FromVar(newTarget), setObject, nullptr, scriptContext) : + setObject; +} + +Var JavascriptSet::EntryAdd(RecyclableObject* function, CallInfo callInfo, ...) +{ + PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); + + ARGUMENTS(args, callInfo); + ScriptContext* scriptContext = function->GetScriptContext(); - return static_cast(aValue); + JavascriptSet* set = JavascriptOperators::TryFromVar(args[0]); + if (set == nullptr) + { + JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Set.prototype.add"), _u("Set")); } - JavascriptSet::SetDataList::Iterator JavascriptSet::GetIterator() + Var value = (args.Info.Count > 1) ? args[1] : scriptContext->GetLibrary()->GetUndefined(); + + if (JavascriptNumber::Is(value) && JavascriptNumber::IsNegZero(JavascriptNumber::GetValue(value))) { - return list.GetIterator(); + // Normalize -0 to +0 + value = JavascriptNumber::New(0.0, scriptContext); } - Var JavascriptSet::NewInstance(RecyclableObject* function, CallInfo callInfo, ...) + set->Add(value); + + return set; +} + +Var JavascriptSet::EntryClear(RecyclableObject* function, CallInfo callInfo, ...) +{ + PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); + + ARGUMENTS(args, callInfo); + ScriptContext* scriptContext = function->GetScriptContext(); + + JavascriptSet* set = JavascriptOperators::TryFromVar(args[0]); + if (set == nullptr) { - PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); + JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Set.prototype.clear"), _u("Set")); + } - ARGUMENTS(args, callInfo); - ScriptContext* scriptContext = function->GetScriptContext(); - JavascriptLibrary* library = scriptContext->GetLibrary(); - AUTO_TAG_NATIVE_LIBRARY_ENTRY(function, callInfo, _u("Set")); + set->Clear(); - Var newTarget = args.GetNewTarget(); - bool isCtorSuperCall = JavascriptOperators::GetAndAssertIsConstructorSuperCall(args); - CHAKRATEL_LANGSTATS_INC_DATACOUNT(ES6_Set); + return scriptContext->GetLibrary()->GetUndefined(); +} - JavascriptSet* setObject = nullptr; +Var JavascriptSet::EntryDelete(RecyclableObject* function, CallInfo callInfo, ...) +{ + PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); - if (callInfo.Flags & CallFlags_New) - { - setObject = library->CreateSet(); - } - else - { - JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Set"), _u("Set")); - } - Assert(setObject != nullptr); + ARGUMENTS(args, callInfo); + ScriptContext* scriptContext = function->GetScriptContext(); - Var iterable = (args.Info.Count > 1) ? args[1] : library->GetUndefined(); + JavascriptSet* set = JavascriptOperators::TryFromVar(args[0]); + if (set == nullptr) + { + JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Set.prototype.delete"), _u("Set")); + } - if (setObject->set != nullptr) - { - JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_ObjectIsAlreadyInitialized, _u("Set"), _u("Set")); - } + Var value = (args.Info.Count > 1) ? args[1] : scriptContext->GetLibrary()->GetUndefined(); - setObject->set = RecyclerNew(scriptContext->GetRecycler(), SetDataSet, scriptContext->GetRecycler()); + bool didDelete = set->Delete(value); - RecyclableObject* iter = nullptr; - RecyclableObject* adder = nullptr; + return scriptContext->GetLibrary()->CreateBoolean(didDelete); +} - if (JavascriptConversion::CheckObjectCoercible(iterable, scriptContext)) - { - iter = JavascriptOperators::GetIterator(iterable, scriptContext); - Var adderVar = JavascriptOperators::GetPropertyNoCache(setObject, PropertyIds::add, scriptContext); - if (!JavascriptConversion::IsCallable(adderVar)) - { - JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedFunction); - } - adder = RecyclableObject::FromVar(adderVar); - } +Var JavascriptSet::EntryForEach(RecyclableObject* function, CallInfo callInfo, ...) +{ + PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); - if (iter != nullptr) - { - JavascriptOperators::DoIteratorStepAndValue(iter, scriptContext, [&](Var nextItem) { - CALL_FUNCTION(scriptContext->GetThreadContext(), adder, CallInfo(CallFlags_Value, 2), setObject, nextItem); - }); - } + ARGUMENTS(args, callInfo); + ScriptContext* scriptContext = function->GetScriptContext(); + AUTO_TAG_NATIVE_LIBRARY_ENTRY(function, callInfo, _u("Set.prototype.forEach")); - return isCtorSuperCall ? - JavascriptOperators::OrdinaryCreateFromConstructor(RecyclableObject::FromVar(newTarget), setObject, nullptr, scriptContext) : - setObject; + JavascriptSet* set = JavascriptOperators::TryFromVar(args[0]); + if (set == nullptr) + { + JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Set.prototype.forEach"), _u("Set")); } - Var JavascriptSet::EntryAdd(RecyclableObject* function, CallInfo callInfo, ...) + if (args.Info.Count < 2 || !JavascriptConversion::IsCallable(args[1])) { - PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); + JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_NeedFunction, _u("Set.prototype.forEach")); + } + RecyclableObject* callBackFn = RecyclableObject::FromVar(args[1]); - ARGUMENTS(args, callInfo); - ScriptContext* scriptContext = function->GetScriptContext(); + Var thisArg = (args.Info.Count > 2) ? args[2] : scriptContext->GetLibrary()->GetUndefined(); - if (!JavascriptSet::Is(args[0])) - { - JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Set.prototype.add"), _u("Set")); - } + auto iterator = set->GetIterator(); - JavascriptSet* set = JavascriptSet::FromVar(args[0]); + while (iterator.Next()) + { + Var value = iterator.Current(); - Var value = (args.Info.Count > 1) ? args[1] : scriptContext->GetLibrary()->GetUndefined(); + CALL_FUNCTION(scriptContext->GetThreadContext(), callBackFn, CallInfo(CallFlags_Value, 4), thisArg, value, value, args[0]); + } - if (JavascriptNumber::Is(value) && JavascriptNumber::IsNegZero(JavascriptNumber::GetValue(value))) - { - // Normalize -0 to +0 - value = JavascriptNumber::New(0.0, scriptContext); - } + return scriptContext->GetLibrary()->GetUndefined(); +} - set->Add(value); +Var JavascriptSet::EntryHas(RecyclableObject* function, CallInfo callInfo, ...) +{ + PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); - return set; - } + ARGUMENTS(args, callInfo); + ScriptContext* scriptContext = function->GetScriptContext(); - Var JavascriptSet::EntryClear(RecyclableObject* function, CallInfo callInfo, ...) + JavascriptSet* set = JavascriptOperators::TryFromVar(args[0]); + if (set == nullptr) { - PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); + JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Set.prototype.has"), _u("Set")); + } - ARGUMENTS(args, callInfo); - ScriptContext* scriptContext = function->GetScriptContext(); + Var value = (args.Info.Count > 1) ? args[1] : scriptContext->GetLibrary()->GetUndefined(); - if (!JavascriptSet::Is(args[0])) - { - JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Set.prototype.clear"), _u("Set")); - } + bool hasValue = set->Has(value); - JavascriptSet* set = JavascriptSet::FromVar(args[0]); + return scriptContext->GetLibrary()->CreateBoolean(hasValue); +} - set->Clear(); +Var JavascriptSet::EntrySizeGetter(RecyclableObject* function, CallInfo callInfo, ...) +{ + PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); - return scriptContext->GetLibrary()->GetUndefined(); - } + ARGUMENTS(args, callInfo); + ScriptContext* scriptContext = function->GetScriptContext(); - Var JavascriptSet::EntryDelete(RecyclableObject* function, CallInfo callInfo, ...) + JavascriptSet* set = JavascriptOperators::TryFromVar(args[0]); + if (set == nullptr) { - PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); - - ARGUMENTS(args, callInfo); - ScriptContext* scriptContext = function->GetScriptContext(); - - if (!JavascriptSet::Is(args[0])) - { - JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Set.prototype.delete"), _u("Set")); - } + JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Set.prototype.size"), _u("Set")); + } - JavascriptSet* set = JavascriptSet::FromVar(args[0]); + int size = set->Size(); - Var value = (args.Info.Count > 1) ? args[1] : scriptContext->GetLibrary()->GetUndefined(); + return JavascriptNumber::ToVar(size, scriptContext); +} - bool didDelete = set->Delete(value); +Var JavascriptSet::EntryEntries(RecyclableObject* function, CallInfo callInfo, ...) +{ + PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); - return scriptContext->GetLibrary()->CreateBoolean(didDelete); - } + ARGUMENTS(args, callInfo); + ScriptContext* scriptContext = function->GetScriptContext(); - Var JavascriptSet::EntryForEach(RecyclableObject* function, CallInfo callInfo, ...) + JavascriptSet* set = JavascriptOperators::TryFromVar(args[0]); + if (set == nullptr) { - PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); + JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Set.prototype.entries"), _u("Set")); + } - ARGUMENTS(args, callInfo); - ScriptContext* scriptContext = function->GetScriptContext(); - AUTO_TAG_NATIVE_LIBRARY_ENTRY(function, callInfo, _u("Set.prototype.forEach")); + return scriptContext->GetLibrary()->CreateSetIterator(set, JavascriptSetIteratorKind::KeyAndValue); +} - if (!JavascriptSet::Is(args[0])) - { - JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Set.prototype.forEach"), _u("Set")); - } +Var JavascriptSet::EntryValues(RecyclableObject* function, CallInfo callInfo, ...) +{ + PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); - JavascriptSet* set = JavascriptSet::FromVar(args[0]); + ARGUMENTS(args, callInfo); + ScriptContext* scriptContext = function->GetScriptContext(); + JavascriptSet* set = JavascriptOperators::TryFromVar(args[0]); + if (set == nullptr) + { + JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Set.prototype.values"), _u("Set")); + } - if (args.Info.Count < 2 || !JavascriptConversion::IsCallable(args[1])) - { - JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_NeedFunction, _u("Set.prototype.forEach")); - } - RecyclableObject* callBackFn = RecyclableObject::FromVar(args[1]); + return scriptContext->GetLibrary()->CreateSetIterator(set, JavascriptSetIteratorKind::Value); +} - Var thisArg = (args.Info.Count > 2) ? args[2] : scriptContext->GetLibrary()->GetUndefined(); +Var JavascriptSet::EntryGetterSymbolSpecies(RecyclableObject* function, CallInfo callInfo, ...) +{ + ARGUMENTS(args, callInfo); - auto iterator = set->GetIterator(); + Assert(args.Info.Count > 0); - while (iterator.Next()) - { - Var value = iterator.Current(); + return args[0]; +} - CALL_FUNCTION(scriptContext->GetThreadContext(), callBackFn, CallInfo(CallFlags_Value, 4), thisArg, value, value, args[0]); - } +template +T* +JavascriptSet::CreateVarSetFromList(uint initialCapacity) +{ + T* varSet = RecyclerNew(this->GetRecycler(), T, this->GetRecycler(), initialCapacity); - return scriptContext->GetLibrary()->GetUndefined(); + JavascriptSet::SetDataList::Iterator iter = this->list.GetIterator(); + // TODO: we can use a more efficient Iterator, since we know there will be no side effects + while (iter.Next()) + { + varSet->Add(iter.Current(), iter.CurrentNode()); } + return varSet; +} - Var JavascriptSet::EntryHas(RecyclableObject* function, CallInfo callInfo, ...) - { - PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); +void +JavascriptSet::PromoteToSimpleVarSet() +{ + AssertOrFailFast(this->kind == SetKind::IntSet); + SimpleVarDataSet* newSet = this->CreateVarSetFromList(this->u.intSet->Count() + 1); - ARGUMENTS(args, callInfo); - ScriptContext* scriptContext = function->GetScriptContext(); + this->kind = SetKind::SimpleVarSet; + this->u.simpleVarSet = newSet; +} - if (!JavascriptSet::Is(args[0])) - { - JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Set.prototype.has"), _u("Set")); - } +void +JavascriptSet::PromoteToComplexVarSet() +{ + uint setSize = 0; + if (this->kind == SetKind::IntSet) + { + setSize = this->u.intSet->Count(); + } + else + { + AssertOrFailFast(this->kind == SetKind::SimpleVarSet); + setSize = this->u.simpleVarSet->Count(); + } + ComplexVarDataSet* newSet = this->CreateVarSetFromList(setSize + 1); - JavascriptSet* set = JavascriptSet::FromVar(args[0]); + this->kind = SetKind::ComplexVarSet; + this->u.complexVarSet = newSet; +} +void +JavascriptSet::AddToEmptySet(Var value) +{ + Assert(this->kind == SetKind::EmptySet); + // We cannot store an int with value -1 inside of a bit vector + Var taggedInt = JavascriptConversion::TryCanonicalizeAsTaggedInt(value); + if (taggedInt) + { + int32 intVal = TaggedInt::ToInt32(taggedInt); - Var value = (args.Info.Count > 1) ? args[1] : scriptContext->GetLibrary()->GetUndefined(); + BVSparse* newIntSet = RecyclerNew(this->GetRecycler(), BVSparse, this->GetRecycler()); + newIntSet->Set(intVal); - bool hasValue = set->Has(value); + this->list.Append(taggedInt, this->GetRecycler()); - return scriptContext->GetLibrary()->CreateBoolean(hasValue); + this->u.intSet = newIntSet; + this->kind = SetKind::IntSet; + return; } - Var JavascriptSet::EntrySizeGetter(RecyclableObject* function, CallInfo callInfo, ...) + Var simpleVar = JavascriptConversion::TryCanonicalizeAsSimpleVar(value); + if (simpleVar) { - PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); + SimpleVarDataSet* newSimpleSet = RecyclerNew(this->GetRecycler(), SimpleVarDataSet, this->GetRecycler()); + SetDataNode* node = this->list.Append(simpleVar, this->GetRecycler()); - ARGUMENTS(args, callInfo); - ScriptContext* scriptContext = function->GetScriptContext(); + newSimpleSet->Add(simpleVar, node); - if (!JavascriptSet::Is(args[0])) - { - JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Set.prototype.size"), _u("Set")); - } + this->u.simpleVarSet = newSimpleSet; + this->kind = SetKind::SimpleVarSet; + return; + } + + ComplexVarDataSet* newComplexSet = RecyclerNew(this->GetRecycler(), ComplexVarDataSet, this->GetRecycler()); + SetDataNode* node = this->list.Append(value, this->GetRecycler()); - JavascriptSet* set = JavascriptSet::FromVar(args[0]); + newComplexSet->Add(value, node); - int size = set->Size(); + this->u.complexVarSet = newComplexSet; + this->kind = SetKind::ComplexVarSet; +} - return JavascriptNumber::ToVar(size, scriptContext); +bool +JavascriptSet::TryAddToIntSet(Var value) +{ + Assert(this->kind == SetKind::IntSet); + Var taggedInt = JavascriptConversion::TryCanonicalizeAsTaggedInt(value); + if (!taggedInt) + { + return false; } - Var JavascriptSet::EntryEntries(RecyclableObject* function, CallInfo callInfo, ...) + int32 intVal = TaggedInt::ToInt32(taggedInt); + if (!this->u.intSet->TestAndSet(intVal)) { - PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); + this->list.Append(taggedInt, this->GetRecycler()); + } + return true; +} - ARGUMENTS(args, callInfo); - ScriptContext* scriptContext = function->GetScriptContext(); +bool +JavascriptSet::TryAddToSimpleVarSet(Var value) +{ + Assert(this->kind == SetKind::SimpleVarSet); + Var simpleVar = JavascriptConversion::TryCanonicalizeAsSimpleVar(value); + if (!simpleVar) + { + return false; + } - if (!JavascriptSet::Is(args[0])) - { - JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Set.prototype.entries"), _u("Set")); - } + if (!this->u.simpleVarSet->ContainsKey(simpleVar)) + { + SetDataNode* node = this->list.Append(simpleVar, this->GetRecycler()); + this->u.simpleVarSet->Add(simpleVar, node); + } - JavascriptSet* set = JavascriptSet::FromVar(args[0]); + return true; +} - return scriptContext->GetLibrary()->CreateSetIterator(set, JavascriptSetIteratorKind::KeyAndValue); +void +JavascriptSet::AddToComplexVarSet(Var value) +{ + Assert(this->kind == SetKind::ComplexVarSet); + if (!this->u.complexVarSet->ContainsKey(value)) + { + SetDataNode* node = this->list.Append(value, this->GetRecycler()); + this->u.complexVarSet->Add(value, node); + } +} + +void +JavascriptSet::Add(Var value) +{ + JS_REENTRANCY_LOCK(jsReentLock, this->GetScriptContext()->GetThreadContext()); + switch (this->kind) + { + case SetKind::EmptySet: + { + this->AddToEmptySet(value); + return; } - Var JavascriptSet::EntryValues(RecyclableObject* function, CallInfo callInfo, ...) + case SetKind::IntSet: { - PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); + if (this->TryAddToIntSet(value)) + { + return; + } - ARGUMENTS(args, callInfo); - ScriptContext* scriptContext = function->GetScriptContext(); + Var simpleVar = JavascriptConversion::TryCanonicalizeAsSimpleVar(value); + if (simpleVar) + { + this->PromoteToSimpleVarSet(); + this->Add(simpleVar); + return; + } + + this->PromoteToComplexVarSet(); + this->Add(value); + return; + } - if (!JavascriptSet::Is(args[0])) + case SetKind::SimpleVarSet: + if (this->TryAddToSimpleVarSet(value)) { - JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Set.prototype.values"), _u("Set")); + return; } - JavascriptSet* set = JavascriptSet::FromVar(args[0]); + this->PromoteToComplexVarSet(); + this->Add(value); + return; - return scriptContext->GetLibrary()->CreateSetIterator(set, JavascriptSetIteratorKind::Value); + case SetKind::ComplexVarSet: + this->AddToComplexVarSet(value); + return; + + default: + Assume(UNREACHED); } +} - Var JavascriptSet::EntryGetterSymbolSpecies(RecyclableObject* function, CallInfo callInfo, ...) +void JavascriptSet::Clear() +{ + JS_REENTRANCY_LOCK(jsReentLock, this->GetScriptContext()->GetThreadContext()); + this->list.Clear(); + switch (this->kind) { - ARGUMENTS(args, callInfo); - - Assert(args.Info.Count > 0); + case SetKind::EmptySet: + return; + case SetKind::IntSet: + this->u.intSet->ClearAll(); + return; + case SetKind::SimpleVarSet: + this->u.simpleVarSet->Clear(); + return; + case SetKind::ComplexVarSet: + this->u.complexVarSet->Clear(); + return; + default: + Assume(UNREACHED); + } +} - return args[0]; +bool +JavascriptSet::IsInIntSet(Var value) +{ + Assert(this->kind == SetKind::IntSet); + Var taggedInt = JavascriptConversion::TryCanonicalizeAsTaggedInt(value); + if (!taggedInt) + { + return false; } + int32 intVal = TaggedInt::ToInt32(taggedInt); + return this->u.intSet->Test(intVal); +} - void JavascriptSet::Add(Var value) +template +bool +JavascriptSet::DeleteFromVarSet(Var value) +{ + Assert(this->kind == (isComplex ? SetKind::ComplexVarSet : SetKind::SimpleVarSet)); + SetDataNode * node = nullptr; + if (isComplex + ? !this->u.complexVarSet->TryGetValueAndRemove(value, &node) + : !this->u.simpleVarSet->TryGetValueAndRemove(value, &node)) { - if (!set->ContainsKey(value)) - { - SetDataNode* node = list.Append(value, GetScriptContext()->GetRecycler()); - set->Add(value, node); - } + return false; } - void JavascriptSet::Clear() + this->list.Remove(node); + return true; +} + +bool +JavascriptSet::DeleteFromSimpleVarSet(Var value) +{ + Assert(this->kind == SetKind::SimpleVarSet); + Var simpleVar = JavascriptConversion::TryCanonicalizeAsSimpleVar(value); + if (!simpleVar) { - // TODO: (Consider) Should we clear the set here and leave it as large as it has grown, or - // toss it away and create a new empty set, letting it grow as needed? - list.Clear(); - set->Clear(); + return false; } - bool JavascriptSet::Delete(Var value) + return this->DeleteFromVarSet(simpleVar); +} + +bool JavascriptSet::Delete(Var value) +{ + JS_REENTRANCY_LOCK(jsReentLock, this->GetScriptContext()->GetThreadContext()); + switch (this->kind) { - if (set->ContainsKey(value)) + case SetKind::EmptySet: + return false; + + case SetKind::IntSet: + if (!IsInIntSet(value)) { - SetDataNode* node = set->Item(value); - list.Remove(node); - return set->Remove(value); + return false; } + // We don't have the list node pointer readily available, so deletion from int sets would require walking the list + // Because of this, let's just promote to a var set + // + // If this promotion becomes an issue, we can consider options to improve this, e.g. deferring until an iterator is requested + this->PromoteToSimpleVarSet(); + return this->Delete(value); + + case SetKind::SimpleVarSet: + return this->DeleteFromSimpleVarSet(value); + + case SetKind::ComplexVarSet: + return this->DeleteFromVarSet(value); + + default: + Assume(UNREACHED); return false; } +} - bool JavascriptSet::Has(Var value) +bool JavascriptSet::Has(Var value) +{ + JS_REENTRANCY_LOCK(jsReentLock, this->GetScriptContext()->GetThreadContext()); + switch (this->kind) + { + case SetKind::EmptySet: + return false; + + case SetKind::IntSet: { - return set->ContainsKey(value); + Var taggedInt = JavascriptConversion::TryCanonicalizeAsTaggedInt(value); + if (!taggedInt) + { + return false; + } + int32 intVal = TaggedInt::ToInt32(taggedInt); + return this->u.intSet->Test(intVal); } - int JavascriptSet::Size() + case SetKind::SimpleVarSet: { - return set->Count(); + // First check if the value is in the set + if (this->u.simpleVarSet->ContainsKey(value)) + { + return true; + } + // If the value isn't in the set, check if the canonical value is + Var simpleVar = JavascriptConversion::TryCanonicalizeAsSimpleVar(value); + // If the simple value is the same as the original value, we know it isn't in the set + if (!simpleVar || simpleVar == value) + { + return false; + } + + return this->u.simpleVarSet->ContainsKey(simpleVar); + } + + case SetKind::ComplexVarSet: + return this->u.complexVarSet->ContainsKey(value); + + default: + Assume(UNREACHED); + return false; } +} - BOOL JavascriptSet::GetDiagTypeString(StringBuilder* stringBuilder, ScriptContext* requestContext) +int JavascriptSet::Size() +{ + JS_REENTRANCY_LOCK(jsReentLock, this->GetScriptContext()->GetThreadContext()); + switch (this->kind) { - stringBuilder->AppendCppLiteral(_u("Set")); - return TRUE; + case SetKind::EmptySet: + return 0; + case SetKind::IntSet: + return this->u.intSet->Count(); + case SetKind::SimpleVarSet: + return this->u.simpleVarSet->Count(); + case SetKind::ComplexVarSet: + return this->u.complexVarSet->Count(); + default: + Assume(UNREACHED); + return 0; } +} + +BOOL JavascriptSet::GetDiagTypeString(StringBuilder* stringBuilder, ScriptContext* requestContext) +{ + stringBuilder->AppendCppLiteral(_u("Set")); + return TRUE; +} #if ENABLE_TTD - void JavascriptSet::MarkVisitKindSpecificPtrs(TTD::SnapshotExtractor* extractor) +void JavascriptSet::MarkVisitKindSpecificPtrs(TTD::SnapshotExtractor* extractor) +{ + auto iterator = this->GetIterator(); + while(iterator.Next()) { - auto iterator = this->GetIterator(); - while(iterator.Next()) - { - extractor->MarkVisitVar(iterator.Current()); - } + extractor->MarkVisitVar(iterator.Current()); } +} - TTD::NSSnapObjects::SnapObjectType JavascriptSet::GetSnapTag_TTD() const +TTD::NSSnapObjects::SnapObjectType JavascriptSet::GetSnapTag_TTD() const +{ + return TTD::NSSnapObjects::SnapObjectType::SnapSetObject; +} + +void JavascriptSet::ExtractSnapObjectDataInto(TTD::NSSnapObjects::SnapObject* objData, TTD::SlabAllocator& alloc) +{ + TTD::NSSnapObjects::SnapSetInfo* ssi = alloc.SlabAllocateStruct(); + ssi->SetSize = 0; + + if(this->Size() == 0) { - return TTD::NSSnapObjects::SnapObjectType::SnapSetObject; + ssi->SetValueArray = nullptr; } - - void JavascriptSet::ExtractSnapObjectDataInto(TTD::NSSnapObjects::SnapObject* objData, TTD::SlabAllocator& alloc) + else { - TTD::NSSnapObjects::SnapSetInfo* ssi = alloc.SlabAllocateStruct(); - ssi->SetSize = 0; + ssi->SetValueArray = alloc.SlabAllocateArray(this->Size()); - if(this->Size() == 0) + auto iter = this->GetIterator(); + while(iter.Next()) { - ssi->SetValueArray = nullptr; + ssi->SetValueArray[ssi->SetSize] = iter.Current(); + ssi->SetSize++; } - else - { - ssi->SetValueArray = alloc.SlabAllocateArray(this->Size()); - - auto iter = this->GetIterator(); - while(iter.Next()) - { - ssi->SetValueArray[ssi->SetSize] = iter.Current(); - ssi->SetSize++; - } - } - - TTD::NSSnapObjects::StdExtractSetKindSpecificInfo(objData, ssi); } - JavascriptSet* JavascriptSet::CreateForSnapshotRestore(ScriptContext* ctx) - { - JavascriptSet* res = ctx->GetLibrary()->CreateSet(); - res->set = RecyclerNew(ctx->GetRecycler(), SetDataSet, ctx->GetRecycler()); + TTD::NSSnapObjects::StdExtractSetKindSpecificInfo(objData, ssi); +} - return res; - } +JavascriptSet* JavascriptSet::CreateForSnapshotRestore(ScriptContext* ctx) +{ + JavascriptSet* res = ctx->GetLibrary()->CreateSet(); + + return res; +} #endif } diff --git a/lib/Runtime/Library/JavascriptSet.h b/lib/Runtime/Library/JavascriptSet.h index 4c5451bbf23..0236c3e14b5 100644 --- a/lib/Runtime/Library/JavascriptSet.h +++ b/lib/Runtime/Library/JavascriptSet.h @@ -11,15 +11,64 @@ namespace Js public: typedef MapOrSetDataNode SetDataNode; typedef MapOrSetDataList SetDataList; - typedef JsUtil::BaseDictionary SetDataSet; + typedef JsUtil::BaseDictionary ComplexVarDataSet; + typedef JsUtil::BaseDictionary SimpleVarDataSet; private: + enum class SetKind : uint8 + { + // An EmptySet is a set containing no elements + EmptySet, + // An IntSet is a set containing only int elements + // + // Adding any TaggedInt or JavascriptNumber that can be represented as a TaggedInt + // will succeed and be stored as an int32 in the set, EXCEPT for the value -1 + // Adding any other value will cause the set to be promoted to a SimpleVarSet or ComplexVarSet, + // depending on the value being added + // + // Deleting any element will also cause the set to be promoted to a SimpleVarSet + IntSet, + // A SimpleVarSet is a set containing only Vars which are comparable by pointer, and don't require + // value comparison + // + // Addition of a Var that is not comparable by pointer value causes the set to be promoted to a ComplexVarSet + SimpleVarSet, + // A ComplexVarSet is a set containing Vars for which we must inspect the values to do a comparison + // This includes Strings, Symbols, and (sometimes) JavascriptNumbers + ComplexVarSet + }; + Field(SetDataList) list; - Field(SetDataSet*) set; + union SetUnion + { + Field(SimpleVarDataSet*) simpleVarSet; + Field(ComplexVarDataSet*) complexVarSet; + Field(BVSparse*) intSet; + SetUnion() {} + }; + + Field(SetUnion) u; + + Field(SetKind) kind = SetKind::EmptySet; DEFINE_VTABLE_CTOR_MEMBER_INIT(JavascriptSet, DynamicObject, list); DEFINE_MARSHAL_OBJECT_TO_SCRIPT_CONTEXT(JavascriptSet); + template + T* CreateVarSetFromList(uint initialCapacity); + void PromoteToSimpleVarSet(); + void PromoteToComplexVarSet(); + + void AddToEmptySet(Var value); + bool TryAddToIntSet(Var value); + bool TryAddToSimpleVarSet(Var value); + void AddToComplexVarSet(Var value); + + bool IsInIntSet(Var value); + + template + bool DeleteFromVarSet(Var value); + bool DeleteFromSimpleVarSet(Var value); public: JavascriptSet(DynamicType* type); @@ -30,7 +79,9 @@ namespace Js static JavascriptSet* UnsafeFromVar(Var aValue); void Add(Var value); + void Clear(); + bool Delete(Var value); bool Has(Var value); int Size(); diff --git a/lib/Runtime/Library/JavascriptString.cpp b/lib/Runtime/Library/JavascriptString.cpp index 7268ea70941..9f89dfb1117 100644 --- a/lib/Runtime/Library/JavascriptString.cpp +++ b/lib/Runtime/Library/JavascriptString.cpp @@ -2976,7 +2976,7 @@ namespace Js return result; } - bool JavascriptString::Equals(Var aLeft, Var aRight) + bool JavascriptString::Equals(JavascriptString* aLeft, JavascriptString* aRight) { return JavascriptStringHelpers::Equals(aLeft, aRight); } @@ -3799,33 +3799,28 @@ namespace Js /* static */ template - bool JavascriptStringHelpers::Equals(Var aLeft, Var aRight) + bool JavascriptStringHelpers::Equals(T* aLeft, T* aRight) { - AssertMsg(T::Is(aLeft) && T::Is(aRight), "string comparison"); - if (aLeft == aRight) return true; - T *leftString = T::UnsafeFromVar(aLeft); - T *rightString = T::UnsafeFromVar(aRight); - // methods could get called a lot of times this can show up as regressions in benchmarks. - volatile T** keepAliveLeftString = (volatile T**)& leftString; - volatile T** keepAliveRightString = (volatile T**)& rightString; + volatile T** keepAliveLeftString = (volatile T**)& aLeft; + volatile T** keepAliveRightString = (volatile T**)& aRight; auto keepAliveLambda = [&]() { UNREFERENCED_PARAMETER(keepAliveLeftString); UNREFERENCED_PARAMETER(keepAliveRightString); }; - if (leftString->GetLength() != rightString->GetLength()) + if (aLeft->GetLength() != aRight->GetLength()) { return false; } - return JsUtil::CharacterBuffer::StaticEquals(leftString->GetString(), rightString->GetString(), leftString->GetLength()); + return JsUtil::CharacterBuffer::StaticEquals(aLeft->GetString(), aRight->GetString(), aLeft->GetLength()); } #if ENABLE_NATIVE_CODEGEN - template bool JavascriptStringHelpers::Equals(Var aLeft, Var aRight); + template bool JavascriptStringHelpers::Equals(JITJavascriptString* aLeft, JITJavascriptString* aRight); #endif } diff --git a/lib/Runtime/Library/JavascriptString.h b/lib/Runtime/Library/JavascriptString.h index 1c8b6519446..e178c3d5698 100644 --- a/lib/Runtime/Library/JavascriptString.h +++ b/lib/Runtime/Library/JavascriptString.h @@ -127,7 +127,7 @@ namespace Js static bool Is(Var aValue); static JavascriptString* FromVar(Var aValue); static JavascriptString* UnsafeFromVar(Var aValue); - static bool Equals(Var aLeft, Var aRight); + static bool Equals(JavascriptString* aLeft, JavascriptString* aRight); static bool LessThan(Var aLeft, Var aRight); static bool IsNegZero(JavascriptString *string); @@ -394,7 +394,7 @@ namespace Js class JavascriptStringHelpers { public: - static bool Equals(Var aLeft, Var aRight); + static bool Equals(T* aLeft, T* aRight); }; } diff --git a/lib/Runtime/Library/MapOrSetDataList.h b/lib/Runtime/Library/MapOrSetDataList.h index 7775689825a..bcd1d89d445 100644 --- a/lib/Runtime/Library/MapOrSetDataList.h +++ b/lib/Runtime/Library/MapOrSetDataList.h @@ -124,6 +124,11 @@ namespace Js { return current->data; } + + Field(MapOrSetDataNode*) CurrentNode() const + { + return current; + } }; void Clear() @@ -189,4 +194,12 @@ namespace Js return Iterator(this); } }; + + + template + class DelayedDeleteMapOrSetDataList : MapOrSetDataList + { + Field(SList*) deletedList; + + }; } diff --git a/lib/Runtime/Library/SameValueComparer.h b/lib/Runtime/Library/SameValueComparer.h index 2fc0dc8990c..357ef6f78c7 100644 --- a/lib/Runtime/Library/SameValueComparer.h +++ b/lib/Runtime/Library/SameValueComparer.h @@ -85,10 +85,16 @@ namespace Js case TypeIds_String: { - JavascriptString* v = JavascriptString::FromVar(i); + JavascriptString* v = JavascriptString::UnsafeFromVar(i); return JsUtil::CharacterBuffer::StaticGetHashCode(v->GetString(), v->GetLength()); } + case TypeIds_Symbol: + { + JavascriptSymbol* sym = JavascriptSymbol::UnsafeFromVar(i); + return sym->GetValue()->GetHashCode(); + } + default: return RecyclerPointerComparer::GetHashCode(i); } diff --git a/lib/Runtime/Types/RecyclableObject.cpp b/lib/Runtime/Types/RecyclableObject.cpp index 7c0b42f9e73..b9a6a85a5af 100644 --- a/lib/Runtime/Types/RecyclableObject.cpp +++ b/lib/Runtime/Types/RecyclableObject.cpp @@ -669,7 +669,8 @@ namespace Js case TypeIds_Symbol: goto ReturnFalse; case TypeIds_String: - goto CompareStrings; + *value = JavascriptString::Equals(JavascriptString::UnsafeFromVar(aLeft), JavascriptString::UnsafeFromVar(aRight)); + return TRUE; case TypeIds_Number: case TypeIds_Integer: case TypeIds_Boolean: @@ -756,9 +757,6 @@ namespace Js rightType = JavascriptOperators::GetTypeId(aRight); redoCount++; goto Redo; - CompareStrings: - *value = JavascriptString::Equals(aLeft, aRight); - return TRUE; CompareDoubles: *value = dblLeft == dblRight; return TRUE;