From 5c012ebad0c97192f7dd3b21affdc293478dfe1c Mon Sep 17 00:00:00 2001 From: Brian Wang Date: Fri, 1 Dec 2023 10:43:38 +0800 Subject: [PATCH] using runtime/cgo instead map store fn/asyncfn & context (#46) --- bridge.go | 78 ++++++++++++++++--------------------------------- context.go | 42 +++++++++++++------------- quickjs_test.go | 19 ++++++++++++ 3 files changed, 64 insertions(+), 75 deletions(-) diff --git a/bridge.go b/bridge.go index 1f9e359..064e1c7 100644 --- a/bridge.go +++ b/bridge.go @@ -2,8 +2,6 @@ package quickjs import ( "runtime/cgo" - "sync" - "sync/atomic" "unsafe" ) @@ -13,80 +11,54 @@ import ( */ import "C" -type funcEntry struct { - ctx *Context - fn func(ctx *Context, this Value, args []Value) Value - asyncFn func(ctx *Context, this Value, promise Value, args []Value) Value -} - -var funcPtrLen int64 -var funcPtrLock sync.Mutex -var funcPtrStore = make(map[int64]funcEntry) -var funcPtrClassID C.JSClassID - -func init() { - C.JS_NewClassID(&funcPtrClassID) -} - -func storeFuncPtr(v funcEntry) int64 { - id := atomic.AddInt64(&funcPtrLen, 1) - 1 - funcPtrLock.Lock() - defer funcPtrLock.Unlock() - funcPtrStore[id] = v - return id -} - -func restoreFuncPtr(ptr int64) funcEntry { - funcPtrLock.Lock() - defer funcPtrLock.Unlock() - return funcPtrStore[ptr] -} - -//func freeFuncPtr(ptr int64) { -// funcPtrLock.Lock() -// defer funcPtrLock.Unlock() -// delete(funcPtrStore, ptr) -//} - //export goProxy func goProxy(ctx *C.JSContext, thisVal C.JSValueConst, argc C.int, argv *C.JSValueConst) C.JSValue { - // https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices refs := unsafe.Slice(argv, argc) // Go 1.17 and later - id := C.int64_t(0) - C.JS_ToInt64(ctx, &id, refs[0]) + // get the function + fnHandler := C.int64_t(0) + C.JS_ToInt64(ctx, &fnHandler, refs[0]) + fn := cgo.Handle(fnHandler).Value().(func(ctx *Context, this Value, args []Value) Value) - entry := restoreFuncPtr(int64(id)) + // get ctx + ctxHandler := C.int64_t(0) + C.JS_ToInt64(ctx, &ctxHandler, refs[1]) + ctxOrigin := cgo.Handle(ctxHandler).Value().(*Context) - args := make([]Value, len(refs)-1) + // refs[0] is the id, refs[1] is the ctx + args := make([]Value, len(refs)-2) for i := 0; i < len(args); i++ { - args[i].ctx = entry.ctx - args[i].ref = refs[1+i] + args[i].ctx = ctxOrigin + args[i].ref = refs[2+i] } - result := entry.fn(entry.ctx, Value{ctx: entry.ctx, ref: thisVal}, args) + result := fn(ctxOrigin, Value{ctx: ctxOrigin, ref: thisVal}, args) return result.ref } //export goAsyncProxy func goAsyncProxy(ctx *C.JSContext, thisVal C.JSValueConst, argc C.int, argv *C.JSValueConst) C.JSValue { - // https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices refs := unsafe.Slice(argv, argc) // Go 1.17 and later - id := C.int64_t(0) - C.JS_ToInt64(ctx, &id, refs[0]) + // get the function + fnHandler := C.int64_t(0) + C.JS_ToInt64(ctx, &fnHandler, refs[0]) + asyncFn := cgo.Handle(fnHandler).Value().(func(ctx *Context, this Value, promise Value, args []Value) Value) - entry := restoreFuncPtr(int64(id)) + // get ctx + ctxHandler := C.int64_t(0) + C.JS_ToInt64(ctx, &ctxHandler, refs[1]) + ctxOrigin := cgo.Handle(ctxHandler).Value().(*Context) - args := make([]Value, len(refs)-1) + args := make([]Value, len(refs)-2) for i := 0; i < len(args); i++ { - args[i].ctx = entry.ctx - args[i].ref = refs[1+i] + args[i].ctx = ctxOrigin + args[i].ref = refs[2+i] } promise := args[0] - result := entry.asyncFn(entry.ctx, Value{ctx: entry.ctx, ref: thisVal}, promise, args[1:]) + result := asyncFn(ctxOrigin, Value{ctx: ctxOrigin, ref: thisVal}, promise, args[1:]) return result.ref } diff --git a/context.go b/context.go index cc29ccf..790e9a8 100644 --- a/context.go +++ b/context.go @@ -149,12 +149,6 @@ func (ctx *Context) Set() *Set { // Function returns a js function value with given function template. func (ctx *Context) Function(fn func(ctx *Context, this Value, args []Value) Value) Value { - val := ctx.eval(`(invokeGoFunction, id) => function() { return invokeGoFunction.call(this, id, ...arguments); }`) - defer val.Free() - - funcPtr := storeFuncPtr(funcEntry{ctx: ctx, fn: fn}) - funcPtrVal := ctx.Int64(funcPtr) - if ctx.proxy == nil { ctx.proxy = &Value{ ctx: ctx, @@ -162,14 +156,30 @@ func (ctx *Context) Function(fn func(ctx *Context, this Value, args []Value) Val } } - args := []C.JSValue{ctx.proxy.ref, funcPtrVal.ref} + fnHandler := ctx.Int64(int64(cgo.NewHandle(fn))) + ctxHandler := ctx.Int64(int64(cgo.NewHandle(ctx))) + args := []C.JSValue{ctx.proxy.ref, fnHandler.ref, ctxHandler.ref} + + val := ctx.eval(`(proxy, fnHandler, ctx) => function() { return proxy.call(this, fnHandler, ctx, ...arguments); }`) + defer val.Free() return Value{ctx: ctx, ref: C.JS_Call(ctx.ref, val.ref, ctx.Null().ref, C.int(len(args)), &args[0])} } // AsyncFunction returns a js async function value with given function template. func (ctx *Context) AsyncFunction(asyncFn func(ctx *Context, this Value, promise Value, args []Value) Value) Value { - val := ctx.eval(`(invokeGoFunction, id) => async function(...arguments) { + if ctx.asyncProxy == nil { + ctx.asyncProxy = &Value{ + ctx: ctx, + ref: C.JS_NewCFunction(ctx.ref, (*C.JSCFunction)(unsafe.Pointer(C.InvokeAsyncProxy)), nil, C.int(0)), + } + } + + fnHandler := ctx.Int64(int64(cgo.NewHandle(asyncFn))) + ctxHandler := ctx.Int64(int64(cgo.NewHandle(ctx))) + args := []C.JSValue{ctx.asyncProxy.ref, fnHandler.ref, ctxHandler.ref} + + val := ctx.eval(`(proxy, fnHandler, ctx) => async function(...arguments) { let resolve, reject; const promise = new Promise((resolve_, reject_) => { resolve = resolve_; @@ -177,24 +187,12 @@ func (ctx *Context) AsyncFunction(asyncFn func(ctx *Context, this Value, promise }); promise.resolve = resolve; promise.reject = reject; - - invokeGoFunction.call(this, id, promise, ...arguments); + + proxy.call(this, fnHandler, ctx, promise, ...arguments); return await promise; }`) defer val.Free() - funcPtr := storeFuncPtr(funcEntry{ctx: ctx, asyncFn: asyncFn}) - funcPtrVal := ctx.Int64(funcPtr) - - if ctx.asyncProxy == nil { - ctx.asyncProxy = &Value{ - ctx: ctx, - ref: C.JS_NewCFunction(ctx.ref, (*C.JSCFunction)(unsafe.Pointer(C.InvokeAsyncProxy)), nil, C.int(0)), - } - } - - args := []C.JSValue{ctx.asyncProxy.ref, funcPtrVal.ref} - return Value{ctx: ctx, ref: C.JS_Call(ctx.ref, val.ref, ctx.Null().ref, C.int(len(args)), &args[0])} } diff --git a/quickjs_test.go b/quickjs_test.go index bf77d9a..e7a88b7 100644 --- a/quickjs_test.go +++ b/quickjs_test.go @@ -618,6 +618,25 @@ func TestSet(t *testing.T) { require.True(t, !test.Has(ctx.Int64(0))) } +func TestFunction(t *testing.T) { + rt := quickjs.NewRuntime() + defer rt.Close() + + ctx := rt.NewContext() + defer ctx.Close() + + ctx.Globals().Set("test", ctx.Function(func(ctx *quickjs.Context, this quickjs.Value, args []quickjs.Value) quickjs.Value { + return ctx.String("Hello " + args[0].String() + args[1].String()) + })) + + ret, _ := ctx.Eval(` + test('Go ', 'JS') + `) + defer ret.Free() + + require.EqualValues(t, "Hello Go JS", ret.String()) +} + func TestAsyncFunction(t *testing.T) { rt := quickjs.NewRuntime() defer rt.Close()