Skip to content

Commit e6d887a

Browse files
neildharfacebook-github-bot
authored andcommitted
Add external ArrayBuffers to JSI (#793)
Summary: Pull Request resolved: #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
1 parent 36ed60d commit e6d887a

File tree

7 files changed

+121
-0
lines changed

7 files changed

+121
-0
lines changed

API/hermes/TracingRuntime.cpp

+5
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,11 @@ jsi::Array TracingRuntime::createArray(size_t length) {
570570
return arr;
571571
}
572572

573+
jsi::ArrayBuffer TracingRuntime::createArrayBuffer(
574+
std::shared_ptr<jsi::MutableBuffer> buffer) {
575+
throw std::logic_error("Cannot create external ArrayBuffers in trace mode.");
576+
}
577+
573578
size_t TracingRuntime::size(const jsi::Array &arr) {
574579
// Array size inquiries read from the length property, which is
575580
// non-configurable and thus cannot have side effects.

API/hermes/TracingRuntime.h

+2
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ class TracingRuntime : public jsi::RuntimeDecorator<jsi::Runtime> {
9090
jsi::Value lockWeakObject(jsi::WeakObject &wo) override;
9191

9292
jsi::Array createArray(size_t length) override;
93+
jsi::ArrayBuffer createArrayBuffer(
94+
std::shared_ptr<jsi::MutableBuffer> buffer) override;
9395

9496
size_t size(const jsi::Array &arr) override;
9597
size_t size(const jsi::ArrayBuffer &buf) override;

API/hermes/hermes.cpp

+20
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,8 @@ class HermesRuntimeImpl final : public HermesRuntime,
737737
jsi::Value lockWeakObject(jsi::WeakObject &) override;
738738

739739
jsi::Array createArray(size_t length) override;
740+
jsi::ArrayBuffer createArrayBuffer(
741+
std::shared_ptr<jsi::MutableBuffer> buffer) override;
740742
size_t size(const jsi::Array &) override;
741743
size_t size(const jsi::ArrayBuffer &) override;
742744
uint8_t *data(const jsi::ArrayBuffer &) override;
@@ -1987,6 +1989,24 @@ jsi::Array HermesRuntimeImpl::createArray(size_t length) {
19871989
return add<jsi::Object>(result->getHermesValue()).getArray(*this);
19881990
}
19891991

1992+
jsi::ArrayBuffer HermesRuntimeImpl::createArrayBuffer(
1993+
std::shared_ptr<jsi::MutableBuffer> buffer) {
1994+
vm::GCScope gcScope(runtime_);
1995+
auto buf = runtime_.makeHandle(vm::JSArrayBuffer::create(
1996+
runtime_,
1997+
vm::Handle<vm::JSObject>::vmcast(&runtime_.arrayBufferPrototype)));
1998+
auto size = buffer->size();
1999+
auto *data = buffer->data();
2000+
auto *ctx = new std::shared_ptr<jsi::MutableBuffer>(std::move(buffer));
2001+
auto finalize = [](void *ctx) {
2002+
delete static_cast<std::shared_ptr<jsi::MutableBuffer> *>(ctx);
2003+
};
2004+
auto res = vm::JSArrayBuffer::setExternalDataBlock(
2005+
runtime_, buf, data, size, ctx, finalize);
2006+
checkStatus(res);
2007+
return add<jsi::Object>(buf.getHermesValue()).getArrayBuffer(*this);
2008+
}
2009+
19902010
size_t HermesRuntimeImpl::size(const jsi::Array &arr) {
19912011
vm::GCScope gcScope(runtime_);
19922012
return getLength(arrayHandle(arr));

API/jsi/jsi/decorator.h

+8
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,10 @@ class RuntimeDecorator : public Base, private jsi::Instrumentation {
302302
Array createArray(size_t length) override {
303303
return plain_.createArray(length);
304304
};
305+
ArrayBuffer createArrayBuffer(
306+
std::shared_ptr<MutableBuffer> buffer) override {
307+
return plain_.createArrayBuffer(std::move(buffer));
308+
};
305309
size_t size(const Array& a) override {
306310
return plain_.size(a);
307311
};
@@ -701,6 +705,10 @@ class WithRuntimeDecorator : public RuntimeDecorator<Plain, Base> {
701705
Around around{with_};
702706
return RD::createArray(length);
703707
};
708+
ArrayBuffer createArrayBuffer(
709+
std::shared_ptr<MutableBuffer> buffer) override {
710+
return RD::createArrayBuffer(std::move(buffer));
711+
};
704712
size_t size(const Array& a) override {
705713
Around around{with_};
706714
return RD::size(a);

API/jsi/jsi/jsi.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ Value callGlobalFunction(Runtime& runtime, const char* name, const Value& arg) {
6666

6767
Buffer::~Buffer() = default;
6868

69+
MutableBuffer::~MutableBuffer() = default;
70+
6971
PreparedJavaScript::~PreparedJavaScript() = default;
7072

7173
Value HostObject::get(Runtime&, const PropNameID&) {

API/jsi/jsi/jsi.h

+21
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ class FBJSRuntime;
3131
namespace facebook {
3232
namespace jsi {
3333

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

59+
/// Base class for buffers of data that need to be passed to the runtime. The
60+
/// result of size() and data() must not change after construction. However, the
61+
/// region pointed to by data() may be modified by the user or the runtime. The
62+
/// user must ensure that access to the contents of the buffer is properly
63+
/// synchronised.
64+
class JSI_EXPORT MutableBuffer {
65+
public:
66+
virtual ~MutableBuffer();
67+
virtual size_t size() const = 0;
68+
virtual uint8_t* data() = 0;
69+
};
70+
5571
/// PreparedJavaScript is a base class representing JavaScript which is in a
5672
/// form optimized for execution, in a runtime-specific way. Construct one via
5773
/// jsi::Runtime::prepareJavaScript().
@@ -336,6 +352,8 @@ class JSI_EXPORT Runtime {
336352
virtual Value lockWeakObject(WeakObject&) = 0;
337353

338354
virtual Array createArray(size_t length) = 0;
355+
virtual ArrayBuffer createArrayBuffer(
356+
std::shared_ptr<MutableBuffer> buffer) = 0;
339357
virtual size_t size(const Array&) = 0;
340358
virtual size_t size(const ArrayBuffer&) = 0;
341359
virtual uint8_t* data(const ArrayBuffer&) = 0;
@@ -915,6 +933,9 @@ class JSI_EXPORT ArrayBuffer : public Object {
915933
ArrayBuffer(ArrayBuffer&&) = default;
916934
ArrayBuffer& operator=(ArrayBuffer&&) = default;
917935

936+
ArrayBuffer(Runtime& runtime, std::shared_ptr<MutableBuffer> buffer)
937+
: ArrayBuffer(runtime.createArrayBuffer(std::move(buffer))) {}
938+
918939
/// \return the size of the ArrayBuffer, according to its byteLength property.
919940
/// (C++ naming convention)
920941
size_t size(Runtime& runtime) const {

unittests/API/APITest.cpp

+63
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <hermes/CompileJS.h>
1111
#include <hermes/Public/JSOutOfMemoryError.h>
1212
#include <hermes/hermes.h>
13+
#include <jsi/instrumentation.h>
1314

1415
#include <tuple>
1516

@@ -105,6 +106,68 @@ TEST_F(HermesRuntimeTest, ArrayBufferTest) {
105106
EXPECT_EQ(buffer[1], 5678);
106107
}
107108

109+
class HermesRuntimeTestMethodsTest : public HermesRuntimeTestBase {
110+
public:
111+
HermesRuntimeTestMethodsTest()
112+
: HermesRuntimeTestBase(::hermes::vm::RuntimeConfig::Builder()
113+
.withEnableHermesInternalTestMethods(true)
114+
.build()) {}
115+
};
116+
117+
TEST_F(HermesRuntimeTestMethodsTest, ExternalArrayBufferTest) {
118+
struct FixedBuffer : MutableBuffer {
119+
size_t size() const override {
120+
return sizeof(arr);
121+
}
122+
uint8_t *data() override {
123+
return reinterpret_cast<uint8_t *>(arr.data());
124+
}
125+
126+
std::array<uint32_t, 256> arr;
127+
};
128+
129+
{
130+
auto buf = std::make_shared<FixedBuffer>();
131+
for (uint32_t i = 0; i < buf->arr.size(); i++)
132+
buf->arr[i] = i;
133+
auto arrayBuffer = ArrayBuffer(*rt, buf);
134+
auto square = eval(
135+
R"#(
136+
(function (buf) {
137+
var view = new Uint32Array(buf);
138+
for(var i = 0; i < view.length; i++) view[i] = view[i] * view[i];
139+
})
140+
)#");
141+
square.asObject(*rt).asFunction(*rt).call(*rt, arrayBuffer);
142+
for (uint32_t i = 0; i < 256; i++)
143+
EXPECT_EQ(buf->arr[i], i * i);
144+
}
145+
146+
{
147+
auto buf = std::make_shared<FixedBuffer>();
148+
std::weak_ptr<FixedBuffer> weakBuf(buf);
149+
auto arrayBuffer = ArrayBuffer(*rt, std::move(buf));
150+
auto detach = eval(
151+
R"#(
152+
(function (buf) {
153+
var view = new Uint32Array(buf);
154+
HermesInternal.detachArrayBuffer(buf);
155+
view[0] = 5;
156+
})
157+
)#");
158+
try {
159+
detach.asObject(*rt).asFunction(*rt).call(*rt, arrayBuffer);
160+
FAIL() << "Expected JSIException";
161+
} catch (const JSError &ex) {
162+
EXPECT_TRUE(
163+
strstr(ex.what(), "Cannot set a value into a detached ArrayBuffer") !=
164+
nullptr);
165+
}
166+
rt->instrumentation().collectGarbage("");
167+
EXPECT_TRUE(weakBuf.expired());
168+
}
169+
}
170+
108171
TEST_F(HermesRuntimeTest, BytecodeTest) {
109172
const uint8_t shortBytes[] = {1, 2, 3};
110173
EXPECT_FALSE(HermesRuntime::isHermesBytecode(shortBytes, 0));

0 commit comments

Comments
 (0)