From 81ad4d322fd4397278d373c16f34a08f9faa1c93 Mon Sep 17 00:00:00 2001 From: Kyle Maxwell Date: Fri, 16 Dec 2022 17:42:36 +1300 Subject: [PATCH 1/5] minimum byte array --- v8go.cc | 40 ++++++++++++++++++++++++++++++++++++++++ v8go.h | 3 +++ value.go | 12 ++++++++++++ value_test.go | 22 ++++++++++++++++++++++ 4 files changed, 77 insertions(+) diff --git a/v8go.cc b/v8go.cc index e55bf45f4..cc7f30ac1 100644 --- a/v8go.cc +++ b/v8go.cc @@ -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 local_ctx = ctx->ptr.Get(iso); + Context::Scope context_scope(local_ctx); + + std::unique_ptr bs = ArrayBuffer::NewBackingStore( + static_cast(const_cast(v)), len, + [](void* data, size_t length, void *deleter_data) { + free(data); + }, nullptr); + + Local arbuf = ArrayBuffer::New(iso, std::move(bs)); + + m_value* val = new m_value; + val->iso = iso; + val->ctx = ctx; + val->ptr = Persistent>( + iso, Uint8Array::New(arbuf, 0, len)); + + + return tracked_value(ctx, val); +} + +uint8_t* ValueToUint8Array(ValuePtr ptr) { + LOCAL_VALUE(ptr); + MaybeLocal array = value.As(); + 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 array = value.As(); + return array.ToLocalChecked()->Length(); +} + ValuePtr NewValueNull(IsolatePtr iso) { ISOLATE_SCOPE_INTERNAL_CONTEXT(iso); m_value* val = new m_value; diff --git a/v8go.h b/v8go.h index bd492c550..6daee2e87 100644 --- a/v8go.h +++ b/v8go.h @@ -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); @@ -208,6 +209,8 @@ 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); int ValueToBoolean(ValuePtr ptr); int32_t ValueToInt32(ValuePtr ptr); diff --git a/value.go b/value.go index c4ba9acf0..197646f03 100644 --- a/value.go +++ b/value.go @@ -54,6 +54,7 @@ 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 @@ -61,6 +62,7 @@ func Null(iso *Isolate) *Value { // 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 ") @@ -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)), @@ -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 { diff --git a/value_test.go b/value_test.go index d386a5893..b3ba94931 100644 --- a/value_test.go +++ b/value_test.go @@ -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() From 445adf80dcdca491023cfc9783ea097aa1e979de Mon Sep 17 00:00:00 2001 From: Kyle Maxwell Date: Sat, 17 Dec 2022 01:03:05 +0800 Subject: [PATCH 2/5] pump message loop api --- isolate.go | 4 ++++ isolate_test.go | 15 +++++++++++++++ v8go.cc | 5 +++++ v8go.h | 3 +++ 4 files changed, 27 insertions(+) diff --git a/isolate.go b/isolate.go index 661fbec05..f3e668439 100644 --- a/isolate.go +++ b/isolate.go @@ -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 diff --git a/isolate_test.go b/isolate_test.go index a6cca8e53..9c791ea4f 100644 --- a/isolate_test.go +++ b/isolate_test.go @@ -199,6 +199,21 @@ func TestIsolateDispose(t *testing.T) { } } +func TestIsolatePumpReturnsFalseWhenDone(t *testing.T) { + t.Parallel() + + iso := v8.NewIsolate() + defer iso.Dispose() + if iso.GetHeapStatistics().TotalHeapSize == 0 { + t.Error("Isolate incorrectly allocated") + } + + if iso.PumpMessageLoop() { + t.Error("pumping returned true") + } + +} + func TestIsolateThrowException(t *testing.T) { t.Parallel() iso := v8.NewIsolate() diff --git a/v8go.cc b/v8go.cc index cc7f30ac1..9c41fbad6 100644 --- a/v8go.cc +++ b/v8go.cc @@ -964,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); diff --git a/v8go.h b/v8go.h index 6daee2e87..dd7b91965 100644 --- a/v8go.h +++ b/v8go.h @@ -212,6 +212,9 @@ 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); From c208afb6e241c7603f40758350f3ee0de7bb3119 Mon Sep 17 00:00:00 2001 From: Kyle Maxwell Date: Tue, 20 Dec 2022 06:19:09 +0800 Subject: [PATCH 3/5] webassembly tests --- hello.wasm | Bin 0 -> 115 bytes hello.wat | 15 +++++++++ isolate_test.go | 73 ++++++++++++++++++++++++++++++++++++++-- textencoder-polyfill.js | 7 ++++ 4 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 hello.wasm create mode 100644 hello.wat create mode 100644 textencoder-polyfill.js diff --git a/hello.wasm b/hello.wasm new file mode 100644 index 0000000000000000000000000000000000000000..a5c95d0f58a9ea7179629604ab66848102c37db5 GIT binary patch literal 115 zcmW-Xu@1s83wl>C*{u#TwFt3E GKgTaC&l%AG literal 0 HcmV?d00001 diff --git a/hello.wat b/hello.wat new file mode 100644 index 000000000..153e55590 --- /dev/null +++ b/hello.wat @@ -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 + ) +) \ No newline at end of file diff --git a/isolate_test.go b/isolate_test.go index 9c791ea4f..acd74f87c 100644 --- a/isolate_test.go +++ b/isolate_test.go @@ -7,9 +7,11 @@ package v8go_test import ( "encoding/json" "fmt" + "io/ioutil" "math/rand" "strings" "testing" + "time" v8 "rogchap.com/v8go" ) @@ -208,8 +210,75 @@ func TestIsolatePumpReturnsFalseWhenDone(t *testing.T) { t.Error("Isolate incorrectly allocated") } - if iso.PumpMessageLoop() { - t.Error("pumping returned true") + 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(` + log("what") + 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()) } } diff --git a/textencoder-polyfill.js b/textencoder-polyfill.js new file mode 100644 index 000000000..e436f4d02 --- /dev/null +++ b/textencoder-polyfill.js @@ -0,0 +1,7 @@ +// public domain from https://github.com/anonyco/FastestSmallestTextEncoderDecoder +'use strict';(function(r){function x(){}function y(){}var z=String.fromCharCode,v={}.toString,A=v.call(r.SharedArrayBuffer),B=v(),q=r.Uint8Array,t=q||Array,w=q?ArrayBuffer:t,C=w.isView||function(g){return g&&"length"in g},D=v.call(w.prototype);w=y.prototype;var E=r.TextEncoder,a=new (q?Uint16Array:t)(32);x.prototype.decode=function(g){if(!C(g)){var l=v.call(g);if(l!==D&&l!==A&&l!==B)throw TypeError("Failed to execute 'decode' on 'TextDecoder': The provided value is not of type '(ArrayBuffer or ArrayBufferView)'"); +g=q?new t(g):g||[]}for(var f=l="",b=0,c=g.length|0,u=c-32|0,e,d,h=0,p=0,m,k=0,n=-1;b>4){case 15:m=g[b=b+1|0]&255;if(2!==m>>6||247>6?p+4|0:24,d=d+256&768;case 13:case 12:m=g[b=b+1|0]&255,h<<=6,h|=(d&31)<<6|m&63,p=p+7|0,b>6&&h>>p&&1114112>h?(d=h,h=h-65536|0,0<=h&&(n=(h>>10)+55296|0,d=(h&1023)+56320|0,31>k?(a[k]=n,k=k+1|0,n=-1): +(m=n,n=d,d=m))):(d>>=8,b=b-d-1|0,d=65533),h=p=0,e=b<=u?32:c-b|0;default:a[k]=d;continue;case 11:case 10:case 9:case 8:}a[k]=65533}f+=z(a[0],a[1],a[2],a[3],a[4],a[5],a[6],a[7],a[8],a[9],a[10],a[11],a[12],a[13],a[14],a[15],a[16],a[17],a[18],a[19],a[20],a[21],a[22],a[23],a[24],a[25],a[26],a[27],a[28],a[29],a[30],a[31]);32>k&&(f=f.slice(0,k-32|0));if(b>>31,n=-1,f.length=e)f[c]=e;else{if(2047>=e)f[c]=192|e>>6;else{a:{if(55296<=e)if(56319>=e){var d=g.charCodeAt(b=b+1|0)|0;if(56320<=d&&57343>=d){e=(e<<10)+d-56613888|0;if(65535>18;f[c=c+1|0]=128|e>>12&63;f[c=c+1|0]=128|e>>6&63;f[c=c+1|0]=128|e&63;continue}break a}e=65533}else 57343>=e&&(e=65533);!u&&b<<1>12;f[c=c+1|0]=128|e>>6&63}f[c=c+1|0]=128|e&63}}return q? +f.subarray(0,c):f.slice(0,c)};E||(r.TextDecoder=x,r.TextEncoder=y)})(""+void 0==typeof global?""+void 0==typeof self?this:self:global);//AnonyCo +//# sourceMappingURL=https://cdn.jsdelivr.net/gh/AnonyCo/FastestSmallestTextEncoderDecoder/EncoderDecoderTogether.min.js.map \ No newline at end of file From 89a08f2579ab67ed0e9e69bf0cedf702458a6b99 Mon Sep 17 00:00:00 2001 From: Kyle Maxwell Date: Tue, 20 Dec 2022 06:25:28 +0800 Subject: [PATCH 4/5] mild cleanup --- isolate_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/isolate_test.go b/isolate_test.go index acd74f87c..502b99d19 100644 --- a/isolate_test.go +++ b/isolate_test.go @@ -250,7 +250,6 @@ func TestIsolatePumpReturnsFalseWhenDone(t *testing.T) { t.Fatal(err) } out, err := ctx.RunScript(` - log("what") let memory = new WebAssembly.Memory({initial:1}); let compiled = awaitAndPump(WebAssembly.compile(getWasm())) let lastLog = ""; From fa2c837135d7f1de0d8ae3f1d74c09724e172bc9 Mon Sep 17 00:00:00 2001 From: Kyle Maxwell Date: Tue, 20 Dec 2022 07:07:39 +0800 Subject: [PATCH 5/5] fix test name --- isolate_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/isolate_test.go b/isolate_test.go index 502b99d19..8a779979d 100644 --- a/isolate_test.go +++ b/isolate_test.go @@ -201,7 +201,7 @@ func TestIsolateDispose(t *testing.T) { } } -func TestIsolatePumpReturnsFalseWhenDone(t *testing.T) { +func TestPumpingWebAssemblyCompilation(t *testing.T) { t.Parallel() iso := v8.NewIsolate()