Skip to content

Commit

Permalink
Bind wasmtime_linker_define_func (bytecodealliance#93)
Browse files Browse the repository at this point in the history
This adds corresponding methods to the `Linker` type to define
Go-defined host functions into the linker in a store-independent
fashion.
  • Loading branch information
alexcrichton authored Jul 28, 2021
1 parent e07dba8 commit 7ed9a8e
Show file tree
Hide file tree
Showing 9 changed files with 293 additions and 44 deletions.
1 change: 1 addition & 0 deletions ci/download-wasmtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 3 additions & 1 deletion engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package wasmtime

// #include <wasm.h>
import "C"
import "runtime"
import (
"runtime"
)

// Engine is an instance of a wasmtime engine which is used to create a `Store`.
//
Expand Down
7 changes: 5 additions & 2 deletions ffi.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 <wasm.h>
import "C"
import "runtime"
import "unsafe"
import (
"runtime"
"unsafe"
)

// # What's up with `ptr()` methods?
//
Expand Down
59 changes: 22 additions & 37 deletions func.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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`")
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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())
Expand Down
69 changes: 68 additions & 1 deletion linker.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ package wasmtime
// #include <wasmtime.h>
// #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
Expand Down Expand Up @@ -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.
Expand Down
54 changes: 54 additions & 0 deletions linker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
}
27 changes: 26 additions & 1 deletion shims.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
12 changes: 11 additions & 1 deletion shims.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,17 @@
#include <wasmtime.h>

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) \
Expand Down
Loading

0 comments on commit 7ed9a8e

Please # to comment.