Skip to content
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

Support for PumpMessageLoop, needed for webassembly #355

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added hello.wasm
Binary file not shown.
15 changes: 15 additions & 0 deletions hello.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
(module
;; Imports from JavaScript namespace
(import "console" "log" (func $log (param i32 i32))) ;; Import log function
(import "js" "mem" (memory 1)) ;; Import 1 page of memory (54kb)

;; Data section of our module
(data (i32.const 0) "Hello World from WebAssembly!")

;; Function declaration: Exported as helloWorld(), no arguments
(func (export "helloWorld")
i32.const 0 ;; pass offset 0 to log
i32.const 29 ;; pass length 29 to log (strlen of sample text)
call $log
)
)
4 changes: 4 additions & 0 deletions isolate.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ type CompileOptions struct {
Mode CompileMode
}

func (i *Isolate) PumpMessageLoop() bool {
return C.PumpMessageLoop(i.ptr) != 0
}

// CompileUnboundScript will create an UnboundScript (i.e. context-indepdent)
// using the provided source JavaScript, origin (a.k.a. filename), and options.
// If options contain a non-null CachedData, compilation of the script will use
Expand Down
83 changes: 83 additions & 0 deletions isolate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ package v8go_test
import (
"encoding/json"
"fmt"
"io/ioutil"
"math/rand"
"strings"
"testing"
"time"

v8 "rogchap.com/v8go"
)
Expand Down Expand Up @@ -199,6 +201,87 @@ func TestIsolateDispose(t *testing.T) {
}
}

func TestPumpingWebAssemblyCompilation(t *testing.T) {
t.Parallel()

iso := v8.NewIsolate()
defer iso.Dispose()
if iso.GetHeapStatistics().TotalHeapSize == 0 {
t.Error("Isolate incorrectly allocated")
}

bytes, _ := ioutil.ReadFile("hello.wasm")
global := v8.NewObjectTemplate(iso)

logs := []string{}

global.Set("log", v8.NewFunctionTemplate(iso,
func(info *v8.FunctionCallbackInfo) *v8.Value {
for _, arg := range info.Args() {
logs = append(logs, arg.String())
}
return nil
},
))

global.Set("getWasm", v8.NewFunctionTemplate(iso,
func(info *v8.FunctionCallbackInfo) *v8.Value {
out, _ := v8.NewValue(iso, bytes)
return out
},
))

global.Set("awaitAndPump", v8.NewFunctionTemplate(iso, func(info *v8.FunctionCallbackInfo) *v8.Value {
promise, err := info.Args()[0].AsPromise()
if err != nil {
panic(err)
}
for iso.PumpMessageLoop() || promise.State() == v8.Pending {
info.Context().PerformMicrotaskCheckpoint()
time.Sleep(14 * time.Millisecond)
}
return promise.Result()
}))

ctx := v8.NewContext(iso, global)
poly, _ := ioutil.ReadFile("textencoder-polyfill.js")
_, err := ctx.RunScript(string(poly), "polyfill.js")
if err != nil {
t.Fatal(err)
}
out, err := ctx.RunScript(`
let memory = new WebAssembly.Memory({initial:1});
let compiled = awaitAndPump(WebAssembly.compile(getWasm()))
let lastLog = "";
function logFromWASM(offset, length) {
let bytes = new Uint8Array(memory.buffer, offset, length);
let string = new TextDecoder('utf8').decode(bytes);
console.log(string);
lastLog = string;
};
let importObject = {
console: {
log: logFromWASM
},
js: {
mem: memory
}
};
const instance = awaitAndPump(WebAssembly.instantiate(compiled, importObject));
instance.exports.helloWorld();
lastLog;
`, "wasm.js")

if err != nil {
t.Fatal(err)
}

if out.String() != "Hello World from WebAssembly!" {
t.Fatal("unexpected output:", out.String())
}

}

func TestIsolateThrowException(t *testing.T) {
t.Parallel()
iso := v8.NewIsolate()
Expand Down
7 changes: 7 additions & 0 deletions textencoder-polyfill.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

45 changes: 45 additions & 0 deletions v8go.cc
Original file line number Diff line number Diff line change
Expand Up @@ -830,6 +830,46 @@ RtnValue NewValueString(IsolatePtr iso, const char* v, int v_length) {
return rtn;
}

ValuePtr NewValueUint8Array(IsolatePtr iso, const uint8_t *v, int len) {
ISOLATE_SCOPE_INTERNAL_CONTEXT(iso);

Local<Context> local_ctx = ctx->ptr.Get(iso);
Context::Scope context_scope(local_ctx);

std::unique_ptr<BackingStore> bs = ArrayBuffer::NewBackingStore(
static_cast<void*>(const_cast<uint8_t*>(v)), len,
[](void* data, size_t length, void *deleter_data) {
free(data);
}, nullptr);

Local<ArrayBuffer> arbuf = ArrayBuffer::New(iso, std::move(bs));

m_value* val = new m_value;
val->iso = iso;
val->ctx = ctx;
val->ptr = Persistent<Value, CopyablePersistentTraits<Value>>(
iso, Uint8Array::New(arbuf, 0, len));


return tracked_value(ctx, val);
}

uint8_t* ValueToUint8Array(ValuePtr ptr) {
LOCAL_VALUE(ptr);
MaybeLocal<Uint8Array> array = value.As<Uint8Array>();
int length = array.ToLocalChecked()->ByteLength();
uint8_t* bytes = new uint8_t[length];
memcpy(bytes, array.ToLocalChecked()->Buffer()->GetBackingStore()->Data(), length);
return bytes;
}

// Returns length of the array (number of elements, not number of bytes)
uint64_t ValueToArrayLength(ValuePtr ptr) {
LOCAL_VALUE(ptr);
MaybeLocal<TypedArray> array = value.As<TypedArray>();
return array.ToLocalChecked()->Length();
}

ValuePtr NewValueNull(IsolatePtr iso) {
ISOLATE_SCOPE_INTERNAL_CONTEXT(iso);
m_value* val = new m_value;
Expand Down Expand Up @@ -924,6 +964,11 @@ const uint32_t* ValueToArrayIndex(ValuePtr ptr) {
return idx;
}

int PumpMessageLoop(IsolatePtr iso) {
ISOLATE_SCOPE(iso);
return platform::PumpMessageLoop(default_platform.get(), iso);
}

int ValueToBoolean(ValuePtr ptr) {
LOCAL_VALUE(ptr);
return value->BooleanValue(iso);
Expand Down
6 changes: 6 additions & 0 deletions v8go.h
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ extern ValuePtr NewValueUndefined(IsolatePtr iso_ptr);
extern ValuePtr NewValueInteger(IsolatePtr iso_ptr, int32_t v);
extern ValuePtr NewValueIntegerFromUnsigned(IsolatePtr iso_ptr, uint32_t v);
extern RtnValue NewValueString(IsolatePtr iso_ptr, const char* v, int v_length);
extern ValuePtr NewValueUint8Array(IsolatePtr iso_ptr, const uint8_t* v, int len);
extern ValuePtr NewValueBoolean(IsolatePtr iso_ptr, int v);
extern ValuePtr NewValueNumber(IsolatePtr iso_ptr, double v);
extern ValuePtr NewValueBigInt(IsolatePtr iso_ptr, int64_t v);
Expand All @@ -208,7 +209,12 @@ extern RtnValue NewValueBigIntFromWords(IsolatePtr iso_ptr,
int word_count,
const uint64_t* words);
extern RtnString ValueToString(ValuePtr ptr);
extern uint8_t* ValueToUint8Array(ValuePtr ptr);
extern uint64_t ValueToArrayLength(ValuePtr ptr);
const uint32_t* ValueToArrayIndex(ValuePtr ptr);

extern int PumpMessageLoop(IsolatePtr iso_ptr);

int ValueToBoolean(ValuePtr ptr);
int32_t ValueToInt32(ValuePtr ptr);
int64_t ValueToInteger(ValuePtr ptr);
Expand Down
12 changes: 12 additions & 0 deletions value.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,15 @@ 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
// []byte -> V8::ArrayBuffer
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 All @@ -74,6 +76,10 @@ func NewValue(iso *Isolate, val interface{}) (*Value, error) {
defer C.free(unsafe.Pointer(cstr))
rtn := C.NewValueString(iso.ptr, cstr, C.int(len(v)))
return valueResult(nil, rtn)
case []uint8:
rtnVal = &Value{
ptr: C.NewValueUint8Array(iso.ptr, (*C.uchar)(C.CBytes(v)), C.int(len(v))),
}
case int32:
rtnVal = &Value{
ptr: C.NewValueInteger(iso.ptr, C.int(v)),
Expand Down Expand Up @@ -244,6 +250,12 @@ func (v *Value) String() string {
return C.GoStringN(s.data, C.int(s.length))
}

func (v *Value) Uint8Array() []uint8 {
bytes := unsafe.Pointer(C.ValueToUint8Array(v.ptr)) // allocates copy on the heap
defer C.free(bytes)
return C.GoBytes(bytes, C.int(C.ValueToArrayLength(v.ptr)))
}

// Uint32 perform the equivalent of `Number(value)` in JS and convert the result to an
// unsigned 32-bit integer by performing the steps in https://tc39.es/ecma262/#sec-touint32.
func (v *Value) Uint32() uint32 {
Expand Down
22 changes: 22 additions & 0 deletions value_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,28 @@ func TestValueIsXXX(t *testing.T) {
}
}

func TestValueNewUint8Array(t *testing.T) {
t.Parallel()
ctx := v8.NewContext()
iso := ctx.Isolate()
in := []uint8{1, 2, 3, 4, 5}
if val, err := v8.NewValue(iso, in); err != nil {
t.Fatalf("Error %v", err)
} else if !val.IsUint8Array() {
t.Errorf("Val is not []uint")
} else {
out := val.Uint8Array()
if len(out) != 5 {
t.Errorf("Expected array length 5, got %d", len(out))
}
for i := 0; i < 5; i++ {
if out[i] != in[i] {
t.Errorf("Wrong byte at %d", i)
}
}
}
}

func TestValueMarshalJSON(t *testing.T) {
t.Parallel()
iso := v8.NewIsolate()
Expand Down