blob: 391a47fa080dba229e35d6945a76219d50a754c5 [file] [log] [blame]
//-------------------------------------------------------------------------------------------------------
// 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"
#define ATOMICS_FUNCTION_ENTRY_CHECKS(length, methodName) \
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); \
ARGUMENTS(args, callInfo); \
ScriptContext* scriptContext = function->GetScriptContext(); \
Assert(!(callInfo.Flags & CallFlags_New)); \
if (args.Info.Count <= length) \
{ \
JavascriptError::ThrowRangeError(scriptContext, JSERR_WinRTFunction_TooFewArguments, _u(methodName)); \
} \
namespace Js
{
Var AtomicsObject::ValidateSharedIntegerTypedArray(Var typedArray, ScriptContext *scriptContext, bool onlyInt32)
{
if (!TypedArrayBase::Is(typedArray))
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedTypedArrayObject);
}
TypeId typeId = JavascriptOperators::GetTypeId(typedArray);
if (onlyInt32)
{
if (typeId != TypeIds_Int32Array)
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_InvalidOperationOnTypedArray);
}
}
else
{
if (!(typeId == TypeIds_Int8Array || typeId == TypeIds_Uint8Array || typeId == TypeIds_Int16Array ||
typeId == TypeIds_Uint16Array || typeId == TypeIds_Int32Array || typeId == TypeIds_Uint32Array))
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_InvalidOperationOnTypedArray);
}
}
TypedArrayBase *typedArrayBase = TypedArrayBase::UnsafeFromVar(typedArray);
ArrayBufferBase* arrayBuffer = typedArrayBase->GetArrayBuffer();
if (arrayBuffer == nullptr || !ArrayBufferBase::Is(arrayBuffer) || !arrayBuffer->IsSharedArrayBuffer())
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedSharedArrayBufferObject);
}
return arrayBuffer;
}
uint32 AtomicsObject::ValidateAtomicAccess(Var typedArray, Var requestIndex, ScriptContext *scriptContext)
{
Assert(TypedArrayBase::Is(typedArray));
int32 accessIndex = -1;
if (TaggedInt::Is(requestIndex))
{
accessIndex = TaggedInt::ToInt32(requestIndex);
}
else if(Js::JavascriptOperators::IsUndefined(requestIndex))
{
accessIndex = 0;
}
else
{
accessIndex = JavascriptConversion::ToInt32_Full(requestIndex, scriptContext);
double dblValue = JavascriptConversion::ToInteger(requestIndex, scriptContext);
if (dblValue != accessIndex)
{
JavascriptError::ThrowRangeError(scriptContext, JSERR_InvalidTypedArrayIndex);
}
}
if (accessIndex < 0 || accessIndex >= (int32)TypedArrayBase::FromVar(typedArray)->GetLength())
{
JavascriptError::ThrowRangeError(scriptContext, JSERR_InvalidTypedArrayIndex);
}
return (uint32)accessIndex;
}
TypedArrayBase * AtomicsObject::ValidateAndGetTypedArray(Var typedArray, Var index, __out uint32 *accessIndex, ScriptContext *scriptContext, bool onlyInt32)
{
ValidateSharedIntegerTypedArray(typedArray, scriptContext, onlyInt32);
uint32 i = ValidateAtomicAccess(typedArray, index, scriptContext);
if (accessIndex != nullptr)
{
*accessIndex = i;
}
return TypedArrayBase::FromVar(typedArray);
}
Var AtomicsObject::EntryAdd(RecyclableObject* function, CallInfo callInfo, ...)
{
ATOMICS_FUNCTION_ENTRY_CHECKS(3, "Atomics.add");
uint32 accessIndex = 0;
TypedArrayBase *typedArrayBase = ValidateAndGetTypedArray(args[1], args[2], &accessIndex, scriptContext);
return typedArrayBase->TypedAdd(accessIndex, args[3]);
}
Var AtomicsObject::EntryAnd(RecyclableObject* function, CallInfo callInfo, ...)
{
ATOMICS_FUNCTION_ENTRY_CHECKS(3, "Atomics.and");
uint32 accessIndex = 0;
TypedArrayBase *typedArrayBase = ValidateAndGetTypedArray(args[1], args[2], &accessIndex, scriptContext);
return typedArrayBase->TypedAnd(accessIndex, args[3]);
}
Var AtomicsObject::EntryCompareExchange(RecyclableObject* function, CallInfo callInfo, ...)
{
ATOMICS_FUNCTION_ENTRY_CHECKS(4, "Atomics.compareExchange");
uint32 accessIndex = 0;
TypedArrayBase *typedArrayBase = ValidateAndGetTypedArray(args[1], args[2], &accessIndex, scriptContext);
return typedArrayBase->TypedCompareExchange(accessIndex, args[3], args[4]);
}
Var AtomicsObject::EntryExchange(RecyclableObject* function, CallInfo callInfo, ...)
{
ATOMICS_FUNCTION_ENTRY_CHECKS(3, "Atomics.exchange");
uint32 accessIndex = 0;
TypedArrayBase *typedArrayBase = ValidateAndGetTypedArray(args[1], args[2], &accessIndex, scriptContext);
return typedArrayBase->TypedExchange(accessIndex, args[3]);
}
Var AtomicsObject::EntryIsLockFree(RecyclableObject* function, CallInfo callInfo, ...)
{
ATOMICS_FUNCTION_ENTRY_CHECKS(1, "Atomics.isLockFree");
uint32 size = JavascriptConversion::ToUInt32(args[1], scriptContext);
// TODO : Currently for the size 1, 2 and 4 we are treating them lock free, we will take a look at this later.
return (size == 1 || size == 2 || size == 4) ? scriptContext->GetLibrary()->GetTrue() : scriptContext->GetLibrary()->GetFalse();
}
Var AtomicsObject::EntryLoad(RecyclableObject* function, CallInfo callInfo, ...)
{
ATOMICS_FUNCTION_ENTRY_CHECKS(2, "Atomics.load");
uint32 accessIndex = 0;
TypedArrayBase *typedArrayBase = ValidateAndGetTypedArray(args[1], args[2], &accessIndex, scriptContext);
return typedArrayBase->TypedLoad(accessIndex);
}
Var AtomicsObject::EntryOr(RecyclableObject* function, CallInfo callInfo, ...)
{
ATOMICS_FUNCTION_ENTRY_CHECKS(3, "Atomics.or");
uint32 accessIndex = 0;
TypedArrayBase *typedArrayBase = ValidateAndGetTypedArray(args[1], args[2], &accessIndex, scriptContext);
return typedArrayBase->TypedOr(accessIndex, args[3]);
}
Var AtomicsObject::EntryStore(RecyclableObject* function, CallInfo callInfo, ...)
{
ATOMICS_FUNCTION_ENTRY_CHECKS(3, "Atomics.store");
uint32 accessIndex = 0;
TypedArrayBase *typedArrayBase = ValidateAndGetTypedArray(args[1], args[2], &accessIndex, scriptContext);
return typedArrayBase->TypedStore(accessIndex, args[3]);
}
Var AtomicsObject::EntrySub(RecyclableObject* function, CallInfo callInfo, ...)
{
ATOMICS_FUNCTION_ENTRY_CHECKS(3, "Atomics.sub");
uint32 accessIndex = 0;
TypedArrayBase *typedArrayBase = ValidateAndGetTypedArray(args[1], args[2], &accessIndex, scriptContext);
return typedArrayBase->TypedSub(accessIndex, args[3]);
}
Var AtomicsObject::EntryWait(RecyclableObject* function, CallInfo callInfo, ...)
{
ATOMICS_FUNCTION_ENTRY_CHECKS(3, "Atomics.wait");
uint32 accessIndex = 0;
TypedArrayBase *typedArrayBase = ValidateAndGetTypedArray(args[1], args[2], &accessIndex, scriptContext, true /*onlyInt32*/);
int32 value = JavascriptConversion::ToInt32(args[3], scriptContext);
uint32 timeout = INFINITE;
if (args.Info.Count > 4 && !JavascriptOperators::IsUndefinedObject(args[4]))
{
double t =JavascriptConversion::ToNumber(args[4], scriptContext);
if (!(NumberUtilities::IsNan(t) || JavascriptNumber::IsPosInf(t)))
{
int32 t1 = JavascriptConversion::ToInt32(t);
timeout = (uint32)max(0, t1);
}
}
if (!AgentOfBuffer::AgentCanSuspend(scriptContext))
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_CannotSuspendBuffer);
}
Assert(typedArrayBase->GetBytesPerElement() == 4);
uint32 bufferIndex = (accessIndex * 4) + typedArrayBase->GetByteOffset();
Assert(bufferIndex < typedArrayBase->GetArrayBuffer()->GetByteLength());
SharedArrayBuffer *sharedArrayBuffer = typedArrayBase->GetArrayBuffer()->GetAsSharedArrayBuffer();
WaiterList *waiterList = sharedArrayBuffer->GetWaiterList(bufferIndex);
bool awoken = false;
{
AutoCriticalSection autoCS(waiterList->GetCriticalSectionForAccess());
int32 w = JavascriptConversion::ToInt32(typedArrayBase->DirectGetItem(accessIndex), scriptContext);
if (value != w)
{
return scriptContext->GetLibrary()->CreateStringFromCppLiteral(_u("not-equal"));
}
DWORD_PTR agent = (DWORD_PTR)scriptContext;
Assert(sharedArrayBuffer->GetSharedContents()->IsValidAgent(agent));
#pragma prefast(suppress:__WARNING_CALLER_FAILING_TO_HOLD, "This is a prefast false-positive caused by it being unable to identify that the critical section used here is the same as the one held by the AutoCriticalSection")
awoken = waiterList->AddAndSuspendWaiter(agent, timeout);
if (!awoken)
{
waiterList->RemoveWaiter(agent);
}
}
return awoken ? scriptContext->GetLibrary()->CreateStringFromCppLiteral(_u("ok"))
: scriptContext->GetLibrary()->CreateStringFromCppLiteral(_u("timed-out"));
}
Var AtomicsObject::EntryNotify(RecyclableObject* function, CallInfo callInfo, ...)
{
ATOMICS_FUNCTION_ENTRY_CHECKS(2, "Atomics.notify");
uint32 accessIndex = 0;
TypedArrayBase *typedArrayBase = ValidateAndGetTypedArray(args[1], args[2], &accessIndex, scriptContext, true /*onlyInt32*/);
int32 count = INT_MAX;
if (args.Info.Count > 3 && !JavascriptOperators::IsUndefinedObject(args[3]))
{
double d = JavascriptConversion::ToInteger(args[3], scriptContext);
if (!(NumberUtilities::IsNan(d) || JavascriptNumber::IsPosInf(d)))
{
int32 c = JavascriptConversion::ToInt32(d);
count = max(0, c);
}
}
Assert(typedArrayBase->GetBytesPerElement() == 4);
uint32 bufferIndex = (accessIndex * 4) + typedArrayBase->GetByteOffset();
Assert(bufferIndex < typedArrayBase->GetArrayBuffer()->GetByteLength());
SharedArrayBuffer *sharedArrayBuffer = typedArrayBase->GetArrayBuffer()->GetAsSharedArrayBuffer();
WaiterList *waiterList = sharedArrayBuffer->GetWaiterList(bufferIndex);
uint32 removed = 0;
{
AutoCriticalSection autoCS(waiterList->GetCriticalSectionForAccess());
removed = waiterList->RemoveAndWakeWaiters(count);
}
return JavascriptNumber::ToVar(removed, scriptContext);
}
Var AtomicsObject::EntryXor(RecyclableObject* function, CallInfo callInfo, ...)
{
ATOMICS_FUNCTION_ENTRY_CHECKS(3, "Atomics.xor");
uint32 accessIndex = 0;
TypedArrayBase *typedArrayBase = ValidateAndGetTypedArray(args[1], args[2], &accessIndex, scriptContext);
return typedArrayBase->TypedXor(accessIndex, args[3]);
}
}