Skip to content

Commit

Permalink
Add external ArrayBuffers to JSI (facebook#793)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: facebook#793

Add a JSI API for constructing `ArrayBuffer`s from a user provided
`jsi::MutableBuffer`.

Changelog: [General][Added] Added ability to construct ArrayBuffers from existing memory buffers.

Reviewed By: jpporto

Differential Revision: D37744467

fbshipit-source-id: 9d9ece00d1dbde341846c45fa30c935b5fa81e9a
  • Loading branch information
neildhar authored and facebook-github-bot committed Aug 23, 2022
1 parent 36ed60d commit e6d887a
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 0 deletions.
5 changes: 5 additions & 0 deletions API/hermes/TracingRuntime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,11 @@ jsi::Array TracingRuntime::createArray(size_t length) {
return arr;
}

jsi::ArrayBuffer TracingRuntime::createArrayBuffer(
std::shared_ptr<jsi::MutableBuffer> buffer) {
throw std::logic_error("Cannot create external ArrayBuffers in trace mode.");
}

size_t TracingRuntime::size(const jsi::Array &arr) {
// Array size inquiries read from the length property, which is
// non-configurable and thus cannot have side effects.
Expand Down
2 changes: 2 additions & 0 deletions API/hermes/TracingRuntime.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ class TracingRuntime : public jsi::RuntimeDecorator<jsi::Runtime> {
jsi::Value lockWeakObject(jsi::WeakObject &wo) override;

jsi::Array createArray(size_t length) override;
jsi::ArrayBuffer createArrayBuffer(
std::shared_ptr<jsi::MutableBuffer> buffer) override;

size_t size(const jsi::Array &arr) override;
size_t size(const jsi::ArrayBuffer &buf) override;
Expand Down
20 changes: 20 additions & 0 deletions API/hermes/hermes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,8 @@ class HermesRuntimeImpl final : public HermesRuntime,
jsi::Value lockWeakObject(jsi::WeakObject &) override;

jsi::Array createArray(size_t length) override;
jsi::ArrayBuffer createArrayBuffer(
std::shared_ptr<jsi::MutableBuffer> buffer) override;
size_t size(const jsi::Array &) override;
size_t size(const jsi::ArrayBuffer &) override;
uint8_t *data(const jsi::ArrayBuffer &) override;
Expand Down Expand Up @@ -1987,6 +1989,24 @@ jsi::Array HermesRuntimeImpl::createArray(size_t length) {
return add<jsi::Object>(result->getHermesValue()).getArray(*this);
}

jsi::ArrayBuffer HermesRuntimeImpl::createArrayBuffer(
std::shared_ptr<jsi::MutableBuffer> buffer) {
vm::GCScope gcScope(runtime_);
auto buf = runtime_.makeHandle(vm::JSArrayBuffer::create(
runtime_,
vm::Handle<vm::JSObject>::vmcast(&runtime_.arrayBufferPrototype)));
auto size = buffer->size();
auto *data = buffer->data();
auto *ctx = new std::shared_ptr<jsi::MutableBuffer>(std::move(buffer));
auto finalize = [](void *ctx) {
delete static_cast<std::shared_ptr<jsi::MutableBuffer> *>(ctx);
};
auto res = vm::JSArrayBuffer::setExternalDataBlock(
runtime_, buf, data, size, ctx, finalize);
checkStatus(res);
return add<jsi::Object>(buf.getHermesValue()).getArrayBuffer(*this);
}

size_t HermesRuntimeImpl::size(const jsi::Array &arr) {
vm::GCScope gcScope(runtime_);
return getLength(arrayHandle(arr));
Expand Down
8 changes: 8 additions & 0 deletions API/jsi/jsi/decorator.h
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,10 @@ class RuntimeDecorator : public Base, private jsi::Instrumentation {
Array createArray(size_t length) override {
return plain_.createArray(length);
};
ArrayBuffer createArrayBuffer(
std::shared_ptr<MutableBuffer> buffer) override {
return plain_.createArrayBuffer(std::move(buffer));
};
size_t size(const Array& a) override {
return plain_.size(a);
};
Expand Down Expand Up @@ -701,6 +705,10 @@ class WithRuntimeDecorator : public RuntimeDecorator<Plain, Base> {
Around around{with_};
return RD::createArray(length);
};
ArrayBuffer createArrayBuffer(
std::shared_ptr<MutableBuffer> buffer) override {
return RD::createArrayBuffer(std::move(buffer));
};
size_t size(const Array& a) override {
Around around{with_};
return RD::size(a);
Expand Down
2 changes: 2 additions & 0 deletions API/jsi/jsi/jsi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ Value callGlobalFunction(Runtime& runtime, const char* name, const Value& arg) {

Buffer::~Buffer() = default;

MutableBuffer::~MutableBuffer() = default;

PreparedJavaScript::~PreparedJavaScript() = default;

Value HostObject::get(Runtime&, const PropNameID&) {
Expand Down
21 changes: 21 additions & 0 deletions API/jsi/jsi/jsi.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ class FBJSRuntime;
namespace facebook {
namespace jsi {

/// Base class for buffers of data or bytecode that need to be passed to the
/// runtime. The buffer is expected to be fully immutable, so the result of
/// size(), data(), and the contents of the pointer returned by data() must not
/// change after construction.
class JSI_EXPORT Buffer {
public:
virtual ~Buffer();
Expand All @@ -52,6 +56,18 @@ class JSI_EXPORT StringBuffer : public Buffer {
std::string s_;
};

/// Base class for buffers of data that need to be passed to the runtime. The
/// result of size() and data() must not change after construction. However, the
/// region pointed to by data() may be modified by the user or the runtime. The
/// user must ensure that access to the contents of the buffer is properly
/// synchronised.
class JSI_EXPORT MutableBuffer {
public:
virtual ~MutableBuffer();
virtual size_t size() const = 0;
virtual uint8_t* data() = 0;
};

/// PreparedJavaScript is a base class representing JavaScript which is in a
/// form optimized for execution, in a runtime-specific way. Construct one via
/// jsi::Runtime::prepareJavaScript().
Expand Down Expand Up @@ -336,6 +352,8 @@ class JSI_EXPORT Runtime {
virtual Value lockWeakObject(WeakObject&) = 0;

virtual Array createArray(size_t length) = 0;
virtual ArrayBuffer createArrayBuffer(
std::shared_ptr<MutableBuffer> buffer) = 0;
virtual size_t size(const Array&) = 0;
virtual size_t size(const ArrayBuffer&) = 0;
virtual uint8_t* data(const ArrayBuffer&) = 0;
Expand Down Expand Up @@ -915,6 +933,9 @@ class JSI_EXPORT ArrayBuffer : public Object {
ArrayBuffer(ArrayBuffer&&) = default;
ArrayBuffer& operator=(ArrayBuffer&&) = default;

ArrayBuffer(Runtime& runtime, std::shared_ptr<MutableBuffer> buffer)
: ArrayBuffer(runtime.createArrayBuffer(std::move(buffer))) {}

/// \return the size of the ArrayBuffer, according to its byteLength property.
/// (C++ naming convention)
size_t size(Runtime& runtime) const {
Expand Down
63 changes: 63 additions & 0 deletions unittests/API/APITest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <hermes/CompileJS.h>
#include <hermes/Public/JSOutOfMemoryError.h>
#include <hermes/hermes.h>
#include <jsi/instrumentation.h>

#include <tuple>

Expand Down Expand Up @@ -105,6 +106,68 @@ TEST_F(HermesRuntimeTest, ArrayBufferTest) {
EXPECT_EQ(buffer[1], 5678);
}

class HermesRuntimeTestMethodsTest : public HermesRuntimeTestBase {
public:
HermesRuntimeTestMethodsTest()
: HermesRuntimeTestBase(::hermes::vm::RuntimeConfig::Builder()
.withEnableHermesInternalTestMethods(true)
.build()) {}
};

TEST_F(HermesRuntimeTestMethodsTest, ExternalArrayBufferTest) {
struct FixedBuffer : MutableBuffer {
size_t size() const override {
return sizeof(arr);
}
uint8_t *data() override {
return reinterpret_cast<uint8_t *>(arr.data());
}

std::array<uint32_t, 256> arr;
};

{
auto buf = std::make_shared<FixedBuffer>();
for (uint32_t i = 0; i < buf->arr.size(); i++)
buf->arr[i] = i;
auto arrayBuffer = ArrayBuffer(*rt, buf);
auto square = eval(
R"#(
(function (buf) {
var view = new Uint32Array(buf);
for(var i = 0; i < view.length; i++) view[i] = view[i] * view[i];
})
)#");
square.asObject(*rt).asFunction(*rt).call(*rt, arrayBuffer);
for (uint32_t i = 0; i < 256; i++)
EXPECT_EQ(buf->arr[i], i * i);
}

{
auto buf = std::make_shared<FixedBuffer>();
std::weak_ptr<FixedBuffer> weakBuf(buf);
auto arrayBuffer = ArrayBuffer(*rt, std::move(buf));
auto detach = eval(
R"#(
(function (buf) {
var view = new Uint32Array(buf);
HermesInternal.detachArrayBuffer(buf);
view[0] = 5;
})
)#");
try {
detach.asObject(*rt).asFunction(*rt).call(*rt, arrayBuffer);
FAIL() << "Expected JSIException";
} catch (const JSError &ex) {
EXPECT_TRUE(
strstr(ex.what(), "Cannot set a value into a detached ArrayBuffer") !=
nullptr);
}
rt->instrumentation().collectGarbage("");
EXPECT_TRUE(weakBuf.expired());
}
}

TEST_F(HermesRuntimeTest, BytecodeTest) {
const uint8_t shortBytes[] = {1, 2, 3};
EXPECT_FALSE(HermesRuntime::isHermesBytecode(shortBytes, 0));
Expand Down

0 comments on commit e6d887a

Please sign in to comment.