blob: 8e7ae82fbd34c00a192145512fc25b382f626de6 [file]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
// Implements ArrayBuffer according to Khronos spec.
//----------------------------------------------------------------------------
#pragma once
namespace Js
{
class ArrayBufferParent;
class ArrayBuffer;
class SharedArrayBuffer;
class ArrayBufferBase : public DynamicObject
{
protected:
#if ENABLE_FAST_ARRAYBUFFER
#define MAX_ASMJS_ARRAYBUFFER_LENGTH 0x100000000 // 4GB
#define MAX_WASM__ARRAYBUFFER_LENGTH 0x200000000 // 8GB
typedef void*(*AllocWrapperType)(size_t);
#define AsmJsVirtualAllocator ((AllocWrapperType)Js::ArrayBuffer::AllocWrapper<MAX_ASMJS_ARRAYBUFFER_LENGTH>)
#define WasmVirtualAllocator ((AllocWrapperType)Js::ArrayBuffer::AllocWrapper<MAX_WASM__ARRAYBUFFER_LENGTH>)
template<size_t MaxVirtualSize = MAX_ASMJS_ARRAYBUFFER_LENGTH>
static void* __cdecl AllocWrapper(DECLSPEC_GUARD_OVERFLOW size_t length)
{
LPVOID address = VirtualAlloc(nullptr, MaxVirtualSize, MEM_RESERVE, PAGE_NOACCESS);
//throw out of memory
if (!address)
{
return nullptr;
}
if (length == 0)
{
return address;
}
LPVOID arrayAddress = VirtualAlloc(address, length, MEM_COMMIT, PAGE_READWRITE);
if (!arrayAddress)
{
VirtualFree(address, 0, MEM_RELEASE);
return nullptr;
}
return arrayAddress;
}
static void FreeMemAlloc(Var ptr)
{
BOOL fSuccess = VirtualFree((LPVOID)ptr, 0, MEM_RELEASE);
Assert(fSuccess);
}
#else
static void* __cdecl AllocWrapper(DECLSPEC_GUARD_OVERFLOW size_t length)
{
// This allocator should never be used
Js::Throw::FatalInternalError();
}
#define AsmJsVirtualAllocator Js::ArrayBuffer::AllocWrapper
#define WasmVirtualAllocator Js::ArrayBuffer::AllocWrapper
#endif
public:
DEFINE_VTABLE_CTOR_ABSTRACT(ArrayBufferBase, DynamicObject);
virtual void MarshalToScriptContext(Js::ScriptContext * scriptContext) = 0;
ArrayBufferBase(DynamicType *type) : DynamicObject(type), isDetached(false) { }
bool IsDetached() { return isDetached; }
#if ENABLE_TTD
virtual void MarshalCrossSite_TTDInflate() = 0;
#endif
virtual bool IsArrayBuffer() = 0;
virtual bool IsSharedArrayBuffer() = 0;
virtual bool IsWebAssemblyArrayBuffer() { return false; }
virtual ArrayBuffer * GetAsArrayBuffer() = 0;
virtual SharedArrayBuffer * GetAsSharedArrayBuffer() { return nullptr; }
virtual void AddParent(ArrayBufferParent* parent) { }
virtual uint32 GetByteLength() const = 0;
virtual BYTE* GetBuffer() const = 0;
virtual bool IsValidVirtualBufferLength(uint length) const { return false; };
static bool Is(Var value);
static ArrayBufferBase* FromVar(Var value);
static int GetIsDetachedOffset() { return offsetof(ArrayBufferBase, isDetached); }
protected:
Field(bool) isDetached;
};
class ArrayBuffer : public ArrayBufferBase
{
public:
// we need to install cross-site thunk on the nested array buffer when marshaling
// typed array.
DEFINE_VTABLE_CTOR_ABSTRACT(ArrayBuffer, ArrayBufferBase);
private:
void DetachBufferFromParent(ArrayBufferParent* parent);
public:
template <typename FreeFN>
class ArrayBufferDetachedState : public ArrayBufferDetachedStateBase
{
public:
FreeFN* freeFunction;
ArrayBufferDetachedState(BYTE* buffer, uint32 bufferLength, FreeFN* freeFunction, ArrayBufferAllocationType allocationType)
: ArrayBufferDetachedStateBase(TypeIds_ArrayBuffer, buffer, bufferLength, allocationType),
freeFunction(freeFunction)
{}
virtual void ClearSelfOnly() override
{
HeapDelete(this);
}
virtual void DiscardState() override
{
if (this->buffer != nullptr)
{
freeFunction(this->buffer);
this->buffer = nullptr;
}
this->bufferLength = 0;
}
virtual void Discard() override
{
ClearSelfOnly();
}
};
template <typename Allocator>
ArrayBuffer(DECLSPEC_GUARD_OVERFLOW uint32 length, DynamicType * type, Allocator allocator);
ArrayBuffer(byte* buffer, DECLSPEC_GUARD_OVERFLOW uint32 length, DynamicType * type);
class EntryInfo
{
public:
static FunctionInfo NewInstance;
static FunctionInfo Slice;
static FunctionInfo IsView;
static FunctionInfo GetterByteLength;
static FunctionInfo GetterSymbolSpecies;
static FunctionInfo Transfer;
};
static Var NewInstance(RecyclableObject* function, CallInfo callInfo, ...);
static Var EntrySlice(RecyclableObject* function, CallInfo callInfo, ...);
static Var EntryIsView(RecyclableObject* function, CallInfo callInfo, ...);
static Var EntryGetterByteLength(RecyclableObject* function, CallInfo callInfo, ...);
static Var EntryGetterSymbolSpecies(RecyclableObject* function, CallInfo callInfo, ...);
static Var EntryTransfer(RecyclableObject* function, CallInfo callInfo, ...);
static bool Is(Var aValue);
static ArrayBuffer* NewFromDetachedState(DetachedStateBase* state, JavascriptLibrary *library);
static ArrayBuffer* FromVar(Var aValue);
virtual BOOL GetDiagTypeString(StringBuilder<ArenaAllocator>* stringBuilder, ScriptContext* requestContext) override;
virtual BOOL GetDiagValueString(StringBuilder<ArenaAllocator>* stringBuilder, ScriptContext* requestContext) override;
virtual ArrayBufferDetachedStateBase* DetachAndGetState();
virtual uint32 GetByteLength() const override { return bufferLength; }
virtual BYTE* GetBuffer() const override { return buffer; }
static int GetByteLengthOffset() { return offsetof(ArrayBuffer, bufferLength); }
static int GetBufferOffset() { return offsetof(ArrayBuffer, buffer); }
virtual void AddParent(ArrayBufferParent* parent) override;
#if _WIN64
//maximum 2G -1 for amd64
static const uint32 MaxArrayBufferLength = 0x7FFFFFFF;
#else
// maximum 1G to avoid arithmetic overflow.
static const uint32 MaxArrayBufferLength = 1 << 30;
#endif
static const uint32 ParentsCleanupThreshold = 1000;
virtual bool IsValidAsmJsBufferLength(uint length, bool forceCheck = false) { return false; }
virtual bool IsArrayBuffer() override { return true; }
virtual bool IsSharedArrayBuffer() override { return false; }
virtual ArrayBuffer * GetAsArrayBuffer() override { return ArrayBuffer::FromVar(this); }
static uint32 ToIndex(Var value, int32 errorCode, ScriptContext *scriptContext, uint32 MaxAllowedLength, bool checkSameValueZero = true);
virtual ArrayBuffer * TransferInternal(DECLSPEC_GUARD_OVERFLOW uint32 newBufferLength) = 0;
protected:
typedef void __cdecl FreeFn(void* ptr);
virtual ArrayBufferDetachedStateBase* CreateDetachedState(BYTE* buffer, DECLSPEC_GUARD_OVERFLOW uint32 bufferLength) = 0;
static uint32 GetIndexFromVar(Js::Var arg, uint32 length, ScriptContext* scriptContext);
//In most cases, the ArrayBuffer will only have one parent
Field(RecyclerWeakReference<ArrayBufferParent>*) primaryParent;
struct OtherParents :public SList<RecyclerWeakReference<ArrayBufferParent>*, Recycler>
{
OtherParents(Recycler* recycler)
:SList<RecyclerWeakReference<ArrayBufferParent>*, Recycler>(recycler), increasedCount(0)
{
}
Field(uint) increasedCount;
};
Field(OtherParents*) otherParents;
FieldNoBarrier(BYTE*) buffer; // Points to a heap allocated RGBA buffer, can be null
Field(uint32) bufferLength; // Number of bytes allocated
};
class ArrayBufferParent : public ArrayObject
{
friend ArrayBuffer;
friend ArrayBufferBase;
private:
Field(ArrayBufferBase*) arrayBuffer;
protected:
DEFINE_VTABLE_CTOR_ABSTRACT(ArrayBufferParent, ArrayObject);
ArrayBufferParent(DynamicType * type, uint32 length, ArrayBufferBase* arrayBuffer)
: ArrayObject(type, /*initSlots*/true, length),
arrayBuffer(arrayBuffer)
{
arrayBuffer->AddParent(this);
}
public:
ArrayBufferBase* GetArrayBuffer() const
{
return this->arrayBuffer;
}
#if ENABLE_TTD
public:
virtual void MarkVisitKindSpecificPtrs(TTD::SnapshotExtractor* extractor) override;
virtual void ProcessCorePaths() override;
#endif
};
// Normally we use malloc/free; for ArrayBuffer created from projection we need to use different allocator.
class JavascriptArrayBuffer : public ArrayBuffer
{
protected:
DEFINE_VTABLE_CTOR(JavascriptArrayBuffer, ArrayBuffer);
DEFINE_MARSHAL_OBJECT_TO_SCRIPT_CONTEXT(JavascriptArrayBuffer);
public:
static JavascriptArrayBuffer* Create(DECLSPEC_GUARD_OVERFLOW uint32 length, DynamicType * type);
static JavascriptArrayBuffer* Create(byte* buffer, DECLSPEC_GUARD_OVERFLOW uint32 length, DynamicType * type);
virtual void Dispose(bool isShutdown) override;
virtual void Finalize(bool isShutdown) override;
static bool IsValidAsmJsBufferLengthAlgo(uint length, bool forceCheck);
virtual bool IsValidAsmJsBufferLength(uint length, bool forceCheck = false) override;
virtual bool IsValidVirtualBufferLength(uint length) const override;
virtual ArrayBuffer * TransferInternal(DECLSPEC_GUARD_OVERFLOW uint32 newBufferLength) override;
template<typename Func>
void ReportDifferentialAllocation(uint32 newBufferLength, Func reportFailureFn);
void ReportDifferentialAllocation(uint32 newBufferLength);
protected:
JavascriptArrayBuffer(DynamicType * type);
virtual ArrayBufferDetachedStateBase* CreateDetachedState(BYTE* buffer, DECLSPEC_GUARD_OVERFLOW uint32 bufferLength) override;
template<typename Allocator>
JavascriptArrayBuffer(uint32 length, DynamicType * type, Allocator allocator): ArrayBuffer(length, type, allocator){}
JavascriptArrayBuffer(uint32 length, DynamicType * type);
JavascriptArrayBuffer(byte* buffer, uint32 length, DynamicType * type);
#if ENABLE_TTD
public:
virtual TTD::NSSnapObjects::SnapObjectType GetSnapTag_TTD() const override;
virtual void ExtractSnapObjectDataInto(TTD::NSSnapObjects::SnapObject* objData, TTD::SlabAllocator& alloc) override;
#endif
};
class WebAssemblyArrayBuffer : public JavascriptArrayBuffer
{
template<typename Allocator>
WebAssemblyArrayBuffer(uint32 length, DynamicType * type, Allocator allocator);
WebAssemblyArrayBuffer(uint32 length, DynamicType * type);
WebAssemblyArrayBuffer(byte* buffer, uint32 length, DynamicType * type);
protected:
DEFINE_VTABLE_CTOR(WebAssemblyArrayBuffer, JavascriptArrayBuffer);
DEFINE_MARSHAL_OBJECT_TO_SCRIPT_CONTEXT(WebAssemblyArrayBuffer);
public:
static WebAssemblyArrayBuffer* Create(byte* buffer, DECLSPEC_GUARD_OVERFLOW uint32 length, DynamicType * type);
WebAssemblyArrayBuffer* GrowMemory(DECLSPEC_GUARD_OVERFLOW uint32 newBufferLength);
virtual bool IsValidVirtualBufferLength(uint length) const override;
virtual ArrayBuffer * TransferInternal(DECLSPEC_GUARD_OVERFLOW uint32 newBufferLength) override;
virtual bool IsWebAssemblyArrayBuffer() override { return true; }
};
// the memory must be allocated via CoTaskMemAlloc.
class ProjectionArrayBuffer : public ArrayBuffer
{
protected:
DEFINE_VTABLE_CTOR(ProjectionArrayBuffer, ArrayBuffer);
DEFINE_MARSHAL_OBJECT_TO_SCRIPT_CONTEXT(ProjectionArrayBuffer);
typedef void __stdcall FreeFn(LPVOID ptr);
virtual ArrayBufferDetachedStateBase* CreateDetachedState(BYTE* buffer, DECLSPEC_GUARD_OVERFLOW uint32 bufferLength) override
{
return HeapNew(ArrayBufferDetachedState<FreeFn>, buffer, bufferLength, CoTaskMemFree, ArrayBufferAllocationType::CoTask);
}
public:
// Create constructor. script engine creates a buffer allocated via CoTaskMemAlloc.
static ProjectionArrayBuffer* Create(DECLSPEC_GUARD_OVERFLOW uint32 length, DynamicType * type);
// take over ownership. a CoTaskMemAlloc'ed buffer passed in via projection.
static ProjectionArrayBuffer* Create(byte* buffer, DECLSPEC_GUARD_OVERFLOW uint32 length, DynamicType * type);
virtual void Dispose(bool isShutdown) override;
virtual void Finalize(bool isShutdown) override {};
virtual ArrayBuffer * TransferInternal(DECLSPEC_GUARD_OVERFLOW uint32 newBufferLength) override;
private:
ProjectionArrayBuffer(uint32 length, DynamicType * type);
ProjectionArrayBuffer(byte* buffer, uint32 length, DynamicType * type);
};
// non-owning ArrayBuffer used for wrapping external data
class ExternalArrayBuffer : public ArrayBuffer
{
protected:
DEFINE_VTABLE_CTOR(ExternalArrayBuffer, ArrayBuffer);
DEFINE_MARSHAL_OBJECT_TO_SCRIPT_CONTEXT(ExternalArrayBuffer);
public:
ExternalArrayBuffer(byte *buffer, DECLSPEC_GUARD_OVERFLOW uint32 length, DynamicType *type);
virtual ArrayBuffer * TransferInternal(DECLSPEC_GUARD_OVERFLOW uint32 newBufferLength) override { Assert(UNREACHED); Throw::InternalError(); };
protected:
virtual ArrayBufferDetachedStateBase* CreateDetachedState(BYTE* buffer, DECLSPEC_GUARD_OVERFLOW uint32 bufferLength) override { Assert(UNREACHED); Throw::InternalError(); };
#if ENABLE_TTD
public:
virtual TTD::NSSnapObjects::SnapObjectType GetSnapTag_TTD() const override;
virtual void ExtractSnapObjectDataInto(TTD::NSSnapObjects::SnapObject* objData, TTD::SlabAllocator& alloc) override;
#endif
};
}