From 00d592cd4d3859c16d7cdedce5d9a7859e765782 Mon Sep 17 00:00:00 2001
From: Andy Finch <andyfinch7@gmail.com>
Date: Sat, 28 Dec 2019 16:29:42 -0500
Subject: [PATCH] add v8::SharedArrayBuffer (#134)

---
 src/array_buffer.rs        | 15 +++++++--
 src/binding.cc             | 16 ++++++++++
 src/lib.rs                 |  2 ++
 src/shared_array_buffer.rs | 62 ++++++++++++++++++++++++++++++++++++++
 tests/test_api.rs          | 49 ++++++++++++++++++++++++++++++
 5 files changed, 142 insertions(+), 2 deletions(-)
 create mode 100644 src/shared_array_buffer.rs

diff --git a/src/array_buffer.rs b/src/array_buffer.rs
index 3237ae3e..08bcbd59 100644
--- a/src/array_buffer.rs
+++ b/src/array_buffer.rs
@@ -28,6 +28,7 @@ extern "C" {
     isolate: *mut Isolate,
     byte_length: usize,
   ) -> *mut BackingStore;
+  fn v8__BackingStore__Data(self_: &mut BackingStore) -> *mut std::ffi::c_void;
   fn v8__BackingStore__ByteLength(self_: &BackingStore) -> usize;
   fn v8__BackingStore__IsShared(self_: &BackingStore) -> bool;
   fn v8__BackingStore__DELETE(self_: &mut BackingStore);
@@ -98,11 +99,21 @@ impl Delete for Allocator {
 pub struct BackingStore([usize; 6]);
 
 impl BackingStore {
+  /// Returns a rust u8 slice with a lifetime equal to the lifetime of the BackingStore.
+  pub fn data_bytes<'a>(&'a mut self) -> &'a mut [u8] {
+    unsafe {
+      std::slice::from_raw_parts_mut::<'a, u8>(
+        v8__BackingStore__Data(self) as *mut u8,
+        self.byte_length(),
+      )
+    }
+  }
+
   /// Return a pointer to the beginning of the memory block for this backing
   /// store. The pointer is only valid as long as this backing store object
   /// lives.
-  pub fn data(&self) -> std::ffi::c_void {
-    unimplemented!()
+  pub fn data(&mut self) -> &mut std::ffi::c_void {
+    unsafe { &mut *v8__BackingStore__Data(self) }
   }
 
   /// The length (in bytes) of this backing store.
diff --git a/src/binding.cc b/src/binding.cc
index f16bc6a2..d93f7ea2 100644
--- a/src/binding.cc
+++ b/src/binding.cc
@@ -335,6 +335,10 @@ two_pointers_t v8__ArrayBuffer__GetBackingStore(v8::ArrayBuffer& self) {
   return make_pod<two_pointers_t>(self.GetBackingStore());
 }
 
+void* v8__BackingStore__Data(v8::BackingStore& self) {
+  return self.Data();
+}
+
 size_t v8__BackingStore__ByteLength(v8::BackingStore& self) {
   return self.ByteLength();
 }
@@ -682,6 +686,18 @@ v8::PrimitiveArray* v8__ScriptOrModule__GetHostDefinedOptions(
   return local_to_ptr(self.GetHostDefinedOptions());
 }
 
+v8::SharedArrayBuffer* v8__SharedArrayBuffer__New(v8::Isolate* isolate, size_t byte_length) {
+  return local_to_ptr(v8::SharedArrayBuffer::New(isolate, byte_length));
+}
+
+size_t v8__SharedArrayBuffer__ByteLength(v8::SharedArrayBuffer& self) {
+  return self.ByteLength();
+}
+
+two_pointers_t v8__SharedArrayBuffer__GetBackingStore(v8::SharedArrayBuffer& self) {
+  return make_pod<two_pointers_t>(self.GetBackingStore());
+}
+
 v8::Value* v8__JSON__Parse(v8::Local<v8::Context> context,
                            v8::Local<v8::String> json_string) {
   return maybe_local_to_ptr(v8::JSON::Parse(context, json_string));
diff --git a/src/lib.rs b/src/lib.rs
index 8c2ea51e..66317de0 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -61,6 +61,7 @@ mod promise;
 mod property;
 mod script;
 mod script_or_module;
+mod shared_array_buffer;
 mod snapshot;
 mod string;
 mod support;
@@ -104,6 +105,7 @@ pub use promise::{
 pub use property::PropertyCallbackInfo;
 pub use script::{Script, ScriptOrigin};
 pub use script_or_module::ScriptOrModule;
+pub use shared_array_buffer::SharedArrayBuffer;
 pub use snapshot::{FunctionCodeHandling, SnapshotCreator, StartupData};
 pub use string::NewStringType;
 pub use string::String;
diff --git a/src/shared_array_buffer.rs b/src/shared_array_buffer.rs
new file mode 100644
index 00000000..dd8464cb
--- /dev/null
+++ b/src/shared_array_buffer.rs
@@ -0,0 +1,62 @@
+use std::ops::Deref;
+
+use crate::support::Opaque;
+use crate::support::SharedRef;
+use crate::BackingStore;
+use crate::Isolate;
+use crate::Local;
+use crate::ToLocal;
+use crate::Value;
+
+extern "C" {
+  fn v8__SharedArrayBuffer__New(
+    isolate: *mut Isolate,
+    byte_length: usize,
+  ) -> *mut SharedArrayBuffer;
+  fn v8__SharedArrayBuffer__ByteLength(
+    self_: *const SharedArrayBuffer,
+  ) -> usize;
+  fn v8__SharedArrayBuffer__GetBackingStore(
+    self_: *const SharedArrayBuffer,
+  ) -> SharedRef<BackingStore>;
+}
+
+/// An instance of the built-in SharedArrayBuffer constructor.
+/// This API is experimental and may change significantly.
+#[repr(C)]
+pub struct SharedArrayBuffer(Opaque);
+
+impl SharedArrayBuffer {
+  /// Create a new SharedArrayBuffer. Allocate |byte_length| bytes.
+  /// Allocated memory will be owned by a created SharedArrayBuffer and
+  /// will be deallocated when it is garbage-collected,
+  /// unless the object is externalized.
+  pub fn new<'sc>(
+    scope: &mut impl ToLocal<'sc>,
+    byte_length: usize,
+  ) -> Option<Local<'sc, SharedArrayBuffer>> {
+    unsafe {
+      Local::from_raw(v8__SharedArrayBuffer__New(scope.isolate(), byte_length))
+    }
+  }
+
+  /// Data length in bytes.
+  pub fn byte_length(&self) -> usize {
+    unsafe { v8__SharedArrayBuffer__ByteLength(self) }
+  }
+
+  /// Get a shared pointer to the backing store of this array buffer. This
+  /// pointer coordinates the lifetime management of the internal storage
+  /// with any live ArrayBuffers on the heap, even across isolates. The embedder
+  /// should not attempt to manage lifetime of the storage through other means.
+  pub fn get_backing_store(&self) -> SharedRef<BackingStore> {
+    unsafe { v8__SharedArrayBuffer__GetBackingStore(self) }
+  }
+}
+
+impl Deref for SharedArrayBuffer {
+  type Target = Value;
+  fn deref(&self) -> &Self::Target {
+    unsafe { &*(self as *const _ as *const Value) }
+  }
+}
diff --git a/tests/test_api.rs b/tests/test_api.rs
index aa46c032..9b78745e 100644
--- a/tests/test_api.rs
+++ b/tests/test_api.rs
@@ -1491,3 +1491,52 @@ fn dynamic_import() {
   isolate.exit();
   drop(g);
 }
+
+#[test]
+fn shared_array_buffer() {
+  let g = setup();
+  let mut params = v8::Isolate::create_params();
+  params.set_array_buffer_allocator(v8::new_default_allocator());
+  let mut isolate = v8::Isolate::new(params);
+  isolate.enter();
+  let mut locker = v8::Locker::new(&isolate);
+  {
+    let mut hs = v8::HandleScope::new(&mut locker);
+    let s = hs.enter();
+    let mut context = v8::Context::new(s);
+    context.enter();
+    let maybe_sab = v8::SharedArrayBuffer::new(s, 16);
+    assert!(maybe_sab.is_some());
+    let sab = maybe_sab.unwrap();
+    let mut backing_store = sab.get_backing_store();
+    let shared_buf = backing_store.data_bytes();
+    shared_buf[5] = 12;
+    shared_buf[12] = 52;
+    let global = context.global(s);
+    assert_eq!(
+      global.create_data_property(
+        context,
+        cast(v8_str(s, "shared")),
+        cast(sab)
+      ),
+      v8::MaybeBool::JustTrue
+    );
+    let source = v8::String::new(
+      s,
+      "sharedBytes = new Uint8Array(shared); sharedBytes[2] = 16; sharedBytes[14] = 62; sharedBytes[5] + sharedBytes[12]",
+    )
+    .unwrap();
+    let mut script = v8::Script::compile(s, context, source, None).unwrap();
+    source.to_rust_string_lossy(s);
+    let result = script.run(s, context).unwrap();
+    // TODO: safer casts.
+    let result: v8::Local<v8::Integer> = cast(result);
+    assert_eq!(result.value(), 64);
+    assert_eq!(shared_buf[2], 16);
+    assert_eq!(shared_buf[14], 62);
+    context.exit();
+  }
+  drop(locker);
+  isolate.exit();
+  drop(g);
+}