Repository: tetratelabs/wazero
Pull request #1241 (2023-03-15 04:10:26)

Removes usage of host functions with Wasm optocdes
Since #1213, engines no longer can support the Wasm-body-implemented host functions,
which has been only used to stub out the unimplemented functions. 
Also, this reduces the complexity of the future development of engines.


Patch:
diff --git a/builder.go b/builder.go
index 6093fdd1..aa9a744e 100644
--- a/builder.go
+++ b/builder.go
@@ -237,7 +237,7 @@ func (h *hostFunctionBuilder) WithGoFunction(fn api.GoFunction, params, results
 	h.fn = &wasm.HostFunc{
 		ParamTypes:  params,
 		ResultTypes: results,
-		Code:        &wasm.Code{IsHostFunction: true, GoFunc: fn},
+		Code:        &wasm.Code{GoFunc: fn},
 	}
 	return h
 }
@@ -247,7 +247,7 @@ func (h *hostFunctionBuilder) WithGoModuleFunction(fn api.GoModuleFunction, para
 	h.fn = &wasm.HostFunc{
 		ParamTypes:  params,
 		ResultTypes: results,
-		Code:        &wasm.Code{IsHostFunction: true, GoFunc: fn},
+		Code:        &wasm.Code{GoFunc: fn},
 	}
 	return h
 }
diff --git a/cmd/wazero/wazero.go b/cmd/wazero/wazero.go
index 03577f5b..4291da65 100644
--- a/cmd/wazero/wazero.go
+++ b/cmd/wazero/wazero.go
@@ -226,13 +226,6 @@ func doRun(args []string, stdOut io.Writer, stdErr logging.Writer, exit func(cod
 		exit(1)
 	}
 
-	if workdirInherit {
-		if ctx, err = gojs.WithOSWorkDir(ctx); err != nil {
-			fmt.Fprintf(stdErr, "cannot read current working directory: %v\n", err)
-			exit(1)
-		}
-	}
-
 	rt := wazero.NewRuntimeWithConfig(ctx, rtc)
 	defer rt.Close(ctx)
 
@@ -274,7 +267,13 @@ func doRun(args []string, stdOut io.Writer, stdErr logging.Writer, exit func(cod
 		}
 	case modeGo:
 		gojs.MustInstantiate(ctx, rt)
-		err = gojs.Run(ctx, rt, code, conf)
+
+		config := gojs.NewConfig(conf)
+		if workdirInherit {
+			config = config.WithOSWorkdir()
+		}
+
+		err = gojs.Run(ctx, rt, code, config)
 	case modeDefault:
 		_, err = rt.InstantiateModule(ctx, code, conf)
 	}
diff --git a/experimental/gojs/example/stars.go b/experimental/gojs/example/stars.go
index 0acd4974..dca7a6ff 100644
--- a/experimental/gojs/example/stars.go
+++ b/experimental/gojs/example/stars.go
@@ -44,7 +44,7 @@ func main() {
 	log.Printf("gojs.MustInstantiate took %dms", goJsInstantiate)
 
 	// Combine the above into our baseline config, overriding defaults.
-	config := wazero.NewModuleConfig().
+	moduleConfig := wazero.NewModuleConfig().
 		// By default, I/O streams are discarded, so you won't see output.
 		WithStdout(os.Stdout).WithStderr(os.Stderr)
 
@@ -63,7 +63,7 @@ func main() {
 	log.Printf("CompileModule took %dms with %dKB cache", compilationTime, dirSize(compilationCacheDir)/1024)
 
 	// Instead of making real HTTP calls, return fake data.
-	ctx = gojs.WithRoundTripper(ctx, &fakeGitHub{})
+	config := gojs.NewConfig(moduleConfig).WithRoundTripper(&fakeGitHub{})
 
 	// Execute the "run" function, which corresponds to "main" in stars/main.go.
 	start = time.Now()
diff --git a/experimental/gojs/gojs.go b/experimental/gojs/gojs.go
index 358a78a5..c5353dcf 100644
--- a/experimental/gojs/gojs.go
+++ b/experimental/gojs/gojs.go
@@ -16,14 +16,12 @@ package gojs
 import (
 	"context"
 	"net/http"
-	"os"
-	"path/filepath"
 
 	"github.com/tetratelabs/wazero"
 	"github.com/tetratelabs/wazero/api"
 	. "github.com/tetratelabs/wazero/internal/gojs"
+	internalconfig "github.com/tetratelabs/wazero/internal/gojs/config"
 	. "github.com/tetratelabs/wazero/internal/gojs/run"
-	"github.com/tetratelabs/wazero/internal/platform"
 	"github.com/tetratelabs/wazero/internal/wasm"
 )
 
@@ -97,42 +95,60 @@ func (e *functionExporter) ExportFunctions(builder wazero.HostModuleBuilder) {
 	hfExporter.ExportHostFunc(Debug)
 }
 
-// WithRoundTripper sets the http.RoundTripper used to Run Wasm.
-//
-// For example, if the code compiled via `GOARCH=wasm GOOS=js` uses
-// http.RoundTripper, you can avoid failures by assigning an implementation
-// like so:
-//
-//	ctx = gojs.WithRoundTripper(ctx, http.DefaultTransport)
-//	err = gojs.Run(ctx, r, compiled, config)
-func WithRoundTripper(ctx context.Context, rt http.RoundTripper) context.Context {
-	return context.WithValue(ctx, RoundTripperKey{}, rt)
+// Config extends wazero.ModuleConfig with GOOS=js specific extensions.
+// Use NewConfig to create an instance.
+type Config interface {
+	// WithOSWorkdir sets the initial working directory used to Run Wasm to
+	// the value of os.Getwd instead of the default of root "/".
+	//
+	// Here's an example that overrides this to the current directory:
+	//
+	//	err = gojs.Run(ctx, r, compiled, gojs.NewConfig(moduleConfig).
+	//			WithOSWorkdir())
+	//
+	// Note: To use this feature requires mounting the real root directory via
+	// wazero.FSConfig `WithDirMount`. On windows, this root must be the same drive
+	// as the value of os.Getwd. For example, it would be an error to mount `C:\`
+	// as the guest path "", while the current directory is inside `D:\`.
+	WithOSWorkdir() Config
+
+	// WithRoundTripper sets the http.RoundTripper used to Run Wasm.
+	//
+	// For example, if the code compiled via `GOARCH=wasm GOOS=js` uses
+	// http.RoundTripper, you can avoid failures by assigning an implementation
+	// like so:
+	//
+	//	err = gojs.Run(ctx, r, compiled, gojs.NewConfig(moduleConfig).
+	//			WithRoundTripper(ctx, http.DefaultTransport))
+	WithRoundTripper(http.RoundTripper) Config
 }
 
-// WithOSWorkDir sets the initial working directory used to Run Wasm to
-// the value of os.Getwd instead of the default of root "/".
-//
-// Here's an example that overrides this to "/usr/local/go/src/os".
-//
-//	ctx = gojs.WithOSWorkDir(ctx)
-//	err = gojs.Run(ctx, r, compiled, config)
-//
-// Note: To use this feature requires mounting the real root directory via
-// wazero.FSConfig `WithDirMount`. On windows, this root must be the same drive
-// as the value of os.Getwd. For example, it would be an error to mount `C:\`
-// as the guest path "", while the current directory is inside `D:\`.
-func WithOSWorkDir(ctx context.Context) (context.Context, error) {
-	workdir, err := os.Getwd()
-	if err != nil {
-		return nil, err
-	}
+// NewConfig returns a Config that can be used for configuring module instantiation.
+func NewConfig(moduleConfig wazero.ModuleConfig) Config {
+	return &cfg{moduleConfig: moduleConfig, internal: internalconfig.NewConfig()}
+}
+
+type cfg struct {
+	moduleConfig wazero.ModuleConfig
+	internal     *internalconfig.Config
+}
 
-	// Ensure if used on windows, the input path is translated to a POSIX one.
-	workdir = platform.ToPosixPath(workdir)
-	// Strip the volume of the path, for example C:\
-	workdir = workdir[len(filepath.VolumeName(workdir)):]
+func (c *cfg) clone() *cfg {
+	return &cfg{moduleConfig: c.moduleConfig, internal: c.internal.Clone()}
+}
+
+// WithOSWorkdir implements Config.WithOSWorkdir
+func (c *cfg) WithOSWorkdir() Config {
+	ret := c.clone()
+	ret.internal.OsWorkdir = true
+	return ret
+}
 
-	return context.WithValue(ctx, WorkdirKey{}, workdir), nil
+// WithRoundTripper implements Config.WithRoundTripper
+func (c *cfg) WithRoundTripper(rt http.RoundTripper) Config {
+	ret := c.clone()
+	ret.internal.Rt = rt
+	return ret
 }
 
 // Run instantiates a new module and calls "run" with the given config.
@@ -142,20 +158,21 @@ func WithOSWorkDir(ctx context.Context) (context.Context, error) {
 //   - ctx: context to use when instantiating the module and calling "run".
 //   - r: runtime to instantiate both the host and guest (compiled) module in.
 //   - compiled: guest binary compiled with `GOARCH=wasm GOOS=js`
-//   - config: the configuration such as args, env or filesystem to use.
+//   - config: the Config to use including wazero.ModuleConfig or extensions of
+//     it.
 //
 // # Example
 //
 // After compiling your Wasm binary with wazero.Runtime's `CompileModule`, run
 // it like below:
 //
-//	// Use compilation cache to reduce performance penalty of multiple runs.
-//	ctx = experimental.WithCompilationCacheDirName(ctx, ".build")
+//	// Instantiate host functions needed by gojs
+//	gojs.MustInstantiate(ctx, r)
 //
-//	// Instantiate the host functions used for each call.
-//	gojs.MustInstantiate(r)
+//	// Assign any configuration relevant for your compiled wasm.
+//	config := gojs.NewConfig(wazero.NewConfig())
 //
-//	// Execute the "run" function, which corresponds to "main" in stars/main.go.
+//	// Run your wasm, notably handing any ExitError
 //	err = gojs.Run(ctx, r, compiled, config)
 //	if exitErr, ok := err.(*sys.ExitError); ok && exitErr.ExitCode() != 0 {
 //		log.Panicln(err)
@@ -165,12 +182,12 @@ func WithOSWorkDir(ctx context.Context) (context.Context, error) {
 //
 // # Notes
 //
-//   - Use wazero.RuntimeConfig `WithWasmCore2` to avoid needing to pick >1.0
-//     features set by `GOWASM` or used internally by Run.
-//   - Wasm generated by `GOARCH=wasm GOOS=js` is very slow to compile.
-//     Use experimental.WithCompilationCacheDirName to improve performance.
+//   - Wasm generated by `GOARCH=wasm GOOS=js` is very slow to compile: Use
+//     wazero.RuntimeConfig with wazero.CompilationCache when re-running the
+//     same binary.
 //   - The guest module is closed after being run.
-func Run(ctx context.Context, r wazero.Runtime, compiled wazero.CompiledModule, config wazero.ModuleConfig) error {
-	_, err := RunAndReturnState(ctx, r, compiled, config)
+func Run(ctx context.Context, r wazero.Runtime, compiled wazero.CompiledModule, moduleConfig Config) error {
+	c := moduleConfig.(*cfg)
+	_, err := RunAndReturnState(ctx, r, compiled, c.moduleConfig, c.internal)
 	return err
 }
diff --git a/imports/assemblyscript/assemblyscript.go b/imports/assemblyscript/assemblyscript.go
index 2b7cd6c9..5bf2ee09 100644
--- a/imports/assemblyscript/assemblyscript.go
+++ b/imports/assemblyscript/assemblyscript.go
@@ -142,10 +142,7 @@ var abortMessageEnabled = &wasm.HostFunc{
 	Name:        "~lib/builtins/abort",
 	ParamTypes:  []api.ValueType{i32, i32, i32, i32},
 	ParamNames:  []string{"message", "fileName", "lineNumber", "columnNumber"},
-	Code: &wasm.Code{
-		IsHostFunction: true,
-		GoFunc:         api.GoModuleFunc(abortWithMessage),
-	},
+	Code:        &wasm.Code{GoFunc: api.GoModuleFunc(abortWithMessage)},
 }
 
 var abortMessageDisabled = abortMessageEnabled.WithGoModuleFunc(abort)
@@ -184,7 +181,7 @@ func abort(ctx context.Context, mod api.Module, _ []uint64) {
 }
 
 // traceDisabled ignores the input.
-var traceDisabled = traceStdout.WithWasm([]byte{wasm.OpcodeEnd})
+var traceDisabled = traceStdout.WithGoModuleFunc(func(context.Context, api.Module, []uint64) {})
 
 // traceStdout implements trace to the configured Stdout.
 var traceStdout = &wasm.HostFunc{
@@ -193,7 +190,6 @@ var traceStdout = &wasm.HostFunc{
 	ParamTypes:  []api.ValueType{i32, i32, f64, f64, f64, f64, f64},
 	ParamNames:  []string{"message", "nArgs", "arg0", "arg1", "arg2", "arg3", "arg4"},
 	Code: &wasm.Code{
-		IsHostFunction: true,
 		GoFunc: api.GoModuleFunc(func(_ context.Context, mod api.Module, stack []uint64) {
 			fsc := mod.(*wasm.CallContext).Sys.FS()
 			traceTo(mod, stack, internalsys.WriterForFile(fsc, internalsys.FdStdout))
@@ -279,7 +275,6 @@ var seed = &wasm.HostFunc{
 	ResultTypes: []api.ValueType{f64},
 	ResultNames: []string{"rand"},
 	Code: &wasm.Code{
-		IsHostFunction: true,
 		GoFunc: api.GoModuleFunc(func(ctx context.Context, mod api.Module, stack []uint64) {
 			r := mod.(*wasm.CallContext).Sys.RandSource()
 			buf := make([]byte, 8)
diff --git a/imports/emscripten/emscripten.go b/imports/emscripten/emscripten.go
index e9e7573a..49c42627 100644
--- a/imports/emscripten/emscripten.go
+++ b/imports/emscripten/emscripten.go
@@ -98,7 +98,7 @@ var notifyMemoryGrowth = &wasm.HostFunc{
 	Name:        functionNotifyMemoryGrowth,
 	ParamTypes:  []wasm.ValueType{wasm.ValueTypeI32},
 	ParamNames:  []string{"memory_index"},
-	Code:        &wasm.Code{IsHostFunction: true, Body: []byte{wasm.OpcodeEnd}},
+	Code:        &wasm.Code{GoFunc: api.GoModuleFunc(func(context.Context, api.Module, []uint64) {})},
 }
 
 // All `invoke_` functions have an initial "index" parameter of
@@ -139,10 +139,7 @@ var invokeI = &wasm.HostFunc{
 	ParamTypes:  []api.ValueType{i32},
 	ParamNames:  []string{"index"},
 	ResultTypes: []api.ValueType{i32},
-	Code: &wasm.Code{
-		IsHostFunction: true,
-		GoFunc:         api.GoModuleFunc(invokeIFn),
-	},
+	Code:        &wasm.Code{GoFunc: api.GoModuleFunc(invokeIFn)},
 }
 
 func invokeIFn(ctx context.Context, mod api.Module, stack []uint64) {
@@ -159,10 +156,7 @@ var invokeIi = &wasm.HostFunc{
 	ParamTypes:  []api.ValueType{i32, i32},
 	ParamNames:  []string{"index", "a1"},
 	ResultTypes: []api.ValueType{i32},
-	Code: &wasm.Code{
-		IsHostFunction: true,
-		GoFunc:         api.GoModuleFunc(invokeIiFn),
-	},
+	Code:        &wasm.Code{GoFunc: api.GoModuleFunc(invokeIiFn)},
 }
 
 func invokeIiFn(ctx context.Context, mod api.Module, stack []uint64) {
@@ -179,10 +173,7 @@ var invokeIii = &wasm.HostFunc{
 	ParamTypes:  []api.ValueType{i32, i32, i32},
 	ParamNames:  []string{"index", "a1", "a2"},
 	ResultTypes: []api.ValueType{i32},
-	Code: &wasm.Code{
-		IsHostFunction: true,
-		GoFunc:         api.GoModuleFunc(invokeIiiFn),
-	},
+	Code:        &wasm.Code{GoFunc: api.GoModuleFunc(invokeIiiFn)},
 }
 
 func invokeIiiFn(ctx context.Context, mod api.Module, stack []uint64) {
@@ -199,10 +190,7 @@ var invokeIiii = &wasm.HostFunc{
 	ParamTypes:  []api.ValueType{i32, i32, i32, i32},
 	ParamNames:  []string{"index", "a1", "a2", "a3"},
 	ResultTypes: []api.ValueType{i32},
-	Code: &wasm.Code{
-		IsHostFunction: true,
-		GoFunc:         api.GoModuleFunc(invokeIiiiFn),
-	},
+	Code:        &wasm.Code{GoFunc: api.GoModuleFunc(invokeIiiiFn)},
 }
 
 func invokeIiiiFn(ctx context.Context, mod api.Module, stack []uint64) {
@@ -219,10 +207,7 @@ var invokeIiiii = &wasm.HostFunc{
 	ParamTypes:  []api.ValueType{i32, i32, i32, i32, i32},
 	ParamNames:  []string{"index", "a1", "a2", "a3", "a4"},
 	ResultTypes: []api.ValueType{i32},
-	Code: &wasm.Code{
-		IsHostFunction: true,
-		GoFunc:         api.GoModuleFunc(invokeIiiiiFn),
-	},
+	Code:        &wasm.Code{GoFunc: api.GoModuleFunc(invokeIiiiiFn)},
 }
 
 func invokeIiiiiFn(ctx context.Context, mod api.Module, stack []uint64) {
@@ -239,10 +224,7 @@ var invokeV = &wasm.HostFunc{
 	ParamTypes:  []api.ValueType{i32},
 	ParamNames:  []string{"index"},
 	ResultTypes: []api.ValueType{},
-	Code: &wasm.Code{
-		IsHostFunction: true,
-		GoFunc:         api.GoModuleFunc(invokeVFn),
-	},
+	Code:        &wasm.Code{GoFunc: api.GoModuleFunc(invokeVFn)},
 }
 
 func invokeVFn(ctx context.Context, mod api.Module, stack []uint64) {
@@ -258,10 +240,7 @@ var invokeVi = &wasm.HostFunc{
 	ParamTypes:  []api.ValueType{i32, i32},
 	ParamNames:  []string{"index", "a1"},
 	ResultTypes: []api.ValueType{},
-	Code: &wasm.Code{
-		IsHostFunction: true,
-		GoFunc:         api.GoModuleFunc(invokeViFn),
-	},
+	Code:        &wasm.Code{GoFunc: api.GoModuleFunc(invokeViFn)},
 }
 
 func invokeViFn(ctx context.Context, mod api.Module, stack []uint64) {
@@ -277,10 +256,7 @@ var invokeVii = &wasm.HostFunc{
 	ParamTypes:  []api.ValueType{i32, i32, i32},
 	ParamNames:  []string{"index", "a1", "a2"},
 	ResultTypes: []api.ValueType{},
-	Code: &wasm.Code{
-		IsHostFunction: true,
-		GoFunc:         api.GoModuleFunc(invokeViiFn),
-	},
+	Code:        &wasm.Code{GoFunc: api.GoModuleFunc(invokeViiFn)},
 }
 
 func invokeViiFn(ctx context.Context, mod api.Module, stack []uint64) {
@@ -296,10 +272,7 @@ var invokeViii = &wasm.HostFunc{
 	ParamTypes:  []api.ValueType{i32, i32, i32, i32},
 	ParamNames:  []string{"index", "a1", "a2", "a3"},
 	ResultTypes: []api.ValueType{},
-	Code: &wasm.Code{
-		IsHostFunction: true,
-		GoFunc:         api.GoModuleFunc(invokeViiiFn),
-	},
+	Code:        &wasm.Code{GoFunc: api.GoModuleFunc(invokeViiiFn)},
 }
 
 func invokeViiiFn(ctx context.Context, mod api.Module, stack []uint64) {
@@ -315,10 +288,7 @@ var invokeViiii = &wasm.HostFunc{
 	ParamTypes:  []api.ValueType{i32, i32, i32, i32, i32},
 	ParamNames:  []string{"index", "a1", "a2", "a3", "a4"},
 	ResultTypes: []api.ValueType{},
-	Code: &wasm.Code{
-		IsHostFunction: true,
-		GoFunc:         api.GoModuleFunc(invokeViiiiFn),
-	},
+	Code:        &wasm.Code{GoFunc: api.GoModuleFunc(invokeViiiiFn)},
 }
 
 func invokeViiiiFn(ctx context.Context, mod api.Module, stack []uint64) {
diff --git a/imports/wasi_snapshot_preview1/proc.go b/imports/wasi_snapshot_preview1/proc.go
index f8c31697..5c76387e 100644
--- a/imports/wasi_snapshot_preview1/proc.go
+++ b/imports/wasi_snapshot_preview1/proc.go
@@ -24,8 +24,7 @@ var procExit = &wasm.HostFunc{
 	ParamTypes:  []api.ValueType{i32},
 	ParamNames:  []string{"rval"},
 	Code: &wasm.Code{
-		IsHostFunction: true,
-		GoFunc:         api.GoModuleFunc(procExitFn),
+		GoFunc: api.GoModuleFunc(procExitFn),
 	},
 }
 
diff --git a/imports/wasi_snapshot_preview1/wasi.go b/imports/wasi_snapshot_preview1/wasi.go
index 480024be..e650dce1 100644
--- a/imports/wasi_snapshot_preview1/wasi.go
+++ b/imports/wasi_snapshot_preview1/wasi.go
@@ -268,7 +268,7 @@ func newHostFunc(
 		ParamNames:  paramNames,
 		ResultTypes: []api.ValueType{i32},
 		ResultNames: []string{"errno"},
-		Code:        &wasm.Code{IsHostFunction: true, GoFunc: goFunc},
+		Code:        &wasm.Code{GoFunc: goFunc},
 	}
 }
 
@@ -292,8 +292,7 @@ func stubFunction(name string, paramTypes []wasm.ValueType, paramNames ...string
 		ResultTypes: []api.ValueType{i32},
 		ResultNames: []string{"errno"},
 		Code: &wasm.Code{
-			IsHostFunction: true,
-			Body:           []byte{wasm.OpcodeI32Const, byte(ErrnoNosys), wasm.OpcodeEnd},
+			GoFunc: api.GoModuleFunc(func(_ context.Context, _ api.Module, stack []uint64) { stack[0] = uint64(ErrnoNosys) }),
 		},
 	}
 }
diff --git a/internal/engine/interpreter/interpreter.go b/internal/engine/interpreter/interpreter.go
index 819e96ca..64a91b51 100644
--- a/internal/engine/interpreter/interpreter.go
+++ b/internal/engine/interpreter/interpreter.go
@@ -178,7 +178,6 @@ type code struct {
 	body              []*interpreterOp
 	listener          experimental.FunctionListener
 	hostFn            interface{}
-	isHostFunction    bool
 	ensureTermination bool
 }
 
@@ -251,7 +250,6 @@ func (e *engine) CompileModule(ctx context.Context, module *wasm.Module, listene
 			compiled.listener = lsn
 		}
 		compiled.source = module
-		compiled.isHostFunction = ir.IsHostFunction
 		compiled.ensureTermination = ir.EnsureTermination
 		funcs[i] = compiled
 	}
@@ -887,7 +885,7 @@ func (ce *callEngine) callNativeFunc(ctx context.Context, callCtx *wasm.CallCont
 	moduleInst := f.source.Module
 	functions := moduleInst.Engine.(*moduleEngine).functions
 	var memoryInst *wasm.MemoryInstance
-	if f.parent.isHostFunction {
+	if f.parent.hostFn != nil {
 		memoryInst = ce.callerMemory()
 	} else {
 		memoryInst = moduleInst.Memory
@@ -4160,14 +4158,7 @@ func (ce *callEngine) callNativeFunc(ctx context.Context, callCtx *wasm.CallCont
 
 // callerMemory returns the caller context memory.
 func (ce *callEngine) callerMemory() *wasm.MemoryInstance {
-	// Search through the call frame stack from the top until we find a non host function.
-	for i := len(ce.frames) - 1; i >= 0; i-- {
-		f := ce.frames[i].f
-		if !f.parent.isHostFunction {
-			return f.source.Module.Memory
-		}
-	}
-	return nil
+	return ce.frames[len(ce.frames)-1].f.source.Module.Memory
 }
 
 func WasmCompatMax32bits(v1, v2 uint32) uint64 {
diff --git a/internal/gojs/builtin.go b/internal/gojs/builtin.go
index 06fe4bd5..17cfbd37 100644
--- a/internal/gojs/builtin.go
+++ b/internal/gojs/builtin.go
@@ -26,7 +26,7 @@ func newJsGlobal(rt http.RoundTripper) *jsVal {
 			"fs":              jsfs,
 			"Date":            jsDateConstructor,
 		}).
-		addFunction("fetch", httpFetch{})
+		addFunction("fetch", &httpFetch{rt})
 }
 
 var (
diff --git a/internal/gojs/config/config.go b/internal/gojs/config/config.go
new file mode 100644
index 00000000..dab63bfe
--- /dev/null
+++ b/internal/gojs/config/config.go
@@ -0,0 +1,42 @@
+// Package config exists to avoid dependency cycles when keeping most of gojs
+// code internal.
+package config
+
+import (
+	"net/http"
+	"os"
+	"path/filepath"
+
+	"github.com/tetratelabs/wazero/internal/platform"
+)
+
+type Config struct {
+	OsWorkdir bool
+	Rt        http.RoundTripper
+
+	// Workdir is the actual working directory value.
+	Workdir string
+}
+
+func NewConfig() *Config {
+	return &Config{Workdir: "/"}
+}
+
+func (c *Config) Clone() *Config {
+	ret := *c // copy except maps which share a ref
+	return &ret
+}
+
+func (c *Config) Init() error {
+	if c.OsWorkdir {
+		workdir, err := os.Getwd()
+		if err != nil {
+			return err
+		}
+		// Ensure if used on windows, the input path is translated to a POSIX one.
+		workdir = platform.ToPosixPath(workdir)
+		// Strip the volume of the path, for example C:\
+		c.Workdir = workdir[len(filepath.VolumeName(workdir)):]
+	}
+	return nil
+}
diff --git a/internal/gojs/fs.go b/internal/gojs/fs.go
index 35df7bba..bc60ecd1 100644
--- a/internal/gojs/fs.go
+++ b/internal/gojs/fs.go
@@ -539,8 +539,8 @@ type jsfsChown struct{}
 
 func (jsfsChown) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
 	path := resolvePath(ctx, args[0].(string))
-	uid := goos.ValueToUint32(args[1])
-	gid := goos.ValueToUint32(args[2])
+	uid := goos.ValueToInt32(args[1])
+	gid := goos.ValueToInt32(args[2])
 	callback := args[3].(funcWrapper)
 
 	fsc := mod.(*wasm.CallContext).Sys.FS()
diff --git a/internal/gojs/goarch/wasm.go b/internal/gojs/goarch/wasm.go
index d7d95d88..0ebb023a 100644
--- a/internal/gojs/goarch/wasm.go
+++ b/internal/gojs/goarch/wasm.go
@@ -20,7 +20,7 @@ func StubFunction(name string) *wasm.HostFunc {
 		Name:        name,
 		ParamTypes:  []wasm.ValueType{wasm.ValueTypeI32},
 		ParamNames:  []string{"sp"},
-		Code:        &wasm.Code{IsHostFunction: true, Body: []byte{wasm.OpcodeUnreachable, wasm.OpcodeEnd}},
+		Code:        &wasm.Code{GoFunc: api.GoModuleFunc(func(ctx context.Context, _ api.Module, stack []uint64) {})},
 	}
 }
 
diff --git a/internal/gojs/goos/goos.go b/internal/gojs/goos/goos.go
index 3f7eb0ac..c56ab0d3 100644
--- a/internal/gojs/goos/goos.go
+++ b/internal/gojs/goos/goos.go
@@ -265,6 +265,15 @@ func ValueToUint32(arg interface{}) uint32 {
 	return uint32(arg.(float64))
 }
 
+func ValueToInt32(arg interface{}) int32 {
+	if arg == RefValueZero || arg == Undefined {
+		return 0
+	} else if u, ok := arg.(int); ok {
+		return int32(u)
+	}
+	return int32(uint32(arg.(float64)))
+}
+
 // GetFunction allows getting a JavaScript property by name.
 type GetFunction interface {
 	Get(ctx context.Context, propertyKey string) interface{}
diff --git a/internal/gojs/http.go b/internal/gojs/http.go
index 11434a76..1026d0d7 100644
--- a/internal/gojs/http.go
+++ b/internal/gojs/http.go
@@ -15,15 +15,6 @@ import (
 // headersConstructor = Get("Headers").New() // http.Roundtrip && "fetch"
 var headersConstructor = newJsVal(goos.RefHttpHeadersConstructor, "Headers")
 
-type RoundTripperKey struct{}
-
-func getRoundTripper(ctx context.Context) http.RoundTripper {
-	if rt, ok := ctx.Value(RoundTripperKey{}).(http.RoundTripper); ok {
-		return rt
-	}
-	return nil
-}
-
 // httpFetch implements jsFn for http.RoundTripper
 //
 // Reference in roundtrip_js.go init
@@ -33,10 +24,10 @@ func getRoundTripper(ctx context.Context) http.RoundTripper {
 // In http.Transport RoundTrip, this returns a promise
 //
 //	fetchPromise := js.Global().Call("fetch", req.URL.String(), opt)
-type httpFetch struct{}
+type httpFetch struct{ rt http.RoundTripper }
 
-func (httpFetch) invoke(ctx context.Context, _ api.Module, args ...interface{}) (interface{}, error) {
-	rt := getRoundTripper(ctx)
+func (h *httpFetch) invoke(ctx context.Context, _ api.Module, args ...interface{}) (interface{}, error) {
+	rt := h.rt
 	if rt == nil {
 		panic("unexpected to reach here without roundtripper as property is nil checked")
 	}
diff --git a/internal/gojs/logging/logging.go b/internal/gojs/logging/logging.go
index 9064ca26..744558d2 100644
--- a/internal/gojs/logging/logging.go
+++ b/internal/gojs/logging/logging.go
@@ -301,6 +301,11 @@ func writeVal(w logging.Writer, name string, val interface{}) {
 		return
 	}
 	switch name {
+	case "uid", "gid":
+		w.WriteString(name) //nolint
+		w.WriteByte('=')    //nolint
+		id := int(goos.ValueToInt32(val))
+		w.WriteString(strconv.Itoa(id)) //nolint
 	case "mask", "mode", "oldmask", "perm":
 		w.WriteString(name) //nolint
 		w.WriteByte('=')    //nolint
diff --git a/internal/gojs/run/gojs.go b/internal/gojs/run/gojs.go
index 06143187..1f9745a1 100644
--- a/internal/gojs/run/gojs.go
+++ b/internal/gojs/run/gojs.go
@@ -7,25 +7,35 @@ import (
 
 	"github.com/tetratelabs/wazero"
 	"github.com/tetratelabs/wazero/internal/gojs"
+	"github.com/tetratelabs/wazero/internal/gojs/config"
 )
 
-func RunAndReturnState(ctx context.Context, r wazero.Runtime, compiled wazero.CompiledModule, config wazero.ModuleConfig) (*gojs.State, error) {
-	// Instantiate the module compiled by go, noting it has no init function.
+func RunAndReturnState(
+	ctx context.Context,
+	r wazero.Runtime,
+	compiled wazero.CompiledModule,
+	moduleConfig wazero.ModuleConfig,
+	config *config.Config,
+) (*gojs.State, error) {
+	if err := config.Init(); err != nil {
+		return nil, err
+	}
 
-	mod, err := r.InstantiateModule(ctx, compiled, config)
+	// Instantiate the module compiled by go, noting it has no init function.
+	mod, err := r.InstantiateModule(ctx, compiled, moduleConfig)
 	if err != nil {
 		return nil, err
 	}
 	defer mod.Close(ctx)
 
-	// Extract the args and env from the module config and write it to memory.
+	// Extract the args and env from the module Config and write it to memory.
 	argc, argv, err := gojs.WriteArgsAndEnviron(mod)
 	if err != nil {
 		return nil, err
 	}
 
 	// Create host-side state for JavaScript values and events.
-	s := gojs.NewState(ctx)
+	s := gojs.NewState(config)
 	ctx = context.WithValue(ctx, gojs.StateKey{}, s)
 
 	// Invoke the run function.
diff --git a/internal/gojs/state.go b/internal/gojs/state.go
index f089586f..e0380a53 100644
--- a/internal/gojs/state.go
+++ b/internal/gojs/state.go
@@ -6,24 +6,16 @@ import (
 	"math"
 
 	"github.com/tetratelabs/wazero/api"
+	"github.com/tetratelabs/wazero/internal/gojs/config"
 	"github.com/tetratelabs/wazero/internal/gojs/goos"
 	"github.com/tetratelabs/wazero/internal/gojs/values"
 )
 
-type WorkdirKey struct{}
-
-func getWorkdir(ctx context.Context) string {
-	if wd, ok := ctx.Value(WorkdirKey{}).(string); ok {
-		return wd
-	}
-	return "/"
-}
-
-func NewState(ctx context.Context) *State {
+func NewState(config *config.Config) *State {
 	return &State{
 		values:                 values.NewValues(),
-		valueGlobal:            newJsGlobal(getRoundTripper(ctx)),
-		cwd:                    getWorkdir(ctx),
+		valueGlobal:            newJsGlobal(config.Rt),
+		cwd:                    config.Workdir,
 		umask:                  0o0022,
 		_nextCallbackTimeoutID: 1,
 		_scheduledTimeouts:     map[uint32]chan bool{},
diff --git a/internal/gojs/util/util.go b/internal/gojs/util/util.go
index d1a2f08a..83834843 100644
--- a/internal/gojs/util/util.go
+++ b/internal/gojs/util/util.go
@@ -41,6 +41,6 @@ func NewFunc(name string, goFunc api.GoModuleFunc) *wasm.HostFunc {
 		Name:        name,
 		ParamTypes:  []api.ValueType{api.ValueTypeI32},
 		ParamNames:  []string{"sp"},
-		Code:        &wasm.Code{IsHostFunction: true, GoFunc: goFunc},
+		Code:        &wasm.Code{GoFunc: goFunc},
 	}
 }
diff --git a/internal/wasm/func_validation.go b/internal/wasm/func_validation.go
index 9c5064eb..89cd683b 100644
--- a/internal/wasm/func_validation.go
+++ b/internal/wasm/func_validation.go
@@ -95,7 +95,7 @@ func (m *Module) validateFunctionWithMaxStackValues(
 		}
 
 		if OpcodeI32Load <= op && op <= OpcodeI64Store32 {
-			if memory == nil && !code.IsHostFunction {
+			if memory == nil {
 				return fmt.Errorf("memory must exist for %s", InstructionName(op))
 			}
 			pc++
@@ -277,7 +277,7 @@ func (m *Module) validateFunctionWithMaxStackValues(
 				}
 			}
 		} else if OpcodeMemorySize <= op && op <= OpcodeMemoryGrow {
-			if memory == nil && !code.IsHostFunction {
+			if memory == nil {
 				return fmt.Errorf("memory must exist for %s", InstructionName(op))
 			}
 			pc++
@@ -1113,7 +1113,7 @@ func (m *Module) validateFunctionWithMaxStackValues(
 				OpcodeVecV128Load32x2s, OpcodeVecV128Load32x2u, OpcodeVecV128Load8Splat, OpcodeVecV128Load16Splat,
 				OpcodeVecV128Load32Splat, OpcodeVecV128Load64Splat,
 				OpcodeVecV128Load32zero, OpcodeVecV128Load64zero:
-				if memory == nil && !code.IsHostFunction {
+				if memory == nil {
 					return fmt.Errorf("memory must exist for %s", VectorInstructionName(vecOpcode))
 				}
 				pc++
@@ -1151,7 +1151,7 @@ func (m *Module) validateFunctionWithMaxStackValues(
 				}
 				valueTypeStack.push(ValueTypeV128)
 			case OpcodeVecV128Store:
-				if memory == nil && !code.IsHostFunction {
+				if memory == nil {
 					return fmt.Errorf("memory must exist for %s", VectorInstructionName(vecOpcode))
 				}
 				pc++
@@ -1170,7 +1170,7 @@ func (m *Module) validateFunctionWithMaxStackValues(
 					return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeVecV128StoreName, err)
 				}
 			case OpcodeVecV128Load8Lane, OpcodeVecV128Load16Lane, OpcodeVecV128Load32Lane, OpcodeVecV128Load64Lane:
-				if memory == nil && !code.IsHostFunction {
+				if memory == nil {
 					return fmt.Errorf("memory must exist for %s", VectorInstructionName(vecOpcode))
 				}
 				attr := vecLoadLanes[vecOpcode]
@@ -1198,7 +1198,7 @@ func (m *Module) validateFunctionWithMaxStackValues(
 				}
 				valueTypeStack.push(ValueTypeV128)
 			case OpcodeVecV128Store8Lane, OpcodeVecV128Store16Lane, OpcodeVecV128Store32Lane, OpcodeVecV128Store64Lane:
-				if memory == nil && !code.IsHostFunction {
+				if memory == nil {
 					return fmt.Errorf("memory must exist for %s", VectorInstructionName(vecOpcode))
 				}
 				attr := vecStoreLanes[vecOpcode]
diff --git a/internal/wasm/gofunc.go b/internal/wasm/gofunc.go
index c98ce1a0..2c75c34b 100644
--- a/internal/wasm/gofunc.go
+++ b/internal/wasm/gofunc.go
@@ -254,7 +254,7 @@ func parseGoReflectFunc(fn interface{}) (params, results []ValueType, code *Code
 		return
 	}
 
-	code = &Code{IsHostFunction: true}
+	code = &Code{}
 	if pk == paramsKindContextModule {
 		code.GoFunc = &reflectGoModuleFunction{fn: &fnV, params: params, results: results}
 	} else {
diff --git a/internal/wasm/host.go b/internal/wasm/host.go
index 15e542b6..311982fd 100644
--- a/internal/wasm/host.go
+++ b/internal/wasm/host.go
@@ -49,14 +49,14 @@ func (f *HostFunc) MustGoReflectFunc(fn interface{}) *HostFunc {
 // WithGoFunc returns a copy of the function, replacing its Code.GoFunc.
 func (f *HostFunc) WithGoFunc(fn api.GoFunc) *HostFunc {
 	ret := *f
-	ret.Code = &Code{IsHostFunction: true, GoFunc: fn}
+	ret.Code = &Code{GoFunc: fn}
 	return &ret
 }
 
 // WithGoModuleFunc returns a copy of the function, replacing its Code.GoFunc.
 func (f *HostFunc) WithGoModuleFunc(fn api.GoModuleFunc) *HostFunc {
 	ret := *f
-	ret.Code = &Code{IsHostFunction: true, GoFunc: fn}
+	ret.Code = &Code{GoFunc: fn}
 	return &ret
 }
 
@@ -68,16 +68,6 @@ func (f *HostFunc) WithGoReflectFunc(fn interface{}) (*HostFunc, error) {
 	return &ret, err
 }
 
-// WithWasm returns a copy of the function, replacing its Code.Body.
-func (f *HostFunc) WithWasm(body []byte) *HostFunc {
-	ret := *f
-	ret.Code = &Code{IsHostFunction: true, Body: body}
-	if f.Code != nil {
-		ret.Code.LocalTypes = f.Code.LocalTypes
-	}
-	return &ret
-}
-
 type HostFuncNames struct {
 	Name        string
 	ParamNames  []string
diff --git a/internal/wasm/module.go b/internal/wasm/module.go
index e19bc71d..9d4bea7f 100644
--- a/internal/wasm/module.go
+++ b/internal/wasm/module.go
@@ -835,15 +835,6 @@ type Export struct {
 // Code is an entry in the Module.CodeSection containing the locals and body of the function.
 // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-code
 type Code struct {
-	// IsHostFunction returns true if the function was implemented by the
-	// embedder (ex via wazero.HostModuleBuilder) instead of a wasm binary.
-	//
-	// Notably, host functions can use the caller's memory, which might be
-	// different from its defining module.
-	//
-	// See https://www.w3.org/TR/wasm-core-1/#host-functions%E2%91%A0
-	IsHostFunction bool
-
 	// LocalTypes are any function-scoped variables in insertion order.
 	// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-local
 	LocalTypes []ValueType
diff --git a/internal/wazeroir/compiler.go b/internal/wazeroir/compiler.go
index 529528b2..a8d75400 100644
--- a/internal/wazeroir/compiler.go
+++ b/internal/wazeroir/compiler.go
@@ -209,10 +209,6 @@ func (c *compiler) resetUnreachable() {
 }
 
 type CompilationResult struct {
-	// IsHostFunction is the data returned by the same field documented on
-	// wasm.Code.
-	IsHostFunction bool
-
 	// GoFunc is the data returned by the same field documented on wasm.Code.
 	// In this case, IsHostFunction is true and other fields can be ignored.
 	GoFunc interface{}
@@ -284,6 +280,7 @@ func CompileFunctions(enabledFeatures api.CoreFeatures, callFrameStackSizeInUint
 		wasmTypes:     types,
 	}
 
+	var goFuncExists bool
 	controlFramesStack := &controlFrames{}
 	var ret []*CompilationResult
 	for funcIndex := range module.FunctionSection {
@@ -295,13 +292,17 @@ func CompileFunctions(enabledFeatures api.CoreFeatures, callFrameStackSizeInUint
 			_, usesMemory := code.GoFunc.(api.GoModuleFunction)
 
 			ret = append(ret, &CompilationResult{
-				IsHostFunction: true,
-				UsesMemory:     usesMemory,
-				GoFunc:         code.GoFunc,
-				Signature:      sig,
+				UsesMemory: usesMemory,
+				GoFunc:     code.GoFunc,
+				Signature:  sig,
 			})
+			goFuncExists = true
 			continue
 		}
+
+		if goFuncExists {
+			panic("BUG: host functions must be implemented as Go functions")
+		}
 		r, err := compile(enabledFeatures, callFrameStackSizeInUint64, sig, code.Body,
 			code.LocalTypes, types, functions, globals, code.BodyOffsetInCodeSection,
 			module.DWARFLines != nil, ensureTermination, funcTypeToSigs, controlFramesStack)
@@ -309,7 +310,6 @@ func CompileFunctions(enabledFeatures api.CoreFeatures, callFrameStackSizeInUint
 			def := module.FunctionDefinitionSection[uint32(funcIndex)+module.ImportFuncCount()]
 			return nil, fmt.Errorf("failed to lower func[%s] to wazeroir: %w", def.DebugName(), err)
 		}
-		r.IsHostFunction = code.IsHostFunction
 		r.Globals = globals
 		r.Functions = functions
 		r.Types = types


Test patch:
diff --git a/builder_test.go b/builder_test.go
index 4476df81..e635317d 100644
--- a/builder_test.go
+++ b/builder_test.go
@@ -171,7 +171,7 @@ func TestNewHostModuleBuilder_Compile(t *testing.T) {
 				},
 				FunctionSection: []wasm.Index{0},
 				CodeSection: []*wasm.Code{
-					{IsHostFunction: true, GoFunc: gofunc1},
+					{GoFunc: gofunc1},
 				},
 				ExportSection: []wasm.Export{
 					{Name: "1", Type: wasm.ExternTypeFunc, Index: 0},
@@ -195,7 +195,7 @@ func TestNewHostModuleBuilder_Compile(t *testing.T) {
 				},
 				FunctionSection: []wasm.Index{0},
 				CodeSection: []*wasm.Code{
-					{IsHostFunction: true, GoFunc: gofunc1},
+					{GoFunc: gofunc1},
 				},
 				ExportSection: []wasm.Export{
 					{Name: "1", Type: wasm.ExternTypeFunc, Index: 0},
@@ -223,7 +223,7 @@ func TestNewHostModuleBuilder_Compile(t *testing.T) {
 				},
 				FunctionSection: []wasm.Index{0},
 				CodeSection: []*wasm.Code{
-					{IsHostFunction: true, GoFunc: gofunc2},
+					{GoFunc: gofunc2},
 				},
 				ExportSection: []wasm.Export{
 					{Name: "1", Type: wasm.ExternTypeFunc, Index: 0},
@@ -252,8 +252,8 @@ func TestNewHostModuleBuilder_Compile(t *testing.T) {
 				},
 				FunctionSection: []wasm.Index{0, 1},
 				CodeSection: []*wasm.Code{
-					{IsHostFunction: true, GoFunc: gofunc1},
-					{IsHostFunction: true, GoFunc: gofunc2},
+					{GoFunc: gofunc1},
+					{GoFunc: gofunc2},
 				},
 				ExportSection: []wasm.Export{
 					{Name: "1", Type: wasm.ExternTypeFunc, Index: 0},
@@ -310,7 +310,7 @@ func TestNewHostModuleBuilder_Compile_Errors(t *testing.T) {
 					WithFunc(&wasm.HostFunc{
 						ExportNames: []string{"fn"},
 						ResultTypes: []wasm.ValueType{wasm.ValueTypeI32},
-						Code:        &wasm.Code{IsHostFunction: true, Body: []byte{wasm.OpcodeEnd}},
+						Code:        &wasm.Code{Body: []byte{wasm.OpcodeEnd}},
 					}).Export("fn")
 			},
 			expectedErr: `invalid function[0] export["fn"]: not enough results
@@ -378,7 +378,6 @@ func requireHostModuleEquals(t *testing.T, expected, actual *wasm.Module) {
 	require.Equal(t, len(expected.CodeSection), len(actual.CodeSection))
 	for i, c := range expected.CodeSection {
 		actualCode := actual.CodeSection[i]
-		require.True(t, actualCode.IsHostFunction)
 		require.Equal(t, c.GoFunc, actualCode.GoFunc)
 
 		// Not wasm
diff --git a/experimental/gojs/example/stars_test.go b/experimental/gojs/example/stars_test.go
index e84e8c13..26fcf683 100644
--- a/experimental/gojs/example/stars_test.go
+++ b/experimental/gojs/example/stars_test.go
@@ -80,8 +80,8 @@ func Benchmark_main(b *testing.B) {
 	gojs.MustInstantiate(ctx, r)
 
 	// Instead of making real HTTP calls, return fake data.
-	ctx = gojs.WithRoundTripper(ctx, &fakeGitHub{})
-	cfg := wazero.NewModuleConfig()
+	cfg := gojs.NewConfig(wazero.NewModuleConfig()).
+		WithRoundTripper(&fakeGitHub{})
 
 	b.Run("gojs.Run", func(b *testing.B) {
 		b.ReportAllocs()
diff --git a/experimental/gojs/gojs_test.go b/experimental/gojs/gojs_test.go
deleted file mode 100644
index bb1ef995..00000000
--- a/experimental/gojs/gojs_test.go
+++ /dev/null
@@ -1,22 +0,0 @@
-package gojs
-
-import (
-	"context"
-	"strings"
-	"testing"
-
-	"github.com/tetratelabs/wazero/internal/gojs"
-	"github.com/tetratelabs/wazero/internal/testing/require"
-)
-
-func TestWithOSWorkdir(t *testing.T) {
-	t.Parallel()
-
-	ctx, err := WithOSWorkDir(context.Background())
-	require.NoError(t, err)
-	actual := ctx.Value(gojs.WorkdirKey{}).(string)
-
-	// Check c:\ or d:\ aren't retained.
-	require.Equal(t, -1, strings.IndexByte(actual, '\\'))
-	require.Equal(t, -1, strings.IndexByte(actual, ':'))
-}
diff --git a/imports/assemblyscript/assemblyscript_test.go b/imports/assemblyscript/assemblyscript_test.go
index 9e2cb601..f72a7aa7 100644
--- a/imports/assemblyscript/assemblyscript_test.go
+++ b/imports/assemblyscript/assemblyscript_test.go
@@ -212,12 +212,11 @@ func TestFunctionExporter_Trace(t *testing.T) {
 		expected, expectedLog string
 	}{
 		{
-			name:     "disabled",
-			exporter: NewFunctionExporter(),
-			params:   noArgs,
-			expected: "",
-			// expect no host call since it is disabled. ==> is host and --> is wasm.
-			expectedLog: strings.ReplaceAll(noArgsLog, "==", "--"),
+			name:        "disabled",
+			exporter:    NewFunctionExporter(),
+			params:      noArgs,
+			expected:    "",
+			expectedLog: noArgsLog,
 		},
 		{
 			name:        "ToStderr",
diff --git a/imports/emscripten/emscripten_test.go b/imports/emscripten/emscripten_test.go
index bc2f9d96..698e287d 100644
--- a/imports/emscripten/emscripten_test.go
+++ b/imports/emscripten/emscripten_test.go
@@ -51,7 +51,7 @@ func TestGrow(t *testing.T) {
 	require.Zero(t, err.(*sys.ExitError).ExitCode())
 
 	// We expect the memory no-op memory growth hook to be invoked as wasm.
-	require.Contains(t, log.String(), "--> env.emscripten_notify_memory_growth(memory_index=0)")
+	require.Contains(t, log.String(), "==> env.emscripten_notify_memory_growth(memory_index=0)")
 }
 
 func TestInvoke(t *testing.T) {
diff --git a/imports/wasi_snapshot_preview1/fs_test.go b/imports/wasi_snapshot_preview1/fs_test.go
index 7550bf46..db5909cb 100644
--- a/imports/wasi_snapshot_preview1/fs_test.go
+++ b/imports/wasi_snapshot_preview1/fs_test.go
@@ -457,8 +457,8 @@ func Test_fdFdstatSetFlags(t *testing.T) {
 func Test_fdFdstatSetRights(t *testing.T) {
 	log := requireErrnoNosys(t, FdFdstatSetRightsName, 0, 0, 0)
 	require.Equal(t, `
---> wasi_snapshot_preview1.fd_fdstat_set_rights(fd=0,fs_rights_base=,fs_rights_inheriting=)
-<-- errno=ENOSYS
+==> wasi_snapshot_preview1.fd_fdstat_set_rights(fd=0,fs_rights_base=,fs_rights_inheriting=)
+<== errno=ENOSYS
 `, log)
 }
 
diff --git a/imports/wasi_snapshot_preview1/proc_test.go b/imports/wasi_snapshot_preview1/proc_test.go
index 8ab55e44..85fed4c2 100644
--- a/imports/wasi_snapshot_preview1/proc_test.go
+++ b/imports/wasi_snapshot_preview1/proc_test.go
@@ -55,7 +55,7 @@ func Test_procExit(t *testing.T) {
 func Test_procRaise(t *testing.T) {
 	log := requireErrnoNosys(t, ProcRaiseName, 0)
 	require.Equal(t, `
---> wasi_snapshot_preview1.proc_raise(sig=0)
-<-- errno=ENOSYS
+==> wasi_snapshot_preview1.proc_raise(sig=0)
+<== errno=ENOSYS
 `, log)
 }
diff --git a/imports/wasi_snapshot_preview1/sock_test.go b/imports/wasi_snapshot_preview1/sock_test.go
index 98cbc056..a2dc0c36 100644
--- a/imports/wasi_snapshot_preview1/sock_test.go
+++ b/imports/wasi_snapshot_preview1/sock_test.go
@@ -11,8 +11,8 @@ import (
 func Test_sockAccept(t *testing.T) {
 	log := requireErrnoNosys(t, SockAcceptName, 0, 0, 0)
 	require.Equal(t, `
---> wasi_snapshot_preview1.sock_accept(fd=0,flags=0,result.fd=0)
-<-- errno=ENOSYS
+==> wasi_snapshot_preview1.sock_accept(fd=0,flags=0,result.fd=0)
+<== errno=ENOSYS
 `, log)
 }
 
@@ -20,8 +20,8 @@ func Test_sockAccept(t *testing.T) {
 func Test_sockRecv(t *testing.T) {
 	log := requireErrnoNosys(t, SockRecvName, 0, 0, 0, 0, 0, 0)
 	require.Equal(t, `
---> wasi_snapshot_preview1.sock_recv(fd=0,ri_data=0,ri_data_count=0,ri_flags=0,result.ro_datalen=0,result.ro_flags=0)
-<-- errno=ENOSYS
+==> wasi_snapshot_preview1.sock_recv(fd=0,ri_data=0,ri_data_count=0,ri_flags=0,result.ro_datalen=0,result.ro_flags=0)
+<== errno=ENOSYS
 `, log)
 }
 
@@ -29,8 +29,8 @@ func Test_sockRecv(t *testing.T) {
 func Test_sockSend(t *testing.T) {
 	log := requireErrnoNosys(t, SockSendName, 0, 0, 0, 0, 0)
 	require.Equal(t, `
---> wasi_snapshot_preview1.sock_send(fd=0,si_data=0,si_data_count=0,si_flags=0,result.so_datalen=0)
-<-- errno=ENOSYS
+==> wasi_snapshot_preview1.sock_send(fd=0,si_data=0,si_data_count=0,si_flags=0,result.so_datalen=0)
+<== errno=ENOSYS
 `, log)
 }
 
@@ -38,7 +38,7 @@ func Test_sockSend(t *testing.T) {
 func Test_sockShutdown(t *testing.T) {
 	log := requireErrnoNosys(t, SockShutdownName, 0, 0)
 	require.Equal(t, `
---> wasi_snapshot_preview1.sock_shutdown(fd=0,how=0)
-<-- errno=ENOSYS
+==> wasi_snapshot_preview1.sock_shutdown(fd=0,how=0)
+<== errno=ENOSYS
 `, log)
 }
diff --git a/internal/gojs/argsenv_test.go b/internal/gojs/argsenv_test.go
index 63c28391..615837c1 100644
--- a/internal/gojs/argsenv_test.go
+++ b/internal/gojs/argsenv_test.go
@@ -4,14 +4,16 @@ import (
 	"testing"
 
 	"github.com/tetratelabs/wazero"
+	"github.com/tetratelabs/wazero/internal/gojs/config"
 	"github.com/tetratelabs/wazero/internal/testing/require"
 )
 
 func Test_argsAndEnv(t *testing.T) {
 	t.Parallel()
 
-	stdout, stderr, err := compileAndRun(testCtx, "argsenv", wazero.NewModuleConfig().
-		WithEnv("c", "d").WithEnv("a", "b"))
+	stdout, stderr, err := compileAndRun(testCtx, "argsenv", func(moduleConfig wazero.ModuleConfig) (wazero.ModuleConfig, *config.Config) {
+		return moduleConfig.WithEnv("c", "d").WithEnv("a", "b"), config.NewConfig()
+	})
 
 	require.EqualError(t, err, `module "" closed with exit_code(0)`)
 	require.Zero(t, stderr)
diff --git a/internal/gojs/compiler_test.go b/internal/gojs/compiler_test.go
index dbe5e81d..c7f46b95 100644
--- a/internal/gojs/compiler_test.go
+++ b/internal/gojs/compiler_test.go
@@ -20,13 +20,20 @@ import (
 	"github.com/tetratelabs/wazero/experimental/gojs"
 	"github.com/tetratelabs/wazero/internal/fstest"
 	internalgojs "github.com/tetratelabs/wazero/internal/gojs"
+	"github.com/tetratelabs/wazero/internal/gojs/config"
 	"github.com/tetratelabs/wazero/internal/gojs/run"
 	"github.com/tetratelabs/wazero/internal/testing/binaryencoding"
 	"github.com/tetratelabs/wazero/internal/wasm"
 	binaryformat "github.com/tetratelabs/wazero/internal/wasm/binary"
 )
 
-func compileAndRun(ctx context.Context, arg string, config wazero.ModuleConfig) (stdout, stderr string, err error) {
+type newConfig func(moduleConfig wazero.ModuleConfig) (wazero.ModuleConfig, *config.Config)
+
+func defaultConfig(moduleConfig wazero.ModuleConfig) (wazero.ModuleConfig, *config.Config) {
+	return moduleConfig, config.NewConfig()
+}
+
+func compileAndRun(ctx context.Context, arg string, config newConfig) (stdout, stderr string, err error) {
 	rt := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfig().
 		// https://github.com/tetratelabs/wazero/issues/992
 		WithMemoryCapacityFromMax(true).
@@ -34,7 +41,7 @@ func compileAndRun(ctx context.Context, arg string, config wazero.ModuleConfig)
 	return compileAndRunWithRuntime(ctx, rt, arg, config) // use global runtime
 }
 
-func compileAndRunWithRuntime(ctx context.Context, r wazero.Runtime, arg string, config wazero.ModuleConfig) (stdout, stderr string, err error) {
+func compileAndRunWithRuntime(ctx context.Context, r wazero.Runtime, arg string, config newConfig) (stdout, stderr string, err error) {
 	var stdoutBuf, stderrBuf bytes.Buffer
 
 	builder := r.NewHostModuleBuilder("go")
@@ -49,13 +56,15 @@ func compileAndRunWithRuntime(ctx context.Context, r wazero.Runtime, arg string,
 		log.Panicln(err)
 	}
 
-	var s *internalgojs.State
-	s, err = run.RunAndReturnState(ctx, r, compiled, config.
+	mc, c := config(wazero.NewModuleConfig().
 		WithStdout(&stdoutBuf).
 		WithStderr(&stderrBuf).
 		WithArgs("test", arg))
+
+	var s *internalgojs.State
+	s, err = run.RunAndReturnState(ctx, r, compiled, mc, c)
 	if err == nil {
-		if !reflect.DeepEqual(s, internalgojs.NewState(ctx)) {
+		if !reflect.DeepEqual(s, internalgojs.NewState(c)) {
 			log.Panicf("unexpected state: %v\n", s)
 		}
 	}
diff --git a/internal/gojs/config/config_test.go b/internal/gojs/config/config_test.go
new file mode 100644
index 00000000..870231b7
--- /dev/null
+++ b/internal/gojs/config/config_test.go
@@ -0,0 +1,22 @@
+package config
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/tetratelabs/wazero/internal/testing/require"
+)
+
+func TestConfig_Init(t *testing.T) {
+	t.Parallel()
+
+	t.Run("OsWorkdir", func(t *testing.T) {
+		c := &Config{OsWorkdir: true}
+		require.NoError(t, c.Init())
+		actual := c.Workdir
+
+		// Check c:\ or d:\ aren't retained.
+		require.Equal(t, -1, strings.IndexByte(actual, '\\'))
+		require.Equal(t, -1, strings.IndexByte(actual, ':'))
+	})
+}
diff --git a/internal/gojs/crypto_test.go b/internal/gojs/crypto_test.go
index 23d9191e..c4c58194 100644
--- a/internal/gojs/crypto_test.go
+++ b/internal/gojs/crypto_test.go
@@ -5,7 +5,6 @@ import (
 	"context"
 	"testing"
 
-	"github.com/tetratelabs/wazero"
 	"github.com/tetratelabs/wazero/experimental"
 	"github.com/tetratelabs/wazero/experimental/logging"
 	"github.com/tetratelabs/wazero/internal/testing/require"
@@ -18,7 +17,7 @@ func Test_crypto(t *testing.T) {
 	loggingCtx := context.WithValue(testCtx, experimental.FunctionListenerFactoryKey{},
 		logging.NewHostLoggingListenerFactory(&log, logging.LogScopeRandom))
 
-	stdout, stderr, err := compileAndRun(loggingCtx, "crypto", wazero.NewModuleConfig())
+	stdout, stderr, err := compileAndRun(loggingCtx, "crypto", defaultConfig)
 
 	require.Zero(t, stderr)
 	require.EqualError(t, err, `module "" closed with exit_code(0)`)
diff --git a/internal/gojs/fs_test.go b/internal/gojs/fs_test.go
index 19fb2581..b5dae811 100644
--- a/internal/gojs/fs_test.go
+++ b/internal/gojs/fs_test.go
@@ -7,6 +7,7 @@ import (
 
 	"github.com/tetratelabs/wazero"
 	"github.com/tetratelabs/wazero/internal/fstest"
+	"github.com/tetratelabs/wazero/internal/gojs/config"
 	"github.com/tetratelabs/wazero/internal/platform"
 	"github.com/tetratelabs/wazero/internal/testing/require"
 )
@@ -14,7 +15,9 @@ import (
 func Test_fs(t *testing.T) {
 	t.Parallel()
 
-	stdout, stderr, err := compileAndRun(testCtx, "fs", wazero.NewModuleConfig().WithFS(testFS))
+	stdout, stderr, err := compileAndRun(testCtx, "fs", func(moduleConfig wazero.ModuleConfig) (wazero.ModuleConfig, *config.Config) {
+		return defaultConfig(moduleConfig.WithFS(testFS))
+	})
 
 	require.Zero(t, stderr)
 	require.EqualError(t, err, `module "" closed with exit_code(0)`)
@@ -44,7 +47,9 @@ func Test_testfs(t *testing.T) {
 	require.NoError(t, fstest.WriteTestFiles(testfsDir))
 
 	fsConfig := wazero.NewFSConfig().WithDirMount(tmpDir, "/")
-	stdout, stderr, err := compileAndRun(testCtx, "testfs", wazero.NewModuleConfig().WithFSConfig(fsConfig))
+	stdout, stderr, err := compileAndRun(testCtx, "testfs", func(moduleConfig wazero.ModuleConfig) (wazero.ModuleConfig, *config.Config) {
+		return defaultConfig(moduleConfig.WithFSConfig(fsConfig))
+	})
 
 	require.Zero(t, stderr)
 	require.EqualError(t, err, `module "" closed with exit_code(0)`)
@@ -59,7 +64,9 @@ func Test_writefs(t *testing.T) {
 	// test expects to write under /tmp
 	require.NoError(t, os.Mkdir(path.Join(tmpDir, "tmp"), 0o700))
 
-	stdout, stderr, err := compileAndRun(testCtx, "writefs", wazero.NewModuleConfig().WithFSConfig(fsConfig))
+	stdout, stderr, err := compileAndRun(testCtx, "writefs", func(moduleConfig wazero.ModuleConfig) (wazero.ModuleConfig, *config.Config) {
+		return defaultConfig(moduleConfig.WithFSConfig(fsConfig))
+	})
 
 	require.Zero(t, stderr)
 	require.EqualError(t, err, `module "" closed with exit_code(0)`)
diff --git a/internal/gojs/http_test.go b/internal/gojs/http_test.go
index 86bd2999..c8cc2dbe 100644
--- a/internal/gojs/http_test.go
+++ b/internal/gojs/http_test.go
@@ -8,7 +8,7 @@ import (
 	"testing"
 
 	"github.com/tetratelabs/wazero"
-	"github.com/tetratelabs/wazero/experimental/gojs"
+	"github.com/tetratelabs/wazero/internal/gojs/config"
 	"github.com/tetratelabs/wazero/internal/testing/require"
 )
 
@@ -21,7 +21,7 @@ func (f roundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) {
 func Test_http(t *testing.T) {
 	t.Parallel()
 
-	ctx := gojs.WithRoundTripper(testCtx, roundTripperFunc(func(req *http.Request) (*http.Response, error) {
+	rt := roundTripperFunc(func(req *http.Request) (*http.Response, error) {
 		if req.URL.Path == "/error" {
 			return nil, errors.New("error")
 		}
@@ -38,10 +38,13 @@ func Test_http(t *testing.T) {
 			Body:          io.NopCloser(strings.NewReader("abcdef")),
 			ContentLength: 6,
 		}, nil
-	}))
+	})
 
-	stdout, stderr, err := compileAndRun(ctx, "http", wazero.NewModuleConfig().
-		WithEnv("BASE_URL", "http://host"))
+	stdout, stderr, err := compileAndRun(testCtx, "http", func(moduleConfig wazero.ModuleConfig) (wazero.ModuleConfig, *config.Config) {
+		return moduleConfig.WithEnv("BASE_URL", "http://host"), &config.Config{
+			Rt: rt,
+		}
+	})
 
 	require.EqualError(t, err, `module "" closed with exit_code(0)`)
 	require.Zero(t, stderr)
diff --git a/internal/gojs/misc_test.go b/internal/gojs/misc_test.go
index ae1bfbf1..e3ca36a6 100644
--- a/internal/gojs/misc_test.go
+++ b/internal/gojs/misc_test.go
@@ -10,6 +10,7 @@ import (
 	"github.com/tetratelabs/wazero"
 	"github.com/tetratelabs/wazero/experimental"
 	"github.com/tetratelabs/wazero/experimental/logging"
+	"github.com/tetratelabs/wazero/internal/gojs/config"
 	"github.com/tetratelabs/wazero/internal/testing/require"
 )
 
@@ -20,7 +21,7 @@ func Test_exit(t *testing.T) {
 	loggingCtx := context.WithValue(testCtx, experimental.FunctionListenerFactoryKey{},
 		logging.NewHostLoggingListenerFactory(&log, logging.LogScopeProc))
 
-	stdout, stderr, err := compileAndRun(loggingCtx, "exit", wazero.NewModuleConfig())
+	stdout, stderr, err := compileAndRun(loggingCtx, "exit", defaultConfig)
 
 	require.EqualError(t, err, `module "" closed with exit_code(255)`)
 	require.Zero(t, stderr)
@@ -33,7 +34,7 @@ func Test_exit(t *testing.T) {
 func Test_goroutine(t *testing.T) {
 	t.Parallel()
 
-	stdout, stderr, err := compileAndRun(testCtx, "goroutine", wazero.NewModuleConfig())
+	stdout, stderr, err := compileAndRun(testCtx, "goroutine", defaultConfig)
 
 	require.EqualError(t, err, `module "" closed with exit_code(0)`)
 	require.Zero(t, stderr)
@@ -49,7 +50,7 @@ func Test_mem(t *testing.T) {
 	loggingCtx := context.WithValue(testCtx, experimental.FunctionListenerFactoryKey{},
 		logging.NewHostLoggingListenerFactory(&log, logging.LogScopeMemory))
 
-	stdout, stderr, err := compileAndRun(loggingCtx, "mem", wazero.NewModuleConfig())
+	stdout, stderr, err := compileAndRun(loggingCtx, "mem", defaultConfig)
 
 	require.EqualError(t, err, `module "" closed with exit_code(0)`)
 	require.Zero(t, stderr)
@@ -65,8 +66,9 @@ func Test_stdio(t *testing.T) {
 	t.Parallel()
 
 	input := "stdin\n"
-	stdout, stderr, err := compileAndRun(testCtx, "stdio", wazero.NewModuleConfig().
-		WithStdin(strings.NewReader(input)))
+	stdout, stderr, err := compileAndRun(testCtx, "stdio", func(moduleConfig wazero.ModuleConfig) (wazero.ModuleConfig, *config.Config) {
+		return defaultConfig(moduleConfig.WithStdin(strings.NewReader(input)))
+	})
 
 	require.Equal(t, "stderr 6\n", stderr)
 	require.EqualError(t, err, `module "" closed with exit_code(0)`)
@@ -83,8 +85,9 @@ func Test_stdio_large(t *testing.T) {
 
 	size := 2 * 1024 * 1024 // 2MB
 	input := make([]byte, size)
-	stdout, stderr, err := compileAndRun(loggingCtx, "stdio", wazero.NewModuleConfig().
-		WithStdin(bytes.NewReader(input)))
+	stdout, stderr, err := compileAndRun(loggingCtx, "stdio", func(moduleConfig wazero.ModuleConfig) (wazero.ModuleConfig, *config.Config) {
+		return defaultConfig(moduleConfig.WithStdin(bytes.NewReader(input)))
+	})
 
 	require.EqualError(t, err, `module "" closed with exit_code(0)`)
 	require.Equal(t, fmt.Sprintf("stderr %d\n", size), stderr)
@@ -102,7 +105,7 @@ func Test_stdio_large(t *testing.T) {
 func Test_gc(t *testing.T) {
 	t.Parallel()
 
-	stdout, stderr, err := compileAndRun(testCtx, "gc", wazero.NewModuleConfig())
+	stdout, stderr, err := compileAndRun(testCtx, "gc", defaultConfig)
 
 	require.EqualError(t, err, `module "" closed with exit_code(0)`)
 	require.Equal(t, "", stderr)
diff --git a/internal/gojs/syscall_test.go b/internal/gojs/syscall_test.go
index 19d01738..e2b81df7 100644
--- a/internal/gojs/syscall_test.go
+++ b/internal/gojs/syscall_test.go
@@ -3,14 +3,13 @@ package gojs_test
 import (
 	"testing"
 
-	"github.com/tetratelabs/wazero"
 	"github.com/tetratelabs/wazero/internal/testing/require"
 )
 
 func Test_syscall(t *testing.T) {
 	t.Parallel()
 
-	stdout, stderr, err := compileAndRun(testCtx, "syscall", wazero.NewModuleConfig())
+	stdout, stderr, err := compileAndRun(testCtx, "syscall", defaultConfig)
 
 	require.EqualError(t, err, `module "" closed with exit_code(0)`)
 	require.Zero(t, stderr)
diff --git a/internal/gojs/time_test.go b/internal/gojs/time_test.go
index be7fec58..3e83fbca 100644
--- a/internal/gojs/time_test.go
+++ b/internal/gojs/time_test.go
@@ -5,7 +5,6 @@ import (
 	"context"
 	"testing"
 
-	"github.com/tetratelabs/wazero"
 	"github.com/tetratelabs/wazero/experimental"
 	"github.com/tetratelabs/wazero/experimental/logging"
 	"github.com/tetratelabs/wazero/internal/testing/require"
@@ -18,7 +17,7 @@ func Test_time(t *testing.T) {
 	loggingCtx := context.WithValue(testCtx, experimental.FunctionListenerFactoryKey{},
 		logging.NewHostLoggingListenerFactory(&log, logging.LogScopeClock))
 
-	stdout, stderr, err := compileAndRun(loggingCtx, "time", wazero.NewModuleConfig())
+	stdout, stderr, err := compileAndRun(loggingCtx, "time", defaultConfig)
 
 	require.EqualError(t, err, `module "" closed with exit_code(0)`)
 	require.Zero(t, stderr)
diff --git a/internal/integration_test/bench/hostfunc_bench_test.go b/internal/integration_test/bench/hostfunc_bench_test.go
index c8729c2d..c01779a7 100644
--- a/internal/integration_test/bench/hostfunc_bench_test.go
+++ b/internal/integration_test/bench/hostfunc_bench_test.go
@@ -22,9 +22,6 @@ const (
 	// callGoReflectHostName is the name of exported function which calls the
 	// Go-implemented host function defined in reflection.
 	callGoReflectHostName = "call_go_reflect_host"
-	// callWasmHostName is the name of exported function which calls the
-	// Wasm-implemented host function.
-	callWasmHostName = "call_wasm_host"
 )
 
 // BenchmarkHostFunctionCall measures the cost of host function calls whose target functions are either
@@ -45,7 +42,7 @@ func BenchmarkHostFunctionCall(b *testing.B) {
 
 	binary.LittleEndian.PutUint32(m.Memory.Buffer[offset:], math.Float32bits(val))
 
-	for _, fn := range []string{callGoReflectHostName, callGoHostName, callWasmHostName} {
+	for _, fn := range []string{callGoReflectHostName, callGoHostName} {
 		fn := fn
 
 		b.Run(fn, func(b *testing.B) {
@@ -77,16 +74,12 @@ func TestBenchmarkFunctionCall(t *testing.T) {
 		require.NoError(t, err)
 	})
 
-	callWasmHost, err := getCallEngine(m, callWasmHostName)
-	require.NoError(t, err)
-
 	callGoHost, err := getCallEngine(m, callGoHostName)
 	require.NoError(t, err)
 
 	callGoReflectHost, err := getCallEngine(m, callGoReflectHostName)
 	require.NoError(t, err)
 
-	require.NotNil(t, callWasmHost)
 	require.NotNil(t, callGoHost)
 	require.NotNil(t, callGoReflectHost)
 
@@ -107,7 +100,6 @@ func TestBenchmarkFunctionCall(t *testing.T) {
 	}{
 		{name: "go", ce: callGoHost},
 		{name: "go-reflect", ce: callGoReflectHost},
-		{name: "wasm", ce: callWasmHost},
 	} {
 		f := f
 		t.Run(f.name, func(t *testing.T) {
@@ -145,10 +137,9 @@ func setupHostCallBench(requireNoError func(error)) *wasm.ModuleInstance {
 	// Build the host module.
 	hostModule := &wasm.Module{
 		TypeSection:     []*wasm.FunctionType{ft},
-		FunctionSection: []wasm.Index{0, 0, 0},
+		FunctionSection: []wasm.Index{0, 0},
 		CodeSection: []*wasm.Code{
 			{
-				IsHostFunction: true,
 				GoFunc: api.GoModuleFunc(func(_ context.Context, mod api.Module, stack []uint64) {
 					ret, ok := mod.Memory().ReadUint32Le(uint32(stack[0]))
 					if !ok {
@@ -166,20 +157,10 @@ func setupHostCallBench(requireNoError func(error)) *wasm.ModuleInstance {
 					return math.Float32frombits(ret)
 				},
 			),
-			{
-				IsHostFunction: true,
-				Body: []byte{
-					wasm.OpcodeLocalGet, 0,
-					wasm.OpcodeI32Load, 0x2, 0x0, // offset = 0
-					wasm.OpcodeF32ReinterpretI32,
-					wasm.OpcodeEnd,
-				},
-			},
 		},
 		ExportSection: []wasm.Export{
 			{Name: "go", Type: wasm.ExternTypeFunc, Index: 0},
 			{Name: "go-reflect", Type: wasm.ExternTypeFunc, Index: 1},
-			{Name: "wasm", Type: wasm.ExternTypeFunc, Index: 2},
 		},
 		ID: wasm.ModuleID{1, 2, 3, 4, 5},
 	}
@@ -212,7 +193,6 @@ func setupHostCallBench(requireNoError func(error)) *wasm.ModuleInstance {
 		ExportSection: []wasm.Export{
 			{Name: callGoHostName, Type: wasm.ExternTypeFunc, Index: 3},
 			{Name: callGoReflectHostName, Type: wasm.ExternTypeFunc, Index: 4},
-			{Name: callWasmHostName, Type: wasm.ExternTypeFunc, Index: 5},
 		},
 		CodeSection: []*wasm.Code{
 			{Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeCall, 0, wasm.OpcodeEnd}}, // Calling the index 0 = host.go.
diff --git a/internal/testing/enginetest/enginetest.go b/internal/testing/enginetest/enginetest.go
index 83e8c84a..d170027f 100644
--- a/internal/testing/enginetest/enginetest.go
+++ b/internal/testing/enginetest/enginetest.go
@@ -311,7 +311,6 @@ func runTestModuleEngine_Call_HostFn_Mem(t *testing.T, et EngineTester, readMem
 func RunTestModuleEngine_Call_HostFn(t *testing.T, et EngineTester) {
 	t.Run("wasm", func(t *testing.T) {
 		runTestModuleEngine_Call_HostFn(t, et, hostDivByWasm)
-		runTestModuleEngine_Call_HostFn_Mem(t, et, hostReadMemWasm)
 	})
 	t.Run("go", func(t *testing.T) {
 		runTestModuleEngine_Call_HostFn(t, et, hostDivByGo)
@@ -617,7 +616,7 @@ var hostDivByGo = wasm.MustParseGoReflectFuncCode(divByGo)
 // (func (export "div_by.wasm") (param i32) (result i32) (i32.div_u (i32.const 1) (local.get 0)))
 var (
 	divByWasm     = []byte{wasm.OpcodeI32Const, 1, wasm.OpcodeLocalGet, 0, wasm.OpcodeI32DivU, wasm.OpcodeEnd}
-	hostDivByWasm = &wasm.Code{IsHostFunction: true, Body: divByWasm}
+	hostDivByWasm = &wasm.Code{Body: divByWasm}
 )
 
 const (
@@ -635,12 +634,6 @@ func readMemGo(_ context.Context, m api.Module) uint64 {
 
 var hostReadMemGo = wasm.MustParseGoReflectFuncCode(readMemGo)
 
-// (func (export "wasm_read_mem") (result i64) i32.const 0 i64.load)
-var (
-	readMemWasm     = []byte{wasm.OpcodeI32Const, 0, wasm.OpcodeI64Load, 0x3, 0x0, wasm.OpcodeEnd}
-	hostReadMemWasm = &wasm.Code{IsHostFunction: true, Body: readMemWasm}
-)
-
 func setupCallTests(t *testing.T, e wasm.Engine, divBy *wasm.Code, fnlf experimental.FunctionListenerFactory) (*wasm.ModuleInstance, *wasm.ModuleInstance, *wasm.ModuleInstance, func()) {
 	ft := &wasm.FunctionType{Params: []wasm.ValueType{i32}, Results: []wasm.ValueType{i32}, ParamNumInUint64: 1, ResultNumInUint64: 1}
 
diff --git a/internal/wasm/host_test.go b/internal/wasm/host_test.go
index c675d692..dc86a5ac 100644
--- a/internal/wasm/host_test.go
+++ b/internal/wasm/host_test.go
@@ -142,7 +142,6 @@ func requireHostModuleEquals(t *testing.T, expected, actual *Module) {
 	require.Equal(t, len(expected.CodeSection), len(actual.CodeSection))
 	for i, c := range expected.CodeSection {
 		actualCode := actual.CodeSection[i]
-		require.True(t, actualCode.IsHostFunction)
 		require.Equal(t, c.GoFunc, actualCode.GoFunc)
 
 		// Not wasm
diff --git a/internal/wazeroir/compiler_test.go b/internal/wazeroir/compiler_test.go
index 7b4cc4f6..2d3f1804 100644
--- a/internal/wazeroir/compiler_test.go
+++ b/internal/wazeroir/compiler_test.go
@@ -63,10 +63,9 @@ func TestCompile(t *testing.T) {
 			module: &wasm.Module{
 				TypeSection:     []*wasm.FunctionType{v_v},
 				FunctionSection: []wasm.Index{0},
-				CodeSection:     []*wasm.Code{{IsHostFunction: true, Body: []byte{wasm.OpcodeEnd}}},
+				CodeSection:     []*wasm.Code{{Body: []byte{wasm.OpcodeEnd}}},
 			},
 			expected: &CompilationResult{
-				IsHostFunction: true,
 				Operations: []Operation{ // begin with params: []
 					OperationBr{Target: Label{Kind: LabelKindReturn}}, // return!
 				},
@@ -84,7 +83,7 @@ func TestCompile(t *testing.T) {
 				FunctionSection: []wasm.Index{0},
 				CodeSection:     []*wasm.Code{wasm.MustParseGoReflectFuncCode(func() {})},
 			},
-			expected: &CompilationResult{IsHostFunction: true},
+			expected: &CompilationResult{},
 		},
 		{
 			name: "host go context.Context api.Module uses memory",
@@ -93,7 +92,7 @@ func TestCompile(t *testing.T) {
 				FunctionSection: []wasm.Index{0},
 				CodeSection:     []*wasm.Code{wasm.MustParseGoReflectFuncCode(func(context.Context, api.Module) {})},
 			},
-			expected: &CompilationResult{IsHostFunction: true, UsesMemory: true},
+			expected: &CompilationResult{UsesMemory: true},
 		},
 		{
 			name: "identity",
@@ -157,7 +156,7 @@ func TestCompile(t *testing.T) {
 			module: &wasm.Module{
 				TypeSection:     []*wasm.FunctionType{v_v},
 				FunctionSection: []wasm.Index{0},
-				CodeSection: []*wasm.Code{{IsHostFunction: true, Body: []byte{
+				CodeSection: []*wasm.Code{{Body: []byte{
 					wasm.OpcodeI32Const, 8, // memory offset to load
 					wasm.OpcodeI32Load, 0x2, 0x0, // load alignment=2 (natural alignment) staticOffset=0
 					wasm.OpcodeDrop,
@@ -165,7 +164,6 @@ func TestCompile(t *testing.T) {
 				}}},
 			},
 			expected: &CompilationResult{
-				IsHostFunction: true,
 				Operations: []Operation{ // begin with params: []
 					OperationConstI32{Value: 8}, // [8]
 					OperationLoad{Type: UnsignedTypeI32, Arg: MemoryArg{Alignment: 2, Offset: 0}}, // [x]
@@ -231,7 +229,6 @@ func TestCompile(t *testing.T) {
 			fn := res[0]
 			if fn.GoFunc != nil { // can't compare functions
 				// Special case because reflect.Value can't be compared with Equals
-				require.True(t, fn.IsHostFunction)
 				require.Equal(t, tc.expected.UsesMemory, fn.UsesMemory)
 				require.Equal(t, &tc.module.CodeSection[0].GoFunc, &fn.GoFunc)
 			} else {