Change ArrayBuffer API and implementation to use embedder-provided allocator.
authordslomov@chromium.org <dslomov@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Tue, 11 Jun 2013 10:41:14 +0000 (10:41 +0000)
committerdslomov@chromium.org <dslomov@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Tue, 11 Jun 2013 10:41:14 +0000 (10:41 +0000)
R=svenpanne@chromium.org

Review URL: https://codereview.chromium.org/15855012

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@15056 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

include/v8.h
src/api.cc
src/d8.cc
src/runtime.cc
src/v8.cc
src/v8.h
test/cctest/cctest.cc
test/cctest/test-api.cc

index b7eb294..c0bec79 100644 (file)
@@ -2370,30 +2370,6 @@ class V8EXPORT Function : public Object {
   static void CheckCast(Value* obj);
 };
 
-/**
- * The contents of an |ArrayBuffer|. Externalization of |ArrayBuffer|
- * populates an instance of this class with a pointer to data and byte length.
- *
- * |ArrayBufferContents| is the owner of its data. When an instance of
- * this class is destructed, the |Data| is freed.
- *
- * This API is experimental and may change significantly.
- */
-class V8EXPORT ArrayBufferContents {
- public:
-  ArrayBufferContents() : data_(NULL), byte_length_(0) {}
-  ~ArrayBufferContents();
-
-  void* Data() const { return data_; }
-  size_t ByteLength() const { return byte_length_; }
-
- private:
-  void* data_;
-  size_t byte_length_;
-
-  friend class ArrayBuffer;
-};
-
 #ifndef V8_ARRAY_BUFFER_INTERNAL_FIELD_COUNT
 #define V8_ARRAY_BUFFER_INTERNAL_FIELD_COUNT 2
 #endif
@@ -2405,6 +2381,53 @@ class V8EXPORT ArrayBufferContents {
 class V8EXPORT ArrayBuffer : public Object {
  public:
   /**
+   * Allocator that V8 uses to allocate |ArrayBuffer|'s memory.
+   * The allocator is a global V8 setting. It should be set with
+   * V8::SetArrayBufferAllocator prior to creation of a first ArrayBuffer.
+   *
+   * This API is experimental and may change significantly.
+   */
+  class V8EXPORT Allocator { // NOLINT
+   public:
+    virtual ~Allocator() {}
+
+    /**
+     * Allocate |length| bytes. Return NULL if allocation is not successful.
+     */
+    virtual void* Allocate(size_t length) = 0;
+    /**
+     * Free the memory pointed to |data|. That memory is guaranteed to be
+     * previously allocated by |Allocate|.
+     */
+    virtual void Free(void* data) = 0;
+  };
+
+  /**
+   * The contents of an |ArrayBuffer|. Externalization of |ArrayBuffer|
+   * returns an instance of this class, populated, with a pointer to data
+   * and byte length.
+   *
+   * The Data pointer of ArrayBuffer::Contents is always allocated with
+   * Allocator::Allocate that is set with V8::SetArrayBufferAllocator.
+   *
+   * This API is experimental and may change significantly.
+   */
+  class V8EXPORT Contents { // NOLINT
+   public:
+    Contents() : data_(NULL), byte_length_(0) {}
+
+    void* Data() const { return data_; }
+    size_t ByteLength() const { return byte_length_; }
+
+   private:
+    void* data_;
+    size_t byte_length_;
+
+    friend class ArrayBuffer;
+  };
+
+
+  /**
    * Data length in bytes.
    */
   size_t ByteLength() const;
@@ -2440,14 +2463,18 @@ class V8EXPORT ArrayBuffer : public Object {
   void Neuter();
 
   /**
-   * Pass the ownership of this ArrayBuffer's backing store to
-   * a given ArrayBufferContents.
+   * Make this ArrayBuffer external. The pointer to underlying memory block
+   * and byte length are returned as |Contents| structure. After ArrayBuffer
+   * had been etxrenalized, it does no longer owns the memory block. The caller
+   * should take steps to free memory when it is no longer needed.
+   *
+   * The memory block is guaranteed to be allocated with |Allocator::Allocate|
+   * that has been set with V8::SetArrayBufferAllocator.
    */
-  void Externalize(ArrayBufferContents* contents);
+  Contents Externalize();
 
   V8_INLINE(static ArrayBuffer* Cast(Value* obj));
 
-
   static const int kInternalFieldCount = V8_ARRAY_BUFFER_INTERNAL_FIELD_COUNT;
 
  private:
@@ -4194,6 +4221,14 @@ class V8EXPORT V8 {
       AllowCodeGenerationFromStringsCallback that);
 
   /**
+   * Set allocator to use for ArrayBuffer memory.
+   * The allocator should be set only once. The allocator should be set
+   * before any code tha uses ArrayBuffers is executed.
+   * This allocator is used in all isolates.
+   */
+  static void SetArrayBufferAllocator(ArrayBuffer::Allocator* allocator);
+
+  /**
    * Ignore out-of-memory exceptions.
    *
    * V8 running out of memory is treated as a fatal error by default.
index 18acc0d..20496fe 100644 (file)
@@ -5160,6 +5160,15 @@ void v8::V8::SetJitCodeEventHandler(
   isolate->logger()->SetCodeEventHandler(options, event_handler);
 }
 
+void v8::V8::SetArrayBufferAllocator(
+    ArrayBuffer::Allocator* allocator) {
+  if (!ApiCheck(i::V8::ArrayBufferAllocator() == NULL,
+                "v8::V8::SetArrayBufferAllocator",
+                "ArrayBufferAllocator might only be set once"))
+    return;
+  i::V8::SetArrayBufferAllocator(allocator);
+}
+
 
 bool v8::V8::Dispose() {
   i::Isolate* isolate = i::Isolate::Current();
@@ -6129,25 +6138,17 @@ bool v8::ArrayBuffer::IsExternal() const {
   return Utils::OpenHandle(this)->is_external();
 }
 
-v8::ArrayBufferContents::~ArrayBufferContents() {
-  free(data_);
-  data_ = NULL;
-  byte_length_ = 0;
-}
-
-
-void v8::ArrayBuffer::Externalize(ArrayBufferContents* contents) {
+v8::ArrayBuffer::Contents v8::ArrayBuffer::Externalize() {
   i::Handle<i::JSArrayBuffer> obj = Utils::OpenHandle(this);
   ApiCheck(!obj->is_external(),
             "v8::ArrayBuffer::Externalize",
             "ArrayBuffer already externalized");
   obj->set_is_external(true);
   size_t byte_length = static_cast<size_t>(obj->byte_length()->Number());
-  ApiCheck(contents->data_ == NULL,
-           "v8::ArrayBuffer::Externalize",
-           "Externalizing into non-empty ArrayBufferContents");
-  contents->data_ = obj->backing_store();
-  contents->byte_length_ = byte_length;
+  Contents contents;
+  contents.data_ = obj->backing_store();
+  contents.byte_length_ = byte_length;
+  return contents;
 }
 
 
index 57e0c04..a917dbd 100644 (file)
--- a/src/d8.cc
+++ b/src/d8.cc
@@ -1571,6 +1571,13 @@ static void EnableHarmonyTypedArraysViaCommandLine() {
 #endif
 
 
+class ShellArrayBufferAllocator : public v8::ArrayBuffer::Allocator {
+ public:
+  virtual void* Allocate(size_t length) { return malloc(length); }
+  virtual void Free(void* data) { free(data); }
+};
+
+
 int Shell::Main(int argc, char* argv[]) {
   if (!SetOptions(argc, argv)) return 1;
 #ifndef V8_SHARED
@@ -1579,6 +1586,8 @@ int Shell::Main(int argc, char* argv[]) {
 #else
   EnableHarmonyTypedArraysViaCommandLine();
 #endif
+  ShellArrayBufferAllocator array_buffer_allocator;
+  v8::V8::SetArrayBufferAllocator(&array_buffer_allocator);
   int result = 0;
   Isolate* isolate = Isolate::GetCurrent();
   DumbLineEditor dumb_line_editor(isolate);
index 074d8dc..a1f465f 100644 (file)
@@ -663,7 +663,8 @@ static void ArrayBufferWeakCallback(v8::Isolate* external_isolate,
         isolate, array_buffer->byte_length());
     isolate->heap()->AdjustAmountOfExternalAllocatedMemory(
         -static_cast<intptr_t>(allocated_length));
-    free(data);
+    CHECK(V8::ArrayBufferAllocator() != NULL);
+    V8::ArrayBufferAllocator()->Free(data);
   }
   object->Dispose(external_isolate);
 }
@@ -699,8 +700,9 @@ bool Runtime::SetupArrayBufferAllocatingData(
     Handle<JSArrayBuffer> array_buffer,
     size_t allocated_length) {
   void* data;
+  CHECK(V8::ArrayBufferAllocator() != NULL);
   if (allocated_length != 0) {
-    data = malloc(allocated_length);
+    data = V8::ArrayBufferAllocator()->Allocate(allocated_length);
     if (data == NULL) return false;
     memset(data, 0, allocated_length);
   } else {
index e21c815..80b12de 100644 (file)
--- a/src/v8.cc
+++ b/src/v8.cc
@@ -56,6 +56,7 @@ bool V8::has_been_disposed_ = false;
 bool V8::has_fatal_error_ = false;
 bool V8::use_crankshaft_ = true;
 List<CallCompletedCallback>* V8::call_completed_callbacks_ = NULL;
+v8::ArrayBuffer::Allocator* V8::array_buffer_allocator_ = NULL;
 
 static LazyMutex entropy_mutex = LAZY_MUTEX_INITIALIZER;
 
index e906035..b8a5ae4 100644 (file)
--- a/src/v8.h
+++ b/src/v8.h
@@ -121,6 +121,15 @@ class V8 : public AllStatic {
   static void RemoveCallCompletedCallback(CallCompletedCallback callback);
   static void FireCallCompletedCallback(Isolate* isolate);
 
+  static v8::ArrayBuffer::Allocator* ArrayBufferAllocator() {
+    return array_buffer_allocator_;
+  }
+
+  static void SetArrayBufferAllocator(v8::ArrayBuffer::Allocator *allocator) {
+    CHECK_EQ(NULL, array_buffer_allocator_);
+    array_buffer_allocator_ = allocator;
+  }
+
  private:
   static void InitializeOncePerProcessImpl();
   static void InitializeOncePerProcess();
@@ -139,6 +148,8 @@ class V8 : public AllStatic {
   static bool use_crankshaft_;
   // List of callbacks when a Call completes.
   static List<CallCompletedCallback>* call_completed_callbacks_;
+  // Allocator for external array buffers.
+  static v8::ArrayBuffer::Allocator* array_buffer_allocator_;
 };
 
 
index b241f32..94dcce1 100644 (file)
@@ -98,10 +98,21 @@ static void PrintTestList(CcTest* current) {
 v8::Isolate* CcTest::default_isolate_;
 
 
+class CcTestArrayBufferAllocator : public v8::ArrayBuffer::Allocator {
+ public:
+  virtual void* Allocate(size_t length) { return malloc(length); }
+  virtual void Free(void* data) { free(data); }
+};
+
+
 int main(int argc, char* argv[]) {
   v8::internal::FlagList::SetFlagsFromCommandLine(&argc, argv, true);
   v8::internal::FLAG_harmony_array_buffer = true;
   v8::internal::FLAG_harmony_typed_arrays = true;
+
+  CcTestArrayBufferAllocator array_buffer_allocator;
+  v8::V8::SetArrayBufferAllocator(&array_buffer_allocator);
+
   CcTest::set_default_isolate(v8::Isolate::GetCurrent());
   CHECK(CcTest::default_isolate() != NULL);
   int tests_run = 0;
index f5e315b..5d3a79d 100755 (executable)
@@ -2570,6 +2570,19 @@ THREADED_TEST(SymbolProperties) {
 }
 
 
+class ScopedArrayBufferContents {
+ public:
+  explicit ScopedArrayBufferContents(
+      const v8::ArrayBuffer::Contents& contents)
+    : contents_(contents) {}
+  ~ScopedArrayBufferContents() { free(contents_.Data()); }
+  void* Data() const { return contents_.Data(); }
+  size_t ByteLength() const { return contents_.ByteLength(); }
+ private:
+  const v8::ArrayBuffer::Contents contents_;
+};
+
+
 THREADED_TEST(ArrayBuffer_ApiInternalToExternal) {
   i::FLAG_harmony_array_buffer = true;
   i::FLAG_harmony_typed_arrays = true;
@@ -2583,8 +2596,7 @@ THREADED_TEST(ArrayBuffer_ApiInternalToExternal) {
   CHECK(!ab->IsExternal());
   HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
 
-  v8::ArrayBufferContents ab_contents;
-  ab->Externalize(&ab_contents);
+  ScopedArrayBufferContents ab_contents(ab->Externalize());
   CHECK(ab->IsExternal());
 
   CHECK_EQ(1024, static_cast<int>(ab_contents.ByteLength()));
@@ -2626,8 +2638,7 @@ THREADED_TEST(ArrayBuffer_JSInternalToExternal) {
   Local<v8::ArrayBuffer> ab1 = v8::ArrayBuffer::Cast(*result);
   CHECK_EQ(2, static_cast<int>(ab1->ByteLength()));
   CHECK(!ab1->IsExternal());
-  v8::ArrayBufferContents ab1_contents;
-  ab1->Externalize(&ab1_contents);
+  ScopedArrayBufferContents ab1_contents(ab1->Externalize());
   CHECK(ab1->IsExternal());
 
   result = CompileRun("ab1.byteLength");
@@ -2734,8 +2745,7 @@ THREADED_TEST(ArrayBuffer_NeuteringApi) {
   v8::Handle<v8::Float64Array> f64a =
     CreateAndCheck<v8::Float64Array, 8>(buffer, 8, 127);
 
-  v8::ArrayBufferContents contents;
-  buffer->Externalize(&contents);
+  ScopedArrayBufferContents contents(buffer->Externalize());
   buffer->Neuter();
   CHECK_EQ(0, static_cast<int>(buffer->ByteLength()));
   CheckIsNeutered(u8a);
@@ -2786,8 +2796,7 @@ THREADED_TEST(ArrayBuffer_NeuteringScript) {
   v8::Handle<v8::Float64Array> f64a(
     v8::Float64Array::Cast(*CompileRun("f64a")));
 
-  v8::ArrayBufferContents contents;
-  ab->Externalize(&contents);
+  ScopedArrayBufferContents contents(ab->Externalize());
   ab->Neuter();
   CHECK_EQ(0, static_cast<int>(ab->ByteLength()));
   CheckIsNeutered(u8a);