lib/VM/JSArrayBuffer.cpp (166 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "hermes/VM/JSArrayBuffer.h"
#include "hermes/VM/BuildMetadata.h"
#include "hermes/VM/Runtime-inline.h"
namespace hermes {
namespace vm {
//===----------------------------------------------------------------------===//
// class JSArrayBuffer
const ObjectVTable JSArrayBuffer::vt{
VTable(
CellKind::JSArrayBufferKind,
cellSize<JSArrayBuffer>(),
_finalizeImpl,
nullptr,
_mallocSizeImpl,
nullptr,
VTable::HeapSnapshotMetadata{
HeapSnapshot::NodeType::Object,
nullptr,
_snapshotAddEdgesImpl,
_snapshotAddNodesImpl,
nullptr}),
_getOwnIndexedRangeImpl,
_haveOwnIndexedImpl,
_getOwnIndexedPropertyFlagsImpl,
_getOwnIndexedImpl,
_setOwnIndexedImpl,
_deleteOwnIndexedImpl,
_checkAllOwnIndexedImpl,
};
void JSArrayBufferBuildMeta(const GCCell *cell, Metadata::Builder &mb) {
mb.addJSObjectOverlapSlots(JSObject::numOverlapSlots<JSArrayBuffer>());
JSObjectBuildMeta(cell, mb);
mb.setVTable(&JSArrayBuffer::vt);
}
PseudoHandle<JSArrayBuffer> JSArrayBuffer::create(
Runtime &runtime,
Handle<JSObject> parentHandle) {
auto *cell = runtime.makeAFixed<JSArrayBuffer, HasFinalizer::Yes>(
runtime,
parentHandle,
runtime.getHiddenClassForPrototype(
*parentHandle, numOverlapSlots<JSArrayBuffer>()));
return JSObjectInit::initToPseudoHandle(runtime, cell);
}
CallResult<Handle<JSArrayBuffer>> JSArrayBuffer::clone(
Runtime &runtime,
Handle<JSArrayBuffer> src,
size_type srcOffset,
size_type srcSize) {
if (!src->attached()) {
return runtime.raiseTypeError("Cannot clone from a detached buffer");
}
auto arr = runtime.makeHandle(JSArrayBuffer::create(
runtime, Handle<JSObject>::vmcast(&runtime.arrayBufferPrototype)));
// Don't need to zero out the data since we'll be copying into it immediately.
if (arr->createDataBlock(runtime, srcSize, false) ==
ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
if (srcSize != 0) {
JSArrayBuffer::copyDataBlockBytes(*arr, 0, *src, srcOffset, srcSize);
}
return arr;
}
void JSArrayBuffer::copyDataBlockBytes(
JSArrayBuffer *dst,
size_type dstIndex,
JSArrayBuffer *src,
size_type srcIndex,
size_type count) {
assert(dst && src && "Must be copied between existing objects");
if (count == 0) {
// Don't do anything if there was no copy requested.
return;
}
assert(
dst->getDataBlock() != src->getDataBlock() &&
"Cannot copy into the same block, must be different blocks");
assert(
srcIndex + count <= src->size() &&
"Cannot copy more data out of a block than what exists");
assert(
dstIndex + count <= dst->size() &&
"Cannot copy more data into a block than it has space for");
// Copy from the other buffer.
memcpy(dst->getDataBlock() + dstIndex, src->getDataBlock() + srcIndex, count);
}
JSArrayBuffer::JSArrayBuffer(
Runtime &runtime,
Handle<JSObject> parent,
Handle<HiddenClass> clazz)
: JSObject(runtime, *parent, *clazz),
data_(nullptr),
size_(0),
attached_(false) {}
void JSArrayBuffer::_finalizeImpl(GCCell *cell, GC *gc) {
auto *self = vmcast<JSArrayBuffer>(cell);
// Need to untrack the native memory that may have been tracked by snapshots.
gc->getIDTracker().untrackNative(self->data_);
gc->debitExternalMemory(self, self->size_);
free(self->data_);
self->~JSArrayBuffer();
}
size_t JSArrayBuffer::_mallocSizeImpl(GCCell *cell) {
const auto *buffer = vmcast<JSArrayBuffer>(cell);
return buffer->size_;
}
void JSArrayBuffer::_snapshotAddEdgesImpl(
GCCell *cell,
GC *gc,
HeapSnapshot &snap) {
auto *const self = vmcast<JSArrayBuffer>(cell);
if (!self->data_) {
return;
}
// While this is an internal edge, it is to a native node which is not
// automatically added by the metadata.
snap.addNamedEdge(
HeapSnapshot::EdgeType::Internal,
"backingStore",
gc->getNativeID(self->data_));
// The backing store just has numbers, so there's no edges to add here.
}
void JSArrayBuffer::_snapshotAddNodesImpl(
GCCell *cell,
GC *gc,
HeapSnapshot &snap) {
auto *const self = vmcast<JSArrayBuffer>(cell);
if (!self->data_) {
return;
}
// Add the native node before the JSArrayBuffer node.
snap.beginNode();
snap.endNode(
HeapSnapshot::NodeType::Native,
"JSArrayBufferData",
gc->getNativeID(self->data_),
self->size_,
0);
}
void JSArrayBuffer::detach(GC *gc) {
if (data_) {
gc->debitExternalMemory(this, size_);
free(data_);
data_ = nullptr;
size_ = 0;
} else {
assert(size_ == 0);
}
// Note that whether a buffer is attached is independent of whether
// it has allocated data.
attached_ = false;
}
ExecutionStatus
JSArrayBuffer::createDataBlock(Runtime &runtime, size_type size, bool zero) {
detach(&runtime.getHeap());
if (size == 0) {
// Even though there is no storage allocated, the spec requires an empty
// ArrayBuffer to still be considered as attached.
attached_ = true;
return ExecutionStatus::RETURNED;
}
// If an external allocation of this size would exceed the GC heap size,
// raise RangeError.
if (LLVM_UNLIKELY(!runtime.getHeap().canAllocExternalMemory(size))) {
return runtime.raiseRangeError(
"Cannot allocate a data block for the ArrayBuffer");
}
// Note that the result of calloc or malloc is immediately checked below, so
// we don't use the checked versions.
data_ = zero ? static_cast<uint8_t *>(calloc(sizeof(uint8_t), size))
: static_cast<uint8_t *>(malloc(sizeof(uint8_t) * size));
if (data_ == nullptr) {
// Failed to allocate.
return runtime.raiseRangeError(
"Cannot allocate a data block for the ArrayBuffer");
} else {
attached_ = true;
size_ = size;
runtime.getHeap().creditExternalMemory(this, size);
return ExecutionStatus::RETURNED;
}
}
} // namespace vm
} // namespace hermes