Repository: tetratelabs/wazero
Pull request #347 (2022-03-08 08:16:03)

Removes host-specific types
This converges host-defined modules with Wasm defined modules by
introducing a custom section for host-defined functions. The net result
are far less types and consistent initialization.

* HostModule is removed for Module
* HostFunction is removed for Function
* ModuleContext is removed for Module

Note: One impact of this is that the low-level API no longer accepts a
go context (context.Context), rather a `wasm.Module` which the function
is called in context of. This meant exposing `wasm.Module.WithContext`
to override the default.

Signed-off-by: Adrian Cole <adrian@tetrate.io>


Patch:
diff --git a/README.md b/README.md
index aa3cf309..f42c7362 100644
--- a/README.md
+++ b/README.md
@@ -21,7 +21,7 @@ func main() {
 	module, _ := wazero.NewRuntime().NewModuleFromSource(source)
 
 	// Discover 7! is 5040
-	fmt.Println(module.Function("fac").Call(context.Background(), 7))
+	fmt.Println(module.ExportedFunction("fac").Call(nil, 7))
 }
 ```
 
diff --git a/config.go b/config.go
index 833f1779..b230463f 100644
--- a/config.go
+++ b/config.go
@@ -40,7 +40,7 @@ type RuntimeConfig struct {
 //
 // Notes:
 // * If the Module defines a start function, this is used to invoke it.
-// * This is the outer-most ancestor of wasm.ModuleContext Context() during wasm.HostFunction invocations.
+// * This is the outer-most ancestor of wasm.Module Context() during wasm.Function invocations.
 // * This is the default context of wasm.Function when callers pass nil.
 //
 // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#start-function%E2%91%A0
@@ -100,7 +100,7 @@ type HostModuleConfig struct {
 	//		return x + y
 	//	}
 	//
-	// Host functions may also have an initial parameter (param[0]) of type context.Context or wasm.ModuleContext.
+	// Host functions may also have an initial parameter (param[0]) of type context.Context or wasm.Module.
 	//
 	// Ex. This uses a Go Context:
 	//
@@ -109,13 +109,13 @@ type HostModuleConfig struct {
 	//		return x + y + ctx.Value(extraKey).(uint32)
 	//	}
 	//
-	// The most sophisticated context is wasm.ModuleContext, which allows access to the Go context, but also
+	// The most sophisticated context is wasm.Module, which allows access to the Go context, but also
 	// allows writing to memory. This is important because there are only numeric types in Wasm. The only way to share other
 	// data is via writing memory and sharing offsets.
 	//
 	// Ex. This reads the parameters from!
 	//
-	//	addInts := func(ctx wasm.ModuleContext, offset uint32) uint32 {
+	//	addInts := func(ctx wasm.Module, offset uint32) uint32 {
 	//		x, _ := ctx.Memory().ReadUint32Le(offset)
 	//		y, _ := ctx.Memory().ReadUint32Le(offset + 4) // 32 bits == 4 bytes!
 	//		return x + y
diff --git a/internal/wasi/wasi.go b/internal/wasi/wasi.go
index 2e75635f..04b302c8 100644
--- a/internal/wasi/wasi.go
+++ b/internal/wasi/wasi.go
@@ -427,7 +427,7 @@ const (
 type SnapshotPreview1 interface {
 	// ArgsGet is the WASI function that reads command-line argument data (Args).
 	//
-	// There are two parameters. Both are offsets in wasm.ModuleContext Memory. If either are invalid due to
+	// There are two parameters. Both are offsets in wasm.Module Memory. If either are invalid due to
 	// memory constraints, this returns ErrnoFault.
 	//
 	// * argv - is the offset to begin writing argument offsets in uint32 little-endian encoding.
@@ -451,12 +451,12 @@ type SnapshotPreview1 interface {
 	// See ArgsSizesGet
 	// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#args_get
 	// See https://en.wikipedia.org/wiki/Null-terminated_string
-	ArgsGet(ctx wasm.ModuleContext, argv, argvBuf uint32) wasi.Errno
+	ArgsGet(ctx wasm.Module, argv, argvBuf uint32) wasi.Errno
 
 	// ArgsSizesGet is the WASI function named FunctionArgsSizesGet that reads command-line argument data (Args)
 	// sizes.
 	//
-	// There are two result parameters: these are offsets in the wasm.ModuleContext Memory to write
+	// There are two result parameters: these are offsets in the wasm.Module Memory to write
 	// corresponding sizes in uint32 little-endian encoding. If either are invalid due to memory constraints, this
 	// returns ErrnoFault.
 	//
@@ -479,11 +479,11 @@ type SnapshotPreview1 interface {
 	// See ArgsGet
 	// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#args_sizes_get
 	// See https://en.wikipedia.org/wiki/Null-terminated_string
-	ArgsSizesGet(ctx wasm.ModuleContext, resultArgc, resultArgvBufSize uint32) wasi.Errno
+	ArgsSizesGet(ctx wasm.Module, resultArgc, resultArgvBufSize uint32) wasi.Errno
 
 	// EnvironGet is the WASI function named FunctionEnvironGet that reads environment variables. (Environ)
 	//
-	// There are two parameters. Both are offsets in wasm.ModuleContext Memory. If either are invalid due to
+	// There are two parameters. Both are offsets in wasm.Module Memory. If either are invalid due to
 	// memory constraints, this returns ErrnoFault.
 	//
 	// * environ - is the offset to begin writing environment variables offsets in uint32 little-endian encoding.
@@ -507,12 +507,12 @@ type SnapshotPreview1 interface {
 	// See EnvironSizesGet
 	// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#environ_get
 	// See https://en.wikipedia.org/wiki/Null-terminated_string
-	EnvironGet(ctx wasm.ModuleContext, environ, environBuf uint32) wasi.Errno
+	EnvironGet(ctx wasm.Module, environ, environBuf uint32) wasi.Errno
 
 	// EnvironSizesGet is the WASI function named FunctionEnvironSizesGet that reads environment variable
 	// (Environ) sizes.
 	//
-	// There are two result parameters: these are offsets in the wasi.ModuleContext Memory to write
+	// There are two result parameters: these are offsets in the wasi.Module Memory to write
 	// corresponding sizes in uint32 little-endian encoding. If either are invalid due to memory constraints, this
 	// returns ErrnoFault.
 	//
@@ -536,10 +536,10 @@ type SnapshotPreview1 interface {
 	// See EnvironGet
 	// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#environ_sizes_get
 	// See https://en.wikipedia.org/wiki/Null-terminated_string
-	EnvironSizesGet(ctx wasm.ModuleContext, resultEnvironc, resultEnvironBufSize uint32) wasi.Errno
+	EnvironSizesGet(ctx wasm.Module, resultEnvironc, resultEnvironBufSize uint32) wasi.Errno
 
 	// ClockResGet is the WASI function named FunctionClockResGet and is stubbed for GrainLang per #271
-	ClockResGet(ctx wasm.ModuleContext, id uint32, resultResolution uint32) wasi.Errno
+	ClockResGet(ctx wasm.Module, id uint32, resultResolution uint32) wasi.Errno
 
 	// ClockTimeGet is the WASI function named FunctionClockTimeGet that returns the time value of a clock (time.Now).
 	//
@@ -561,13 +561,13 @@ type SnapshotPreview1 interface {
 	// Note: This is similar to `clock_gettime` in POSIX.
 	// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-clock_time_getid-clockid-precision-timestamp---errno-timestamp
 	// See https://linux.die.net/man/3/clock_gettime
-	ClockTimeGet(ctx wasm.ModuleContext, id uint32, precision uint64, resultTimestamp uint32) wasi.Errno
+	ClockTimeGet(ctx wasm.Module, id uint32, precision uint64, resultTimestamp uint32) wasi.Errno
 
 	// FdAdvise is the WASI function named FunctionFdAdvise and is stubbed for GrainLang per #271
-	FdAdvise(ctx wasm.ModuleContext, fd uint32, offset, len uint64, resultAdvice uint32) wasi.Errno
+	FdAdvise(ctx wasm.Module, fd uint32, offset, len uint64, resultAdvice uint32) wasi.Errno
 
 	// FdAllocate is the WASI function named FunctionFdAllocate and is stubbed for GrainLang per #271
-	FdAllocate(ctx wasm.ModuleContext, fd uint32, offset, len uint64, resultAdvice uint32) wasi.Errno
+	FdAllocate(ctx wasm.Module, fd uint32, offset, len uint64, resultAdvice uint32) wasi.Errno
 
 	// FdClose is the WASI function to close a file descriptor. This returns ErrnoBadf if the fd is invalid.
 	//
@@ -577,10 +577,10 @@ type SnapshotPreview1 interface {
 	// Note: This is similar to `close` in POSIX.
 	// See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#fd_close
 	// See https://linux.die.net/man/3/close
-	FdClose(ctx wasm.ModuleContext, fd uint32) wasi.Errno
+	FdClose(ctx wasm.Module, fd uint32) wasi.Errno
 
 	// FdDatasync is the WASI function named FunctionFdDatasync and is stubbed for GrainLang per #271
-	FdDatasync(ctx wasm.ModuleContext, fd uint32) wasi.Errno
+	FdDatasync(ctx wasm.Module, fd uint32) wasi.Errno
 
 	// FdFdstatGet is the WASI function to return the attributes of a file descriptor.
 	//
@@ -614,25 +614,25 @@ type SnapshotPreview1 interface {
 	// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fdstat
 	// See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#fd_fdstat_get
 	// See https://linux.die.net/man/3/fsync
-	FdFdstatGet(ctx wasm.ModuleContext, fd, resultFdstat uint32) wasi.Errno
+	FdFdstatGet(ctx wasm.Module, fd, resultFdstat uint32) wasi.Errno
 
 	// FdFdstatSetFlags is the WASI function named FunctionFdFdstatSetFlags and is stubbed for GrainLang per #271
-	FdFdstatSetFlags(ctx wasm.ModuleContext, fd uint32, flags uint32) wasi.Errno
+	FdFdstatSetFlags(ctx wasm.Module, fd uint32, flags uint32) wasi.Errno
 
 	// FdFdstatSetRights is the WASI function named FunctionFdFdstatSetRights and is stubbed for GrainLang per #271
-	FdFdstatSetRights(ctx wasm.ModuleContext, fd uint32, fsRightsBase, fsRightsInheriting uint64) wasi.Errno
+	FdFdstatSetRights(ctx wasm.Module, fd uint32, fsRightsBase, fsRightsInheriting uint64) wasi.Errno
 
 	// FdFilestatGet is the WASI function named FunctionFdFilestatGet
-	FdFilestatGet(ctx wasm.ModuleContext, fd uint32, resultBuf uint32) wasi.Errno
+	FdFilestatGet(ctx wasm.Module, fd uint32, resultBuf uint32) wasi.Errno
 
 	// FdFilestatSetSize is the WASI function named FunctionFdFilestatSetSize
-	FdFilestatSetSize(ctx wasm.ModuleContext, fd uint32, size uint64) wasi.Errno
+	FdFilestatSetSize(ctx wasm.Module, fd uint32, size uint64) wasi.Errno
 
 	// FdFilestatSetTimes is the WASI function named FunctionFdFilestatSetTimes
-	FdFilestatSetTimes(ctx wasm.ModuleContext, fd uint32, atim, mtim uint64, fstFlags uint32) wasi.Errno
+	FdFilestatSetTimes(ctx wasm.Module, fd uint32, atim, mtim uint64, fstFlags uint32) wasi.Errno
 
 	// FdPread is the WASI function named FunctionFdPread
-	FdPread(ctx wasm.ModuleContext, fd, iovs uint32, offset uint64, resultNread uint32) wasi.Errno
+	FdPread(ctx wasm.Module, fd, iovs uint32, offset uint64, resultNread uint32) wasi.Errno
 
 	// FdPrestatGet is the WASI function to return the prestat data of a file descriptor.
 	// This returns wasi.ErrnoBadf if the fd is invalid.
@@ -658,7 +658,7 @@ type SnapshotPreview1 interface {
 	// See FdPrestatDirName
 	// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#prestat
 	// See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#fd_prestat_get
-	FdPrestatGet(ctx wasm.ModuleContext, fd uint32, resultPrestat uint32) wasi.Errno
+	FdPrestatGet(ctx wasm.Module, fd uint32, resultPrestat uint32) wasi.Errno
 
 	// FdPrestatDirName is the WASI function to return the path of the pre-opened directory of a file descriptor.
 	//
@@ -684,11 +684,11 @@ type SnapshotPreview1 interface {
 	// Note: ImportFdPrestatDirName shows this signature in the WebAssembly 1.0 (20191205) Text Format.
 	// See FdPrestatGet
 	// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_prestat_dir_name
-	FdPrestatDirName(ctx wasm.ModuleContext, fd, path, pathLen uint32) wasi.Errno
+	FdPrestatDirName(ctx wasm.Module, fd, path, pathLen uint32) wasi.Errno
 	// TODO: FdPrestatDirName may have to return ErrnoNotdir if the type of the prestat data of `fd` is not a PrestatDir.
 
 	// FdPwrite is the WASI function named FunctionFdPwrite
-	FdPwrite(ctx wasm.ModuleContext, fd, iovs uint32, offset uint64, resultNwritten uint32) wasi.Errno
+	FdPwrite(ctx wasm.Module, fd, iovs uint32, offset uint64, resultNwritten uint32) wasi.Errno
 
 	// FdRead is the WASI function to read from a file descriptor.
 	//
@@ -733,22 +733,22 @@ type SnapshotPreview1 interface {
 	// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_read
 	// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#iovec
 	// See https://linux.die.net/man/3/readv
-	FdRead(ctx wasm.ModuleContext, fd, iovs, iovsCount, resultSize uint32) wasi.Errno
+	FdRead(ctx wasm.Module, fd, iovs, iovsCount, resultSize uint32) wasi.Errno
 
 	// FdReaddir is the WASI function named FunctionFdReaddir
-	FdReaddir(ctx wasm.ModuleContext, fd, buf, bufLen uint32, cookie uint64, resultBufused uint32) wasi.Errno
+	FdReaddir(ctx wasm.Module, fd, buf, bufLen uint32, cookie uint64, resultBufused uint32) wasi.Errno
 
 	// FdRenumber is the WASI function named FunctionFdRenumber
-	FdRenumber(ctx wasm.ModuleContext, fd, to uint32) wasi.Errno
+	FdRenumber(ctx wasm.Module, fd, to uint32) wasi.Errno
 
 	// FdSeek is the WASI function named FunctionFdSeek
-	FdSeek(ctx wasm.ModuleContext, fd uint32, offset uint64, whence uint32, resultNewoffset uint32) wasi.Errno
+	FdSeek(ctx wasm.Module, fd uint32, offset uint64, whence uint32, resultNewoffset uint32) wasi.Errno
 
 	// FdSync is the WASI function named FunctionFdSync
-	FdSync(ctx wasm.ModuleContext, fd uint32) wasi.Errno
+	FdSync(ctx wasm.Module, fd uint32) wasi.Errno
 
 	// FdTell is the WASI function named FunctionFdTell
-	FdTell(ctx wasm.ModuleContext, fd, resultOffset uint32) wasi.Errno
+	FdTell(ctx wasm.Module, fd, resultOffset uint32) wasi.Errno
 
 	// FdWrite is the WASI function to write to a file descriptor.
 	//
@@ -799,19 +799,19 @@ type SnapshotPreview1 interface {
 	// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#ciovec
 	// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_write
 	// See https://linux.die.net/man/3/writev
-	FdWrite(ctx wasm.ModuleContext, fd, iovs, iovsCount, resultSize uint32) wasi.Errno
+	FdWrite(ctx wasm.Module, fd, iovs, iovsCount, resultSize uint32) wasi.Errno
 
 	// PathCreateDirectory is the WASI function named FunctionPathCreateDirectory
-	PathCreateDirectory(ctx wasm.ModuleContext, fd, path, pathLen uint32) wasi.Errno
+	PathCreateDirectory(ctx wasm.Module, fd, path, pathLen uint32) wasi.Errno
 
 	// PathFilestatGet is the WASI function named FunctionPathFilestatGet
-	PathFilestatGet(ctx wasm.ModuleContext, fd, flags, path, pathLen, resultBuf uint32) wasi.Errno
+	PathFilestatGet(ctx wasm.Module, fd, flags, path, pathLen, resultBuf uint32) wasi.Errno
 
 	// PathFilestatSetTimes is the WASI function named FunctionPathFilestatSetTimes
-	PathFilestatSetTimes(ctx wasm.ModuleContext, fd, flags, path, pathLen uint32, atim, mtime uint64, fstFlags uint32) wasi.Errno
+	PathFilestatSetTimes(ctx wasm.Module, fd, flags, path, pathLen uint32, atim, mtime uint64, fstFlags uint32) wasi.Errno
 
 	// PathLink is the WASI function named FunctionPathLink
-	PathLink(ctx wasm.ModuleContext, oldFd, oldFlags, oldPath, oldPathLen, newFd, newPath, newPathLen uint32) wasi.Errno
+	PathLink(ctx wasm.Module, oldFd, oldFlags, oldPath, oldPathLen, newFd, newPath, newPathLen uint32) wasi.Errno
 
 	// PathOpen is the WASI function to open a file or directory. This returns ErrnoBadf if the fd is invalid.
 	//
@@ -849,25 +849,25 @@ type SnapshotPreview1 interface {
 	// Note: The returned file descriptor is not guaranteed to be the lowest-numbered file
 	// See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#path_open
 	// See https://linux.die.net/man/3/openat
-	PathOpen(ctx wasm.ModuleContext, fd, dirflags, path, pathLen, oflags uint32, fsRightsBase, fsRightsInheriting uint32, fdflags, resultOpenedFd uint32) wasi.Errno
+	PathOpen(ctx wasm.Module, fd, dirflags, path, pathLen, oflags uint32, fsRightsBase, fsRightsInheriting uint32, fdflags, resultOpenedFd uint32) wasi.Errno
 
 	// PathReadlink is the WASI function named FunctionPathReadlink
-	PathReadlink(ctx wasm.ModuleContext, fd, path, pathLen, buf, bufLen, resultBufused uint32) wasi.Errno
+	PathReadlink(ctx wasm.Module, fd, path, pathLen, buf, bufLen, resultBufused uint32) wasi.Errno
 
 	// PathRemoveDirectory is the WASI function named FunctionPathRemoveDirectory
-	PathRemoveDirectory(ctx wasm.ModuleContext, fd, path, pathLen uint32) wasi.Errno
+	PathRemoveDirectory(ctx wasm.Module, fd, path, pathLen uint32) wasi.Errno
 
 	// PathRename is the WASI function named FunctionPathRename
-	PathRename(ctx wasm.ModuleContext, fd, oldPath, oldPathLen, newFd, newPath, newPathLen uint32) wasi.Errno
+	PathRename(ctx wasm.Module, fd, oldPath, oldPathLen, newFd, newPath, newPathLen uint32) wasi.Errno
 
 	// PathSymlink is the WASI function named FunctionPathSymlink
-	PathSymlink(ctx wasm.ModuleContext, oldPath, oldPathLen, fd, newFd, newPath, newPathLen uint32) wasi.Errno
+	PathSymlink(ctx wasm.Module, oldPath, oldPathLen, fd, newFd, newPath, newPathLen uint32) wasi.Errno
 
 	// PathUnlinkFile is the WASI function named FunctionPathUnlinkFile
-	PathUnlinkFile(ctx wasm.ModuleContext, fd, path, pathLen uint32) wasi.Errno
+	PathUnlinkFile(ctx wasm.Module, fd, path, pathLen uint32) wasi.Errno
 
 	// PollOneoff is the WASI function named FunctionPollOneoff
-	PollOneoff(ctx wasm.ModuleContext, in, out, nsubscriptions, resultNevents uint32) wasi.Errno
+	PollOneoff(ctx wasm.Module, in, out, nsubscriptions, resultNevents uint32) wasi.Errno
 
 	// ProcExit is the WASI function that terminates the execution of the module with an exit code.
 	// An exit code of 0 indicates successful termination. The meanings of other values are not defined by WASI.
@@ -883,10 +883,10 @@ type SnapshotPreview1 interface {
 	ProcExit(rval uint32)
 
 	// ProcRaise is the WASI function named FunctionProcRaise
-	ProcRaise(ctx wasm.ModuleContext, sig uint32) wasi.Errno
+	ProcRaise(ctx wasm.Module, sig uint32) wasi.Errno
 
 	// SchedYield is the WASI function named FunctionSchedYield
-	SchedYield(ctx wasm.ModuleContext) wasi.Errno
+	SchedYield(ctx wasm.Module) wasi.Errno
 
 	// RandomGet is the WASI function named FunctionRandomGet that write random data in buffer (rand.Read()).
 	//
@@ -903,16 +903,16 @@ type SnapshotPreview1 interface {
 	//
 	// Note: ImportRandomGet shows this signature in the WebAssembly 1.0 (20191205) Text Format.
 	// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-random_getbuf-pointeru8-bufLen-size---errno
-	RandomGet(ctx wasm.ModuleContext, buf, bufLen uint32) wasi.Errno
+	RandomGet(ctx wasm.Module, buf, bufLen uint32) wasi.Errno
 
 	// SockRecv is the WASI function named FunctionSockRecv
-	SockRecv(ctx wasm.ModuleContext, fd, riData, riDataCount, riFlags, resultRoDataLen, resultRoFlags uint32) wasi.Errno
+	SockRecv(ctx wasm.Module, fd, riData, riDataCount, riFlags, resultRoDataLen, resultRoFlags uint32) wasi.Errno
 
 	// SockSend is the WASI function named FunctionSockSend
-	SockSend(ctx wasm.ModuleContext, fd, siData, siDataCount, siFlags, resultSoDataLen uint32) wasi.Errno
+	SockSend(ctx wasm.Module, fd, siData, siDataCount, siFlags, resultSoDataLen uint32) wasi.Errno
 
 	// SockShutdown is the WASI function named FunctionSockShutdown
-	SockShutdown(ctx wasm.ModuleContext, fd, how uint32) wasi.Errno
+	SockShutdown(ctx wasm.Module, fd, how uint32) wasi.Errno
 }
 
 type wasiAPI struct {
@@ -931,7 +931,7 @@ type wasiAPI struct {
 
 // SnapshotPreview1Functions returns all go functions that implement SnapshotPreview1.
 // These should be exported in the module named wasi.ModuleSnapshotPreview1.
-// See internalwasm.newGoFunc
+// See internalwasm.NewHostModule
 func SnapshotPreview1Functions(opts ...Option) (nameToGoFunc map[string]interface{}) {
 	a := NewAPI(opts...)
 	// Note: these are ordered per spec for consistency even if the resulting map can't guarantee that.
@@ -987,7 +987,7 @@ func SnapshotPreview1Functions(opts ...Option) (nameToGoFunc map[string]interfac
 }
 
 // ArgsGet implements SnapshotPreview1.ArgsGet
-func (a *wasiAPI) ArgsGet(ctx wasm.ModuleContext, argv, argvBuf uint32) wasi.Errno {
+func (a *wasiAPI) ArgsGet(ctx wasm.Module, argv, argvBuf uint32) wasi.Errno {
 	for _, arg := range a.args.nullTerminatedValues {
 		if !ctx.Memory().WriteUint32Le(argv, argvBuf) {
 			return wasi.ErrnoFault
@@ -1003,7 +1003,7 @@ func (a *wasiAPI) ArgsGet(ctx wasm.ModuleContext, argv, argvBuf uint32) wasi.Err
 }
 
 // ArgsSizesGet implements SnapshotPreview1.ArgsSizesGet
-func (a *wasiAPI) ArgsSizesGet(ctx wasm.ModuleContext, resultArgc, resultArgvBufSize uint32) wasi.Errno {
+func (a *wasiAPI) ArgsSizesGet(ctx wasm.Module, resultArgc, resultArgvBufSize uint32) wasi.Errno {
 	if !ctx.Memory().WriteUint32Le(resultArgc, uint32(len(a.args.nullTerminatedValues))) {
 		return wasi.ErrnoFault
 	}
@@ -1014,7 +1014,7 @@ func (a *wasiAPI) ArgsSizesGet(ctx wasm.ModuleContext, resultArgc, resultArgvBuf
 }
 
 // EnvironGet implements SnapshotPreview1.EnvironGet
-func (a *wasiAPI) EnvironGet(ctx wasm.ModuleContext, environ uint32, environBuf uint32) wasi.Errno {
+func (a *wasiAPI) EnvironGet(ctx wasm.Module, environ uint32, environBuf uint32) wasi.Errno {
 	// w.environ holds the environment variables in the form of "key=val\x00", so just copies it to the linear memory.
 	for _, env := range a.environ.nullTerminatedValues {
 		if !ctx.Memory().WriteUint32Le(environ, environBuf) {
@@ -1031,7 +1031,7 @@ func (a *wasiAPI) EnvironGet(ctx wasm.ModuleContext, environ uint32, environBuf
 }
 
 // EnvironSizesGet implements SnapshotPreview1.EnvironSizesGet
-func (a *wasiAPI) EnvironSizesGet(ctx wasm.ModuleContext, resultEnvironc uint32, resultEnvironBufSize uint32) wasi.Errno {
+func (a *wasiAPI) EnvironSizesGet(ctx wasm.Module, resultEnvironc uint32, resultEnvironBufSize uint32) wasi.Errno {
 	if !ctx.Memory().WriteUint32Le(resultEnvironc, uint32(len(a.environ.nullTerminatedValues))) {
 		return wasi.ErrnoFault
 	}
@@ -1043,12 +1043,12 @@ func (a *wasiAPI) EnvironSizesGet(ctx wasm.ModuleContext, resultEnvironc uint32,
 }
 
 // ClockResGet implements SnapshotPreview1.ClockResGet
-func (a *wasiAPI) ClockResGet(ctx wasm.ModuleContext, id uint32, resultResolution uint32) wasi.Errno {
+func (a *wasiAPI) ClockResGet(ctx wasm.Module, id uint32, resultResolution uint32) wasi.Errno {
 	return wasi.ErrnoNosys // stubbed for GrainLang per #271
 }
 
 // ClockTimeGet implements SnapshotPreview1.ClockTimeGet
-func (a *wasiAPI) ClockTimeGet(ctx wasm.ModuleContext, id uint32, precision uint64, resultTimestamp uint32) wasi.Errno {
+func (a *wasiAPI) ClockTimeGet(ctx wasm.Module, id uint32, precision uint64, resultTimestamp uint32) wasi.Errno {
 	// TODO: id and precision are currently ignored.
 	if !ctx.Memory().WriteUint64Le(resultTimestamp, a.timeNowUnixNano()) {
 		return wasi.ErrnoFault
@@ -1057,17 +1057,17 @@ func (a *wasiAPI) ClockTimeGet(ctx wasm.ModuleContext, id uint32, precision uint
 }
 
 // FdAdvise implements SnapshotPreview1.FdAdvise
-func (a *wasiAPI) FdAdvise(ctx wasm.ModuleContext, fd uint32, offset, len uint64, resultAdvice uint32) wasi.Errno {
+func (a *wasiAPI) FdAdvise(ctx wasm.Module, fd uint32, offset, len uint64, resultAdvice uint32) wasi.Errno {
 	return wasi.ErrnoNosys // stubbed for GrainLang per #271
 }
 
 // FdAllocate implements SnapshotPreview1.FdAllocate
-func (a *wasiAPI) FdAllocate(ctx wasm.ModuleContext, fd uint32, offset, len uint64) wasi.Errno {
+func (a *wasiAPI) FdAllocate(ctx wasm.Module, fd uint32, offset, len uint64) wasi.Errno {
 	return wasi.ErrnoNosys // stubbed for GrainLang per #271
 }
 
 // FdClose implements SnapshotPreview1.FdClose
-func (a *wasiAPI) FdClose(ctx wasm.ModuleContext, fd uint32) wasi.Errno {
+func (a *wasiAPI) FdClose(ctx wasm.Module, fd uint32) wasi.Errno {
 	f, ok := a.opened[fd]
 	if !ok {
 		return wasi.ErrnoBadf
@@ -1083,13 +1083,13 @@ func (a *wasiAPI) FdClose(ctx wasm.ModuleContext, fd uint32) wasi.Errno {
 }
 
 // FdDatasync implements SnapshotPreview1.FdDatasync
-func (a *wasiAPI) FdDatasync(ctx wasm.ModuleContext, fd uint32) wasi.Errno {
+func (a *wasiAPI) FdDatasync(ctx wasm.Module, fd uint32) wasi.Errno {
 	return wasi.ErrnoNosys // stubbed for GrainLang per #271
 }
 
 // FdFdstatGet implements SnapshotPreview1.FdFdstatGet
 // TODO: Currently FdFdstatget implements nothing except returning fake fs_right_inheriting
-func (a *wasiAPI) FdFdstatGet(ctx wasm.ModuleContext, fd uint32, resultStat uint32) wasi.Errno {
+func (a *wasiAPI) FdFdstatGet(ctx wasm.Module, fd uint32, resultStat uint32) wasi.Errno {
 	if _, ok := a.opened[fd]; !ok {
 		return wasi.ErrnoBadf
 	}
@@ -1101,7 +1101,7 @@ func (a *wasiAPI) FdFdstatGet(ctx wasm.ModuleContext, fd uint32, resultStat uint
 
 // FdPrestatGet implements SnapshotPreview1.FdPrestatGet
 // TODO: Currently FdPrestatGet implements nothing except returning ErrnoBadf
-func (a *wasiAPI) FdPrestatGet(ctx wasm.ModuleContext, fd uint32, bufPtr uint32) wasi.Errno {
+func (a *wasiAPI) FdPrestatGet(ctx wasm.Module, fd uint32, bufPtr uint32) wasi.Errno {
 	if _, ok := a.opened[fd]; !ok {
 		return wasi.ErrnoBadf
 	}
@@ -1109,37 +1109,37 @@ func (a *wasiAPI) FdPrestatGet(ctx wasm.ModuleContext, fd uint32, bufPtr uint32)
 }
 
 // FdFdstatSetFlags implements SnapshotPreview1.FdFdstatSetFlags
-func (a *wasiAPI) FdFdstatSetFlags(ctx wasm.ModuleContext, fd uint32, flags uint32) wasi.Errno {
+func (a *wasiAPI) FdFdstatSetFlags(ctx wasm.Module, fd uint32, flags uint32) wasi.Errno {
 	return wasi.ErrnoNosys // stubbed for GrainLang per #271
 }
 
 // FdFdstatSetRights implements SnapshotPreview1.FdFdstatSetRights
-func (a *wasiAPI) FdFdstatSetRights(ctx wasm.ModuleContext, fd uint32, fsRightsBase, fsRightsInheriting uint64) wasi.Errno {
+func (a *wasiAPI) FdFdstatSetRights(ctx wasm.Module, fd uint32, fsRightsBase, fsRightsInheriting uint64) wasi.Errno {
 	return wasi.ErrnoNosys // stubbed for GrainLang per #271
 }
 
 // FdFilestatGet implements SnapshotPreview1.FdFilestatGet
-func (a *wasiAPI) FdFilestatGet(ctx wasm.ModuleContext, fd uint32, resultBuf uint32) wasi.Errno {
+func (a *wasiAPI) FdFilestatGet(ctx wasm.Module, fd uint32, resultBuf uint32) wasi.Errno {
 	return wasi.ErrnoNosys // stubbed for GrainLang per #271
 }
 
 // FdFilestatSetSize implements SnapshotPreview1.FdFilestatSetSize
-func (a *wasiAPI) FdFilestatSetSize(ctx wasm.ModuleContext, fd uint32, size uint64) wasi.Errno {
+func (a *wasiAPI) FdFilestatSetSize(ctx wasm.Module, fd uint32, size uint64) wasi.Errno {
 	return wasi.ErrnoNosys // stubbed for GrainLang per #271
 }
 
 // FdFilestatSetTimes implements SnapshotPreview1.FdFilestatSetTimes
-func (a *wasiAPI) FdFilestatSetTimes(ctx wasm.ModuleContext, fd uint32, atim, mtim uint64, fstFlags uint32) wasi.Errno {
+func (a *wasiAPI) FdFilestatSetTimes(ctx wasm.Module, fd uint32, atim, mtim uint64, fstFlags uint32) wasi.Errno {
 	return wasi.ErrnoNosys // stubbed for GrainLang per #271
 }
 
 // FdPread implements SnapshotPreview1.FdPread
-func (a *wasiAPI) FdPread(ctx wasm.ModuleContext, fd, iovs uint32, offset uint64, resultNread uint32) wasi.Errno {
+func (a *wasiAPI) FdPread(ctx wasm.Module, fd, iovs uint32, offset uint64, resultNread uint32) wasi.Errno {
 	return wasi.ErrnoNosys // stubbed for GrainLang per #271
 }
 
 // FdPrestatDirName implements SnapshotPreview1.FdPrestatDirName
-func (a *wasiAPI) FdPrestatDirName(ctx wasm.ModuleContext, fd uint32, pathPtr uint32, pathLen uint32) wasi.Errno {
+func (a *wasiAPI) FdPrestatDirName(ctx wasm.Module, fd uint32, pathPtr uint32, pathLen uint32) wasi.Errno {
 	f, ok := a.opened[fd]
 	if !ok {
 		return wasi.ErrnoBadf
@@ -1158,12 +1158,12 @@ func (a *wasiAPI) FdPrestatDirName(ctx wasm.ModuleContext, fd uint32, pathPtr ui
 }
 
 // FdPwrite implements SnapshotPreview1.FdPwrite
-func (a *wasiAPI) FdPwrite(ctx wasm.ModuleContext, fd, iovs uint32, offset uint64, resultNwritten uint32) wasi.Errno {
+func (a *wasiAPI) FdPwrite(ctx wasm.Module, fd, iovs uint32, offset uint64, resultNwritten uint32) wasi.Errno {
 	return wasi.ErrnoNosys // stubbed for GrainLang per #271
 }
 
 // FdRead implements SnapshotPreview1.FdRead
-func (a *wasiAPI) FdRead(ctx wasm.ModuleContext, fd, iovs, iovsCount, resultSize uint32) wasi.Errno {
+func (a *wasiAPI) FdRead(ctx wasm.Module, fd, iovs, iovsCount, resultSize uint32) wasi.Errno {
 	var reader io.Reader
 
 	switch fd {
@@ -1207,32 +1207,32 @@ func (a *wasiAPI) FdRead(ctx wasm.ModuleContext, fd, iovs, iovsCount, resultSize
 }
 
 // FdReaddir implements SnapshotPreview1.FdReaddir
-func (a *wasiAPI) FdReaddir(ctx wasm.ModuleContext, fd, buf, bufLen uint32, cookie uint64, resultBufused uint32) wasi.Errno {
+func (a *wasiAPI) FdReaddir(ctx wasm.Module, fd, buf, bufLen uint32, cookie uint64, resultBufused uint32) wasi.Errno {
 	return wasi.ErrnoNosys // stubbed for GrainLang per #271
 }
 
 // FdRenumber implements SnapshotPreview1.FdRenumber
-func (a *wasiAPI) FdRenumber(ctx wasm.ModuleContext, fd, to uint32) wasi.Errno {
+func (a *wasiAPI) FdRenumber(ctx wasm.Module, fd, to uint32) wasi.Errno {
 	return wasi.ErrnoNosys // stubbed for GrainLang per #271
 }
 
 // FdSeek implements SnapshotPreview1.FdSeek
-func (a *wasiAPI) FdSeek(ctx wasm.ModuleContext, fd uint32, offset uint64, whence uint32, resultNewoffset uint32) wasi.Errno {
+func (a *wasiAPI) FdSeek(ctx wasm.Module, fd uint32, offset uint64, whence uint32, resultNewoffset uint32) wasi.Errno {
 	return wasi.ErrnoNosys // stubbed for GrainLang per #271
 }
 
 // FdSync implements SnapshotPreview1.FdSync
-func (a *wasiAPI) FdSync(ctx wasm.ModuleContext, fd uint32) wasi.Errno {
+func (a *wasiAPI) FdSync(ctx wasm.Module, fd uint32) wasi.Errno {
 	return wasi.ErrnoNosys // stubbed for GrainLang per #271
 }
 
 // FdTell implements SnapshotPreview1.FdTell
-func (a *wasiAPI) FdTell(ctx wasm.ModuleContext, fd, resultOffset uint32) wasi.Errno {
+func (a *wasiAPI) FdTell(ctx wasm.Module, fd, resultOffset uint32) wasi.Errno {
 	return wasi.ErrnoNosys // stubbed for GrainLang per #271
 }
 
 // FdWrite implements SnapshotPreview1.FdWrite
-func (a *wasiAPI) FdWrite(ctx wasm.ModuleContext, fd, iovs, iovsCount, resultSize uint32) wasi.Errno {
+func (a *wasiAPI) FdWrite(ctx wasm.Module, fd, iovs, iovsCount, resultSize uint32) wasi.Errno {
 	var writer io.Writer
 
 	switch fd {
@@ -1276,27 +1276,27 @@ func (a *wasiAPI) FdWrite(ctx wasm.ModuleContext, fd, iovs, iovsCount, resultSiz
 }
 
 // PathCreateDirectory implements SnapshotPreview1.PathCreateDirectory
-func (a *wasiAPI) PathCreateDirectory(ctx wasm.ModuleContext, fd, path, pathLen uint32) wasi.Errno {
+func (a *wasiAPI) PathCreateDirectory(ctx wasm.Module, fd, path, pathLen uint32) wasi.Errno {
 	return wasi.ErrnoNosys // stubbed for GrainLang per #271
 }
 
 // PathFilestatGet implements SnapshotPreview1.PathFilestatGet
-func (a *wasiAPI) PathFilestatGet(ctx wasm.ModuleContext, fd, flags, path, pathLen, resultBuf uint32) wasi.Errno {
+func (a *wasiAPI) PathFilestatGet(ctx wasm.Module, fd, flags, path, pathLen, resultBuf uint32) wasi.Errno {
 	return wasi.ErrnoNosys // stubbed for GrainLang per #271
 }
 
 // PathFilestatSetTimes implements SnapshotPreview1.PathFilestatSetTimes
-func (a *wasiAPI) PathFilestatSetTimes(ctx wasm.ModuleContext, fd, flags, path, pathLen uint32, atim, mtime uint64, fstFlags uint32) wasi.Errno {
+func (a *wasiAPI) PathFilestatSetTimes(ctx wasm.Module, fd, flags, path, pathLen uint32, atim, mtime uint64, fstFlags uint32) wasi.Errno {
 	return wasi.ErrnoNosys // stubbed for GrainLang per #271
 }
 
 // PathLink implements SnapshotPreview1.PathLink
-func (a *wasiAPI) PathLink(ctx wasm.ModuleContext, oldFd, oldFlags, oldPath, oldPathLen, newFd, newPath, newPathLen uint32) wasi.Errno {
+func (a *wasiAPI) PathLink(ctx wasm.Module, oldFd, oldFlags, oldPath, oldPathLen, newFd, newPath, newPathLen uint32) wasi.Errno {
 	return wasi.ErrnoNosys // stubbed for GrainLang per #271
 }
 
 // PathOpen implements SnapshotPreview1.PathOpen
-func (a *wasiAPI) PathOpen(ctx wasm.ModuleContext, fd, dirflags, path, pathLen, oflags uint32, fsRightsBase,
+func (a *wasiAPI) PathOpen(ctx wasm.Module, fd, dirflags, path, pathLen, oflags uint32, fsRightsBase,
 	fsRightsInheriting uint64, fdflags, resultOpenedFd uint32) (errno wasi.Errno) {
 	dir, ok := a.opened[fd]
 	if !ok || dir.fileSys == nil {
@@ -1336,32 +1336,32 @@ func (a *wasiAPI) PathOpen(ctx wasm.ModuleContext, fd, dirflags, path, pathLen,
 }
 
 // PathReadlink implements SnapshotPreview1.PathReadlink
-func (a *wasiAPI) PathReadlink(ctx wasm.ModuleContext, fd, path, pathLen, buf, bufLen, resultBufused uint32) wasi.Errno {
+func (a *wasiAPI) PathReadlink(ctx wasm.Module, fd, path, pathLen, buf, bufLen, resultBufused uint32) wasi.Errno {
 	return wasi.ErrnoNosys // stubbed for GrainLang per #271
 }
 
 // PathRemoveDirectory implements SnapshotPreview1.PathRemoveDirectory
-func (a *wasiAPI) PathRemoveDirectory(ctx wasm.ModuleContext, fd, path, pathLen uint32) wasi.Errno {
+func (a *wasiAPI) PathRemoveDirectory(ctx wasm.Module, fd, path, pathLen uint32) wasi.Errno {
 	return wasi.ErrnoNosys // stubbed for GrainLang per #271
 }
 
 // PathRename implements SnapshotPreview1.PathRename
-func (a *wasiAPI) PathRename(ctx wasm.ModuleContext, fd, oldPath, oldPathLen, newFd, newPath, newPathLen uint32) wasi.Errno {
+func (a *wasiAPI) PathRename(ctx wasm.Module, fd, oldPath, oldPathLen, newFd, newPath, newPathLen uint32) wasi.Errno {
 	return wasi.ErrnoNosys // stubbed for GrainLang per #271
 }
 
 // PathSymlink implements SnapshotPreview1.PathSymlink
-func (a *wasiAPI) PathSymlink(ctx wasm.ModuleContext, oldPath, oldPathLen, fd, newFd, newPath, newPathLen uint32) wasi.Errno {
+func (a *wasiAPI) PathSymlink(ctx wasm.Module, oldPath, oldPathLen, fd, newFd, newPath, newPathLen uint32) wasi.Errno {
 	return wasi.ErrnoNosys // stubbed for GrainLang per #271
 }
 
 // PathUnlinkFile implements SnapshotPreview1.PathUnlinkFile
-func (a *wasiAPI) PathUnlinkFile(ctx wasm.ModuleContext, fd, path, pathLen uint32) wasi.Errno {
+func (a *wasiAPI) PathUnlinkFile(ctx wasm.Module, fd, path, pathLen uint32) wasi.Errno {
 	return wasi.ErrnoNosys // stubbed for GrainLang per #271
 }
 
 // PollOneoff implements SnapshotPreview1.PollOneoff
-func (a *wasiAPI) PollOneoff(ctx wasm.ModuleContext, in, out, nsubscriptions, resultNevents uint32) wasi.Errno {
+func (a *wasiAPI) PollOneoff(ctx wasm.Module, in, out, nsubscriptions, resultNevents uint32) wasi.Errno {
 	return wasi.ErrnoNosys // stubbed for GrainLang per #271
 }
 
@@ -1373,17 +1373,17 @@ func (a *wasiAPI) ProcExit(exitCode uint32) {
 }
 
 // ProcRaise implements SnapshotPreview1.ProcRaise
-func (a *wasiAPI) ProcRaise(ctx wasm.ModuleContext, sig uint32) wasi.Errno {
+func (a *wasiAPI) ProcRaise(ctx wasm.Module, sig uint32) wasi.Errno {
 	return wasi.ErrnoNosys // stubbed for GrainLang per #271
 }
 
 // SchedYield implements SnapshotPreview1.SchedYield
-func (a *wasiAPI) SchedYield(ctx wasm.ModuleContext) wasi.Errno {
+func (a *wasiAPI) SchedYield(ctx wasm.Module) wasi.Errno {
 	return wasi.ErrnoNosys // stubbed for GrainLang per #271
 }
 
 // RandomGet implements SnapshotPreview1.RandomGet
-func (a *wasiAPI) RandomGet(ctx wasm.ModuleContext, buf uint32, bufLen uint32) (errno wasi.Errno) {
+func (a *wasiAPI) RandomGet(ctx wasm.Module, buf uint32, bufLen uint32) (errno wasi.Errno) {
 	randomBytes := make([]byte, bufLen)
 	err := a.randSource(randomBytes)
 	if err != nil {
@@ -1399,17 +1399,17 @@ func (a *wasiAPI) RandomGet(ctx wasm.ModuleContext, buf uint32, bufLen uint32) (
 }
 
 // SockRecv implements SnapshotPreview1.SockRecv
-func (a *wasiAPI) SockRecv(ctx wasm.ModuleContext, fd, riData, riDataCount, riFlags, resultRoDataLen, resultRoFlags uint32) wasi.Errno {
+func (a *wasiAPI) SockRecv(ctx wasm.Module, fd, riData, riDataCount, riFlags, resultRoDataLen, resultRoFlags uint32) wasi.Errno {
 	return wasi.ErrnoNosys // stubbed for GrainLang per #271
 }
 
 // SockSend implements SnapshotPreview1.SockSend
-func (a *wasiAPI) SockSend(ctx wasm.ModuleContext, fd, siData, siDataCount, siFlags, resultSoDataLen uint32) wasi.Errno {
+func (a *wasiAPI) SockSend(ctx wasm.Module, fd, siData, siDataCount, siFlags, resultSoDataLen uint32) wasi.Errno {
 	return wasi.ErrnoNosys // stubbed for GrainLang per #271
 }
 
 // SockShutdown implements SnapshotPreview1.SockShutdown
-func (a *wasiAPI) SockShutdown(ctx wasm.ModuleContext, fd, how uint32) wasi.Errno {
+func (a *wasiAPI) SockShutdown(ctx wasm.Module, fd, how uint32) wasi.Errno {
 	return wasi.ErrnoNosys // stubbed for GrainLang per #271
 }
 
diff --git a/internal/wasm/binary/encoder.go b/internal/wasm/binary/encoder.go
index 975f08bb..a6810750 100644
--- a/internal/wasm/binary/encoder.go
+++ b/internal/wasm/binary/encoder.go
@@ -10,6 +10,10 @@ var sizePrefixedName = []byte{4, 'n', 'a', 'm', 'e'}
 // Note: If saving to a file, the conventional extension is wasm
 // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-format%E2%91%A0
 func EncodeModule(m *wasm.Module) (bytes []byte) {
+	if m.SectionElementCount(wasm.SectionIDHostFunction) > 0 {
+		// TODO: See if there's a way to serialize reflect.Value references, potentially by name lookup in a store.
+		panic("BUG: HostFunctionSection is not encodable")
+	}
 	bytes = append(Magic, version...)
 	if m.SectionElementCount(wasm.SectionIDType) > 0 {
 		bytes = append(bytes, encodeTypeSection(m.TypeSection)...)
diff --git a/internal/wasm/counts.go b/internal/wasm/counts.go
index 96865ea2..14260e55 100644
--- a/internal/wasm/counts.go
+++ b/internal/wasm/counts.go
@@ -42,6 +42,7 @@ func (m *Module) importCount(et ExternType) (res uint32) {
 // For example...
 // * SectionIDType returns the count of FunctionType
 // * SectionIDCustom returns one if the NameSection is present
+// * SectionIDHostFunction returns the count of HostFunctionSection
 // * SectionIDExport returns the count of unique export names
 func (m *Module) SectionElementCount(sectionID SectionID) uint32 { // element as in vector elements!
 	switch sectionID {
@@ -75,6 +76,8 @@ func (m *Module) SectionElementCount(sectionID SectionID) uint32 { // element as
 		return uint32(len(m.CodeSection))
 	case SectionIDData:
 		return uint32(len(m.DataSection))
+	case SectionIDHostFunction:
+		return uint32(len(m.HostFunctionSection))
 	default:
 		panic(fmt.Errorf("BUG: unknown section: %d", sectionID))
 	}
diff --git a/internal/wasm/global.go b/internal/wasm/global.go
index 679ebfc2..6c453748 100644
--- a/internal/wasm/global.go
+++ b/internal/wasm/global.go
@@ -121,27 +121,3 @@ func (g globalF64) Get() uint64 {
 func (g globalF64) String() string {
 	return fmt.Sprintf("global(%f)", publicwasm.DecodeF64(g.Get()))
 }
-
-// Global implements wasm.Module Global
-func (m *PublicModule) Global(name string) publicwasm.Global {
-	exp, err := m.instance.getExport(name, ExternTypeGlobal)
-	if err != nil {
-		return nil
-	}
-	if exp.Global.Type.Mutable {
-		return &mutableGlobal{exp.Global}
-	}
-	valType := exp.Global.Type.ValType
-	switch valType {
-	case ValueTypeI32:
-		return globalI32(exp.Global.Val)
-	case ValueTypeI64:
-		return globalI64(exp.Global.Val)
-	case ValueTypeF32:
-		return globalF32(exp.Global.Val)
-	case ValueTypeF64:
-		return globalF64(exp.Global.Val)
-	default:
-		panic(fmt.Errorf("BUG: unknown value type %X", valType))
-	}
-}
diff --git a/internal/wasm/gofunc.go b/internal/wasm/gofunc.go
index 4e9a517d..7a31de07 100644
--- a/internal/wasm/gofunc.go
+++ b/internal/wasm/gofunc.go
@@ -20,29 +20,14 @@ const (
 	// FunctionKindGoContext is a function implemented in Go, with a signature matching FunctionType, except arg zero is
 	// a context.Context.
 	FunctionKindGoContext
-	// FunctionKindGoModuleContext is a function implemented in Go, with a signature matching FunctionType, except arg zero is
-	// a ModuleContext.
-	FunctionKindGoModuleContext
+	// FunctionKindGoModule is a function implemented in Go, with a signature matching FunctionType, except arg
+	// zero is a Module.
+	FunctionKindGoModule
 )
 
-// goFunc binds a WebAssembly 1.0 (20191205) Type Use to a Go func signature.
-type goFunc struct {
-	// functionKind is never FunctionKindWasm
-	functionKind FunctionKind
-	functionType *FunctionType
-	goFunc       *reflect.Value
-}
-
-func newGoFunc(i interface{}) (hf *goFunc, err error) {
-	fn := reflect.ValueOf(i)
-	hf = &goFunc{goFunc: &fn}
-	hf.functionKind, hf.functionType, _, err = getFunctionType(hf.goFunc, false)
-	return
-}
-
 // Below are reflection code to get the interface type used to parse functions and set values.
 
-var moduleContextType = reflect.TypeOf((*publicwasm.ModuleContext)(nil)).Elem()
+var moduleType = reflect.TypeOf((*publicwasm.Module)(nil)).Elem()
 var goContextType = reflect.TypeOf((*context.Context)(nil)).Elem()
 var errorType = reflect.TypeOf((*error)(nil)).Elem()
 
@@ -54,8 +39,8 @@ func GetHostFunctionCallContextValue(fk FunctionKind, ctx *ModuleContext) *refle
 		val := reflect.New(goContextType).Elem()
 		val.Set(reflect.ValueOf(ctx.Context()))
 		return &val
-	case FunctionKindGoModuleContext:
-		val := reflect.New(moduleContextType).Elem()
+	case FunctionKindGoModule:
+		val := reflect.New(moduleType).Elem()
 		val.Set(reflect.ValueOf(ctx))
 		return &val
 	}
@@ -72,19 +57,8 @@ func getFunctionType(fn *reflect.Value, allowErrorResult bool) (fk FunctionKind,
 	}
 
 	pOffset := 0
-	pCount := p.NumIn()
-	fk = FunctionKindGoNoContext
-	if pCount > 0 && p.In(0).Kind() == reflect.Interface {
-		p0 := p.In(0)
-		if p0.Implements(moduleContextType) {
-			fk = FunctionKindGoModuleContext
-			pOffset = 1
-			pCount--
-		} else if p0.Implements(goContextType) {
-			fk = FunctionKindGoContext
-			pOffset = 1
-			pCount--
-		}
+	if fk = kind(p); fk != FunctionKindGoNoContext {
+		pOffset = 1
 	}
 
 	rCount := p.NumOut()
@@ -101,7 +75,7 @@ func getFunctionType(fn *reflect.Value, allowErrorResult bool) (fk FunctionKind,
 		}
 	}
 
-	ft = &FunctionType{Params: make([]ValueType, pCount), Results: make([]ValueType, rCount)}
+	ft = &FunctionType{Params: make([]ValueType, p.NumIn()-pOffset), Results: make([]ValueType, rCount)}
 
 	for i := 0; i < len(ft.Params); i++ {
 		pI := p.In(i + pOffset)
@@ -112,8 +86,8 @@ func getFunctionType(fn *reflect.Value, allowErrorResult bool) (fk FunctionKind,
 
 		// Now, we will definitely err, decide which message is best
 		var arg0Type reflect.Type
-		if hc := pI.Implements(moduleContextType); hc {
-			arg0Type = moduleContextType
+		if hc := pI.Implements(moduleType); hc {
+			arg0Type = moduleType
 		} else if gc := pI.Implements(goContextType); gc {
 			arg0Type = goContextType
 		}
@@ -144,6 +118,19 @@ func getFunctionType(fn *reflect.Value, allowErrorResult bool) (fk FunctionKind,
 	return
 }
 
+func kind(p reflect.Type) FunctionKind {
+	pCount := p.NumIn()
+	if pCount > 0 && p.In(0).Kind() == reflect.Interface {
+		p0 := p.In(0)
+		if p0.Implements(moduleType) {
+			return FunctionKindGoModule
+		} else if p0.Implements(goContextType) {
+			return FunctionKindGoContext
+		}
+	}
+	return FunctionKindGoNoContext
+}
+
 func getTypeOf(kind reflect.Kind) (ValueType, bool) {
 	switch kind {
 	case reflect.Float64:
diff --git a/internal/wasm/host.go b/internal/wasm/host.go
index 12303259..eef5fa5f 100644
--- a/internal/wasm/host.go
+++ b/internal/wasm/host.go
@@ -1,189 +1,104 @@
 package internalwasm
 
 import (
-	"context"
 	"fmt"
-
-	publicwasm "github.com/tetratelabs/wazero/wasm"
+	"reflect"
+	"sort"
 )
 
-// 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,
-		memory: instance.Memory,
-		Module: instance,
+// NewHostModule is defined internally for use in WASI tests and to keep the code size in the root directory small.
+func NewHostModule(moduleName string, nameToGoFunc map[string]interface{}) (*Module, error) {
+	hostFunctionCount := uint32(len(nameToGoFunc))
+	if hostFunctionCount == 0 {
+		if moduleName != "" {
+			return &Module{NameSection: &NameSection{ModuleName: moduleName}}, nil
+		} else {
+			return &Module{}, nil
+		}
 	}
-}
-
-// ModuleContext implements wasm.ModuleContext and wasm.Module
-type ModuleContext struct {
-	// ctx is returned by Context and overridden WithContext
-	ctx context.Context
-	// engine is used to implement function.Call
-	engine Engine
-	// Module is exported for spectests
-	Module *ModuleInstance
-	// memory is returned by Memory and overridden WithMemory
-	memory publicwasm.Memory
-}
 
-// WithContext allows overriding context without re-allocation when the result would be the same.
-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}
+	m := &Module{
+		NameSection:         &NameSection{ModuleName: moduleName, FunctionNames: make([]*NameAssoc, 0, hostFunctionCount)},
+		HostFunctionSection: make([]*reflect.Value, 0, hostFunctionCount),
+		ExportSection:       make(map[string]*Export, hostFunctionCount),
 	}
-	return c
-}
 
-// WithMemory allows overriding memory without re-allocation when the result would be the same.
-func (c *ModuleContext) WithMemory(memory *MemoryInstance) *ModuleContext {
-	// only re-allocate if it will change the effective memory
-	if c.memory == nil || (memory != nil && memory.Max != nil && *memory.Max > 0 && memory != c.memory) {
-		return &ModuleContext{engine: c.engine, Module: c.Module, memory: memory, ctx: c.ctx}
+	// Ensure insertion order is consistent
+	names := make([]string, 0, hostFunctionCount)
+	for k := range nameToGoFunc {
+		names = append(names, k)
 	}
-	return c
-}
+	sort.Strings(names)
 
-// Context implements wasm.ModuleContext Context
-func (c *ModuleContext) Context() context.Context {
-	return c.ctx
-}
-
-// Memory implements wasm.ModuleContext Memory
-func (c *ModuleContext) Memory() publicwasm.Memory {
-	return c.memory
-}
+	for idx := Index(0); idx < hostFunctionCount; idx++ {
+		name := names[idx]
+		fn := reflect.ValueOf(nameToGoFunc[name])
+		_, functionType, _, err := getFunctionType(&fn, false)
+		if err != nil {
+			return nil, fmt.Errorf("func[%s] %w", name, err)
+		}
 
-// Function implements wasm.ModuleContext Function
-func (c *ModuleContext) Function(name string) publicwasm.Function {
-	exp, err := c.Module.getExport(name, ExternTypeFunc)
-	if err != nil {
-		return nil
+		m.FunctionSection = append(m.FunctionSection, m.maybeAddType(functionType))
+		m.HostFunctionSection = append(m.HostFunctionSection, &fn)
+		m.ExportSection[name] = &Export{Type: ExternTypeFunc, Name: name, Index: idx}
+		m.NameSection.FunctionNames = append(m.NameSection.FunctionNames, &NameAssoc{Index: idx, Name: name})
 	}
-	return &exportedFunction{module: c, function: exp.Function}
-}
-
-// exportedFunction wraps FunctionInstance so that it is called in context of the exporting module.
-type exportedFunction struct {
-	module   *ModuleContext
-	function *FunctionInstance
+	return m, nil
 }
 
-// ParamTypes implements wasm.Function ParamTypes
-func (f *exportedFunction) ParamTypes() []publicwasm.ValueType {
-	return f.function.ParamTypes()
-}
-
-// ResultTypes implements wasm.Function ResultTypes
-func (f *exportedFunction) ResultTypes() []publicwasm.ValueType {
-	return f.function.ResultTypes()
-}
+func (m *Module) maybeAddType(ft *FunctionType) Index {
+	for i, t := range m.TypeSection {
+		if t.EqualsSignature(ft.Params, ft.Results) {
+			return Index(i)
+		}
+	}
 
-// Call implements wasm.Function Call in the ModuleContext of the exporting module.
-func (f *exportedFunction) Call(ctx context.Context, params ...uint64) ([]uint64, error) {
-	modCtx := f.module.WithContext(ctx)
-	return f.module.engine.Call(modCtx, f.function, params...)
+	result := m.SectionElementCount(SectionIDType)
+	m.TypeSection = append(m.TypeSection, ft)
+	return result
 }
 
-// NewHostModule is defined internally for use in WASI tests and to keep the code size in the root directory small.
-//
-// TOOD(adrian): make this goroutine-safe like store.Instantiate.
-func (s *Store) NewHostModule(moduleName string, nameToGoFunc map[string]interface{}) (*HostModule, error) {
-	exportCount := len(nameToGoFunc)
-	ret := &HostModule{name: moduleName, NameToFunction: make(map[string]*FunctionInstance, exportCount)}
-	hostModule := &ModuleInstance{
-		Name:       moduleName,
-		Exports:    make(map[string]*ExportInstance, exportCount),
-		hostModule: ret,
+func (m *Module) validateHostFunctions() error {
+	functionCount := m.SectionElementCount(SectionIDFunction)
+	hostFunctionCount := m.SectionElementCount(SectionIDHostFunction)
+	if functionCount == 0 && hostFunctionCount == 0 {
+		return nil
 	}
 
-	if err := s.requireModuleName(moduleName); err != nil {
-		return nil, err
+	typeCount := m.SectionElementCount(SectionIDType)
+	if hostFunctionCount != functionCount {
+		return fmt.Errorf("host function count (%d) != function count (%d)", hostFunctionCount, functionCount)
 	}
 
-	for name, goFunc := range nameToGoFunc {
-		hf, err := newGoFunc(goFunc)
+	for idx, typeIndex := range m.FunctionSection {
+		_, ft, _, err := getFunctionType(m.HostFunctionSection[idx], false)
 		if err != nil {
-			s.deleteModule(moduleName)
-			return nil, fmt.Errorf("func[%s] %w", name, err)
-		}
-
-		f := &FunctionInstance{
-			Module: hostModule,
-			Name:   fmt.Sprintf("%s.%s", hostModule.Name, name),
-			Kind:   hf.functionKind,
-			Type:   hf.functionType,
-			GoFunc: hf.goFunc,
+			return fmt.Errorf("%s is not a valid go func: %w", m.funcDesc(SectionIDHostFunction, Index(idx)), err)
 		}
-		hostModule.Exports[name] = &ExportInstance{Type: ExternTypeFunc, Function: f}
-		hostModule.Functions = append(hostModule.Functions, f)
-		ret.NameToFunction[name] = f
 
-		if err = s.compileHostFunction(f); err != nil {
-			s.deleteModule(moduleName)
-			return nil, err
+		if typeIndex >= typeCount {
+			return fmt.Errorf("%s type section index out of range: %d", m.funcDesc(SectionIDHostFunction, Index(idx)), typeIndex)
 		}
-	}
-
-	// Now that the instantiation is complete without error, add it. This makes it visible for import.
-	s.addModule(hostModule)
-	return ret, nil
-}
 
-func (s *Store) compileHostFunction(f *FunctionInstance) (err error) {
-	ti, err := s.getTypeInstance(f.Type)
-	if err != nil {
-		return err
-	}
-	f.TypeID = ti.TypeID
-	s.addFunctions(f)
-
-	if err = s.engine.Compile(f); err != nil {
-		// On failure, we must release the function instance.
-		if err = s.releaseFunctions(f); err != nil {
-			return fmt.Errorf("failed to compile %s: %v", f.Name, err)
+		t := m.TypeSection[typeIndex]
+		if !t.EqualsSignature(ft.Params, ft.Results) {
+			return fmt.Errorf("%s signature doesn't match type section: %s != %s", m.funcDesc(SectionIDHostFunction, Index(idx)), ft, t)
 		}
 	}
 	return nil
 }
 
-// HostModule implements wasm.HostModule
-type HostModule struct {
-	// name is for String and Store.ReleaseModule
-	name           string
-	NameToFunction map[string]*FunctionInstance
-}
-
-// String implements fmt.Stringer
-func (m *HostModule) String() string {
-	return fmt.Sprintf("HostModule[%s]", m.name)
-}
-
-// ParamTypes implements wasm.HostFunction ParamTypes
-func (f *FunctionInstance) ParamTypes() []publicwasm.ValueType {
-	return f.Type.Params
-}
-
-// ResultTypes implements wasm.HostFunction ResultTypes
-func (f *FunctionInstance) ResultTypes() []publicwasm.ValueType {
-	return f.Type.Results
-}
-
-// Call implements wasm.HostFunction Call
-func (f *FunctionInstance) Call(ctx publicwasm.ModuleContext, params ...uint64) ([]uint64, error) {
-	modCtx, ok := ctx.(*ModuleContext)
-	if !ok { // TODO: guard that modCtx.Module actually imported this!
-		return nil, fmt.Errorf("this function was not imported by %s", ctx)
+func (m *Module) buildHostFunctionInstances() (functions []*FunctionInstance) {
+	var functionNames = m.NameSection.FunctionNames
+	for idx, typeIndex := range m.FunctionSection {
+		fn := m.HostFunctionSection[idx]
+		f := &FunctionInstance{
+			Name:   functionNames[idx].Name,
+			Kind:   kind(fn.Type()),
+			Type:   m.TypeSection[typeIndex],
+			GoFunc: fn,
+		}
+		functions = append(functions, f)
 	}
-	return modCtx.engine.Call(modCtx, f, params...)
-}
-
-// Function implements wasm.HostModule Function
-func (m *HostModule) Function(name string) publicwasm.HostFunction {
-	return m.NameToFunction[name]
+	return
 }
diff --git a/internal/wasm/interpreter/interpreter.go b/internal/wasm/interpreter/interpreter.go
index 4e4f1c20..9751c33f 100644
--- a/internal/wasm/interpreter/interpreter.go
+++ b/internal/wasm/interpreter/interpreter.go
@@ -45,7 +45,7 @@ type callEngine struct {
 	// stack contains the operands.
 	// Note that all the values are represented as uint64.
 	stack []uint64
-	// Function call stack.
+	// frames are the function call stack.
 	frames []*callFrame
 
 	// compiledFunctions is engine.functions at the time when this virtual machine was created.
diff --git a/internal/wasm/memory.go b/internal/wasm/memory.go
index f75110ad..8af4777b 100644
--- a/internal/wasm/memory.go
+++ b/internal/wasm/memory.go
@@ -17,6 +17,17 @@ const (
 	MemoryPageSizeInBits = 16
 )
 
+// MemoryInstance represents a memory instance in a store, and implements wasm.Memory.
+//
+// Note: In WebAssembly 1.0 (20191205), there may be up to one Memory per store, which means the precise memory is always
+// wasm.Store Memories index zero: `store.Memories[0]`
+// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-instances%E2%91%A0.
+type MemoryInstance struct {
+	Buffer []byte
+	Min    uint32
+	Max    *uint32
+}
+
 // Size implements wasm.Memory Size
 func (m *MemoryInstance) Size() uint32 {
 	return uint32(len(m.Buffer))
diff --git a/internal/wasm/module.go b/internal/wasm/module.go
index c3947f5e..61e5f1f2 100644
--- a/internal/wasm/module.go
+++ b/internal/wasm/module.go
@@ -2,7 +2,10 @@ package internalwasm
 
 import (
 	"bytes"
+	"errors"
 	"fmt"
+	"reflect"
+	"sort"
 	"strings"
 
 	"github.com/tetratelabs/wazero/internal/ieee754"
@@ -29,6 +32,7 @@ type EncodeModule func(m *Module) (bytes []byte)
 // Differences from the specification:
 // * NameSection is the only key ("name") decoded from the SectionIDCustom.
 // * ExportSection is represented as a map for lookup convenience.
+// * HostFunctionSection is a custom section that contains any go `func`s. It may be present when CodeSection is not.
 type Module struct {
 	// TypeSection contains the unique FunctionType of functions imported or defined in this module.
 	//
@@ -124,6 +128,7 @@ type Module struct {
 	ElementSection []*ElementSegment
 
 	// CodeSection is index-correlated with FunctionSection and contains each function's locals and body.
+	// When present, the HostFunctionSection must be nil.
 	//
 	// Note: In the Binary Format, this is SectionIDCode.
 	//
@@ -141,6 +146,13 @@ type Module struct {
 	// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#name-section%E2%91%A0
 	// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#custom-section%E2%91%A0
 	NameSection *NameSection
+
+	// HostFunctionSection is index-correlated with FunctionSection and contains a host function defined in Go.
+	// When present, the CodeSection must be nil.
+	//
+	// Note: This section currently has no serialization format, so is not encodable.
+	// See https://www.w3.org/TR/wasm-core-1/#host-functions%E2%91%A2
+	HostFunctionSection []*reflect.Value
 }
 
 // TypeOfFunction returns the internalwasm.SectionIDType index for the given function namespace index or nil.
@@ -179,6 +191,10 @@ func (m *Module) Validate(enabledFeatures Features) error {
 		return err
 	}
 
+	if m.SectionElementCount(SectionIDCode) > 0 && m.SectionElementCount(SectionIDHostFunction) > 0 {
+		return errors.New("cannot mix functions and host functions in the same module")
+	}
+
 	functions, globals, memories, tables := m.allDeclarations()
 
 	// The wazero specific limitation described at RATIONALE.md.
@@ -195,8 +211,14 @@ func (m *Module) Validate(enabledFeatures Features) error {
 		return err
 	}
 
-	if err := m.validateFunctions(functions, globals, memories, tables, enabledFeatures); err != nil {
-		return err
+	if m.CodeSection != nil {
+		if err := m.validateFunctions(functions, globals, memories, tables, enabledFeatures); err != nil {
+			return err
+		}
+	} else {
+		if err := m.validateHostFunctions(); err != nil {
+			return err
+		}
 	}
 
 	if err := m.validateExports(functions, globals, memories, tables); err != nil {
@@ -227,7 +249,7 @@ func (m *Module) validateGlobals(globals []*GlobalType, maxGlobals int) error {
 		return fmt.Errorf("too many globals in a module")
 	}
 
-	// Global's initialization constant expression can only reference the imported globals.
+	// Global initialization constant expression can only reference the imported globals.
 	// See the note on https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#constant-expressions%E2%91%A0
 	importedGlobals := globals[:m.ImportGlobalCount()]
 	for _, g := range m.GlobalSection {
@@ -239,9 +261,13 @@ func (m *Module) validateGlobals(globals []*GlobalType, maxGlobals int) error {
 }
 
 func (m *Module) validateFunctions(functions []Index, globals []*GlobalType, memories []*MemoryType, tables []*TableType, enabledFeatures Features) error {
-	typeCount := m.SectionElementCount(SectionIDType)
-	codeCount := m.SectionElementCount(SectionIDCode)
 	functionCount := m.SectionElementCount(SectionIDFunction)
+	codeCount := m.SectionElementCount(SectionIDCode)
+	if functionCount == 0 && codeCount == 0 {
+		return nil
+	}
+
+	typeCount := m.SectionElementCount(SectionIDType)
 	if codeCount != functionCount {
 		return fmt.Errorf("code count (%d) != function count (%d)", codeCount, functionCount)
 	}
@@ -278,6 +304,7 @@ func (m *Module) funcDesc(sectionID SectionID, sectionIndex Index) string {
 	if exportNames == nil {
 		return fmt.Sprintf("%s[%d]", sectionIDName, sectionIndex)
 	}
+	sort.Strings(exportNames) // go map keys do not iterate consistently
 	return fmt.Sprintf("%s[%d] (export %s)", sectionIDName, sectionIndex, strings.Join(exportNames, ","))
 }
 
@@ -391,7 +418,7 @@ func validateConstExpression(globals []*GlobalType, expr *ConstantExpression, ex
 func (m *Module) buildGlobals(importedGlobals []*GlobalInstance) (globals []*GlobalInstance) {
 	for _, gs := range m.GlobalSection {
 		var gv uint64
-		// Global's initialization constant expression can only reference the imported globals.
+		// Global initialization constant expression can only reference the imported globals.
 		// See the note on https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#constant-expressions%E2%91%A0
 		switch v := executeConstExpression(importedGlobals, gs.Init).(type) {
 		case int32:
@@ -710,6 +737,11 @@ const (
 	SectionIDData
 )
 
+// SectionIDHostFunction is a pseudo-section ID for host functions.
+//
+// Note: This is not defined in the WebAssembly 1.0 (20191205) Binary Format.
+const SectionIDHostFunction = SectionID(0xff)
+
 // SectionIDName returns the canonical name of a module section.
 // https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#sections%E2%91%A0
 func SectionIDName(sectionID SectionID) string {
@@ -738,6 +770,8 @@ func SectionIDName(sectionID SectionID) string {
 		return "code"
 	case SectionIDData:
 		return "data"
+	case SectionIDHostFunction:
+		return "host_function"
 	}
 	return "unknown"
 }
diff --git a/internal/wasm/module_context.go b/internal/wasm/module_context.go
new file mode 100644
index 00000000..723a6662
--- /dev/null
+++ b/internal/wasm/module_context.go
@@ -0,0 +1,126 @@
+package internalwasm
+
+import (
+	"context"
+	"fmt"
+
+	publicwasm "github.com/tetratelabs/wazero/wasm"
+)
+
+// compile time check to ensure ModuleContext implements wasm.Module
+var _ publicwasm.Module = &ModuleContext{}
+
+func NewModuleContext(ctx context.Context, engine Engine, instance *ModuleInstance) *ModuleContext {
+	return &ModuleContext{
+		ctx:    ctx,
+		engine: engine,
+		memory: instance.Memory,
+		Module: instance,
+	}
+}
+
+// ModuleContext implements wasm.Module
+type ModuleContext struct {
+	// ctx is returned by Context and overridden WithContext
+	ctx context.Context
+	// engine is used to implement function.Call
+	engine Engine
+	// Module is exported for spectests
+	Module *ModuleInstance
+	// memory is returned by Memory and overridden WithMemory
+	memory publicwasm.Memory
+}
+
+// WithMemory allows overriding memory without re-allocation when the result would be the same.
+func (m *ModuleContext) WithMemory(memory *MemoryInstance) *ModuleContext {
+	// only re-allocate if it will change the effective memory
+	if m.memory == nil || (memory != nil && memory.Max != nil && *memory.Max > 0 && memory != m.memory) {
+		return &ModuleContext{engine: m.engine, Module: m.Module, memory: memory, ctx: m.ctx}
+	}
+	return m
+}
+
+// String implements fmt.Stringer
+func (m *ModuleContext) String() string {
+	return fmt.Sprintf("Module[%s]", m.Module.Name)
+}
+
+// Context implements wasm.Module Context
+func (m *ModuleContext) Context() context.Context {
+	return m.ctx
+}
+
+// WithContext implements wasm.Module WithContext
+func (m *ModuleContext) WithContext(ctx context.Context) publicwasm.Module {
+	// only re-allocate if it will change the effective context
+	if ctx != nil && ctx != m.ctx {
+		return &ModuleContext{engine: m.engine, Module: m.Module, memory: m.memory, ctx: ctx}
+	}
+	return m
+}
+
+// Memory implements wasm.Module Memory
+func (m *ModuleContext) Memory() publicwasm.Memory {
+	return m.Module.Memory
+}
+
+// ExportedMemory implements wasm.Module ExportedMemory
+func (m *ModuleContext) ExportedMemory(name string) publicwasm.Memory {
+	exp, err := m.Module.getExport(name, ExternTypeMemory)
+	if err != nil {
+		return nil
+	}
+	return exp.Memory
+}
+
+// ExportedFunction implements wasm.Module ExportedFunction
+func (m *ModuleContext) ExportedFunction(name string) publicwasm.Function {
+	exp, err := m.Module.getExport(name, ExternTypeFunc)
+	if err != nil {
+		return nil
+	}
+	return exp.Function
+}
+
+// ParamTypes implements wasm.Function ParamTypes
+func (f *FunctionInstance) ParamTypes() []publicwasm.ValueType {
+	return f.Type.Params
+}
+
+// ResultTypes implements wasm.Function ResultTypes
+func (f *FunctionInstance) ResultTypes() []publicwasm.ValueType {
+	return f.Type.Results
+}
+
+// Call implements wasm.Function Call
+func (f *FunctionInstance) Call(ctx publicwasm.Module, params ...uint64) ([]uint64, error) {
+	if modCtx, ok := ctx.(*ModuleContext); !ok { // allow nil to substitute for the defining module
+		return f.Module.Ctx.engine.Call(f.Module.Ctx, f, params...)
+	} else { // TODO: check if the importing context is correct
+		return modCtx.engine.Call(modCtx, f, params...)
+	}
+}
+
+// ExportedGlobal implements wasm.Module ExportedGlobal
+func (m *ModuleContext) ExportedGlobal(name string) publicwasm.Global {
+	exp, err := m.Module.getExport(name, ExternTypeGlobal)
+	if err != nil {
+		return nil
+	}
+	if exp.Global.Type.Mutable {
+		return &mutableGlobal{exp.Global}
+	}
+	valType := exp.Global.Type.ValType
+	switch valType {
+	case ValueTypeI32:
+		return globalI32(exp.Global.Val)
+	case ValueTypeI64:
+		return globalI64(exp.Global.Val)
+	case ValueTypeF32:
+		return globalF32(exp.Global.Val)
+	case ValueTypeF64:
+		return globalF64(exp.Global.Val)
+	default:
+		panic(fmt.Errorf("BUG: unknown value type %X", valType))
+	}
+}
diff --git a/internal/wasm/store.go b/internal/wasm/store.go
index 01254b62..210f62de 100644
--- a/internal/wasm/store.go
+++ b/internal/wasm/store.go
@@ -92,9 +92,6 @@ type (
 		// Ctx holds default function call context from this function instance.
 		Ctx *ModuleContext
 
-		// hostModule holds HostModule if this is a "host module" which is created in store.NewHostModule.
-		hostModule *HostModule
-
 		// mux is used to guard the fields from concurrent access.
 		mux sync.Mutex
 
@@ -209,17 +206,6 @@ type (
 		FunctionTypeID FunctionTypeID
 	}
 
-	// MemoryInstance represents a memory instance in a store, and implements wasm.Memory.
-	//
-	// Note: In WebAssembly 1.0 (20191205), there may be up to one Memory per store, which means the precise memory is always
-	// wasm.Store Memories index zero: `store.Memories[0]`
-	// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-instances%E2%91%A0.
-	MemoryInstance struct {
-		Buffer []byte
-		Min    uint32
-		Max    *uint32
-	}
-
 	// FunctionIndex is funcaddr (https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-funcaddr),
 	// and the index to Store.Functions.
 	FunctionIndex storeIndex
@@ -283,7 +269,7 @@ func (m *ModuleInstance) buildExports(exports map[string]*Export) {
 			ei = &ExportInstance{Type: exp.Type, Function: m.Functions[index]}
 			// The module instance of the host function is a fake that only includes the function and its types.
 			// We need to assign the ModuleInstance when re-exporting so that any memory defined in the target is
-			// available to the wasm.ModuleContext Memory.
+			// available to the wasm.Module Memory.
 			if ei.Function.GoFunc != nil {
 				ei.Function.Module = m
 			}
@@ -389,7 +375,7 @@ func (s *Store) checkFunctionIndexOverflow(newInstanceNum int) error {
 
 // Instantiate uses name instead of the Module.NameSection ModuleName as it allows instantiating the same module under
 // different names safely and concurrently.
-func (s *Store) Instantiate(module *Module, name string) (*PublicModule, error) {
+func (s *Store) Instantiate(module *Module, name string) (*ModuleContext, error) {
 	if err := s.requireModuleName(name); err != nil {
 		return nil, err
 	}
@@ -421,8 +407,18 @@ func (s *Store) Instantiate(module *Module, name string) (*PublicModule, error)
 		return nil, err
 	}
 
-	functions, globals, table, memory :=
-		module.buildFunctions(), module.buildGlobals(importedGlobals), module.buildTable(), module.buildMemory()
+	globals, table, memory := module.buildGlobals(importedGlobals), module.buildTable(), module.buildMemory()
+
+	// If there are no module-defined functions, assume this is a host module.
+	var functions []*FunctionInstance
+	var funcSection SectionID
+	if module.HostFunctionSection == nil {
+		funcSection = SectionIDFunction
+		functions = module.buildFunctions()
+	} else {
+		funcSection = SectionIDHostFunction
+		functions = module.buildHostFunctionInstances()
+	}
 
 	// Now we have all instances from imports and local ones, so ready to create a new ModuleInstance.
 	m := &ModuleInstance{Name: name}
@@ -447,7 +443,7 @@ func (s *Store) Instantiate(module *Module, name string) (*PublicModule, error)
 			s.deleteModule(name)
 			// On the failure, release the assigned funcaddr and already compiled functions.
 			_ = s.releaseFunctions(functions[:i]...) // ignore any release error, so we can report the original one.
-			return nil, fmt.Errorf("%s compilation failed: %w", module.funcDesc(SectionIDFunction, Index(i)), err)
+			return nil, fmt.Errorf("%s compilation failed: %w", module.funcDesc(funcSection, Index(i)), err)
 		}
 	}
 
@@ -466,13 +462,13 @@ func (s *Store) Instantiate(module *Module, name string) (*PublicModule, error)
 		funcIdx := *module.StartSection
 		if _, err = s.engine.Call(m.Ctx, m.Functions[funcIdx]); err != nil {
 			s.deleteModule(name)
-			return nil, fmt.Errorf("start %s failed: %w", module.funcDesc(SectionIDFunction, funcIdx), err)
+			return nil, fmt.Errorf("start %s failed: %w", module.funcDesc(funcSection, funcIdx), err)
 		}
 	}
 
 	// Now that the instantiation is complete without error, add it. This makes it visible for import.
 	s.addModule(m)
-	return &PublicModule{s: s, instance: m}, nil
+	return m.Ctx, nil
 }
 
 // ReleaseModule deallocates resources if a module with the given name exists.
@@ -581,10 +577,10 @@ func (s *Store) addModule(m *ModuleInstance) {
 	s.modules[m.Name] = m
 }
 
-// Module implements wasm.Store Module
+// Module implements wazero.Runtime Module
 func (s *Store) Module(moduleName string) publicwasm.Module {
 	if m := s.module(moduleName); m != nil {
-		return &PublicModule{s: s, instance: m}
+		return m.Ctx
 	} else {
 		return nil
 	}
@@ -596,42 +592,6 @@ func (s *Store) module(moduleName string) *ModuleInstance {
 	return s.modules[moduleName]
 }
 
-// PublicModule implements wasm.Module
-type PublicModule struct {
-	s        *Store
-	instance *ModuleInstance
-}
-
-// String implements fmt.Stringer
-func (m *PublicModule) String() string {
-	return fmt.Sprintf("Module[%s]", m.instance.Name)
-}
-
-// Function implements wasm.Module Function
-func (m *PublicModule) Function(name string) publicwasm.Function {
-	exp, err := m.instance.getExport(name, ExternTypeFunc)
-	if err != nil {
-		return nil
-	}
-	return &exportedFunction{module: m.instance.Ctx, function: exp.Function}
-}
-
-// Memory implements wasm.Module Memory
-func (m *PublicModule) Memory(name string) publicwasm.Memory {
-	exp, err := m.instance.getExport(name, ExternTypeMemory)
-	if err != nil {
-		return nil
-	}
-	return exp.Memory
-}
-
-// HostModule implements wasm.Store HostModule
-func (s *Store) HostModule(moduleName string) publicwasm.HostModule {
-	s.mux.RLock()
-	defer s.mux.RUnlock()
-	return s.modules[moduleName].hostModule
-}
-
 func (s *Store) resolveImports(module *Module) (
 	importedFunctions []*FunctionInstance, importedGlobals []*GlobalInstance,
 	importedTable *TableInstance, importedMemory *MemoryInstance,
diff --git a/wasi.go b/wasi.go
index c2cffb3e..b09ef55c 100644
--- a/wasi.go
+++ b/wasi.go
@@ -129,8 +129,8 @@ func StartWASICommand(r Runtime, module *DecodedModule) (wasm.Module, error) {
 		return nil, err
 	}
 
-	start := mod.Function(internalwasi.FunctionStart)
-	if _, err = start.Call(internal.ctx); err != nil {
+	start := mod.ExportedFunction(internalwasi.FunctionStart)
+	if _, err = start.Call(nil); err != nil {
 		return nil, fmt.Errorf("module[%s] function[%s] failed: %w", module.name, internalwasi.FunctionStart, err)
 	}
 	return mod, nil
diff --git a/wasi/wasi.go b/wasi/wasi.go
index 26a64a5e..5b07c0b0 100644
--- a/wasi/wasi.go
+++ b/wasi/wasi.go
@@ -165,7 +165,7 @@ const (
 	ErrnoNoprotoopt
 	// ErrnoNospc No space left on device.
 	ErrnoNospc
-	// ErrnoNosys Function not supported.
+	// ErrnoNosys ExportedFunction not supported.
 	ErrnoNosys
 	// ErrnoNotconn The socket is not connected.
 	ErrnoNotconn
@@ -303,7 +303,7 @@ var errnoToString = [...]string{
 // In wazero, if ProcExit is called, the calling function returns immediately, returning the given exit code as the error.
 // You can get the exit code by casting the error as follows.
 //
-//   wasmFunction := m.Function(/* omitted */)  // Some function which may call proc_exit
+//   wasmFunction := m.ExportedFunction(/* omitted */)  // Some function which may call proc_exit
 //   err := wasmFunction()
 //   var exitCode wasi.ExitCode
 //   if errors.As(err, &exitCode) {
diff --git a/wasm.go b/wasm.go
index a1375b57..1ac59443 100644
--- a/wasm.go
+++ b/wasm.go
@@ -2,7 +2,6 @@ package wazero
 
 import (
 	"bytes"
-	"context"
 	"errors"
 
 	internalwasm "github.com/tetratelabs/wazero/internal/wasm"
@@ -20,7 +19,8 @@ import (
 //
 // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/
 type Runtime interface {
-	wasm.Store
+	// Module returns exports from an instantiated module or nil if there aren't any.
+	Module(moduleName string) wasm.Module
 
 	// DecodeModule decodes the WebAssembly 1.0 (20191205) text or binary source or errs if invalid.
 	//
@@ -45,19 +45,16 @@ type Runtime interface {
 	//	decoded, _ := r.DecodeModule(source)
 	//	module, _ := r.NewModule(decoded)
 	//
-	// Note: The last value of RuntimeConfig.WithContext is used for any WebAssembly 1.0 (20191205) Start Function.
+	// Note: The last value of RuntimeConfig.WithContext is used for any WebAssembly 1.0 (20191205) Start ExportedFunction.
 	NewModule(module *DecodedModule) (wasm.Module, error)
 
-	// TODO: RemoveModule
-
-	// NewHostModule instantiates the module namespace from the host or errs if the configuration was invalid.
+	// NewHostModuleFromConfig instantiates the module namespace from the host or errs if the configuration was invalid.
 	//
 	// Ex.
-	//	r := wazero.NewRuntime()
-	//	wasiExports, _ := r.NewHostModule(wazero.WASISnapshotPreview1())
-	NewHostModule(hostModule *HostModuleConfig) (wasm.HostModule, error)
+	//	module, _ := wazero.NewRuntime().NewHostModuleFromConfig(wazero.WASISnapshotPreview1())
+	NewHostModuleFromConfig(hostModule *HostModuleConfig) (wasm.Module, error)
 
-	// TODO: RemoveHostModule
+	// TODO: RemoveModule
 }
 
 func NewRuntime() Runtime {
@@ -67,7 +64,6 @@ func NewRuntime() Runtime {
 // NewRuntimeWithConfig returns a runtime with the given configuration.
 func NewRuntimeWithConfig(config *RuntimeConfig) Runtime {
 	return &runtime{
-		ctx:             config.ctx,
 		store:           internalwasm.NewStore(config.ctx, config.engine, config.enabledFeatures),
 		enabledFeatures: config.enabledFeatures,
 	}
@@ -75,7 +71,6 @@ func NewRuntimeWithConfig(config *RuntimeConfig) Runtime {
 
 // runtime allows decoupling of public interfaces from internal representation.
 type runtime struct {
-	ctx             context.Context
 	store           *internalwasm.Store
 	enabledFeatures internalwasm.Features
 }
@@ -85,11 +80,6 @@ func (r *runtime) Module(moduleName string) wasm.Module {
 	return r.store.Module(moduleName)
 }
 
-// HostModule implements wasm.Store HostModule
-func (r *runtime) HostModule(moduleName string) wasm.HostModule {
-	return r.store.HostModule(moduleName)
-}
-
 // DecodeModule implements Runtime.DecodeModule
 func (r *runtime) DecodeModule(source []byte) (*DecodedModule, error) {
 	if source == nil {
@@ -139,7 +129,11 @@ func (r *runtime) NewModule(module *DecodedModule) (wasm.Module, error) {
 	return r.store.Instantiate(module.module, module.name)
 }
 
-// NewHostModule implements Runtime.NewHostModule
-func (r *runtime) NewHostModule(hostModule *HostModuleConfig) (wasm.HostModule, error) {
-	return r.store.NewHostModule(hostModule.Name, hostModule.Functions)
+// NewHostModuleFromConfig implements Runtime.NewHostModuleFromConfig
+func (r *runtime) NewHostModuleFromConfig(hostModule *HostModuleConfig) (wasm.Module, error) {
+	if m, err := internalwasm.NewHostModule(hostModule.Name, hostModule.Functions); err != nil {
+		return nil, err
+	} else {
+		return r.store.Instantiate(m, hostModule.Name)
+	}
 }
diff --git a/wasm/wasm.go b/wasm/wasm.go
index 31b1585f..92f49b8c 100644
--- a/wasm/wasm.go
+++ b/wasm/wasm.go
@@ -55,15 +55,6 @@ func ValueTypeName(t ValueType) string {
 	return "unknown"
 }
 
-// Store allows access to instantiated modules and host functions
-type Store interface {
-	// Module returns exports from an instantiated module or nil if there aren't any.
-	Module(moduleName string) Module
-
-	// HostModule returns exported host functions for the moduleName or nil if there aren't any.
-	HostModule(moduleName string) HostModule
-}
-
 // Module return functions exported in a module, post-instantiation.
 //
 // Note: This is an interface for decoupling, not third-party implementations. All implementations are in wazero.
@@ -71,20 +62,31 @@ type Store interface {
 type Module interface {
 	fmt.Stringer
 
-	// Function returns a function exported from this module or nil if it wasn't.
-	Function(name string) Function
+	// Context returns any propagated context from the Runtime or a prior function call.
+	//
+	// The returned context is always non-nil; it defaults to context.Background.
+	Context() context.Context
+
+	// WithContext allows callers to override the propagated context, for example, to add values to it.
+	WithContext(ctx context.Context) Module
+
+	// Memory returns a memory defined in this module or nil if there are none wasn't.
+	Memory() Memory
+
+	// ExportedFunction returns a function exported from this module or nil if it wasn't.
+	ExportedFunction(name string) Function
 
 	// TODO: Table
 
-	// Memory returns a memory exported from this module or nil if it wasn't.
+	// ExportedMemory returns a memory exported from this module or nil if it wasn't.
 	//
 	// Note: WASI modules require exporting a Memory named "memory". This means that a module successfully initialized
 	// as a WASI Command or Reactor will never return nil for this name.
 	// See https://github.com/WebAssembly/WASI/blob/snapshot-01/design/application-abi.md#current-unstable-abi
-	Memory(name string) Memory
+	ExportedMemory(name string) Memory
 
-	// Global returns a global exported from this module or nil if it wasn't.
-	Global(name string) Global
+	// ExportedGlobal a global exported from this module or nil if it wasn't.
+	ExportedGlobal(name string) Global
 }
 
 // Function is a WebAssembly 1.0 (20191205) function exported from an instantiated module (wazero.Runtime NewModule).
@@ -105,27 +107,31 @@ type Function interface {
 	// encoded according to ResultTypes. An error is returned for any failure looking up or invoking the function
 	// including signature mismatch.
 	//
-	// If the `ctx` is nil, it defaults to the same context as the module was initialized with.
+	// If the `ctx` is nil, it defaults to the module the function was defined in.
 	//
-	// To ensure context propagation in a HostFunction body, use or derive `ctx` from ModuleContext.Context:
+	// To override context propagation, use Module.WithContext
+	//	fn = mod.ExportedFunction("fib")
+	//	results, err := fn(mod.WithContext(ctx), 5)
+	//	--snip--
 	//
-	//	hostFunction := func(ctx wasm.ModuleContext, offset, byteCount uint32) uint32 {
-	//		fn, _ = ctx.Function("__read")
-	//		results, err := fn(ctx.Context(), offset, byteCount)
+	// To ensure context propagation in a host function body, pass the `ctx` parameter:
+	//	hostFunction := func(ctx wasm.Module, offset, byteCount uint32) uint32 {
+	//		fn = ctx.ExportedFunction("__read")
+	//		results, err := fn(ctx, offset, byteCount)
 	//	--snip--
-	Call(ctx context.Context, params ...uint64) ([]uint64, error)
+	Call(ctx Module, params ...uint64) ([]uint64, error)
 }
 
 // Global is a WebAssembly 1.0 (20191205) global exported from an instantiated module (wazero.Runtime NewModule).
 //
 // Ex. If the value is not mutable, you can read it once:
 //
-//	offset := module.Global("memory.offset").Get()
+//	offset := module.ExportedGlobal("memory.offset").Get()
 //
 // Globals are allowed by specification to be mutable. However, this can be disabled by configuration. When in doubt,
 // safe cast to find out if the value can change. Ex.
 //
-//	offset := module.Global("memory.offset")
+//	offset := module.ExportedGlobal("memory.offset")
 //	if _, ok := offset.(wasm.MutableGlobal); ok {
 //		// value can change
 //	} else {
@@ -153,49 +159,6 @@ type MutableGlobal interface {
 	Set(v uint64)
 }
 
-// HostModule return functions defined in Go, a.k.a. "Host Functions" in WebAssembly 1.0 (20191205).
-//
-// Note: This is an interface for decoupling, not third-party implementations. All implementations are in wazero.
-// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-hostfunc
-// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#external-types%E2%91%A0
-type HostModule interface {
-	fmt.Stringer
-
-	// 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 (20191205).
-//
-// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-hostfunc
-type HostFunction interface {
-	// ParamTypes are documented as Function.ParamTypes
-	ParamTypes() []ValueType
-
-	// ResultTypes are documented as Function.ResultTypes
-	ResultTypes() []ValueType
-
-	// Call is the same as Function.Call, except it must be called from an importing module (ctx). The can also err if
-	// the module did not import this function!
-	Call(ctx ModuleContext, params ...uint64) ([]uint64, error)
-}
-
-// ModuleContext is the first argument of a HostFunction.
-//
-// Note: This is an interface for decoupling, not third-party implementations. All implementations are in wazero.
-type ModuleContext interface {
-	// Context returns the host call's context.
-	//
-	// The returned context is always non-nil; it defaults to the background context.
-	Context() context.Context
-
-	// Memory returns a potentially zero memory of the importing module
-	Memory() Memory
-
-	// 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.
 //
 // Note: This is an interface for decoupling, not third-party implementations. All implementations are in wazero.


Test patch:
diff --git a/examples/add_test.go b/examples/add_test.go
index 99422f3c..80fb2349 100644
--- a/examples/add_test.go
+++ b/examples/add_test.go
@@ -1,7 +1,6 @@
 package examples
 
 import (
-	"context"
 	"testing"
 
 	"github.com/stretchr/testify/require"
@@ -24,15 +23,15 @@ func Test_AddInt(t *testing.T) {
 )`))
 	require.NoError(t, err)
 
-	addInt := module.Function("AddInt")
+	addInt := module.ExportedFunction("AddInt")
 
 	for _, c := range []struct {
-		value1, value2, expected uint64 // i32i32_i32 sig, but wasm.Function params and results are uint64
+		value1, value2, expected uint64 // i32i32_i32 sig, but wasm.ExportedFunction params and results are uint64
 	}{
 		{value1: 1, value2: 2, expected: 3},
 		{value1: 5, value2: 5, expected: 10},
 	} {
-		results, err := addInt.Call(context.Background(), c.value1, c.value2)
+		results, err := addInt.Call(nil, c.value1, c.value2)
 		require.NoError(t, err)
 		require.Equal(t, c.expected, results[0])
 	}
diff --git a/examples/fibonacci_test.go b/examples/fibonacci_test.go
index 5fb52beb..a5a1c6bc 100644
--- a/examples/fibonacci_test.go
+++ b/examples/fibonacci_test.go
@@ -1,7 +1,6 @@
 package examples
 
 import (
-	"context"
 	_ "embed"
 	"testing"
 
@@ -18,22 +17,22 @@ func Test_fibonacci(t *testing.T) {
 	r := wazero.NewRuntime()
 
 	// Note: fibonacci.go doesn't directly use WASI, but TinyGo needs to be initialized as a WASI Command.
-	_, err := r.NewHostModule(wazero.WASISnapshotPreview1())
+	_, err := r.NewHostModuleFromConfig(wazero.WASISnapshotPreview1())
 	require.NoError(t, err)
 
 	module, err := wazero.StartWASICommandFromSource(r, fibWasm)
 	require.NoError(t, err)
 
-	fibonacci := module.Function("fibonacci")
+	fibonacci := module.ExportedFunction("fibonacci")
 
 	for _, c := range []struct {
-		input, expected uint64 // i32_i32 sig, but wasm.Function params and results are uint64
+		input, expected uint64 // i32_i32 sig, but wasm.ExportedFunction params and results are uint64
 	}{
 		{input: 20, expected: 6765},
 		{input: 10, expected: 55},
 		{input: 5, expected: 5},
 	} {
-		results, err := fibonacci.Call(context.Background(), c.input)
+		results, err := fibonacci.Call(nil, c.input)
 		require.NoError(t, err)
 		require.Equal(t, c.expected, results[0])
 	}
diff --git a/examples/file_system_test.go b/examples/file_system_test.go
index 803101e4..125eda47 100644
--- a/examples/file_system_test.go
+++ b/examples/file_system_test.go
@@ -52,7 +52,7 @@ func Test_file_system(t *testing.T) {
 	require.NoError(t, err)
 
 	wasiConfig := &wazero.WASIConfig{Preopens: map[string]wasi.FS{".": memFS}}
-	_, err = r.NewHostModule(wazero.WASISnapshotPreview1WithConfig(wasiConfig))
+	_, err = r.NewHostModuleFromConfig(wazero.WASISnapshotPreview1WithConfig(wasiConfig))
 	require.NoError(t, err)
 
 	// Note: TinyGo binaries must be treated as WASI Commands to initialize memory.
diff --git a/examples/host_func_test.go b/examples/host_func_test.go
index 3d43e588..eee7f79e 100644
--- a/examples/host_func_test.go
+++ b/examples/host_func_test.go
@@ -24,20 +24,20 @@ var hostFuncWasm []byte
 func Test_hostFunc(t *testing.T) {
 	// The function for allocating the in-Wasm memory region.
 	// We resolve this function after main module instantiation.
-	allocateInWasmBuffer := func(context.Context, uint32) uint32 {
+	allocateInWasmBuffer := func(wasm.Module, uint32) uint32 {
 		panic("unimplemented")
 	}
 
 	var expectedBase64String string
 
 	// Host-side implementation of get_random_string on Wasm import.
-	getRandomBytes := func(ctx wasm.ModuleContext, retBufPtr uint32, retBufSize uint32) {
+	getRandomBytes := func(ctx wasm.Module, retBufPtr uint32, retBufSize uint32) {
 		// Assert that context values passed in from CallFunctionContext are accessible.
 		contextValue := ctx.Context().Value(testKey{}).(int64)
 		require.Equal(t, int64(12345), contextValue)
 
 		const bufferSize = 10
-		offset := allocateInWasmBuffer(ctx.Context(), bufferSize)
+		offset := allocateInWasmBuffer(ctx, bufferSize)
 
 		// Store the address info to the memory.
 		require.True(t, ctx.Memory().WriteUint32Le(retBufPtr, offset))
@@ -57,32 +57,32 @@ func Test_hostFunc(t *testing.T) {
 	r := wazero.NewRuntime()
 
 	env := &wazero.HostModuleConfig{Name: "env", Functions: map[string]interface{}{"get_random_bytes": getRandomBytes}}
-	_, err := r.NewHostModule(env)
+	_, err := r.NewHostModuleFromConfig(env)
 	require.NoError(t, err)
 
 	// Note: host_func.go doesn't directly use WASI, but TinyGo needs to be initialized as a WASI Command.
 	stdout := bytes.NewBuffer(nil)
-	_, err = r.NewHostModule(wazero.WASISnapshotPreview1WithConfig(&wazero.WASIConfig{Stdout: stdout}))
+	_, err = r.NewHostModuleFromConfig(wazero.WASISnapshotPreview1WithConfig(&wazero.WASIConfig{Stdout: stdout}))
 	require.NoError(t, err)
 
 	module, err := wazero.StartWASICommandFromSource(r, hostFuncWasm)
 	require.NoError(t, err)
 
-	allocateInWasmBufferFn := module.Function("allocate_buffer")
+	allocateInWasmBufferFn := module.ExportedFunction("allocate_buffer")
 	require.NotNil(t, allocateInWasmBuffer)
 
 	// Implement the function pointer. This mainly shows how you can decouple a module function dependency.
-	allocateInWasmBuffer = func(ctx context.Context, size uint32) uint32 {
+	allocateInWasmBuffer = func(ctx wasm.Module, size uint32) uint32 {
 		res, err := allocateInWasmBufferFn.Call(ctx, uint64(size))
 		require.NoError(t, err)
 		return uint32(res[0])
 	}
 
-	// Set a context variable that should be available in wasm.ModuleContext.
+	// Set a context variable that should be available in wasm.Module.
 	ctx := context.WithValue(context.Background(), testKey{}, int64(12345))
 
 	// Invoke a module-defined function that depends on a host function import
-	_, err = module.Function("base64").Call(ctx)
+	_, err = module.ExportedFunction("base64").Call(module.WithContext(ctx))
 	require.NoError(t, err)
 
 	// Verify that in-Wasm calculated base64 string matches the one calculated in native Go.
diff --git a/examples/simple_test.go b/examples/simple_test.go
index fb41937e..c824e065 100644
--- a/examples/simple_test.go
+++ b/examples/simple_test.go
@@ -21,7 +21,7 @@ func Test_Simple(t *testing.T) {
 
 	// Host functions can be exported as any module name, including the empty string.
 	env := &wazero.HostModuleConfig{Name: "", Functions: map[string]interface{}{"hello": goFunc}}
-	_, err := r.NewHostModule(env)
+	_, err := r.NewHostModuleFromConfig(env)
 	require.NoError(t, err)
 
 	// The "hello" function was imported as $hello in Wasm. Since it was marked as the start
diff --git a/examples/stdio_test.go b/examples/stdio_test.go
index 5aef8576..746ccfb2 100644
--- a/examples/stdio_test.go
+++ b/examples/stdio_test.go
@@ -24,7 +24,7 @@ func Test_stdio(t *testing.T) {
 
 	// Configure WASI host functions with the IO buffers
 	wasiConfig := &wazero.WASIConfig{Stdin: stdinBuf, Stdout: stdoutBuf, Stderr: stderrBuf}
-	_, err := r.NewHostModule(wazero.WASISnapshotPreview1WithConfig(wasiConfig))
+	_, err := r.NewHostModuleFromConfig(wazero.WASISnapshotPreview1WithConfig(wasiConfig))
 	require.NoError(t, err)
 
 	// StartWASICommand runs the "_start" function which is what TinyGo compiles "main" to
diff --git a/examples/wasi_test.go b/examples/wasi_test.go
index 61a08a9e..92c52041 100644
--- a/examples/wasi_test.go
+++ b/examples/wasi_test.go
@@ -14,12 +14,12 @@ import (
 
 func Test_WASI(t *testing.T) {
 	// built-in WASI function to write a random value to memory
-	randomGet := func(ctx wasm.ModuleContext, buf, bufLen uint32) wasi.Errno {
+	randomGet := func(ctx wasm.Module, buf, bufLen uint32) wasi.Errno {
 		panic("unimplemented")
 	}
 
 	stdout := new(bytes.Buffer)
-	goFunc := func(ctx wasm.ModuleContext) {
+	goFunc := func(ctx wasm.Module) {
 		// Write 8 random bytes to memory using WASI.
 		errno := randomGet(ctx, 0, 8)
 		require.Equal(t, wasi.ErrnoSuccess, errno)
@@ -34,15 +34,15 @@ func Test_WASI(t *testing.T) {
 
 	// Host functions can be exported as any module name, including the empty string.
 	env := &wazero.HostModuleConfig{Name: "", Functions: map[string]interface{}{"random": goFunc}}
-	_, err := r.NewHostModule(env)
+	_, err := r.NewHostModuleFromConfig(env)
 
 	// Configure WASI and implement the function to use it
-	we, err := r.NewHostModule(wazero.WASISnapshotPreview1())
+	we, err := r.NewHostModuleFromConfig(wazero.WASISnapshotPreview1())
 	require.NoError(t, err)
-	randomGetFn := we.Function("random_get")
+	randomGetFn := we.ExportedFunction("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 {
+	randomGet = func(ctx wasm.Module, buf, bufLen uint32) wasi.Errno {
 		res, err := randomGetFn.Call(ctx, uint64(buf), uint64(bufLen))
 		require.NoError(t, err)
 		return wasi.Errno(res[0])
diff --git a/internal/wasi/wasi_test.go b/internal/wasi/wasi_test.go
index 36066c3e..77ad01ae 100644
--- a/internal/wasi/wasi_test.go
+++ b/internal/wasi/wasi_test.go
@@ -55,44 +55,39 @@ func TestSnapshotPreview1_ArgsGet(t *testing.T) {
 		'?', // stopped after encoding
 	}
 
-	mem, fn := instantiateModule(t, FunctionArgsGet, ImportArgsGet, moduleName, args)
+	mod, fn := instantiateModule(t, FunctionArgsGet, ImportArgsGet, moduleName, args)
 
 	t.Run("SnapshotPreview1.ArgsGet", func(t *testing.T) {
-		maskMemory(t, mem, len(expectedMemory))
+		maskMemory(t, mod, len(expectedMemory))
 
 		// Invoke ArgsGet directly and check the memory side effects.
-		errno := NewAPI(args).ArgsGet(ctx(mem), argv, argvBuf)
+		errno := NewAPI(args).ArgsGet(mod, argv, argvBuf)
 		require.Equal(t, wasi.ErrnoSuccess, errno)
 
-		actual, ok := mem.Read(0, uint32(len(expectedMemory)))
+		actual, ok := mod.Memory().Read(0, uint32(len(expectedMemory)))
 		require.True(t, ok)
 		require.Equal(t, expectedMemory, actual)
 	})
 
 	t.Run(FunctionArgsGet, func(t *testing.T) {
-		maskMemory(t, mem, len(expectedMemory))
+		maskMemory(t, mod, len(expectedMemory))
 
-		results, err := fn.Call(context.Background(), uint64(argv), uint64(argvBuf))
+		results, err := fn.Call(nil, uint64(argv), uint64(argvBuf))
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoSuccess, wasi.Errno(results[0])) // cast because results are always uint64
 
-		actual, ok := mem.Read(0, uint32(len(expectedMemory)))
+		actual, ok := mod.Memory().Read(0, uint32(len(expectedMemory)))
 		require.True(t, ok)
 		require.Equal(t, expectedMemory, actual)
 	})
 }
 
-func ctx(mem publicwasm.Memory) *wasm.ModuleContext {
-	ctx := (&wasm.ModuleContext{}).WithMemory(mem.(*wasm.MemoryInstance))
-	return ctx
-}
-
 func TestSnapshotPreview1_ArgsGet_Errors(t *testing.T) {
 	args, err := Args("a", "bc")
 	require.NoError(t, err)
-	mem, fn := instantiateModule(t, FunctionArgsGet, ImportArgsGet, moduleName, args)
+	mod, fn := instantiateModule(t, FunctionArgsGet, ImportArgsGet, moduleName, args)
 
-	memorySize := mem.Size()
+	memorySize := mod.Memory().Size()
 	validAddress := uint32(0) // arbitrary valid address as arguments to args_get. We chose 0 here.
 
 	tests := []struct {
@@ -128,7 +123,7 @@ func TestSnapshotPreview1_ArgsGet_Errors(t *testing.T) {
 		tc := tt
 
 		t.Run(tc.name, func(t *testing.T) {
-			results, err := fn.Call(context.Background(), uint64(tc.argv), uint64(tc.argvBuf))
+			results, err := fn.Call(nil, uint64(tc.argv), uint64(tc.argvBuf))
 			require.NoError(t, err)
 			require.Equal(t, uint64(wasi.ErrnoFault), results[0]) // results[0] is the errno
 		})
@@ -148,28 +143,28 @@ func TestSnapshotPreview1_ArgsSizesGet(t *testing.T) {
 		'?', // stopped after encoding
 	}
 
-	mem, fn := instantiateModule(t, FunctionArgsSizesGet, ImportArgsSizesGet, moduleName, args)
+	mod, fn := instantiateModule(t, FunctionArgsSizesGet, ImportArgsSizesGet, moduleName, args)
 
 	t.Run("SnapshotPreview1.ArgsSizesGet", func(t *testing.T) {
-		maskMemory(t, mem, len(expectedMemory))
+		maskMemory(t, mod, len(expectedMemory))
 
 		// Invoke ArgsSizesGet directly and check the memory side effects.
-		errno := NewAPI(args).ArgsSizesGet(ctx(mem), resultArgc, resultArgvBufSize)
+		errno := NewAPI(args).ArgsSizesGet(mod, resultArgc, resultArgvBufSize)
 		require.Equal(t, wasi.ErrnoSuccess, errno)
 
-		actual, ok := mem.Read(0, uint32(len(expectedMemory)))
+		actual, ok := mod.Memory().Read(0, uint32(len(expectedMemory)))
 		require.True(t, ok)
 		require.Equal(t, expectedMemory, actual)
 	})
 
 	t.Run(FunctionArgsSizesGet, func(t *testing.T) {
-		maskMemory(t, mem, len(expectedMemory))
+		maskMemory(t, mod, len(expectedMemory))
 
-		results, err := fn.Call(context.Background(), uint64(resultArgc), uint64(resultArgvBufSize))
+		results, err := fn.Call(nil, uint64(resultArgc), uint64(resultArgvBufSize))
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoSuccess, wasi.Errno(results[0])) // cast because results are always uint64
 
-		actual, ok := mem.Read(0, uint32(len(expectedMemory)))
+		actual, ok := mod.Memory().Read(0, uint32(len(expectedMemory)))
 		require.True(t, ok)
 		require.Equal(t, expectedMemory, actual)
 	})
@@ -179,9 +174,9 @@ func TestSnapshotPreview1_ArgsSizesGet_Errors(t *testing.T) {
 	args, err := Args("a", "bc")
 	require.NoError(t, err)
 
-	mem, fn := instantiateModule(t, FunctionArgsSizesGet, ImportArgsSizesGet, moduleName, args)
+	mod, fn := instantiateModule(t, FunctionArgsSizesGet, ImportArgsSizesGet, moduleName, args)
 
-	memorySize := mem.Size()
+	memorySize := mod.Memory().Size()
 	validAddress := uint32(0) // arbitrary valid address as arguments to args_sizes_get. We chose 0 here.
 
 	tests := []struct {
@@ -215,7 +210,7 @@ func TestSnapshotPreview1_ArgsSizesGet_Errors(t *testing.T) {
 		tc := tt
 
 		t.Run(tc.name, func(t *testing.T) {
-			results, err := fn.Call(context.Background(), uint64(tc.argc), uint64(tc.argvBufSize))
+			results, err := fn.Call(nil, uint64(tc.argc), uint64(tc.argvBufSize))
 			require.NoError(t, err)
 			require.Equal(t, uint64(wasi.ErrnoFault), results[0]) // results[0] is the errno
 		})
@@ -273,28 +268,28 @@ func TestSnapshotPreview1_EnvironGet(t *testing.T) {
 		'?', // stopped after encoding
 	}
 
-	mem, fn := instantiateModule(t, FunctionEnvironGet, ImportEnvironGet, moduleName, envOpt)
+	mod, fn := instantiateModule(t, FunctionEnvironGet, ImportEnvironGet, moduleName, envOpt)
 
 	t.Run("SnapshotPreview1.EnvironGet", func(t *testing.T) {
-		maskMemory(t, mem, len(expectedMemory))
+		maskMemory(t, mod, len(expectedMemory))
 
 		// Invoke EnvironGet directly and check the memory side effects.
-		errno := NewAPI(envOpt).EnvironGet(ctx(mem), resultEnviron, resultEnvironBuf)
+		errno := NewAPI(envOpt).EnvironGet(mod, resultEnviron, resultEnvironBuf)
 		require.Equal(t, wasi.ErrnoSuccess, errno)
 
-		actual, ok := mem.Read(0, uint32(len(expectedMemory)))
+		actual, ok := mod.Memory().Read(0, uint32(len(expectedMemory)))
 		require.True(t, ok)
 		require.Equal(t, expectedMemory, actual)
 	})
 
 	t.Run(FunctionEnvironGet, func(t *testing.T) {
-		maskMemory(t, mem, len(expectedMemory))
+		maskMemory(t, mod, len(expectedMemory))
 
-		results, err := fn.Call(context.Background(), uint64(resultEnviron), uint64(resultEnvironBuf))
+		results, err := fn.Call(nil, uint64(resultEnviron), uint64(resultEnvironBuf))
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoSuccess, wasi.Errno(results[0])) // cast because results are always uint64
 
-		actual, ok := mem.Read(0, uint32(len(expectedMemory)))
+		actual, ok := mod.Memory().Read(0, uint32(len(expectedMemory)))
 		require.True(t, ok)
 		require.Equal(t, expectedMemory, actual)
 	})
@@ -304,9 +299,9 @@ func TestSnapshotPreview1_EnvironGet_Errors(t *testing.T) {
 	envOpt, err := Environ("a=bc", "b=cd")
 	require.NoError(t, err)
 
-	mem, fn := instantiateModule(t, FunctionEnvironGet, ImportEnvironGet, moduleName, envOpt)
+	mod, fn := instantiateModule(t, FunctionEnvironGet, ImportEnvironGet, moduleName, envOpt)
 
-	memorySize := mem.Size()
+	memorySize := mod.Memory().Size()
 	validAddress := uint32(0) // arbitrary valid address as arguments to environ_get. We chose 0 here.
 
 	tests := []struct {
@@ -342,7 +337,7 @@ func TestSnapshotPreview1_EnvironGet_Errors(t *testing.T) {
 		tc := tt
 
 		t.Run(tc.name, func(t *testing.T) {
-			results, err := fn.Call(context.Background(), uint64(tc.environ), uint64(tc.environBuf))
+			results, err := fn.Call(nil, uint64(tc.environ), uint64(tc.environBuf))
 			require.NoError(t, err)
 			require.Equal(t, uint64(wasi.ErrnoFault), results[0]) // results[0] is the errno
 		})
@@ -362,28 +357,28 @@ func TestSnapshotPreview1_EnvironSizesGet(t *testing.T) {
 		'?', // stopped after encoding
 	}
 
-	mem, fn := instantiateModule(t, FunctionEnvironSizesGet, ImportEnvironSizesGet, moduleName, envOpt)
+	mod, fn := instantiateModule(t, FunctionEnvironSizesGet, ImportEnvironSizesGet, moduleName, envOpt)
 
 	t.Run("SnapshotPreview1.EnvironSizesGet", func(t *testing.T) {
-		maskMemory(t, mem, len(expectedMemory))
+		maskMemory(t, mod, len(expectedMemory))
 
 		// Invoke EnvironSizesGet directly and check the memory side effects.
-		errno := NewAPI(envOpt).EnvironSizesGet(ctx(mem), resultEnvironc, resultEnvironBufSize)
+		errno := NewAPI(envOpt).EnvironSizesGet(mod, resultEnvironc, resultEnvironBufSize)
 		require.Equal(t, wasi.ErrnoSuccess, errno)
 
-		actual, ok := mem.Read(0, uint32(len(expectedMemory)))
+		actual, ok := mod.Memory().Read(0, uint32(len(expectedMemory)))
 		require.True(t, ok)
 		require.Equal(t, expectedMemory, actual)
 	})
 
 	t.Run(FunctionEnvironSizesGet, func(t *testing.T) {
-		maskMemory(t, mem, len(expectedMemory))
+		maskMemory(t, mod, len(expectedMemory))
 
-		results, err := fn.Call(context.Background(), uint64(resultEnvironc), uint64(resultEnvironBufSize))
+		results, err := fn.Call(nil, uint64(resultEnvironc), uint64(resultEnvironBufSize))
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoSuccess, wasi.Errno(results[0])) // cast because results are always uint64
 
-		actual, ok := mem.Read(0, uint32(len(expectedMemory)))
+		actual, ok := mod.Memory().Read(0, uint32(len(expectedMemory)))
 		require.True(t, ok)
 		require.Equal(t, expectedMemory, actual)
 	})
@@ -393,9 +388,9 @@ func TestSnapshotPreview1_EnvironSizesGet_Errors(t *testing.T) {
 	envOpt, err := Environ("a=b", "b=cd")
 	require.NoError(t, err)
 
-	mem, fn := instantiateModule(t, FunctionEnvironSizesGet, ImportEnvironSizesGet, moduleName, envOpt)
+	mod, fn := instantiateModule(t, FunctionEnvironSizesGet, ImportEnvironSizesGet, moduleName, envOpt)
 
-	memorySize := mem.Size()
+	memorySize := mod.Memory().Size()
 	validAddress := uint32(0) // arbitrary valid address as arguments to environ_sizes_get. We chose 0 here.
 
 	tests := []struct {
@@ -429,7 +424,7 @@ func TestSnapshotPreview1_EnvironSizesGet_Errors(t *testing.T) {
 		tc := tt
 
 		t.Run(tc.name, func(t *testing.T) {
-			results, err := fn.Call(context.Background(), uint64(tc.environc), uint64(tc.environBufSize))
+			results, err := fn.Call(nil, uint64(tc.environc), uint64(tc.environBufSize))
 			require.NoError(t, err)
 			require.Equal(t, uint64(wasi.ErrnoFault), results[0]) // results[0] is the errno
 		})
@@ -438,14 +433,14 @@ func TestSnapshotPreview1_EnvironSizesGet_Errors(t *testing.T) {
 
 // TestSnapshotPreview1_ClockResGet only tests it is stubbed for GrainLang per #271
 func TestSnapshotPreview1_ClockResGet(t *testing.T) {
-	mem, fn := instantiateModule(t, FunctionClockResGet, ImportClockResGet, moduleName)
+	mod, fn := instantiateModule(t, FunctionClockResGet, ImportClockResGet, moduleName)
 
 	t.Run("SnapshotPreview1.ClockResGet", func(t *testing.T) {
-		require.Equal(t, wasi.ErrnoNosys, NewAPI().ClockResGet(ctx(mem), 0, 0))
+		require.Equal(t, wasi.ErrnoNosys, NewAPI().ClockResGet(mod, 0, 0))
 	})
 
 	t.Run(FunctionClockResGet, func(t *testing.T) {
-		results, err := fn.Call(context.Background(), 0, 0)
+		results, err := fn.Call(nil, 0, 0)
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoNosys, wasi.Errno(results[0])) // cast because results are always uint64
 	})
@@ -463,28 +458,28 @@ func TestSnapshotPreview1_ClockTimeGet(t *testing.T) {
 	clockOpt := func(api *wasiAPI) {
 		api.timeNowUnixNano = func() uint64 { return epochNanos }
 	}
-	mem, fn := instantiateModule(t, FunctionClockTimeGet, ImportClockTimeGet, moduleName, clockOpt)
+	mod, fn := instantiateModule(t, FunctionClockTimeGet, ImportClockTimeGet, moduleName, clockOpt)
 
 	t.Run("SnapshotPreview1.ClockTimeGet", func(t *testing.T) {
-		maskMemory(t, mem, len(expectedMemory))
+		maskMemory(t, mod, len(expectedMemory))
 
 		// invoke ClockTimeGet directly and check the memory side effects!
-		errno := NewAPI(clockOpt).ClockTimeGet(ctx(mem), 0 /* TODO: id */, 0 /* TODO: precision */, resultTimestamp)
+		errno := NewAPI(clockOpt).ClockTimeGet(mod, 0 /* TODO: id */, 0 /* TODO: precision */, resultTimestamp)
 		require.Equal(t, wasi.ErrnoSuccess, errno)
 
-		actual, ok := mem.Read(0, uint32(len(expectedMemory)))
+		actual, ok := mod.Memory().Read(0, uint32(len(expectedMemory)))
 		require.True(t, ok)
 		require.Equal(t, expectedMemory, actual)
 	})
 
 	t.Run(FunctionClockTimeGet, func(t *testing.T) {
-		maskMemory(t, mem, len(expectedMemory))
+		maskMemory(t, mod, len(expectedMemory))
 
-		results, err := fn.Call(context.Background(), 0 /* TODO: id */, 0 /* TODO: precision */, uint64(resultTimestamp))
+		results, err := fn.Call(nil, 0 /* TODO: id */, 0 /* TODO: precision */, uint64(resultTimestamp))
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoSuccess, wasi.Errno(results[0])) // cast because results are always uint64
 
-		actual, ok := mem.Read(0, uint32(len(expectedMemory)))
+		actual, ok := mod.Memory().Read(0, uint32(len(expectedMemory)))
 		require.True(t, ok)
 		require.Equal(t, expectedMemory, actual)
 	})
@@ -493,11 +488,11 @@ func TestSnapshotPreview1_ClockTimeGet(t *testing.T) {
 func TestSnapshotPreview1_ClockTimeGet_Errors(t *testing.T) {
 	epochNanos := uint64(1640995200000000000) // midnight UTC 2022-01-01
 
-	mem, fn := instantiateModule(t, FunctionClockTimeGet, ImportClockTimeGet, moduleName, func(api *wasiAPI) {
+	mod, fn := instantiateModule(t, FunctionClockTimeGet, ImportClockTimeGet, moduleName, func(api *wasiAPI) {
 		api.timeNowUnixNano = func() uint64 { return epochNanos }
 	})
 
-	memorySize := mem.Size()
+	memorySize := mod.Memory().Size()
 
 	tests := []struct {
 		name            string
@@ -519,7 +514,7 @@ func TestSnapshotPreview1_ClockTimeGet_Errors(t *testing.T) {
 		tc := tt
 
 		t.Run(tc.name, func(t *testing.T) {
-			results, err := fn.Call(context.Background(), 0 /* TODO: id */, 0 /* TODO: precision */, uint64(tc.resultTimestamp))
+			results, err := fn.Call(nil, 0 /* TODO: id */, 0 /* TODO: precision */, uint64(tc.resultTimestamp))
 			require.NoError(t, err)
 			require.Equal(t, uint64(wasi.ErrnoFault), results[0]) // results[0] is the errno
 		})
@@ -528,14 +523,14 @@ func TestSnapshotPreview1_ClockTimeGet_Errors(t *testing.T) {
 
 // TestSnapshotPreview1_FdAdvise only tests it is stubbed for GrainLang per #271
 func TestSnapshotPreview1_FdAdvise(t *testing.T) {
-	mem, fn := instantiateModule(t, FunctionFdAdvise, ImportFdAdvise, moduleName)
+	mod, fn := instantiateModule(t, FunctionFdAdvise, ImportFdAdvise, moduleName)
 
 	t.Run("SnapshotPreview1.FdAdvise", func(t *testing.T) {
-		require.Equal(t, wasi.ErrnoNosys, NewAPI().FdAdvise(ctx(mem), 0, 0, 0, 0))
+		require.Equal(t, wasi.ErrnoNosys, NewAPI().FdAdvise(mod, 0, 0, 0, 0))
 	})
 
 	t.Run(FunctionFdAdvise, func(t *testing.T) {
-		results, err := fn.Call(context.Background(), 0, 0, 0, 0)
+		results, err := fn.Call(nil, 0, 0, 0, 0)
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoNosys, wasi.Errno(results[0])) // cast because results are always uint64
 	})
@@ -543,14 +538,14 @@ func TestSnapshotPreview1_FdAdvise(t *testing.T) {
 
 // TestSnapshotPreview1_FdAllocate only tests it is stubbed for GrainLang per #271
 func TestSnapshotPreview1_FdAllocate(t *testing.T) {
-	mem, fn := instantiateModule(t, FunctionFdAllocate, ImportFdAllocate, moduleName)
+	mod, fn := instantiateModule(t, FunctionFdAllocate, ImportFdAllocate, moduleName)
 
 	t.Run("SnapshotPreview1.FdAllocate", func(t *testing.T) {
-		require.Equal(t, wasi.ErrnoNosys, NewAPI().FdAllocate(ctx(mem), 0, 0, 0))
+		require.Equal(t, wasi.ErrnoNosys, NewAPI().FdAllocate(mod, 0, 0, 0))
 	})
 
 	t.Run(FunctionFdAllocate, func(t *testing.T) {
-		results, err := fn.Call(context.Background(), 0, 0, 0)
+		results, err := fn.Call(nil, 0, 0, 0)
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoNosys, wasi.Errno(results[0])) // cast because results are always uint64
 	})
@@ -560,9 +555,9 @@ func TestSnapshotPreview1_FdClose(t *testing.T) {
 	fdToClose := uint32(3) // arbitrary fd
 	fdToKeep := uint32(4)  // another arbitrary fd
 
-	setupFD := func() (publicwasm.Memory, publicwasm.Function, *wasiAPI) {
+	setupFD := func() (publicwasm.Module, publicwasm.Function, *wasiAPI) {
 		var api *wasiAPI
-		mem, fn := instantiateModule(t, FunctionFdClose, ImportFdClose, moduleName, func(a *wasiAPI) {
+		mod, fn := instantiateModule(t, FunctionFdClose, ImportFdClose, moduleName, func(a *wasiAPI) {
 			memFs := &MemFS{}
 			a.opened = map[uint32]fileEntry{
 				fdToClose: {
@@ -576,13 +571,13 @@ func TestSnapshotPreview1_FdClose(t *testing.T) {
 			}
 			api = a // for later tests
 		})
-		return mem, fn, api
+		return mod, fn, api
 	}
 
 	t.Run("SnapshotPreview1.FdClose", func(t *testing.T) {
-		mem, _, api := setupFD()
+		mod, _, api := setupFD()
 
-		errno := api.FdClose(ctx(mem), fdToClose)
+		errno := api.FdClose(mod, fdToClose)
 		require.Equal(t, wasi.ErrnoSuccess, errno)
 		require.NotContains(t, api.opened, fdToClose) // Fd is closed and removed from the opened FDs.
 		require.Contains(t, api.opened, fdToKeep)
@@ -590,30 +585,30 @@ func TestSnapshotPreview1_FdClose(t *testing.T) {
 	t.Run(FunctionFdClose, func(t *testing.T) {
 		_, fn, api := setupFD()
 
-		ret, err := fn.Call(context.Background(), uint64(fdToClose))
+		ret, err := fn.Call(nil, uint64(fdToClose))
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoSuccess, wasi.Errno(ret[0])) // cast because results are always uint64
 		require.NotContains(t, api.opened, fdToClose)           // Fd is closed and removed from the opened FDs.
 		require.Contains(t, api.opened, fdToKeep)
 	})
 	t.Run("ErrnoBadF for an invalid FD", func(t *testing.T) {
-		mem, _, api := setupFD()
+		mod, _, api := setupFD()
 
-		errno := api.FdClose(ctx(mem), 42) // 42 is an arbitrary invalid FD
+		errno := api.FdClose(mod, 42) // 42 is an arbitrary invalid FD
 		require.Equal(t, wasi.ErrnoBadf, errno)
 	})
 }
 
 // TestSnapshotPreview1_FdDatasync only tests it is stubbed for GrainLang per #271
 func TestSnapshotPreview1_FdDatasync(t *testing.T) {
-	mem, fn := instantiateModule(t, FunctionFdDatasync, ImportFdDatasync, moduleName)
+	mod, fn := instantiateModule(t, FunctionFdDatasync, ImportFdDatasync, moduleName)
 
 	t.Run("SnapshotPreview1.FdDatasync", func(t *testing.T) {
-		require.Equal(t, wasi.ErrnoNosys, NewAPI().FdDatasync(ctx(mem), 0))
+		require.Equal(t, wasi.ErrnoNosys, NewAPI().FdDatasync(mod, 0))
 	})
 
 	t.Run(FunctionFdDatasync, func(t *testing.T) {
-		results, err := fn.Call(context.Background(), 0)
+		results, err := fn.Call(nil, 0)
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoNosys, wasi.Errno(results[0])) // cast because results are always uint64
 	})
@@ -623,14 +618,14 @@ func TestSnapshotPreview1_FdDatasync(t *testing.T) {
 
 // TestSnapshotPreview1_FdFdstatSetFlags only tests it is stubbed for GrainLang per #271
 func TestSnapshotPreview1_FdFdstatSetFlags(t *testing.T) {
-	mem, fn := instantiateModule(t, FunctionFdFdstatSetFlags, ImportFdFdstatSetFlags, moduleName)
+	mod, fn := instantiateModule(t, FunctionFdFdstatSetFlags, ImportFdFdstatSetFlags, moduleName)
 
 	t.Run("SnapshotPreview1.FdFdstatSetFlags", func(t *testing.T) {
-		require.Equal(t, wasi.ErrnoNosys, NewAPI().FdFdstatSetFlags(ctx(mem), 0, 0))
+		require.Equal(t, wasi.ErrnoNosys, NewAPI().FdFdstatSetFlags(mod, 0, 0))
 	})
 
 	t.Run(FunctionFdFdstatSetFlags, func(t *testing.T) {
-		results, err := fn.Call(context.Background(), 0, 0)
+		results, err := fn.Call(nil, 0, 0)
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoNosys, wasi.Errno(results[0])) // cast because results are always uint64
 	})
@@ -638,14 +633,14 @@ func TestSnapshotPreview1_FdFdstatSetFlags(t *testing.T) {
 
 // TestSnapshotPreview1_FdFdstatSetRights only tests it is stubbed for GrainLang per #271
 func TestSnapshotPreview1_FdFdstatSetRights(t *testing.T) {
-	mem, fn := instantiateModule(t, FunctionFdFdstatSetRights, ImportFdFdstatSetRights, moduleName)
+	mod, fn := instantiateModule(t, FunctionFdFdstatSetRights, ImportFdFdstatSetRights, moduleName)
 
 	t.Run("SnapshotPreview1.FdFdstatSetRights", func(t *testing.T) {
-		require.Equal(t, wasi.ErrnoNosys, NewAPI().FdFdstatSetRights(ctx(mem), 0, 0, 0))
+		require.Equal(t, wasi.ErrnoNosys, NewAPI().FdFdstatSetRights(mod, 0, 0, 0))
 	})
 
 	t.Run(FunctionFdFdstatSetRights, func(t *testing.T) {
-		results, err := fn.Call(context.Background(), 0, 0, 0)
+		results, err := fn.Call(nil, 0, 0, 0)
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoNosys, wasi.Errno(results[0])) // cast because results are always uint64
 	})
@@ -653,14 +648,14 @@ func TestSnapshotPreview1_FdFdstatSetRights(t *testing.T) {
 
 // TestSnapshotPreview1_FdFilestatGet only tests it is stubbed for GrainLang per #271
 func TestSnapshotPreview1_FdFilestatGet(t *testing.T) {
-	mem, fn := instantiateModule(t, FunctionFdFilestatGet, ImportFdFilestatGet, moduleName)
+	mod, fn := instantiateModule(t, FunctionFdFilestatGet, ImportFdFilestatGet, moduleName)
 
 	t.Run("SnapshotPreview1.FdFilestatGet", func(t *testing.T) {
-		require.Equal(t, wasi.ErrnoNosys, NewAPI().FdFilestatGet(ctx(mem), 0, 0))
+		require.Equal(t, wasi.ErrnoNosys, NewAPI().FdFilestatGet(mod, 0, 0))
 	})
 
 	t.Run(FunctionFdFilestatGet, func(t *testing.T) {
-		results, err := fn.Call(context.Background(), 0, 0)
+		results, err := fn.Call(nil, 0, 0)
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoNosys, wasi.Errno(results[0])) // cast because results are always uint64
 	})
@@ -668,14 +663,14 @@ func TestSnapshotPreview1_FdFilestatGet(t *testing.T) {
 
 // TestSnapshotPreview1_FdFilestatSetSize only tests it is stubbed for GrainLang per #271
 func TestSnapshotPreview1_FdFilestatSetSize(t *testing.T) {
-	mem, fn := instantiateModule(t, FunctionFdFilestatSetSize, ImportFdFilestatSetSize, moduleName)
+	mod, fn := instantiateModule(t, FunctionFdFilestatSetSize, ImportFdFilestatSetSize, moduleName)
 
 	t.Run("SnapshotPreview1.FdFilestatSetSize", func(t *testing.T) {
-		require.Equal(t, wasi.ErrnoNosys, NewAPI().FdFilestatSetSize(ctx(mem), 0, 0))
+		require.Equal(t, wasi.ErrnoNosys, NewAPI().FdFilestatSetSize(mod, 0, 0))
 	})
 
 	t.Run(FunctionFdFilestatSetSize, func(t *testing.T) {
-		results, err := fn.Call(context.Background(), 0, 0)
+		results, err := fn.Call(nil, 0, 0)
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoNosys, wasi.Errno(results[0])) // cast because results are always uint64
 	})
@@ -683,14 +678,14 @@ func TestSnapshotPreview1_FdFilestatSetSize(t *testing.T) {
 
 // TestSnapshotPreview1_FdFilestatSetTimes only tests it is stubbed for GrainLang per #271
 func TestSnapshotPreview1_FdFilestatSetTimes(t *testing.T) {
-	mem, fn := instantiateModule(t, FunctionFdFilestatSetTimes, ImportFdFilestatSetTimes, moduleName)
+	mod, fn := instantiateModule(t, FunctionFdFilestatSetTimes, ImportFdFilestatSetTimes, moduleName)
 
 	t.Run("SnapshotPreview1.FdFilestatSetTimes", func(t *testing.T) {
-		require.Equal(t, wasi.ErrnoNosys, NewAPI().FdFilestatSetTimes(ctx(mem), 0, 0, 0, 0))
+		require.Equal(t, wasi.ErrnoNosys, NewAPI().FdFilestatSetTimes(mod, 0, 0, 0, 0))
 	})
 
 	t.Run(FunctionFdFilestatSetTimes, func(t *testing.T) {
-		results, err := fn.Call(context.Background(), 0, 0, 0, 0)
+		results, err := fn.Call(nil, 0, 0, 0, 0)
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoNosys, wasi.Errno(results[0])) // cast because results are always uint64
 	})
@@ -698,14 +693,14 @@ func TestSnapshotPreview1_FdFilestatSetTimes(t *testing.T) {
 
 // TestSnapshotPreview1_FdPread only tests it is stubbed for GrainLang per #271
 func TestSnapshotPreview1_FdPread(t *testing.T) {
-	mem, fn := instantiateModule(t, FunctionFdPread, ImportFdPread, moduleName)
+	mod, fn := instantiateModule(t, FunctionFdPread, ImportFdPread, moduleName)
 
 	t.Run("SnapshotPreview1.FdPread", func(t *testing.T) {
-		require.Equal(t, wasi.ErrnoNosys, NewAPI().FdPread(ctx(mem), 0, 0, 0, 0))
+		require.Equal(t, wasi.ErrnoNosys, NewAPI().FdPread(mod, 0, 0, 0, 0))
 	})
 
 	t.Run(FunctionFdPread, func(t *testing.T) {
-		results, err := fn.Call(context.Background(), 0, 0, 0, 0)
+		results, err := fn.Call(nil, 0, 0, 0, 0)
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoNosys, wasi.Errno(results[0])) // cast because results are always uint64
 	})
@@ -716,7 +711,7 @@ func TestSnapshotPreview1_FdPread(t *testing.T) {
 func TestSnapshotPreview1_FdPrestatDirName(t *testing.T) {
 	fd := uint32(3) // arbitrary fd after 0, 1, and 2, that are stdin/out/err
 	var api *wasiAPI
-	mem, fn := instantiateModule(t, FunctionFdPrestatDirName, ImportFdPrestatDirName, moduleName, func(a *wasiAPI) {
+	mod, fn := instantiateModule(t, FunctionFdPrestatDirName, ImportFdPrestatDirName, moduleName, func(a *wasiAPI) {
 		a.opened[fd] = fileEntry{
 			path:    "/tmp",
 			fileSys: &MemFS{},
@@ -733,24 +728,24 @@ func TestSnapshotPreview1_FdPrestatDirName(t *testing.T) {
 	}
 
 	t.Run("SnapshotPreview1.FdPrestatDirName", func(t *testing.T) {
-		maskMemory(t, mem, len(expectedMemory))
+		maskMemory(t, mod, len(expectedMemory))
 
-		errno := api.FdPrestatDirName(ctx(mem), fd, path, pathLen)
+		errno := api.FdPrestatDirName(mod, fd, path, pathLen)
 		require.Equal(t, wasi.ErrnoSuccess, errno)
 
-		actual, ok := mem.Read(0, uint32(len(expectedMemory)))
+		actual, ok := mod.Memory().Read(0, uint32(len(expectedMemory)))
 		require.True(t, ok)
 		require.Equal(t, expectedMemory, actual)
 	})
 
 	t.Run(FunctionFdPrestatDirName, func(t *testing.T) {
-		maskMemory(t, mem, len(expectedMemory))
+		maskMemory(t, mod, len(expectedMemory))
 
-		ret, err := fn.Call(context.Background(), uint64(fd), uint64(path), uint64(pathLen))
+		ret, err := fn.Call(nil, uint64(fd), uint64(path), uint64(pathLen))
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoSuccess, wasi.Errno(ret[0])) // cast because results are always uint64
 
-		actual, ok := mem.Read(0, uint32(len(expectedMemory)))
+		actual, ok := mod.Memory().Read(0, uint32(len(expectedMemory)))
 		require.True(t, ok)
 		require.Equal(t, expectedMemory, actual)
 	})
@@ -759,9 +754,9 @@ func TestSnapshotPreview1_FdPrestatDirName(t *testing.T) {
 func TestSnapshotPreview1_FdPrestatDirName_Errors(t *testing.T) {
 	dirName := "/tmp"
 	opt := Preopen(dirName, &MemFS{})
-	mem, fn := instantiateModule(t, FunctionFdPrestatDirName, ImportFdPrestatDirName, moduleName, opt)
+	mod, fn := instantiateModule(t, FunctionFdPrestatDirName, ImportFdPrestatDirName, moduleName, opt)
 
-	memorySize := mem.Size()
+	memorySize := mod.Memory().Size()
 	validAddress := uint32(0) // Arbitrary valid address as arguments to fd_prestat_dir_name. We chose 0 here.
 	fd := uint32(3)           // fd 3 will be opened for the "/tmp" directory after 0, 1, and 2, that are stdin/out/err
 
@@ -806,7 +801,7 @@ func TestSnapshotPreview1_FdPrestatDirName_Errors(t *testing.T) {
 		tc := tt
 
 		t.Run(tc.name, func(t *testing.T) {
-			results, err := fn.Call(context.Background(), uint64(tc.fd), uint64(tc.path), uint64(tc.pathLen))
+			results, err := fn.Call(nil, uint64(tc.fd), uint64(tc.path), uint64(tc.pathLen))
 			require.NoError(t, err)
 			require.Equal(t, tc.expectedErrno, wasi.Errno(results[0])) // results[0] is the errno
 		})
@@ -815,14 +810,14 @@ func TestSnapshotPreview1_FdPrestatDirName_Errors(t *testing.T) {
 
 // TestSnapshotPreview1_FdPwrite only tests it is stubbed for GrainLang per #271
 func TestSnapshotPreview1_FdPwrite(t *testing.T) {
-	mem, fn := instantiateModule(t, FunctionFdPwrite, ImportFdPwrite, moduleName)
+	mod, fn := instantiateModule(t, FunctionFdPwrite, ImportFdPwrite, moduleName)
 
 	t.Run("SnapshotPreview1.FdPwrite", func(t *testing.T) {
-		require.Equal(t, wasi.ErrnoNosys, NewAPI().FdPwrite(ctx(mem), 0, 0, 0, 0))
+		require.Equal(t, wasi.ErrnoNosys, NewAPI().FdPwrite(mod, 0, 0, 0, 0))
 	})
 
 	t.Run(FunctionFdPwrite, func(t *testing.T) {
-		results, err := fn.Call(context.Background(), 0, 0, 0, 0)
+		results, err := fn.Call(nil, 0, 0, 0, 0)
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoNosys, wasi.Errno(results[0])) // cast because results are always uint64
 	})
@@ -852,12 +847,12 @@ func TestSnapshotPreview1_FdRead(t *testing.T) {
 	)
 
 	var api *wasiAPI
-	mem, fn := instantiateModule(t, FunctionFdRead, ImportFdRead, moduleName, func(a *wasiAPI) {
+	mod, fn := instantiateModule(t, FunctionFdRead, ImportFdRead, moduleName, func(a *wasiAPI) {
 		api = a // for later tests
 	})
 
 	// TestSnapshotPreview1_FdRead uses a matrix because setting up test files is complicated and has to be clean each time.
-	type fdReadFn func(ctx publicwasm.ModuleContext, fd, iovs, iovsCount, resultSize uint32) wasi.Errno
+	type fdReadFn func(ctx publicwasm.Module, fd, iovs, iovsCount, resultSize uint32) wasi.Errno
 	tests := []struct {
 		name   string
 		fdRead func() fdReadFn
@@ -866,8 +861,8 @@ func TestSnapshotPreview1_FdRead(t *testing.T) {
 			return api.FdRead
 		}},
 		{FunctionFdRead, func() fdReadFn {
-			return func(ctx publicwasm.ModuleContext, fd, iovs, iovsCount, resultSize uint32) wasi.Errno {
-				ret, err := fn.Call(context.Background(), uint64(fd), uint64(iovs), uint64(iovsCount), uint64(resultSize))
+			return func(ctx publicwasm.Module, fd, iovs, iovsCount, resultSize uint32) wasi.Errno {
+				ret, err := fn.Call(nil, uint64(fd), uint64(iovs), uint64(iovsCount), uint64(resultSize))
 				require.NoError(t, err)
 				return wasi.Errno(ret[0])
 			}
@@ -884,15 +879,15 @@ func TestSnapshotPreview1_FdRead(t *testing.T) {
 				fileSys: memFS,
 				file:    file,
 			}
-			maskMemory(t, mem, len(expectedMemory))
+			maskMemory(t, mod, len(expectedMemory))
 
-			ok := mem.Write(0, initialMemory)
+			ok := mod.Memory().Write(0, initialMemory)
 			require.True(t, ok)
 
-			errno := tc.fdRead()(ctx(mem), fd, iovs, iovsCount, resultSize)
+			errno := tc.fdRead()(mod, fd, iovs, iovsCount, resultSize)
 			require.Equal(t, wasi.ErrnoSuccess, errno)
 
-			actual, ok := mem.Read(0, uint32(len(expectedMemory)))
+			actual, ok := mod.Memory().Read(0, uint32(len(expectedMemory)))
 			require.True(t, ok)
 			require.Equal(t, expectedMemory, actual)
 		})
@@ -902,7 +897,7 @@ func TestSnapshotPreview1_FdRead(t *testing.T) {
 func TestSnapshotPreview1_FdRead_Errors(t *testing.T) {
 	validFD := uint64(3)                                // arbitrary valid fd after 0, 1, and 2, that are stdin/out/err
 	file, memFS := createFile(t, "test_path", []byte{}) // file with empty contents
-	mem, fn := instantiateModule(t, FunctionFdRead, ImportFdRead, moduleName, func(a *wasiAPI) {
+	mod, fn := instantiateModule(t, FunctionFdRead, ImportFdRead, moduleName, func(a *wasiAPI) {
 		a.opened[uint32(validFD)] = fileEntry{
 			path:    "test_path",
 			fileSys: memFS,
@@ -981,10 +976,10 @@ func TestSnapshotPreview1_FdRead_Errors(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			offset := uint64(wasm.MemoryPagesToBytesNum(testMemoryPageSize)) - uint64(len(tc.memory))
 
-			memoryWriteOK := mem.Write(uint32(offset), tc.memory)
+			memoryWriteOK := mod.Memory().Write(uint32(offset), tc.memory)
 			require.True(t, memoryWriteOK)
 
-			results, err := fn.Call(context.Background(), tc.fd, tc.iovs+offset, tc.iovsCount+offset, tc.resultSize+offset)
+			results, err := fn.Call(nil, tc.fd, tc.iovs+offset, tc.iovsCount+offset, tc.resultSize+offset)
 			require.NoError(t, err)
 			require.Equal(t, tc.expectedErrno, wasi.Errno(results[0])) // results[0] is the errno
 		})
@@ -993,14 +988,14 @@ func TestSnapshotPreview1_FdRead_Errors(t *testing.T) {
 
 // TestSnapshotPreview1_FdReaddir only tests it is stubbed for GrainLang per #271
 func TestSnapshotPreview1_FdReaddir(t *testing.T) {
-	mem, fn := instantiateModule(t, FunctionFdReaddir, ImportFdReaddir, moduleName)
+	mod, fn := instantiateModule(t, FunctionFdReaddir, ImportFdReaddir, moduleName)
 
 	t.Run("SnapshotPreview1.FdReaddir", func(t *testing.T) {
-		require.Equal(t, wasi.ErrnoNosys, NewAPI().FdReaddir(ctx(mem), 0, 0, 0, 0, 0))
+		require.Equal(t, wasi.ErrnoNosys, NewAPI().FdReaddir(mod, 0, 0, 0, 0, 0))
 	})
 
 	t.Run(FunctionFdReaddir, func(t *testing.T) {
-		results, err := fn.Call(context.Background(), 0, 0, 0, 0, 0)
+		results, err := fn.Call(nil, 0, 0, 0, 0, 0)
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoNosys, wasi.Errno(results[0])) // cast because results are always uint64
 	})
@@ -1008,14 +1003,14 @@ func TestSnapshotPreview1_FdReaddir(t *testing.T) {
 
 // TestSnapshotPreview1_FdRenumber only tests it is stubbed for GrainLang per #271
 func TestSnapshotPreview1_FdRenumber(t *testing.T) {
-	mem, fn := instantiateModule(t, FunctionFdRenumber, ImportFdRenumber, moduleName)
+	mod, fn := instantiateModule(t, FunctionFdRenumber, ImportFdRenumber, moduleName)
 
 	t.Run("SnapshotPreview1.FdRenumber", func(t *testing.T) {
-		require.Equal(t, wasi.ErrnoNosys, NewAPI().FdRenumber(ctx(mem), 0, 0))
+		require.Equal(t, wasi.ErrnoNosys, NewAPI().FdRenumber(mod, 0, 0))
 	})
 
 	t.Run(FunctionFdRenumber, func(t *testing.T) {
-		results, err := fn.Call(context.Background(), 0, 0)
+		results, err := fn.Call(nil, 0, 0)
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoNosys, wasi.Errno(results[0])) // cast because results are always uint64
 	})
@@ -1023,14 +1018,14 @@ func TestSnapshotPreview1_FdRenumber(t *testing.T) {
 
 // TestSnapshotPreview1_FdSeek only tests it is stubbed for GrainLang per #271
 func TestSnapshotPreview1_FdSeek(t *testing.T) {
-	mem, fn := instantiateModule(t, FunctionFdSeek, ImportFdSeek, moduleName)
+	mod, fn := instantiateModule(t, FunctionFdSeek, ImportFdSeek, moduleName)
 
 	t.Run("SnapshotPreview1.FdSeek", func(t *testing.T) {
-		require.Equal(t, wasi.ErrnoNosys, NewAPI().FdSeek(ctx(mem), 0, 0, 0, 0))
+		require.Equal(t, wasi.ErrnoNosys, NewAPI().FdSeek(mod, 0, 0, 0, 0))
 	})
 
 	t.Run(FunctionFdSeek, func(t *testing.T) {
-		results, err := fn.Call(context.Background(), 0, 0, 0, 0)
+		results, err := fn.Call(nil, 0, 0, 0, 0)
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoNosys, wasi.Errno(results[0])) // cast because results are always uint64
 	})
@@ -1038,14 +1033,14 @@ func TestSnapshotPreview1_FdSeek(t *testing.T) {
 
 // TestSnapshotPreview1_FdSync only tests it is stubbed for GrainLang per #271
 func TestSnapshotPreview1_FdSync(t *testing.T) {
-	mem, fn := instantiateModule(t, FunctionFdSync, ImportFdSync, moduleName)
+	mod, fn := instantiateModule(t, FunctionFdSync, ImportFdSync, moduleName)
 
 	t.Run("SnapshotPreview1.FdSync", func(t *testing.T) {
-		require.Equal(t, wasi.ErrnoNosys, NewAPI().FdSync(ctx(mem), 0))
+		require.Equal(t, wasi.ErrnoNosys, NewAPI().FdSync(mod, 0))
 	})
 
 	t.Run(FunctionFdSync, func(t *testing.T) {
-		results, err := fn.Call(context.Background(), 0)
+		results, err := fn.Call(nil, 0)
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoNosys, wasi.Errno(results[0])) // cast because results are always uint64
 	})
@@ -1053,14 +1048,14 @@ func TestSnapshotPreview1_FdSync(t *testing.T) {
 
 // TestSnapshotPreview1_FdTell only tests it is stubbed for GrainLang per #271
 func TestSnapshotPreview1_FdTell(t *testing.T) {
-	mem, fn := instantiateModule(t, FunctionFdTell, ImportFdTell, moduleName)
+	mod, fn := instantiateModule(t, FunctionFdTell, ImportFdTell, moduleName)
 
 	t.Run("SnapshotPreview1.FdTell", func(t *testing.T) {
-		require.Equal(t, wasi.ErrnoNosys, NewAPI().FdTell(ctx(mem), 0, 0))
+		require.Equal(t, wasi.ErrnoNosys, NewAPI().FdTell(mod, 0, 0))
 	})
 
 	t.Run(FunctionFdTell, func(t *testing.T) {
-		results, err := fn.Call(context.Background(), 0, 0)
+		results, err := fn.Call(nil, 0, 0)
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoNosys, wasi.Errno(results[0])) // cast because results are always uint64
 	})
@@ -1090,12 +1085,12 @@ func TestSnapshotPreview1_FdWrite(t *testing.T) {
 	)
 
 	var api *wasiAPI
-	mem, fn := instantiateModule(t, FunctionFdWrite, ImportFdWrite, moduleName, func(a *wasiAPI) {
+	mod, fn := instantiateModule(t, FunctionFdWrite, ImportFdWrite, moduleName, func(a *wasiAPI) {
 		api = a // for later tests
 	})
 
 	// TestSnapshotPreview1_FdWrite uses a matrix because setting up test files is complicated and has to be clean each time.
-	type fdWriteFn func(ctx publicwasm.ModuleContext, fd, iovs, iovsCount, resultSize uint32) wasi.Errno
+	type fdWriteFn func(ctx publicwasm.Module, fd, iovs, iovsCount, resultSize uint32) wasi.Errno
 	tests := []struct {
 		name    string
 		fdWrite func() fdWriteFn
@@ -1104,8 +1099,8 @@ func TestSnapshotPreview1_FdWrite(t *testing.T) {
 			return api.FdWrite
 		}},
 		{FunctionFdWrite, func() fdWriteFn {
-			return func(ctx publicwasm.ModuleContext, fd, iovs, iovsCount, resultSize uint32) wasi.Errno {
-				ret, err := fn.Call(context.Background(), uint64(fd), uint64(iovs), uint64(iovsCount), uint64(resultSize))
+			return func(ctx publicwasm.Module, fd, iovs, iovsCount, resultSize uint32) wasi.Errno {
+				ret, err := fn.Call(nil, uint64(fd), uint64(iovs), uint64(iovsCount), uint64(resultSize))
 				require.NoError(t, err)
 				return wasi.Errno(ret[0])
 			}
@@ -1122,14 +1117,14 @@ func TestSnapshotPreview1_FdWrite(t *testing.T) {
 				fileSys: memFS,
 				file:    file,
 			}
-			maskMemory(t, mem, len(expectedMemory))
-			ok := mem.Write(0, initialMemory)
+			maskMemory(t, mod, len(expectedMemory))
+			ok := mod.Memory().Write(0, initialMemory)
 			require.True(t, ok)
 
-			errno := tc.fdWrite()(ctx(mem), fd, iovs, iovsCount, resultSize)
+			errno := tc.fdWrite()(mod, fd, iovs, iovsCount, resultSize)
 			require.Equal(t, wasi.ErrnoSuccess, errno)
 
-			actual, ok := mem.Read(0, uint32(len(expectedMemory)))
+			actual, ok := mod.Memory().Read(0, uint32(len(expectedMemory)))
 			require.True(t, ok)
 			require.Equal(t, expectedMemory, actual)
 			require.Equal(t, []byte("wazero"), file.buf.Bytes()) // verify the file was actually written
@@ -1140,7 +1135,7 @@ func TestSnapshotPreview1_FdWrite(t *testing.T) {
 func TestSnapshotPreview1_FdWrite_Errors(t *testing.T) {
 	validFD := uint64(3)                                // arbitrary valid fd after 0, 1, and 2, that are stdin/out/err
 	file, memFS := createFile(t, "test_path", []byte{}) // file with empty contents
-	mem, fn := instantiateModule(t, FunctionFdWrite, ImportFdWrite, moduleName, func(a *wasiAPI) {
+	mod, fn := instantiateModule(t, FunctionFdWrite, ImportFdWrite, moduleName, func(a *wasiAPI) {
 		a.opened[uint32(validFD)] = fileEntry{
 			path:    "test_path",
 			fileSys: memFS,
@@ -1176,7 +1171,7 @@ func TestSnapshotPreview1_FdWrite_Errors(t *testing.T) {
 		{
 			name:          "out-of-memory reading iovs[0].length",
 			fd:            validFD,
-			memory:        memory[0:4], // iovs[0].offset was 4 bytes and iovs[0].length next, but not enough mem!
+			memory:        memory[0:4], // iovs[0].offset was 4 bytes and iovs[0].length next, but not enough mod.Memory()!
 			expectedErrno: wasi.ErrnoFault,
 		},
 		{
@@ -1203,9 +1198,9 @@ func TestSnapshotPreview1_FdWrite_Errors(t *testing.T) {
 	for _, tt := range tests {
 		tc := tt
 		t.Run(tc.name, func(t *testing.T) {
-			mem.(*wasm.MemoryInstance).Buffer = tc.memory
+			mod.Memory().(*wasm.MemoryInstance).Buffer = tc.memory
 
-			results, err := fn.Call(context.Background(), tc.fd, iovs, iovsCount, tc.resultSize)
+			results, err := fn.Call(nil, tc.fd, iovs, iovsCount, tc.resultSize)
 			require.NoError(t, err)
 			require.Equal(t, tc.expectedErrno, wasi.Errno(results[0])) // results[0] is the errno
 		})
@@ -1226,14 +1221,14 @@ func createFile(t *testing.T, path string, contents []byte) (*memFile, *MemFS) {
 
 // TestSnapshotPreview1_PathCreateDirectory only tests it is stubbed for GrainLang per #271
 func TestSnapshotPreview1_PathCreateDirectory(t *testing.T) {
-	mem, fn := instantiateModule(t, FunctionPathCreateDirectory, ImportPathCreateDirectory, moduleName)
+	mod, fn := instantiateModule(t, FunctionPathCreateDirectory, ImportPathCreateDirectory, moduleName)
 
 	t.Run("SnapshotPreview1.PathCreateDirectory", func(t *testing.T) {
-		require.Equal(t, wasi.ErrnoNosys, NewAPI().PathCreateDirectory(ctx(mem), 0, 0, 0))
+		require.Equal(t, wasi.ErrnoNosys, NewAPI().PathCreateDirectory(mod, 0, 0, 0))
 	})
 
 	t.Run(FunctionPathCreateDirectory, func(t *testing.T) {
-		results, err := fn.Call(context.Background(), 0, 0, 0)
+		results, err := fn.Call(nil, 0, 0, 0)
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoNosys, wasi.Errno(results[0])) // cast because results are always uint64
 	})
@@ -1241,14 +1236,14 @@ func TestSnapshotPreview1_PathCreateDirectory(t *testing.T) {
 
 // TestSnapshotPreview1_PathFilestatGet only tests it is stubbed for GrainLang per #271
 func TestSnapshotPreview1_PathFilestatGet(t *testing.T) {
-	mem, fn := instantiateModule(t, FunctionPathFilestatGet, ImportPathFilestatGet, moduleName)
+	mod, fn := instantiateModule(t, FunctionPathFilestatGet, ImportPathFilestatGet, moduleName)
 
 	t.Run("SnapshotPreview1.PathFilestatGet", func(t *testing.T) {
-		require.Equal(t, wasi.ErrnoNosys, NewAPI().PathFilestatGet(ctx(mem), 0, 0, 0, 0, 0))
+		require.Equal(t, wasi.ErrnoNosys, NewAPI().PathFilestatGet(mod, 0, 0, 0, 0, 0))
 	})
 
 	t.Run(FunctionPathFilestatGet, func(t *testing.T) {
-		results, err := fn.Call(context.Background(), 0, 0, 0, 0, 0)
+		results, err := fn.Call(nil, 0, 0, 0, 0, 0)
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoNosys, wasi.Errno(results[0])) // cast because results are always uint64
 	})
@@ -1256,14 +1251,14 @@ func TestSnapshotPreview1_PathFilestatGet(t *testing.T) {
 
 // TestSnapshotPreview1_PathFilestatSetTimes only tests it is stubbed for GrainLang per #271
 func TestSnapshotPreview1_PathFilestatSetTimes(t *testing.T) {
-	mem, fn := instantiateModule(t, FunctionPathFilestatSetTimes, ImportPathFilestatSetTimes, moduleName)
+	mod, fn := instantiateModule(t, FunctionPathFilestatSetTimes, ImportPathFilestatSetTimes, moduleName)
 
 	t.Run("SnapshotPreview1.PathFilestatSetTimes", func(t *testing.T) {
-		require.Equal(t, wasi.ErrnoNosys, NewAPI().PathFilestatSetTimes(ctx(mem), 0, 0, 0, 0, 0, 0, 0))
+		require.Equal(t, wasi.ErrnoNosys, NewAPI().PathFilestatSetTimes(mod, 0, 0, 0, 0, 0, 0, 0))
 	})
 
 	t.Run(FunctionPathFilestatSetTimes, func(t *testing.T) {
-		results, err := fn.Call(context.Background(), 0, 0, 0, 0, 0, 0, 0)
+		results, err := fn.Call(nil, 0, 0, 0, 0, 0, 0, 0)
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoNosys, wasi.Errno(results[0])) // cast because results are always uint64
 	})
@@ -1271,14 +1266,14 @@ func TestSnapshotPreview1_PathFilestatSetTimes(t *testing.T) {
 
 // TestSnapshotPreview1_PathLink only tests it is stubbed for GrainLang per #271
 func TestSnapshotPreview1_PathLink(t *testing.T) {
-	mem, fn := instantiateModule(t, FunctionPathLink, ImportPathLink, moduleName)
+	mod, fn := instantiateModule(t, FunctionPathLink, ImportPathLink, moduleName)
 
 	t.Run("SnapshotPreview1.PathLink", func(t *testing.T) {
-		require.Equal(t, wasi.ErrnoNosys, NewAPI().PathLink(ctx(mem), 0, 0, 0, 0, 0, 0, 0))
+		require.Equal(t, wasi.ErrnoNosys, NewAPI().PathLink(mod, 0, 0, 0, 0, 0, 0, 0))
 	})
 
 	t.Run(FunctionPathLink, func(t *testing.T) {
-		results, err := fn.Call(context.Background(), 0, 0, 0, 0, 0, 0, 0)
+		results, err := fn.Call(nil, 0, 0, 0, 0, 0, 0, 0)
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoNosys, wasi.Errno(results[0])) // cast because results are always uint64
 	})
@@ -1307,7 +1302,7 @@ func TestSnapshotPreview1_PathOpen(t *testing.T) {
 	expectedFD := uint32(4) // arbitrary expected FD
 
 	var api *wasiAPI
-	mem, fn := instantiateModule(t, FunctionPathOpen, ImportPathOpen, moduleName, func(a *wasiAPI) {
+	mod, fn := instantiateModule(t, FunctionPathOpen, ImportPathOpen, moduleName, func(a *wasiAPI) {
 		// randSouce is used to determine the new fd. Fix it to the expectedFD for testing.
 		a.randSource = func(b []byte) error {
 			binary.LittleEndian.PutUint32(b, expectedFD)
@@ -1317,7 +1312,7 @@ func TestSnapshotPreview1_PathOpen(t *testing.T) {
 	})
 
 	// TestSnapshotPreview1_PathOpen uses a matrix because setting up test files is complicated and has to be clean each time.
-	type pathOpenFn func(ctx publicwasm.ModuleContext, fd, dirflags, path, pathLen, oflags uint32,
+	type pathOpenFn func(ctx publicwasm.Module, fd, dirflags, path, pathLen, oflags uint32,
 		fsRightsBase, fsRightsInheriting uint64,
 		fdFlags, resultOpenedFd uint32) wasi.Errno
 	tests := []struct {
@@ -1328,10 +1323,10 @@ func TestSnapshotPreview1_PathOpen(t *testing.T) {
 			return api.PathOpen
 		}},
 		{FunctionPathOpen, func() pathOpenFn {
-			return func(ctx publicwasm.ModuleContext, fd, dirflags, path, pathLen, oflags uint32,
+			return func(ctx publicwasm.Module, fd, dirflags, path, pathLen, oflags uint32,
 				fsRightsBase, fsRightsInheriting uint64,
 				fdFlags, resultOpenedFd uint32) wasi.Errno {
-				ret, err := fn.Call(context.Background(), uint64(fd), uint64(dirflags), uint64(path), uint64(pathLen), uint64(oflags), uint64(fsRightsBase), uint64(fsRightsInheriting), uint64(fdFlags), uint64(resultOpenedFd))
+				ret, err := fn.Call(nil, uint64(fd), uint64(dirflags), uint64(path), uint64(pathLen), uint64(oflags), uint64(fsRightsBase), uint64(fsRightsInheriting), uint64(fdFlags), uint64(resultOpenedFd))
 				require.NoError(t, err)
 				return wasi.Errno(ret[0])
 			}
@@ -1354,14 +1349,14 @@ func TestSnapshotPreview1_PathOpen(t *testing.T) {
 				},
 			}
 
-			maskMemory(t, mem, len(expectedMemory))
-			ok := mem.Write(0, initialMemory)
+			maskMemory(t, mod, len(expectedMemory))
+			ok := mod.Memory().Write(0, initialMemory)
 			require.True(t, ok)
 
-			errno := tc.pathOpen()(ctx(mem), fd, dirflags, path, pathLen, oflags, fsRightsBase, fsRightsInheriting, fdFlags, resultOpenedFd)
+			errno := tc.pathOpen()(mod, fd, dirflags, path, pathLen, oflags, fsRightsBase, fsRightsInheriting, fdFlags, resultOpenedFd)
 			require.Equal(t, wasi.ErrnoSuccess, errno)
 
-			actual, ok := mem.Read(0, uint32(len(expectedMemory)))
+			actual, ok := mod.Memory().Read(0, uint32(len(expectedMemory)))
 			require.True(t, ok)
 			require.Equal(t, expectedMemory, actual)
 			require.Equal(t, "wazero", api.opened[expectedFD].path) // verify the file was actually opened
@@ -1371,7 +1366,7 @@ func TestSnapshotPreview1_PathOpen(t *testing.T) {
 
 func TestSnapshotPreview1_PathOpen_Erros(t *testing.T) {
 	validFD := uint64(3) // arbitrary valid fd after 0, 1, and 2, that are stdin/out/err
-	mem, fn := instantiateModule(t, FunctionPathOpen, ImportPathOpen, moduleName, func(a *wasiAPI) {
+	mod, fn := instantiateModule(t, FunctionPathOpen, ImportPathOpen, moduleName, func(a *wasiAPI) {
 		// Create a memFS for testing that has "./wazero" file.
 		memFS := &MemFS{
 			Files: map[string][]byte{
@@ -1387,7 +1382,7 @@ func TestSnapshotPreview1_PathOpen_Erros(t *testing.T) {
 	})
 	validPath := uint64(0)    // arbitrary offset
 	validPathLen := uint64(6) // the length of "wazero"
-	mem.Write(uint32(validPath), []byte{
+	mod.Memory().Write(uint32(validPath), []byte{
 		'w', 'a', 'z', 'e', 'r', 'o', // write to offset 0 (= validPath)
 	}) // wazero is the path to the file in the memFS
 
@@ -1404,7 +1399,7 @@ func TestSnapshotPreview1_PathOpen_Erros(t *testing.T) {
 		{
 			name:          "out-of-memory reading path",
 			fd:            validFD,
-			path:          uint64(mem.Size()),
+			path:          uint64(mod.Memory().Size()),
 			pathLen:       validPathLen,
 			expectedErrno: wasi.ErrnoFault,
 		},
@@ -1412,7 +1407,7 @@ func TestSnapshotPreview1_PathOpen_Erros(t *testing.T) {
 			name:          "out-of-memory reading pathLen",
 			fd:            validFD,
 			path:          validPath,
-			pathLen:       uint64(mem.Size() + 1), // path is in the valid memory range, but pathLen is out-of-memory for path
+			pathLen:       uint64(mod.Memory().Size() + 1), // path is in the valid memory range, but pathLen is out-of-memory for path
 			expectedErrno: wasi.ErrnoFault,
 		},
 		{
@@ -1427,7 +1422,7 @@ func TestSnapshotPreview1_PathOpen_Erros(t *testing.T) {
 			fd:             validFD,
 			path:           validPath,
 			pathLen:        validPathLen,
-			resultOpenedFd: uint64(mem.Size()), // path and pathLen correctly point to the right path, but where to write the opened FD is outside memory.
+			resultOpenedFd: uint64(mod.Memory().Size()), // path and pathLen correctly point to the right path, but where to write the opened FD is outside memory.
 			expectedErrno:  wasi.ErrnoFault,
 		},
 	}
@@ -1435,7 +1430,7 @@ func TestSnapshotPreview1_PathOpen_Erros(t *testing.T) {
 	for _, tt := range tests {
 		tc := tt
 		t.Run(tc.name, func(t *testing.T) {
-			results, err := fn.Call(context.Background(), tc.fd, 0, tc.path, tc.pathLen, 0, 0, 0, 0, tc.resultOpenedFd)
+			results, err := fn.Call(nil, tc.fd, 0, tc.path, tc.pathLen, 0, 0, 0, 0, tc.resultOpenedFd)
 			require.NoError(t, err)
 			require.Equal(t, tc.expectedErrno, wasi.Errno(results[0])) // results[0] is the errno
 		})
@@ -1444,14 +1439,14 @@ func TestSnapshotPreview1_PathOpen_Erros(t *testing.T) {
 
 // TestSnapshotPreview1_PathReadlink only tests it is stubbed for GrainLang per #271
 func TestSnapshotPreview1_PathReadlink(t *testing.T) {
-	mem, fn := instantiateModule(t, FunctionPathReadlink, ImportPathReadlink, moduleName)
+	mod, fn := instantiateModule(t, FunctionPathReadlink, ImportPathReadlink, moduleName)
 
 	t.Run("SnapshotPreview1.PathLink", func(t *testing.T) {
-		require.Equal(t, wasi.ErrnoNosys, NewAPI().PathReadlink(ctx(mem), 0, 0, 0, 0, 0, 0))
+		require.Equal(t, wasi.ErrnoNosys, NewAPI().PathReadlink(mod, 0, 0, 0, 0, 0, 0))
 	})
 
 	t.Run(FunctionPathReadlink, func(t *testing.T) {
-		results, err := fn.Call(context.Background(), 0, 0, 0, 0, 0, 0)
+		results, err := fn.Call(nil, 0, 0, 0, 0, 0, 0)
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoNosys, wasi.Errno(results[0])) // cast because results are always uint64
 	})
@@ -1459,14 +1454,14 @@ func TestSnapshotPreview1_PathReadlink(t *testing.T) {
 
 // TestSnapshotPreview1_PathRemoveDirectory only tests it is stubbed for GrainLang per #271
 func TestSnapshotPreview1_PathRemoveDirectory(t *testing.T) {
-	mem, fn := instantiateModule(t, FunctionPathRemoveDirectory, ImportPathRemoveDirectory, moduleName)
+	mod, fn := instantiateModule(t, FunctionPathRemoveDirectory, ImportPathRemoveDirectory, moduleName)
 
 	t.Run("SnapshotPreview1.PathRemoveDirectory", func(t *testing.T) {
-		require.Equal(t, wasi.ErrnoNosys, NewAPI().PathRemoveDirectory(ctx(mem), 0, 0, 0))
+		require.Equal(t, wasi.ErrnoNosys, NewAPI().PathRemoveDirectory(mod, 0, 0, 0))
 	})
 
 	t.Run(FunctionPathRemoveDirectory, func(t *testing.T) {
-		results, err := fn.Call(context.Background(), 0, 0, 0)
+		results, err := fn.Call(nil, 0, 0, 0)
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoNosys, wasi.Errno(results[0])) // cast because results are always uint64
 	})
@@ -1474,14 +1469,14 @@ func TestSnapshotPreview1_PathRemoveDirectory(t *testing.T) {
 
 // TestSnapshotPreview1_PathRename only tests it is stubbed for GrainLang per #271
 func TestSnapshotPreview1_PathRename(t *testing.T) {
-	mem, fn := instantiateModule(t, FunctionPathRename, ImportPathRename, moduleName)
+	mod, fn := instantiateModule(t, FunctionPathRename, ImportPathRename, moduleName)
 
 	t.Run("SnapshotPreview1.PathRename", func(t *testing.T) {
-		require.Equal(t, wasi.ErrnoNosys, NewAPI().PathRename(ctx(mem), 0, 0, 0, 0, 0, 0))
+		require.Equal(t, wasi.ErrnoNosys, NewAPI().PathRename(mod, 0, 0, 0, 0, 0, 0))
 	})
 
 	t.Run(FunctionPathRename, func(t *testing.T) {
-		results, err := fn.Call(context.Background(), 0, 0, 0, 0, 0, 0)
+		results, err := fn.Call(nil, 0, 0, 0, 0, 0, 0)
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoNosys, wasi.Errno(results[0])) // cast because results are always uint64
 	})
@@ -1489,14 +1484,14 @@ func TestSnapshotPreview1_PathRename(t *testing.T) {
 
 // TestSnapshotPreview1_PathSymlink only tests it is stubbed for GrainLang per #271
 func TestSnapshotPreview1_PathSymlink(t *testing.T) {
-	mem, fn := instantiateModule(t, FunctionPathSymlink, ImportPathSymlink, moduleName)
+	mod, fn := instantiateModule(t, FunctionPathSymlink, ImportPathSymlink, moduleName)
 
 	t.Run("SnapshotPreview1.PathSymlink", func(t *testing.T) {
-		require.Equal(t, wasi.ErrnoNosys, NewAPI().PathSymlink(ctx(mem), 0, 0, 0, 0, 0, 0))
+		require.Equal(t, wasi.ErrnoNosys, NewAPI().PathSymlink(mod, 0, 0, 0, 0, 0, 0))
 	})
 
 	t.Run(FunctionPathSymlink, func(t *testing.T) {
-		results, err := fn.Call(context.Background(), 0, 0, 0, 0, 0, 0)
+		results, err := fn.Call(nil, 0, 0, 0, 0, 0, 0)
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoNosys, wasi.Errno(results[0])) // cast because results are always uint64
 	})
@@ -1504,14 +1499,14 @@ func TestSnapshotPreview1_PathSymlink(t *testing.T) {
 
 // TestSnapshotPreview1_PathUnlinkFile only tests it is stubbed for GrainLang per #271
 func TestSnapshotPreview1_PathUnlinkFile(t *testing.T) {
-	mem, fn := instantiateModule(t, FunctionPathUnlinkFile, ImportPathUnlinkFile, moduleName)
+	mod, fn := instantiateModule(t, FunctionPathUnlinkFile, ImportPathUnlinkFile, moduleName)
 
 	t.Run("SnapshotPreview1.PathUnlinkFile", func(t *testing.T) {
-		require.Equal(t, wasi.ErrnoNosys, NewAPI().PathUnlinkFile(ctx(mem), 0, 0, 0))
+		require.Equal(t, wasi.ErrnoNosys, NewAPI().PathUnlinkFile(mod, 0, 0, 0))
 	})
 
 	t.Run(FunctionPathUnlinkFile, func(t *testing.T) {
-		results, err := fn.Call(context.Background(), 0, 0, 0)
+		results, err := fn.Call(nil, 0, 0, 0)
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoNosys, wasi.Errno(results[0])) // cast because results are always uint64
 	})
@@ -1519,14 +1514,14 @@ func TestSnapshotPreview1_PathUnlinkFile(t *testing.T) {
 
 // TestSnapshotPreview1_PollOneoff only tests it is stubbed for GrainLang per #271
 func TestSnapshotPreview1_PollOneoff(t *testing.T) {
-	mem, fn := instantiateModule(t, FunctionPollOneoff, ImportPollOneoff, moduleName)
+	mod, fn := instantiateModule(t, FunctionPollOneoff, ImportPollOneoff, moduleName)
 
 	t.Run("SnapshotPreview1.PollOneoff", func(t *testing.T) {
-		require.Equal(t, wasi.ErrnoNosys, NewAPI().PollOneoff(ctx(mem), 0, 0, 0, 0))
+		require.Equal(t, wasi.ErrnoNosys, NewAPI().PollOneoff(mod, 0, 0, 0, 0))
 	})
 
 	t.Run(FunctionPollOneoff, func(t *testing.T) {
-		results, err := fn.Call(context.Background(), 0, 0, 0, 0)
+		results, err := fn.Call(nil, 0, 0, 0, 0)
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoNosys, wasi.Errno(results[0])) // cast because results are always uint64
 	})
@@ -1555,7 +1550,7 @@ func TestSnapshotPreview1_ProcExit(t *testing.T) {
 
 		t.Run(tc.name, func(t *testing.T) {
 			// When ProcExit is called, store.CallFunction returns immediately, returning the exit code as the error.
-			_, err := fn.Call(context.Background(), uint64(tc.exitCode))
+			_, err := fn.Call(nil, uint64(tc.exitCode))
 			var code wasi.ExitCode
 			require.ErrorAs(t, err, &code)
 			require.Equal(t, code, wasi.ExitCode(tc.exitCode))
@@ -1565,14 +1560,14 @@ func TestSnapshotPreview1_ProcExit(t *testing.T) {
 
 // TestSnapshotPreview1_ProcRaise only tests it is stubbed for GrainLang per #271
 func TestSnapshotPreview1_ProcRaise(t *testing.T) {
-	mem, fn := instantiateModule(t, FunctionProcRaise, ImportProcRaise, moduleName)
+	mod, fn := instantiateModule(t, FunctionProcRaise, ImportProcRaise, moduleName)
 
 	t.Run("SnapshotPreview1.ProcRaise", func(t *testing.T) {
-		require.Equal(t, wasi.ErrnoNosys, NewAPI().ProcRaise(ctx(mem), 0))
+		require.Equal(t, wasi.ErrnoNosys, NewAPI().ProcRaise(mod, 0))
 	})
 
 	t.Run(FunctionProcRaise, func(t *testing.T) {
-		results, err := fn.Call(context.Background(), 0)
+		results, err := fn.Call(nil, 0)
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoNosys, wasi.Errno(results[0])) // cast because results are always uint64
 	})
@@ -1580,14 +1575,14 @@ func TestSnapshotPreview1_ProcRaise(t *testing.T) {
 
 // TestSnapshotPreview1_SchedYield only tests it is stubbed for GrainLang per #271
 func TestSnapshotPreview1_SchedYield(t *testing.T) {
-	mem, fn := instantiateModule(t, FunctionSchedYield, ImportSchedYield, moduleName)
+	mod, fn := instantiateModule(t, FunctionSchedYield, ImportSchedYield, moduleName)
 
 	t.Run("SnapshotPreview1.SchedYield", func(t *testing.T) {
-		require.Equal(t, wasi.ErrnoNosys, NewAPI().SchedYield(ctx(mem)))
+		require.Equal(t, wasi.ErrnoNosys, NewAPI().SchedYield(mod))
 	})
 
 	t.Run(FunctionSchedYield, func(t *testing.T) {
-		results, err := fn.Call(context.Background())
+		results, err := fn.Call(nil)
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoNosys, wasi.Errno(results[0])) // cast because results are always uint64
 	})
@@ -1614,27 +1609,27 @@ func TestSnapshotPreview1_RandomGet(t *testing.T) {
 		}
 	}
 
-	mem, fn := instantiateModule(t, FunctionRandomGet, ImportRandomGet, moduleName, randOpt)
+	mod, fn := instantiateModule(t, FunctionRandomGet, ImportRandomGet, moduleName, randOpt)
 	t.Run("SnapshotPreview1.RandomGet", func(t *testing.T) {
-		maskMemory(t, mem, len(expectedMemory))
+		maskMemory(t, mod, len(expectedMemory))
 
 		// Invoke RandomGet directly and check the memory side effects!
-		errno := NewAPI(randOpt).RandomGet(ctx(mem), offset, length)
+		errno := NewAPI(randOpt).RandomGet(mod, offset, length)
 		require.Equal(t, wasi.ErrnoSuccess, errno)
 
-		actual, ok := mem.Read(0, offset+length+1)
+		actual, ok := mod.Memory().Read(0, offset+length+1)
 		require.True(t, ok)
 		require.Equal(t, expectedMemory, actual)
 	})
 
 	t.Run(FunctionRandomGet, func(t *testing.T) {
-		maskMemory(t, mem, len(expectedMemory))
+		maskMemory(t, mod, len(expectedMemory))
 
-		results, err := fn.Call(context.Background(), uint64(offset), uint64(length))
+		results, err := fn.Call(nil, uint64(offset), uint64(length))
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoSuccess, wasi.Errno(results[0])) // cast because results are always uint64
 
-		actual, ok := mem.Read(0, offset+length+1)
+		actual, ok := mod.Memory().Read(0, offset+length+1)
 		require.True(t, ok)
 		require.Equal(t, expectedMemory, actual)
 	})
@@ -1643,8 +1638,8 @@ func TestSnapshotPreview1_RandomGet(t *testing.T) {
 func TestSnapshotPreview1_RandomGet_Errors(t *testing.T) {
 	validAddress := uint32(0) // arbitrary valid address
 
-	mem, fn := instantiateModule(t, FunctionRandomGet, ImportRandomGet, moduleName)
-	memorySize := mem.Size()
+	mod, fn := instantiateModule(t, FunctionRandomGet, ImportRandomGet, moduleName)
+	memorySize := mod.Memory().Size()
 
 	tests := []struct {
 		name   string
@@ -1668,7 +1663,7 @@ func TestSnapshotPreview1_RandomGet_Errors(t *testing.T) {
 		tc := tt
 
 		t.Run(tc.name, func(t *testing.T) {
-			results, err := fn.Call(context.Background(), uint64(tc.offset), uint64(tc.length))
+			results, err := fn.Call(nil, uint64(tc.offset), uint64(tc.length))
 			require.NoError(t, err)
 			require.Equal(t, uint64(wasi.ErrnoFault), results[0]) // results[0] is the errno
 		})
@@ -1682,21 +1677,21 @@ func TestSnapshotPreview1_RandomGet_SourceError(t *testing.T) {
 		}
 	})
 
-	results, err := fn.Call(context.Background(), uint64(1), uint64(5)) // arbitrary offset and length
+	results, err := fn.Call(nil, uint64(1), uint64(5)) // arbitrary offset and length
 	require.NoError(t, err)
 	require.Equal(t, uint64(wasi.ErrnoIo), results[0]) // results[0] is the errno
 }
 
 // TestSnapshotPreview1_SockRecv only tests it is stubbed for GrainLang per #271
 func TestSnapshotPreview1_SockRecv(t *testing.T) {
-	mem, fn := instantiateModule(t, FunctionSockRecv, ImportSockRecv, moduleName)
+	mod, fn := instantiateModule(t, FunctionSockRecv, ImportSockRecv, moduleName)
 
 	t.Run("SnapshotPreview1.SockRecv", func(t *testing.T) {
-		require.Equal(t, wasi.ErrnoNosys, NewAPI().SockRecv(ctx(mem), 0, 0, 0, 0, 0, 0))
+		require.Equal(t, wasi.ErrnoNosys, NewAPI().SockRecv(mod, 0, 0, 0, 0, 0, 0))
 	})
 
 	t.Run(FunctionSockRecv, func(t *testing.T) {
-		results, err := fn.Call(context.Background(), 0, 0, 0, 0, 0, 0)
+		results, err := fn.Call(nil, 0, 0, 0, 0, 0, 0)
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoNosys, wasi.Errno(results[0])) // cast because results are always uint64
 	})
@@ -1704,14 +1699,14 @@ func TestSnapshotPreview1_SockRecv(t *testing.T) {
 
 // TestSnapshotPreview1_SockSend only tests it is stubbed for GrainLang per #271
 func TestSnapshotPreview1_SockSend(t *testing.T) {
-	mem, fn := instantiateModule(t, FunctionSockSend, ImportSockSend, moduleName)
+	mod, fn := instantiateModule(t, FunctionSockSend, ImportSockSend, moduleName)
 
 	t.Run("SnapshotPreview1.SockSend", func(t *testing.T) {
-		require.Equal(t, wasi.ErrnoNosys, NewAPI().SockSend(ctx(mem), 0, 0, 0, 0, 0))
+		require.Equal(t, wasi.ErrnoNosys, NewAPI().SockSend(mod, 0, 0, 0, 0, 0))
 	})
 
 	t.Run(FunctionSockSend, func(t *testing.T) {
-		results, err := fn.Call(context.Background(), 0, 0, 0, 0, 0)
+		results, err := fn.Call(nil, 0, 0, 0, 0, 0)
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoNosys, wasi.Errno(results[0])) // cast because results are always uint64
 	})
@@ -1719,14 +1714,14 @@ func TestSnapshotPreview1_SockSend(t *testing.T) {
 
 // TestSnapshotPreview1_SockShutdown only tests it is stubbed for GrainLang per #271
 func TestSnapshotPreview1_SockShutdown(t *testing.T) {
-	mem, fn := instantiateModule(t, FunctionSockShutdown, ImportSockShutdown, moduleName)
+	mod, fn := instantiateModule(t, FunctionSockShutdown, ImportSockShutdown, moduleName)
 
 	t.Run("SnapshotPreview1.SockShutdown", func(t *testing.T) {
-		require.Equal(t, wasi.ErrnoNosys, NewAPI().SockShutdown(ctx(mem), 0, 0))
+		require.Equal(t, wasi.ErrnoNosys, NewAPI().SockShutdown(mod, 0, 0))
 	})
 
 	t.Run(FunctionSockShutdown, func(t *testing.T) {
-		results, err := fn.Call(context.Background(), 0, 0)
+		results, err := fn.Call(nil, 0, 0)
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoNosys, wasi.Errno(results[0])) // cast because results are always uint64
 	})
@@ -1734,9 +1729,9 @@ func TestSnapshotPreview1_SockShutdown(t *testing.T) {
 
 const testMemoryPageSize = 1
 
-func instantiateModule(t *testing.T, wasiFunction, wasiImport, moduleName string, opts ...Option) (publicwasm.Memory, publicwasm.Function) {
+func instantiateModule(t *testing.T, wasiFunction, wasiImport, moduleName string, opts ...Option) (*wasm.ModuleContext, publicwasm.Function) {
 	enabledFeatures := wasm.Features20191205
-	mem, err := text.DecodeModule([]byte(fmt.Sprintf(`(module
+	m, err := text.DecodeModule([]byte(fmt.Sprintf(`(module
   %[2]s
   (memory 1)  ;; just an arbitrary size big enough for tests
   (export "memory" (memory 0))
@@ -1745,23 +1740,26 @@ func instantiateModule(t *testing.T, wasiFunction, wasiImport, moduleName string
 	require.NoError(t, err)
 
 	// The package `wazero` has a simpler interface for adding host modules, but we can't use that as it would create an
-	// import cycle. Instead, we export Store.NewHostModule and use it here.
+	// import cycle. Instead, we export internalwasm.NewHostModule and use it here.
+	wasi, err := wasm.NewHostModule(wasi.ModuleSnapshotPreview1, SnapshotPreview1Functions(opts...))
+	require.NoError(t, err)
+
 	store := wasm.NewStore(context.Background(), interpreter.NewEngine(), enabledFeatures)
-	_, err = store.NewHostModule(wasi.ModuleSnapshotPreview1, SnapshotPreview1Functions(opts...))
+
+	_, err = store.Instantiate(wasi, wasi.NameSection.ModuleName)
 	require.NoError(t, err)
 
-	instantiated, err := store.Instantiate(mem, moduleName)
+	mod, err := store.Instantiate(m, moduleName)
 	require.NoError(t, err)
 
-	fn := instantiated.Function(wasiFunction)
+	fn := mod.ExportedFunction(wasiFunction)
 	require.NotNil(t, fn)
-	return instantiated.Memory("memory"), fn
+	return mod, fn
 }
 
 // maskMemory sets the first memory in the store to '?' * size, so tests can see what's written.
-//
-func maskMemory(t *testing.T, mem publicwasm.Memory, size int) {
+func maskMemory(t *testing.T, mod publicwasm.Module, size int) {
 	for i := uint32(0); i < uint32(size); i++ {
-		require.True(t, mem.WriteByte(i, '?'))
+		require.True(t, mod.Memory().WriteByte(i, '?'))
 	}
 }
diff --git a/internal/wasm/binary/decoder_test.go b/internal/wasm/binary/decoder_test.go
index b9b2393a..1b7fcdc5 100644
--- a/internal/wasm/binary/decoder_test.go
+++ b/internal/wasm/binary/decoder_test.go
@@ -93,6 +93,7 @@ func TestDecodeModule(t *testing.T) {
 			require.Equal(t, tc.input, m)
 		})
 	}
+
 	t.Run("skips custom section", func(t *testing.T) {
 		input := append(append(Magic, version...),
 			wasm.SectionIDCustom, 0xf, // 15 bytes in this section
@@ -102,6 +103,7 @@ func TestDecodeModule(t *testing.T) {
 		require.NoError(t, e)
 		require.Equal(t, &wasm.Module{}, m)
 	})
+
 	t.Run("skips custom section, but not name", func(t *testing.T) {
 		input := append(append(Magic, version...),
 			wasm.SectionIDCustom, 0xf, // 15 bytes in this section
diff --git a/internal/wasm/binary/encoder_test.go b/internal/wasm/binary/encoder_test.go
index 28bd0b48..2c456946 100644
--- a/internal/wasm/binary/encoder_test.go
+++ b/internal/wasm/binary/encoder_test.go
@@ -1,6 +1,7 @@
 package binary
 
 import (
+	"reflect"
 	"testing"
 
 	"github.com/stretchr/testify/require"
@@ -169,3 +170,11 @@ func TestModule_Encode(t *testing.T) {
 		})
 	}
 }
+
+func TestModule_Encode_HostFunctionSection_Unsupported(t *testing.T) {
+	// We don't currently have an approach to serialize reflect.Value pointers
+	fn := reflect.ValueOf(func(wasm.Module) {})
+	require.Panics(t, func() {
+		EncodeModule(&wasm.Module{HostFunctionSection: []*reflect.Value{&fn}})
+	})
+}
diff --git a/internal/wasm/counts_test.go b/internal/wasm/counts_test.go
index cacee95c..4e3c58a6 100644
--- a/internal/wasm/counts_test.go
+++ b/internal/wasm/counts_test.go
@@ -1,9 +1,12 @@
 package internalwasm
 
 import (
+	"reflect"
 	"testing"
 
 	"github.com/stretchr/testify/require"
+
+	"github.com/tetratelabs/wazero/wasm"
 )
 
 func TestModule_ImportFuncCount(t *testing.T) {
@@ -201,6 +204,7 @@ func TestModule_SectionElementCount(t *testing.T) {
 	i32, f32 := ValueTypeI32, ValueTypeF32
 	zero := uint32(0)
 	empty := &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x00}}
+	fn := reflect.ValueOf(func(wasm.Module) {})
 
 	tests := []struct {
 		name     string
@@ -217,6 +221,16 @@ func TestModule_SectionElementCount(t *testing.T) {
 			input:    &Module{NameSection: &NameSection{ModuleName: "simple"}},
 			expected: map[string]uint32{"custom": 1},
 		},
+		{
+			name:     "HostFunctionSection",
+			input:    &Module{HostFunctionSection: []*reflect.Value{&fn}},
+			expected: map[string]uint32{"host_function": 1},
+		},
+		{
+			name:     "NameSection and HostFunctionSection",
+			input:    &Module{NameSection: &NameSection{ModuleName: "simple"}, HostFunctionSection: []*reflect.Value{&fn}},
+			expected: map[string]uint32{"custom": 1, "host_function": 1},
+		},
 		{
 			name: "TypeSection",
 			input: &Module{
@@ -292,6 +306,11 @@ func TestModule_SectionElementCount(t *testing.T) {
 					actual[SectionIDName(i)] = size
 				}
 			}
+
+			// SectionIDHostFunction is intentionally not after data
+			if size := tc.input.SectionElementCount(SectionIDHostFunction); size > 0 {
+				actual[SectionIDName(SectionIDHostFunction)] = size
+			}
 			require.Equal(t, tc.expected, actual)
 		})
 	}
diff --git a/internal/wasm/global_test.go b/internal/wasm/global_test.go
index 1c50713a..5141f5a3 100644
--- a/internal/wasm/global_test.go
+++ b/internal/wasm/global_test.go
@@ -262,7 +262,7 @@ func TestPublicModule_Global(t *testing.T) {
 			module, err := s.Instantiate(tc.module, "")
 			require.NoError(t, err)
 
-			if global := module.Global("global"); tc.expected != nil {
+			if global := module.ExportedGlobal("global"); tc.expected != nil {
 				require.Equal(t, tc.expected, global)
 			} else {
 				require.Nil(t, global)
diff --git a/internal/wasm/gofunc_test.go b/internal/wasm/gofunc_test.go
index 1d5319c0..c59d23cc 100644
--- a/internal/wasm/gofunc_test.go
+++ b/internal/wasm/gofunc_test.go
@@ -67,9 +67,9 @@ func TestGetFunctionType(t *testing.T) {
 			expectedType:      &FunctionType{Params: []ValueType{}, Results: []ValueType{i32}},
 		},
 		{
-			name:         "wasm.ModuleContext void return",
-			inputFunc:    func(publicwasm.ModuleContext) {},
-			expectedKind: FunctionKindGoModuleContext,
+			name:         "wasm.Module void return",
+			inputFunc:    func(publicwasm.Module) {},
+			expectedKind: FunctionKindGoModule,
 			expectedType: &FunctionType{Params: []ValueType{}, Results: []ValueType{}},
 		},
 		{
@@ -85,9 +85,9 @@ func TestGetFunctionType(t *testing.T) {
 			expectedType: &FunctionType{Params: []ValueType{i32, i64, f32, f64}, Results: []ValueType{i32}},
 		},
 		{
-			name:         "all supported params and i32 result - wasm.ModuleContext",
-			inputFunc:    func(publicwasm.ModuleContext, uint32, uint64, float32, float64) uint32 { return 0 },
-			expectedKind: FunctionKindGoModuleContext,
+			name:         "all supported params and i32 result - wasm.Module",
+			inputFunc:    func(publicwasm.Module, uint32, uint64, float32, float64) uint32 { return 0 },
+			expectedKind: FunctionKindGoModule,
 			expectedType: &FunctionType{Params: []ValueType{i32, i64, f32, f64}, Results: []ValueType{i32}},
 		},
 		{
@@ -146,7 +146,7 @@ func TestGetFunctionTypeErrors(t *testing.T) {
 		},
 		{
 			name:        "multiple context types",
-			input:       func(publicwasm.ModuleContext, context.Context) error { return nil },
+			input:       func(publicwasm.Module, context.Context) error { return nil },
 			expectedErr: "param[1] is a context.Context, which may be defined only once as param[0]",
 		},
 		{
@@ -155,9 +155,9 @@ func TestGetFunctionTypeErrors(t *testing.T) {
 			expectedErr: "param[2] is a context.Context, which may be defined only once as param[0]",
 		},
 		{
-			name:        "multiple wasm.ModuleContext",
-			input:       func(publicwasm.ModuleContext, uint64, publicwasm.ModuleContext) error { return nil },
-			expectedErr: "param[2] is a wasm.ModuleContext, which may be defined only once as param[0]",
+			name:        "multiple wasm.Module",
+			input:       func(publicwasm.Module, uint64, publicwasm.Module) error { return nil },
+			expectedErr: "param[2] is a wasm.Module, which may be defined only once as param[0]",
 		},
 	}
 
diff --git a/internal/wasm/host_test.go b/internal/wasm/host_test.go
index 9109bddf..029d3599 100644
--- a/internal/wasm/host_test.go
+++ b/internal/wasm/host_test.go
@@ -1,238 +1,76 @@
 package internalwasm
 
 import (
-	"math"
+	"reflect"
 	"testing"
 
 	"github.com/stretchr/testify/require"
 
+	"github.com/tetratelabs/wazero/wasi"
 	"github.com/tetratelabs/wazero/wasm"
 )
 
-func TestMemoryInstance_HasLen(t *testing.T) {
-	memory := &MemoryInstance{Buffer: make([]byte, 100)}
-
-	tests := []struct {
-		name        string
-		offset      uint32
-		sizeInBytes uint64
-		expected    bool
-	}{
-		{
-			name:        "simple valid arguments",
-			offset:      0, // arbitrary valid offset
-			sizeInBytes: 8, // arbitrary valid size
-			expected:    true,
-		},
-		{
-			name:        "maximum valid sizeInBytes",
-			offset:      memory.Size() - 8,
-			sizeInBytes: 8,
-			expected:    true,
-		},
-		{
-			name:        "sizeInBytes exceeds the valid size by 1",
-			offset:      100, // arbitrary valid offset
-			sizeInBytes: uint64(memory.Size() - 99),
-			expected:    false,
-		},
-		{
-			name:        "offset exceeds the memory size",
-			offset:      memory.Size(),
-			sizeInBytes: 1, // arbitrary size
-			expected:    false,
-		},
-	}
-
-	for _, tt := range tests {
-		tc := tt
-
-		t.Run(tc.name, func(t *testing.T) {
-			require.Equal(t, tc.expected, memory.hasSize(tc.offset, uint32(tc.sizeInBytes)))
-		})
-	}
+// wasiAPI simulates the real WASI api
+type wasiAPI struct {
 }
 
-func TestMemoryInstance_ReadUint32Le(t *testing.T) {
-	tests := []struct {
-		name       string
-		memory     []byte
-		offset     uint32
-		expected   uint32
-		expectedOk bool
-	}{
-		{
-			name:       "valid offset with an endian-insensitive v",
-			memory:     []byte{0xff, 0xff, 0xff, 0xff},
-			offset:     0, // arbitrary valid offset.
-			expected:   math.MaxUint32,
-			expectedOk: true,
-		},
-		{
-			name:       "valid offset with an endian-sensitive v",
-			memory:     []byte{0xfe, 0xff, 0xff, 0xff},
-			offset:     0, // arbitrary valid offset.
-			expected:   math.MaxUint32 - 1,
-			expectedOk: true,
-		},
-		{
-			name:       "maximum boundary valid offset",
-			offset:     1,
-			memory:     []byte{0x00, 0x1, 0x00, 0x00, 0x00},
-			expected:   1, // arbitrary valid v
-			expectedOk: true,
-		},
-		{
-			name:   "offset exceeds the maximum valid offset by 1",
-			memory: []byte{0xff, 0xff, 0xff, 0xff},
-			offset: 1,
-		},
-	}
-
-	for _, tt := range tests {
-		tc := tt
-
-		t.Run(tc.name, func(t *testing.T) {
-			memory := &MemoryInstance{Buffer: tc.memory}
-
-			v, ok := memory.ReadUint32Le(tc.offset)
-			require.Equal(t, tc.expectedOk, ok)
-			require.Equal(t, tc.expected, v)
-		})
-	}
+func (a *wasiAPI) ArgsSizesGet(ctx wasm.Module, resultArgc, resultArgvBufSize uint32) wasi.Errno {
+	return 0
 }
 
-func TestMemoryInstance_ReadUint64Le(t *testing.T) {
-	tests := []struct {
-		name       string
-		memory     []byte
-		offset     uint32
-		expected   uint64
-		expectedOk bool
-	}{
-		{
-			name:       "valid offset with an endian-insensitive v",
-			memory:     []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
-			offset:     0, // arbitrary valid offset.
-			expected:   math.MaxUint64,
-			expectedOk: true,
-		},
-		{
-			name:       "valid offset with an endian-sensitive v",
-			memory:     []byte{0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
-			offset:     0, // arbitrary valid offset.
-			expected:   math.MaxUint64 - 1,
-			expectedOk: true,
-		},
-		{
-			name:       "maximum boundary valid offset",
-			offset:     1,
-			memory:     []byte{0x00, 0x1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
-			expected:   1, // arbitrary valid v
-			expectedOk: true,
-		},
-		{
-			name:   "offset exceeds the maximum valid offset by 1",
-			memory: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
-			offset: 1,
-		},
-	}
-
-	for _, tt := range tests {
-		tc := tt
-
-		t.Run(tc.name, func(t *testing.T) {
-			memory := &MemoryInstance{Buffer: tc.memory}
-
-			v, ok := memory.ReadUint64Le(tc.offset)
-			require.Equal(t, tc.expectedOk, ok)
-			require.Equal(t, tc.expected, v)
-		})
-	}
+func (a *wasiAPI) FdWrite(ctx wasm.Module, fd, iovs, iovsCount, resultSize uint32) wasi.Errno {
+	return 0
 }
 
-func TestMemoryInstance_ReadFloat32Le(t *testing.T) {
-	tests := []struct {
-		name       string
-		memory     []byte
-		offset     uint32
-		expected   float32
-		expectedOk bool
-	}{
-		{
-			name:       "valid offset with an endian-insensitive v",
-			memory:     []byte{0xff, 0x00, 0x00, 0xff},
-			offset:     0, // arbitrary valid offset.
-			expected:   math.Float32frombits(uint32(0xff0000ff)),
-			expectedOk: true,
-		},
-		{
-			name:       "valid offset with an endian-sensitive v",
-			memory:     []byte{0xfe, 0x00, 0x00, 0xff},
-			offset:     0, // arbitrary valid offset.
-			expected:   math.Float32frombits(uint32(0xff0000fe)),
-			expectedOk: true,
-		},
-		{
-			name:       "maximum boundary valid offset",
-			offset:     1,
-			memory:     []byte{0x00, 0xcd, 0xcc, 0xcc, 0x3d},
-			expected:   0.1, // arbitrary valid v
-			expectedOk: true,
-		},
-		{
-			name:   "offset exceeds the maximum valid offset by 1",
-			memory: []byte{0xff, 0xff, 0xff, 0xff},
-			offset: 1,
-		},
-	}
-
-	for _, tt := range tests {
-		tc := tt
+func TestNewHostModule(t *testing.T) {
+	i32 := ValueTypeI32
 
-		t.Run(tc.name, func(t *testing.T) {
-			memory := &MemoryInstance{Buffer: tc.memory}
+	a := wasiAPI{}
+	functionArgsSizesGet := "args_sizes_get"
+	fnArgsSizesGet := reflect.ValueOf(a.ArgsSizesGet)
+	functionFdWrite := "fd_write"
+	fnFdWrite := reflect.ValueOf(a.FdWrite)
 
-			v, ok := memory.ReadFloat32Le(tc.offset)
-			require.Equal(t, tc.expectedOk, ok)
-			require.Equal(t, tc.expected, v)
-		})
-	}
-}
-
-func TestMemoryInstance_ReadFloat64Le(t *testing.T) {
 	tests := []struct {
-		name       string
-		memory     []byte
-		offset     uint32
-		expected   float64
-		expectedOk bool
+		name, moduleName string
+		goFuncs          map[string]interface{}
+		expected         *Module
 	}{
 		{
-			name:       "valid offset with an endian-insensitive v",
-			memory:     []byte{0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff},
-			offset:     0, // arbitrary valid offset.
-			expected:   math.Float64frombits(uint64(0xff000000000000ff)),
-			expectedOk: true,
-		},
-		{
-			name:       "valid offset with an endian-sensitive v",
-			memory:     []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x7f},
-			offset:     0,               // arbitrary valid offset.
-			expected:   math.MaxFloat64, // arbitrary valid v
-			expectedOk: true,
-		},
-		{
-			name:       "maximum boundary valid offset",
-			offset:     1,
-			memory:     []byte{0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x7f},
-			expected:   math.MaxFloat64, // arbitrary valid v
-			expectedOk: true,
-		},
-		{
-			name:   "offset exceeds the maximum valid offset by 1",
-			memory: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
-			offset: 1,
+			name:     "empty",
+			expected: &Module{},
+		},
+		{
+			name:       "only name",
+			moduleName: "test",
+			expected:   &Module{NameSection: &NameSection{ModuleName: "test"}},
+		},
+		{
+			name:       "two struct funcs",
+			moduleName: wasi.ModuleSnapshotPreview1,
+			goFuncs: map[string]interface{}{
+				functionArgsSizesGet: a.ArgsSizesGet,
+				functionFdWrite:      a.FdWrite,
+			},
+			expected: &Module{
+				TypeSection: []*FunctionType{
+					{Params: []ValueType{i32, i32}, Results: []ValueType{i32}},
+					{Params: []ValueType{i32, i32, i32, i32}, Results: []ValueType{i32}},
+				},
+				FunctionSection:     []Index{0, 1},
+				HostFunctionSection: []*reflect.Value{&fnArgsSizesGet, &fnFdWrite},
+				ExportSection: map[string]*Export{
+					"args_sizes_get": {Name: "args_sizes_get", Type: ExternTypeFunc, Index: 0},
+					"fd_write":       {Name: "fd_write", Type: ExternTypeFunc, Index: 1},
+				},
+				NameSection: &NameSection{
+					ModuleName: wasi.ModuleSnapshotPreview1,
+					FunctionNames: NameMap{
+						{Index: 0, Name: "args_sizes_get"},
+						{Index: 1, Name: "fd_write"},
+					},
+				},
+			},
 		},
 	}
 
@@ -240,266 +78,137 @@ func TestMemoryInstance_ReadFloat64Le(t *testing.T) {
 		tc := tt
 
 		t.Run(tc.name, func(t *testing.T) {
-			memory := &MemoryInstance{Buffer: tc.memory}
-
-			v, ok := memory.ReadFloat64Le(tc.offset)
-			require.Equal(t, tc.expectedOk, ok)
-			require.Equal(t, tc.expected, v)
-		})
-	}
-}
-
-func TestMemoryInstance_WriteUint32Le(t *testing.T) {
-	memory := &MemoryInstance{Buffer: make([]byte, 100)}
-
-	tests := []struct {
-		name          string
-		offset        uint32
-		v             uint32
-		expectedOk    bool
-		expectedBytes []byte
-	}{
-		{
-			name:          "valid offset with an endian-insensitive v",
-			offset:        0, // arbitrary valid offset.
-			v:             math.MaxUint32,
-			expectedOk:    true,
-			expectedBytes: []byte{0xff, 0xff, 0xff, 0xff},
-		},
-		{
-			name:          "valid offset with an endian-sensitive v",
-			offset:        0, // arbitrary valid offset.
-			v:             math.MaxUint32 - 1,
-			expectedOk:    true,
-			expectedBytes: []byte{0xfe, 0xff, 0xff, 0xff},
-		},
-		{
-			name:          "maximum boundary valid offset",
-			offset:        memory.Size() - 4, // 4 is the size of uint32
-			v:             1,                 // arbitrary valid v
-			expectedOk:    true,
-			expectedBytes: []byte{0x1, 0x00, 0x00, 0x00},
-		},
-		{
-			name:          "offset exceeds the maximum valid offset by 1",
-			offset:        memory.Size() - 4 + 1, // 4 is the size of uint32
-			v:             1,                     // arbitrary valid v
-			expectedBytes: []byte{0xff, 0xff, 0xff, 0xff},
-		},
-	}
-
-	for _, tt := range tests {
-		tc := tt
-
-		t.Run(tc.name, func(t *testing.T) {
-			require.Equal(t, tc.expectedOk, memory.WriteUint32Le(tc.offset, tc.v))
-			if tc.expectedOk {
-				require.Equal(t, tc.expectedBytes, memory.Buffer[tc.offset:tc.offset+4]) // 4 is the size of uint32
-			}
-		})
-	}
-}
-
-func TestMemoryInstance_WriteUint64Le(t *testing.T) {
-	memory := &MemoryInstance{Buffer: make([]byte, 100)}
-	tests := []struct {
-		name          string
-		offset        uint32
-		v             uint64
-		expectedOk    bool
-		expectedBytes []byte
-	}{
-		{
-			name:          "valid offset with an endian-insensitive v",
-			offset:        0, // arbitrary valid offset.
-			v:             math.MaxUint64,
-			expectedOk:    true,
-			expectedBytes: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
-		},
-		{
-			name:          "valid offset with an endian-sensitive v",
-			offset:        0, // arbitrary valid offset.
-			v:             math.MaxUint64 - 1,
-			expectedOk:    true,
-			expectedBytes: []byte{0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
-		},
-		{
-			name:          "maximum boundary valid offset",
-			offset:        memory.Size() - 8, // 8 is the size of uint64
-			v:             1,                 // arbitrary valid v
-			expectedOk:    true,
-			expectedBytes: []byte{0x1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
-		},
-		{
-			name:       "offset exceeds the maximum valid offset by 1",
-			offset:     memory.Size() - 8 + 1, // 8 is the size of uint64
-			v:          1,                     // arbitrary valid v
-			expectedOk: false,
-		},
-	}
-
-	for _, tt := range tests {
-		tc := tt
-
-		t.Run(tc.name, func(t *testing.T) {
-			require.Equal(t, tc.expectedOk, memory.WriteUint64Le(tc.offset, tc.v))
-			if tc.expectedOk {
-				require.Equal(t, tc.expectedBytes, memory.Buffer[tc.offset:tc.offset+8]) // 8 is the size of uint64
-			}
-		})
-	}
-}
-
-func TestMemoryInstance_WriteFloat32Le(t *testing.T) {
-	memory := &MemoryInstance{Buffer: make([]byte, 100)}
-
-	tests := []struct {
-		name          string
-		offset        uint32
-		v             float32
-		expectedOk    bool
-		expectedBytes []byte
-	}{
-		{
-			name:          "valid offset with an endian-insensitive v",
-			offset:        0, // arbitrary valid offset.
-			v:             math.Float32frombits(uint32(0xff0000ff)),
-			expectedOk:    true,
-			expectedBytes: []byte{0xff, 0x00, 0x00, 0xff},
-		},
-		{
-			name:          "valid offset with an endian-sensitive v",
-			offset:        0,                                        // arbitrary valid offset.
-			v:             math.Float32frombits(uint32(0xff0000fe)), // arbitrary valid v
-			expectedOk:    true,
-			expectedBytes: []byte{0xfe, 0x00, 0x00, 0xff},
-		},
-		{
-			name:          "maximum boundary valid offset",
-			offset:        memory.Size() - 4, // 4 is the size of float32
-			v:             0.1,               // arbitrary valid v
-			expectedOk:    true,
-			expectedBytes: []byte{0xcd, 0xcc, 0xcc, 0x3d},
-		},
-		{
-			name:          "offset exceeds the maximum valid offset by 1",
-			offset:        memory.Size() - 4 + 1, // 4 is the size of float32
-			v:             math.MaxFloat32,       // arbitrary valid v
-			expectedBytes: []byte{0xff, 0xff, 0xff, 0xff},
-		},
-	}
-
-	for _, tt := range tests {
-		tc := tt
-
-		t.Run(tc.name, func(t *testing.T) {
-			require.Equal(t, tc.expectedOk, memory.WriteFloat32Le(tc.offset, tc.v))
-			if tc.expectedOk {
-				require.Equal(t, tc.expectedBytes, memory.Buffer[tc.offset:tc.offset+4]) // 4 is the size of float32
+			m, e := NewHostModule(tc.moduleName, tc.goFuncs)
+			require.NoError(t, e)
+
+			// `require.Equal(t, tc.expected, m)` fails reflect pointers don't match, so brute compare:
+			require.Equal(t, tc.expected.TypeSection, m.TypeSection)
+			require.Equal(t, tc.expected.ImportSection, m.ImportSection)
+			require.Equal(t, tc.expected.FunctionSection, m.FunctionSection)
+			require.Equal(t, tc.expected.TableSection, m.TableSection)
+			require.Equal(t, tc.expected.MemorySection, m.MemorySection)
+			require.Equal(t, tc.expected.GlobalSection, m.GlobalSection)
+			require.Equal(t, tc.expected.ExportSection, m.ExportSection)
+			require.Equal(t, tc.expected.StartSection, m.StartSection)
+			require.Equal(t, tc.expected.ElementSection, m.ElementSection)
+			require.Nil(t, m.CodeSection) // Host functions are implemented in Go, not Wasm!
+			require.Equal(t, tc.expected.DataSection, m.DataSection)
+			require.Equal(t, tc.expected.NameSection, m.NameSection)
+
+			// Special case because reflect.Value can't be compared with Equals
+			require.Equal(t, len(tc.expected.HostFunctionSection), len(m.HostFunctionSection))
+			for i := range tc.expected.HostFunctionSection {
+				require.Equal(t, (*tc.expected.HostFunctionSection[i]).Type(), (*m.HostFunctionSection[i]).Type())
 			}
 		})
 	}
 }
 
-func TestMemoryInstance_WriteFloat64Le(t *testing.T) {
-	memory := &MemoryInstance{Buffer: make([]byte, 100)}
-	tests := []struct {
-		name          string
-		offset        uint32
-		v             float64
-		expectedOk    bool
-		expectedBytes []byte
-	}{
-		{
-			name:          "valid offset with an endian-insensitive v",
-			offset:        0, // arbitrary valid offset.
-			v:             math.Float64frombits(uint64(0xff000000000000ff)),
-			expectedOk:    true,
-			expectedBytes: []byte{0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff},
-		},
-		{
-			name:          "valid offset with an endian-sensitive v",
-			offset:        0,               // arbitrary valid offset.
-			v:             math.MaxFloat64, // arbitrary valid v
-			expectedOk:    true,
-			expectedBytes: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x7f},
-		},
-		{
-			name:          "maximum boundary valid offset",
-			offset:        memory.Size() - 8, // 8 is the size of float64
-			v:             math.MaxFloat64,   // arbitrary valid v
-			expectedOk:    true,
-			expectedBytes: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x7f},
-		},
-		{
-			name:       "offset exceeds the maximum valid offset by 1",
-			offset:     memory.Size() - 8 + 1, // 8 is the size of float64
-			v:          math.MaxFloat64,       // arbitrary valid v
-			expectedOk: false,
-		},
-	}
-
-	for _, tt := range tests {
-		tc := tt
-
-		t.Run(tc.name, func(t *testing.T) {
-			require.Equal(t, tc.expectedOk, memory.WriteFloat64Le(tc.offset, tc.v))
-			if tc.expectedOk {
-				require.Equal(t, tc.expectedBytes, memory.Buffer[tc.offset:tc.offset+8]) // 8 is the size of float64
-			}
-		})
-	}
-}
-
-func TestStore_NewHostModule(t *testing.T) {
-	s := newStore()
-
-	// Add the host module
-	_, err := s.NewHostModule("test", map[string]interface{}{"fn": func(wasm.ModuleContext) {}})
-	require.NoError(t, err)
-
-	// Ensure it was added to module instances
-	hm := s.modules["test"]
-	require.NotNil(t, hm)
-
-	// The function was added to the store, prefixed by the owning module name
-	require.Equal(t, 1, len(s.functions))
-	fn := s.functions[0]
-	require.Equal(t, "test.fn", fn.Name)
-
-	// The function was exported in the module
-	require.Equal(t, 1, len(hm.Exports))
-	_, ok := hm.Exports["fn"]
-	require.True(t, ok)
-
-	// Trying to register it again should fail
-	_, err = s.NewHostModule("test", map[string]interface{}{"host_fn": func(wasm.ModuleContext) {}})
-	require.EqualError(t, err, "module test has already been instantiated")
-}
-
-func TestStore_NewHostModule_Errors(t *testing.T) {
-	s := newStore()
-
+func TestNewHostModule_Errors(t *testing.T) {
 	t.Run("Adds export name to error message", func(t *testing.T) {
-		_, err := s.NewHostModule("test", map[string]interface{}{"fn": "hello"})
+		_, err := NewHostModule("test", map[string]interface{}{"fn": "hello"})
 		require.EqualError(t, err, "func[fn] kind != func: string")
-
 	})
+}
 
-	t.Run("Fails if module name already in use", func(t *testing.T) {
-		_, err := s.NewHostModule("test", map[string]interface{}{"host_fn": func(wasm.ModuleContext) {}})
+func TestModule_validateHostFunctions(t *testing.T) {
+	notFn := reflect.ValueOf(t)
+	fn := reflect.ValueOf(func(wasm.Module) {})
+
+	t.Run("ok", func(t *testing.T) {
+		m := Module{
+			TypeSection:         []*FunctionType{{}},
+			FunctionSection:     []uint32{0},
+			HostFunctionSection: []*reflect.Value{&fn},
+		}
+		err := m.validateHostFunctions()
 		require.NoError(t, err)
-		// Trying to register it again should fail
-		_, err = s.NewHostModule("test", map[string]interface{}{"host_fn": func(wasm.ModuleContext) {}})
-		require.EqualError(t, err, "module test has already been instantiated")
 	})
-}
-
-func TestHostModule_String(t *testing.T) {
-	s := newStore()
-
-	// Ensure paths that can create the host module can see the name.
-	hm, err := s.NewHostModule("host", map[string]interface{}{"host_fn": func(wasm.ModuleContext) {}})
-	require.NoError(t, err)
-	require.Equal(t, "HostModule[host]", hm.String())
+	t.Run("function, but no host function", func(t *testing.T) {
+		m := Module{
+			TypeSection:         []*FunctionType{{}},
+			FunctionSection:     []Index{0},
+			HostFunctionSection: nil,
+		}
+		err := m.validateHostFunctions()
+		require.Error(t, err)
+		require.EqualError(t, err, "host function count (0) != function count (1)")
+	})
+	t.Run("function out of range of host functions", func(t *testing.T) {
+		m := Module{
+			TypeSection:         []*FunctionType{{}},
+			FunctionSection:     []Index{1},
+			HostFunctionSection: []*reflect.Value{&fn},
+		}
+		err := m.validateHostFunctions()
+		require.Error(t, err)
+		require.EqualError(t, err, "host_function[0] type section index out of range: 1")
+	})
+	t.Run("mismatch params", func(t *testing.T) {
+		m := Module{
+			TypeSection:         []*FunctionType{{Params: []ValueType{ValueTypeF32}}},
+			FunctionSection:     []Index{0},
+			HostFunctionSection: []*reflect.Value{&fn},
+		}
+		err := m.validateHostFunctions()
+		require.Error(t, err)
+		require.EqualError(t, err, "host_function[0] signature doesn't match type section: v_v != f32_v")
+	})
+	t.Run("mismatch results", func(t *testing.T) {
+		m := Module{
+			TypeSection:         []*FunctionType{{Results: []ValueType{ValueTypeF32}}},
+			FunctionSection:     []Index{0},
+			HostFunctionSection: []*reflect.Value{&fn},
+		}
+		err := m.validateHostFunctions()
+		require.Error(t, err)
+		require.EqualError(t, err, "host_function[0] signature doesn't match type section: v_v != v_f32")
+	})
+	t.Run("not a function", func(t *testing.T) {
+		m := Module{
+			TypeSection:         []*FunctionType{{}},
+			FunctionSection:     []Index{0},
+			HostFunctionSection: []*reflect.Value{&notFn},
+		}
+		err := m.validateHostFunctions()
+		require.Error(t, err)
+		require.EqualError(t, err, "host_function[0] is not a valid go func: kind != func: ptr")
+	})
+	t.Run("not a function - exported", func(t *testing.T) {
+		m := Module{
+			TypeSection:         []*FunctionType{{}},
+			FunctionSection:     []Index{0},
+			HostFunctionSection: []*reflect.Value{&notFn},
+			ExportSection:       map[string]*Export{"f1": {Name: "f1", Type: ExternTypeFunc, Index: 0}},
+		}
+		err := m.validateHostFunctions()
+		require.Error(t, err)
+		require.EqualError(t, err, `host_function[0] (export "f1") is not a valid go func: kind != func: ptr`)
+	})
+	t.Run("not a function  - exported after import", func(t *testing.T) {
+		m := Module{
+			TypeSection:         []*FunctionType{{}},
+			ImportSection:       []*Import{{Type: ExternTypeFunc}},
+			FunctionSection:     []Index{1},
+			HostFunctionSection: []*reflect.Value{&notFn},
+			ExportSection:       map[string]*Export{"f1": {Name: "f1", Type: ExternTypeFunc, Index: 1}},
+		}
+		err := m.validateHostFunctions()
+		require.Error(t, err)
+		require.EqualError(t, err, `host_function[0] (export "f1") is not a valid go func: kind != func: ptr`)
+	})
+	t.Run("not a function - exported twice", func(t *testing.T) {
+		m := Module{
+			TypeSection:         []*FunctionType{{}},
+			FunctionSection:     []Index{0},
+			HostFunctionSection: []*reflect.Value{&notFn},
+			ExportSection: map[string]*Export{
+				"f1": {Name: "f1", Type: ExternTypeFunc, Index: 0},
+				"f2": {Name: "f2", Type: ExternTypeFunc, Index: 0},
+			},
+		}
+		err := m.validateHostFunctions()
+		require.Error(t, err)
+		require.EqualError(t, err, `host_function[0] (export "f1","f2") is not a valid go func: kind != func: ptr`)
+	})
 }
diff --git a/internal/wasm/interpreter/interpreter_test.go b/internal/wasm/interpreter/interpreter_test.go
index 9c395a74..64c59ba9 100644
--- a/internal/wasm/interpreter/interpreter_test.go
+++ b/internal/wasm/interpreter/interpreter_test.go
@@ -61,21 +61,21 @@ func TestEngine_Call(t *testing.T) {
 	mod, err := store.Instantiate(m, "")
 	require.NoError(t, err)
 
-	fn := mod.Function("fn")
+	fn := mod.ExportedFunction("fn")
 	require.NotNil(t, fn)
 
 	// ensure base case doesn't fail
-	results, err := fn.Call(context.Background(), 3)
+	results, err := fn.Call(nil, 3)
 	require.NoError(t, err)
 	require.Equal(t, uint64(3), results[0])
 
 	t.Run("errs when not enough parameters", func(t *testing.T) {
-		_, err := fn.Call(context.Background())
+		_, err := fn.Call(nil)
 		require.EqualError(t, err, "expected 1 params, but passed 0")
 	})
 
 	t.Run("errs when too many parameters", func(t *testing.T) {
-		_, err := fn.Call(context.Background(), 1, 2)
+		_, err := fn.Call(nil, 1, 2)
 		require.EqualError(t, err, "expected 1 params, but passed 2")
 	})
 }
@@ -83,7 +83,7 @@ func TestEngine_Call(t *testing.T) {
 func TestEngine_Call_HostFn(t *testing.T) {
 	memory := &wasm.MemoryInstance{}
 	var ctxMemory publicwasm.Memory
-	hostFn := reflect.ValueOf(func(ctx publicwasm.ModuleContext, v uint64) uint64 {
+	hostFn := reflect.ValueOf(func(ctx publicwasm.Module, v uint64) uint64 {
 		ctxMemory = ctx.Memory()
 		return v
 	})
@@ -93,7 +93,7 @@ func TestEngine_Call_HostFn(t *testing.T) {
 	modCtx := wasm.NewModuleContext(context.Background(), e, module)
 	f := &wasm.FunctionInstance{
 		GoFunc: &hostFn,
-		Kind:   wasm.FunctionKindGoModuleContext,
+		Kind:   wasm.FunctionKindGoModule,
 		Type: &wasm.FunctionType{
 			Params:  []wasm.ValueType{wasm.ValueTypeI64},
 			Results: []wasm.ValueType{wasm.ValueTypeI64},
diff --git a/internal/wasm/jit/engine_test.go b/internal/wasm/jit/engine_test.go
index 717dbb2c..fd4eee0e 100644
--- a/internal/wasm/jit/engine_test.go
+++ b/internal/wasm/jit/engine_test.go
@@ -102,21 +102,21 @@ func TestEngine_Call(t *testing.T) {
 	mod, err := store.Instantiate(m, "")
 	require.NoError(t, err)
 
-	fn := mod.Function("fn")
+	fn := mod.ExportedFunction("fn")
 	require.NotNil(t, fn)
 
 	// ensure base case doesn't fail
-	results, err := fn.Call(context.Background(), 3)
+	results, err := fn.Call(nil, 3)
 	require.NoError(t, err)
 	require.Equal(t, uint64(3), results[0])
 
 	t.Run("errs when not enough parameters", func(t *testing.T) {
-		_, err := fn.Call(context.Background())
+		_, err := fn.Call(nil)
 		require.EqualError(t, err, "expected 1 params, but passed 0")
 	})
 
 	t.Run("errs when too many parameters", func(t *testing.T) {
-		_, err := fn.Call(context.Background(), 1, 2)
+		_, err := fn.Call(nil, 1, 2)
 		require.EqualError(t, err, "expected 1 params, but passed 2")
 	})
 }
@@ -126,7 +126,7 @@ func TestEngine_Call_HostFn(t *testing.T) {
 
 	memory := &wasm.MemoryInstance{}
 	var ctxMemory publicwasm.Memory
-	hostFn := reflect.ValueOf(func(ctx publicwasm.ModuleContext, v uint64) uint64 {
+	hostFn := reflect.ValueOf(func(ctx publicwasm.Module, v uint64) uint64 {
 		ctxMemory = ctx.Memory()
 		return v
 	})
@@ -136,7 +136,7 @@ func TestEngine_Call_HostFn(t *testing.T) {
 	modCtx := wasm.NewModuleContext(context.Background(), e, module)
 	f := &wasm.FunctionInstance{
 		GoFunc: &hostFn,
-		Kind:   wasm.FunctionKindGoModuleContext,
+		Kind:   wasm.FunctionKindGoModule,
 		Type: &wasm.FunctionType{
 			Params:  []wasm.ValueType{wasm.ValueTypeI64},
 			Results: []wasm.ValueType{wasm.ValueTypeI64},
diff --git a/internal/wasm/memory_test.go b/internal/wasm/memory_test.go
index 073e665a..425ee4f3 100644
--- a/internal/wasm/memory_test.go
+++ b/internal/wasm/memory_test.go
@@ -1,6 +1,7 @@
 package internalwasm
 
 import (
+	"math"
 	"testing"
 
 	"github.com/stretchr/testify/require"
@@ -87,3 +88,445 @@ func TestWriteUint32Le(t *testing.T) {
 	require.False(t, mem.WriteUint32Le(5, 16))
 	require.False(t, mem.WriteUint32Le(9, 16))
 }
+
+func TestMemoryInstance_HasLen(t *testing.T) {
+	memory := &MemoryInstance{Buffer: make([]byte, 100)}
+
+	tests := []struct {
+		name        string
+		offset      uint32
+		sizeInBytes uint64
+		expected    bool
+	}{
+		{
+			name:        "simple valid arguments",
+			offset:      0, // arbitrary valid offset
+			sizeInBytes: 8, // arbitrary valid size
+			expected:    true,
+		},
+		{
+			name:        "maximum valid sizeInBytes",
+			offset:      memory.Size() - 8,
+			sizeInBytes: 8,
+			expected:    true,
+		},
+		{
+			name:        "sizeInBytes exceeds the valid size by 1",
+			offset:      100, // arbitrary valid offset
+			sizeInBytes: uint64(memory.Size() - 99),
+			expected:    false,
+		},
+		{
+			name:        "offset exceeds the memory size",
+			offset:      memory.Size(),
+			sizeInBytes: 1, // arbitrary size
+			expected:    false,
+		},
+	}
+
+	for _, tt := range tests {
+		tc := tt
+
+		t.Run(tc.name, func(t *testing.T) {
+			require.Equal(t, tc.expected, memory.hasSize(tc.offset, uint32(tc.sizeInBytes)))
+		})
+	}
+}
+
+func TestMemoryInstance_ReadUint32Le(t *testing.T) {
+	tests := []struct {
+		name       string
+		memory     []byte
+		offset     uint32
+		expected   uint32
+		expectedOk bool
+	}{
+		{
+			name:       "valid offset with an endian-insensitive v",
+			memory:     []byte{0xff, 0xff, 0xff, 0xff},
+			offset:     0, // arbitrary valid offset.
+			expected:   math.MaxUint32,
+			expectedOk: true,
+		},
+		{
+			name:       "valid offset with an endian-sensitive v",
+			memory:     []byte{0xfe, 0xff, 0xff, 0xff},
+			offset:     0, // arbitrary valid offset.
+			expected:   math.MaxUint32 - 1,
+			expectedOk: true,
+		},
+		{
+			name:       "maximum boundary valid offset",
+			offset:     1,
+			memory:     []byte{0x00, 0x1, 0x00, 0x00, 0x00},
+			expected:   1, // arbitrary valid v
+			expectedOk: true,
+		},
+		{
+			name:   "offset exceeds the maximum valid offset by 1",
+			memory: []byte{0xff, 0xff, 0xff, 0xff},
+			offset: 1,
+		},
+	}
+
+	for _, tt := range tests {
+		tc := tt
+
+		t.Run(tc.name, func(t *testing.T) {
+			memory := &MemoryInstance{Buffer: tc.memory}
+
+			v, ok := memory.ReadUint32Le(tc.offset)
+			require.Equal(t, tc.expectedOk, ok)
+			require.Equal(t, tc.expected, v)
+		})
+	}
+}
+
+func TestMemoryInstance_ReadUint64Le(t *testing.T) {
+	tests := []struct {
+		name       string
+		memory     []byte
+		offset     uint32
+		expected   uint64
+		expectedOk bool
+	}{
+		{
+			name:       "valid offset with an endian-insensitive v",
+			memory:     []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
+			offset:     0, // arbitrary valid offset.
+			expected:   math.MaxUint64,
+			expectedOk: true,
+		},
+		{
+			name:       "valid offset with an endian-sensitive v",
+			memory:     []byte{0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
+			offset:     0, // arbitrary valid offset.
+			expected:   math.MaxUint64 - 1,
+			expectedOk: true,
+		},
+		{
+			name:       "maximum boundary valid offset",
+			offset:     1,
+			memory:     []byte{0x00, 0x1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+			expected:   1, // arbitrary valid v
+			expectedOk: true,
+		},
+		{
+			name:   "offset exceeds the maximum valid offset by 1",
+			memory: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
+			offset: 1,
+		},
+	}
+
+	for _, tt := range tests {
+		tc := tt
+
+		t.Run(tc.name, func(t *testing.T) {
+			memory := &MemoryInstance{Buffer: tc.memory}
+
+			v, ok := memory.ReadUint64Le(tc.offset)
+			require.Equal(t, tc.expectedOk, ok)
+			require.Equal(t, tc.expected, v)
+		})
+	}
+}
+
+func TestMemoryInstance_ReadFloat32Le(t *testing.T) {
+	tests := []struct {
+		name       string
+		memory     []byte
+		offset     uint32
+		expected   float32
+		expectedOk bool
+	}{
+		{
+			name:       "valid offset with an endian-insensitive v",
+			memory:     []byte{0xff, 0x00, 0x00, 0xff},
+			offset:     0, // arbitrary valid offset.
+			expected:   math.Float32frombits(uint32(0xff0000ff)),
+			expectedOk: true,
+		},
+		{
+			name:       "valid offset with an endian-sensitive v",
+			memory:     []byte{0xfe, 0x00, 0x00, 0xff},
+			offset:     0, // arbitrary valid offset.
+			expected:   math.Float32frombits(uint32(0xff0000fe)),
+			expectedOk: true,
+		},
+		{
+			name:       "maximum boundary valid offset",
+			offset:     1,
+			memory:     []byte{0x00, 0xcd, 0xcc, 0xcc, 0x3d},
+			expected:   0.1, // arbitrary valid v
+			expectedOk: true,
+		},
+		{
+			name:   "offset exceeds the maximum valid offset by 1",
+			memory: []byte{0xff, 0xff, 0xff, 0xff},
+			offset: 1,
+		},
+	}
+
+	for _, tt := range tests {
+		tc := tt
+
+		t.Run(tc.name, func(t *testing.T) {
+			memory := &MemoryInstance{Buffer: tc.memory}
+
+			v, ok := memory.ReadFloat32Le(tc.offset)
+			require.Equal(t, tc.expectedOk, ok)
+			require.Equal(t, tc.expected, v)
+		})
+	}
+}
+
+func TestMemoryInstance_ReadFloat64Le(t *testing.T) {
+	tests := []struct {
+		name       string
+		memory     []byte
+		offset     uint32
+		expected   float64
+		expectedOk bool
+	}{
+		{
+			name:       "valid offset with an endian-insensitive v",
+			memory:     []byte{0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff},
+			offset:     0, // arbitrary valid offset.
+			expected:   math.Float64frombits(uint64(0xff000000000000ff)),
+			expectedOk: true,
+		},
+		{
+			name:       "valid offset with an endian-sensitive v",
+			memory:     []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x7f},
+			offset:     0,               // arbitrary valid offset.
+			expected:   math.MaxFloat64, // arbitrary valid v
+			expectedOk: true,
+		},
+		{
+			name:       "maximum boundary valid offset",
+			offset:     1,
+			memory:     []byte{0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x7f},
+			expected:   math.MaxFloat64, // arbitrary valid v
+			expectedOk: true,
+		},
+		{
+			name:   "offset exceeds the maximum valid offset by 1",
+			memory: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
+			offset: 1,
+		},
+	}
+
+	for _, tt := range tests {
+		tc := tt
+
+		t.Run(tc.name, func(t *testing.T) {
+			memory := &MemoryInstance{Buffer: tc.memory}
+
+			v, ok := memory.ReadFloat64Le(tc.offset)
+			require.Equal(t, tc.expectedOk, ok)
+			require.Equal(t, tc.expected, v)
+		})
+	}
+}
+
+func TestMemoryInstance_WriteUint32Le(t *testing.T) {
+	memory := &MemoryInstance{Buffer: make([]byte, 100)}
+
+	tests := []struct {
+		name          string
+		offset        uint32
+		v             uint32
+		expectedOk    bool
+		expectedBytes []byte
+	}{
+		{
+			name:          "valid offset with an endian-insensitive v",
+			offset:        0, // arbitrary valid offset.
+			v:             math.MaxUint32,
+			expectedOk:    true,
+			expectedBytes: []byte{0xff, 0xff, 0xff, 0xff},
+		},
+		{
+			name:          "valid offset with an endian-sensitive v",
+			offset:        0, // arbitrary valid offset.
+			v:             math.MaxUint32 - 1,
+			expectedOk:    true,
+			expectedBytes: []byte{0xfe, 0xff, 0xff, 0xff},
+		},
+		{
+			name:          "maximum boundary valid offset",
+			offset:        memory.Size() - 4, // 4 is the size of uint32
+			v:             1,                 // arbitrary valid v
+			expectedOk:    true,
+			expectedBytes: []byte{0x1, 0x00, 0x00, 0x00},
+		},
+		{
+			name:          "offset exceeds the maximum valid offset by 1",
+			offset:        memory.Size() - 4 + 1, // 4 is the size of uint32
+			v:             1,                     // arbitrary valid v
+			expectedBytes: []byte{0xff, 0xff, 0xff, 0xff},
+		},
+	}
+
+	for _, tt := range tests {
+		tc := tt
+
+		t.Run(tc.name, func(t *testing.T) {
+			require.Equal(t, tc.expectedOk, memory.WriteUint32Le(tc.offset, tc.v))
+			if tc.expectedOk {
+				require.Equal(t, tc.expectedBytes, memory.Buffer[tc.offset:tc.offset+4]) // 4 is the size of uint32
+			}
+		})
+	}
+}
+
+func TestMemoryInstance_WriteUint64Le(t *testing.T) {
+	memory := &MemoryInstance{Buffer: make([]byte, 100)}
+	tests := []struct {
+		name          string
+		offset        uint32
+		v             uint64
+		expectedOk    bool
+		expectedBytes []byte
+	}{
+		{
+			name:          "valid offset with an endian-insensitive v",
+			offset:        0, // arbitrary valid offset.
+			v:             math.MaxUint64,
+			expectedOk:    true,
+			expectedBytes: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
+		},
+		{
+			name:          "valid offset with an endian-sensitive v",
+			offset:        0, // arbitrary valid offset.
+			v:             math.MaxUint64 - 1,
+			expectedOk:    true,
+			expectedBytes: []byte{0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
+		},
+		{
+			name:          "maximum boundary valid offset",
+			offset:        memory.Size() - 8, // 8 is the size of uint64
+			v:             1,                 // arbitrary valid v
+			expectedOk:    true,
+			expectedBytes: []byte{0x1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+		},
+		{
+			name:       "offset exceeds the maximum valid offset by 1",
+			offset:     memory.Size() - 8 + 1, // 8 is the size of uint64
+			v:          1,                     // arbitrary valid v
+			expectedOk: false,
+		},
+	}
+
+	for _, tt := range tests {
+		tc := tt
+
+		t.Run(tc.name, func(t *testing.T) {
+			require.Equal(t, tc.expectedOk, memory.WriteUint64Le(tc.offset, tc.v))
+			if tc.expectedOk {
+				require.Equal(t, tc.expectedBytes, memory.Buffer[tc.offset:tc.offset+8]) // 8 is the size of uint64
+			}
+		})
+	}
+}
+
+func TestMemoryInstance_WriteFloat32Le(t *testing.T) {
+	memory := &MemoryInstance{Buffer: make([]byte, 100)}
+
+	tests := []struct {
+		name          string
+		offset        uint32
+		v             float32
+		expectedOk    bool
+		expectedBytes []byte
+	}{
+		{
+			name:          "valid offset with an endian-insensitive v",
+			offset:        0, // arbitrary valid offset.
+			v:             math.Float32frombits(uint32(0xff0000ff)),
+			expectedOk:    true,
+			expectedBytes: []byte{0xff, 0x00, 0x00, 0xff},
+		},
+		{
+			name:          "valid offset with an endian-sensitive v",
+			offset:        0,                                        // arbitrary valid offset.
+			v:             math.Float32frombits(uint32(0xff0000fe)), // arbitrary valid v
+			expectedOk:    true,
+			expectedBytes: []byte{0xfe, 0x00, 0x00, 0xff},
+		},
+		{
+			name:          "maximum boundary valid offset",
+			offset:        memory.Size() - 4, // 4 is the size of float32
+			v:             0.1,               // arbitrary valid v
+			expectedOk:    true,
+			expectedBytes: []byte{0xcd, 0xcc, 0xcc, 0x3d},
+		},
+		{
+			name:          "offset exceeds the maximum valid offset by 1",
+			offset:        memory.Size() - 4 + 1, // 4 is the size of float32
+			v:             math.MaxFloat32,       // arbitrary valid v
+			expectedBytes: []byte{0xff, 0xff, 0xff, 0xff},
+		},
+	}
+
+	for _, tt := range tests {
+		tc := tt
+
+		t.Run(tc.name, func(t *testing.T) {
+			require.Equal(t, tc.expectedOk, memory.WriteFloat32Le(tc.offset, tc.v))
+			if tc.expectedOk {
+				require.Equal(t, tc.expectedBytes, memory.Buffer[tc.offset:tc.offset+4]) // 4 is the size of float32
+			}
+		})
+	}
+}
+
+func TestMemoryInstance_WriteFloat64Le(t *testing.T) {
+	memory := &MemoryInstance{Buffer: make([]byte, 100)}
+	tests := []struct {
+		name          string
+		offset        uint32
+		v             float64
+		expectedOk    bool
+		expectedBytes []byte
+	}{
+		{
+			name:          "valid offset with an endian-insensitive v",
+			offset:        0, // arbitrary valid offset.
+			v:             math.Float64frombits(uint64(0xff000000000000ff)),
+			expectedOk:    true,
+			expectedBytes: []byte{0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff},
+		},
+		{
+			name:          "valid offset with an endian-sensitive v",
+			offset:        0,               // arbitrary valid offset.
+			v:             math.MaxFloat64, // arbitrary valid v
+			expectedOk:    true,
+			expectedBytes: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x7f},
+		},
+		{
+			name:          "maximum boundary valid offset",
+			offset:        memory.Size() - 8, // 8 is the size of float64
+			v:             math.MaxFloat64,   // arbitrary valid v
+			expectedOk:    true,
+			expectedBytes: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x7f},
+		},
+		{
+			name:       "offset exceeds the maximum valid offset by 1",
+			offset:     memory.Size() - 8 + 1, // 8 is the size of float64
+			v:          math.MaxFloat64,       // arbitrary valid v
+			expectedOk: false,
+		},
+	}
+
+	for _, tt := range tests {
+		tc := tt
+
+		t.Run(tc.name, func(t *testing.T) {
+			require.Equal(t, tc.expectedOk, memory.WriteFloat64Le(tc.offset, tc.v))
+			if tc.expectedOk {
+				require.Equal(t, tc.expectedBytes, memory.Buffer[tc.offset:tc.offset+8]) // 8 is the size of float64
+			}
+		})
+	}
+}
diff --git a/internal/wasm/module_test.go b/internal/wasm/module_test.go
index dcb4aae8..2bd077f8 100644
--- a/internal/wasm/module_test.go
+++ b/internal/wasm/module_test.go
@@ -4,10 +4,12 @@ import (
 	"encoding/binary"
 	"fmt"
 	"math"
-	"strings"
+	"reflect"
 	"testing"
 
 	"github.com/stretchr/testify/require"
+
+	"github.com/tetratelabs/wazero/wasm"
 )
 
 func TestFunctionType_String(t *testing.T) {
@@ -53,6 +55,7 @@ func TestSectionIDName(t *testing.T) {
 		{"element", SectionIDElement, "element"},
 		{"code", SectionIDCode, "code"},
 		{"data", SectionIDData, "data"},
+		{"host_function", SectionIDHostFunction, "host_function"},
 		{"unknown", 100, "unknown"},
 	}
 
@@ -282,6 +285,8 @@ func TestValidateConstExpression(t *testing.T) {
 
 func TestModule_Validate_Errors(t *testing.T) {
 	zero := Index(0)
+	fn := reflect.ValueOf(func(wasm.Module) {})
+
 	tests := []struct {
 		name        string
 		input       *Module
@@ -297,6 +302,16 @@ func TestModule_Validate_Errors(t *testing.T) {
 			},
 			expectedErr: "invalid start function: func[0] has an invalid type",
 		},
+		{
+			name: "CodeSection and HostFunctionSection",
+			input: &Module{
+				TypeSection:         []*FunctionType{{}},
+				FunctionSection:     []uint32{0},
+				CodeSection:         []*Code{{Body: []byte{OpcodeEnd}}},
+				HostFunctionSection: []*reflect.Value{&fn},
+			},
+			expectedErr: "cannot mix functions and host functions in the same module",
+		},
 	}
 
 	for _, tt := range tests {
@@ -467,11 +482,7 @@ func TestModule_validateFunctions(t *testing.T) {
 		}
 		err := m.validateFunctions(nil, nil, nil, nil, Features20191205)
 		require.Error(t, err)
-
-		// go map keys do not iterate consistently
-		if !strings.Contains(err.Error(), `invalid function[0] (export "f1","f2"): cannot pop the 1st f32 operand`) {
-			require.Contains(t, err.Error(), `invalid function[0] (export "f2","f1"): cannot pop the 1st f32 operand`)
-		}
+		require.Contains(t, err.Error(), `invalid function[0] (export "f1","f2"): cannot pop the 1st f32 operand`)
 	})
 }
 
diff --git a/internal/wasm/store_test.go b/internal/wasm/store_test.go
index af8bec2d..ea0e0110 100644
--- a/internal/wasm/store_test.go
+++ b/internal/wasm/store_test.go
@@ -77,7 +77,7 @@ func TestModuleInstance_Memory(t *testing.T) {
 			instance, err := s.Instantiate(tc.input, "test")
 			require.NoError(t, err)
 
-			mem := instance.Memory("memory")
+			mem := instance.ExportedMemory("memory")
 			if tc.expected {
 				require.Equal(t, tc.expectedLen, mem.Size())
 			} else {
@@ -94,7 +94,7 @@ func TestPublicModule_String(t *testing.T) {
 	m, err := s.Instantiate(&Module{}, "module")
 	require.NoError(t, err)
 	require.Equal(t, "Module[module]", m.String())
-	require.Equal(t, "Module[module]", s.Module(m.instance.Name).String())
+	require.Equal(t, "Module[module]", s.Module(m.Module.Name).String())
 }
 
 func TestStore_ReleaseModule(t *testing.T) {
@@ -108,12 +108,14 @@ func TestStore_ReleaseModule(t *testing.T) {
 		{
 			name: "Module imports HostModule",
 			initializer: func(t *testing.T, s *Store) {
-				_, err := s.NewHostModule(importedModuleName, map[string]interface{}{"fn": func(wasm.ModuleContext) {}})
+				m, err := NewHostModule(importedModuleName, map[string]interface{}{"fn": func(wasm.Module) {}})
+				require.NoError(t, err)
+				_, err = s.Instantiate(m, importedModuleName)
 				require.NoError(t, err)
 			},
 		},
 		{
-			name: "Module imports Moudle",
+			name: "Module imports Module",
 			initializer: func(t *testing.T, s *Store) {
 				_, err := s.Instantiate(&Module{
 					TypeSection:     []*FunctionType{{}},
@@ -172,10 +174,13 @@ func TestStore_concurrent(t *testing.T) {
 	const importedModuleName = "imported"
 	const goroutines = 1000
 
+	m, err := NewHostModule(importedModuleName, map[string]interface{}{"fn": func(wasm.Module) {}})
+	require.NoError(t, err)
+
 	var wg sync.WaitGroup
 
 	s := newStore()
-	_, err := s.NewHostModule(importedModuleName, map[string]interface{}{"fn": func(wasm.ModuleContext) {}})
+	_, err = s.Instantiate(m, importedModuleName)
 	require.NoError(t, err)
 
 	hm, ok := s.modules[importedModuleName]
@@ -244,9 +249,22 @@ func TestStore_Instantiate_Errors(t *testing.T) {
 	const importedModuleName = "imported"
 	const importingModuleName = "test"
 
+	m, err := NewHostModule(importedModuleName, map[string]interface{}{"fn": func(wasm.Module) {}})
+	require.NoError(t, err)
+
+	t.Run("Fails if module name already in use", func(t *testing.T) {
+		s := newStore()
+		_, err = s.Instantiate(m, importedModuleName)
+		require.NoError(t, err)
+
+		// Trying to register it again should fail
+		_, err = s.Instantiate(m, importedModuleName)
+		require.EqualError(t, err, "module imported has already been instantiated")
+	})
+
 	t.Run("fail resolve import", func(t *testing.T) {
 		s := newStore()
-		_, err := s.NewHostModule(importedModuleName, map[string]interface{}{"fn": func(wasm.ModuleContext) {}})
+		_, err = s.Instantiate(m, importedModuleName)
 		require.NoError(t, err)
 
 		hm := s.modules[importedModuleName]
@@ -272,7 +290,7 @@ func TestStore_Instantiate_Errors(t *testing.T) {
 		catch := s.engine.(*catchContext)
 		catch.compilationFailIndex = 3
 
-		_, err := s.NewHostModule(importedModuleName, map[string]interface{}{"fn": func(wasm.ModuleContext) {}})
+		_, err = s.Instantiate(m, importedModuleName)
 		require.NoError(t, err)
 
 		hm := s.modules[importedModuleName]
@@ -308,7 +326,7 @@ func TestStore_Instantiate_Errors(t *testing.T) {
 		catch := s.engine.(*catchContext)
 		catch.callFailIndex = 1
 
-		_, err := s.NewHostModule(importedModuleName, map[string]interface{}{"fn": func(wasm.ModuleContext) {}})
+		_, err = s.Instantiate(m, importedModuleName)
 		require.NoError(t, err)
 
 		hm := s.modules[importedModuleName]
@@ -332,10 +350,13 @@ func TestStore_Instantiate_Errors(t *testing.T) {
 }
 
 func TestStore_ExportImportedHostFunction(t *testing.T) {
+	m, err := NewHostModule("", map[string]interface{}{"host_fn": func(wasm.Module) {}})
+	require.NoError(t, err)
+
 	s := newStore()
 
 	// Add the host module
-	_, err := s.NewHostModule("", map[string]interface{}{"host_fn": func(wasm.ModuleContext) {}})
+	_, err = s.Instantiate(m, "")
 	require.NoError(t, err)
 
 	t.Run("Module is the importing module", func(t *testing.T) {
@@ -362,9 +383,15 @@ func TestStore_ExportImportedHostFunction(t *testing.T) {
 func TestFunctionInstance_Call(t *testing.T) {
 	type key string
 	storeCtx := context.WithValue(context.Background(), key("wa"), "zero")
-
 	notStoreCtx := context.WithValue(context.Background(), key("wazer"), "o")
 
+	// Add the host module
+	functionName := "fn"
+	m, err := NewHostModule("host",
+		map[string]interface{}{functionName: func(wasm.Module) {}},
+	)
+	require.NoError(t, err)
+
 	tests := []struct {
 		name     string
 		ctx      context.Context
@@ -390,18 +417,15 @@ func TestFunctionInstance_Call(t *testing.T) {
 			store := NewStore(storeCtx, engine, Features20191205)
 
 			// Add the host module
-			functionName := "fn"
-			hm, err := store.NewHostModule("host",
-				map[string]interface{}{functionName: func(wasm.ModuleContext) {}},
-			)
+			hm, err := store.Instantiate(m, "host")
 			require.NoError(t, err)
 
 			// Make a module to import the function
-			instantiated, err := store.Instantiate(&Module{
+			mod, err := store.Instantiate(&Module{
 				TypeSection: []*FunctionType{{}},
 				ImportSection: []*Import{{
 					Type:     ExternTypeFunc,
-					Module:   hm.name,
+					Module:   hm.Module.Name,
 					Name:     functionName,
 					DescFunc: 0,
 				}},
@@ -411,7 +435,7 @@ func TestFunctionInstance_Call(t *testing.T) {
 			require.NoError(t, err)
 
 			// This fails if the function wasn't invoked, or had an unexpected context.
-			_, err = instantiated.Function(functionName).Call(tc.ctx)
+			_, err = mod.ExportedFunction(functionName).Call(mod.WithContext(tc.ctx))
 			require.NoError(t, err)
 			if tc.expected == nil {
 				require.Nil(t, engine.ctx)
diff --git a/tests/bench/bench_test.go b/tests/bench/bench_test.go
index b9738a93..cab9e787 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"
@@ -16,9 +15,6 @@ import (
 //go:embed testdata/case.wasm
 var caseWasm []byte
 
-// ctx is a default context used to avoid lint warnings even though these tests don't use any context data.
-var ctx = context.Background()
-
 func BenchmarkEngines(b *testing.B) {
 	b.Run("interpreter", func(b *testing.B) {
 		m := instantiateHostFunctionModuleWithEngine(b, wazero.NewRuntimeConfigInterpreter())
@@ -41,13 +37,13 @@ func runAllBenches(b *testing.B, m wasm.Module) {
 }
 
 func runBase64Benches(b *testing.B, m wasm.Module) {
-	base64 := m.Function("base64")
+	base64 := m.ExportedFunction("base64")
 
 	for _, numPerExec := range []int{5, 100, 10000} {
 		numPerExec := uint64(numPerExec)
 		b.ResetTimer()
 		b.Run(fmt.Sprintf("base64_%d_per_exec", numPerExec), func(b *testing.B) {
-			if _, err := base64.Call(ctx, numPerExec); err != nil {
+			if _, err := base64.Call(nil, numPerExec); err != nil {
 				b.Fatal(err)
 			}
 		})
@@ -55,14 +51,14 @@ func runBase64Benches(b *testing.B, m wasm.Module) {
 }
 
 func runFibBenches(b *testing.B, m wasm.Module) {
-	fibonacci := m.Function("fibonacci")
+	fibonacci := m.ExportedFunction("fibonacci")
 
 	for _, num := range []int{5, 10, 20, 30} {
 		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 := fibonacci.Call(ctx, num); err != nil {
+				if _, err := fibonacci.Call(nil, num); err != nil {
 					b.Fatal(err)
 				}
 			}
@@ -71,14 +67,14 @@ func runFibBenches(b *testing.B, m wasm.Module) {
 }
 
 func runStringManipulationBenches(b *testing.B, m wasm.Module) {
-	stringManipulation := m.Function("string_manipulation")
+	stringManipulation := m.ExportedFunction("string_manipulation")
 
 	for _, initialSize := range []int{50, 100, 1000} {
 		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 := stringManipulation.Call(ctx, initialSize); err != nil {
+				if _, err := stringManipulation.Call(nil, initialSize); err != nil {
 					b.Fatal(err)
 				}
 			}
@@ -87,14 +83,14 @@ func runStringManipulationBenches(b *testing.B, m wasm.Module) {
 }
 
 func runReverseArrayBenches(b *testing.B, m wasm.Module) {
-	reverseArray := m.Function("reverse_array")
+	reverseArray := m.ExportedFunction("reverse_array")
 
 	for _, arraySize := range []int{500, 1000, 10000} {
 		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 := reverseArray.Call(ctx, arraySize); err != nil {
+				if _, err := reverseArray.Call(nil, arraySize); err != nil {
 					b.Fatal(err)
 				}
 			}
@@ -103,14 +99,14 @@ func runReverseArrayBenches(b *testing.B, m wasm.Module) {
 }
 
 func runRandomMatMul(b *testing.B, m wasm.Module) {
-	randomMatMul := m.Function("random_mat_mul")
+	randomMatMul := m.ExportedFunction("random_mat_mul")
 
 	for _, matrixSize := range []int{5, 10, 20} {
 		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 := randomMatMul.Call(ctx, matrixSize); err != nil {
+				if _, err := randomMatMul.Call(nil, matrixSize); err != nil {
 					b.Fatal(err)
 				}
 			}
@@ -119,8 +115,8 @@ func runRandomMatMul(b *testing.B, m wasm.Module) {
 }
 
 func instantiateHostFunctionModuleWithEngine(b *testing.B, engine *wazero.RuntimeConfig) wasm.Module {
-	getRandomString := func(ctx wasm.ModuleContext, retBufPtr uint32, retBufSize uint32) {
-		results, err := ctx.Function("allocate_buffer").Call(ctx.Context(), 10)
+	getRandomString := func(ctx wasm.Module, retBufPtr uint32, retBufSize uint32) {
+		results, err := ctx.ExportedFunction("allocate_buffer").Call(ctx, 10)
 		if err != nil {
 			b.Fatal(err)
 		}
@@ -136,14 +132,14 @@ func instantiateHostFunctionModuleWithEngine(b *testing.B, engine *wazero.Runtim
 	r := wazero.NewRuntimeWithConfig(engine)
 
 	env := &wazero.HostModuleConfig{Name: "env", Functions: map[string]interface{}{"get_random_string": getRandomString}}
-	_, err := r.NewHostModule(env)
+	_, err := r.NewHostModuleFromConfig(env)
 	if err != nil {
 		b.Fatal(err)
 	}
 
 	// Note: host_func.go doesn't directly use WASI, but TinyGo needs to be initialized as a WASI Command.
 	// Add WASI to satisfy import tests
-	_, err = r.NewHostModule(wazero.WASISnapshotPreview1())
+	_, err = r.NewHostModuleFromConfig(wazero.WASISnapshotPreview1())
 	if err != nil {
 		b.Fatal(err)
 	}
diff --git a/tests/bench/wasi_test.go b/tests/bench/wasi_test.go
index 150a284c..6e2447fb 100644
--- a/tests/bench/wasi_test.go
+++ b/tests/bench/wasi_test.go
@@ -1,6 +1,7 @@
 package bench
 
 import (
+	"context"
 	"testing"
 
 	"github.com/stretchr/testify/require"
@@ -10,26 +11,30 @@ import (
 	"github.com/tetratelabs/wazero/wasi"
 )
 
-var environGetMem = []byte{
-	0,                // environBuf is after this
-	'a', '=', 'b', 0, // null terminated "a=b",
-	'b', '=', 'c', 'd', 0, // null terminated "b=cd"
-	0,          // environ is after this
-	1, 0, 0, 0, // little endian-encoded offset of "a=b"
-	5, 0, 0, 0, // little endian-encoded offset of "b=cd"
-	0,
-}
+var ctx = wasm.NewModuleContext(context.Background(), nil, &wasm.ModuleInstance{
+	Memory: &wasm.MemoryInstance{
+		Min: 1,
+		Buffer: []byte{
+			0,                // environBuf is after this
+			'a', '=', 'b', 0, // null terminated "a=b",
+			'b', '=', 'c', 'd', 0, // null terminated "b=cd"
+			0,          // environ is after this
+			1, 0, 0, 0, // little endian-encoded offset of "a=b"
+			5, 0, 0, 0, // little endian-encoded offset of "b=cd"
+			0,
+		},
+	},
+})
 
 func Test_EnvironGet(t *testing.T) {
 	envOpt, err := internalwasi.Environ("a=b", "b=cd")
 	require.NoError(t, err)
 
-	var mem = &wasm.MemoryInstance{Buffer: make([]byte, 20), Min: 1}
-	ctx := (&wasm.ModuleContext{}).WithMemory(mem)
+	testCtx := newCtx(make([]byte, 20))
 	environGet := internalwasi.NewAPI(envOpt).EnvironGet
 
-	require.Equal(t, wasi.ErrnoSuccess, environGet(ctx, 11, 1))
-	require.Equal(t, environGetMem, mem.Buffer)
+	require.Equal(t, wasi.ErrnoSuccess, environGet(testCtx, 11, 1))
+	require.Equal(t, testCtx.Memory(), ctx.Memory())
 }
 
 func Benchmark_EnvironGet(b *testing.B) {
@@ -38,10 +43,7 @@ func Benchmark_EnvironGet(b *testing.B) {
 		b.Fatal(err)
 	}
 
-	var mem = &wasm.MemoryInstance{Buffer: environGetMem, Min: 1}
-	ctx := (&wasm.ModuleContext{}).WithMemory(mem)
 	environGet := internalwasi.NewAPI(envOpt).EnvironGet
-
 	b.Run("EnvironGet", func(b *testing.B) {
 		for i := 0; i < b.N; i++ {
 			if environGet(ctx, 0, 4) != wasi.ErrnoSuccess {
@@ -50,3 +52,9 @@ func Benchmark_EnvironGet(b *testing.B) {
 		}
 	})
 }
+
+func newCtx(buf []byte) *wasm.ModuleContext {
+	return wasm.NewModuleContext(context.Background(), nil, &wasm.ModuleInstance{
+		Memory: &wasm.MemoryInstance{Min: 1, Buffer: buf},
+	})
+}
diff --git a/tests/engine/adhoc_test.go b/tests/engine/adhoc_test.go
index f7c30566..7f043af3 100644
--- a/tests/engine/adhoc_test.go
+++ b/tests/engine/adhoc_test.go
@@ -14,9 +14,6 @@ import (
 	publicwasm "github.com/tetratelabs/wazero/wasm"
 )
 
-// ctx is a default context used to avoid lint warnings even though these tests don't use any context data.
-var ctx = context.Background()
-
 func TestJITAdhoc(t *testing.T) {
 	if !wazero.JITSupported {
 		t.Skip()
@@ -75,10 +72,10 @@ func testHugeStack(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig)
 	module, err := r.NewModuleFromSource(hugestackWasm)
 	require.NoError(t, err)
 
-	fn := module.Function("main")
+	fn := module.ExportedFunction("main")
 	require.NotNil(t, fn)
 
-	_, err = fn.Call(ctx)
+	_, err = fn.Call(nil)
 	require.NoError(t, err)
 }
 
@@ -87,10 +84,10 @@ func testFibonacci(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig)
 	module, err := r.NewModuleFromSource(fibWasm)
 	require.NoError(t, err)
 
-	fib := module.Function("fib")
+	fib := module.ExportedFunction("fib")
 	require.NotNil(t, fib)
 
-	results, err := fib.Call(ctx, 20)
+	results, err := fib.Call(nil, 20)
 	require.NoError(t, err)
 	require.Equal(t, uint64(10946), results[0])
 }
@@ -108,39 +105,39 @@ func testFac(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig) {
 	} {
 		name := name
 
-		fac := module.Function("fac")
+		fac := module.ExportedFunction("fac")
 
 		t.Run(name, func(t *testing.T) {
-			results, err := fac.Call(ctx, 25)
+			results, err := fac.Call(nil, 25)
 			require.NoError(t, err)
 			require.Equal(t, uint64(7034535277573963776), results[0])
 		})
 	}
 
 	t.Run("fac-rec - stack overflow", func(t *testing.T) {
-		_, err := module.Function("fac-rec").Call(ctx, 1073741824)
+		_, err := module.ExportedFunction("fac-rec").Call(nil, 1073741824)
 		require.ErrorIs(t, err, wasm.ErrRuntimeCallStackOverflow)
 	})
 }
 
 func testUnreachable(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig) {
-	callUnreachable := func(ctx publicwasm.ModuleContext) {
+	callUnreachable := func(nil publicwasm.Module) {
 		panic("panic in host function")
 	}
 
 	r := wazero.NewRuntimeWithConfig(newRuntimeConfig())
 
 	hostModule := &wazero.HostModuleConfig{Name: "host", Functions: map[string]interface{}{"cause_unreachable": callUnreachable}}
-	_, err := r.NewHostModule(hostModule)
+	_, err := r.NewHostModuleFromConfig(hostModule)
 	require.NoError(t, err)
 
 	module, err := r.NewModuleFromSource(unreachableWasm)
 	require.NoError(t, err)
 
-	_, err = module.Function("main").Call(ctx)
+	_, err = module.ExportedFunction("main").Call(nil)
 	exp := `wasm runtime error: panic in host function
 wasm backtrace:
-	0: host.cause_unreachable
+	0: cause_unreachable
 	1: two
 	2: one
 	3: main`
@@ -152,70 +149,70 @@ func testMemory(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig) {
 	module, err := r.NewModuleFromSource(memoryWasm)
 	require.NoError(t, err)
 
-	size := module.Function("size")
+	size := module.ExportedFunction("size")
 
 	// First, we have zero-length memory instance.
-	results, err := size.Call(ctx)
+	results, err := size.Call(nil)
 	require.NoError(t, err)
 	require.Equal(t, uint64(0), results[0])
 
-	grow := module.Function("grow")
+	grow := module.ExportedFunction("grow")
 
 	// Then grow the memory.
 	newPages := uint64(10)
-	results, err = grow.Call(ctx, newPages)
+	results, err = grow.Call(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.Call(ctx)
+	results, err = size.Call(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.Call(ctx, 0)
+	results, err = grow.Call(nil, 0)
 	require.NoError(t, err)
 	require.Equal(t, newPages, results[0])
 }
 
 func testRecursiveEntry(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig) {
-	hostfunc := func(mod publicwasm.ModuleContext) {
-		_, err := mod.Function("called_by_host_func").Call(ctx)
+	hostfunc := func(mod publicwasm.Module) {
+		_, err := mod.ExportedFunction("called_by_host_func").Call(nil)
 		require.NoError(t, err)
 	}
 
 	r := wazero.NewRuntimeWithConfig(newRuntimeConfig())
 
 	hostModule := &wazero.HostModuleConfig{Name: "env", Functions: map[string]interface{}{"host_func": hostfunc}}
-	_, err := r.NewHostModule(hostModule)
+	_, err := r.NewHostModuleFromConfig(hostModule)
 	require.NoError(t, err)
 
 	module, err := r.NewModuleFromSource(recursiveWasm)
 	require.NoError(t, err)
 
-	_, err = module.Function("main").Call(ctx, 1)
+	_, err = module.ExportedFunction("main").Call(nil, 1)
 	require.NoError(t, err)
 }
 
 // testImportedAndExportedFunc fails if the engine cannot call an "imported-and-then-exported-back" function
-// Notably, this uses memory, which ensures wasm.ModuleContext is valid in both interpreter and JIT engines.
+// Notably, this uses memory, which ensures wasm.Module is valid in both interpreter and JIT engines.
 func testImportedAndExportedFunc(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig) {
 	var memory *wasm.MemoryInstance
-	storeInt := func(ctx publicwasm.ModuleContext, offset uint32, val uint64) uint32 {
-		if !ctx.Memory().WriteUint64Le(offset, val) {
+	storeInt := func(nil publicwasm.Module, offset uint32, val uint64) uint32 {
+		if !nil.Memory().WriteUint64Le(offset, val) {
 			return 1
 		}
 		// sneak a reference to the memory, so we can check it later
-		memory = ctx.Memory().(*wasm.MemoryInstance)
+		memory = nil.Memory().(*wasm.MemoryInstance)
 		return 0
 	}
 
 	r := wazero.NewRuntimeWithConfig(newRuntimeConfig())
 
 	hostModule := &wazero.HostModuleConfig{Name: "", Functions: map[string]interface{}{"store_int": storeInt}}
-	_, err := r.NewHostModule(hostModule)
+	_, err := r.NewHostModuleFromConfig(hostModule)
 	require.NoError(t, err)
 
 	module, err := r.NewModuleFromSource([]byte(`(module $test
@@ -229,7 +226,7 @@ func testImportedAndExportedFunc(t *testing.T, newRuntimeConfig func() *wazero.R
 	require.NoError(t, err)
 
 	// Call store_int and ensure it didn't return an error code.
-	results, err := module.Function("store_int").Call(ctx, 1, math.MaxUint64)
+	results, err := module.ExportedFunction("store_int").Call(nil, 1, math.MaxUint64)
 	require.NoError(t, err)
 	require.Equal(t, uint64(0), results[0])
 
@@ -237,9 +234,15 @@ func testImportedAndExportedFunc(t *testing.T, newRuntimeConfig func() *wazero.R
 	require.Equal(t, []byte{0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0}, memory.Buffer[0:10])
 }
 
+func TestHostFunctions(t *testing.T) {
+	testHostFunctions(t, func() *wazero.RuntimeConfig {
+		return wazero.NewRuntimeConfig()
+	})
+}
+
 //  testHostFunctions ensures arg0 is optionally a context, and fails if a float parameter corrupts a host function value
 func testHostFunctions(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig) {
-	ctx := context.Background()
+	var m publicwasm.Module
 
 	floatFuncs := map[string]interface{}{
 		"identity_f32": func(value float32) float32 {
@@ -250,37 +253,37 @@ func testHostFunctions(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConf
 		}}
 
 	floatFuncsGoContext := map[string]interface{}{
-		"identity_f32": func(funcCtx context.Context, value float32) float32 {
-			require.Equal(t, ctx, funcCtx)
+		"identity_f32": func(ctx context.Context, value float32) float32 {
+			require.Equal(t, context.Background(), ctx)
 			return value
 		},
-		"identity_f64": func(funcCtx context.Context, value float64) float64 {
-			require.Equal(t, ctx, funcCtx)
+		"identity_f64": func(ctx context.Context, value float64) float64 {
+			require.Equal(t, context.Background(), ctx)
 			return value
 		}}
 
-	floatFuncsModuleContext := map[string]interface{}{
-		"identity_f32": func(funcCtx publicwasm.ModuleContext, value float32) float32 {
-			require.Equal(t, ctx, funcCtx.Context())
+	floatFuncsModule := map[string]interface{}{
+		"identity_f32": func(ctx publicwasm.Module, value float32) float32 {
+			require.Equal(t, m, ctx)
 			return value
 		},
-		"identity_f64": func(funcCtx publicwasm.ModuleContext, value float64) float64 {
-			require.Equal(t, ctx, funcCtx.Context())
+		"identity_f64": func(ctx publicwasm.Module, value float64) float64 {
+			require.Equal(t, m, ctx)
 			return value
 		}}
 
 	for k, v := range map[string]map[string]interface{}{
-		"":                      floatFuncs,
-		" - context.Context":    floatFuncsGoContext,
-		" - wasm.ModuleContext": floatFuncsModuleContext,
+		"":                   floatFuncs,
+		" - context.Context": floatFuncsGoContext,
+		" - wasm.Module":     floatFuncsModule,
 	} {
 		r := wazero.NewRuntimeWithConfig(newRuntimeConfig())
 
 		hostModule := &wazero.HostModuleConfig{Name: "host", Functions: v}
-		_, err := r.NewHostModule(hostModule)
+		_, err := r.NewHostModuleFromConfig(hostModule)
 		require.NoError(t, err)
 
-		m, err := r.NewModuleFromSource([]byte(`(module $test
+		m, err = r.NewModuleFromSource([]byte(`(module $test
 	;; these imports return the input param
 	(import "host" "identity_f32" (func $test.identity_f32 (param f32) (result f32)))
 	(import "host" "identity_f64" (func $test.identity_f64 (param f64) (result f64)))
@@ -303,7 +306,7 @@ func testHostFunctions(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConf
 			name := "call->test.identity_f32"
 			input := float32(math.MaxFloat32)
 
-			results, err := m.Function(name).Call(ctx, publicwasm.EncodeF32(input)) // float bits are a uint32 value, call requires uint64
+			results, err := m.ExportedFunction(name).Call(nil, publicwasm.EncodeF32(input)) // float bits are a uint32 value, call requires uint64
 			require.NoError(t, err)
 			require.Equal(t, input, publicwasm.DecodeF32(results[0]))
 		})
@@ -312,7 +315,7 @@ func testHostFunctions(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConf
 			name := "call->test.identity_f64"
 			input := math.MaxFloat64
 
-			results, err := m.Function(name).Call(ctx, publicwasm.EncodeF64(input))
+			results, err := m.ExportedFunction(name).Call(nil, publicwasm.EncodeF64(input))
 			require.NoError(t, err)
 			require.Equal(t, input, publicwasm.DecodeF64(results[0]))
 		})
diff --git a/tests/post1_0/post1_0_test.go b/tests/post1_0/post1_0_test.go
index 29c9a8da..040d6be5 100644
--- a/tests/post1_0/post1_0_test.go
+++ b/tests/post1_0/post1_0_test.go
@@ -1,7 +1,6 @@
 package post1_0
 
 import (
-	"context"
 	_ "embed"
 	"fmt"
 	"testing"
@@ -92,10 +91,10 @@ func testSignExtensionOps(t *testing.T, newRuntimeConfig func() *wazero.RuntimeC
 			} {
 				tc := tc
 				t.Run(fmt.Sprintf("0x%x", tc.in), func(t *testing.T) {
-					fn := module.Function(tc.funcname)
+					fn := module.ExportedFunction(tc.funcname)
 					require.NotNil(t, fn)
 
-					actual, err := fn.Call(context.Background(), uint64(uint32(tc.in)))
+					actual, err := fn.Call(nil, uint64(uint32(tc.in)))
 					require.NoError(t, err)
 					require.Equal(t, tc.expected, int32(actual[0]))
 				})
@@ -140,10 +139,10 @@ func testSignExtensionOps(t *testing.T, newRuntimeConfig func() *wazero.RuntimeC
 			} {
 				tc := tc
 				t.Run(fmt.Sprintf("0x%x", tc.in), func(t *testing.T) {
-					fn := module.Function(tc.funcname)
+					fn := module.ExportedFunction(tc.funcname)
 					require.NotNil(t, fn)
 
-					actual, err := fn.Call(context.Background(), uint64(tc.in))
+					actual, err := fn.Call(nil, uint64(tc.in))
 					require.NoError(t, err)
 					require.Equal(t, tc.expected, int64(actual[0]))
 				})
diff --git a/tests/spectest/spec_test.go b/tests/spectest/spec_test.go
index 0d814bdc..8253384a 100644
--- a/tests/spectest/spec_test.go
+++ b/tests/spectest/spec_test.go
@@ -363,7 +363,7 @@ func runTest(t *testing.T, newEngine func() wasm.Engine) {
 							}
 							module := store.Module(moduleName)
 							require.NotNil(t, module)
-							global := module.Global(c.Action.Field)
+							global := module.ExportedGlobal(c.Action.Field)
 							require.NotNil(t, global)
 							var expType wasm.ValueType
 							switch c.Exps[0].ValType {
@@ -504,7 +504,7 @@ func requireValueEq(t *testing.T, actual, expected uint64, valType wasm.ValueTyp
 // callFunction is inlined here as the spectest needs to validate the signature was correct
 // TODO: This is likely already covered with unit tests!
 func callFunction(s *wasm.Store, moduleName, funcName string, params ...uint64) ([]uint64, []wasm.ValueType, error) {
-	fn := s.Module(moduleName).Function(funcName)
-	results, err := fn.Call(context.Background(), params...)
+	fn := s.Module(moduleName).ExportedFunction(funcName)
+	results, err := fn.Call(nil, params...)
 	return results, fn.ResultTypes(), err
 }
diff --git a/vs/bench_fac_iter_test.go b/vs/bench_fac_iter_test.go
index ac35c0fc..f4565f91 100644
--- a/vs/bench_fac_iter_test.go
+++ b/vs/bench_fac_iter_test.go
@@ -29,7 +29,6 @@ var facWasm []byte
 
 // TestFacIter ensures that the code in BenchmarkFacIter works as expected.
 func TestFacIter(t *testing.T) {
-	ctx := context.Background()
 	const in = 30
 	expValue := uint64(0x865df5dd54000000)
 
@@ -38,7 +37,7 @@ func TestFacIter(t *testing.T) {
 		require.NoError(t, err)
 
 		for i := 0; i < 10000; i++ {
-			res, err := fn.Call(ctx, in)
+			res, err := fn.Call(nil, in)
 			require.NoError(t, err)
 			require.Equal(t, expValue, res[0])
 		}
@@ -49,7 +48,7 @@ func TestFacIter(t *testing.T) {
 		require.NoError(t, err)
 
 		for i := 0; i < 10000; i++ {
-			res, err := fn.Call(ctx, in)
+			res, err := fn.Call(nil, in)
 			require.NoError(t, err)
 			require.Equal(t, expValue, res[0])
 		}
@@ -194,7 +193,7 @@ func interpreterFacIterInvoke(b *testing.B) {
 	}
 	b.ResetTimer()
 	for i := 0; i < b.N; i++ {
-		if _, err = fn.Call(ctx, facIterArgumentU64); err != nil {
+		if _, err = fn.Call(nil, facIterArgumentU64); err != nil {
 			b.Fatal(err)
 		}
 	}
@@ -207,7 +206,7 @@ func jitFacIterInvoke(b *testing.B) {
 	}
 	b.ResetTimer()
 	for i := 0; i < b.N; i++ {
-		if _, err = fn.Call(ctx, facIterArgumentU64); err != nil {
+		if _, err = fn.Call(nil, facIterArgumentU64); err != nil {
 			b.Fatal(err)
 		}
 	}
@@ -249,7 +248,7 @@ func newWazeroFacIterBench(engine *wazero.RuntimeConfig) (wasm.Function, error)
 		return nil, err
 	}
 
-	return m.Function("fac-iter"), nil
+	return m.ExportedFunction("fac-iter"), nil
 }
 
 // newWasmerForFacIterBench returns the store and instance that scope the factorial function.
diff --git a/vs/codec_test.go b/vs/codec_test.go
index c7df41f5..ac6e5d33 100644
--- a/vs/codec_test.go
+++ b/vs/codec_test.go
@@ -5,7 +5,6 @@
 package vs
 
 import (
-	"context"
 	_ "embed"
 	"testing"
 
@@ -109,7 +108,7 @@ func TestExampleUpToDate(t *testing.T) {
 		r := wazero.NewRuntimeWithConfig(wazero.NewRuntimeConfig().WithFeatureSignExtensionOps(true))
 
 		// Add WASI to satisfy import tests
-		_, err := r.NewHostModule(wazero.WASISnapshotPreview1())
+		_, err := r.NewHostModuleFromConfig(wazero.WASISnapshotPreview1())
 		require.NoError(t, err)
 
 		// Decode and instantiate the module
@@ -117,7 +116,7 @@ func TestExampleUpToDate(t *testing.T) {
 		require.NoError(t, err)
 
 		// Call the add function as a smoke test
-		results, err := module.Function("AddInt").Call(context.Background(), 1, 2)
+		results, err := module.ExportedFunction("AddInt").Call(nil, 1, 2)
 		require.NoError(t, err)
 		require.Equal(t, uint64(3), results[0])
 	})
diff --git a/wasi_test.go b/wasi_test.go
index ff264022..e5acbe9c 100644
--- a/wasi_test.go
+++ b/wasi_test.go
@@ -16,15 +16,15 @@ func TestStartWASICommand_UsesStoreContext(t *testing.T) {
 
 	// Define a function that will be re-exported as the WASI function: _start
 	var calledStart bool
-	start := func(ctx wasm.ModuleContext) {
+	start := func(ctx wasm.Module) {
 		calledStart = true
 		require.Equal(t, config.ctx, ctx.Context())
 	}
 
-	_, err := r.NewHostModule(&HostModuleConfig{Functions: map[string]interface{}{"start": start}})
+	_, err := r.NewHostModuleFromConfig(&HostModuleConfig{Functions: map[string]interface{}{"start": start}})
 	require.NoError(t, err)
 
-	_, err = r.NewHostModule(WASISnapshotPreview1())
+	_, err = r.NewHostModuleFromConfig(WASISnapshotPreview1())
 	require.NoError(t, err)
 
 	decoded, err := r.DecodeModule([]byte(`(module $wasi_test.go
diff --git a/wasm_test.go b/wasm_test.go
index be0b5401..7d2e7213 100644
--- a/wasm_test.go
+++ b/wasm_test.go
@@ -106,12 +106,12 @@ func TestDecodedModule_WithName(t *testing.T) {
 	m1, err := r.NewModule(base.WithName("1"))
 	require.NoError(t, err)
 	require.Nil(t, internal.Module("0"))
-	require.Equal(t, internal.Module("1"), m1.(*internalwasm.PublicModule))
+	require.Equal(t, internal.Module("1"), m1)
 
 	m2, err := r.NewModule(base.WithName("2"))
 	require.NoError(t, err)
 	require.Nil(t, internal.Module("0"))
-	require.Equal(t, internal.Module("2"), m2.(*internalwasm.PublicModule))
+	require.Equal(t, internal.Module("2"), m2)
 }
 
 // TestModule_Memory only covers a couple cases to avoid duplication of internal/wasm/runtime_test.go
@@ -145,7 +145,7 @@ func TestModule_Memory(t *testing.T) {
 			module, err := r.NewModule(decoded)
 			require.NoError(t, err)
 
-			mem := module.Memory("memory")
+			mem := module.ExportedMemory("memory")
 			if tc.expected {
 				require.Equal(t, tc.expectedLen, mem.Size())
 			} else {
@@ -219,7 +219,7 @@ func TestModule_Global(t *testing.T) {
 			module, err := r.NewModule(&DecodedModule{module: tc.module})
 			require.NoError(t, err)
 
-			global := module.Global("global")
+			global := module.ExportedGlobal("global")
 			if !tc.expected {
 				require.Nil(t, global)
 				return
@@ -269,7 +269,7 @@ func TestFunction_Context(t *testing.T) {
 			// Define a host function so that we can catch the context propagated from a module function call
 			functionName := "fn"
 			expectedResult := uint64(math.MaxUint64)
-			hostFn := func(ctx wasm.ModuleContext) uint64 {
+			hostFn := func(ctx wasm.Module) uint64 {
 				require.Equal(t, tc.expected, ctx.Context())
 				return expectedResult
 			}
@@ -283,7 +283,7 @@ func TestFunction_Context(t *testing.T) {
 			require.NoError(t, err)
 
 			// This fails if the function wasn't invoked, or had an unexpected context.
-			results, err := module.Function(functionName).Call(tc.ctx)
+			results, err := module.ExportedFunction(functionName).Call(module.WithContext(tc.ctx))
 			require.NoError(t, err)
 			require.Equal(t, expectedResult, results[0])
 		})
@@ -298,12 +298,12 @@ func TestRuntime_NewModule_UsesStoreContext(t *testing.T) {
 
 	// Define a function that will be set as the start function
 	var calledStart bool
-	start := func(ctx wasm.ModuleContext) {
+	start := func(ctx wasm.Module) {
 		calledStart = true
 		require.Equal(t, runtimeCtx, ctx.Context())
 	}
 
-	_, err := r.NewHostModule(&HostModuleConfig{Functions: map[string]interface{}{"start": start}})
+	_, err := r.NewHostModuleFromConfig(&HostModuleConfig{Functions: map[string]interface{}{"start": start}})
 	require.NoError(t, err)
 
 	decoded, err := r.DecodeModule([]byte(`(module $runtime_test.go
@@ -319,8 +319,8 @@ func TestRuntime_NewModule_UsesStoreContext(t *testing.T) {
 }
 
 // requireImportAndExportFunction re-module a host function because only host functions can see the propagated context.
-func requireImportAndExportFunction(t *testing.T, r Runtime, hostFn func(ctx wasm.ModuleContext) uint64, functionName string) []byte {
-	_, err := r.NewHostModule(&HostModuleConfig{
+func requireImportAndExportFunction(t *testing.T, r Runtime, hostFn func(ctx wasm.Module) uint64, functionName string) []byte {
+	_, err := r.NewHostModuleFromConfig(&HostModuleConfig{
 		Name: "host", Functions: map[string]interface{}{functionName: hostFn},
 	})
 	require.NoError(t, err)