blob: 0ce9be33240022ebc4d594fe2abb4e8e17062044 [file]
//-------------------------------------------------------------------------------------------------------
// 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
{
#if DBG
void SharedContents::AddAgent(DWORD_PTR agent)
{
AutoCriticalSection autoCS(&csAgent);
if (allowedAgents == nullptr)
{
allowedAgents = HeapNew(SharableAgents, &HeapAllocator::Instance);
}
allowedAgents->Add(agent);
}
bool SharedContents::IsValidAgent(DWORD_PTR agent)
{
AutoCriticalSection autoCS(&csAgent);
return allowedAgents != nullptr && allowedAgents->Contains(agent);
}
#endif
long SharedContents::AddRef()
{
return InterlockedIncrement(&refCount);
}
long SharedContents::Release()
{
long ret = InterlockedDecrement(&refCount);
AssertOrFailFastMsg(ret >= 0, "Buffer already freed");
return ret;
}
void SharedContents::Cleanup()
{
Assert(refCount == 0);
buffer = nullptr;
bufferLength = 0;
#if DBG
{
AutoCriticalSection autoCS(&csAgent);
if (allowedAgents != nullptr)
{
HeapDelete(allowedAgents);
allowedAgents = nullptr;
}
}
#endif
if (indexToWaiterList != nullptr)
{
// TODO: the map should be empty here?
// or we need to wake all the waiters from current context?
indexToWaiterList->Map([](uint index, WaiterList *waiters)
{
if (waiters != nullptr)
{
waiters->Cleanup();
HeapDelete(waiters);
waiters = nullptr;
}
});
HeapDelete(indexToWaiterList);
indexToWaiterList = nullptr;
}
}
Var SharedArrayBuffer::NewInstance(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
ScriptContext* scriptContext = function->GetScriptContext();
AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'");
Var newTarget = callInfo.Flags & CallFlags_NewTarget ? args.Values[args.Info.Count] : args[0];
bool isCtorSuperCall = (callInfo.Flags & CallFlags_New) && newTarget != nullptr && !JavascriptOperators::IsUndefined(newTarget);
Assert(isCtorSuperCall || !(callInfo.Flags & CallFlags_New) || args[0] == nullptr);
if (!(callInfo.Flags & CallFlags_New) || (newTarget && JavascriptOperators::IsUndefinedObject(newTarget)))
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_ClassConstructorCannotBeCalledWithoutNew, _u("SharedArrayBuffer"));
}
uint32 byteLength = 0;
if (args.Info.Count > 1)
{
byteLength = ArrayBuffer::ToIndex(args[1], JSERR_ArrayLengthConstructIncorrect, scriptContext, MaxSharedArrayBufferLength);
}
RecyclableObject* newArr = scriptContext->GetLibrary()->CreateSharedArrayBuffer(byteLength);
return isCtorSuperCall ?
JavascriptOperators::OrdinaryCreateFromConstructor(RecyclableObject::FromVar(newTarget), newArr, nullptr, scriptContext) :
newArr;
}
// SharedArrayBuffer.prototype.byteLength
Var SharedArrayBuffer::EntryGetterByteLength(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
ScriptContext* scriptContext = function->GetScriptContext();
Assert(!(callInfo.Flags & CallFlags_New));
if (args.Info.Count == 0 || !SharedArrayBuffer::Is(args[0]))
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedSharedArrayBufferObject);
}
SharedArrayBuffer* sharedArrayBuffer = SharedArrayBuffer::FromVar(args[0]);
return JavascriptNumber::ToVar(sharedArrayBuffer->GetByteLength(), scriptContext);
}
// SharedArrayBuffer.prototype.slice
Var SharedArrayBuffer::EntrySlice(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
ScriptContext* scriptContext = function->GetScriptContext();
AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'");
Assert(!(callInfo.Flags & CallFlags_New));
if (!SharedArrayBuffer::Is(args[0]))
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedSharedArrayBufferObject);
}
JavascriptLibrary* library = scriptContext->GetLibrary();
SharedArrayBuffer* currentBuffer = SharedArrayBuffer::FromVar(args[0]);
int64 currentLen = (int64)currentBuffer->GetByteLength();
int64 start = 0, end = 0;
int64 newLen = 0;
// If no start or end arguments, use the entire length
if (args.Info.Count < 2)
{
newLen = currentLen;
}
else
{
start = JavascriptArray::GetIndexFromVar(args[1], currentLen, scriptContext);
// If no end argument, use length as the end
if (args.Info.Count < 3 || args[2] == library->GetUndefined())
{
end = currentLen;
}
else
{
end = JavascriptArray::GetIndexFromVar(args[2], currentLen, scriptContext);
}
newLen = end > start ? end - start : 0;
}
// We can't have allocated an SharedArrayBuffer with byteLength > MaxArrayBufferLength.
// start and end are clamped to valid indices, so the new length also cannot exceed MaxArrayBufferLength.
// Therefore, should be safe to cast down newLen to uint32.
Assert(newLen < MaxSharedArrayBufferLength);
uint32 newbyteLength = static_cast<uint32>(newLen);
SharedArrayBuffer* newBuffer = nullptr;
if (scriptContext->GetConfig()->IsES6SpeciesEnabled())
{
Var constructorVar = JavascriptOperators::SpeciesConstructor(currentBuffer, scriptContext->GetLibrary()->GetSharedArrayBufferConstructor(), scriptContext);
JavascriptFunction* constructor = JavascriptFunction::FromVar(constructorVar);
Js::Var constructorArgs[] = { constructor, JavascriptNumber::ToVar(newbyteLength, scriptContext) };
Js::CallInfo constructorCallInfo(Js::CallFlags_New, _countof(constructorArgs));
Js::Var newVar = JavascriptOperators::NewScObject(constructor, Js::Arguments(constructorCallInfo, constructorArgs), scriptContext);
if (!SharedArrayBuffer::Is(newVar))
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedSharedArrayBufferObject);
}
newBuffer = SharedArrayBuffer::FromVar(newVar);
if (newBuffer == currentBuffer)
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedSharedArrayBufferObject);
}
if (newBuffer->GetByteLength() < newbyteLength)
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_ArgumentOutOfRange, _u("SharedArrayBuffer.prototype.slice"));
}
}
else
{
newBuffer = library->CreateSharedArrayBuffer(newbyteLength);
}
Assert(newBuffer);
Assert(newBuffer->GetByteLength() >= newbyteLength);
// Don't bother doing memcpy if we aren't copying any elements
if (newbyteLength > 0)
{
AssertMsg(currentBuffer->GetBuffer() != nullptr, "buffer must not be null when we copy from it");
js_memcpy_s(newBuffer->GetBuffer(), newbyteLength, currentBuffer->GetBuffer() + start, newbyteLength);
}
return newBuffer;
}
Var SharedArrayBuffer::EntryGetterSymbolSpecies(RecyclableObject* function, CallInfo callInfo, ...)
{
ARGUMENTS(args, callInfo);
Assert(args.Info.Count > 0);
return args[0];
}
SharedArrayBuffer* SharedArrayBuffer::FromVar(Var aValue)
{
AssertMsg(Is(aValue), "var must be an SharedArrayBuffer");
return static_cast<SharedArrayBuffer *>(RecyclableObject::FromVar(aValue));
}
bool SharedArrayBuffer::Is(Var aValue)
{
return JavascriptOperators::GetTypeId(aValue) == TypeIds_SharedArrayBuffer;
}
SharedArrayBuffer::SharedArrayBuffer(uint32 length, DynamicType * type) :
ArrayBufferBase(type), sharedContents(nullptr)
{
BYTE * buffer = nullptr;
if (length > MaxSharedArrayBufferLength)
{
// http://tc39.github.io/ecmascript_sharedmem/shmem.html#DataTypesValues.SpecTypes.DataBlocks.CreateSharedByteDataBlock
// Let db be a new Shared Data Block value consisting of size bytes.
// If it is impossible to create such a Shared Data Block, throw a RangeError exception.
JavascriptError::ThrowRangeError(GetScriptContext(), JSERR_FunctionArgument_Invalid);
}
Recycler* recycler = GetType()->GetLibrary()->GetRecycler();
auto alloc = [this](uint len)->BYTE*
{
#if ENABLE_FAST_ARRAYBUFFER
if (this->IsValidVirtualBufferLength(len))
{
return (BYTE*)AsmJsVirtualAllocator(len);
}
else
#endif
{
return HeapNewNoThrowArray(BYTE, len);
}
};
if (recycler->ReportExternalMemoryAllocation(length))
{
buffer = alloc(length);
if (buffer == nullptr)
{
recycler->CollectNow<CollectOnTypedArrayAllocation>();
buffer = alloc(length);
if (buffer == nullptr)
{
recycler->ReportExternalMemoryFailure(length);
JavascriptError::ThrowOutOfMemoryError(GetScriptContext());
}
}
}
else
{
JavascriptError::ThrowOutOfMemoryError(GetScriptContext());
}
Assert(buffer != nullptr);
ZeroMemory(buffer, length);
sharedContents = HeapNewNoThrow(SharedContents, buffer, length);
if (sharedContents == nullptr)
{
#if ENABLE_FAST_ARRAYBUFFER
//AsmJS Virtual Free
if (this->IsValidVirtualBufferLength(length))
{
FreeMemAlloc(buffer);
}
else
#endif
{
HeapDeleteArray(length, buffer);
}
recycler->ReportExternalMemoryFailure(length);
JavascriptError::ThrowOutOfMemoryError(GetScriptContext());
}
#if DBG
sharedContents->AddAgent((DWORD_PTR)GetScriptContext());
#endif
}
SharedArrayBuffer::SharedArrayBuffer(SharedContents * contents, DynamicType * type) :
ArrayBufferBase(type), sharedContents(nullptr)
{
if (contents == nullptr || contents->bufferLength > MaxSharedArrayBufferLength)
{
JavascriptError::ThrowTypeError(GetScriptContext(), JSERR_FunctionArgument_Invalid);
}
if (contents->AddRef() > 1)
{
sharedContents = contents;
}
else
{
Js::Throw::FatalInternalError();
}
#if DBG
sharedContents->AddAgent((DWORD_PTR)GetScriptContext());
#endif
}
CriticalSection SharedArrayBuffer::csSharedArrayBuffer;
WaiterList *SharedArrayBuffer::GetWaiterList(uint index)
{
if (sharedContents != nullptr)
{
// REVIEW: only lock creating the map and pass the lock to the map?
// use one lock per instance?
AutoCriticalSection autoCS(&csSharedArrayBuffer);
if (sharedContents->indexToWaiterList == nullptr)
{
sharedContents->indexToWaiterList = HeapNew(IndexToWaitersMap, &HeapAllocator::Instance);
}
WaiterList * waiters = nullptr;
if (!sharedContents->indexToWaiterList->TryGetValue(index, &waiters))
{
waiters = HeapNew(WaiterList);
sharedContents->indexToWaiterList->Add(index, waiters);
}
return waiters;
}
Assert(false);
return nullptr;
}
uint32 SharedArrayBuffer::GetByteLength() const
{
return sharedContents != nullptr ? sharedContents->bufferLength : 0;
}
BYTE* SharedArrayBuffer::GetBuffer() const
{
return sharedContents != nullptr ? sharedContents->buffer : nullptr;
}
BOOL SharedArrayBuffer::GetDiagTypeString(StringBuilder<ArenaAllocator>* stringBuilder, ScriptContext* requestContext)
{
stringBuilder->AppendCppLiteral(_u("Object, (SharedArrayBuffer)"));
return TRUE;
}
BOOL SharedArrayBuffer::GetDiagValueString(StringBuilder<ArenaAllocator>* stringBuilder, ScriptContext* requestContext)
{
stringBuilder->AppendCppLiteral(_u("[object SharedArrayBuffer]"));
return TRUE;
}
JavascriptSharedArrayBuffer::JavascriptSharedArrayBuffer(uint32 length, DynamicType * type) :
SharedArrayBuffer(length, type)
{
}
JavascriptSharedArrayBuffer::JavascriptSharedArrayBuffer(SharedContents *sharedContents, DynamicType * type) :
SharedArrayBuffer(sharedContents, type)
{
}
JavascriptSharedArrayBuffer* JavascriptSharedArrayBuffer::Create(uint32 length, DynamicType * type)
{
Recycler* recycler = type->GetScriptContext()->GetRecycler();
JavascriptSharedArrayBuffer* result = RecyclerNewFinalized(recycler, JavascriptSharedArrayBuffer, length, type);
recycler->AddExternalMemoryUsage(length);
return result;
}
JavascriptSharedArrayBuffer* JavascriptSharedArrayBuffer::Create(SharedContents *sharedContents, DynamicType * type)
{
Recycler* recycler = type->GetScriptContext()->GetRecycler();
JavascriptSharedArrayBuffer* result = RecyclerNewFinalized(recycler, JavascriptSharedArrayBuffer, sharedContents, type);
return result;
}
bool SharedArrayBuffer::IsValidVirtualBufferLength(uint length) const
{
#if ENABLE_FAST_ARRAYBUFFER
/*
1. length >= 2^16
2. length is power of 2 or (length > 2^24 and length is multiple of 2^24)
3. length is a multiple of 4K
*/
return (!PHASE_OFF1(Js::TypedArrayVirtualPhase) &&
(length >= 0x10000) &&
(((length & (~length + 1)) == length) ||
(length >= 0x1000000 &&
((length & 0xFFFFFF) == 0)
)
) &&
((length % AutoSystemInfo::PageSize) == 0)
);
#else
return false;
#endif
}
void JavascriptSharedArrayBuffer::Finalize(bool isShutdown)
{
if (sharedContents == nullptr)
{
return;
}
uint ref = sharedContents->Release();
if (ref == 0)
{
#if ENABLE_FAST_ARRAYBUFFER
//AsmJS Virtual Free
if (this->IsValidVirtualBufferLength(sharedContents->bufferLength))
{
FreeMemAlloc(sharedContents->buffer);
}
else
#endif
{
HeapDeleteArray(sharedContents->bufferLength, sharedContents->buffer);
}
Recycler* recycler = GetType()->GetLibrary()->GetRecycler();
recycler->ReportExternalMemoryFree(sharedContents->bufferLength);
sharedContents->Cleanup();
HeapDelete(sharedContents);
}
sharedContents = nullptr;
}
void JavascriptSharedArrayBuffer::Dispose(bool isShutdown)
{
/* See JavascriptArrayBuffer::Finalize */
}
WaiterList::WaiterList()
: m_waiters(nullptr)
{
InitWaiterList();
}
void WaiterList::InitWaiterList()
{
Assert(m_waiters == nullptr);
m_waiters = HeapNew(Waiters, &HeapAllocator::Instance);
}
void WaiterList::Cleanup()
{
if (m_waiters != nullptr)
{
HeapDelete(m_waiters);
m_waiters = nullptr;
}
}
bool WaiterList::Contains(DWORD_PTR agent)
{
Assert(m_waiters != nullptr);
for (int i = 0; i < m_waiters->Count(); i++)
{
if (m_waiters->Item(i).identity == agent)
{
return true;
}
}
return false;
}
bool WaiterList::AddAndSuspendWaiter(DWORD_PTR waiter, uint32 timeout)
{
#ifdef _WIN32
Assert(m_waiters != nullptr);
Assert(waiter != NULL);
Assert(!Contains(waiter));
AgentOfBuffer agent(waiter, CreateEvent(NULL, TRUE, FALSE, NULL));
m_waiters->Add(agent);
csForAccess.Leave();
DWORD result = WaitForSingleObject(agent.event, timeout);
csForAccess.Enter();
return result == WAIT_OBJECT_0;
#else
// TODO for xplat
return false;
#endif
}
void WaiterList::RemoveWaiter(DWORD_PTR waiter)
{
#ifdef _WIN32
Assert(m_waiters != nullptr);
for (int i = m_waiters->Count() - 1; i >= 0; i--)
{
if (m_waiters->Item(i).identity == waiter)
{
CloseHandle(m_waiters->Item(i).event);
m_waiters->RemoveAt(i);
return;
}
}
Assert(false);
#endif
// TODO for xplat
}
uint32 WaiterList::RemoveAndWakeWaiters(int32 count)
{
Assert(m_waiters != nullptr);
Assert(count >= 0);
uint32 removed = 0;
#ifdef _WIN32
while (count > 0 && m_waiters->Count() > 0)
{
AgentOfBuffer agent = m_waiters->Item(0);
m_waiters->RemoveAt(0);
count--; removed++;
SetEvent(agent.event);
// This agent will be closed when their respective call to wait has returned
}
#endif
return removed;
}
bool AgentOfBuffer::AgentCanSuspend(ScriptContext *scriptContext)
{
// Currently we are allowing every agent (including main page) to suspend. If we want only web-worker to suspend un-comment below code.
// return scriptContext != nullptr && scriptContext->webWorkerId != Constants::NonWebWorkerContextId;
return true;
}
}