diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/binding.cc | 55 | ||||
-rw-r--r-- | src/deno.h | 13 | ||||
-rw-r--r-- | src/flatbuffer_builder.cc | 78 | ||||
-rw-r--r-- | src/flatbuffer_builder.h | 64 | ||||
-rw-r--r-- | src/flatbuffer_builder_test.cc | 78 | ||||
-rw-r--r-- | src/main.cc | 12 | ||||
-rw-r--r-- | src/main.rs | 13 | ||||
-rw-r--r-- | src/mock_runtime_test.cc | 91 |
8 files changed, 363 insertions, 41 deletions
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); |