compiler: adds support for FunctionListeners (#869)
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
This commit is contained in:
@@ -309,11 +309,11 @@ func (b *hostModuleBuilder) Compile(ctx context.Context) (CompiledModule, error)
|
||||
}
|
||||
|
||||
c := &compiledModule{module: module, compiledEngine: b.r.store.Engine}
|
||||
if c.listeners, err = buildListeners(ctx, b.r, module); err != nil {
|
||||
if c.listeners, err = buildListeners(ctx, module); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = b.r.store.Engine.CompileModule(ctx, module); err != nil {
|
||||
if err = b.r.store.Engine.CompileModule(ctx, module, c.listeners); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
@@ -607,7 +607,7 @@ func Test_compiledModule_Close(t *testing.T) {
|
||||
var cs []*compiledModule
|
||||
for i := 0; i < 10; i++ {
|
||||
m := &wasm.Module{}
|
||||
err := e.CompileModule(ctx, m)
|
||||
err := e.CompileModule(ctx, m, nil)
|
||||
require.NoError(t, err)
|
||||
cs = append(cs, &compiledModule{module: m, compiledEngine: e})
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
// Package experimental_test includes examples for experimental features. When these complete, they'll end up as real
|
||||
// examples in the /examples directory.
|
||||
package experimental_test
|
||||
|
||||
import "context"
|
||||
|
||||
// testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors.
|
||||
var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary")
|
||||
@@ -8,8 +8,6 @@ import (
|
||||
|
||||
// FunctionListenerFactoryKey is a context.Context Value key. Its associated value should be a FunctionListenerFactory.
|
||||
//
|
||||
// Note: This is interpreter-only for now!
|
||||
//
|
||||
// See https://github.com/tetratelabs/wazero/issues/451
|
||||
type FunctionListenerFactoryKey struct{}
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ func Example_customListenerFactory() {
|
||||
// Set context to one that has an experimental listener
|
||||
ctx := context.WithValue(context.Background(), FunctionListenerFactoryKey{}, u)
|
||||
|
||||
r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfigInterpreter())
|
||||
r := wazero.NewRuntime(ctx)
|
||||
defer r.Close(ctx) // This closes everything this Runtime created.
|
||||
|
||||
wasi_snapshot_preview1.MustInstantiate(ctx, r)
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
. "github.com/tetratelabs/wazero/experimental"
|
||||
"github.com/tetratelabs/wazero/internal/platform"
|
||||
"github.com/tetratelabs/wazero/internal/testing/require"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
"github.com/tetratelabs/wazero/internal/wasm/binary"
|
||||
@@ -18,17 +17,27 @@ import (
|
||||
var _ FunctionListenerFactory = &recorder{}
|
||||
|
||||
type recorder struct {
|
||||
m map[string]struct{}
|
||||
m map[string]struct{}
|
||||
beforeNames, afterNames []string
|
||||
}
|
||||
|
||||
func (r *recorder) Before(ctx context.Context, def api.FunctionDefinition, _ []uint64) context.Context {
|
||||
r.beforeNames = append(r.beforeNames, def.DebugName())
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (r *recorder) After(_ context.Context, def api.FunctionDefinition, _ error, _ []uint64) {
|
||||
r.afterNames = append(r.afterNames, def.DebugName())
|
||||
}
|
||||
|
||||
func (r *recorder) NewListener(definition api.FunctionDefinition) FunctionListener {
|
||||
r.m[definition.Name()] = struct{}{}
|
||||
return nil
|
||||
return r
|
||||
}
|
||||
|
||||
func TestFunctionListenerFactory(t *testing.T) {
|
||||
// Set context to one that has an experimental listener
|
||||
factory := &recorder{map[string]struct{}{}}
|
||||
factory := &recorder{m: map[string]struct{}{}}
|
||||
ctx := context.WithValue(context.Background(), FunctionListenerFactoryKey{}, factory)
|
||||
|
||||
// Define a module with two functions
|
||||
@@ -37,37 +46,57 @@ func TestFunctionListenerFactory(t *testing.T) {
|
||||
ImportSection: []*wasm.Import{{}},
|
||||
FunctionSection: []wasm.Index{0, 0},
|
||||
CodeSection: []*wasm.Code{
|
||||
{Body: []byte{wasm.OpcodeEnd}}, {Body: []byte{wasm.OpcodeEnd}},
|
||||
// fn1
|
||||
{Body: []byte{
|
||||
// call fn2 twice
|
||||
wasm.OpcodeCall, 2,
|
||||
wasm.OpcodeCall, 2,
|
||||
wasm.OpcodeEnd,
|
||||
}},
|
||||
// fn2
|
||||
{Body: []byte{wasm.OpcodeEnd}},
|
||||
},
|
||||
ExportSection: []*wasm.Export{{Name: "fn1", Type: wasm.ExternTypeFunc, Index: 1}},
|
||||
NameSection: &wasm.NameSection{
|
||||
ModuleName: "test",
|
||||
FunctionNames: wasm.NameMap{
|
||||
{Index: 0, Name: "import"}, // should skip
|
||||
{Index: 0, Name: "import"}, // should skip for building listeners.
|
||||
{Index: 1, Name: "fn1"},
|
||||
{Index: 2, Name: "fn2"},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if platform.CompilerSupported() {
|
||||
t.Run("fails on compile if compiler", func(t *testing.T) {
|
||||
r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfigCompiler())
|
||||
defer r.Close(testCtx) // This closes everything this Runtime created.
|
||||
_, err := r.CompileModule(ctx, bin)
|
||||
require.EqualError(t, err,
|
||||
"context includes a FunctionListenerFactoryKey, which is only supported in the interpreter")
|
||||
})
|
||||
}
|
||||
|
||||
r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfigInterpreter())
|
||||
r := wazero.NewRuntime(ctx)
|
||||
defer r.Close(ctx) // This closes everything this Runtime created.
|
||||
|
||||
_, err := r.CompileModule(ctx, bin)
|
||||
_, err := r.NewHostModuleBuilder("").NewFunctionBuilder().WithFunc(func() {}).Export("").Instantiate(ctx, r)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Ensure the imported function was converted to a listener.
|
||||
require.Equal(t, map[string]struct{}{"": {}}, factory.m)
|
||||
|
||||
compiled, err := r.CompileModule(ctx, bin)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Ensure each function was converted to a listener eagerly
|
||||
require.Equal(t, map[string]struct{}{
|
||||
"": {},
|
||||
"fn1": {},
|
||||
"fn2": {},
|
||||
}, factory.m)
|
||||
|
||||
// Ensures that FunctionListener is a compile-time option, so passing context.Background here
|
||||
// is ok to use listeners at runtime.
|
||||
m, err := r.InstantiateModule(context.Background(), compiled, wazero.NewModuleConfig())
|
||||
require.NoError(t, err)
|
||||
|
||||
fn1 := m.ExportedFunction("fn1")
|
||||
require.NotNil(t, fn1)
|
||||
|
||||
_, err = fn1.Call(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, []string{"test.fn1", "test.fn2", "test.fn2"}, factory.beforeNames)
|
||||
require.Equal(t, []string{"test.fn2", "test.fn2", "test.fn1"}, factory.afterNames) // after is in the reverse order.
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ func Example_newHostLoggingListenerFactory() {
|
||||
// Set context to one that has an experimental listener
|
||||
ctx := context.WithValue(context.Background(), experimental.FunctionListenerFactoryKey{}, logging.NewHostLoggingListenerFactory(os.Stdout))
|
||||
|
||||
r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfigInterpreter())
|
||||
r := wazero.NewRuntime(ctx)
|
||||
defer r.Close(ctx) // This closes everything this Runtime created.
|
||||
|
||||
wasi_snapshot_preview1.MustInstantiate(ctx, r)
|
||||
@@ -63,7 +63,7 @@ func Example_newLoggingListenerFactory() {
|
||||
// Set context to one that has an experimental listener
|
||||
ctx := context.WithValue(context.Background(), experimental.FunctionListenerFactoryKey{}, logging.NewLoggingListenerFactory(os.Stdout))
|
||||
|
||||
r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfigInterpreter())
|
||||
r := wazero.NewRuntime(ctx)
|
||||
defer r.Close(ctx) // This closes everything this Runtime created.
|
||||
|
||||
wasi_snapshot_preview1.MustInstantiate(ctx, r)
|
||||
|
||||
@@ -422,7 +422,7 @@ func requireProxyModule(t *testing.T, fns FunctionExporter, config wazero.Module
|
||||
// Set context to one that has an experimental listener
|
||||
ctx := context.WithValue(testCtx, FunctionListenerFactoryKey{}, logging.NewLoggingListenerFactory(&log))
|
||||
|
||||
r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfigInterpreter())
|
||||
r := wazero.NewRuntime(ctx)
|
||||
|
||||
builder := r.NewHostModuleBuilder("env")
|
||||
fns.ExportFunctions(builder)
|
||||
|
||||
@@ -36,7 +36,7 @@ func TestGrow(t *testing.T) {
|
||||
// Set context to one that has an experimental listener
|
||||
ctx := context.WithValue(testCtx, FunctionListenerFactoryKey{}, logging.NewLoggingListenerFactory(&log))
|
||||
|
||||
r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfigInterpreter())
|
||||
r := wazero.NewRuntime(ctx)
|
||||
defer r.Close(ctx)
|
||||
|
||||
wasi_snapshot_preview1.MustInstantiate(ctx, r)
|
||||
@@ -59,7 +59,7 @@ func TestInvoke(t *testing.T) {
|
||||
// Set context to one that has an experimental listener
|
||||
ctx := context.WithValue(testCtx, FunctionListenerFactoryKey{}, logging.NewLoggingListenerFactory(&log))
|
||||
|
||||
r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfigInterpreter())
|
||||
r := wazero.NewRuntime(ctx)
|
||||
defer r.Close(ctx)
|
||||
|
||||
_, err := Instantiate(ctx, r)
|
||||
|
||||
@@ -87,7 +87,7 @@ func requireProxyModule(t *testing.T, config wazero.ModuleConfig) (api.Module, a
|
||||
// Set context to one that has an experimental listener
|
||||
ctx := context.WithValue(testCtx, FunctionListenerFactoryKey{}, logging.NewLoggingListenerFactory(&log))
|
||||
|
||||
r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfigInterpreter())
|
||||
r := wazero.NewRuntime(ctx)
|
||||
|
||||
wasiModuleCompiled, err := (&builder{r}).hostModuleBuilder().Compile(ctx)
|
||||
require.NoError(t, err)
|
||||
@@ -115,7 +115,7 @@ func requireErrnoNosys(t *testing.T, funcName string, params ...uint64) string {
|
||||
// Set context to one that has an experimental listener
|
||||
ctx := context.WithValue(testCtx, FunctionListenerFactoryKey{}, logging.NewHostLoggingListenerFactory(&log))
|
||||
|
||||
r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfigInterpreter())
|
||||
r := wazero.NewRuntime(ctx)
|
||||
defer r.Close(ctx)
|
||||
|
||||
// Instantiate the wasi module.
|
||||
|
||||
@@ -22,6 +22,6 @@ func newArchContextImpl() (ret archContext) { return }
|
||||
|
||||
// newCompiler returns a new compiler interface which can be used to compile the given function instance.
|
||||
// Note: ir param can be nil for host functions.
|
||||
func newCompiler(ir *wazeroir.CompilationResult) (compiler, error) {
|
||||
return newAmd64Compiler(ir)
|
||||
func newCompiler(ir *wazeroir.CompilationResult, withListener bool) (compiler, error) {
|
||||
return newAmd64Compiler(ir, withListener)
|
||||
}
|
||||
|
||||
@@ -47,6 +47,6 @@ func newArchContextImpl() archContext {
|
||||
|
||||
// newCompiler returns a new compiler interface which can be used to compile the given function instance.
|
||||
// Note: ir param can be nil for host functions.
|
||||
func newCompiler(ir *wazeroir.CompilationResult) (compiler, error) {
|
||||
return newArm64Compiler(ir)
|
||||
func newCompiler(ir *wazeroir.CompilationResult, withListener bool) (compiler, error) {
|
||||
return newArm64Compiler(ir, withListener)
|
||||
}
|
||||
|
||||
@@ -13,6 +13,6 @@ import (
|
||||
type archContext struct{}
|
||||
|
||||
// newCompiler returns an unsupported error.
|
||||
func newCompiler(ir *wazeroir.CompilationResult) (compiler, error) {
|
||||
func newCompiler(ir *wazeroir.CompilationResult, _ bool) (compiler, error) {
|
||||
return nil, fmt.Errorf("unsupported GOARCH %s", runtime.GOARCH)
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ func BenchmarkCompiler_compileMemoryCopy(b *testing.B) {
|
||||
testMem[i] = byte(i)
|
||||
}
|
||||
|
||||
compiler, _ := newCompiler(&wazeroir.CompilationResult{HasMemory: true, Signature: &wasm.FunctionType{}})
|
||||
compiler, _ := newCompiler(&wazeroir.CompilationResult{HasMemory: true, Signature: &wasm.FunctionType{}}, false)
|
||||
err := compiler.compilePreamble()
|
||||
requireNoError(b, err)
|
||||
|
||||
@@ -77,7 +77,7 @@ func BenchmarkCompiler_compileMemoryFill(b *testing.B) {
|
||||
testMem[i] = byte(i)
|
||||
}
|
||||
|
||||
compiler, _ := newCompiler(&wazeroir.CompilationResult{HasMemory: true, Signature: &wasm.FunctionType{}})
|
||||
compiler, _ := newCompiler(&wazeroir.CompilationResult{HasMemory: true, Signature: &wasm.FunctionType{}}, false)
|
||||
err := compiler.compilePreamble()
|
||||
requireNoError(b, err)
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
func Test_compileDropRange(t *testing.T) {
|
||||
t.Run("nil range", func(t *testing.T) {
|
||||
c, err := newCompiler(nil) // we don't use ir in compileDropRange, so passing nil is fine.
|
||||
c, err := newCompiler(nil, false) // we don't use ir in compileDropRange, so passing nil is fine.
|
||||
require.NoError(t, err)
|
||||
|
||||
err = compileDropRange(c, nil)
|
||||
@@ -19,7 +19,7 @@ func Test_compileDropRange(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("start at the top", func(t *testing.T) {
|
||||
c, err := newCompiler(nil) // we don't use ir in compileDropRange, so passing nil is fine.
|
||||
c, err := newCompiler(nil, false) // we don't use ir in compileDropRange, so passing nil is fine.
|
||||
require.NoError(t, err)
|
||||
|
||||
// Use up all unreserved registers.
|
||||
@@ -94,7 +94,7 @@ func TestRuntimeValueLocationStack_dropsLivesForInclusiveRange(t *testing.T) {
|
||||
func Test_getTemporariesForStackedLiveValues(t *testing.T) {
|
||||
t.Run("no stacked values", func(t *testing.T) {
|
||||
liveValues := []*runtimeValueLocation{{register: 1}, {register: 2}}
|
||||
c, err := newCompiler(nil) // we don't use ir in compileDropRange, so passing nil is fine.
|
||||
c, err := newCompiler(nil, false) // we don't use ir in compileDropRange, so passing nil is fine.
|
||||
require.NoError(t, err)
|
||||
|
||||
gpTmp, vecTmp, err := getTemporariesForStackedLiveValues(c, liveValues)
|
||||
@@ -113,7 +113,7 @@ func Test_getTemporariesForStackedLiveValues(t *testing.T) {
|
||||
{valueType: runtimeValueTypeI32},
|
||||
{valueType: runtimeValueTypeI64},
|
||||
}
|
||||
c, err := newCompiler(nil) // we don't use ir in compileDropRange, so passing nil is fine.
|
||||
c, err := newCompiler(nil, false) // we don't use ir in compileDropRange, so passing nil is fine.
|
||||
require.NoError(t, err)
|
||||
|
||||
if !freeRegisterExists {
|
||||
@@ -154,7 +154,7 @@ func Test_getTemporariesForStackedLiveValues(t *testing.T) {
|
||||
{valueType: runtimeValueTypeV128Lo},
|
||||
{valueType: runtimeValueTypeV128Hi},
|
||||
}
|
||||
c, err := newCompiler(nil) // we don't use ir in compileDropRange, so passing nil is fine.
|
||||
c, err := newCompiler(nil, false) // we don't use ir in compileDropRange, so passing nil is fine.
|
||||
require.NoError(t, err)
|
||||
|
||||
if !freeRegisterExists {
|
||||
@@ -189,7 +189,7 @@ func Test_migrateLiveValue(t *testing.T) {
|
||||
})
|
||||
t.Run("already on register", func(t *testing.T) {
|
||||
// This case, we don't use tmp registers.
|
||||
c, err := newCompiler(nil) // we don't use ir in compileDropRange, so passing nil is fine.
|
||||
c, err := newCompiler(nil, false) // we don't use ir in compileDropRange, so passing nil is fine.
|
||||
require.NoError(t, err)
|
||||
|
||||
// Push the dummy values.
|
||||
|
||||
@@ -226,7 +226,7 @@ func (j *compilerEnv) exec(codeSegment []byte) {
|
||||
}
|
||||
|
||||
// newTestCompiler allows us to test a different architecture than the current one.
|
||||
type newTestCompiler func(ir *wazeroir.CompilationResult) (compiler, error)
|
||||
type newTestCompiler func(ir *wazeroir.CompilationResult, _ bool) (compiler, error)
|
||||
|
||||
func (j *compilerEnv) requireNewCompiler(t *testing.T, fn newTestCompiler, ir *wazeroir.CompilationResult) compilerImpl {
|
||||
requireSupportedOSArch(t)
|
||||
@@ -237,7 +237,7 @@ func (j *compilerEnv) requireNewCompiler(t *testing.T, fn newTestCompiler, ir *w
|
||||
Signature: &wasm.FunctionType{},
|
||||
}
|
||||
}
|
||||
c, err := fn(ir)
|
||||
c, err := fn(ir, false)
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/experimental"
|
||||
"github.com/tetratelabs/wazero/internal/compilationcache"
|
||||
"github.com/tetratelabs/wazero/internal/platform"
|
||||
"github.com/tetratelabs/wazero/internal/version"
|
||||
@@ -118,6 +119,20 @@ type (
|
||||
|
||||
// initialFn is the initial function for this call engine.
|
||||
initialFn *function
|
||||
|
||||
// ctx is the context.Context passed to all the host function calls.
|
||||
// This is modified when there's a function listener call, otherwise it's always the context.Context
|
||||
// passed to the Call API.
|
||||
ctx context.Context
|
||||
// contextStack is a stack of contexts which is pushed and popped by function listeners.
|
||||
// This is used and modified when there are function listeners.
|
||||
contextStack *contextStack
|
||||
}
|
||||
|
||||
// contextStack is a stack of context.Context.
|
||||
contextStack struct {
|
||||
self context.Context
|
||||
prev *contextStack
|
||||
}
|
||||
|
||||
// moduleContext holds the per-function call specific module information.
|
||||
@@ -441,7 +456,7 @@ func (e *engine) DeleteCompiledModule(module *wasm.Module) {
|
||||
}
|
||||
|
||||
// CompileModule implements the same method as documented on wasm.Engine.
|
||||
func (e *engine) CompileModule(ctx context.Context, module *wasm.Module) error {
|
||||
func (e *engine) CompileModule(ctx context.Context, module *wasm.Module, listeners []experimental.FunctionListener) error {
|
||||
if _, ok, err := e.getCodes(module); ok { // cache hit!
|
||||
return nil
|
||||
} else if err != nil {
|
||||
@@ -455,15 +470,18 @@ func (e *engine) CompileModule(ctx context.Context, module *wasm.Module) error {
|
||||
|
||||
importedFuncs := module.ImportFuncCount()
|
||||
funcs := make([]*code, len(module.FunctionSection))
|
||||
ln := len(listeners)
|
||||
for i, ir := range irs {
|
||||
withListener := i < ln && listeners[i] != nil
|
||||
|
||||
funcIndex := wasm.Index(i)
|
||||
var compiled *code
|
||||
if ir.GoFunc != nil {
|
||||
if compiled, err = compileGoDefinedHostFunction(ir); err != nil {
|
||||
if compiled, err = compileGoDefinedHostFunction(ir, withListener); err != nil {
|
||||
def := module.FunctionDefinitionSection[funcIndex+importedFuncs]
|
||||
return fmt.Errorf("error compiling host go func[%s]: %w", def.DebugName(), err)
|
||||
}
|
||||
} else if compiled, err = compileWasmFunction(e.enabledFeatures, ir); err != nil {
|
||||
} else if compiled, err = compileWasmFunction(e.enabledFeatures, ir, withListener); err != nil {
|
||||
def := module.FunctionDefinitionSection[funcIndex+importedFuncs]
|
||||
return fmt.Errorf("error compiling wasm func[%s]: %w", def.DebugName(), err)
|
||||
}
|
||||
@@ -805,6 +823,8 @@ const (
|
||||
builtinFunctionIndexMemoryGrow wasm.Index = iota
|
||||
builtinFunctionIndexGrowStack
|
||||
builtinFunctionIndexTableGrow
|
||||
builtinFunctionIndexFunctionListenerBefore
|
||||
builtinFunctionIndexFunctionListenerAfter
|
||||
// builtinFunctionIndexBreakPoint is internal (only for wazero developers). Disabled by default.
|
||||
builtinFunctionIndexBreakPoint
|
||||
)
|
||||
@@ -812,6 +832,7 @@ const (
|
||||
func (ce *callEngine) execWasmFunction(ctx context.Context, callCtx *wasm.CallContext) {
|
||||
codeAddr := ce.initialFn.codeInitialAddress
|
||||
modAddr := ce.initialFn.moduleInstanceAddress
|
||||
ce.ctx = ctx
|
||||
|
||||
entry:
|
||||
{
|
||||
@@ -837,9 +858,9 @@ entry:
|
||||
fn := calleeHostFunction.source.GoFunc
|
||||
switch fn := fn.(type) {
|
||||
case api.GoModuleFunction:
|
||||
fn.Call(ctx, callCtx.WithMemory(ce.memoryInstance), stack)
|
||||
fn.Call(ce.ctx, callCtx.WithMemory(ce.memoryInstance), stack)
|
||||
case api.GoFunction:
|
||||
fn.Call(ctx, stack)
|
||||
fn.Call(ce.ctx, stack)
|
||||
}
|
||||
|
||||
codeAddr, modAddr = ce.returnAddress, ce.moduleInstanceAddress
|
||||
@@ -848,11 +869,15 @@ entry:
|
||||
caller := ce.moduleContext.fn
|
||||
switch ce.exitContext.builtinFunctionCallIndex {
|
||||
case builtinFunctionIndexMemoryGrow:
|
||||
ce.builtinFunctionMemoryGrow(ctx, caller.source.Module.Memory)
|
||||
ce.builtinFunctionMemoryGrow(ce.ctx, caller.source.Module.Memory)
|
||||
case builtinFunctionIndexGrowStack:
|
||||
ce.builtinFunctionGrowStack(caller.stackPointerCeil)
|
||||
case builtinFunctionIndexTableGrow:
|
||||
ce.builtinFunctionTableGrow(ctx, caller.source.Module.Tables)
|
||||
ce.builtinFunctionTableGrow(ce.ctx, caller.source.Module.Tables)
|
||||
case builtinFunctionIndexFunctionListenerBefore:
|
||||
ce.builtinFunctionFunctionListenerBefore(ce.ctx, caller.source)
|
||||
case builtinFunctionIndexFunctionListenerAfter:
|
||||
ce.builtinFunctionFunctionListenerAfter(ce.ctx, caller.source)
|
||||
}
|
||||
if false {
|
||||
if ce.exitContext.builtinFunctionCallIndex == builtinFunctionIndexBreakPoint {
|
||||
@@ -917,8 +942,23 @@ func (ce *callEngine) builtinFunctionTableGrow(ctx context.Context, tables []*wa
|
||||
ce.pushValue(uint64(res))
|
||||
}
|
||||
|
||||
func compileGoDefinedHostFunction(ir *wazeroir.CompilationResult) (*code, error) {
|
||||
compiler, err := newCompiler(ir)
|
||||
func (ce *callEngine) builtinFunctionFunctionListenerBefore(ctx context.Context, fn *wasm.FunctionInstance) {
|
||||
base := int(ce.stackBasePointerInBytes >> 3)
|
||||
listerCtx := fn.Listener.Before(ctx, fn.Definition, ce.stack[base:base+fn.Type.ParamNumInUint64])
|
||||
prevStackTop := ce.contextStack
|
||||
ce.contextStack = &contextStack{self: ctx, prev: prevStackTop}
|
||||
ce.ctx = listerCtx
|
||||
}
|
||||
|
||||
func (ce *callEngine) builtinFunctionFunctionListenerAfter(ctx context.Context, fn *wasm.FunctionInstance) {
|
||||
base := int(ce.stackBasePointerInBytes >> 3)
|
||||
fn.Listener.After(ctx, fn.Definition, nil, ce.stack[base:base+fn.Type.ResultNumInUint64])
|
||||
ce.ctx = ce.contextStack.self
|
||||
ce.contextStack = ce.contextStack.prev
|
||||
}
|
||||
|
||||
func compileGoDefinedHostFunction(ir *wazeroir.CompilationResult, withListener bool) (*code, error) {
|
||||
compiler, err := newCompiler(ir, withListener)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -935,8 +975,8 @@ func compileGoDefinedHostFunction(ir *wazeroir.CompilationResult) (*code, error)
|
||||
return &code{codeSegment: c}, nil
|
||||
}
|
||||
|
||||
func compileWasmFunction(_ api.CoreFeatures, ir *wazeroir.CompilationResult) (*code, error) {
|
||||
compiler, err := newCompiler(ir)
|
||||
func compileWasmFunction(_ api.CoreFeatures, ir *wazeroir.CompilationResult, withListener bool) (*code, error) {
|
||||
compiler, err := newCompiler(ir, withListener)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to initialize assembly builder: %w", err)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -10,6 +11,7 @@ import (
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/experimental"
|
||||
"github.com/tetratelabs/wazero/experimental/logging"
|
||||
"github.com/tetratelabs/wazero/internal/platform"
|
||||
"github.com/tetratelabs/wazero/internal/testing/enginetest"
|
||||
"github.com/tetratelabs/wazero/internal/testing/require"
|
||||
@@ -19,8 +21,12 @@ import (
|
||||
// testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors.
|
||||
var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary")
|
||||
|
||||
// et is used for tests defined in the enginetest package.
|
||||
var et = &engineTester{}
|
||||
var (
|
||||
// et is used for tests defined in the enginetest package.
|
||||
et = &engineTester{}
|
||||
functionLog bytes.Buffer
|
||||
listenerFactory = logging.NewLoggingListenerFactory(&functionLog)
|
||||
)
|
||||
|
||||
// engineTester implements enginetest.EngineTester.
|
||||
type engineTester struct{}
|
||||
@@ -32,7 +38,7 @@ func (e *engineTester) IsCompiler() bool {
|
||||
|
||||
// ListenerFactory implements the same method as documented on enginetest.EngineTester.
|
||||
func (e *engineTester) ListenerFactory() experimental.FunctionListenerFactory {
|
||||
return nil
|
||||
return listenerFactory
|
||||
}
|
||||
|
||||
// NewEngine implements the same method as documented on enginetest.EngineTester.
|
||||
@@ -82,18 +88,74 @@ func TestCompiler_ModuleEngine_LookupFunction(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCompiler_ModuleEngine_Call(t *testing.T) {
|
||||
defer functionLog.Reset()
|
||||
requireSupportedOSArch(t)
|
||||
enginetest.RunTestModuleEngine_Call(t, et)
|
||||
require.Equal(t, `
|
||||
--> .$0(1,2)
|
||||
<-- (1,2)
|
||||
`, "\n"+functionLog.String())
|
||||
}
|
||||
|
||||
func TestCompiler_ModuleEngine_Call_HostFn(t *testing.T) {
|
||||
defer functionLog.Reset()
|
||||
requireSupportedOSArch(t)
|
||||
enginetest.RunTestModuleEngine_Call_HostFn(t, et)
|
||||
}
|
||||
|
||||
func TestCompiler_ModuleEngine_Call_Errors(t *testing.T) {
|
||||
defer functionLog.Reset()
|
||||
requireSupportedOSArch(t)
|
||||
enginetest.RunTestModuleEngine_Call_Errors(t, et)
|
||||
|
||||
// TODO: Currently, the listener doesn't get notified on errors as they are
|
||||
// implemented with panic. This means the end hooks aren't make resulting
|
||||
// in dangling logs like this:
|
||||
// ==> host.host_div_by(4294967295)
|
||||
// instead of seeing a return like
|
||||
// <== DivByZero
|
||||
require.Equal(t, `
|
||||
--> imported.div_by.wasm(1)
|
||||
<-- (1)
|
||||
--> imported.div_by.wasm(1)
|
||||
<-- (1)
|
||||
--> imported.div_by.wasm(0)
|
||||
--> imported.div_by.wasm(1)
|
||||
<-- (1)
|
||||
--> imported.call->div_by.go(4294967295)
|
||||
==> host.div_by.go(4294967295)
|
||||
--> imported.call->div_by.go(1)
|
||||
==> host.div_by.go(1)
|
||||
<== (1)
|
||||
<-- (1)
|
||||
--> importing.call_import->call->div_by.go(0)
|
||||
--> imported.call->div_by.go(0)
|
||||
==> host.div_by.go(0)
|
||||
--> importing.call_import->call->div_by.go(1)
|
||||
--> imported.call->div_by.go(1)
|
||||
==> host.div_by.go(1)
|
||||
<== (1)
|
||||
<-- (1)
|
||||
<-- (1)
|
||||
--> importing.call_import->call->div_by.go(4294967295)
|
||||
--> imported.call->div_by.go(4294967295)
|
||||
==> host.div_by.go(4294967295)
|
||||
--> importing.call_import->call->div_by.go(1)
|
||||
--> imported.call->div_by.go(1)
|
||||
==> host.div_by.go(1)
|
||||
<== (1)
|
||||
<-- (1)
|
||||
<-- (1)
|
||||
--> importing.call_import->call->div_by.go(0)
|
||||
--> imported.call->div_by.go(0)
|
||||
==> host.div_by.go(0)
|
||||
--> importing.call_import->call->div_by.go(1)
|
||||
--> imported.call->div_by.go(1)
|
||||
==> host.div_by.go(1)
|
||||
<== (1)
|
||||
<-- (1)
|
||||
<-- (1)
|
||||
`, "\n"+functionLog.String())
|
||||
}
|
||||
|
||||
func TestCompiler_ModuleEngine_Memory(t *testing.T) {
|
||||
@@ -136,11 +198,11 @@ func TestCompiler_CompileModule(t *testing.T) {
|
||||
ID: wasm.ModuleID{},
|
||||
}
|
||||
|
||||
err := e.CompileModule(testCtx, okModule)
|
||||
err := e.CompileModule(testCtx, okModule, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Compiling same module shouldn't be compiled again, but instead should be cached.
|
||||
err = e.CompileModule(testCtx, okModule)
|
||||
err = e.CompileModule(testCtx, okModule, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
compiled, ok := e.codes[okModule.ID]
|
||||
@@ -167,7 +229,7 @@ func TestCompiler_CompileModule(t *testing.T) {
|
||||
errModule.BuildFunctionDefinitions()
|
||||
|
||||
e := et.NewEngine(api.CoreFeaturesV1).(*engine)
|
||||
err := e.CompileModule(testCtx, errModule)
|
||||
err := e.CompileModule(testCtx, errModule, nil)
|
||||
require.EqualError(t, err, "failed to lower func[.$2] to wazeroir: handling instruction: apply stack failed for call: reading immediates: EOF")
|
||||
|
||||
// On the compilation failure, the compiled functions must not be cached.
|
||||
@@ -217,7 +279,7 @@ func TestCompiler_SliceAllocatedOnHeap(t *testing.T) {
|
||||
}}, nil, enabledFeatures)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = s.Engine.CompileModule(testCtx, hm)
|
||||
err = s.Engine.CompileModule(testCtx, hm, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = s.Instantiate(testCtx, ns, hm, hostModuleName, nil, nil)
|
||||
@@ -273,7 +335,7 @@ func TestCompiler_SliceAllocatedOnHeap(t *testing.T) {
|
||||
}
|
||||
m.BuildFunctionDefinitions()
|
||||
|
||||
err = s.Engine.CompileModule(testCtx, m)
|
||||
err = s.Engine.CompileModule(testCtx, m, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
mi, err := s.Instantiate(testCtx, ns, m, t.Name(), nil, nil)
|
||||
@@ -518,3 +580,65 @@ func Test_callFrameOffset(t *testing.T) {
|
||||
require.Equal(t, 10, callFrameOffset(&wasm.FunctionType{ParamNumInUint64: 10, ResultNumInUint64: 5}))
|
||||
require.Equal(t, 100, callFrameOffset(&wasm.FunctionType{ParamNumInUint64: 100, ResultNumInUint64: 50}))
|
||||
}
|
||||
|
||||
func TestCallEngine_builtinFunctionFunctionListenerBefore(t *testing.T) {
|
||||
nextContext, currentContext, prevContext := context.Background(), context.Background(), context.Background()
|
||||
f := &wasm.FunctionInstance{
|
||||
Definition: newMockFunctionDefinition("1"),
|
||||
Type: &wasm.FunctionType{ParamNumInUint64: 3},
|
||||
Listener: mockListener{
|
||||
before: func(ctx context.Context, def api.FunctionDefinition, paramValues []uint64) context.Context {
|
||||
require.Equal(t, currentContext, ctx)
|
||||
require.Equal(t, []uint64{2, 3, 4}, paramValues)
|
||||
return nextContext
|
||||
},
|
||||
},
|
||||
}
|
||||
ce := &callEngine{
|
||||
ctx: currentContext, stack: []uint64{0, 1, 2, 3, 4, 5},
|
||||
stackContext: stackContext{stackBasePointerInBytes: 16},
|
||||
contextStack: &contextStack{self: prevContext},
|
||||
}
|
||||
ce.builtinFunctionFunctionListenerBefore(ce.ctx, f)
|
||||
|
||||
// Contexts must be stacked.
|
||||
require.Equal(t, currentContext, ce.contextStack.self)
|
||||
require.Equal(t, prevContext, ce.contextStack.prev.self)
|
||||
}
|
||||
|
||||
func TestCallEngine_builtinFunctionFunctionListenerAfter(t *testing.T) {
|
||||
currentContext, prevContext := context.Background(), context.Background()
|
||||
f := &wasm.FunctionInstance{
|
||||
Definition: newMockFunctionDefinition("1"),
|
||||
Type: &wasm.FunctionType{ResultNumInUint64: 1},
|
||||
Listener: mockListener{
|
||||
after: func(ctx context.Context, def api.FunctionDefinition, err error, resultValues []uint64) {
|
||||
require.Equal(t, currentContext, ctx)
|
||||
require.Equal(t, []uint64{5}, resultValues)
|
||||
},
|
||||
},
|
||||
}
|
||||
ce := &callEngine{
|
||||
ctx: currentContext, stack: []uint64{0, 1, 2, 3, 4, 5},
|
||||
stackContext: stackContext{stackBasePointerInBytes: 40},
|
||||
contextStack: &contextStack{self: prevContext},
|
||||
}
|
||||
ce.builtinFunctionFunctionListenerAfter(ce.ctx, f)
|
||||
|
||||
// Contexts must be popped.
|
||||
require.Nil(t, ce.contextStack)
|
||||
require.Equal(t, prevContext, ce.ctx)
|
||||
}
|
||||
|
||||
type mockListener struct {
|
||||
before func(ctx context.Context, def api.FunctionDefinition, paramValues []uint64) context.Context
|
||||
after func(ctx context.Context, def api.FunctionDefinition, err error, resultValues []uint64)
|
||||
}
|
||||
|
||||
func (m mockListener) Before(ctx context.Context, def api.FunctionDefinition, paramValues []uint64) context.Context {
|
||||
return m.before(ctx, def, paramValues)
|
||||
}
|
||||
|
||||
func (m mockListener) After(ctx context.Context, def api.FunctionDefinition, err error, resultValues []uint64) {
|
||||
m.after(ctx, def, err, resultValues)
|
||||
}
|
||||
|
||||
@@ -91,15 +91,17 @@ type amd64Compiler struct {
|
||||
currentLabel string
|
||||
// onStackPointerCeilDeterminedCallBack hold a callback which are called when the max stack pointer is determined BEFORE generating native code.
|
||||
onStackPointerCeilDeterminedCallBack func(stackPointerCeil uint64)
|
||||
withListener bool
|
||||
}
|
||||
|
||||
func newAmd64Compiler(ir *wazeroir.CompilationResult) (compiler, error) {
|
||||
func newAmd64Compiler(ir *wazeroir.CompilationResult, withListener bool) (compiler, error) {
|
||||
c := &amd64Compiler{
|
||||
assembler: amd64.NewAssembler(),
|
||||
locationStack: newRuntimeValueLocationStack(),
|
||||
currentLabel: wazeroir.EntrypointLabel,
|
||||
ir: ir,
|
||||
labels: map[string]*amd64LabelInfo{},
|
||||
withListener: withListener,
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
@@ -158,6 +160,12 @@ func (c *amd64Compiler) compileGoDefinedHostFunction() error {
|
||||
// First we must update the location stack to reflect the number of host function inputs.
|
||||
c.locationStack.init(c.ir.Signature)
|
||||
|
||||
if c.withListener {
|
||||
if err := c.compileCallBuiltinFunction(builtinFunctionIndexFunctionListenerBefore); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.compileCallGoHostFunction(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -4572,6 +4580,14 @@ func (c *amd64Compiler) compileReturnFunction() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.withListener {
|
||||
if err := c.compileCallBuiltinFunction(builtinFunctionIndexFunctionListenerAfter); err != nil {
|
||||
return err
|
||||
}
|
||||
// After return, we re-initialize the stack base pointer as that is used to return to the caller below.
|
||||
c.compileReservedStackBasePointerInitialization()
|
||||
}
|
||||
|
||||
// amd64CallingConventionDestinationFunctionModuleInstanceAddressRegister holds the module instance's address
|
||||
// so mark it used so that it won't be used as a free register.
|
||||
c.locationStack.markRegisterUsed(amd64CallingConventionDestinationFunctionModuleInstanceAddressRegister)
|
||||
@@ -4733,6 +4749,12 @@ func (c *amd64Compiler) compilePreamble() (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.withListener {
|
||||
if err = c.compileCallBuiltinFunction(builtinFunctionIndexFunctionListenerBefore); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
c.compileReservedStackBasePointerInitialization()
|
||||
|
||||
// Finally, we initialize the reserved memory register based on the module context.
|
||||
|
||||
@@ -28,14 +28,16 @@ type arm64Compiler struct {
|
||||
stackPointerCeil uint64
|
||||
// onStackPointerCeilDeterminedCallBack hold a callback which are called when the ceil of stack pointer is determined before generating native code.
|
||||
onStackPointerCeilDeterminedCallBack func(stackPointerCeil uint64)
|
||||
withListener bool
|
||||
}
|
||||
|
||||
func newArm64Compiler(ir *wazeroir.CompilationResult) (compiler, error) {
|
||||
func newArm64Compiler(ir *wazeroir.CompilationResult, withListener bool) (compiler, error) {
|
||||
return &arm64Compiler{
|
||||
assembler: arm64.NewAssembler(arm64ReservedRegisterForTemporary),
|
||||
locationStack: newRuntimeValueLocationStack(),
|
||||
ir: ir,
|
||||
labels: map[string]*arm64LabelInfo{},
|
||||
withListener: withListener,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -188,10 +190,17 @@ func (c *arm64Compiler) compilePreamble() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.withListener {
|
||||
if err := c.compileCallGoFunction(nativeCallStatusCodeCallBuiltInFunction, builtinFunctionIndexFunctionListenerBefore); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// We must initialize the stack base pointer register so that we can manipulate the stack properly.
|
||||
c.compileReservedStackBasePointerRegisterInitialization()
|
||||
|
||||
c.compileReservedMemoryRegisterInitialization()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -263,6 +272,14 @@ func (c *arm64Compiler) compileReturnFunction() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.withListener {
|
||||
if err := c.compileCallGoFunction(nativeCallStatusCodeCallBuiltInFunction, builtinFunctionIndexFunctionListenerAfter); err != nil {
|
||||
return err
|
||||
}
|
||||
// After return, we re-initialize the stack base pointer as that is used to return to the caller below.
|
||||
c.compileReservedStackBasePointerRegisterInitialization()
|
||||
}
|
||||
|
||||
// arm64CallingConventionModuleInstanceAddressRegister holds the module intstance's address
|
||||
// so mark it used so that it won't be used as a free register.
|
||||
c.locationStack.markRegisterUsed(arm64CallingConventionModuleInstanceAddressRegister)
|
||||
@@ -339,6 +356,13 @@ func (c *arm64Compiler) compileGoDefinedHostFunction() error {
|
||||
// First we must update the location stack to reflect the number of host function inputs.
|
||||
c.locationStack.init(c.ir.Signature)
|
||||
|
||||
if c.withListener {
|
||||
if err := c.compileCallGoFunction(nativeCallStatusCodeCallBuiltInFunction,
|
||||
builtinFunctionIndexFunctionListenerBefore); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.compileCallGoFunction(nativeCallStatusCodeCallGoHostFunction, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -220,7 +220,7 @@ type interpreterOp struct {
|
||||
const callFrameStackSize = 0
|
||||
|
||||
// CompileModule implements the same method as documented on wasm.Engine.
|
||||
func (e *engine) CompileModule(ctx context.Context, module *wasm.Module) error {
|
||||
func (e *engine) CompileModule(ctx context.Context, module *wasm.Module, _ []experimental.FunctionListener) error {
|
||||
if _, ok := e.getCodes(module); ok { // cache hit!
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -542,7 +542,7 @@ func TestInterpreter_Compile(t *testing.T) {
|
||||
}
|
||||
errModule.BuildFunctionDefinitions()
|
||||
|
||||
err := e.CompileModule(testCtx, errModule)
|
||||
err := e.CompileModule(testCtx, errModule, nil)
|
||||
require.EqualError(t, err, "failed to lower func[.$2] to wazeroir: handling instruction: apply stack failed for call: reading immediates: EOF")
|
||||
|
||||
// On the compilation failure, all the compiled functions including succeeded ones must be released.
|
||||
@@ -563,7 +563,7 @@ func TestInterpreter_Compile(t *testing.T) {
|
||||
},
|
||||
ID: wasm.ModuleID{},
|
||||
}
|
||||
err := e.CompileModule(testCtx, okModule)
|
||||
err := e.CompileModule(testCtx, okModule, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
compiled, ok := e.codes[okModule.ID]
|
||||
|
||||
@@ -191,7 +191,7 @@ func setupHostCallBench(requireNoError func(error)) *wasm.ModuleInstance {
|
||||
goReflectFn := host.Exports["go-reflect"].Function
|
||||
wasnFn := host.Exports["wasm"].Function
|
||||
|
||||
err := eng.CompileModule(testCtx, hostModule)
|
||||
err := eng.CompileModule(testCtx, hostModule, nil)
|
||||
requireNoError(err)
|
||||
|
||||
hostME, err := eng.NewModuleEngine(host.Name, hostModule, nil, host.Functions, nil, nil)
|
||||
@@ -224,7 +224,7 @@ func setupHostCallBench(requireNoError func(error)) *wasm.ModuleInstance {
|
||||
}
|
||||
|
||||
importingModule.BuildFunctionDefinitions()
|
||||
err = eng.CompileModule(testCtx, importingModule)
|
||||
err = eng.CompileModule(testCtx, importingModule, nil)
|
||||
requireNoError(err)
|
||||
|
||||
importing := &wasm.ModuleInstance{TypeIDs: []wasm.FunctionTypeID{0}}
|
||||
|
||||
@@ -367,7 +367,7 @@ func addSpectestModule(t *testing.T, ctx context.Context, s *wasm.Store, ns *was
|
||||
err = mod.Validate(enabledFeatures)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = s.Engine.CompileModule(ctx, mod)
|
||||
err = s.Engine.CompileModule(ctx, mod, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = s.Instantiate(ctx, ns, mod, mod.NameSection.ModuleName, sys.DefaultContext(nil), nil)
|
||||
@@ -433,7 +433,7 @@ func Run(t *testing.T, testDataFS embed.FS, ctx context.Context, newEngine func(
|
||||
|
||||
maybeSetMemoryCap(mod)
|
||||
mod.BuildFunctionDefinitions()
|
||||
err = s.Engine.CompileModule(ctx, mod)
|
||||
err = s.Engine.CompileModule(ctx, mod, nil)
|
||||
require.NoError(t, err, msg)
|
||||
|
||||
_, err = s.Instantiate(ctx, ns, mod, moduleName, nil, nil)
|
||||
@@ -571,7 +571,7 @@ func Run(t *testing.T, testDataFS embed.FS, ctx context.Context, newEngine func(
|
||||
|
||||
maybeSetMemoryCap(mod)
|
||||
mod.BuildFunctionDefinitions()
|
||||
err = s.Engine.CompileModule(ctx, mod)
|
||||
err = s.Engine.CompileModule(ctx, mod, nil)
|
||||
require.NoError(t, err, msg)
|
||||
|
||||
_, err = s.Instantiate(ctx, ns, mod, t.Name(), nil, nil)
|
||||
@@ -604,7 +604,7 @@ func requireInstantiationError(t *testing.T, ctx context.Context, s *wasm.Store,
|
||||
|
||||
maybeSetMemoryCap(mod)
|
||||
mod.BuildFunctionDefinitions()
|
||||
err = s.Engine.CompileModule(ctx, mod)
|
||||
err = s.Engine.CompileModule(ctx, mod, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ func RunTestEngine_NewModuleEngine(t *testing.T, et EngineTester) {
|
||||
|
||||
t.Run("sets module name", func(t *testing.T) {
|
||||
m := &wasm.Module{}
|
||||
err := e.CompileModule(testCtx, m)
|
||||
err := e.CompileModule(testCtx, m, nil)
|
||||
require.NoError(t, err)
|
||||
me, err := e.NewModuleEngine(t.Name(), m, nil, nil, nil, nil)
|
||||
require.NoError(t, err)
|
||||
@@ -92,7 +92,7 @@ func RunTestEngine_InitializeFuncrefGlobals(t *testing.T, et EngineTester) {
|
||||
},
|
||||
}
|
||||
m.BuildFunctionDefinitions()
|
||||
err := e.CompileModule(testCtx, m)
|
||||
err := e.CompileModule(testCtx, m, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// To use the function, we first need to add it to a module.
|
||||
@@ -140,13 +140,15 @@ func RunTestModuleEngine_Call(t *testing.T, et EngineTester) {
|
||||
{Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeLocalGet, 1, wasm.OpcodeEnd}},
|
||||
},
|
||||
}
|
||||
|
||||
m.BuildFunctionDefinitions()
|
||||
err := e.CompileModule(testCtx, m)
|
||||
listeners := buildListeners(et.ListenerFactory(), m)
|
||||
err := e.CompileModule(testCtx, m, listeners)
|
||||
require.NoError(t, err)
|
||||
|
||||
// To use the function, we first need to add it to a module.
|
||||
module := &wasm.ModuleInstance{Name: t.Name(), TypeIDs: []wasm.FunctionTypeID{0}}
|
||||
module.Functions = module.BuildFunctions(m, buildListeners(et.ListenerFactory(), m))
|
||||
module.Functions = module.BuildFunctions(m, listeners)
|
||||
|
||||
// Compile the module
|
||||
me, err := e.NewModuleEngine(module.Name, m, nil, module.Functions, nil, nil)
|
||||
@@ -210,7 +212,7 @@ func requireNewModuleEngine_emptyTable(t *testing.T, e wasm.Engine, et EngineTes
|
||||
ID: wasm.ModuleID{0},
|
||||
}
|
||||
m.BuildFunctionDefinitions()
|
||||
err := e.CompileModule(testCtx, m)
|
||||
err := e.CompileModule(testCtx, m, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
module = &wasm.ModuleInstance{Name: t.Name(), Tables: tables, TypeIDs: []wasm.FunctionTypeID{0}}
|
||||
@@ -245,7 +247,7 @@ func requireNewModuleEngine_multiTable(t *testing.T, e wasm.Engine, et EngineTes
|
||||
ID: wasm.ModuleID{1},
|
||||
}
|
||||
m.BuildFunctionDefinitions()
|
||||
err := e.CompileModule(testCtx, m)
|
||||
err := e.CompileModule(testCtx, m, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
module = &wasm.ModuleInstance{Name: t.Name(), Tables: tables, TypeIDs: []wasm.FunctionTypeID{0}}
|
||||
@@ -284,7 +286,7 @@ func requireNewModuleEngine_tableWithImportedFunction(t *testing.T, e wasm.Engin
|
||||
ID: wasm.ModuleID{2},
|
||||
}
|
||||
importedModule.BuildFunctionDefinitions()
|
||||
err := e.CompileModule(testCtx, importedModule)
|
||||
err := e.CompileModule(testCtx, importedModule, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
imported := &wasm.ModuleInstance{Name: t.Name(), Tables: tables, TypeIDs: []wasm.FunctionTypeID{0}}
|
||||
@@ -303,7 +305,7 @@ func requireNewModuleEngine_tableWithImportedFunction(t *testing.T, e wasm.Engin
|
||||
ID: wasm.ModuleID{3},
|
||||
}
|
||||
importingModule.BuildFunctionDefinitions()
|
||||
err = e.CompileModule(testCtx, importingModule)
|
||||
err = e.CompileModule(testCtx, importingModule, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
tableInits := []wasm.TableInitEntry{
|
||||
@@ -336,7 +338,7 @@ func requireNewModuleEngine_tableWithMixedFunctions(t *testing.T, e wasm.Engine,
|
||||
ID: wasm.ModuleID{4},
|
||||
}
|
||||
importedModule.BuildFunctionDefinitions()
|
||||
err := e.CompileModule(testCtx, importedModule)
|
||||
err := e.CompileModule(testCtx, importedModule, nil)
|
||||
require.NoError(t, err)
|
||||
imported := &wasm.ModuleInstance{Name: t.Name(), TypeIDs: []wasm.FunctionTypeID{0}}
|
||||
importedFunctions := imported.BuildFunctions(importedModule, buildListeners(et.ListenerFactory(), importedModule))
|
||||
@@ -358,7 +360,7 @@ func requireNewModuleEngine_tableWithMixedFunctions(t *testing.T, e wasm.Engine,
|
||||
ID: wasm.ModuleID{5},
|
||||
}
|
||||
importingModule.BuildFunctionDefinitions()
|
||||
err = e.CompileModule(testCtx, importingModule)
|
||||
err = e.CompileModule(testCtx, importingModule, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
importing = &wasm.ModuleInstance{Name: t.Name(), Tables: tables, TypeIDs: []wasm.FunctionTypeID{0}}
|
||||
@@ -678,8 +680,9 @@ func RunTestModuleEngine_Memory(t *testing.T, et EngineTester) {
|
||||
},
|
||||
}
|
||||
m.BuildFunctionDefinitions()
|
||||
listeners := buildListeners(et.ListenerFactory(), m)
|
||||
|
||||
err := e.CompileModule(testCtx, m)
|
||||
err := e.CompileModule(testCtx, m, listeners)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Assign memory to the module instance
|
||||
@@ -692,7 +695,7 @@ func RunTestModuleEngine_Memory(t *testing.T, et EngineTester) {
|
||||
var memory api.Memory = module.Memory
|
||||
|
||||
// To use functions, we need to instantiate them (associate them with a ModuleInstance).
|
||||
module.Functions = module.BuildFunctions(m, buildListeners(et.ListenerFactory(), m))
|
||||
module.Functions = module.BuildFunctions(m, listeners)
|
||||
module.BuildExports(m.ExportSection)
|
||||
grow, init := module.Functions[0], module.Functions[1]
|
||||
|
||||
@@ -820,10 +823,11 @@ func setupCallTests(t *testing.T, e wasm.Engine, divBy *wasm.Code, fnlf experime
|
||||
ID: wasm.ModuleID{0},
|
||||
}
|
||||
hostModule.BuildFunctionDefinitions()
|
||||
err := e.CompileModule(testCtx, hostModule)
|
||||
lns := buildListeners(fnlf, hostModule)
|
||||
err := e.CompileModule(testCtx, hostModule, lns)
|
||||
require.NoError(t, err)
|
||||
host := &wasm.ModuleInstance{Name: hostModule.NameSection.ModuleName, TypeIDs: []wasm.FunctionTypeID{0}}
|
||||
host.Functions = host.BuildFunctions(hostModule, buildListeners(fnlf, hostModule))
|
||||
host.Functions = host.BuildFunctions(hostModule, lns)
|
||||
host.BuildExports(hostModule.ExportSection)
|
||||
hostFn := host.Exports[divByGoName].Function
|
||||
|
||||
@@ -854,11 +858,12 @@ func setupCallTests(t *testing.T, e wasm.Engine, divBy *wasm.Code, fnlf experime
|
||||
ID: wasm.ModuleID{1},
|
||||
}
|
||||
importedModule.BuildFunctionDefinitions()
|
||||
err = e.CompileModule(testCtx, importedModule)
|
||||
lns = buildListeners(fnlf, importedModule)
|
||||
err = e.CompileModule(testCtx, importedModule, lns)
|
||||
require.NoError(t, err)
|
||||
|
||||
imported := &wasm.ModuleInstance{Name: importedModule.NameSection.ModuleName, TypeIDs: []wasm.FunctionTypeID{0}}
|
||||
importedFunctions := imported.BuildFunctions(importedModule, buildListeners(fnlf, importedModule))
|
||||
importedFunctions := imported.BuildFunctions(importedModule, lns)
|
||||
imported.Functions = append([]*wasm.FunctionInstance{hostFn}, importedFunctions...)
|
||||
imported.BuildExports(importedModule.ExportSection)
|
||||
callHostFn := imported.Exports[callDivByGoName].Function
|
||||
@@ -886,12 +891,13 @@ func setupCallTests(t *testing.T, e wasm.Engine, divBy *wasm.Code, fnlf experime
|
||||
ID: wasm.ModuleID{2},
|
||||
}
|
||||
importingModule.BuildFunctionDefinitions()
|
||||
err = e.CompileModule(testCtx, importingModule)
|
||||
lns = buildListeners(fnlf, importingModule)
|
||||
err = e.CompileModule(testCtx, importingModule, lns)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Add the exported function.
|
||||
importing := &wasm.ModuleInstance{Name: importingModule.NameSection.ModuleName, TypeIDs: []wasm.FunctionTypeID{0}}
|
||||
importingFunctions := importing.BuildFunctions(importingModule, buildListeners(fnlf, importingModule))
|
||||
importingFunctions := importing.BuildFunctions(importingModule, lns)
|
||||
importing.Functions = append([]*wasm.FunctionInstance{callHostFn}, importingFunctions...)
|
||||
importing.BuildExports(importingModule.ExportSection)
|
||||
|
||||
@@ -936,7 +942,7 @@ func setupCallMemTests(t *testing.T, e wasm.Engine, readMem *wasm.Code, fnlf exp
|
||||
ID: wasm.ModuleID{0},
|
||||
}
|
||||
hostModule.BuildFunctionDefinitions()
|
||||
err := e.CompileModule(testCtx, hostModule)
|
||||
err := e.CompileModule(testCtx, hostModule, nil)
|
||||
require.NoError(t, err)
|
||||
host := &wasm.ModuleInstance{Name: hostModule.NameSection.ModuleName, TypeIDs: []wasm.FunctionTypeID{0}}
|
||||
host.Functions = host.BuildFunctions(hostModule, buildListeners(fnlf, hostModule))
|
||||
@@ -976,7 +982,7 @@ func setupCallMemTests(t *testing.T, e wasm.Engine, readMem *wasm.Code, fnlf exp
|
||||
ID: wasm.ModuleID{1},
|
||||
}
|
||||
importingModule.BuildFunctionDefinitions()
|
||||
err = e.CompileModule(testCtx, importingModule)
|
||||
err = e.CompileModule(testCtx, importingModule, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Add the exported function.
|
||||
|
||||
@@ -3,13 +3,15 @@ package wasm
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/tetratelabs/wazero/experimental"
|
||||
)
|
||||
|
||||
// Engine is a Store-scoped mechanism to compile functions declared or imported by a module.
|
||||
// This is a top-level type implemented by an interpreter or compiler.
|
||||
type Engine interface {
|
||||
// CompileModule implements the same method as documented on wasm.Engine.
|
||||
CompileModule(ctx context.Context, module *Module) error
|
||||
CompileModule(ctx context.Context, module *Module, listeners []experimental.FunctionListener) error
|
||||
|
||||
// CompiledModuleCount is exported for testing, to track the size of the compilation cache.
|
||||
CompiledModuleCount() uint32
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/experimental"
|
||||
"github.com/tetratelabs/wazero/internal/leb128"
|
||||
"github.com/tetratelabs/wazero/internal/sys"
|
||||
"github.com/tetratelabs/wazero/internal/testing/hammer"
|
||||
@@ -362,7 +363,9 @@ func newStore() (*Store, *Namespace) {
|
||||
}
|
||||
|
||||
// CompileModule implements the same method as documented on wasm.Engine.
|
||||
func (e *mockEngine) CompileModule(context.Context, *Module) error { return nil }
|
||||
func (e *mockEngine) CompileModule(context.Context, *Module, []experimental.FunctionListener) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// LookupFunction implements the same method as documented on wasm.Engine.
|
||||
func (e *mockModuleEngine) LookupFunction(*TableInstance, FunctionTypeID, Index) (Index, error) {
|
||||
|
||||
@@ -189,11 +189,11 @@ func (r *runtime) CompileModule(ctx context.Context, binary []byte) (CompiledMod
|
||||
|
||||
c := &compiledModule{module: internal, compiledEngine: r.store.Engine}
|
||||
|
||||
if c.listeners, err = buildListeners(ctx, r, internal); err != nil {
|
||||
if c.listeners, err = buildListeners(ctx, internal); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = r.store.Engine.CompileModule(ctx, internal); err != nil {
|
||||
if err = r.store.Engine.CompileModule(ctx, internal, c.listeners); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -201,15 +201,12 @@ func (r *runtime) CompileModule(ctx context.Context, binary []byte) (CompiledMod
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func buildListeners(ctx context.Context, r *runtime, internal *wasm.Module) ([]experimentalapi.FunctionListener, error) {
|
||||
func buildListeners(ctx context.Context, internal *wasm.Module) ([]experimentalapi.FunctionListener, error) {
|
||||
// Test to see if internal code are using an experimental feature.
|
||||
fnlf := ctx.Value(experimentalapi.FunctionListenerFactoryKey{})
|
||||
if fnlf == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if !r.isInterpreter {
|
||||
return nil, errors.New("context includes a FunctionListenerFactoryKey, which is only supported in the interpreter")
|
||||
}
|
||||
factory := fnlf.(experimentalapi.FunctionListenerFactory)
|
||||
importCount := internal.ImportFuncCount()
|
||||
listeners := make([]experimentalapi.FunctionListener, len(internal.FunctionSection))
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/experimental"
|
||||
"github.com/tetratelabs/wazero/internal/leb128"
|
||||
"github.com/tetratelabs/wazero/internal/testing/require"
|
||||
"github.com/tetratelabs/wazero/internal/version"
|
||||
@@ -297,7 +298,7 @@ func TestModule_Global(t *testing.T) {
|
||||
|
||||
code := &compiledModule{module: tc.module}
|
||||
|
||||
err := r.store.Engine.CompileModule(testCtx, code.module)
|
||||
err := r.store.Engine.CompileModule(testCtx, code.module, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Instantiate the module and get the export of the above global
|
||||
@@ -647,7 +648,7 @@ type mockEngine struct {
|
||||
}
|
||||
|
||||
// CompileModule implements the same method as documented on wasm.Engine.
|
||||
func (e *mockEngine) CompileModule(_ context.Context, module *wasm.Module) error {
|
||||
func (e *mockEngine) CompileModule(_ context.Context, module *wasm.Module, _ []experimental.FunctionListener) error {
|
||||
e.cachedModules[module] = struct{}{}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user