Skip to content

Commit

Permalink
using runtime/cgo instead map store fn/asyncfn & context (#46)
Browse files Browse the repository at this point in the history
  • Loading branch information
buke authored Dec 1, 2023
1 parent d495b02 commit 5c012eb
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 75 deletions.
78 changes: 25 additions & 53 deletions bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package quickjs

import (
"runtime/cgo"
"sync"
"sync/atomic"
"unsafe"
)

Expand All @@ -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

}
Expand Down
42 changes: 20 additions & 22 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,52 +149,50 @@ 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,
ref: C.JS_NewCFunction(ctx.ref, (*C.JSCFunction)(unsafe.Pointer(C.InvokeProxy)), nil, C.int(0)),
}
}

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_;
reject = reject_;
});
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])}
}

Expand Down
19 changes: 19 additions & 0 deletions quickjs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down

0 comments on commit 5c012eb

Please sign in to comment.