Skip to content

Commit

Permalink
shared array buffers (#378)
Browse files Browse the repository at this point in the history
* Initial support for SharedArrayBuffer
  • Loading branch information
jargv authored Apr 10, 2023
1 parent 0e40e6e commit cee5f84
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 8 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [v0.10.0] - 2023-04-10

### Changed
- Required Go version changed to 1.17 (needed for SharedArrayBuffer support)

### Added
- Support for getting the underlying data (as a `[]byte`) from a SharedArrayBuffer

### Fixed
- Upgrade to V8 11.1.277.13


## [v0.9.0] - 2023-03-30

### Fixed
Expand Down
1 change: 1 addition & 0 deletions function_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ func (tmpl *FunctionTemplate) GetFunction(ctx *Context) *Function {

// Note that ideally `thisAndArgs` would be split into two separate arguments, but they were combined
// to workaround an ERROR_COMMITMENT_LIMIT error on windows that was detected in CI.
//
//export goFunctionCallback
func goFunctionCallback(ctxref int, cbref int, thisAndArgs *C.ValuePtr, argsCount int) C.ValuePtr {
ctx := getContext(ctxref)
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module rogchap.com/v8go

go 1.16
go 1.17
39 changes: 39 additions & 0 deletions v8go.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1670,4 +1670,43 @@ const char* Version() {
void SetFlags(const char* flags) {
V8::SetFlagsFromString(flags);
}

/********** SharedArrayBuffer & BackingStore ***********/

struct v8BackingStore {
v8BackingStore(std::shared_ptr<v8::BackingStore>&& ptr)
: backing_store{ptr} {}
std::shared_ptr<v8::BackingStore> backing_store;
};

BackingStorePtr SharedArrayBufferGetBackingStore(ValuePtr ptr) {
LOCAL_VALUE(ptr);
auto buffer = Local<SharedArrayBuffer>::Cast(value);
auto backing_store = buffer->GetBackingStore();
auto proxy = new v8BackingStore(std::move(backing_store));
return proxy;
}

void BackingStoreRelease(BackingStorePtr ptr) {
if (ptr == nullptr) {
return;
}
ptr->backing_store.reset();
delete ptr;
}

void* BackingStoreData(BackingStorePtr ptr) {
if (ptr == nullptr) {
return nullptr;
}

return ptr->backing_store->Data();
}

size_t BackingStoreByteLength(BackingStorePtr ptr) {
if (ptr == nullptr) {
return 0;
}
return ptr->backing_store->ByteLength();
}
}
9 changes: 9 additions & 0 deletions v8go.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ typedef struct v8ScriptCompilerCachedData v8ScriptCompilerCachedData;
typedef const v8ScriptCompilerCachedData* ScriptCompilerCachedDataPtr;
#endif

// Opaque to both C and C++
typedef struct v8BackingStore v8BackingStore;
typedef v8BackingStore* BackingStorePtr;

#include <stddef.h>
#include <stdint.h>

Expand Down Expand Up @@ -307,6 +311,11 @@ ValuePtr FunctionSourceMapUrl(ValuePtr ptr);
const char* Version();
extern void SetFlags(const char* flags);

extern BackingStorePtr SharedArrayBufferGetBackingStore(ValuePtr ptr);
extern void BackingStoreRelease(BackingStorePtr ptr);
extern void* BackingStoreData(BackingStorePtr ptr);
extern size_t BackingStoreByteLength(BackingStorePtr ptr);

#ifdef __cplusplus
} // extern "C"
#endif
Expand Down
32 changes: 25 additions & 7 deletions value.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,14 @@ func Null(iso *Isolate) *Value {
}

// NewValue will create a primitive value. Supported values types to create are:
// string -> V8::String
// int32 -> V8::Integer
// uint32 -> V8::Integer
// int64 -> V8::BigInt
// uint64 -> V8::BigInt
// bool -> V8::Boolean
// *big.Int -> V8::BigInt
//
// string -> V8::String
// int32 -> V8::Integer
// uint32 -> V8::Integer
// int64 -> V8::BigInt
// uint64 -> V8::BigInt
// bool -> V8::Boolean
// *big.Int -> V8::BigInt
func NewValue(iso *Isolate, val interface{}) (*Value, error) {
if iso == nil {
return nil, errors.New("v8go: failed to create new Value: Isolate cannot be <nil>")
Expand Down Expand Up @@ -580,3 +581,20 @@ func (v *Value) MarshalJSON() ([]byte, error) {
}
return []byte(jsonStr), nil
}

func (v *Value) SharedArrayBufferGetContents() ([]byte, func(), error) {
if !v.IsSharedArrayBuffer() {
return nil, nil, errors.New("v8go: value is not a SharedArrayBuffer")
}

backingStore := C.SharedArrayBufferGetBackingStore(v.ptr)
release := func() {
C.BackingStoreRelease(backingStore)
}

byte_ptr := (*byte)(unsafe.Pointer(C.BackingStoreData(backingStore)))
byte_size := C.BackingStoreByteLength(backingStore)
byte_slice := unsafe.Slice(byte_ptr, byte_size)

return byte_slice, release, nil
}
59 changes: 59 additions & 0 deletions value_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -711,3 +711,62 @@ func TestValueMarshalJSON(t *testing.T) {
})
}
}

func TestValueArrayBufferContents(t *testing.T) {
t.Parallel()
iso := v8.NewIsolate()
defer iso.Dispose()

ctx := v8.NewContext(iso)
defer ctx.Close()

val, err := ctx.RunScript(`
(()=>{
let buf = new SharedArrayBuffer(1024);
let arr = new Int8Array(buf);
arr[0] = 42;
arr[1] = 52;
return buf;
})();
`, "test.js")

if err != nil {
t.Fatalf("failed to run script: %v", err)
}

if !val.IsSharedArrayBuffer() {
t.Fatalf("expected SharedArrayBuffer value")
}

buf, cleanup, err := val.SharedArrayBufferGetContents()
if err != nil {
t.Fatalf("error getting array buffer contents: %#v", err)
}
defer cleanup()

if len(buf) != 1024 {
t.Fatalf("expected len(buf) to be 1024")
}

if buf[0] != 42 {
t.Fatalf("expected buf[0] to be 42")
}

if buf[1] != 52 {
t.Fatalf("expected buf[1] to be 52")
}

if buf[3] != 0 {
t.Fatalf("expected buf[1] to be 0")
}

// ensure there's an error if we call the method on something that isn't a SharedArrayBuffer
val, err = ctx.RunScript("7", "test2.js")
if err != nil {
t.Fatalf("error running trivial script")
}
_, _, err = val.SharedArrayBufferGetContents()
if err == nil {
t.Fatalf("Expected an error trying call SharedArrayBufferGetContents on value of incorrect type")
}
}

0 comments on commit cee5f84

Please sign in to comment.