Renames ModuleBuilder to HostModuleBuilder and drops memory and globals (#812)

We at one point considered making `ModuleBuilder` create complete
WebAssembly binaries. However, we recently spun out
[wabin](https://github.com/tetratelabs/wabin), which allows this.

Meanwhile, the features in `ModuleBuilder` were confusing and misused.
For example, the only two cases memory was exported on GitHub were done
by accident. This is because host functions act on the guest's memory,
not their own.

Hence, this removes memory and globals from host side definitions, and
renames the type to HostModuleBuilder to clarify this is not ever going
to be used to construct normal Wasm binaries.

Most importantly, this simplifies the API and reduces a lot of code. It
is important to make changes like this, particularly deleting any
experimental things that didn't end up useful.

Signed-off-by: Adrian Cole <adrian@tetrate.io>
Co-authored-by: Anuraag Agrawal <anuraaga@gmail.com>
This commit is contained in:
Crypt Keeper
2022-09-28 14:42:14 +08:00
committed by GitHub
parent 06e2cd3757
commit 429334cf98
38 changed files with 196 additions and 703 deletions

View File

@@ -411,7 +411,7 @@ ways acts similar to a process with a `main` function.
To capture "hello world" written to the console (stdout a.k.a. file descriptor 1) in `exec.Cmd`, you would set the To capture "hello world" written to the console (stdout a.k.a. file descriptor 1) in `exec.Cmd`, you would set the
`Stdout` field accordingly, perhaps to a buffer. In WebAssembly 1.0 (20191205), the only way to perform something like `Stdout` field accordingly, perhaps to a buffer. In WebAssembly 1.0 (20191205), the only way to perform something like
this is via a host function (ex `ModuleBuilder.ExportFunction`) and internally copy memory corresponding to that string this is via a host function (ex `HostModuleBuilder.ExportFunction`) and internally copy memory corresponding to that string
to a buffer. to a buffer.
WASI implements system interfaces with host functions. Concretely, to write to console, a WASI command `Module` imports WASI implements system interfaces with host functions. Concretely, to write to console, a WASI command `Module` imports

View File

@@ -38,7 +38,7 @@ For example, you can grant WebAssembly code access to your console by exporting
a function written in Go. The below function can be imported into standard a function written in Go. The below function can be imported into standard
WebAssembly as the module "env" and the function name "log_i32". WebAssembly as the module "env" and the function name "log_i32".
```go ```go
_, err := r.NewModuleBuilder("env"). _, err := r.NewHostModuleBuilder("env").
ExportFunction("log_i32", func(v uint32) { ExportFunction("log_i32", func(v uint32) {
fmt.Println("log_i32 >>", v) fmt.Println("log_i32 >>", v)
}). }).

View File

@@ -95,7 +95,7 @@ const (
// (func (import "env" "f") (param externref) (result externref)) // (func (import "env" "f") (param externref) (result externref))
// //
// This can be defined in Go as: // This can be defined in Go as:
// r.NewModuleBuilder("env").ExportFunctions(map[string]interface{}{ // r.NewHostModuleBuilder("env").ExportFunctions(map[string]interface{}{
// "f": func(externref uintptr) (resultExternRef uintptr) { return }, // "f": func(externref uintptr) (resultExternRef uintptr) { return },
// }) // })
// //
@@ -229,7 +229,7 @@ type FunctionDefinition interface {
ExportNames() []string ExportNames() []string
// GoFunc is present when the function was implemented by the embedder // GoFunc is present when the function was implemented by the embedder
// (ex via wazero.ModuleBuilder) instead of a wasm binary. // (ex via wazero.HostModuleBuilder) instead of a wasm binary.
// //
// This function can be non-deterministic or cause side effects. It also // This function can be non-deterministic or cause side effects. It also
// has special properties not defined in the WebAssembly Core // has special properties not defined in the WebAssembly Core

View File

@@ -2,15 +2,16 @@ package wazero
import ( import (
"context" "context"
"fmt"
"github.com/tetratelabs/wazero/api" "github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/u64"
"github.com/tetratelabs/wazero/internal/wasm" "github.com/tetratelabs/wazero/internal/wasm"
) )
// ModuleBuilder is a way to define a WebAssembly Module in Go. // HostModuleBuilder is a way to define host functions (in Go), so that a
// WebAssembly binary (ex. %.wasm file) can import and use them.
//
// Specifically, this implements the host side of an Application Binary
// Interface (ABI) like WASI or AssemblyScript.
// //
// Ex. Below defines and instantiates a module named "env" with one function: // Ex. Below defines and instantiates a module named "env" with one function:
// //
@@ -21,30 +22,47 @@ import (
// hello := func() { // hello := func() {
// fmt.Fprintln(stdout, "hello!") // fmt.Fprintln(stdout, "hello!")
// } // }
// env, _ := r.NewModuleBuilder("env"). // env, _ := r.NewHostModuleBuilder("env").
// ExportFunction("hello", hello). // ExportFunction("hello", hello).
// Instantiate(ctx, r) // Instantiate(ctx, r)
// //
// If the same module may be instantiated multiple times, it is more efficient // If the same module may be instantiated multiple times, it is more efficient
// to separate steps. Ex. // to separate steps. Ex.
// //
// compiled, _ := r.NewModuleBuilder("env"). // compiled, _ := r.NewHostModuleBuilder("env").
// ExportFunction("get_random_string", getRandomString). // ExportFunction("get_random_string", getRandomString).
// Compile(ctx, wazero.NewCompileConfig()) // Compile(ctx)
// //
// env1, _ := r.InstantiateModule(ctx, compiled, wazero.NewModuleConfig().WithName("env.1")) // env1, _ := r.InstantiateModule(ctx, compiled, wazero.NewModuleConfig().WithName("env.1"))
// //
// env2, _ := r.InstantiateModule(ctx, compiled, wazero.NewModuleConfig().WithName("env.2")) // env2, _ := r.InstantiateModule(ctx, compiled, wazero.NewModuleConfig().WithName("env.2"))
// //
// # Memory
//
// All host functions act on the importing api.Module, including any memory
// exported in its binary (%.wasm file). If you are reading or writing memory,
// it is sand-boxed Wasm memory defined by the guest.
//
// Below, `m` is the importing module, defined in Wasm. `fn` is a host function
// added via ExportFunction. This means that `x` was read from memory defined
// in Wasm, not arbitrary memory in the process.
//
// fn := func(ctx context.Context, m api.Module, offset uint32) uint32 {
// x, _ := m.Memory().ReadUint32Le(ctx, offset)
// return x
// }
//
// See ExportFunction for valid host function signatures and other details.
//
// # Notes // # Notes
// //
// - ModuleBuilder is mutable: each method returns the same instance for // - HostModuleBuilder is mutable: each method returns the same instance for
// chaining. // chaining.
// - methods do not return errors, to allow chaining. Any validation errors // - methods do not return errors, to allow chaining. Any validation errors
// are deferred until Compile. // are deferred until Compile.
// - Insertion order is not retained. Anything defined by this builder is // - Insertion order is not retained. Anything defined by this builder is
// sorted lexicographically on Compile. // sorted lexicographically on Compile.
type ModuleBuilder interface { type HostModuleBuilder interface {
// Note: until golang/go#5860, we can't use example tests to embed code in interface godocs. // Note: until golang/go#5860, we can't use example tests to embed code in interface godocs.
// ExportFunction adds a function written in Go, which a WebAssembly module can import. // ExportFunction adds a function written in Go, which a WebAssembly module can import.
@@ -66,7 +84,7 @@ type ModuleBuilder interface {
// builder.ExportFunction("abort", env.abort, "~lib/builtins/abort", // builder.ExportFunction("abort", env.abort, "~lib/builtins/abort",
// "message", "fileName", "lineNumber", "columnNumber") // "message", "fileName", "lineNumber", "columnNumber")
// //
// Valid Signature // # Valid Signature
// //
// Noting a context exception described later, all parameters or result // Noting a context exception described later, all parameters or result
// types must match WebAssembly 1.0 (20191205) value types. This means // types must match WebAssembly 1.0 (20191205) value types. This means
@@ -108,90 +126,15 @@ type ModuleBuilder interface {
// --snip-- // --snip--
// //
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#host-functions%E2%91%A2 // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#host-functions%E2%91%A2
ExportFunction(exportName string, goFunc interface{}, names ...string) ModuleBuilder ExportFunction(exportName string, goFunc interface{}, names ...string) HostModuleBuilder
// ExportFunctions is a convenience that calls ExportFunction for each key/value in the provided map. // ExportFunctions is a convenience that calls ExportFunction for each key/value in the provided map.
ExportFunctions(nameToGoFunc map[string]interface{}) ModuleBuilder ExportFunctions(nameToGoFunc map[string]interface{}) HostModuleBuilder
// ExportMemory adds linear memory, which a WebAssembly module can import and become available via api.Memory.
// If a memory is already exported with the same name, this overwrites it.
//
// # Parameters
//
// - name - the name to export. Ex "memory" for wasi_snapshot_preview1.ModuleSnapshotPreview1
// - minPages - the possibly zero initial size in pages (65536 bytes per page).
//
// For example, the WebAssembly 1.0 Text Format below is the equivalent of this builder method:
// // (memory (export "memory") 1)
// builder.ExportMemory(1)
//
// # Notes
//
// - This is allowed to grow to (4GiB) limited by api.MemorySizer. To bound it, use ExportMemoryWithMax.
// - Version 1.0 (20191205) of the WebAssembly spec allows at most one memory per module.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-section%E2%91%A0
ExportMemory(name string, minPages uint32) ModuleBuilder
// ExportMemoryWithMax is like ExportMemory, but can prevent overuse of memory.
//
// For example, the WebAssembly 1.0 Text Format below is the equivalent of this builder method:
// // (memory (export "memory") 1 1)
// builder.ExportMemoryWithMax(1, 1)
//
// Note: api.MemorySizer determines the capacity.
ExportMemoryWithMax(name string, minPages, maxPages uint32) ModuleBuilder
// ExportGlobalI32 exports a global constant of type api.ValueTypeI32.
// If a global is already exported with the same name, this overwrites it.
//
// For example, the WebAssembly 1.0 Text Format below is the equivalent of this builder method:
// // (global (export "canvas_width") i32 (i32.const 1024))
// builder.ExportGlobalI32("canvas_width", 1024)
//
// Note: The maximum value of v is math.MaxInt32 to match constraints of initialization in binary format.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#value-types%E2%91%A0 and
// https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-globaltype
ExportGlobalI32(name string, v int32) ModuleBuilder
// ExportGlobalI64 exports a global constant of type api.ValueTypeI64.
// If a global is already exported with the same name, this overwrites it.
//
// For example, the WebAssembly 1.0 Text Format below is the equivalent of this builder method:
// // (global (export "start_epoch") i64 (i64.const 1620216263544))
// builder.ExportGlobalI64("start_epoch", 1620216263544)
//
// Note: The maximum value of v is math.MaxInt64 to match constraints of initialization in binary format.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#value-types%E2%91%A0 and
// https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-globaltype
ExportGlobalI64(name string, v int64) ModuleBuilder
// ExportGlobalF32 exports a global constant of type api.ValueTypeF32.
// If a global is already exported with the same name, this overwrites it.
//
// For example, the WebAssembly 1.0 Text Format below is the equivalent of this builder method:
// // (global (export "math/pi") f32 (f32.const 3.1415926536))
// builder.ExportGlobalF32("math/pi", 3.1415926536)
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-globaltype
ExportGlobalF32(name string, v float32) ModuleBuilder
// ExportGlobalF64 exports a global constant of type api.ValueTypeF64.
// If a global is already exported with the same name, this overwrites it.
//
// For example, the WebAssembly 1.0 Text Format below is the equivalent of this builder method:
// // (global (export "math/pi") f64 (f64.const 3.14159265358979323846264338327950288419716939937510582097494459))
// builder.ExportGlobalF64("math/pi", 3.14159265358979323846264338327950288419716939937510582097494459)
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-globaltype
ExportGlobalF64(name string, v float64) ModuleBuilder
// Compile returns a CompiledModule that can instantiated in any namespace (Namespace). // Compile returns a CompiledModule that can instantiated in any namespace (Namespace).
// //
// Note: Closing the Namespace has the same effect as closing the result. // Note: Closing the Namespace has the same effect as closing the result.
Compile(context.Context, CompileConfig) (CompiledModule, error) Compile(context.Context) (CompiledModule, error)
// Instantiate is a convenience that calls Compile, then Namespace.InstantiateModule. // Instantiate is a convenience that calls Compile, then Namespace.InstantiateModule.
// //
@@ -204,7 +147,7 @@ type ModuleBuilder interface {
// hello := func() { // hello := func() {
// fmt.Fprintln(stdout, "hello!") // fmt.Fprintln(stdout, "hello!")
// } // }
// env, _ := r.NewModuleBuilder("env"). // env, _ := r.NewHostModuleBuilder("env").
// ExportFunction("hello", hello). // ExportFunction("hello", hello).
// Instantiate(ctx, r) // Instantiate(ctx, r)
// //
@@ -216,30 +159,26 @@ type ModuleBuilder interface {
Instantiate(context.Context, Namespace) (api.Module, error) Instantiate(context.Context, Namespace) (api.Module, error)
} }
// moduleBuilder implements ModuleBuilder // hostModuleBuilder implements HostModuleBuilder
type moduleBuilder struct { type hostModuleBuilder struct {
r *runtime r *runtime
moduleName string moduleName string
nameToGoFunc map[string]interface{} nameToGoFunc map[string]interface{}
funcToNames map[string][]string funcToNames map[string][]string
nameToMemory map[string]*wasm.Memory
nameToGlobal map[string]*wasm.Global
} }
// NewModuleBuilder implements Runtime.NewModuleBuilder // NewHostModuleBuilder implements Runtime.NewHostModuleBuilder
func (r *runtime) NewModuleBuilder(moduleName string) ModuleBuilder { func (r *runtime) NewHostModuleBuilder(moduleName string) HostModuleBuilder {
return &moduleBuilder{ return &hostModuleBuilder{
r: r, r: r,
moduleName: moduleName, moduleName: moduleName,
nameToGoFunc: map[string]interface{}{}, nameToGoFunc: map[string]interface{}{},
funcToNames: map[string][]string{}, funcToNames: map[string][]string{},
nameToMemory: map[string]*wasm.Memory{},
nameToGlobal: map[string]*wasm.Global{},
} }
} }
// ExportFunction implements ModuleBuilder.ExportFunction // ExportFunction implements HostModuleBuilder.ExportFunction
func (b *moduleBuilder) ExportFunction(exportName string, goFunc interface{}, names ...string) ModuleBuilder { func (b *hostModuleBuilder) ExportFunction(exportName string, goFunc interface{}, names ...string) HostModuleBuilder {
b.nameToGoFunc[exportName] = goFunc b.nameToGoFunc[exportName] = goFunc
if len(names) > 0 { if len(names) > 0 {
b.funcToNames[exportName] = names b.funcToNames[exportName] = names
@@ -247,81 +186,17 @@ func (b *moduleBuilder) ExportFunction(exportName string, goFunc interface{}, na
return b return b
} }
// ExportFunctions implements ModuleBuilder.ExportFunctions // ExportFunctions implements HostModuleBuilder.ExportFunctions
func (b *moduleBuilder) ExportFunctions(nameToGoFunc map[string]interface{}) ModuleBuilder { func (b *hostModuleBuilder) ExportFunctions(nameToGoFunc map[string]interface{}) HostModuleBuilder {
for k, v := range nameToGoFunc { for k, v := range nameToGoFunc {
b.ExportFunction(k, v) b.ExportFunction(k, v)
} }
return b return b
} }
// ExportMemory implements ModuleBuilder.ExportMemory // Compile implements HostModuleBuilder.Compile
func (b *moduleBuilder) ExportMemory(name string, minPages uint32) ModuleBuilder { func (b *hostModuleBuilder) Compile(ctx context.Context) (CompiledModule, error) {
b.nameToMemory[name] = &wasm.Memory{Min: minPages} module, err := wasm.NewHostModule(b.moduleName, b.nameToGoFunc, b.funcToNames, b.r.enabledFeatures)
return b
}
// ExportMemoryWithMax implements ModuleBuilder.ExportMemoryWithMax
func (b *moduleBuilder) ExportMemoryWithMax(name string, minPages, maxPages uint32) ModuleBuilder {
b.nameToMemory[name] = &wasm.Memory{Min: minPages, Max: maxPages, IsMaxEncoded: true}
return b
}
// ExportGlobalI32 implements ModuleBuilder.ExportGlobalI32
func (b *moduleBuilder) ExportGlobalI32(name string, v int32) ModuleBuilder {
b.nameToGlobal[name] = &wasm.Global{
Type: &wasm.GlobalType{ValType: wasm.ValueTypeI32},
// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: leb128.EncodeInt32(v)},
}
return b
}
// ExportGlobalI64 implements ModuleBuilder.ExportGlobalI64
func (b *moduleBuilder) ExportGlobalI64(name string, v int64) ModuleBuilder {
b.nameToGlobal[name] = &wasm.Global{
Type: &wasm.GlobalType{ValType: wasm.ValueTypeI64},
// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI64Const, Data: leb128.EncodeInt64(v)},
}
return b
}
// ExportGlobalF32 implements ModuleBuilder.ExportGlobalF32
func (b *moduleBuilder) ExportGlobalF32(name string, v float32) ModuleBuilder {
b.nameToGlobal[name] = &wasm.Global{
Type: &wasm.GlobalType{ValType: wasm.ValueTypeF32},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeF32Const, Data: u64.LeBytes(api.EncodeF32(v))},
}
return b
}
// ExportGlobalF64 implements ModuleBuilder.ExportGlobalF64
func (b *moduleBuilder) ExportGlobalF64(name string, v float64) ModuleBuilder {
b.nameToGlobal[name] = &wasm.Global{
Type: &wasm.GlobalType{ValType: wasm.ValueTypeF64},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeF64Const, Data: u64.LeBytes(api.EncodeF64(v))},
}
return b
}
// Compile implements ModuleBuilder.Compile
func (b *moduleBuilder) Compile(ctx context.Context, cConfig CompileConfig) (CompiledModule, error) {
config := cConfig.(*compileConfig)
// Verify the maximum limit here, so we don't have to pass it to wasm.NewHostModule
for name, mem := range b.nameToMemory {
var maxP *uint32
if mem.IsMaxEncoded {
maxP = &mem.Max
}
mem.Min, mem.Cap, mem.Max = config.memorySizer(mem.Min, maxP)
if err := mem.Validate(); err != nil {
return nil, fmt.Errorf("memory[%s] %v", name, err)
}
}
module, err := wasm.NewHostModule(b.moduleName, b.nameToGoFunc, b.funcToNames, b.nameToMemory, b.nameToGlobal, b.r.enabledFeatures)
if err != nil { if err != nil {
return nil, err return nil, err
} else if err = module.Validate(b.r.enabledFeatures); err != nil { } else if err = module.Validate(b.r.enabledFeatures); err != nil {
@@ -340,9 +215,9 @@ func (b *moduleBuilder) Compile(ctx context.Context, cConfig CompileConfig) (Com
return c, nil return c, nil
} }
// Instantiate implements ModuleBuilder.Instantiate // Instantiate implements HostModuleBuilder.Instantiate
func (b *moduleBuilder) Instantiate(ctx context.Context, ns Namespace) (api.Module, error) { func (b *hostModuleBuilder) Instantiate(ctx context.Context, ns Namespace) (api.Module, error) {
if compiled, err := b.Compile(ctx, NewCompileConfig()); err != nil { if compiled, err := b.Compile(ctx); err != nil {
return nil, err return nil, err
} else { } else {
compiled.(*compiledModule).closeWithModule = true compiled.(*compiledModule).closeWithModule = true

View File

@@ -1,18 +1,15 @@
package wazero package wazero
import ( import (
"math"
"testing" "testing"
"github.com/tetratelabs/wazero/api" "github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/testing/require" "github.com/tetratelabs/wazero/internal/testing/require"
"github.com/tetratelabs/wazero/internal/u64"
"github.com/tetratelabs/wazero/internal/wasm" "github.com/tetratelabs/wazero/internal/wasm"
) )
// TestNewModuleBuilder_Compile only covers a few scenarios to avoid duplicating tests in internal/wasm/host_test.go // TestNewHostModuleBuilder_Compile only covers a few scenarios to avoid duplicating tests in internal/wasm/host_test.go
func TestNewModuleBuilder_Compile(t *testing.T) { func TestNewHostModuleBuilder_Compile(t *testing.T) {
i32, i64 := api.ValueTypeI32, api.ValueTypeI64 i32, i64 := api.ValueTypeI32, api.ValueTypeI64
uint32_uint32 := func(uint32) uint32 { uint32_uint32 := func(uint32) uint32 {
@@ -24,27 +21,27 @@ func TestNewModuleBuilder_Compile(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
input func(Runtime) ModuleBuilder input func(Runtime) HostModuleBuilder
expected *wasm.Module expected *wasm.Module
}{ }{
{ {
name: "empty", name: "empty",
input: func(r Runtime) ModuleBuilder { input: func(r Runtime) HostModuleBuilder {
return r.NewModuleBuilder("") return r.NewHostModuleBuilder("")
}, },
expected: &wasm.Module{}, expected: &wasm.Module{},
}, },
{ {
name: "only name", name: "only name",
input: func(r Runtime) ModuleBuilder { input: func(r Runtime) HostModuleBuilder {
return r.NewModuleBuilder("env") return r.NewHostModuleBuilder("env")
}, },
expected: &wasm.Module{NameSection: &wasm.NameSection{ModuleName: "env"}}, expected: &wasm.Module{NameSection: &wasm.NameSection{ModuleName: "env"}},
}, },
{ {
name: "ExportFunction", name: "ExportFunction",
input: func(r Runtime) ModuleBuilder { input: func(r Runtime) HostModuleBuilder {
return r.NewModuleBuilder("").ExportFunction("1", uint32_uint32) return r.NewHostModuleBuilder("").ExportFunction("1", uint32_uint32)
}, },
expected: &wasm.Module{ expected: &wasm.Module{
TypeSection: []*wasm.FunctionType{ TypeSection: []*wasm.FunctionType{
@@ -62,8 +59,8 @@ func TestNewModuleBuilder_Compile(t *testing.T) {
}, },
{ {
name: "ExportFunction with names", name: "ExportFunction with names",
input: func(r Runtime) ModuleBuilder { input: func(r Runtime) HostModuleBuilder {
return r.NewModuleBuilder("").ExportFunction("1", uint32_uint32, "get", "x") return r.NewHostModuleBuilder("").ExportFunction("1", uint32_uint32, "get", "x")
}, },
expected: &wasm.Module{ expected: &wasm.Module{
TypeSection: []*wasm.FunctionType{ TypeSection: []*wasm.FunctionType{
@@ -82,8 +79,8 @@ func TestNewModuleBuilder_Compile(t *testing.T) {
}, },
{ {
name: "ExportFunction overwrites existing", name: "ExportFunction overwrites existing",
input: func(r Runtime) ModuleBuilder { input: func(r Runtime) HostModuleBuilder {
return r.NewModuleBuilder("").ExportFunction("1", uint32_uint32).ExportFunction("1", uint64_uint32) return r.NewHostModuleBuilder("").ExportFunction("1", uint32_uint32).ExportFunction("1", uint64_uint32)
}, },
expected: &wasm.Module{ expected: &wasm.Module{
TypeSection: []*wasm.FunctionType{ TypeSection: []*wasm.FunctionType{
@@ -101,9 +98,9 @@ func TestNewModuleBuilder_Compile(t *testing.T) {
}, },
{ {
name: "ExportFunction twice", name: "ExportFunction twice",
input: func(r Runtime) ModuleBuilder { input: func(r Runtime) HostModuleBuilder {
// Intentionally out of order // Intentionally out of order
return r.NewModuleBuilder("").ExportFunction("2", uint64_uint32).ExportFunction("1", uint32_uint32) return r.NewHostModuleBuilder("").ExportFunction("2", uint64_uint32).ExportFunction("1", uint32_uint32)
}, },
expected: &wasm.Module{ expected: &wasm.Module{
TypeSection: []*wasm.FunctionType{ TypeSection: []*wasm.FunctionType{
@@ -123,8 +120,8 @@ func TestNewModuleBuilder_Compile(t *testing.T) {
}, },
{ {
name: "ExportFunctions", name: "ExportFunctions",
input: func(r Runtime) ModuleBuilder { input: func(r Runtime) HostModuleBuilder {
return r.NewModuleBuilder("").ExportFunctions(map[string]interface{}{ return r.NewHostModuleBuilder("").ExportFunctions(map[string]interface{}{
"1": uint32_uint32, "1": uint32_uint32,
"2": uint64_uint32, "2": uint64_uint32,
}) })
@@ -147,8 +144,8 @@ func TestNewModuleBuilder_Compile(t *testing.T) {
}, },
{ {
name: "ExportFunctions overwrites", name: "ExportFunctions overwrites",
input: func(r Runtime) ModuleBuilder { input: func(r Runtime) HostModuleBuilder {
b := r.NewModuleBuilder("").ExportFunction("1", uint64_uint32) b := r.NewHostModuleBuilder("").ExportFunction("1", uint64_uint32)
return b.ExportFunctions(map[string]interface{}{ return b.ExportFunctions(map[string]interface{}{
"1": uint32_uint32, "1": uint32_uint32,
"2": uint64_uint32, "2": uint64_uint32,
@@ -170,198 +167,14 @@ func TestNewModuleBuilder_Compile(t *testing.T) {
}, },
}, },
}, },
{
name: "ExportMemory",
input: func(r Runtime) ModuleBuilder {
return r.NewModuleBuilder("").ExportMemory("memory", 1)
},
expected: &wasm.Module{
MemorySection: &wasm.Memory{Min: 1, Cap: 1, Max: wasm.MemoryLimitPages},
ExportSection: []*wasm.Export{
{Name: "memory", Type: wasm.ExternTypeMemory, Index: 0},
},
},
},
{
name: "ExportMemory overwrites",
input: func(r Runtime) ModuleBuilder {
return r.NewModuleBuilder("").ExportMemory("memory", 1).ExportMemory("memory", 2)
},
expected: &wasm.Module{
MemorySection: &wasm.Memory{Min: 2, Cap: 2, Max: wasm.MemoryLimitPages},
ExportSection: []*wasm.Export{
{Name: "memory", Type: wasm.ExternTypeMemory, Index: 0},
},
},
},
{
name: "ExportMemoryWithMax",
input: func(r Runtime) ModuleBuilder {
return r.NewModuleBuilder("").ExportMemoryWithMax("memory", 1, 1)
},
expected: &wasm.Module{
MemorySection: &wasm.Memory{Min: 1, Cap: 1, Max: 1, IsMaxEncoded: true},
ExportSection: []*wasm.Export{
{Name: "memory", Type: wasm.ExternTypeMemory, Index: 0},
},
},
},
{
name: "ExportMemoryWithMax overwrites",
input: func(r Runtime) ModuleBuilder {
return r.NewModuleBuilder("").ExportMemoryWithMax("memory", 1, 1).ExportMemoryWithMax("memory", 1, 2)
},
expected: &wasm.Module{
MemorySection: &wasm.Memory{Min: 1, Cap: 1, Max: 2, IsMaxEncoded: true},
ExportSection: []*wasm.Export{
{Name: "memory", Type: wasm.ExternTypeMemory, Index: 0},
},
},
},
{
name: "ExportGlobalI32",
input: func(r Runtime) ModuleBuilder {
return r.NewModuleBuilder("").ExportGlobalI32("canvas_width", 1024)
},
expected: &wasm.Module{
GlobalSection: []*wasm.Global{
{
Type: &wasm.GlobalType{ValType: wasm.ValueTypeI32},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: leb128.EncodeInt32(1024)},
},
},
ExportSection: []*wasm.Export{
{Name: "canvas_width", Type: wasm.ExternTypeGlobal, Index: 0},
},
},
},
{
name: "ExportGlobalI32 overwrites",
input: func(r Runtime) ModuleBuilder {
return r.NewModuleBuilder("").ExportGlobalI32("canvas_width", 1024).ExportGlobalI32("canvas_width", math.MaxInt32)
},
expected: &wasm.Module{
GlobalSection: []*wasm.Global{
{
Type: &wasm.GlobalType{ValType: wasm.ValueTypeI32},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: leb128.EncodeUint32(math.MaxInt32)},
},
},
ExportSection: []*wasm.Export{
{Name: "canvas_width", Type: wasm.ExternTypeGlobal, Index: 0},
},
},
},
{
name: "ExportGlobalI64",
input: func(r Runtime) ModuleBuilder {
return r.NewModuleBuilder("").ExportGlobalI64("start_epoch", 1620216263544)
},
expected: &wasm.Module{
GlobalSection: []*wasm.Global{
{
Type: &wasm.GlobalType{ValType: wasm.ValueTypeI64},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI64Const, Data: leb128.EncodeUint64(1620216263544)},
},
},
ExportSection: []*wasm.Export{
{Name: "start_epoch", Type: wasm.ExternTypeGlobal, Index: 0},
},
},
},
{
name: "ExportGlobalI64 overwrites",
input: func(r Runtime) ModuleBuilder {
return r.NewModuleBuilder("").ExportGlobalI64("start_epoch", 1620216263544).ExportGlobalI64("start_epoch", math.MaxInt64)
},
expected: &wasm.Module{
GlobalSection: []*wasm.Global{
{
Type: &wasm.GlobalType{ValType: wasm.ValueTypeI64},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI64Const, Data: leb128.EncodeInt64(math.MaxInt64)},
},
},
ExportSection: []*wasm.Export{
{Name: "start_epoch", Type: wasm.ExternTypeGlobal, Index: 0},
},
},
},
{
name: "ExportGlobalF32",
input: func(r Runtime) ModuleBuilder {
return r.NewModuleBuilder("").ExportGlobalF32("math/pi", 3.1415926536)
},
expected: &wasm.Module{
GlobalSection: []*wasm.Global{
{
Type: &wasm.GlobalType{ValType: wasm.ValueTypeF32},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeF32Const, Data: u64.LeBytes(api.EncodeF32(3.1415926536))},
},
},
ExportSection: []*wasm.Export{
{Name: "math/pi", Type: wasm.ExternTypeGlobal, Index: 0},
},
},
},
{
name: "ExportGlobalF32 overwrites",
input: func(r Runtime) ModuleBuilder {
return r.NewModuleBuilder("").ExportGlobalF32("math/pi", 3.1415926536).ExportGlobalF32("math/pi", math.MaxFloat32)
},
expected: &wasm.Module{
GlobalSection: []*wasm.Global{
{
Type: &wasm.GlobalType{ValType: wasm.ValueTypeF32},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeF32Const, Data: u64.LeBytes(api.EncodeF32(math.MaxFloat32))},
},
},
ExportSection: []*wasm.Export{
{Name: "math/pi", Type: wasm.ExternTypeGlobal, Index: 0},
},
},
},
{
name: "ExportGlobalF64",
input: func(r Runtime) ModuleBuilder {
return r.NewModuleBuilder("").ExportGlobalF64("math/pi", math.Pi)
},
expected: &wasm.Module{
GlobalSection: []*wasm.Global{
{
Type: &wasm.GlobalType{ValType: wasm.ValueTypeF64},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeF64Const, Data: u64.LeBytes(api.EncodeF64(math.Pi))},
},
},
ExportSection: []*wasm.Export{
{Name: "math/pi", Type: wasm.ExternTypeGlobal, Index: 0},
},
},
},
{
name: "ExportGlobalF64 overwrites",
input: func(r Runtime) ModuleBuilder {
return r.NewModuleBuilder("").ExportGlobalF64("math/pi", math.Pi).ExportGlobalF64("math/pi", math.MaxFloat64)
},
expected: &wasm.Module{
GlobalSection: []*wasm.Global{
{
Type: &wasm.GlobalType{ValType: wasm.ValueTypeF64},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeF64Const, Data: u64.LeBytes(api.EncodeF64(math.MaxFloat64))},
},
},
ExportSection: []*wasm.Export{
{Name: "math/pi", Type: wasm.ExternTypeGlobal, Index: 0},
},
},
},
} }
for _, tt := range tests { for _, tt := range tests {
tc := tt tc := tt
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
b := tc.input(NewRuntime(testCtx)).(*moduleBuilder) b := tc.input(NewRuntime(testCtx)).(*hostModuleBuilder)
compiled, err := b.Compile(testCtx, NewCompileConfig()) compiled, err := b.Compile(testCtx)
require.NoError(t, err) require.NoError(t, err)
m := compiled.(*compiledModule) m := compiled.(*compiledModule)
@@ -380,31 +193,27 @@ func TestNewModuleBuilder_Compile(t *testing.T) {
} }
} }
// TestNewModuleBuilder_Compile_Errors only covers a few scenarios to avoid duplicating tests in internal/wasm/host_test.go // TestNewHostModuleBuilder_Compile_Errors only covers a few scenarios to avoid
func TestNewModuleBuilder_Compile_Errors(t *testing.T) { // duplicating tests in internal/wasm/host_test.go
func TestNewHostModuleBuilder_Compile_Errors(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
input func(Runtime) ModuleBuilder input func(Runtime) HostModuleBuilder
config CompileConfig
expectedErr string expectedErr string
}{ }{
{ {
name: "memory min > limit", // only one test to avoid duplicating tests in module_test.go name: "error compiling", // should fail due to missing result.
input: func(rt Runtime) ModuleBuilder { input: func(rt Runtime) HostModuleBuilder {
return rt.NewModuleBuilder("").ExportMemory("memory", math.MaxUint32) return rt.NewHostModuleBuilder("").
ExportFunction("fn", &wasm.HostFunc{
ExportNames: []string{"fn"},
ResultTypes: []wasm.ValueType{wasm.ValueTypeI32},
Code: &wasm.Code{IsHostFunction: true, Body: []byte{wasm.OpcodeEnd}},
})
}, },
config: NewCompileConfig(), expectedErr: `invalid function[0] export["fn"]: not enough results
expectedErr: "memory[memory] min 4294967295 pages (3 Ti) over limit of 65536 pages (4 Gi)", have ()
}, want (i32)`,
{
name: "memory cap < min", // only one test to avoid duplicating tests in module_test.go
input: func(rt Runtime) ModuleBuilder {
return rt.NewModuleBuilder("").ExportMemory("memory", 2)
},
config: NewCompileConfig().WithMemorySizer(func(minPages uint32, maxPages *uint32) (min, capacity, max uint32) {
return 2, 1, 2
}),
expectedErr: "memory[memory] capacity 1 pages (64 Ki) less than minimum 2 pages (128 Ki)",
}, },
} }
@@ -412,16 +221,16 @@ func TestNewModuleBuilder_Compile_Errors(t *testing.T) {
tc := tt tc := tt
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
_, e := tc.input(NewRuntime(testCtx)).Compile(testCtx, tc.config) _, e := tc.input(NewRuntime(testCtx)).Compile(testCtx)
require.EqualError(t, e, tc.expectedErr) require.EqualError(t, e, tc.expectedErr)
}) })
} }
} }
// TestNewModuleBuilder_Instantiate ensures Runtime.InstantiateModule is called on success. // TestNewHostModuleBuilder_Instantiate ensures Runtime.InstantiateModule is called on success.
func TestNewModuleBuilder_Instantiate(t *testing.T) { func TestNewHostModuleBuilder_Instantiate(t *testing.T) {
r := NewRuntime(testCtx) r := NewRuntime(testCtx)
m, err := r.NewModuleBuilder("env").Instantiate(testCtx, r) m, err := r.NewHostModuleBuilder("env").Instantiate(testCtx, r)
require.NoError(t, err) require.NoError(t, err)
// If this was instantiated, it would be added to the store under the same name // If this was instantiated, it would be added to the store under the same name
@@ -432,13 +241,13 @@ func TestNewModuleBuilder_Instantiate(t *testing.T) {
require.Zero(t, r.(*runtime).store.Engine.CompiledModuleCount()) require.Zero(t, r.(*runtime).store.Engine.CompiledModuleCount())
} }
// TestNewModuleBuilder_Instantiate_Errors ensures errors propagate from Runtime.InstantiateModule // TestNewHostModuleBuilder_Instantiate_Errors ensures errors propagate from Runtime.InstantiateModule
func TestNewModuleBuilder_Instantiate_Errors(t *testing.T) { func TestNewHostModuleBuilder_Instantiate_Errors(t *testing.T) {
r := NewRuntime(testCtx) r := NewRuntime(testCtx)
_, err := r.NewModuleBuilder("env").Instantiate(testCtx, r) _, err := r.NewHostModuleBuilder("env").Instantiate(testCtx, r)
require.NoError(t, err) require.NoError(t, err)
_, err = r.NewModuleBuilder("env").Instantiate(testCtx, r) _, err = r.NewHostModuleBuilder("env").Instantiate(testCtx, r)
require.EqualError(t, err, "module[env] has already been instantiated") require.EqualError(t, err, "module[env] has already been instantiated")
} }

View File

@@ -165,7 +165,7 @@ func (c *compiledModule) ExportedFunctions() map[string]api.FunctionDefinition {
return c.module.ExportedFunctions() return c.module.ExportedFunctions()
} }
// CompileConfig allows you to override what was decoded from wasm, prior to compilation (ModuleBuilder.Compile or // CompileConfig allows you to override what was decoded from wasm, prior to compilation (HostModuleBuilder.Compile or
// Runtime.CompileModule). // Runtime.CompileModule).
// //
// For example, WithMemorySizer allows you to override memory size that doesn't match your requirements. // For example, WithMemorySizer allows you to override memory size that doesn't match your requirements.

View File

@@ -30,7 +30,7 @@ func main() {
// Instantiate a Go-defined module named "env" that exports a function to // Instantiate a Go-defined module named "env" that exports a function to
// log to the console. // log to the console.
_, err := r.NewModuleBuilder("env"). _, err := r.NewHostModuleBuilder("env").
ExportFunction("log", logString). ExportFunction("log", logString).
Instantiate(ctx, r) Instantiate(ctx, r)
if err != nil { if err != nil {

View File

@@ -31,7 +31,7 @@ func main() {
// Instantiate a Go-defined module named "env" that exports a function to // Instantiate a Go-defined module named "env" that exports a function to
// log to the console. // log to the console.
_, err := r.NewModuleBuilder("env"). _, err := r.NewHostModuleBuilder("env").
ExportFunction("log", logString). ExportFunction("log", logString).
Instantiate(ctx, r) Instantiate(ctx, r)
if err != nil { if err != nil {

View File

@@ -36,7 +36,7 @@ func run() error {
// Instantiate a Go-defined module named "env" that exports a function to // Instantiate a Go-defined module named "env" that exports a function to
// log to the console. // log to the console.
_, err := r.NewModuleBuilder("env"). _, err := r.NewHostModuleBuilder("env").
ExportFunction("log", logString). ExportFunction("log", logString).
Instantiate(ctx, r) Instantiate(ctx, r)
if err != nil { if err != nil {

View File

@@ -14,7 +14,7 @@ log_i32 >> 21
WebAssembly has neither a mechanism to get the current year, nor one to print to the console, so we define these in Go. WebAssembly has neither a mechanism to get the current year, nor one to print to the console, so we define these in Go.
Similar to Go, WebAssembly functions are namespaced, into modules instead of packages. Just like Go, only exported Similar to Go, WebAssembly functions are namespaced, into modules instead of packages. Just like Go, only exported
functions can be imported into another module. What you'll learn in [age-calculator.go](age-calculator.go), is how to functions can be imported into another module. What you'll learn in [age-calculator.go](age-calculator.go), is how to
export functions using [ModuleBuilder](https://pkg.go.dev/github.com/tetratelabs/wazero#ModuleBuilder) and how a export functions using [HostModuleBuilder](https://pkg.go.dev/github.com/tetratelabs/wazero#HostModuleBuilder) and how a
WebAssembly module defined in its [text format](https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#text-format%E2%91%A0) WebAssembly module defined in its [text format](https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#text-format%E2%91%A0)
imports it. This only uses the text format for demonstration purposes, to show you what's going on. It is likely, you imports it. This only uses the text format for demonstration purposes, to show you what's going on. It is likely, you
will use another language to compile a Wasm (WebAssembly Module) binary, such as TinyGo. Regardless of how wasm is will use another language to compile a Wasm (WebAssembly Module) binary, such as TinyGo. Regardless of how wasm is

View File

@@ -38,7 +38,7 @@ func main() {
// constrained to a subset of numeric types. // constrained to a subset of numeric types.
// Note: "env" is a module name conventionally used for arbitrary // Note: "env" is a module name conventionally used for arbitrary
// host-defined functions, but any name would do. // host-defined functions, but any name would do.
_, err := r.NewModuleBuilder("env"). _, err := r.NewHostModuleBuilder("env").
ExportFunction("log_i32", func(v uint32) { ExportFunction("log_i32", func(v uint32) {
fmt.Println("log_i32 >>", v) fmt.Println("log_i32 >>", v)
}). }).

View File

@@ -109,7 +109,7 @@ var multiValueFromImportedHostWasm []byte
// The imported "get_age" function returns multiple results. The source is in testdata/multi_value_imported.wat // The imported "get_age" function returns multiple results. The source is in testdata/multi_value_imported.wat
func multiValueFromImportedHostWasmFunctions(ctx context.Context, r wazero.Runtime) (api.Module, error) { func multiValueFromImportedHostWasmFunctions(ctx context.Context, r wazero.Runtime) (api.Module, error) {
// Instantiate the host module with the exported `get_age` function which returns multiple results. // Instantiate the host module with the exported `get_age` function which returns multiple results.
if _, err := r.NewModuleBuilder("multi-value/host"). if _, err := r.NewHostModuleBuilder("multi-value/host").
// Define a function that returns two results // Define a function that returns two results
ExportFunction("get_age", func() (age uint64, errno uint32) { ExportFunction("get_age", func() (age uint64, errno uint32) {
age = 37 age = 37

View File

@@ -71,7 +71,7 @@ func instantiateWithEnv(ctx context.Context, r wazero.Runtime, module wazero.Com
// Instantiate a new "env" module which exports a stateful function. // Instantiate a new "env" module which exports a stateful function.
c := &counter{} c := &counter{}
_, err := r.NewModuleBuilder("env"). _, err := r.NewHostModuleBuilder("env").
ExportFunction("next_i32", c.getAndIncrement). ExportFunction("next_i32", c.getAndIncrement).
Instantiate(ctx, ns) Instantiate(ctx, ns)
if err != nil { if err != nil {

View File

@@ -54,7 +54,7 @@ const (
// - To add more functions to the "env" module, use FunctionExporter. // - To add more functions to the "env" module, use FunctionExporter.
// - To instantiate into another wazero.Namespace, use FunctionExporter. // - To instantiate into another wazero.Namespace, use FunctionExporter.
func Instantiate(ctx context.Context, r wazero.Runtime) (api.Closer, error) { func Instantiate(ctx context.Context, r wazero.Runtime) (api.Closer, error) {
builder := r.NewModuleBuilder("env") builder := r.NewHostModuleBuilder("env")
NewFunctionExporter().ExportFunctions(builder) NewFunctionExporter().ExportFunctions(builder)
return builder.Instantiate(ctx, r) return builder.Instantiate(ctx, r)
} }
@@ -77,9 +77,9 @@ type FunctionExporter interface {
// appropriate to use WithTraceToStdout instead. // appropriate to use WithTraceToStdout instead.
WithTraceToStderr() FunctionExporter WithTraceToStderr() FunctionExporter
// ExportFunctions builds functions to export with a wazero.ModuleBuilder // ExportFunctions builds functions to export with a wazero.HostModuleBuilder
// named "env". // named "env".
ExportFunctions(builder wazero.ModuleBuilder) ExportFunctions(builder wazero.HostModuleBuilder)
} }
// NewFunctionExporter returns a FunctionExporter object with trace disabled. // NewFunctionExporter returns a FunctionExporter object with trace disabled.
@@ -107,7 +107,7 @@ func (e *functionExporter) WithTraceToStderr() FunctionExporter {
} }
// ExportFunctions implements FunctionExporter.ExportFunctions // ExportFunctions implements FunctionExporter.ExportFunctions
func (e *functionExporter) ExportFunctions(builder wazero.ModuleBuilder) { func (e *functionExporter) ExportFunctions(builder wazero.HostModuleBuilder) {
builder.ExportFunction(functionAbort, e.abortFn) builder.ExportFunction(functionAbort, e.abortFn)
builder.ExportFunction(functionTrace, e.traceFn) builder.ExportFunction(functionTrace, e.traceFn)
builder.ExportFunction(functionSeed, seed) builder.ExportFunction(functionSeed, seed)

View File

@@ -34,7 +34,7 @@ func Example_functionExporter() {
defer r.Close(ctx) // This closes everything this Runtime created. defer r.Close(ctx) // This closes everything this Runtime created.
// First construct your own module builder for "env" // First construct your own module builder for "env"
envBuilder := r.NewModuleBuilder("env"). envBuilder := r.NewHostModuleBuilder("env").
ExportFunction("get_int", func() uint32 { return 1 }) ExportFunction("get_int", func() uint32 { return 1 })
// Now, add AssemblyScript special function imports into it. // Now, add AssemblyScript special function imports into it.

View File

@@ -424,10 +424,10 @@ func requireProxyModule(t *testing.T, fns FunctionExporter, config wazero.Module
r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfigInterpreter()) r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfigInterpreter())
builder := r.NewModuleBuilder("env") builder := r.NewHostModuleBuilder("env")
fns.ExportFunctions(builder) fns.ExportFunctions(builder)
envCompiled, err := builder.Compile(ctx, wazero.NewCompileConfig()) envCompiled, err := builder.Compile(ctx)
require.NoError(t, err) require.NoError(t, err)
_, err = r.InstantiateModule(ctx, envCompiled, config) _, err = r.InstantiateModule(ctx, envCompiled, config)

View File

@@ -29,7 +29,7 @@ import (
// - To add more functions to the "env" module, use FunctionExporter. // - To add more functions to the "env" module, use FunctionExporter.
// - To instantiate into another wazero.Namespace, use FunctionExporter. // - To instantiate into another wazero.Namespace, use FunctionExporter.
func Instantiate(ctx context.Context, r wazero.Runtime) (api.Closer, error) { func Instantiate(ctx context.Context, r wazero.Runtime) (api.Closer, error) {
builder := r.NewModuleBuilder("env") builder := r.NewHostModuleBuilder("env")
NewFunctionExporter().ExportFunctions(builder) NewFunctionExporter().ExportFunctions(builder)
return builder.Instantiate(ctx, r) return builder.Instantiate(ctx, r)
} }
@@ -37,9 +37,9 @@ func Instantiate(ctx context.Context, r wazero.Runtime) (api.Closer, error) {
// FunctionExporter configures the functions in the "env" module used by // FunctionExporter configures the functions in the "env" module used by
// Emscripten. // Emscripten.
type FunctionExporter interface { type FunctionExporter interface {
// ExportFunctions builds functions to export with a wazero.ModuleBuilder // ExportFunctions builds functions to export with a wazero.HostModuleBuilder
// named "env". // named "env".
ExportFunctions(builder wazero.ModuleBuilder) ExportFunctions(builder wazero.HostModuleBuilder)
} }
// NewFunctionExporter returns a FunctionExporter object with trace disabled. // NewFunctionExporter returns a FunctionExporter object with trace disabled.
@@ -50,7 +50,7 @@ func NewFunctionExporter() FunctionExporter {
type functionExporter struct{} type functionExporter struct{}
// ExportFunctions implements FunctionExporter.ExportFunctions // ExportFunctions implements FunctionExporter.ExportFunctions
func (e *functionExporter) ExportFunctions(builder wazero.ModuleBuilder) { func (e *functionExporter) ExportFunctions(builder wazero.HostModuleBuilder) {
builder.ExportFunction(notifyMemoryGrowth.Name, notifyMemoryGrowth) builder.ExportFunction(notifyMemoryGrowth.Name, notifyMemoryGrowth)
} }

View File

@@ -45,7 +45,7 @@ func Example_functionExporter() {
// Next, construct your own module builder for "env" with any functions // Next, construct your own module builder for "env" with any functions
// you need. // you need.
envBuilder := r.NewModuleBuilder("env"). envBuilder := r.NewHostModuleBuilder("env").
ExportFunction("get_int", func() uint32 { return 1 }) ExportFunction("get_int", func() uint32 { return 1 })
// Now, add Emscripten special function imports into it. // Now, add Emscripten special function imports into it.

View File

@@ -62,7 +62,7 @@ func WithRoundTripper(ctx context.Context, rt http.RoundTripper) context.Context
// - Both the host and guest module are closed after being run. // - Both the host and guest module are closed after being run.
func Run(ctx context.Context, r wazero.Runtime, compiled wazero.CompiledModule, config wazero.ModuleConfig) error { func Run(ctx context.Context, r wazero.Runtime, compiled wazero.CompiledModule, config wazero.ModuleConfig) error {
// Instantiate the imports needed by go-compiled wasm. // Instantiate the imports needed by go-compiled wasm.
js, err := moduleBuilder(r).Instantiate(ctx, r) js, err := hostModuleBuilder(r).Instantiate(ctx, r)
if err != nil { if err != nil {
return err return err
} }
@@ -86,9 +86,9 @@ func Run(ctx context.Context, r wazero.Runtime, compiled wazero.CompiledModule,
return err return err
} }
// moduleBuilder returns a new wazero.ModuleBuilder // hostModuleBuilder returns a new wazero.HostModuleBuilder
func moduleBuilder(r wazero.Runtime) wazero.ModuleBuilder { func hostModuleBuilder(r wazero.Runtime) wazero.HostModuleBuilder {
return r.NewModuleBuilder("go"). return r.NewHostModuleBuilder("go").
ExportFunction(GetRandomData.Name(), GetRandomData). ExportFunction(GetRandomData.Name(), GetRandomData).
ExportFunction(Nanotime1.Name(), Nanotime1). ExportFunction(Nanotime1.Name(), Nanotime1).
ExportFunction(WasmExit.Name(), WasmExit). ExportFunction(WasmExit.Name(), WasmExit).

View File

@@ -47,12 +47,12 @@ type Builder interface {
// Compile compiles the ModuleName module that can instantiated in any // Compile compiles the ModuleName module that can instantiated in any
// namespace (wazero.Namespace). // namespace (wazero.Namespace).
// //
// Note: This has the same effect as the same function on wazero.ModuleBuilder. // Note: This has the same effect as the same function on wazero.HostModuleBuilder.
Compile(context.Context, wazero.CompileConfig) (wazero.CompiledModule, error) Compile(context.Context) (wazero.CompiledModule, error)
// Instantiate instantiates the ModuleName module into the given namespace. // Instantiate instantiates the ModuleName module into the given namespace.
// //
// Note: This has the same effect as the same function on wazero.ModuleBuilder. // Note: This has the same effect as the same function on wazero.HostModuleBuilder.
Instantiate(context.Context, wazero.Namespace) (api.Closer, error) Instantiate(context.Context, wazero.Namespace) (api.Closer, error)
} }
@@ -63,21 +63,21 @@ func NewBuilder(r wazero.Runtime) Builder {
type builder struct{ r wazero.Runtime } type builder struct{ r wazero.Runtime }
// moduleBuilder returns a new wazero.ModuleBuilder for ModuleName // hostModuleBuilder returns a new wazero.HostModuleBuilder for ModuleName
func (b *builder) moduleBuilder() wazero.ModuleBuilder { func (b *builder) hostModuleBuilder() wazero.HostModuleBuilder {
ret := b.r.NewModuleBuilder(ModuleName) ret := b.r.NewHostModuleBuilder(ModuleName)
exportFunctions(ret) exportFunctions(ret)
return ret return ret
} }
// Compile implements Builder.Compile // Compile implements Builder.Compile
func (b *builder) Compile(ctx context.Context, config wazero.CompileConfig) (wazero.CompiledModule, error) { func (b *builder) Compile(ctx context.Context) (wazero.CompiledModule, error) {
return b.moduleBuilder().Compile(ctx, config) return b.hostModuleBuilder().Compile(ctx)
} }
// Instantiate implements Builder.Instantiate // Instantiate implements Builder.Instantiate
func (b *builder) Instantiate(ctx context.Context, ns wazero.Namespace) (api.Closer, error) { func (b *builder) Instantiate(ctx context.Context, ns wazero.Namespace) (api.Closer, error) {
return b.moduleBuilder().Instantiate(ctx, ns) return b.hostModuleBuilder().Instantiate(ctx, ns)
} }
// ## Translation notes // ## Translation notes
@@ -109,7 +109,7 @@ func (b *builder) Instantiate(ctx context.Context, ns wazero.Namespace) (api.Clo
// exportFunctions adds all go functions that implement wasi. // exportFunctions adds all go functions that implement wasi.
// These should be exported in the module named ModuleName. // These should be exported in the module named ModuleName.
func exportFunctions(builder wazero.ModuleBuilder) { func exportFunctions(builder wazero.HostModuleBuilder) {
// Note: these are ordered per spec for consistency even if the resulting // Note: these are ordered per spec for consistency even if the resulting
// map can't guarantee that. // map can't guarantee that.
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#functions // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#functions

View File

@@ -4,7 +4,10 @@ import (
"testing" "testing"
"github.com/tetratelabs/wazero" "github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/testing/require" "github.com/tetratelabs/wazero/internal/testing/require"
"github.com/tetratelabs/wazero/internal/wasm"
binaryformat "github.com/tetratelabs/wazero/internal/wasm/binary"
) )
var testMem = []byte{ var testMem = []byte{
@@ -37,11 +40,13 @@ func Test_Benchmark_EnvironGet(t *testing.T) {
} }
func Benchmark_EnvironGet(b *testing.B) { func Benchmark_EnvironGet(b *testing.B) {
r := wazero.NewRuntimeWithConfig(testCtx, wazero.NewRuntimeConfigInterpreter()) r := wazero.NewRuntime(testCtx)
defer r.Close(testCtx)
compiled, err := r.NewModuleBuilder(b.Name()). compiled, err := r.CompileModule(testCtx, binaryformat.EncodeModule(&wasm.Module{
ExportMemoryWithMax("memory", 1, 1). MemorySection: &wasm.Memory{Min: 1, Max: 1},
Compile(testCtx, wazero.NewCompileConfig()) ExportSection: []*wasm.Export{{Name: "memory", Type: api.ExternTypeMemory}},
}), wazero.NewCompileConfig())
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }

View File

@@ -34,8 +34,7 @@ func requireProxyModule(t *testing.T, config wazero.ModuleConfig) (api.Module, a
r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfigInterpreter()) r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfigInterpreter())
wasiModuleCompiled, err := (&builder{r}).moduleBuilder(). wasiModuleCompiled, err := (&builder{r}).hostModuleBuilder().Compile(ctx)
Compile(ctx, wazero.NewCompileConfig())
require.NoError(t, err) require.NoError(t, err)
_, err = r.InstantiateModule(ctx, wasiModuleCompiled, config) _, err = r.InstantiateModule(ctx, wasiModuleCompiled, config)
@@ -65,8 +64,7 @@ func requireErrnoNosys(t *testing.T, funcName string, params ...uint64) string {
defer r.Close(ctx) defer r.Close(ctx)
// Instantiate the wasi module. // Instantiate the wasi module.
wasiModuleCompiled, err := (&builder{r}).moduleBuilder(). wasiModuleCompiled, err := (&builder{r}).hostModuleBuilder().Compile(ctx)
Compile(ctx, wazero.NewCompileConfig())
require.NoError(t, err) require.NoError(t, err)
_, err = r.InstantiateModule(ctx, wasiModuleCompiled, wazero.NewModuleConfig()) _, err = r.InstantiateModule(ctx, wasiModuleCompiled, wazero.NewModuleConfig())

View File

@@ -210,7 +210,7 @@ func TestCompiler_SliceAllocatedOnHeap(t *testing.T) {
// Trigger relocation of goroutine stack because at this point we have the majority of // Trigger relocation of goroutine stack because at this point we have the majority of
// goroutine stack unused after recursive call. // goroutine stack unused after recursive call.
runtime.GC() runtime.GC()
}}, nil, map[string]*wasm.Memory{}, map[string]*wasm.Global{}, enabledFeatures) }}, nil, enabledFeatures)
require.NoError(t, err) require.NoError(t, err)
err = s.Engine.CompileModule(testCtx, hm) err = s.Engine.CompileModule(testCtx, hm)

View File

@@ -197,7 +197,7 @@ func TestMustCallFromSP(t *testing.T) {
defer r.Close(testCtx) defer r.Close(testCtx)
funcName := "i64i32i32i32i32_i64i32_withSP" funcName := "i64i32i32i32i32_i64i32_withSP"
im, err := r.NewModuleBuilder("go"). im, err := r.NewHostModuleBuilder("go").
ExportFunction(funcName, MustCallFromSP(true, wasm.NewGoFunc( ExportFunction(funcName, MustCallFromSP(true, wasm.NewGoFunc(
funcName, funcName, funcName, funcName,
[]string{"v", "mAddr", "mLen", "argsArray", "argsLen"}, []string{"v", "mAddr", "mLen", "argsArray", "argsLen"},

View File

@@ -215,7 +215,7 @@ func createRuntime(b *testing.B, config wazero.RuntimeConfig) wazero.Runtime {
r := wazero.NewRuntimeWithConfig(testCtx, config) r := wazero.NewRuntimeWithConfig(testCtx, config)
_, err := r.NewModuleBuilder("env"). _, err := r.NewHostModuleBuilder("env").
ExportFunction("get_random_string", getRandomString). ExportFunction("get_random_string", getRandomString).
Instantiate(testCtx, r) Instantiate(testCtx, r)
if err != nil { if err != nil {

View File

@@ -99,7 +99,7 @@ func testReftypeImports(t *testing.T, r wazero.Runtime) {
} }
hostObj := &dog{name: "hello"} hostObj := &dog{name: "hello"}
host, err := r.NewModuleBuilder("host"). host, err := r.NewHostModuleBuilder("host").
ExportFunctions(map[string]interface{}{ ExportFunctions(map[string]interface{}{
"externref": func(externrefFromRefNull uintptr) uintptr { "externref": func(externrefFromRefNull uintptr) uintptr {
require.Zero(t, externrefFromRefNull) require.Zero(t, externrefFromRefNull)
@@ -176,7 +176,7 @@ func testUnreachable(t *testing.T, r wazero.Runtime) {
panic("panic in host function") panic("panic in host function")
} }
_, err := r.NewModuleBuilder("host").ExportFunction("cause_unreachable", callUnreachable).Instantiate(testCtx, r) _, err := r.NewHostModuleBuilder("host").ExportFunction("cause_unreachable", callUnreachable).Instantiate(testCtx, r)
require.NoError(t, err) require.NoError(t, err)
module, err := r.InstantiateModuleFromBinary(testCtx, unreachableWasm) module, err := r.InstantiateModuleFromBinary(testCtx, unreachableWasm)
@@ -199,7 +199,7 @@ func testRecursiveEntry(t *testing.T, r wazero.Runtime) {
require.NoError(t, err) require.NoError(t, err)
} }
_, err := r.NewModuleBuilder("env").ExportFunction("host_func", hostfunc).Instantiate(testCtx, r) _, err := r.NewHostModuleBuilder("env").ExportFunction("host_func", hostfunc).Instantiate(testCtx, r)
require.NoError(t, err) require.NoError(t, err)
module, err := r.InstantiateModuleFromBinary(testCtx, recursiveWasm) module, err := r.InstantiateModuleFromBinary(testCtx, recursiveWasm)
@@ -222,7 +222,7 @@ func testHostFuncMemory(t *testing.T, r wazero.Runtime) {
return 0 return 0
} }
host, err := r.NewModuleBuilder("").ExportFunction("store_int", storeInt).Instantiate(testCtx, r) host, err := r.NewHostModuleBuilder("").ExportFunction("store_int", storeInt).Instantiate(testCtx, r)
require.NoError(t, err) require.NoError(t, err)
defer host.Close(testCtx) defer host.Close(testCtx)
@@ -262,7 +262,7 @@ func testNestedGoContext(t *testing.T, r wazero.Runtime) {
}, },
} }
imported, err := r.NewModuleBuilder(importedName).ExportFunctions(fns).Instantiate(testCtx, r) imported, err := r.NewHostModuleBuilder(importedName).ExportFunctions(fns).Instantiate(testCtx, r)
require.NoError(t, err) require.NoError(t, err)
defer imported.Close(testCtx) defer imported.Close(testCtx)
@@ -299,7 +299,7 @@ func testHostFunctionContextParameter(t *testing.T, r wazero.Runtime) {
for test := range fns { for test := range fns {
t.Run(test, func(t *testing.T) { t.Run(test, func(t *testing.T) {
imported, err := r.NewModuleBuilder(importedName). imported, err := r.NewHostModuleBuilder(importedName).
ExportFunction("return_input", fns[test]). ExportFunction("return_input", fns[test]).
Instantiate(testCtx, r) Instantiate(testCtx, r)
require.NoError(t, err) require.NoError(t, err)
@@ -369,7 +369,7 @@ func testHostFunctionNumericParameter(t *testing.T, r wazero.Runtime) {
}, },
} { } {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
imported, err := r.NewModuleBuilder(importedName). imported, err := r.NewHostModuleBuilder(importedName).
ExportFunction("return_input", fns[test.name]). ExportFunction("return_input", fns[test.name]).
Instantiate(testCtx, r) Instantiate(testCtx, r)
require.NoError(t, err) require.NoError(t, err)
@@ -519,8 +519,8 @@ func testCloseInFlight(t *testing.T, r wazero.Runtime) {
} }
// Create the host module, which exports the function that closes the importing module. // Create the host module, which exports the function that closes the importing module.
importedCode, err = r.NewModuleBuilder(t.Name()+"-imported"). importedCode, err = r.NewHostModuleBuilder(t.Name()+"-imported").
ExportFunction("return_input", closeAndReturn).Compile(testCtx, compileConfig) ExportFunction("return_input", closeAndReturn).Compile(testCtx)
require.NoError(t, err) require.NoError(t, err)
imported, err = r.InstantiateModule(testCtx, importedCode, moduleConfig) imported, err = r.InstantiateModule(testCtx, importedCode, moduleConfig)

View File

@@ -49,7 +49,7 @@ func closeImportedModuleWhileInUse(t *testing.T, r wazero.Runtime) {
require.NoError(t, imported.Close(testCtx)) require.NoError(t, imported.Close(testCtx))
// Redefine the imported module, with a function that no longer blocks. // Redefine the imported module, with a function that no longer blocks.
imported, err := r.NewModuleBuilder(imported.Name()).ExportFunction("return_input", func(x uint32) uint32 { imported, err := r.NewHostModuleBuilder(imported.Name()).ExportFunction("return_input", func(x uint32) uint32 {
return x return x
}).Instantiate(testCtx, r) }).Instantiate(testCtx, r)
require.NoError(t, err) require.NoError(t, err)
@@ -78,7 +78,7 @@ func closeModuleWhileInUse(t *testing.T, r wazero.Runtime, closeFn func(imported
} }
// Create the host module, which exports the blocking function. // Create the host module, which exports the blocking function.
imported, err := r.NewModuleBuilder(t.Name()+"-imported"). imported, err := r.NewHostModuleBuilder(t.Name()+"-imported").
ExportFunction("return_input", blockAndReturn).Instantiate(testCtx, r) ExportFunction("return_input", blockAndReturn).Instantiate(testCtx, r)
require.NoError(t, err) require.NoError(t, err)
defer imported.Close(testCtx) defer imported.Close(testCtx)

View File

@@ -93,18 +93,18 @@ func (r *wazeroRuntime) Compile(ctx context.Context, cfg *RuntimeConfig) (err er
r.runtime = wazero.NewRuntimeWithConfig(ctx, r.config) r.runtime = wazero.NewRuntimeWithConfig(ctx, r.config)
if cfg.LogFn != nil { if cfg.LogFn != nil {
r.logFn = cfg.LogFn r.logFn = cfg.LogFn
if r.env, err = r.runtime.NewModuleBuilder("env"). if r.env, err = r.runtime.NewHostModuleBuilder("env").
ExportFunction("log", r.log).Compile(ctx, wazero.NewCompileConfig()); err != nil { ExportFunction("log", r.log).Compile(ctx); err != nil {
return err return err
} }
} else if cfg.EnvFReturnValue != 0 { } else if cfg.EnvFReturnValue != 0 {
if r.env, err = r.runtime.NewModuleBuilder("env"). if r.env, err = r.runtime.NewHostModuleBuilder("env").
ExportFunction("f", ExportFunction("f",
// Note: accepting (context.Context, api.Module) is the slowest type of host function with wazero. // Note: accepting (context.Context, api.Module) is the slowest type of host function with wazero.
func(context.Context, api.Module, uint64) uint64 { func(context.Context, api.Module, uint64) uint64 {
return cfg.EnvFReturnValue return cfg.EnvFReturnValue
}, },
).Compile(ctx, wazero.NewCompileConfig()); err != nil { ).Compile(ctx); err != nil {
return err return err
} }
} }

View File

@@ -298,7 +298,7 @@ func fail(t TestingT, m1, m2 string, formatWithArgs ...interface{}) {
} }
} }
// failStack returns the stack leading to the require fail, without test infrastructure. // failStack returns the stack leading to the failure, without test infrastructure.
// //
// Note: This is similar to assert.CallerInfo in testify // Note: This is similar to assert.CallerInfo in testify
// Note: This is untested because it is a lot of work to do that. The rationale to punt is this is a test-only internal // Note: This is untested because it is a lot of work to do that. The rationale to punt is this is a test-only internal

View File

@@ -114,7 +114,7 @@ func (m *CallContext) close(ctx context.Context, exitCode uint32) (c bool, err e
return false, nil return false, nil
} }
c = true c = true
if sysCtx := m.Sys; sysCtx != nil { // nil if from ModuleBuilder if sysCtx := m.Sys; sysCtx != nil { // nil if from HostModuleBuilder
err = sysCtx.FS(ctx).Close(ctx) err = sysCtx.FS(ctx).Close(ctx)
} }
return return

View File

@@ -4,7 +4,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"sort" "sort"
"strings"
"github.com/tetratelabs/wazero/api" "github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/wasmdebug" "github.com/tetratelabs/wazero/internal/wasmdebug"
@@ -91,8 +90,6 @@ func NewHostModule(
moduleName string, moduleName string,
nameToGoFunc map[string]interface{}, nameToGoFunc map[string]interface{},
funcToNames map[string][]string, funcToNames map[string][]string,
nameToMemory map[string]*Memory,
nameToGlobal map[string]*Global,
enabledFeatures api.CoreFeatures, enabledFeatures api.CoreFeatures,
) (m *Module, err error) { ) (m *Module, err error) {
if moduleName != "" { if moduleName != "" {
@@ -101,52 +98,15 @@ func NewHostModule(
m = &Module{} m = &Module{}
} }
funcCount := uint32(len(nameToGoFunc)) if exportCount := uint32(len(nameToGoFunc)); exportCount > 0 {
memoryCount := uint32(len(nameToMemory))
globalCount := uint32(len(nameToGlobal))
exportCount := funcCount + memoryCount + globalCount
if exportCount > 0 {
m.ExportSection = make([]*Export, 0, exportCount) m.ExportSection = make([]*Export, 0, exportCount)
}
// Check name collision as exports cannot collide on names, regardless of type.
for name := range nameToGoFunc {
// manually generate the error message as we don't have debug names yet.
if _, ok := nameToMemory[name]; ok {
return nil, fmt.Errorf("func[%s.%s] exports the same name as a memory", moduleName, name)
}
if _, ok := nameToGlobal[name]; ok {
return nil, fmt.Errorf("func[%s.%s] exports the same name as a global", moduleName, name)
}
}
for name := range nameToMemory {
if _, ok := nameToGlobal[name]; ok {
return nil, fmt.Errorf("memory[%s] exports the same name as a global", name)
}
}
if funcCount > 0 {
if err = addFuncs(m, nameToGoFunc, funcToNames, enabledFeatures); err != nil { if err = addFuncs(m, nameToGoFunc, funcToNames, enabledFeatures); err != nil {
return return
} }
} }
if memoryCount > 0 {
if err = addMemory(m, nameToMemory); err != nil {
return
}
}
// TODO: we can use enabledFeatures to fail early on things like mutable globals (once supported)
if globalCount > 0 {
if err = addGlobals(m, nameToGlobal); err != nil {
return
}
}
// Assigns the ModuleID by calculating sha256 on inputs as host modules do not have `wasm` to hash. // Assigns the ModuleID by calculating sha256 on inputs as host modules do not have `wasm` to hash.
m.AssignModuleID([]byte(fmt.Sprintf("%s:%v:%v:%v:%v", m.AssignModuleID([]byte(fmt.Sprintf("%s:%v:%v", moduleName, nameToGoFunc, enabledFeatures)))
moduleName, nameToGoFunc, nameToMemory, nameToGlobal, enabledFeatures)))
m.BuildFunctionDefinitions() m.BuildFunctionDefinitions()
return return
} }
@@ -264,50 +224,6 @@ func addFuncs(
return nil return nil
} }
func addMemory(m *Module, nameToMemory map[string]*Memory) error {
memoryCount := uint32(len(nameToMemory))
// Only one memory can be defined or imported
if memoryCount > 1 {
memoryNames := make([]string, 0, memoryCount)
for k := range nameToMemory {
memoryNames = append(memoryNames, k)
}
sort.Strings(memoryNames) // For consistent error messages
return fmt.Errorf("only one memory is allowed, but configured: %s", strings.Join(memoryNames, ", "))
}
// Find the memory name to export.
var name string
for k, v := range nameToMemory {
name = k
if v.Min > v.Max {
return fmt.Errorf("memory[%s] min %d pages (%s) > max %d pages (%s)", name, v.Min, PagesToUnitOfBytes(v.Min), v.Max, PagesToUnitOfBytes(v.Max))
}
m.MemorySection = v
}
m.ExportSection = append(m.ExportSection, &Export{Type: ExternTypeMemory, Name: name, Index: 0})
return nil
}
func addGlobals(m *Module, globals map[string]*Global) error {
globalCount := len(globals)
m.GlobalSection = make([]*Global, 0, globalCount)
globalNames := make([]string, 0, globalCount)
for name := range globals {
globalNames = append(globalNames, name)
}
sort.Strings(globalNames) // For consistent iteration order
for i, name := range globalNames {
m.GlobalSection = append(m.GlobalSection, globals[name])
m.ExportSection = append(m.ExportSection, &Export{Type: ExternTypeGlobal, Name: name, Index: Index(i)})
}
return nil
}
func (m *Module) maybeAddType(params, results []ValueType, enabledFeatures api.CoreFeatures) (Index, error) { func (m *Module) maybeAddType(params, results []ValueType, enabledFeatures api.CoreFeatures) (Index, error) {
if len(results) > 1 { if len(results) > 1 {
// Guard >1.0 feature multi-value // Guard >1.0 feature multi-value

View File

@@ -4,7 +4,6 @@ import (
"testing" "testing"
"github.com/tetratelabs/wazero/api" "github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/testing/require" "github.com/tetratelabs/wazero/internal/testing/require"
) )
@@ -12,10 +11,6 @@ import (
type wasiAPI struct { type wasiAPI struct {
} }
func ArgsSizesGet(ctx api.Module, resultArgc, resultArgvBufSize uint32) uint32 {
return 0
}
func (a *wasiAPI) ArgsSizesGet(ctx api.Module, resultArgc, resultArgvBufSize uint32) uint32 { func (a *wasiAPI) ArgsSizesGet(ctx api.Module, resultArgc, resultArgvBufSize uint32) uint32 {
return 0 return 0
} }
@@ -37,8 +32,6 @@ func TestNewHostModule(t *testing.T) {
tests := []struct { tests := []struct {
name, moduleName string name, moduleName string
nameToGoFunc map[string]interface{} nameToGoFunc map[string]interface{}
nameToMemory map[string]*Memory
nameToGlobal map[string]*Global
expected *Module expected *Module
}{ }{
{ {
@@ -91,91 +84,13 @@ func TestNewHostModule(t *testing.T) {
NameSection: &NameSection{ModuleName: "swapper", FunctionNames: NameMap{{Index: 0, Name: "swap"}}}, NameSection: &NameSection{ModuleName: "swapper", FunctionNames: NameMap{{Index: 0, Name: "swap"}}},
}, },
}, },
{
name: "memory",
nameToMemory: map[string]*Memory{"memory": {Min: 1, Max: 2}},
expected: &Module{
MemorySection: &Memory{Min: 1, Max: 2},
ExportSection: []*Export{{Name: "memory", Type: ExternTypeMemory, Index: 0}},
},
},
{
name: "globals",
nameToGlobal: map[string]*Global{
"g2": {
Type: &GlobalType{ValType: i32},
Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(2)},
},
"g1": {
Type: &GlobalType{ValType: i32},
Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: const1},
},
},
expected: &Module{
GlobalSection: []*Global{
{
Type: &GlobalType{ValType: i32},
Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: const1},
},
{
Type: &GlobalType{ValType: i32},
Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(2)},
},
},
ExportSection: []*Export{
{Name: "g1", Type: ExternTypeGlobal, Index: 0},
{Name: "g2", Type: ExternTypeGlobal, Index: 1},
},
},
},
{
name: "one of each",
moduleName: "env",
nameToGoFunc: map[string]interface{}{
functionArgsSizesGet: a.ArgsSizesGet,
},
nameToMemory: map[string]*Memory{
"memory": {Min: 1, Max: 1},
},
nameToGlobal: map[string]*Global{
"g": {
Type: &GlobalType{ValType: i32},
Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: const1},
},
},
expected: &Module{
TypeSection: []*FunctionType{
{Params: []ValueType{i32, i32}, Results: []ValueType{i32}},
},
FunctionSection: []Index{0},
CodeSection: []*Code{MustParseGoFuncCode(a.ArgsSizesGet)},
GlobalSection: []*Global{
{
Type: &GlobalType{ValType: i32},
Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: const1},
},
},
MemorySection: &Memory{Min: 1, Max: 1},
ExportSection: []*Export{
{Name: "args_sizes_get", Type: ExternTypeFunc, Index: 0},
{Name: "memory", Type: ExternTypeMemory, Index: 0},
{Name: "g", Type: ExternTypeGlobal, Index: 0},
},
NameSection: &NameSection{
ModuleName: "env",
FunctionNames: NameMap{
{Index: 0, Name: "args_sizes_get"},
},
},
},
},
} }
for _, tt := range tests { for _, tt := range tests {
tc := tt tc := tt
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
m, e := NewHostModule(tc.moduleName, tc.nameToGoFunc, nil, tc.nameToMemory, tc.nameToGlobal, m, e := NewHostModule(tc.moduleName, tc.nameToGoFunc, nil,
api.CoreFeaturesV1|api.CoreFeatureMultiValue) api.CoreFeaturesV1|api.CoreFeatureMultiValue)
require.NoError(t, e) require.NoError(t, e)
requireHostModuleEquals(t, tc.expected, m) requireHostModuleEquals(t, tc.expected, m)
@@ -216,8 +131,6 @@ func TestNewHostModule_Errors(t *testing.T) {
tests := []struct { tests := []struct {
name, moduleName string name, moduleName string
nameToGoFunc map[string]interface{} nameToGoFunc map[string]interface{}
nameToMemory map[string]*Memory
nameToGlobal map[string]*Global
expectedErr string expectedErr string
}{ }{
{ {
@@ -228,38 +141,15 @@ func TestNewHostModule_Errors(t *testing.T) {
{ {
name: "function has multiple results", name: "function has multiple results",
nameToGoFunc: map[string]interface{}{"fn": func() (uint32, uint32) { return 0, 0 }}, nameToGoFunc: map[string]interface{}{"fn": func() (uint32, uint32) { return 0, 0 }},
nameToMemory: map[string]*Memory{"mem": {Min: 1, Max: 1}},
expectedErr: "func[.fn] multiple result types invalid as feature \"multi-value\" is disabled", expectedErr: "func[.fn] multiple result types invalid as feature \"multi-value\" is disabled",
}, },
{
name: "func collides on memory name",
nameToGoFunc: map[string]interface{}{"fn": ArgsSizesGet},
nameToMemory: map[string]*Memory{"fn": {Min: 1, Max: 1}},
expectedErr: "func[.fn] exports the same name as a memory",
},
{
name: "multiple memories",
nameToMemory: map[string]*Memory{"memory": {Min: 1, Max: 1}, "mem": {Min: 2, Max: 2}},
expectedErr: "only one memory is allowed, but configured: mem, memory",
},
{
name: "memory max < min",
nameToMemory: map[string]*Memory{"memory": {Min: 1, Max: 0}},
expectedErr: "memory[memory] min 1 pages (64 Ki) > max 0 pages (0 Ki)",
},
{
name: "func collides on global name",
nameToGoFunc: map[string]interface{}{"fn": ArgsSizesGet},
nameToGlobal: map[string]*Global{"fn": {}},
expectedErr: "func[.fn] exports the same name as a global",
},
} }
for _, tt := range tests { for _, tt := range tests {
tc := tt tc := tt
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
_, e := NewHostModule(tc.moduleName, tc.nameToGoFunc, nil, tc.nameToMemory, tc.nameToGlobal, api.CoreFeaturesV1) _, e := NewHostModule(tc.moduleName, tc.nameToGoFunc, nil, api.CoreFeaturesV1)
require.EqualError(t, e, tc.expectedErr) require.EqualError(t, e, tc.expectedErr)
}) })
} }

View File

@@ -805,7 +805,7 @@ type Export struct {
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-code // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-code
type Code struct { type Code struct {
// IsHostFunction returns true if the function was implemented by the // IsHostFunction returns true if the function was implemented by the
// embedder (ex via wazero.ModuleBuilder) instead of a wasm binary. // embedder (ex via wazero.HostModuleBuilder) instead of a wasm binary.
// //
// Notably, host functions can use the caller's memory, which might be // Notably, host functions can use the caller's memory, which might be
// different from its defining module. // different from its defining module.

View File

@@ -91,7 +91,7 @@ func TestModuleInstance_Memory(t *testing.T) {
func TestStore_Instantiate(t *testing.T) { func TestStore_Instantiate(t *testing.T) {
s, ns := newStore() s, ns := newStore()
m, err := NewHostModule("", map[string]interface{}{"fn": func(api.Module) {}}, nil, map[string]*Memory{}, map[string]*Global{}, api.CoreFeaturesV1) m, err := NewHostModule("", map[string]interface{}{"fn": func(api.Module) {}}, nil, api.CoreFeaturesV1)
require.NoError(t, err) require.NoError(t, err)
sysCtx := sys.DefaultContext(nil) sysCtx := sys.DefaultContext(nil)
@@ -169,7 +169,7 @@ func TestStore_CloseWithExitCode(t *testing.T) {
func TestStore_hammer(t *testing.T) { func TestStore_hammer(t *testing.T) {
const importedModuleName = "imported" const importedModuleName = "imported"
m, err := NewHostModule(importedModuleName, map[string]interface{}{"fn": func(api.Module) {}}, nil, map[string]*Memory{}, map[string]*Global{}, api.CoreFeaturesV1) m, err := NewHostModule(importedModuleName, map[string]interface{}{"fn": func(api.Module) {}}, nil, api.CoreFeaturesV1)
require.NoError(t, err) require.NoError(t, err)
s, ns := newStore() s, ns := newStore()
@@ -223,7 +223,7 @@ func TestStore_Instantiate_Errors(t *testing.T) {
const importedModuleName = "imported" const importedModuleName = "imported"
const importingModuleName = "test" const importingModuleName = "test"
m, err := NewHostModule(importedModuleName, map[string]interface{}{"fn": func(api.Module) {}}, nil, map[string]*Memory{}, map[string]*Global{}, api.CoreFeaturesV1) m, err := NewHostModule(importedModuleName, map[string]interface{}{"fn": func(api.Module) {}}, nil, api.CoreFeaturesV1)
require.NoError(t, err) require.NoError(t, err)
t.Run("Fails if module name already in use", func(t *testing.T) { t.Run("Fails if module name already in use", func(t *testing.T) {
@@ -314,7 +314,7 @@ func TestStore_Instantiate_Errors(t *testing.T) {
} }
func TestCallContext_ExportedFunction(t *testing.T) { func TestCallContext_ExportedFunction(t *testing.T) {
host, err := NewHostModule("host", map[string]interface{}{"host_fn": func(api.Module) {}}, nil, map[string]*Memory{}, map[string]*Global{}, api.CoreFeaturesV1) host, err := NewHostModule("host", map[string]interface{}{"host_fn": func(api.Module) {}}, nil, api.CoreFeaturesV1)
require.NoError(t, err) require.NoError(t, err)
s, ns := newStore() s, ns := newStore()

View File

@@ -128,13 +128,13 @@ func (s *stackTrace) FromRecovered(recovered interface{}) error {
} }
// If we have a runtime.Error, something severe happened which should include the stack trace. This could be // If we have a runtime.Error, something severe happened which should include the stack trace. This could be
// a nil pointer from wazero or a user-defined function from ModuleBuilder. // a nil pointer from wazero or a user-defined function from HostModuleBuilder.
if runtimeErr, ok := recovered.(runtime.Error); ok { if runtimeErr, ok := recovered.(runtime.Error); ok {
// TODO: consider adding debug.Stack(), but last time we attempted, some tests became unstable. // TODO: consider adding debug.Stack(), but last time we attempted, some tests became unstable.
return fmt.Errorf("%w (recovered by wazero)\nwasm stack trace:\n\t%s", runtimeErr, stack) return fmt.Errorf("%w (recovered by wazero)\nwasm stack trace:\n\t%s", runtimeErr, stack)
} }
// At this point we expect the error was from a function defined by ModuleBuilder that intentionally called panic. // At this point we expect the error was from a function defined by HostModuleBuilder that intentionally called panic.
if runtimeErr, ok := recovered.(error); ok { // Ex. panic(errors.New("whoops")) if runtimeErr, ok := recovered.(error); ok { // Ex. panic(errors.New("whoops"))
return fmt.Errorf("%w (recovered by wazero)\nwasm stack trace:\n\t%s", runtimeErr, stack) return fmt.Errorf("%w (recovered by wazero)\nwasm stack trace:\n\t%s", runtimeErr, stack)
} else { // Ex. panic("whoops") } else { // Ex. panic("whoops")

View File

@@ -13,7 +13,7 @@ func TestRuntime_Namespace(t *testing.T) {
defer r.Close(testCtx) defer r.Close(testCtx)
// Compile a module to add to the runtime // Compile a module to add to the runtime
compiled, err := r.NewModuleBuilder("env").Compile(testCtx, NewCompileConfig()) compiled, err := r.NewHostModuleBuilder("env").Compile(testCtx)
require.NoError(t, err) require.NoError(t, err)
// Instantiate "env" into the runtime default namespace (base case) // Instantiate "env" into the runtime default namespace (base case)

View File

@@ -22,7 +22,7 @@ import (
// //
// module, _ := r.InstantiateModuleFromBinary(ctx, wasm) // module, _ := r.InstantiateModuleFromBinary(ctx, wasm)
type Runtime interface { type Runtime interface {
// NewModuleBuilder lets you create modules out of functions defined in Go. // NewHostModuleBuilder lets you create modules out of functions defined in Go.
// //
// Ex. Below defines and instantiates a module named "env" with one function: // Ex. Below defines and instantiates a module named "env" with one function:
// //
@@ -30,8 +30,8 @@ type Runtime interface {
// hello := func() { // hello := func() {
// fmt.Fprintln(stdout, "hello!") // fmt.Fprintln(stdout, "hello!")
// } // }
// _, err := r.NewModuleBuilder("env").ExportFunction("hello", hello).Instantiate(ctx, r) // _, err := r.NewHostModuleBuilder("env").ExportFunction("hello", hello).Instantiate(ctx, r)
NewModuleBuilder(moduleName string) ModuleBuilder NewHostModuleBuilder(moduleName string) HostModuleBuilder
// CompileModule decodes the WebAssembly binary (%.wasm) or errs if invalid. // CompileModule decodes the WebAssembly binary (%.wasm) or errs if invalid.
// Any pre-compilation done after decoding wasm is dependent on RuntimeConfig or CompileConfig. // Any pre-compilation done after decoding wasm is dependent on RuntimeConfig or CompileConfig.

View File

@@ -158,21 +158,20 @@ func TestRuntime_CompileModule_Errors(t *testing.T) {
func TestModule_Memory(t *testing.T) { func TestModule_Memory(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
builder func(Runtime) ModuleBuilder wasm []byte
expected bool expected bool
expectedLen uint32 expectedLen uint32
}{ }{
{ {
name: "no memory", name: "no memory",
builder: func(r Runtime) ModuleBuilder { wasm: binaryformat.EncodeModule(&wasm.Module{}),
return r.NewModuleBuilder(t.Name())
},
}, },
{ {
name: "memory exported, one page", name: "memory exported, one page",
builder: func(r Runtime) ModuleBuilder { wasm: binaryformat.EncodeModule(&wasm.Module{
return r.NewModuleBuilder(t.Name()).ExportMemory("memory", 1) MemorySection: &wasm.Memory{Min: 1},
}, ExportSection: []*wasm.Export{{Name: "memory", Type: api.ExternTypeMemory}},
}),
expected: true, expected: true,
expectedLen: 65536, expectedLen: 65536,
}, },
@@ -186,7 +185,7 @@ func TestModule_Memory(t *testing.T) {
defer r.Close(testCtx) defer r.Close(testCtx)
// Instantiate the module and get the export of the above memory // Instantiate the module and get the export of the above memory
module, err := tc.builder(r).Instantiate(testCtx, r) module, err := r.InstantiateModuleFromBinary(testCtx, tc.wasm)
require.NoError(t, err) require.NoError(t, err)
mem := module.ExportedMemory("memory") mem := module.ExportedMemory("memory")
@@ -206,7 +205,6 @@ func TestModule_Global(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
module *wasm.Module // module as wat doesn't yet support globals module *wasm.Module // module as wat doesn't yet support globals
builder func(Runtime) ModuleBuilder
expected, expectedMutable bool expected, expectedMutable bool
}{ }{
{ {
@@ -226,8 +224,16 @@ func TestModule_Global(t *testing.T) {
}, },
{ {
name: "global exported", name: "global exported",
builder: func(r Runtime) ModuleBuilder { module: &wasm.Module{
return r.NewModuleBuilder(t.Name()).ExportGlobalI64("global", globalVal) GlobalSection: []*wasm.Global{
{
Type: &wasm.GlobalType{ValType: wasm.ValueTypeI64},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI64Const, Data: leb128.EncodeInt64(globalVal)},
},
},
ExportSection: []*wasm.Export{
{Type: wasm.ExternTypeGlobal, Name: "global"},
},
}, },
expected: true, expected: true,
}, },
@@ -256,13 +262,7 @@ func TestModule_Global(t *testing.T) {
r := NewRuntime(testCtx).(*runtime) r := NewRuntime(testCtx).(*runtime)
defer r.Close(testCtx) defer r.Close(testCtx)
var m CompiledModule code := &compiledModule{module: tc.module}
if tc.module != nil {
m = &compiledModule{module: tc.module}
} else {
m, _ = tc.builder(r).Compile(testCtx, NewCompileConfig())
}
code := m.(*compiledModule)
err := r.store.Engine.CompileModule(testCtx, code.module) err := r.store.Engine.CompileModule(testCtx, code.module)
require.NoError(t, err) require.NoError(t, err)
@@ -299,7 +299,7 @@ func TestRuntime_InstantiateModule_UsesContext(t *testing.T) {
require.Equal(t, testCtx, ctx) require.Equal(t, testCtx, ctx)
} }
_, err := r.NewModuleBuilder("env"). _, err := r.NewHostModuleBuilder("env").
ExportFunction("start", start). ExportFunction("start", start).
Instantiate(testCtx, r) Instantiate(testCtx, r)
require.NoError(t, err) require.NoError(t, err)
@@ -376,7 +376,7 @@ func TestRuntime_InstantiateModuleFromBinary_ErrorOnStart(t *testing.T) {
panic(errors.New("ice cream")) panic(errors.New("ice cream"))
} }
host, err := r.NewModuleBuilder(""). host, err := r.NewHostModuleBuilder("").
ExportFunction("start", start). ExportFunction("start", start).
Instantiate(testCtx, r) Instantiate(testCtx, r)
require.NoError(t, err) require.NoError(t, err)
@@ -428,7 +428,7 @@ func TestRuntime_InstantiateModule_ExitError(t *testing.T) {
require.NoError(t, m.CloseWithExitCode(ctx, 2)) require.NoError(t, m.CloseWithExitCode(ctx, 2))
} }
_, err := r.NewModuleBuilder("env").ExportFunction("exit", start).Instantiate(testCtx, r) _, err := r.NewHostModuleBuilder("env").ExportFunction("exit", start).Instantiate(testCtx, r)
require.NoError(t, err) require.NoError(t, err)
one := uint32(1) one := uint32(1)