-
-
Notifications
You must be signed in to change notification settings - Fork 225
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
uint8 array support #143
base: master
Are you sure you want to change the base?
uint8 array support #143
Conversation
Codecov ReportPatch coverage:
Additional details and impacted files@@ Coverage Diff @@
## master #143 +/- ##
==========================================
+ Coverage 95.23% 97.03% +1.79%
==========================================
Files 17 13 -4
Lines 588 472 -116
==========================================
- Hits 560 458 -102
+ Misses 19 9 -10
+ Partials 9 5 -4
☔ View full report in Codecov by Sentry. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd suggest separating the exception support from the uint8 support. Looks like a promising contribution!
array_test.go
Outdated
"testing" | ||
) | ||
|
||
type NativeObject interface { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you intend this to be part of the public API for the package? if so it shouldn't be in a "*_test.go" file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not a V8 object, so it shouldn't be part of the public API
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I not think you need this to be an interface
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code is testing-only, should never have been exported. Will fix.
isolate.go
Outdated
@@ -115,3 +117,10 @@ func (i *Isolate) getCallback(ref int) FunctionCallback { | |||
defer i.cbMutex.RUnlock() | |||
return i.cbs[ref] | |||
} | |||
|
|||
func (i *Isolate) ThrowException(msg string) *Value { // TwinTag added |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New exported functions should have a comment.
No need to include TwinTag added here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will add missing comment.
The 'TwintTag' thingy is an oversight. I must have missed this one when I removed these tags :-(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is there changes to the linux lib?
@@ -82,6 +82,11 @@ func NewValue(iso *Isolate, val interface{}) (*Value, error) { | |||
rtnVal = &Value{ | |||
ptr: C.NewValueNumber(iso.ptr, C.double(v)), | |||
} | |||
case []uint8: // TwinTag added | |||
rtnVal = &Value{ | |||
//NOTE: C.CBytes() allocates memory, must be freed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How will CBytes be freed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The newly added function C.NewValueUint8Array() creates a BackingStore that takes ownership over the array allocated here by C.CBytes(). This allocation gets freed when V8 decides it can get rid of the BackingStore. Verified manually by adding a print statement to the deallocator.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would/Could this cause a panic on the Go side? For example V8 removes the backing store, and then the caller tries to read from the Go slice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The bytes allocated by C.CBytes() are passed directly into the V8 BackingStore that then takes ownership over the allocation. They are not exposed or handed out anywhere else, so it is not possible for any Go code to do an invalid access.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment in the code is not very useful at the moment.
What is important from the perspective of this function is that NewValueUint8Array
takes ownership of that memory allocation.
//NOTE: C.CBytes() allocates memory, must be freed | |
// C.NewValueUint8Array takes ownership of the C.CBytes() allocation, making sure it gets freed. |
Details of how C.NewValueUint8Array does that matter more in the context of its implementation. I think it is clear enough from looking at that function that it is passing a deletion callback to the backing store to free the memory. If you disagree, please comment on that code.
There is some great code here; but I'm unsure about this API as it's not following the V8 API (in hindsight I think this is also true for BigInt and maybe some of the other simple types) I would like to see the API reflect the inheritance structure that exits in the V8 API eg: https://v8.github.io/api/head/classv8_1_1Uint8Array.html Similar to how we deal with Object and Function, Array and ArrayBuffers should follow suit. Once that API is in place we can add convenance methods between the Go world and the V8 world. Although there is an overhead, I would avoid shared memory for the slices as it can be hard for callers to free allocated memory; we want the public API to be as Go friendly as possible. |
This is just wonderful PR! Cant wait to have this merged. 👍 |
@teuget btw I did messed around with changes. Seems to me that try this:
func TestNewObject(t *testing.T) {
t.Parallel()
iso, _ := v8go.NewIsolate()
ctx, _ := v8go.NewContext(iso)
obj := v8go.NewObject(ctx)
err := obj.Set("test", "ok")
if err != nil {
t.Errorf("Got error from setting object property: %v", err)
}
} |
but seems that |
Richard,
Great to hear that Enter/Exit fixes the issue. That was also the first
thing I was going to try when I got the chance.
Thanks,
Tom
…On Tue, 6 Jul 2021 at 14:06, Richard Hutta ***@***.***> wrote:
but seems that c->Enter(); and c->Exit(); does the job for NewObject as
well.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#143 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/ACFKA4726JWGAU5P74J2NCTTWLWVXANCNFSM46JJSGVA>
.
|
array_test.go
Outdated
@@ -0,0 +1,119 @@ | |||
package v8go |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tests should be in the test package.
Also, this is missing a copyright header that all the other go files have.
package v8go | |
// Copyright 2021 the v8go contributors. All rights reserved. | |
// Use of this source code is governed by a BSD-style license that can be | |
// found in the LICENSE file. | |
package v8go_test |
This will also require importing v8go and using its namespace when accessing its contents.
Lastly, an array_test.go test file would conventionally test code in a corresponding array.go file, but that file doesn't exist.
"errors" | ||
"fmt" | ||
"log" | ||
"testing" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"testing" | |
"testing" | |
v8 "rogchap.com/v8go" |
array_test.go
Outdated
"testing" | ||
) | ||
|
||
func reverseUint8ArrayFunctionCallback(info *FunctionCallbackInfo) *Value { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
func reverseUint8ArrayFunctionCallback(info *FunctionCallbackInfo) *Value { | |
func reverseUint8ArrayFunctionCallback(info *v8.FunctionCallbackInfo) *Value { |
if val, err := ctx.RunScript("native.reverseUint8Array(new Uint8Array([0,1,2,3,4,5,6,7,8,9]))", ""); err != nil { | ||
t.Error(err) | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use fatal errors to avoid needing to deeply nest the non-error paths. There is a test helper method for this
if val, err := ctx.RunScript("native.reverseUint8Array(new Uint8Array([0,1,2,3,4,5,6,7,8,9]))", ""); err != nil { | |
t.Error(err) | |
} else { | |
val, err := ctx.RunScript("native.reverseUint8Array(new Uint8Array([0,1,2,3,4,5,6,7,8,9]))", "") | |
failIf(t, err) |
array_test.go
Outdated
if !val.IsUint8Array() { | ||
t.Errorf("Expected uint8 array return value") | ||
} | ||
fmt.Printf("Reversed array: %v\n", val.Uint8Array()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid polluting the test output with print statements.
fmt.Printf("Reversed array: %v\n", val.Uint8Array()) |
// The Context::Enter/Exit is only needed when calling this code from low-level unit tests, | ||
// otherwise ArrayBuffer::New() trips over missing context. | ||
// They are not needed when this code gets called through an executing script. | ||
c->Enter(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that this is normally done using Context::Scope context_scope(c)
so there is no need for the separate Enter and Exit call.
} | ||
read := args[0].Int32() | ||
written := args[1].Int32() | ||
obj := v8go.NewObject(info.Context()) // create object |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just like in array_test.go and arraybuffer_test.go, these tests are very indirect making them much less readable by not being focused on what is actually being tested.
} | ||
|
||
func (ab *ArrayBuffer) PutBytes(bytes []uint8) { | ||
cbytes := C.CBytes(bytes) //FIXME is there really no way to avoid this malloc+memcpy? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should be able to pass in unsafe.Pointer(&bytes[0])
and len(bytes)
to the C function to avoid the extra allocation and copy.
return int64(C.ArrayBufferByteLength(ab.ptr)) | ||
} | ||
|
||
func (ab *ArrayBuffer) GetBytes() []uint8 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prefixing methods with Get
isn't idiomatic for Go.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dylanahsmith can you expand on this? I want to make sure I do this correctly in the future. Do you mean that it should just be Bytes()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean that it should just be
Bytes()
?
Yes
len := C.ArrayBufferByteLength(ab.ptr) | ||
cbytes := unsafe.Pointer(C.GetArrayBufferBytes(ab.ptr)) // points into BackingStore | ||
return C.GoBytes(cbytes, C.int(len)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can't safely just expose the C++ data. However, we could expose more primitive operations. For instance, if we exposed a method to append the data to a Go slice, then we could implement a method that does this in pure Go using that on an empty slice.
Another possible primitive would be something like v8::ArrayBufferView::CopyContents which could be used to copy as much as it could into a slice without growing it for the fixed size buffer case. It might also be useful to be able to get something like a V8::ArrayBufferView without having to allocate the V8 object for it, in order to specify the part of the ArrayBuffer to copy/append into or out of a Go slice.
I suppose more primitive operations could always be added later though.
92acee2
to
40a3423
Compare
e01fad2
to
4c49aa9
Compare
@sharmaashish13 @teuget are you still on this? I think this is a great addition and would make this library so much better. I actually need workarounds for this as of now and this being finished would be a charm! ❤️ |
@sharmaashish13 @teuget I agree with @boindil . I'd also dig String arrays. |
+1 in wanting to see this get merged |
+1 here 😅 |
…tep: test that we can pass a go []uint8 to js.
…reverses a uint8 array via a function implemented in go. Still to be scrutinized: memory management, error reporting.
…n. This commit allows Go code to directly access the raw ArrayBuffer bytes. A next step could be to add typed View objects to mirror those found in JS. Feedback welcome.
This PR adds code to v8go to convert []uint8 array from javascript to golang and back,
and also an Isolate method to allow throwing javascript exceptions from golang callbacks.
Test case resides in file array_test.go.