mirror of
https://github.com/denoland/deno.git
synced 2024-12-22 15:24:46 -05:00
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.
This commit is contained in:
parent
bbcd4c8dd3
commit
24b0e91d80
12 changed files with 438 additions and 70 deletions
6
BUILD.gn
6
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" ]
|
||||
}
|
||||
|
||||
|
|
4
js/deno.d.ts
vendored
4
js/deno.d.ts
vendored
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
13
js/main.ts
13
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;
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
13
src/deno.h
13
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);
|
||||
|
|
78
src/flatbuffer_builder.cc
Normal file
78
src/flatbuffer_builder.cc
Normal file
|
@ -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
|
64
src/flatbuffer_builder.h
Normal file
64
src/flatbuffer_builder.h
Normal file
|
@ -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_
|
78
src/flatbuffer_builder_test.cc
Normal file
78
src/flatbuffer_builder_test.cc
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
12
src/main.cc
12
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];
|
||||
|
|
13
src/main.rs
13
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);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue