Repository: tetratelabs/wazero
Pull request #274 (2022-02-22 03:27:11)

Makes API easier by returning nil when a function isn't available
Before, we emulated the map api by returning `fn, ok` to tell if a
function was there or not. This made a lot of cruft vs returning `nil`
instead, especially as many times configuration of functions is near
where they are called in the source (so they aren't likely to be nil).



Patch:
diff --git a/README.md b/README.md
index 01137f30..e218f106 100644
--- a/README.md
+++ b/README.md
@@ -23,11 +23,8 @@ func main() {
 	// Instantiate the module with a Wasm Interpreter, to return its exported functions
 	exports, _ := wazero.InstantiateModule(wazero.NewStore(), mod)
 
-	// Get the factorial function
-	fac, _ := exports.Function("fac")
-
 	// Discover 7! is 5040
-	fmt.Println(fac(context.Background(), 7))
+	fmt.Println(exports.Function("fac")(context.Background(), 7))
 }
 ```
 
@@ -39,7 +36,7 @@ There's the concept called "engine" in wazero (which is a word commonly used in
 There are two types of engines are available for wazero:
 
 1. _Interpreter_: a naive interpreter-based implementation of Wasm virtual machine. Its implementation doesn't have any platform (GOARCH, GOOS) specific code, therefore _interpreter_ engine can be used for any compilation target available for Go (such as `arm64`).
-2. _JIT engine_: compiles WebAssembly modules, generates the machine code, and executing it all at runtime. Currently wazero only implements the JIT compiler for `amd64` target. Generally speaking, _JIT engine_ is faster than _Interpreter_ by order of magnitude. However, the implementation is immature and has bunch of aspects that could be improved (for example, it just does a singlepass compilation and doesn't do any optimizations, etc.). Please refer to [internal/wasm/jit/RATIONALE.md](internal/wasm/jit/RATIONALE.md) for the design choices and considerations in our JIT engine.
+2. _JIT engine_: compiles WebAssembly modules, generates the machine code, and executing it all at runtime. Currently wazero only implements the JIT compiler for `amd64` target. Generally speaking, _JIT engine_ is faster than _Interpreter_ by order of magnitude. However, the implementation is immature and has a bunch of aspects that could be improved (for example, it just does a singlepass compilation and doesn't do any optimizations, etc.). Please refer to [internal/wasm/jit/RATIONALE.md](internal/wasm/jit/RATIONALE.md) for the design choices and considerations in our JIT engine.
 
 Both of engines passes 100% of [WebAssembly spec test suites]((https://github.com/WebAssembly/spec/tree/wg-1.0/test/core)) (on supported platforms).
 
@@ -57,13 +54,13 @@ store, err := wazero.NewStoreWithConfig(&wazero.StoreConfig{Engine: wazero.NewEn
 
 If you want to provide Wasm host environments in your Go programs, currently there's no other choice than using CGO and leveraging the state-of-the-art runtimes written in C++/Rust (e.g. V8, Wasmtime, Wasmer, WAVM, etc.), and there's no pure Go Wasm runtime out there. (There's only one exception named [wagon](https://github.com/go-interpreter/wagon), but it was archived with the mention to this project.)
 
-First of all, why do you want to write host environments in Go? You might want to have plugin systems in your Go project and want these plugin systems to be safe/fast/flexible, and enable users to
-write plugins in their favorite lanugages. That's where Wasm comes into play. You write your own Wasm host environments and embed Wasm runtime in your projects, and now users are able to write plugins in their own favorite lanugages (AssembyScript, C, C++, Rust, Zig, etc.). As an specific example, you maybe write proxy severs in Go and want to allow users to extend the proxy via [Proxy-Wasm ABI](https://github.com/proxy-wasm/spec). Maybe you are writing server-side rendering applications via Wasm, or [OpenPolicyAgent](https://www.openpolicyagent.org/docs/latest/wasm/) is using Wasm for plugin system.
+First, why do you want to write host environments in Go? You might want to have plugin systems in your Go project and want these plugin systems to be safe/fast/flexible, and enable users to
+write plugins in their favorite languages. That's where Wasm comes into play. You write your own Wasm host environments and embed Wasm runtime in your projects, and now users are able to write plugins in their own favorite lanugages (AssembyScript, C, C++, Rust, Zig, etc.). As a specific example, you maybe write proxy severs in Go and want to allow users to extend the proxy via [Proxy-Wasm ABI](https://github.com/proxy-wasm/spec). Maybe you are writing server-side rendering applications via Wasm, or [OpenPolicyAgent](https://www.openpolicyagent.org/docs/latest/wasm/) is using Wasm for plugin system.
 
 However, experienced Golang developers often avoid using CGO because [_CGO is not Go_](https://dave.cheney.net/2016/01/18/cgo-is-not-go)[ -- _Rob_ _Pike_](https://www.youtube.com/watch?v=PAAkCSZUG1c&t=757s), and it introduces another complexity into your projects. But unfortunately, as I mentioned there's no pure Go Wasm runtime out there, so you have to resort to CGO.
 
-Currently any performance optimization hasn't been done to this runtime yet, and the runtime is just a simple interpreter of Wasm binary. That means in terms of performance, the runtime here is infereior to any aforementioned runtimes (e.g. Wasmtime) for now.
+Currently, any performance optimization hasn't been done to this runtime yet, and the runtime is just a simple interpreter of Wasm binary. That means in terms of performance, the runtime here is inferior to any aforementioned runtimes (e.g. Wasmtime) for now.
 
-However _theoretically speaking_, this project have the potential to compete with these state-of-the-art JIT-style runtimes. The rationale for that is it is well-know that [CGO is slow](https://github.com/golang/go/issues/19574). More specifically, if you make large amount of CGO calls which cross the boundary between Go and C (stack) space, then the usage of CGO could be a bottleneck.
+However, _theoretically speaking_, this project has the potential to compete with these state-of-the-art JIT-style runtimes. The rationale for that is it is well-know that [CGO is slow](https://github.com/golang/go/issues/19574). More specifically, if you make large amount of CGO calls which cross the boundary between Go and C (stack) space, then the usage of CGO could be a bottleneck.
 
 Since we can do JIT compilation purely in Go, this runtime could be the fastest one for some use cases where we have to make large amount of CGO calls (e.g. Proxy-Wasm host environment, or request-based plugin systems).
diff --git a/internal/wasm/host.go b/internal/wasm/host.go
index b4775989..6fdb4b6b 100644
--- a/internal/wasm/host.go
+++ b/internal/wasm/host.go
@@ -9,13 +9,13 @@ import (
 	publicwasm "github.com/tetratelabs/wazero/wasm"
 )
 
-// compile time check to ensure ModuleContext implements publicwasm.ModuleContext
+// compile time check to ensure ModuleContext implements wasm.ModuleContext
 var _ publicwasm.ModuleContext = &ModuleContext{}
 
 func NewModuleContext(ctx context.Context, engine Engine, instance *ModuleInstance) *ModuleContext {
 	return &ModuleContext{
 		ctx:    ctx,
-		Engine: engine,
+		engine: engine,
 		memory: instance.Memory,
 		Module: instance,
 	}
@@ -24,10 +24,9 @@ func NewModuleContext(ctx context.Context, engine Engine, instance *ModuleInstan
 // ModuleContext implements wasm.ModuleContext and wasm.Module
 type ModuleContext struct {
 	// ctx is the default context, exposed as wasm.ModuleContext Context
-	ctx context.Context
-	// Engine is exported for wazero.MakeWasmFunc
-	Engine Engine
-	// Module is exported for wazero.MakeWasmFunc
+	ctx    context.Context
+	engine Engine
+	// Module is exported for spectests.callFunction
 	Module *ModuleInstance
 	// memory is exposed as wasm.ModuleContext Memory
 	memory publicwasm.Memory
@@ -37,7 +36,7 @@ type ModuleContext struct {
 func (c *ModuleContext) WithContext(ctx context.Context) *ModuleContext {
 	// only re-allocate if it will change the effective context
 	if ctx != nil && ctx != c.ctx {
-		return &ModuleContext{Engine: c.Engine, Module: c.Module, memory: c.memory, ctx: ctx}
+		return &ModuleContext{engine: c.engine, Module: c.Module, memory: c.memory, ctx: ctx}
 	}
 	return c
 }
@@ -46,7 +45,7 @@ func (c *ModuleContext) WithContext(ctx context.Context) *ModuleContext {
 func (c *ModuleContext) WithMemory(memory *MemoryInstance) *ModuleContext {
 	// only re-allocate if it will change the effective memory
 	if memory != nil && memory.Max != nil && *memory.Max > 0 && memory != c.memory {
-		return &ModuleContext{Engine: c.Engine, Module: c.Module, memory: memory, ctx: c.ctx}
+		return &ModuleContext{engine: c.engine, Module: c.Module, memory: memory, ctx: c.ctx}
 	}
 	return c
 }
@@ -55,18 +54,18 @@ func (c *ModuleContext) Context() context.Context {
 	return c.ctx
 }
 
-// Memory implements wasm.Module Memory
+// Memory implements wasm.ModuleExports Memory
 func (c *ModuleContext) Memory() publicwasm.Memory {
 	return c.memory
 }
 
-// Function implements wasm.Functions Function
-func (c *ModuleContext) Function(name string) (publicwasm.Function, bool) {
+// Function implements wasm.ModuleExports Function
+func (c *ModuleContext) Function(name string) publicwasm.Function {
 	exp, err := c.Module.GetExport(name, ExportKindFunc)
 	if err != nil {
-		return nil, false
+		return nil
 	}
-	return (&function{c: c, f: exp.Function}).Call, true
+	return (&function{c: c, f: exp.Function}).Call
 }
 
 type function struct {
@@ -81,7 +80,7 @@ func (f *function) Call(ctx context.Context, params ...uint64) ([]uint64, error)
 		return nil, fmt.Errorf("expected %d params, but passed %d", len(paramSignature), paramCount)
 	}
 	hc := f.c.WithContext(ctx)
-	return hc.Engine.Call(hc, f.f, params...)
+	return hc.engine.Call(hc, f.f, params...)
 }
 
 // ExportHostFunctions is defined internally for use in WASI tests and to keep the code size in the root directory small.
@@ -124,13 +123,16 @@ func (f *FunctionInstance) call(ctx publicwasm.ModuleContext, params ...uint64)
 	if !ok { // TODO: guard that hCtx.Module actually imported this!
 		return nil, fmt.Errorf("this function was not imported by %s", ctx)
 	}
-	return hCtx.Engine.Call(hCtx, f, params...)
+	return hCtx.engine.Call(hCtx, f, params...)
 }
 
 // Function implements wasm.HostExports Function
-func (g *HostExports) Function(name string) (publicwasm.HostFunction, bool) {
+func (g *HostExports) Function(name string) publicwasm.HostFunction {
 	f, ok := g.NameToFunctionInstance[name]
-	return f.call, ok
+	if !ok {
+		return nil
+	}
+	return f.call
 }
 
 // Len implements wasm.ModuleContext Len
diff --git a/wasi.go b/wasi.go
index 66993bc1..b21e0753 100644
--- a/wasi.go
+++ b/wasi.go
@@ -104,7 +104,7 @@ func StartWASICommand(store wasm.Store, module *Module) (wasm.ModuleExports, err
 	}
 
 	exports := internal.ModuleContexts[module.name]
-	start, _ := exports.Function(internalwasi.FunctionStart)
+	start := exports.Function(internalwasi.FunctionStart)
 	if _, err = start(exports.Context()); err != nil {
 		return nil, fmt.Errorf("module[%s] function[%s] failed: %w", module.name, internalwasi.FunctionStart, err)
 	}
diff --git a/wasm/wasm.go b/wasm/wasm.go
index ac5e9067..fb04d8c9 100644
--- a/wasm/wasm.go
+++ b/wasm/wasm.go
@@ -19,8 +19,8 @@ type Store interface {
 //
 // Note: This is an interface for decoupling, not third-party implementations. All implementations are in wazero.
 type ModuleExports interface {
-	// Function returns a function exported from this module or false if it wasn't.
-	Function(name string) (Function, bool)
+	// Function returns a function exported from this module or nil if it wasn't.
+	Function(name string) Function
 }
 
 // Function is an advanced API allowing efficient invocation of WebAssembly 1.0 (MVP) functions, given predefined
@@ -60,8 +60,8 @@ type Function func(ctx context.Context, params ...uint64) ([]uint64, error)
 // Note: This is an interface for decoupling, not third-party implementations. All implementations are in wazero.
 // See https://www.w3.org/TR/wasm-core-1/#syntax-hostfunc
 type HostExports interface {
-	// Function returns a function in this module or false if it isn't available under that name.
-	Function(name string) (HostFunction, bool)
+	// Function returns a host function exported under this module name or nil if it wasn't.
+	Function(name string) HostFunction
 }
 
 // HostFunction is like a Function, except it is implemented in Go. This is a "Host Function" in WebAssembly 1.0 (MVP).
@@ -83,8 +83,8 @@ type ModuleContext interface {
 	// Memory returns a potentially zero memory of the importing module
 	Memory() Memory
 
-	// Function returns a function exported from this module or false if it wasn't.
-	Function(name string) (Function, bool)
+	// Function returns a function exported from this module or nil if it wasn't.
+	Function(name string) Function
 }
 
 // Memory allows restricted access to a module's memory. Notably, this does not allow growing.


Test patch:
diff --git a/examples/add_test.go b/examples/add_test.go
index 0fc57fd1..34a6a6bb 100644
--- a/examples/add_test.go
+++ b/examples/add_test.go
@@ -30,8 +30,7 @@ func Test_AddInt(t *testing.T) {
 	exports, err := wazero.InstantiateModule(store, mod)
 	require.NoError(t, err)
 
-	addInt, ok := exports.Function("AddInt")
-	require.True(t, ok)
+	addInt := exports.Function("AddInt")
 
 	for _, c := range []struct {
 		value1, value2, expected uint64 // i32i32_i32 sig, but wasm.Function params and results are uint64
diff --git a/examples/fibonacci_test.go b/examples/fibonacci_test.go
index 2bc8606e..3bdff1be 100644
--- a/examples/fibonacci_test.go
+++ b/examples/fibonacci_test.go
@@ -28,8 +28,7 @@ func Test_fibonacci(t *testing.T) {
 	exports, err := wazero.StartWASICommand(store, mod)
 	require.NoError(t, err)
 
-	fibonacci, ok := exports.Function("fibonacci")
-	require.True(t, ok)
+	fibonacci := exports.Function("fibonacci")
 
 	for _, c := range []struct {
 		input, expected uint64 // i32_i32 sig, but wasm.Function params and results are uint64
diff --git a/examples/host_func_test.go b/examples/host_func_test.go
index 0b811837..54de954e 100644
--- a/examples/host_func_test.go
+++ b/examples/host_func_test.go
@@ -65,8 +65,7 @@ func Test_hostFunc(t *testing.T) {
 	exports, err := wazero.StartWASICommand(store, mod)
 	require.NoError(t, err)
 
-	allocateBufferFn, ok := exports.Function("allocate_buffer")
-	require.True(t, ok)
+	allocateBufferFn := exports.Function("allocate_buffer")
 
 	// Implement the function pointer. This mainly shows how you can decouple a module function dependency.
 	allocateBuffer = func(ctx context.Context, size uint32) uint32 {
@@ -79,8 +78,6 @@ func Test_hostFunc(t *testing.T) {
 	ctx := context.WithValue(context.Background(), testKey{}, int64(12345))
 
 	// Invoke a module-defined function that depends on a host function import
-	base64, ok := exports.Function("base64")
-	require.True(t, ok)
-	_, err = base64(ctx, uint64(5))
+	_, err = exports.Function("base64")(ctx, uint64(5))
 	require.NoError(t, err)
 }
diff --git a/examples/wasi_test.go b/examples/wasi_test.go
index 9034e42b..08092fb1 100644
--- a/examples/wasi_test.go
+++ b/examples/wasi_test.go
@@ -47,8 +47,7 @@ func Test_WASI(t *testing.T) {
 	// Configure WASI and implement the function to use it
 	we, err := wazero.ExportHostFunctions(store, wasi.ModuleSnapshotPreview1, wazero.WASISnapshotPreview1())
 	require.NoError(t, err)
-	randomGetFn, ok := we.Function("random_get")
-	require.True(t, ok)
+	randomGetFn := we.Function("random_get")
 
 	// Implement the function pointer. This mainly shows how you can decouple a host function dependency.
 	randomGet = func(ctx wasm.ModuleContext, buf, bufLen uint32) wasi.Errno {
diff --git a/store_test.go b/store_test.go
index a9b1a300..6cfb1005 100644
--- a/store_test.go
+++ b/store_test.go
@@ -53,11 +53,9 @@ func TestFunction_Context(t *testing.T) {
 			// Instantiate the module and get the export of the above hostFn
 			exports, err := InstantiateModule(store, mod)
 			require.NoError(t, err)
-			fn, ok := exports.Function(functionName)
-			require.True(t, ok)
 
 			// This fails if the function wasn't invoked, or had an unexpected context.
-			results, err := fn(tc.ctx)
+			results, err := exports.Function(functionName)(tc.ctx)
 			require.NoError(t, err)
 			require.Equal(t, expectedResult, results[0])
 		})
diff --git a/tests/bench/bench_fac_iter_test.go b/tests/bench/bench_fac_iter_test.go
index 5e7eacee..64e8ff63 100644
--- a/tests/bench/bench_fac_iter_test.go
+++ b/tests/bench/bench_fac_iter_test.go
@@ -8,7 +8,6 @@ import (
 	"context"
 	_ "embed"
 	"errors"
-	"fmt"
 	"testing"
 
 	"github.com/bytecodealliance/wasmtime-go"
@@ -188,11 +187,7 @@ func newWazeroFacIterBench(engine *wazero.Engine) (wasm.Function, error) {
 		return nil, err
 	}
 
-	fn, ok := m.Function("fac-iter")
-	if !ok {
-		return nil, fmt.Errorf("fac-iter is not an exported function")
-	}
-	return fn, nil
+	return m.Function("fac-iter"), nil
 }
 
 // newWasmerForFacIterBench returns the store and instance that scope the factorial function.
diff --git a/tests/bench/bench_test.go b/tests/bench/bench_test.go
index b45fcab6..54397223 100644
--- a/tests/bench/bench_test.go
+++ b/tests/bench/bench_test.go
@@ -1,7 +1,6 @@
 package bench
 
 import (
-	"context"
 	_ "embed"
 	"fmt"
 	"math/rand"
@@ -33,22 +32,19 @@ func BenchmarkEngines(b *testing.B) {
 func runAllBenches(b *testing.B, m wasm.ModuleExports) {
 	runBase64Benches(b, m)
 	runFibBenches(b, m)
-	runStringsManipulationBenches(b, m)
+	runStringManipulationBenches(b, m)
 	runReverseArrayBenches(b, m)
 	runRandomMatMul(b, m)
 }
 
 func runBase64Benches(b *testing.B, m wasm.ModuleExports) {
-	fn, ok := m.Function("base64")
-	if !ok {
-		b.Fatal("function base64 not exported")
-	}
-	ctx := context.Background()
+	base64 := m.Function("base64")
+
 	for _, numPerExec := range []int{5, 100, 10000} {
-		numPerExec := numPerExec
+		numPerExec := uint64(numPerExec)
 		b.ResetTimer()
 		b.Run(fmt.Sprintf("base64_%d_per_exec", numPerExec), func(b *testing.B) {
-			if _, err := fn(ctx, uint64(numPerExec)); err != nil {
+			if _, err := base64(nil, numPerExec); err != nil {
 				b.Fatal(err)
 			}
 		})
@@ -56,17 +52,14 @@ func runBase64Benches(b *testing.B, m wasm.ModuleExports) {
 }
 
 func runFibBenches(b *testing.B, m wasm.ModuleExports) {
-	fn, ok := m.Function("fibonacci")
-	if !ok {
-		b.Fatal("function base64 not exported")
-	}
-	ctx := context.Background()
+	fibonacci := m.Function("fibonacci")
+
 	for _, num := range []int{5, 10, 20, 30} {
-		num := num
+		num := uint64(num)
 		b.ResetTimer()
 		b.Run(fmt.Sprintf("fib_for_%d", num), func(b *testing.B) {
 			for i := 0; i < b.N; i++ {
-				if _, err := fn(ctx, uint64(num)); err != nil {
+				if _, err := fibonacci(nil, num); err != nil {
 					b.Fatal(err)
 				}
 			}
@@ -74,18 +67,15 @@ func runFibBenches(b *testing.B, m wasm.ModuleExports) {
 	}
 }
 
-func runStringsManipulationBenches(b *testing.B, m wasm.ModuleExports) {
-	fn, ok := m.Function("string_manipulation")
-	if !ok {
-		b.Fatal("function string_manipulation not exported")
-	}
-	ctx := context.Background()
+func runStringManipulationBenches(b *testing.B, m wasm.ModuleExports) {
+	stringManipulation := m.Function("string_manipulation")
+
 	for _, initialSize := range []int{50, 100, 1000} {
-		initialSize := initialSize
+		initialSize := uint64(initialSize)
 		b.ResetTimer()
 		b.Run(fmt.Sprintf("string_manipulation_size_%d", initialSize), func(b *testing.B) {
 			for i := 0; i < b.N; i++ {
-				if _, err := fn(ctx, uint64(initialSize)); err != nil {
+				if _, err := stringManipulation(nil, initialSize); err != nil {
 					b.Fatal(err)
 				}
 			}
@@ -94,17 +84,14 @@ func runStringsManipulationBenches(b *testing.B, m wasm.ModuleExports) {
 }
 
 func runReverseArrayBenches(b *testing.B, m wasm.ModuleExports) {
-	fn, ok := m.Function("reverse_array")
-	if !ok {
-		b.Fatal("function reverse_array not exported")
-	}
-	ctx := context.Background()
+	reverseArray := m.Function("reverse_array")
+
 	for _, arraySize := range []int{500, 1000, 10000} {
-		arraySize := arraySize
+		arraySize := uint64(arraySize)
 		b.ResetTimer()
 		b.Run(fmt.Sprintf("reverse_array_size_%d", arraySize), func(b *testing.B) {
 			for i := 0; i < b.N; i++ {
-				if _, err := fn(ctx, uint64(arraySize)); err != nil {
+				if _, err := reverseArray(nil, arraySize); err != nil {
 					b.Fatal(err)
 				}
 			}
@@ -113,17 +100,14 @@ func runReverseArrayBenches(b *testing.B, m wasm.ModuleExports) {
 }
 
 func runRandomMatMul(b *testing.B, m wasm.ModuleExports) {
-	fn, ok := m.Function("random_mat_mul")
-	if !ok {
-		b.Fatal("function random_mat_mul not exported")
-	}
-	ctx := context.Background()
+	randomMatMul := m.Function("random_mat_mul")
+
 	for _, matrixSize := range []int{5, 10, 20} {
-		matrixSize := matrixSize
+		matrixSize := uint64(matrixSize)
 		b.ResetTimer()
 		b.Run(fmt.Sprintf("random_mat_mul_size_%d", matrixSize), func(b *testing.B) {
 			for i := 0; i < b.N; i++ {
-				if _, err := fn(ctx, uint64(matrixSize)); err != nil {
+				if _, err := randomMatMul(nil, matrixSize); err != nil {
 					b.Fatal(err)
 				}
 			}
@@ -133,12 +117,7 @@ func runRandomMatMul(b *testing.B, m wasm.ModuleExports) {
 
 func instantiateHostFunctionModuleWithEngine(b *testing.B, engine *wazero.Engine) wasm.ModuleExports {
 	getRandomString := func(ctx wasm.ModuleContext, retBufPtr uint32, retBufSize uint32) {
-		allocateBuffer, ok := ctx.Function("allocate_buffer")
-		if !ok {
-			b.Fatal("couldn't find function allocate_buffer")
-		}
-
-		results, err := allocateBuffer(ctx.Context(), 10)
+		results, err := ctx.Function("allocate_buffer")(ctx.Context(), 10)
 		if err != nil {
 			b.Fatal(err)
 		}
diff --git a/tests/codec/codec_test.go b/tests/codec/codec_test.go
index c74133d4..af6e5613 100644
--- a/tests/codec/codec_test.go
+++ b/tests/codec/codec_test.go
@@ -116,10 +116,7 @@ func TestExampleUpToDate(t *testing.T) {
 		require.NoError(t, err)
 
 		// Call the add function as a smoke test
-		addInt, ok := exports.Function("AddInt")
-		require.True(t, ok)
-
-		results, err := addInt(context.Background(), uint64(1), uint64(2))
+		results, err := exports.Function("AddInt")(context.Background(), uint64(1), uint64(2))
 		require.NoError(t, err)
 		require.Equal(t, uint64(3), results[0])
 	})
diff --git a/tests/engine/adhoc_test.go b/tests/engine/adhoc_test.go
index b2ad7c9b..34725176 100644
--- a/tests/engine/adhoc_test.go
+++ b/tests/engine/adhoc_test.go
@@ -80,10 +80,7 @@ func testFibonacci(t *testing.T, newEngine func() *wazero.Engine) {
 			exports, err := wazero.InstantiateModule(store, mod)
 			require.NoError(t, err)
 
-			fib, ok := exports.Function("fib")
-			require.True(t, ok)
-
-			results, err := fib(context.Background(), 20)
+			results, err := exports.Function("fib")(nil, 20)
 			require.NoError(t, err)
 
 			require.Equal(t, uint64(10946), results[0])
@@ -107,21 +104,17 @@ func testFac(t *testing.T, newEngine func() *wazero.Engine) {
 	} {
 		name := name
 
-		fac, ok := exports.Function("fac")
-		require.True(t, ok)
+		fac := exports.Function("fac")
 
 		t.Run(name, func(t *testing.T) {
-			results, err := fac(context.Background(), 25)
+			results, err := fac(nil, 25)
 			require.NoError(t, err)
 			require.Equal(t, uint64(7034535277573963776), results[0])
 		})
 	}
 
 	t.Run("fac-rec - stack overflow", func(t *testing.T) {
-		fac, ok := exports.Function("fac-rec")
-		require.True(t, ok)
-
-		_, err := fac(context.Background(), 1073741824)
+		_, err := exports.Function("fac-rec")(nil, 1073741824)
 		require.ErrorIs(t, err, wasm.ErrRuntimeCallStackOverflow)
 	})
 }
@@ -131,10 +124,7 @@ func testUnreachable(t *testing.T, newEngine func() *wazero.Engine) {
 	require.NoError(t, err)
 
 	callUnreachable := func(ctx publicwasm.ModuleContext) {
-		unreachable, ok := ctx.Function("unreachable_func")
-		require.True(t, ok)
-
-		_, err := unreachable(ctx.Context())
+		_, err := ctx.Function("unreachable_func")(ctx.Context())
 		require.NoError(t, err)
 	}
 
@@ -146,10 +136,7 @@ func testUnreachable(t *testing.T, newEngine func() *wazero.Engine) {
 	exports, err := wazero.InstantiateModule(store, mod)
 	require.NoError(t, err)
 
-	main, ok := exports.Function("main")
-	require.True(t, ok)
-
-	_, err = main(context.Background())
+	_, err = exports.Function("main")(nil)
 	exp := `wasm runtime error: unreachable
 wasm backtrace:
 	0: unreachable_func
@@ -168,32 +155,30 @@ func testMemory(t *testing.T, newEngine func() *wazero.Engine) {
 	exports, err := wazero.InstantiateModule(store, mod)
 	require.NoError(t, err)
 
-	size, ok := exports.Function("size")
-	require.True(t, ok)
+	size := exports.Function("size")
 
 	// First, we have zero-length memory instance.
-	results, err := size(context.Background())
+	results, err := size(nil)
 	require.NoError(t, err)
 	require.Equal(t, uint64(0), results[0])
 
-	grow, ok := exports.Function("grow")
-	require.True(t, ok)
+	grow := exports.Function("grow")
 
 	// Then grow the memory.
 	newPages := uint64(10)
-	results, err = grow(context.Background(), newPages)
+	results, err = grow(nil, newPages)
 	require.NoError(t, err)
 
 	// Grow returns the previous number of memory pages, namely zero.
 	require.Equal(t, uint64(0), results[0])
 
 	// Now size should return the new pages -- 10.
-	results, err = size(context.Background())
+	results, err = size(nil)
 	require.NoError(t, err)
 	require.Equal(t, newPages, results[0])
 
 	// Growing memory with zero pages is valid but should be noop.
-	results, err = grow(context.Background(), 0)
+	results, err = grow(nil, 0)
 	require.NoError(t, err)
 	require.Equal(t, newPages, results[0])
 }
@@ -203,10 +188,7 @@ func testRecursiveEntry(t *testing.T, newEngine func() *wazero.Engine) {
 	require.NoError(t, err)
 
 	hostfunc := func(ctx publicwasm.ModuleContext) {
-		fn, ok := ctx.Function("called_by_host_func")
-		require.True(t, ok)
-
-		_, err = fn(ctx.Context())
+		_, err = ctx.Function("called_by_host_func")(nil)
 		require.NoError(t, err)
 	}
 
@@ -218,10 +200,7 @@ func testRecursiveEntry(t *testing.T, newEngine func() *wazero.Engine) {
 	exports, err := wazero.InstantiateModule(store, mod)
 	require.NoError(t, err)
 
-	main, ok := exports.Function("main")
-	require.True(t, ok)
-
-	_, err = main(context.Background(), 1)
+	_, err = exports.Function("main")(nil, 1)
 	require.NoError(t, err)
 }
 
@@ -257,10 +236,7 @@ func testImportedAndExportedFunc(t *testing.T, newEngine func() *wazero.Engine)
 	require.NoError(t, err)
 
 	// Call store_int and ensure it didn't return an error code.
-	storeIntFn, ok := exports.Function("store_int")
-	require.True(t, ok)
-
-	results, err := storeIntFn(context.Background(), 1, math.MaxUint64)
+	results, err := exports.Function("store_int")(nil, 1, math.MaxUint64)
 	require.NoError(t, err)
 	require.Equal(t, uint64(0), results[0])
 
@@ -335,10 +311,7 @@ func testHostFunctions(t *testing.T, newEngine func() *wazero.Engine) {
 			name := "call->test.identity_f32"
 			input := float32(math.MaxFloat32)
 
-			fn, ok := m.Function(name)
-			require.True(t, ok)
-
-			results, err := fn(ctx, publicwasm.EncodeF32(input)) // float bits are a uint32 value, call requires uint64
+			results, err := m.Function(name)(ctx, publicwasm.EncodeF32(input)) // float bits are a uint32 value, call requires uint64
 			require.NoError(t, err)
 			require.Equal(t, input, publicwasm.DecodeF32(results[0]))
 		})
@@ -346,10 +319,8 @@ func testHostFunctions(t *testing.T, newEngine func() *wazero.Engine) {
 		t.Run(fmt.Sprintf("host function with f64 param%s", k), func(t *testing.T) {
 			name := "call->test.identity_f64"
 			input := math.MaxFloat64
-			fn, ok := m.Function(name)
-			require.True(t, ok)
 
-			results, err := fn(ctx, publicwasm.EncodeF64(input))
+			results, err := m.Function(name)(ctx, publicwasm.EncodeF64(input))
 			require.NoError(t, err)
 			require.Equal(t, input, publicwasm.DecodeF64(results[0]))
 		})