From 7ed9a8e16ca9c43a7f719df5076c7986d6f57119 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 28 Jul 2021 11:00:29 -0500 Subject: [PATCH] Bind `wasmtime_linker_define_func` (#93) This adds corresponding methods to the `Linker` type to define Go-defined host functions into the linker in a store-independent fashion. --- ci/download-wasmtime.py | 1 + engine.go | 4 +- ffi.go | 7 ++- func.go | 59 +++++++++-------------- linker.go | 69 +++++++++++++++++++++++++- linker_test.go | 54 +++++++++++++++++++++ shims.c | 27 ++++++++++- shims.h | 12 ++++- store.go | 104 +++++++++++++++++++++++++++++++++++++++- 9 files changed, 293 insertions(+), 44 deletions(-) diff --git a/ci/download-wasmtime.py b/ci/download-wasmtime.py index dfa5e0960246..52660552ac15 100644 --- a/ci/download-wasmtime.py +++ b/ci/download-wasmtime.py @@ -13,6 +13,7 @@ ['wasmtime-dev-x86_64-mingw-c-api.zip', 'windows-x86_64'], ['wasmtime-dev-x86_64-linux-c-api.tar.xz', 'linux-x86_64'], ['wasmtime-dev-x86_64-macos-c-api.tar.xz', 'macos-x86_64'], + ['wasmtime-dev-aarch64-linux-c-api.tar.xz', 'linux-aarch64'], ] try: diff --git a/engine.go b/engine.go index 47daf8667d7a..e84c2d189175 100644 --- a/engine.go +++ b/engine.go @@ -2,7 +2,9 @@ package wasmtime // #include import "C" -import "runtime" +import ( + "runtime" +) // Engine is an instance of a wasmtime engine which is used to create a `Store`. // diff --git a/ffi.go b/ffi.go index 0aa6f4cf8c58..b1f2606ae0ba 100644 --- a/ffi.go +++ b/ffi.go @@ -5,12 +5,15 @@ package wasmtime // #cgo windows CFLAGS:-DWASM_API_EXTERN= -DWASI_API_EXTERN= // #cgo windows LDFLAGS:-lwasmtime -luserenv -lole32 -lntdll -lws2_32 -lkernel32 -lbcrypt // #cgo linux,amd64 LDFLAGS:-L${SRCDIR}/build/linux-x86_64 +// #cgo linux,arm64 LDFLAGS:-L${SRCDIR}/build/linux-aarch64 // #cgo darwin,amd64 LDFLAGS:-L${SRCDIR}/build/macos-x86_64 // #cgo windows,amd64 LDFLAGS:-L${SRCDIR}/build/windows-x86_64 // #include import "C" -import "runtime" -import "unsafe" +import ( + "runtime" + "unsafe" +) // # What's up with `ptr()` methods? // diff --git a/func.go b/func.go index 7c458aff7a9a..2887d3101a6a 100644 --- a/func.go +++ b/func.go @@ -30,15 +30,6 @@ type Caller struct { ptr *C.wasmtime_caller_t } -type funcNewEntry struct { - callback func(*Caller, []Val) ([]Val, *Trap) - results []*ValType -} - -type funcWrapEntry struct { - callback reflect.Value -} - // NewFunc creates a new `Func` with the given `ty` which, when called, will call `f` // // The `ty` given is the wasm type signature of the `Func` to create. When called @@ -60,12 +51,7 @@ func NewFunc( ty *FuncType, f func(*Caller, []Val) ([]Val, *Trap), ) *Func { - data := getDataInStore(store) - idx := len(data.funcNew) - data.funcNew = append(data.funcNew, funcNewEntry{ - callback: f, - results: ty.Results(), - }) + idx := insertFuncNew(getDataInStore(store), ty, f) ret := C.wasmtime_func_t{} C.go_func_new( @@ -93,7 +79,7 @@ func goTrampolineNew( caller := &Caller{ptr: callerPtr} defer func() { caller.ptr = nil }() data := getDataInStore(caller) - entry := data.funcNew[int(env)] + entry := data.getFuncNew(int(env)) params := make([]Val, int(argsNum)) var val C.wasmtime_val_t @@ -176,8 +162,25 @@ func WrapFunc( store Storelike, f interface{}, ) *Func { - // Make sure the `interface{}` passed in was indeed a function val := reflect.ValueOf(f) + wasmTy := inferFuncType(val) + idx := insertFuncWrap(getDataInStore(store), val) + + ret := C.wasmtime_func_t{} + C.go_func_new( + store.Context(), + wasmTy.ptr(), + C.size_t(idx), + 1, // this is `WrapFunc`, not `NewFunc` + &ret, + ) + runtime.KeepAlive(store) + runtime.KeepAlive(wasmTy) + return mkFunc(ret) +} + +func inferFuncType(val reflect.Value) *FuncType { + // Make sure the `interface{}` passed in was indeed a function ty := val.Type() if ty.Kind() != reflect.Func { panic("callback provided must be a `func`") @@ -205,25 +208,7 @@ func WrapFunc( } results = append(results, typeToValType(resultTy)) } - wasmTy := NewFuncType(params, results) - - // Store our `f` callback into the list for wrapped functions, and now - // we've got everything necessary to make the wasm handle. - data := getDataInStore(store) - idx := len(data.funcWrap) - data.funcWrap = append(data.funcWrap, funcWrapEntry{callback: val}) - - ret := C.wasmtime_func_t{} - C.go_func_new( - store.Context(), - wasmTy.ptr(), - C.size_t(idx), - 1, // this is `WrapFunc`, not `NewFunc` - &ret, - ) - runtime.KeepAlive(store) - runtime.KeepAlive(wasmTy) - return mkFunc(ret) + return NewFuncType(params, results) } func typeToValType(ty reflect.Type) *ValType { @@ -264,7 +249,7 @@ func goTrampolineWrap( caller := &Caller{ptr: callerPtr} defer func() { caller.ptr = nil }() data := getDataInStore(caller) - entry := data.funcWrap[int(env)] + entry := data.getFuncWrap(int(env)) ty := entry.callback.Type() params := make([]reflect.Value, ty.NumIn()) diff --git a/linker.go b/linker.go index e19db995109d..e0ee18a6afb4 100644 --- a/linker.go +++ b/linker.go @@ -3,7 +3,10 @@ package wasmtime // #include // #include "shims.h" import "C" -import "runtime" +import ( + "reflect" + "runtime" +) // Linker implements a wasmtime Linking module, which can link instantiated modules together. // More details you can see [examples for C](https://bytecodealliance.github.io/wasmtime/examples-c-linking.html) or @@ -65,6 +68,70 @@ func (l *Linker) DefineFunc(store Storelike, module, name string, f interface{}) return l.Define(module, name, WrapFunc(store, f)) } +// FuncNew defines a function in this linker in the same style as `NewFunc` +// +// Note that this function does not require a `Storelike`, which is +// intentional. This function can be used to insert store-independent functions +// into this linker which allows this linker to be used for instantiating +// modules in multiple different stores. +// +// Returns an error if shadowing is disabled and the name is already defined. +func (l *Linker) FuncNew(module, name string, ty *FuncType, f func(*Caller, []Val) ([]Val, *Trap)) error { + idx := insertFuncNew(nil, ty, f) + err := C.go_linker_define_func( + l.ptr(), + C._GoStringPtr(module), + C._GoStringLen(module), + C._GoStringPtr(name), + C._GoStringLen(name), + ty.ptr(), + 0, // this is "new" + C.size_t(idx), + ) + runtime.KeepAlive(l) + runtime.KeepAlive(module) + runtime.KeepAlive(name) + runtime.KeepAlive(ty) + if err == nil { + return nil + } + + return mkError(err) +} + +// FuncWrap defines a function in this linker in the same style as `WrapFunc` +// +// Note that this function does not require a `Storelike`, which is +// intentional. This function can be used to insert store-independent functions +// into this linker which allows this linker to be used for instantiating +// modules in multiple different stores. +// +// Returns an error if shadowing is disabled and the name is already defined. +func (l *Linker) FuncWrap(module, name string, f interface{}) error { + val := reflect.ValueOf(f) + ty := inferFuncType(val) + idx := insertFuncWrap(nil, val) + err := C.go_linker_define_func( + l.ptr(), + C._GoStringPtr(module), + C._GoStringLen(module), + C._GoStringPtr(name), + C._GoStringLen(name), + ty.ptr(), + 1, // this is "wrap" + C.size_t(idx), + ) + runtime.KeepAlive(l) + runtime.KeepAlive(module) + runtime.KeepAlive(name) + runtime.KeepAlive(ty) + if err == nil { + return nil + } + + return mkError(err) +} + // DefineInstance defines all exports of an instance provided under the module name provided. // // Returns an error if shadowing is disabled and names are already defined. diff --git a/linker_test.go b/linker_test.go index 020f765443a8..8e9f02456b82 100644 --- a/linker_test.go +++ b/linker_test.go @@ -190,3 +190,57 @@ func TestLinkerGetOneByName(t *testing.T) { f = linker.Get(store, "foo", "baz") f.Func().Call(store) } + +func TestLinkerFuncs(t *testing.T) { + engine := NewEngine() + linker := NewLinker(engine) + var called int + err := linker.FuncWrap("foo", "bar", func() { + called += 1 + }) + check(err) + + wasm, err := Wat2Wasm(` + (module + (import "foo" "bar" (func)) + (start 0) + ) + `) + check(err) + + module, err := NewModule(engine, wasm) + check(err) + + _, err = linker.Instantiate(NewStore(engine), module) + check(err) + if called != 1 { + panic("expected a call") + } + + _, err = linker.Instantiate(NewStore(engine), module) + check(err) + if called != 2 { + panic("expected a call") + } + + cb := func(caller *Caller, args []Val) ([]Val, *Trap) { + called += 2 + return []Val{}, nil + } + ty := NewFuncType([]*ValType{}, []*ValType{}) + linker.AllowShadowing(true) + err = linker.FuncNew("foo", "bar", ty, cb) + check(err) + + _, err = linker.Instantiate(NewStore(engine), module) + check(err) + if called != 4 { + panic("expected a call") + } + + _, err = linker.Instantiate(NewStore(engine), module) + check(err) + if called != 6 { + panic("expected a call") + } +} diff --git a/shims.c b/shims.c index f121c3f85c53..6940f95fe3de 100644 --- a/shims.c +++ b/shims.c @@ -31,13 +31,38 @@ static wasm_trap_t* wrap_trampoline( results, nresults); } -void go_func_new(wasmtime_context_t *store, wasm_functype_t *ty, size_t env, int wrap, wasmtime_func_t *ret) { +void go_func_new( + wasmtime_context_t *store, + wasm_functype_t *ty, + size_t env, + int wrap, + wasmtime_func_t *ret +) { wasmtime_func_callback_t callback = trampoline; if (wrap) callback = wrap_trampoline; return wasmtime_func_new(store, ty, callback, (void*) env, NULL, ret); } +wasmtime_error_t *go_linker_define_func( + wasmtime_linker_t *linker, + const char *module, + size_t module_len, + const char *name, + size_t name_len, + const wasm_functype_t *ty, + int wrap, + size_t env +) { + wasmtime_func_callback_t cb = trampoline; + void(*finalizer)(void*) = goFinalizeFuncNew; + if (wrap) { + cb = wrap_trampoline; + finalizer = goFinalizeFuncWrap; + } + return wasmtime_linker_define_func(linker, module, module_len, name, name_len, ty, cb, (void*) env, finalizer); +} + wasmtime_externref_t *go_externref_new(size_t env) { return wasmtime_externref_new((void*) env, goFinalizeExternref); } diff --git a/shims.h b/shims.h index 3b58a8e28ee0..b9a91d19f8b4 100644 --- a/shims.h +++ b/shims.h @@ -2,7 +2,17 @@ #include wasmtime_store_t *go_store_new(wasm_engine_t *engine, size_t env); -void go_func_new(wasmtime_context_t *context, wasm_functype_t *ty, size_t env, int wrap, wasmtime_func_t *ret); +void go_func_new(wasmtime_context_t *context, wasm_functype_t *ty, size_t env, int wrap, wasmtime_func_t *ret); +wasmtime_error_t *go_linker_define_func( + wasmtime_linker_t *linker, + const char *module, + size_t module_len, + const char *name, + size_t name_len, + const wasm_functype_t *ty, + int wrap, + size_t env +); wasmtime_externref_t *go_externref_new(size_t env); #define EACH_UNION_ACCESSOR(name) \ diff --git a/store.go b/store.go index 7b28177060e3..69fd0c1da90e 100644 --- a/store.go +++ b/store.go @@ -5,6 +5,7 @@ package wasmtime import "C" import ( "errors" + "reflect" "runtime" "sync" "unsafe" @@ -39,18 +40,28 @@ var gStoreSlab slab // information through invocations as well as store Go closures that have been // added to the store. type storeData struct { + engine *Engine funcNew []funcNewEntry funcWrap []funcWrapEntry lastPanic interface{} } +type funcNewEntry struct { + callback func(*Caller, []Val) ([]Val, *Trap) + results []*ValType +} + +type funcWrapEntry struct { + callback reflect.Value +} + // NewStore creates a new `Store` from the configuration provided in `engine` func NewStore(engine *Engine) *Store { // Allocate an index for this store and allocate some internal data to go with // the store. gStoreLock.Lock() idx := gStoreSlab.allocate() - gStoreMap[idx] = &storeData{} + gStoreMap[idx] = &storeData{engine: engine} gStoreLock.Unlock() ptr := C.go_store_new(engine.ptr(), C.size_t(idx)) @@ -162,3 +173,94 @@ func (i *InterruptHandle) ptr() *C.wasmtime_interrupt_handle_t { maybeGC() return ret } + +var gEngineFuncLock sync.Mutex +var gEngineFuncNew = make(map[int]*funcNewEntry) +var gEngineFuncNewSlab slab +var gEngineFuncWrap = make(map[int]*funcWrapEntry) +var gEngineFuncWrapSlab slab + +func insertFuncNew(data *storeData, ty *FuncType, callback func(*Caller, []Val) ([]Val, *Trap)) int { + var idx int + entry := funcNewEntry{ + callback: callback, + results: ty.Results(), + } + if data == nil { + gEngineFuncLock.Lock() + defer gEngineFuncLock.Unlock() + idx = gEngineFuncNewSlab.allocate() + gEngineFuncNew[idx] = &entry + idx = (idx << 1) | 0 + } else { + idx = len(data.funcNew) + data.funcNew = append(data.funcNew, entry) + idx = (idx << 1) | 1 + } + return idx +} + +func (data *storeData) getFuncNew(idx int) *funcNewEntry { + if idx&1 == 0 { + gEngineFuncLock.Lock() + defer gEngineFuncLock.Unlock() + return gEngineFuncNew[idx>>1] + } else { + return &data.funcNew[idx>>1] + } +} + +func insertFuncWrap(data *storeData, callback reflect.Value) int { + var idx int + entry := funcWrapEntry{callback} + if data == nil { + gEngineFuncLock.Lock() + defer gEngineFuncLock.Unlock() + idx = gEngineFuncWrapSlab.allocate() + gEngineFuncWrap[idx] = &entry + idx = (idx << 1) | 0 + } else { + idx = len(data.funcWrap) + data.funcWrap = append(data.funcWrap, entry) + idx = (idx << 1) | 1 + } + return idx + +} + +func (data *storeData) getFuncWrap(idx int) *funcWrapEntry { + if idx&1 == 0 { + gEngineFuncLock.Lock() + defer gEngineFuncLock.Unlock() + return gEngineFuncWrap[idx>>1] + } else { + return &data.funcWrap[idx>>1] + } +} + +//export goFinalizeFuncNew +func goFinalizeFuncNew(env unsafe.Pointer) { + idx := int(uintptr(env)) + if idx&1 != 0 { + panic("shouldn't finalize a store-local index") + } + idx = idx >> 1 + gEngineFuncLock.Lock() + defer gEngineFuncLock.Unlock() + delete(gEngineFuncNew, idx) + gEngineFuncNewSlab.deallocate(idx) + +} + +//export goFinalizeFuncWrap +func goFinalizeFuncWrap(env unsafe.Pointer) { + idx := int(uintptr(env)) + if idx&1 != 0 { + panic("shouldn't finalize a store-local index") + } + idx = idx >> 1 + gEngineFuncLock.Lock() + defer gEngineFuncLock.Unlock() + delete(gEngineFuncWrap, idx) + gEngineFuncWrapSlab.deallocate(idx) +}