summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBert Belder <bertbelder@gmail.com>2018-07-09 03:35:34 +0200
committerBert Belder <bertbelder@gmail.com>2018-07-12 21:26:38 +0200
commit24b0e91d8096f84a114f662e3eb42c83d0d3878d (patch)
treeacd9ae306618276020e75450c064ead463e64387
parentbbcd4c8dd33121868d82123a3d36e3df282af45f (diff)
Move buffers between V8 and native
* send()/recv() now operate on TypedArrays rather than ArrayBuffers. * Remove a copy (through ArrayBuffer.slice()) from the send path. * Remove a copy (through v8::ArrayBuffer::New()) from the return path. * After moving a buffer from JS to native, the ArrayBuffer object and it's views are made inaccessible ('neutered'). * `struct deno_buf` now holds two [ptr, length] tuples, one for the actual memory allocation, and one for the logical data contained therein. This is necessary because flatbuffers fills it's buffer bottom-up, so the serialized blob doesn't start at beginning of the buffer, but somewhere in the middle.
-rw-r--r--BUILD.gn6
-rw-r--r--js/deno.d.ts4
-rw-r--r--js/main.ts13
-rw-r--r--js/mock_runtime.js81
-rw-r--r--src/binding.cc55
-rw-r--r--src/deno.h13
-rw-r--r--src/flatbuffer_builder.cc78
-rw-r--r--src/flatbuffer_builder.h64
-rw-r--r--src/flatbuffer_builder_test.cc78
-rw-r--r--src/main.cc12
-rw-r--r--src/main.rs13
-rw-r--r--src/mock_runtime_test.cc91
12 files changed, 438 insertions, 70 deletions
diff --git a/BUILD.gn b/BUILD.gn
index 163df2607..488a98300 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -76,6 +76,7 @@ executable("mock_runtime_test") {
testonly = true
sources = [
"src/file_util_test.cc",
+ "src/flatbuffer_builder_test.cc",
"src/from_snapshot.cc",
"src/mock_runtime_test.cc",
]
@@ -106,11 +107,16 @@ v8_source_set("deno_bindings") {
"src/deno.h",
"src/file_util.cc",
"src/file_util.h",
+ "src/flatbuffer_builder.cc",
+ "src/flatbuffer_builder.h",
"src/internal.h",
]
deps = [
"third_party/v8:v8_monolith",
]
+ public_deps = [
+ "build_extra/flatbuffers:flatbuffers",
+ ]
configs = [ ":deno_config" ]
}
diff --git a/js/deno.d.ts b/js/deno.d.ts
index f554135bc..a54dab851 100644
--- a/js/deno.d.ts
+++ b/js/deno.d.ts
@@ -1,10 +1,10 @@
// Copyright 2018 Ryan Dahl <ry@tinyclouds.org>
// All rights reserved. MIT License.
-type MessageCallback = (msg: ArrayBuffer) => void;
+type MessageCallback = (msg: Uint8Array) => void;
interface Deno {
recv(cb: MessageCallback): void;
- send(msg: ArrayBuffer): null | ArrayBuffer;
+ send(msg: ArrayBufferView): null | Uint8Array;
print(x: string): void;
}
diff --git a/js/main.ts b/js/main.ts
index 027baa3cf..379b69c97 100644
--- a/js/main.ts
+++ b/js/main.ts
@@ -19,7 +19,7 @@ function assignCmdId(): number {
return cmdId;
}
-function startMsg(cmdId: number): ArrayBuffer {
+function startMsg(cmdId: number): Uint8Array {
const builder = new flatbuffers.Builder();
const msg = fbs.Start.createStart(builder, 0);
fbs.Base.startBase(builder);
@@ -27,7 +27,7 @@ function startMsg(cmdId: number): ArrayBuffer {
fbs.Base.addMsg(builder, msg);
fbs.Base.addMsgType(builder, fbs.Any.Start);
builder.finish(fbs.Base.endBase(builder));
- return typedArrayToArrayBuffer(builder.asUint8Array());
+ return builder.asUint8Array();
}
window["denoMain"] = () => {
@@ -47,7 +47,7 @@ window["denoMain"] = () => {
}
// Deserialize res into startResMsg.
- const bb = new flatbuffers.ByteBuffer(new Uint8Array(res));
+ const bb = new flatbuffers.ByteBuffer(res);
const base = fbs.Base.getRootAsBase(bb);
assert(base.cmdId() === cmdId);
assert(fbs.Any.StartRes === base.msgType());
@@ -69,10 +69,3 @@ window["denoMain"] = () => {
mod.compileAndRun();
*/
};
-
-function typedArrayToArrayBuffer(ta: Uint8Array): ArrayBuffer {
- return ta.buffer.slice(
- ta.byteOffset,
- ta.byteOffset + ta.byteLength
- ) as ArrayBuffer;
-}
diff --git a/js/mock_runtime.js b/js/mock_runtime.js
index 66a34657e..934fbeab4 100644
--- a/js/mock_runtime.js
+++ b/js/mock_runtime.js
@@ -7,10 +7,6 @@ function assert(cond) {
if (!cond) throw Error("mock_runtime.js assert failed");
}
-global.typedArrayToArrayBuffer = ta => {
- return ta.buffer.slice(ta.byteOffset, ta.byteOffset + ta.byteLength);
-};
-
global.CanCallFunction = () => {
deno.print("Hello world from foo");
return "foo";
@@ -41,22 +37,20 @@ global.SendByteLength = () => {
};
global.RecvReturnEmpty = () => {
- const ui8 = new Uint8Array("abc".split("").map(c => c.charCodeAt(0)));
- const ab = typedArrayToArrayBuffer(ui8);
- let r = deno.send(ab);
- assert(r == null);
- r = deno.send(ab);
- assert(r == null);
+ const m1 = new Uint8Array("abc".split("").map(c => c.charCodeAt(0)));
+ const m2 = m1.slice();
+ const r1 = deno.send(m1);
+ assert(r1 == null);
+ const r2 = deno.send(m2);
+ assert(r2 == null);
};
global.RecvReturnBar = () => {
- const ui8 = new Uint8Array("abc".split("").map(c => c.charCodeAt(0)));
- const ab = typedArrayToArrayBuffer(ui8);
- const r = deno.send(ab);
- assert(r instanceof ArrayBuffer);
+ const m = new Uint8Array("abc".split("").map(c => c.charCodeAt(0)));
+ const r = deno.send(m);
+ assert(r instanceof Uint8Array);
assert(r.byteLength === 3);
- const rui8 = new Uint8Array(r);
- const rstr = String.fromCharCode(...rui8);
+ const rstr = String.fromCharCode(...r);
assert(rstr === "bar");
};
@@ -67,6 +61,59 @@ global.DoubleRecvFails = () => {
deno.recv((channel, msg) => assert(false));
};
+global.SendRecvSlice = () => {
+ const abLen = 1024;
+ let buf = new Uint8Array(abLen);
+ for (let i = 0; i < 5; i++) {
+ // Set first and last byte, for verification by the native side.
+ buf[0] = 100 + i;
+ buf[buf.length - 1] = 100 - i;
+ // On the native side, the slice is shortened by 19 bytes.
+ buf = deno.send(buf);
+ assert(buf.byteOffset === i * 11);
+ assert(buf.byteLength === abLen - i * 30 - 19);
+ assert(buf.buffer.byteLength == abLen);
+ // Look for values written by the backend.
+ assert(buf[0] === 200 + i);
+ assert(buf[buf.length - 1] === 200 - i);
+ // On the JS side, the start of the slice is moved up by 11 bytes.
+ buf = buf.subarray(11);
+ assert(buf.byteOffset === (i + 1) * 11);
+ assert(buf.byteLength === abLen - (i + 1) * 30);
+ }
+};
+
+global.JSSendArrayBufferViewTypes = () => {
+ // Test that ArrayBufferView slices are transferred correctly.
+ // Send Uint8Array.
+ const ab1 = new ArrayBuffer(4321);
+ const u8 = new Uint8Array(ab1, 2468, 1000);
+ u8[0] = 1;
+ deno.send(u8);
+ // Send Uint32Array.
+ const ab2 = new ArrayBuffer(4321);
+ const u32 = new Uint32Array(ab2, 2468, 1000 / Uint32Array.BYTES_PER_ELEMENT);
+ u32[0] = 0x02020202;
+ deno.send(u32);
+ // Send DataView.
+ const ab3 = new ArrayBuffer(4321);
+ const dv = new DataView(ab3, 2468, 1000);
+ dv.setUint8(0, 3);
+ deno.send(dv);
+};
+
+global.JSSendNeutersBuffer = () => {
+ // Buffer should be neutered after transferring it to the native side.
+ const u8 = new Uint8Array([42]);
+ assert(u8.byteLength === 1);
+ assert(u8.buffer.byteLength === 1);
+ assert(u8[0] === 42);
+ const r = deno.send(u8);
+ assert(u8.byteLength === 0);
+ assert(u8.buffer.byteLength === 0);
+ assert(u8[0] === undefined);
+};
+
// The following join has caused SnapshotBug to segfault when using kKeep.
[].join("");
@@ -82,7 +129,7 @@ global.ErrorHandling = () => {
assert(line === 3);
assert(col === 1);
assert(error instanceof Error);
- deno.send(typedArrayToArrayBuffer(new Uint8Array([42])));
+ deno.send(new Uint8Array([42]));
};
eval("\n\n notdefined()\n//# sourceURL=helloworld.js");
};
diff --git a/src/binding.cc b/src/binding.cc
index 50b029b90..2c4b6f722 100644
--- a/src/binding.cc
+++ b/src/binding.cc
@@ -139,6 +139,34 @@ void Print(const v8::FunctionCallbackInfo<v8::Value>& args) {
fflush(stdout);
}
+static v8::Local<v8::Uint8Array> ImportBuf(v8::Isolate* isolate, deno_buf buf) {
+ auto ab = v8::ArrayBuffer::New(
+ isolate, reinterpret_cast<void*>(buf.alloc_ptr), buf.alloc_len,
+ v8::ArrayBufferCreationMode::kInternalized);
+ auto view =
+ v8::Uint8Array::New(ab, buf.data_ptr - buf.alloc_ptr, buf.data_len);
+ return view;
+}
+
+static deno_buf ExportBuf(v8::Isolate* isolate,
+ v8::Local<v8::ArrayBufferView> view) {
+ auto ab = view->Buffer();
+ auto contents = ab->Externalize();
+
+ deno_buf buf;
+ buf.alloc_ptr = reinterpret_cast<uint8_t*>(contents.Data());
+ buf.alloc_len = contents.ByteLength();
+ buf.data_ptr = buf.alloc_ptr + view->ByteOffset();
+ buf.data_len = view->ByteLength();
+
+ // Prevent JS from modifying buffer contents after exporting.
+ ab->Neuter();
+
+ return buf;
+}
+
+static void FreeBuf(deno_buf buf) { free(buf.alloc_ptr); }
+
// Sets the recv callback.
void Recv(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
@@ -169,20 +197,21 @@ void Send(const v8::FunctionCallbackInfo<v8::Value>& args) {
CHECK_EQ(args.Length(), 1);
v8::Local<v8::Value> ab_v = args[0];
- CHECK(ab_v->IsArrayBuffer());
- auto ab = v8::Local<v8::ArrayBuffer>::Cast(ab_v);
- auto contents = ab->GetContents();
-
- // data is only a valid pointer until the end of this call.
- const char* data =
- const_cast<const char*>(reinterpret_cast<char*>(contents.Data()));
- deno_buf buf{data, contents.ByteLength()};
+ CHECK(ab_v->IsArrayBufferView());
+ auto buf = ExportBuf(isolate, v8::Local<v8::ArrayBufferView>::Cast(ab_v));
DCHECK_EQ(d->currentArgs, nullptr);
d->currentArgs = &args;
d->cb(d, buf);
+ // Buffer is only valid until the end of the callback.
+ // TODO(piscisaureus):
+ // It's possible that data in the buffer is needed after the callback
+ // returns, e.g. when the handler offloads work to a thread pool, therefore
+ // make the callback responsible for releasing the buffer.
+ FreeBuf(buf);
+
d->currentArgs = nullptr;
}
@@ -303,12 +332,8 @@ int deno_send(Deno* d, deno_buf buf) {
return 0;
}
- // TODO(ry) support zero-copy.
- auto ab = v8::ArrayBuffer::New(d->isolate, buf.len);
- memcpy(ab->GetContents().Data(), buf.data, buf.len);
-
v8::Local<v8::Value> args[1];
- args[0] = ab;
+ args[0] = deno::ImportBuf(d->isolate, buf);
recv->Call(context->Global(), 1, args);
if (try_catch.HasCaught()) {
@@ -320,9 +345,7 @@ int deno_send(Deno* d, deno_buf buf) {
}
void deno_set_response(Deno* d, deno_buf buf) {
- // TODO(ry) Support zero-copy.
- auto ab = v8::ArrayBuffer::New(d->isolate, buf.len);
- memcpy(ab->GetContents().Data(), buf.data, buf.len);
+ auto ab = deno::ImportBuf(d->isolate, buf);
d->currentArgs->GetReturnValue().Set(ab);
}
diff --git a/src/deno.h b/src/deno.h
index fd2b4766a..ab214ab03 100644
--- a/src/deno.h
+++ b/src/deno.h
@@ -3,6 +3,7 @@
#ifndef DENO_H_
#define DENO_H_
#include <stddef.h>
+#include <stdint.h>
// Neither Rust nor Go support calling directly into C++ functions, therefore
// the public interface to libdeno is done in C.
#ifdef __cplusplus
@@ -11,8 +12,10 @@ extern "C" {
// Data that gets transmitted.
typedef struct {
- const char* data;
- size_t len;
+ uint8_t* alloc_ptr; // Start of memory allocation (returned from `malloc()`).
+ size_t alloc_len; // Length of the memory allocation.
+ uint8_t* data_ptr; // Start of logical contents (within the allocation).
+ size_t data_len; // Length of logical contents.
} deno_buf;
struct deno_s;
@@ -37,11 +40,17 @@ int deno_execute(Deno* d, const char* js_filename, const char* js_source);
// Routes message to the javascript callback set with deno.recv(). A false
// return value indicates error. Check deno_last_exception() for exception text.
// 0 = fail, 1 = success
+// After calling deno_send(), the caller no longer owns `buf` and must not use
+// it; deno_send() is responsible for releasing it's memory.
+// TODO(piscisaureus) In C++ and/or Rust, use a smart pointer or similar to
+// enforce this rule.
int deno_send(Deno* d, deno_buf buf);
// Call this inside a deno_recv_cb to respond synchronously to messages.
// If this is not called during the life time of a deno_recv_cb callback
// the deno.send() call in javascript will return null.
+// After calling deno_set_response(), the caller no longer owns `buf` and must
+// not access it; deno_set_response() is responsible for releasing it's memory.
void deno_set_response(Deno* d, deno_buf buf);
const char* deno_last_exception(Deno* d);
diff --git a/src/flatbuffer_builder.cc b/src/flatbuffer_builder.cc
new file mode 100644
index 000000000..8a7a0c649
--- /dev/null
+++ b/src/flatbuffer_builder.cc
@@ -0,0 +1,78 @@
+// Copyright 2018 Bert Belder <bertbelder@gmail.com>
+// All rights reserved. MIT License.
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "deno.h"
+#include "flatbuffer_builder.h"
+#include "flatbuffers/flatbuffers.h"
+
+namespace deno {
+
+deno_buf FlatBufferBuilder::ExportBuf() {
+ uint8_t* data_ptr = GetBufferPointer();
+ size_t data_len = GetSize();
+ return allocator_.GetAndKeepBuf(data_ptr, data_len);
+}
+
+deno_buf FlatBufferBuilder::Allocator::GetAndKeepBuf(uint8_t* data_ptr,
+ size_t data_len) {
+ // The builder will typically allocate one chunk of memory with some
+ // default size. After that, it'll only call allocate() again if the
+ // initial allocation wasn't big enough, which is then immediately
+ // followed by deallocate() to release the buffer that was too small.
+ //
+ // Therefore we can assume that the `data_ptr` points somewhere inside
+ // the last allocation, and that we never have to protect earlier
+ // allocations from being released.
+ //
+ // Each builder gets it's own Allocator instance, so multiple builders
+ // can be exist at the same time without conflicts.
+
+ assert(last_alloc_ptr_ != nullptr); // Must have allocated.
+ assert(keep_alloc_ptr_ == nullptr); // Didn't export any buffer so far.
+ assert(data_ptr >= last_alloc_ptr_); // Data must be within allocation.
+ assert(data_ptr + data_len <= last_alloc_ptr_ + last_alloc_len_);
+
+ keep_alloc_ptr_ = last_alloc_ptr_;
+
+ deno_buf buf;
+ buf.alloc_ptr = last_alloc_ptr_;
+ buf.alloc_len = last_alloc_len_;
+ buf.data_ptr = data_ptr;
+ buf.data_len = data_len;
+ return buf;
+}
+
+uint8_t* FlatBufferBuilder::Allocator::allocate(size_t size) {
+ auto ptr = reinterpret_cast<uint8_t*>(malloc(size));
+ if (ptr == nullptr) {
+ return nullptr;
+ }
+
+ last_alloc_ptr_ = ptr;
+ last_alloc_len_ = size;
+
+ return ptr;
+}
+
+void FlatBufferBuilder::Allocator::deallocate(uint8_t* ptr, size_t size) {
+ if (ptr == last_alloc_ptr_) {
+ last_alloc_ptr_ = nullptr;
+ last_alloc_len_ = 0;
+ }
+
+ if (ptr == keep_alloc_ptr_) {
+ // This allocation became an exported buffer, so don't free it.
+ // Clearing keep_alloc_ptr_ makes it possible to export another
+ // buffer later (after the builder is reset with `Reset()`).
+ keep_alloc_ptr_ = nullptr;
+ return;
+ }
+
+ free(ptr);
+}
+
+} // namespace deno
diff --git a/src/flatbuffer_builder.h b/src/flatbuffer_builder.h
new file mode 100644
index 000000000..1bc973afe
--- /dev/null
+++ b/src/flatbuffer_builder.h
@@ -0,0 +1,64 @@
+// Copyright 2018 Bert Belder <bertbelder@gmail.com>
+// All rights reserved. MIT License.
+#ifndef FLATBUFFER_BUILDER_H_
+#define FLATBUFFER_BUILDER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "deno.h"
+#include "flatbuffers/flatbuffers.h"
+
+namespace deno {
+
+// Wrap the default FlatBufferBuilder class, because the default one can't give
+// us a pointer to the output buffer that we own. Nominally,
+// FlatBufferBuilder::Release() should do that, but it returns some
+// smart-pointer-like object (DetachedBuffer) that frees the buffer when it goes
+// out of scope.
+//
+// This wrapper adds the `ExportBuf` method that returns a deno_buf, which
+// is really not owned at all -- the caller is responsible for releasing the
+// allocation with free().
+//
+// The alternative allocator also uses malloc()/free(), rather than
+// new/delete[], so that the exported buffer can be later be converted to an
+// ArrayBuffer; the (default) V8 ArrayBuffer allocator also uses free().
+class FlatBufferBuilder : public flatbuffers::FlatBufferBuilder {
+ static const size_t kDefaultInitialSize = 1024;
+
+ class Allocator : public flatbuffers::Allocator {
+ uint8_t* keep_alloc_ptr_ = nullptr;
+ uint8_t* last_alloc_ptr_ = nullptr;
+ size_t last_alloc_len_ = 0;
+
+ public:
+ deno_buf GetAndKeepBuf(uint8_t* data_ptr, size_t data_len);
+
+ protected:
+ virtual uint8_t* allocate(size_t size);
+ virtual void deallocate(uint8_t* ptr, size_t size);
+ };
+
+ Allocator allocator_;
+
+ public:
+ explicit FlatBufferBuilder(size_t initial_size = kDefaultInitialSize)
+ : flatbuffers::FlatBufferBuilder(initial_size, &allocator_) {}
+
+ // Export the finalized flatbuffer as a deno_buf structure. The caller takes
+ // ownership of the underlying memory allocation, which must be released with
+ // free().
+ // Afer calling ExportBuf() the FlatBufferBuilder should no longer be used;
+ // However it can be used again once it is reset with the Reset() method.
+ deno_buf ExportBuf();
+
+ // Don't use these.
+ flatbuffers::DetachedBuffer Release() = delete;
+ flatbuffers::DetachedBuffer ReleaseBufferPointer() = delete;
+};
+
+} // namespace deno
+
+#endif // FLATBUFFER_BUILDER_H_
diff --git a/src/flatbuffer_builder_test.cc b/src/flatbuffer_builder_test.cc
new file mode 100644
index 000000000..cfa12f0a0
--- /dev/null
+++ b/src/flatbuffer_builder_test.cc
@@ -0,0 +1,78 @@
+// Copyright 2018 Bert Belder <bertbelder@gmail.com>
+// All rights reserved. MIT License.
+
+#include <stdint.h>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+#include "deno.h"
+#include "flatbuffer_builder.h"
+
+template <typename T, std::size_t N>
+constexpr std::size_t countof(T const (&)[N]) noexcept {
+ return N;
+}
+
+TEST(FlatBufferBuilderTest, ExportBuf) {
+ const uint32_t nums[] = {1, 2, 3};
+ const char str[] = "hello mars";
+ deno_buf nums_buf;
+ deno_buf str_buf;
+ // Use scope so builder gets destroyed after building buffers.
+ {
+ deno::FlatBufferBuilder builder;
+ // Build first flatbuffer.
+ auto nums_fb = builder.CreateVector(nums, countof(nums));
+ builder.Finish(nums_fb);
+ nums_buf = builder.ExportBuf();
+ // Reset builder.
+ builder.Reset();
+ // Build second flatbuffer using the same builder.
+ auto str_fb = builder.CreateString(str);
+ builder.Finish(str_fb);
+ str_buf = builder.ExportBuf();
+ }
+ // Allocations should be different.
+ EXPECT_NE(nums_buf.alloc_ptr, str_buf.alloc_ptr);
+ // Logical buffer data should be contained inside their allocations.
+ EXPECT_GE(nums_buf.data_ptr, nums_buf.alloc_ptr);
+ EXPECT_LE(nums_buf.data_ptr + nums_buf.data_len,
+ nums_buf.alloc_ptr + nums_buf.alloc_len);
+ EXPECT_GE(str_buf.data_ptr, str_buf.alloc_ptr);
+ EXPECT_LE(str_buf.data_ptr + str_buf.data_len,
+ str_buf.alloc_ptr + str_buf.alloc_len);
+ // Since there is no way to parse these buffers without generating code,
+ // just check whether the data is contained in the raw content.
+ // Both the numbers vector and the string start at offset 8 in the flatbuffer.
+ auto nums_data = reinterpret_cast<uint32_t*>(nums_buf.data_ptr + 8);
+ for (size_t i = 0; i < countof(nums); i++) {
+ EXPECT_EQ(nums_data[i], nums[i]);
+ }
+ auto str_data = str_buf.data_ptr + 8;
+ for (size_t i = 0; i < countof(str); i++) {
+ EXPECT_EQ(str_data[i], str[i]);
+ }
+}
+
+TEST(FlatBufferBuilderTest, CanGrowBuffer) {
+ static const size_t kSmallInitialSize = 32;
+ static const char zeroes[1024] = {0};
+ {
+ // Create buffer with small initial size.
+ deno::FlatBufferBuilder builder(kSmallInitialSize);
+ // Write 1 byte and export buffer.
+ builder.Finish(builder.CreateVector(zeroes, 1));
+ auto buf = builder.ExportBuf();
+ // Exported buffer should have initial size.
+ EXPECT_EQ(buf.alloc_len, kSmallInitialSize);
+ }
+ {
+ // Create buffer with small initial size.
+ deno::FlatBufferBuilder builder(kSmallInitialSize);
+ // Write 1024 bytes and export buffer.
+ builder.Finish(builder.CreateVector(zeroes, countof(zeroes)));
+ auto buf = builder.ExportBuf();
+ // Exported buffer have grown.
+ EXPECT_GT(buf.alloc_len, kSmallInitialSize);
+ }
+}
diff --git a/src/main.cc b/src/main.cc
index a801bc292..81b8a2710 100644
--- a/src/main.cc
+++ b/src/main.cc
@@ -11,6 +11,7 @@
#endif
#include "deno.h"
+#include "flatbuffer_builder.h"
#include "flatbuffers/flatbuffers.h"
#include "src/handlers.h"
#include "src/msg_generated.h"
@@ -23,7 +24,7 @@ static int global_argc;
// Sends StartRes message
void HandleStart(Deno* d, uint32_t cmd_id) {
- flatbuffers::FlatBufferBuilder builder;
+ deno::FlatBufferBuilder builder;
char cwdbuf[1024];
// TODO(piscisaureus): support unicode on windows.
@@ -39,9 +40,7 @@ void HandleStart(Deno* d, uint32_t cmd_id) {
auto start_msg = CreateStartRes(builder, start_cwd, start_argv);
auto base = CreateBase(builder, cmd_id, 0, Any_StartRes, start_msg.Union());
builder.Finish(base);
- deno_buf bufout{reinterpret_cast<const char*>(builder.GetBufferPointer()),
- builder.GetSize()};
- deno_set_response(d, bufout);
+ deno_set_response(d, builder.ExportBuf());
}
void HandleCodeFetch(Deno* d, uint32_t cmd_id, const CodeFetch* msg) {
@@ -54,11 +53,10 @@ void HandleCodeFetch(Deno* d, uint32_t cmd_id, const CodeFetch* msg) {
}
void MessagesFromJS(Deno* d, deno_buf buf) {
- auto data = reinterpret_cast<const uint8_t*>(buf.data);
- flatbuffers::Verifier verifier(data, buf.len);
+ flatbuffers::Verifier verifier(buf.data_ptr, buf.data_len);
DCHECK(verifier.VerifyBuffer<Base>());
- auto base = flatbuffers::GetRoot<Base>(buf.data);
+ auto base = flatbuffers::GetRoot<Base>(buf.data_ptr);
auto cmd_id = base->cmdId();
auto msg_type = base->msg_type();
const char* msg_type_name = EnumNamesAny()[msg_type];
diff --git a/src/main.rs b/src/main.rs
index ece311a4f..2755a3a9c 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -9,8 +9,10 @@ use std::ptr;
#[repr(C)]
struct deno_buf {
- data: *const c_char,
- len: c_int, // TODO(ry) should be size_t.
+ alloc_ptr: *mut u8,
+ alloc_len: usize,
+ data_ptr: *mut u8,
+ data_len: usize,
}
#[repr(C)]
@@ -120,9 +122,10 @@ fn main() {
let mut d = Deno::new();
- d.execute("deno_main.js", "denoMain();")
- .unwrap_or_else(|err| {
+ d.execute("deno_main.js", "denoMain();").unwrap_or_else(
+ |err| {
println!("Error {}\n", err);
std::process::exit(1);
- });
+ },
+ );
}
diff --git a/src/mock_runtime_test.cc b/src/mock_runtime_test.cc
index 67b097e8d..19b224950 100644
--- a/src/mock_runtime_test.cc
+++ b/src/mock_runtime_test.cc
@@ -23,7 +23,17 @@ TEST(MockRuntimeTest, ErrorsCorrectly) {
deno_delete(d);
}
-deno_buf strbuf(const char* str) { return deno_buf{str, strlen(str)}; }
+deno_buf strbuf(const char* str) {
+ auto len = strlen(str);
+
+ deno_buf buf;
+ buf.alloc_ptr = reinterpret_cast<uint8_t*>(strdup(str));
+ buf.alloc_len = len + 1;
+ buf.data_ptr = buf.alloc_ptr;
+ buf.data_len = len;
+
+ return buf;
+}
TEST(MockRuntimeTest, SendSuccess) {
Deno* d = deno_new(nullptr, nullptr);
@@ -51,10 +61,10 @@ TEST(MockRuntimeTest, RecvReturnEmpty) {
static int count = 0;
Deno* d = deno_new(nullptr, [](auto _, auto buf) {
count++;
- EXPECT_EQ(static_cast<size_t>(3), buf.len);
- EXPECT_EQ(buf.data[0], 'a');
- EXPECT_EQ(buf.data[1], 'b');
- EXPECT_EQ(buf.data[2], 'c');
+ EXPECT_EQ(static_cast<size_t>(3), buf.data_len);
+ EXPECT_EQ(buf.data_ptr[0], 'a');
+ EXPECT_EQ(buf.data_ptr[1], 'b');
+ EXPECT_EQ(buf.data_ptr[2], 'c');
});
EXPECT_TRUE(deno_execute(d, "a.js", "RecvReturnEmpty()"));
EXPECT_EQ(count, 2);
@@ -65,10 +75,10 @@ TEST(MockRuntimeTest, RecvReturnBar) {
static int count = 0;
Deno* d = deno_new(nullptr, [](auto deno, auto buf) {
count++;
- EXPECT_EQ(static_cast<size_t>(3), buf.len);
- EXPECT_EQ(buf.data[0], 'a');
- EXPECT_EQ(buf.data[1], 'b');
- EXPECT_EQ(buf.data[2], 'c');
+ EXPECT_EQ(static_cast<size_t>(3), buf.data_len);
+ EXPECT_EQ(buf.data_ptr[0], 'a');
+ EXPECT_EQ(buf.data_ptr[1], 'b');
+ EXPECT_EQ(buf.data_ptr[2], 'c');
deno_set_response(deno, strbuf("bar"));
});
EXPECT_TRUE(deno_execute(d, "a.js", "RecvReturnBar()"));
@@ -82,6 +92,65 @@ TEST(MockRuntimeTest, DoubleRecvFails) {
deno_delete(d);
}
+TEST(MockRuntimeTest, SendRecvSlice) {
+ static int count = 0;
+ Deno* d = deno_new(nullptr, [](auto deno, auto buf) {
+ static const size_t alloc_len = 1024;
+ size_t i = count++;
+ // Check the size and offset of the slice.
+ size_t data_offset = buf.data_ptr - buf.alloc_ptr;
+ EXPECT_EQ(data_offset, i * 11);
+ EXPECT_EQ(buf.data_len, alloc_len - i * 30);
+ EXPECT_EQ(buf.alloc_len, alloc_len);
+ // Check values written by the JS side.
+ EXPECT_EQ(buf.data_ptr[0], 100 + i);
+ EXPECT_EQ(buf.data_ptr[buf.data_len - 1], 100 - i);
+ // Make copy of the backing buffer -- this is currently necessary because
+ // deno_set_response() takes ownership over the buffer, but we are not given
+ // ownership of `buf` by our caller.
+ uint8_t* alloc_ptr = reinterpret_cast<uint8_t*>(malloc(alloc_len));
+ memcpy(alloc_ptr, buf.alloc_ptr, alloc_len);
+ // Make a slice that is a bit shorter than the original.
+ deno_buf buf2{alloc_ptr, alloc_len, alloc_ptr + data_offset,
+ buf.data_len - 19};
+ // Place some values into the buffer for the JS side to verify.
+ buf2.data_ptr[0] = 200 + i;
+ buf2.data_ptr[buf2.data_len - 1] = 200 - i;
+ // Send back.
+ deno_set_response(deno, buf2);
+ });
+ EXPECT_TRUE(deno_execute(d, "a.js", "SendRecvSlice()"));
+ EXPECT_EQ(count, 5);
+ deno_delete(d);
+}
+
+TEST(MockRuntimeTest, JSSendArrayBufferViewTypes) {
+ static int count = 0;
+ Deno* d = deno_new(nullptr, [](auto _, auto buf) {
+ count++;
+ size_t data_offset = buf.data_ptr - buf.alloc_ptr;
+ EXPECT_EQ(data_offset, 2468);
+ EXPECT_EQ(buf.data_len, 1000);
+ EXPECT_EQ(buf.alloc_len, 4321);
+ EXPECT_EQ(buf.data_ptr[0], count);
+ });
+ EXPECT_TRUE(deno_execute(d, "a.js", "JSSendArrayBufferViewTypes()"));
+ EXPECT_EQ(count, 3);
+ deno_delete(d);
+}
+
+TEST(MockRuntimeTest, JSSendNeutersBuffer) {
+ static int count = 0;
+ Deno* d = deno_new(nullptr, [](auto _, auto buf) {
+ count++;
+ EXPECT_EQ(buf.data_len, 1);
+ EXPECT_EQ(buf.data_ptr[0], 42);
+ });
+ EXPECT_TRUE(deno_execute(d, "a.js", "JSSendNeutersBuffer()"));
+ EXPECT_EQ(count, 1);
+ deno_delete(d);
+}
+
TEST(MockRuntimeTest, TypedArraySnapshots) {
Deno* d = deno_new(nullptr, nullptr);
EXPECT_TRUE(deno_execute(d, "a.js", "TypedArraySnapshots()"));
@@ -98,8 +167,8 @@ TEST(MockRuntimeTest, ErrorHandling) {
static int count = 0;
Deno* d = deno_new(nullptr, [](auto deno, auto buf) {
count++;
- EXPECT_EQ(static_cast<size_t>(1), buf.len);
- EXPECT_EQ(buf.data[0], 42);
+ EXPECT_EQ(static_cast<size_t>(1), buf.data_len);
+ EXPECT_EQ(buf.data_ptr[0], 42);
});
EXPECT_FALSE(deno_execute(d, "a.js", "ErrorHandling()"));
EXPECT_EQ(count, 1);