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:
@@ -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
|
||||
`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.
|
||||
|
||||
WASI implements system interfaces with host functions. Concretely, to write to console, a WASI command `Module` imports
|
||||
|
||||
@@ -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
|
||||
WebAssembly as the module "env" and the function name "log_i32".
|
||||
```go
|
||||
_, err := r.NewModuleBuilder("env").
|
||||
_, err := r.NewHostModuleBuilder("env").
|
||||
ExportFunction("log_i32", func(v uint32) {
|
||||
fmt.Println("log_i32 >>", v)
|
||||
}).
|
||||
|
||||
@@ -95,7 +95,7 @@ const (
|
||||
// (func (import "env" "f") (param externref) (result externref))
|
||||
//
|
||||
// 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 },
|
||||
// })
|
||||
//
|
||||
@@ -229,7 +229,7 @@ type FunctionDefinition interface {
|
||||
ExportNames() []string
|
||||
|
||||
// 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
|
||||
// has special properties not defined in the WebAssembly Core
|
||||
|
||||
219
builder.go
219
builder.go
@@ -2,15 +2,16 @@ package wazero
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/leb128"
|
||||
"github.com/tetratelabs/wazero/internal/u64"
|
||||
"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:
|
||||
//
|
||||
@@ -21,30 +22,47 @@ import (
|
||||
// hello := func() {
|
||||
// fmt.Fprintln(stdout, "hello!")
|
||||
// }
|
||||
// env, _ := r.NewModuleBuilder("env").
|
||||
// env, _ := r.NewHostModuleBuilder("env").
|
||||
// ExportFunction("hello", hello).
|
||||
// Instantiate(ctx, r)
|
||||
//
|
||||
// If the same module may be instantiated multiple times, it is more efficient
|
||||
// to separate steps. Ex.
|
||||
//
|
||||
// compiled, _ := r.NewModuleBuilder("env").
|
||||
// compiled, _ := r.NewHostModuleBuilder("env").
|
||||
// ExportFunction("get_random_string", getRandomString).
|
||||
// Compile(ctx, wazero.NewCompileConfig())
|
||||
// Compile(ctx)
|
||||
//
|
||||
// env1, _ := r.InstantiateModule(ctx, compiled, wazero.NewModuleConfig().WithName("env.1"))
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// - ModuleBuilder is mutable: each method returns the same instance for
|
||||
// - HostModuleBuilder is mutable: each method returns the same instance for
|
||||
// chaining.
|
||||
// - methods do not return errors, to allow chaining. Any validation errors
|
||||
// are deferred until Compile.
|
||||
// - Insertion order is not retained. Anything defined by this builder is
|
||||
// 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.
|
||||
|
||||
// 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",
|
||||
// "message", "fileName", "lineNumber", "columnNumber")
|
||||
//
|
||||
// Valid Signature
|
||||
// # Valid Signature
|
||||
//
|
||||
// Noting a context exception described later, all parameters or result
|
||||
// types must match WebAssembly 1.0 (20191205) value types. This means
|
||||
@@ -108,90 +126,15 @@ type ModuleBuilder interface {
|
||||
// --snip--
|
||||
//
|
||||
// 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(nameToGoFunc map[string]interface{}) ModuleBuilder
|
||||
|
||||
// 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
|
||||
ExportFunctions(nameToGoFunc map[string]interface{}) HostModuleBuilder
|
||||
|
||||
// Compile returns a CompiledModule that can instantiated in any namespace (Namespace).
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
@@ -204,7 +147,7 @@ type ModuleBuilder interface {
|
||||
// hello := func() {
|
||||
// fmt.Fprintln(stdout, "hello!")
|
||||
// }
|
||||
// env, _ := r.NewModuleBuilder("env").
|
||||
// env, _ := r.NewHostModuleBuilder("env").
|
||||
// ExportFunction("hello", hello).
|
||||
// Instantiate(ctx, r)
|
||||
//
|
||||
@@ -216,30 +159,26 @@ type ModuleBuilder interface {
|
||||
Instantiate(context.Context, Namespace) (api.Module, error)
|
||||
}
|
||||
|
||||
// moduleBuilder implements ModuleBuilder
|
||||
type moduleBuilder struct {
|
||||
// hostModuleBuilder implements HostModuleBuilder
|
||||
type hostModuleBuilder struct {
|
||||
r *runtime
|
||||
moduleName string
|
||||
nameToGoFunc map[string]interface{}
|
||||
funcToNames map[string][]string
|
||||
nameToMemory map[string]*wasm.Memory
|
||||
nameToGlobal map[string]*wasm.Global
|
||||
}
|
||||
|
||||
// NewModuleBuilder implements Runtime.NewModuleBuilder
|
||||
func (r *runtime) NewModuleBuilder(moduleName string) ModuleBuilder {
|
||||
return &moduleBuilder{
|
||||
// NewHostModuleBuilder implements Runtime.NewHostModuleBuilder
|
||||
func (r *runtime) NewHostModuleBuilder(moduleName string) HostModuleBuilder {
|
||||
return &hostModuleBuilder{
|
||||
r: r,
|
||||
moduleName: moduleName,
|
||||
nameToGoFunc: map[string]interface{}{},
|
||||
funcToNames: map[string][]string{},
|
||||
nameToMemory: map[string]*wasm.Memory{},
|
||||
nameToGlobal: map[string]*wasm.Global{},
|
||||
}
|
||||
}
|
||||
|
||||
// ExportFunction implements ModuleBuilder.ExportFunction
|
||||
func (b *moduleBuilder) ExportFunction(exportName string, goFunc interface{}, names ...string) ModuleBuilder {
|
||||
// ExportFunction implements HostModuleBuilder.ExportFunction
|
||||
func (b *hostModuleBuilder) ExportFunction(exportName string, goFunc interface{}, names ...string) HostModuleBuilder {
|
||||
b.nameToGoFunc[exportName] = goFunc
|
||||
if len(names) > 0 {
|
||||
b.funcToNames[exportName] = names
|
||||
@@ -247,81 +186,17 @@ func (b *moduleBuilder) ExportFunction(exportName string, goFunc interface{}, na
|
||||
return b
|
||||
}
|
||||
|
||||
// ExportFunctions implements ModuleBuilder.ExportFunctions
|
||||
func (b *moduleBuilder) ExportFunctions(nameToGoFunc map[string]interface{}) ModuleBuilder {
|
||||
// ExportFunctions implements HostModuleBuilder.ExportFunctions
|
||||
func (b *hostModuleBuilder) ExportFunctions(nameToGoFunc map[string]interface{}) HostModuleBuilder {
|
||||
for k, v := range nameToGoFunc {
|
||||
b.ExportFunction(k, v)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// ExportMemory implements ModuleBuilder.ExportMemory
|
||||
func (b *moduleBuilder) ExportMemory(name string, minPages uint32) ModuleBuilder {
|
||||
b.nameToMemory[name] = &wasm.Memory{Min: minPages}
|
||||
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)
|
||||
// Compile implements HostModuleBuilder.Compile
|
||||
func (b *hostModuleBuilder) Compile(ctx context.Context) (CompiledModule, error) {
|
||||
module, err := wasm.NewHostModule(b.moduleName, b.nameToGoFunc, b.funcToNames, b.r.enabledFeatures)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} 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
|
||||
}
|
||||
|
||||
// Instantiate implements ModuleBuilder.Instantiate
|
||||
func (b *moduleBuilder) Instantiate(ctx context.Context, ns Namespace) (api.Module, error) {
|
||||
if compiled, err := b.Compile(ctx, NewCompileConfig()); err != nil {
|
||||
// Instantiate implements HostModuleBuilder.Instantiate
|
||||
func (b *hostModuleBuilder) Instantiate(ctx context.Context, ns Namespace) (api.Module, error) {
|
||||
if compiled, err := b.Compile(ctx); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
compiled.(*compiledModule).closeWithModule = true
|
||||
|
||||
279
builder_test.go
279
builder_test.go
@@ -1,18 +1,15 @@
|
||||
package wazero
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/leb128"
|
||||
"github.com/tetratelabs/wazero/internal/testing/require"
|
||||
"github.com/tetratelabs/wazero/internal/u64"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
// TestNewModuleBuilder_Compile only covers a few scenarios to avoid duplicating tests in internal/wasm/host_test.go
|
||||
func TestNewModuleBuilder_Compile(t *testing.T) {
|
||||
// TestNewHostModuleBuilder_Compile only covers a few scenarios to avoid duplicating tests in internal/wasm/host_test.go
|
||||
func TestNewHostModuleBuilder_Compile(t *testing.T) {
|
||||
i32, i64 := api.ValueTypeI32, api.ValueTypeI64
|
||||
|
||||
uint32_uint32 := func(uint32) uint32 {
|
||||
@@ -24,27 +21,27 @@ func TestNewModuleBuilder_Compile(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input func(Runtime) ModuleBuilder
|
||||
input func(Runtime) HostModuleBuilder
|
||||
expected *wasm.Module
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
input: func(r Runtime) ModuleBuilder {
|
||||
return r.NewModuleBuilder("")
|
||||
input: func(r Runtime) HostModuleBuilder {
|
||||
return r.NewHostModuleBuilder("")
|
||||
},
|
||||
expected: &wasm.Module{},
|
||||
},
|
||||
{
|
||||
name: "only name",
|
||||
input: func(r Runtime) ModuleBuilder {
|
||||
return r.NewModuleBuilder("env")
|
||||
input: func(r Runtime) HostModuleBuilder {
|
||||
return r.NewHostModuleBuilder("env")
|
||||
},
|
||||
expected: &wasm.Module{NameSection: &wasm.NameSection{ModuleName: "env"}},
|
||||
},
|
||||
{
|
||||
name: "ExportFunction",
|
||||
input: func(r Runtime) ModuleBuilder {
|
||||
return r.NewModuleBuilder("").ExportFunction("1", uint32_uint32)
|
||||
input: func(r Runtime) HostModuleBuilder {
|
||||
return r.NewHostModuleBuilder("").ExportFunction("1", uint32_uint32)
|
||||
},
|
||||
expected: &wasm.Module{
|
||||
TypeSection: []*wasm.FunctionType{
|
||||
@@ -62,8 +59,8 @@ func TestNewModuleBuilder_Compile(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "ExportFunction with names",
|
||||
input: func(r Runtime) ModuleBuilder {
|
||||
return r.NewModuleBuilder("").ExportFunction("1", uint32_uint32, "get", "x")
|
||||
input: func(r Runtime) HostModuleBuilder {
|
||||
return r.NewHostModuleBuilder("").ExportFunction("1", uint32_uint32, "get", "x")
|
||||
},
|
||||
expected: &wasm.Module{
|
||||
TypeSection: []*wasm.FunctionType{
|
||||
@@ -82,8 +79,8 @@ func TestNewModuleBuilder_Compile(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "ExportFunction overwrites existing",
|
||||
input: func(r Runtime) ModuleBuilder {
|
||||
return r.NewModuleBuilder("").ExportFunction("1", uint32_uint32).ExportFunction("1", uint64_uint32)
|
||||
input: func(r Runtime) HostModuleBuilder {
|
||||
return r.NewHostModuleBuilder("").ExportFunction("1", uint32_uint32).ExportFunction("1", uint64_uint32)
|
||||
},
|
||||
expected: &wasm.Module{
|
||||
TypeSection: []*wasm.FunctionType{
|
||||
@@ -101,9 +98,9 @@ func TestNewModuleBuilder_Compile(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "ExportFunction twice",
|
||||
input: func(r Runtime) ModuleBuilder {
|
||||
input: func(r Runtime) HostModuleBuilder {
|
||||
// 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{
|
||||
TypeSection: []*wasm.FunctionType{
|
||||
@@ -123,8 +120,8 @@ func TestNewModuleBuilder_Compile(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "ExportFunctions",
|
||||
input: func(r Runtime) ModuleBuilder {
|
||||
return r.NewModuleBuilder("").ExportFunctions(map[string]interface{}{
|
||||
input: func(r Runtime) HostModuleBuilder {
|
||||
return r.NewHostModuleBuilder("").ExportFunctions(map[string]interface{}{
|
||||
"1": uint32_uint32,
|
||||
"2": uint64_uint32,
|
||||
})
|
||||
@@ -147,8 +144,8 @@ func TestNewModuleBuilder_Compile(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "ExportFunctions overwrites",
|
||||
input: func(r Runtime) ModuleBuilder {
|
||||
b := r.NewModuleBuilder("").ExportFunction("1", uint64_uint32)
|
||||
input: func(r Runtime) HostModuleBuilder {
|
||||
b := r.NewHostModuleBuilder("").ExportFunction("1", uint64_uint32)
|
||||
return b.ExportFunctions(map[string]interface{}{
|
||||
"1": uint32_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 {
|
||||
tc := tt
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
b := tc.input(NewRuntime(testCtx)).(*moduleBuilder)
|
||||
compiled, err := b.Compile(testCtx, NewCompileConfig())
|
||||
b := tc.input(NewRuntime(testCtx)).(*hostModuleBuilder)
|
||||
compiled, err := b.Compile(testCtx)
|
||||
require.NoError(t, err)
|
||||
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
|
||||
func TestNewModuleBuilder_Compile_Errors(t *testing.T) {
|
||||
// TestNewHostModuleBuilder_Compile_Errors only covers a few scenarios to avoid
|
||||
// duplicating tests in internal/wasm/host_test.go
|
||||
func TestNewHostModuleBuilder_Compile_Errors(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input func(Runtime) ModuleBuilder
|
||||
config CompileConfig
|
||||
input func(Runtime) HostModuleBuilder
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "memory min > limit", // only one test to avoid duplicating tests in module_test.go
|
||||
input: func(rt Runtime) ModuleBuilder {
|
||||
return rt.NewModuleBuilder("").ExportMemory("memory", math.MaxUint32)
|
||||
name: "error compiling", // should fail due to missing result.
|
||||
input: func(rt Runtime) HostModuleBuilder {
|
||||
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: "memory[memory] min 4294967295 pages (3 Ti) over limit of 65536 pages (4 Gi)",
|
||||
},
|
||||
{
|
||||
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)",
|
||||
expectedErr: `invalid function[0] export["fn"]: not enough results
|
||||
have ()
|
||||
want (i32)`,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -412,16 +221,16 @@ func TestNewModuleBuilder_Compile_Errors(t *testing.T) {
|
||||
tc := tt
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestNewModuleBuilder_Instantiate ensures Runtime.InstantiateModule is called on success.
|
||||
func TestNewModuleBuilder_Instantiate(t *testing.T) {
|
||||
// TestNewHostModuleBuilder_Instantiate ensures Runtime.InstantiateModule is called on success.
|
||||
func TestNewHostModuleBuilder_Instantiate(t *testing.T) {
|
||||
r := NewRuntime(testCtx)
|
||||
m, err := r.NewModuleBuilder("env").Instantiate(testCtx, r)
|
||||
m, err := r.NewHostModuleBuilder("env").Instantiate(testCtx, r)
|
||||
require.NoError(t, err)
|
||||
|
||||
// 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())
|
||||
}
|
||||
|
||||
// TestNewModuleBuilder_Instantiate_Errors ensures errors propagate from Runtime.InstantiateModule
|
||||
func TestNewModuleBuilder_Instantiate_Errors(t *testing.T) {
|
||||
// TestNewHostModuleBuilder_Instantiate_Errors ensures errors propagate from Runtime.InstantiateModule
|
||||
func TestNewHostModuleBuilder_Instantiate_Errors(t *testing.T) {
|
||||
r := NewRuntime(testCtx)
|
||||
_, err := r.NewModuleBuilder("env").Instantiate(testCtx, r)
|
||||
_, err := r.NewHostModuleBuilder("env").Instantiate(testCtx, r)
|
||||
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")
|
||||
}
|
||||
|
||||
|
||||
@@ -165,7 +165,7 @@ func (c *compiledModule) ExportedFunctions() map[string]api.FunctionDefinition {
|
||||
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).
|
||||
//
|
||||
// For example, WithMemorySizer allows you to override memory size that doesn't match your requirements.
|
||||
|
||||
@@ -30,7 +30,7 @@ func main() {
|
||||
|
||||
// Instantiate a Go-defined module named "env" that exports a function to
|
||||
// log to the console.
|
||||
_, err := r.NewModuleBuilder("env").
|
||||
_, err := r.NewHostModuleBuilder("env").
|
||||
ExportFunction("log", logString).
|
||||
Instantiate(ctx, r)
|
||||
if err != nil {
|
||||
|
||||
@@ -31,7 +31,7 @@ func main() {
|
||||
|
||||
// Instantiate a Go-defined module named "env" that exports a function to
|
||||
// log to the console.
|
||||
_, err := r.NewModuleBuilder("env").
|
||||
_, err := r.NewHostModuleBuilder("env").
|
||||
ExportFunction("log", logString).
|
||||
Instantiate(ctx, r)
|
||||
if err != nil {
|
||||
|
||||
@@ -36,7 +36,7 @@ func run() error {
|
||||
|
||||
// Instantiate a Go-defined module named "env" that exports a function to
|
||||
// log to the console.
|
||||
_, err := r.NewModuleBuilder("env").
|
||||
_, err := r.NewHostModuleBuilder("env").
|
||||
ExportFunction("log", logString).
|
||||
Instantiate(ctx, r)
|
||||
if err != nil {
|
||||
|
||||
@@ -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.
|
||||
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
|
||||
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)
|
||||
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
|
||||
|
||||
@@ -38,7 +38,7 @@ func main() {
|
||||
// constrained to a subset of numeric types.
|
||||
// Note: "env" is a module name conventionally used for arbitrary
|
||||
// host-defined functions, but any name would do.
|
||||
_, err := r.NewModuleBuilder("env").
|
||||
_, err := r.NewHostModuleBuilder("env").
|
||||
ExportFunction("log_i32", func(v uint32) {
|
||||
fmt.Println("log_i32 >>", v)
|
||||
}).
|
||||
|
||||
@@ -109,7 +109,7 @@ var multiValueFromImportedHostWasm []byte
|
||||
// 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) {
|
||||
// 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
|
||||
ExportFunction("get_age", func() (age uint64, errno uint32) {
|
||||
age = 37
|
||||
|
||||
@@ -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.
|
||||
c := &counter{}
|
||||
_, err := r.NewModuleBuilder("env").
|
||||
_, err := r.NewHostModuleBuilder("env").
|
||||
ExportFunction("next_i32", c.getAndIncrement).
|
||||
Instantiate(ctx, ns)
|
||||
if err != nil {
|
||||
|
||||
@@ -54,7 +54,7 @@ const (
|
||||
// - To add more functions to the "env" module, use FunctionExporter.
|
||||
// - To instantiate into another wazero.Namespace, use FunctionExporter.
|
||||
func Instantiate(ctx context.Context, r wazero.Runtime) (api.Closer, error) {
|
||||
builder := r.NewModuleBuilder("env")
|
||||
builder := r.NewHostModuleBuilder("env")
|
||||
NewFunctionExporter().ExportFunctions(builder)
|
||||
return builder.Instantiate(ctx, r)
|
||||
}
|
||||
@@ -77,9 +77,9 @@ type FunctionExporter interface {
|
||||
// appropriate to use WithTraceToStdout instead.
|
||||
WithTraceToStderr() FunctionExporter
|
||||
|
||||
// ExportFunctions builds functions to export with a wazero.ModuleBuilder
|
||||
// ExportFunctions builds functions to export with a wazero.HostModuleBuilder
|
||||
// named "env".
|
||||
ExportFunctions(builder wazero.ModuleBuilder)
|
||||
ExportFunctions(builder wazero.HostModuleBuilder)
|
||||
}
|
||||
|
||||
// NewFunctionExporter returns a FunctionExporter object with trace disabled.
|
||||
@@ -107,7 +107,7 @@ func (e *functionExporter) WithTraceToStderr() FunctionExporter {
|
||||
}
|
||||
|
||||
// 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(functionTrace, e.traceFn)
|
||||
builder.ExportFunction(functionSeed, seed)
|
||||
|
||||
@@ -34,7 +34,7 @@ func Example_functionExporter() {
|
||||
defer r.Close(ctx) // This closes everything this Runtime created.
|
||||
|
||||
// First construct your own module builder for "env"
|
||||
envBuilder := r.NewModuleBuilder("env").
|
||||
envBuilder := r.NewHostModuleBuilder("env").
|
||||
ExportFunction("get_int", func() uint32 { return 1 })
|
||||
|
||||
// Now, add AssemblyScript special function imports into it.
|
||||
|
||||
@@ -424,10 +424,10 @@ func requireProxyModule(t *testing.T, fns FunctionExporter, config wazero.Module
|
||||
|
||||
r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfigInterpreter())
|
||||
|
||||
builder := r.NewModuleBuilder("env")
|
||||
builder := r.NewHostModuleBuilder("env")
|
||||
fns.ExportFunctions(builder)
|
||||
|
||||
envCompiled, err := builder.Compile(ctx, wazero.NewCompileConfig())
|
||||
envCompiled, err := builder.Compile(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = r.InstantiateModule(ctx, envCompiled, config)
|
||||
|
||||
@@ -29,7 +29,7 @@ import (
|
||||
// - To add more functions to the "env" module, use FunctionExporter.
|
||||
// - To instantiate into another wazero.Namespace, use FunctionExporter.
|
||||
func Instantiate(ctx context.Context, r wazero.Runtime) (api.Closer, error) {
|
||||
builder := r.NewModuleBuilder("env")
|
||||
builder := r.NewHostModuleBuilder("env")
|
||||
NewFunctionExporter().ExportFunctions(builder)
|
||||
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
|
||||
// Emscripten.
|
||||
type FunctionExporter interface {
|
||||
// ExportFunctions builds functions to export with a wazero.ModuleBuilder
|
||||
// ExportFunctions builds functions to export with a wazero.HostModuleBuilder
|
||||
// named "env".
|
||||
ExportFunctions(builder wazero.ModuleBuilder)
|
||||
ExportFunctions(builder wazero.HostModuleBuilder)
|
||||
}
|
||||
|
||||
// NewFunctionExporter returns a FunctionExporter object with trace disabled.
|
||||
@@ -50,7 +50,7 @@ func NewFunctionExporter() FunctionExporter {
|
||||
type functionExporter struct{}
|
||||
|
||||
// ExportFunctions implements FunctionExporter.ExportFunctions
|
||||
func (e *functionExporter) ExportFunctions(builder wazero.ModuleBuilder) {
|
||||
func (e *functionExporter) ExportFunctions(builder wazero.HostModuleBuilder) {
|
||||
builder.ExportFunction(notifyMemoryGrowth.Name, notifyMemoryGrowth)
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ func Example_functionExporter() {
|
||||
|
||||
// Next, construct your own module builder for "env" with any functions
|
||||
// you need.
|
||||
envBuilder := r.NewModuleBuilder("env").
|
||||
envBuilder := r.NewHostModuleBuilder("env").
|
||||
ExportFunction("get_int", func() uint32 { return 1 })
|
||||
|
||||
// Now, add Emscripten special function imports into it.
|
||||
|
||||
@@ -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.
|
||||
func Run(ctx context.Context, r wazero.Runtime, compiled wazero.CompiledModule, config wazero.ModuleConfig) error {
|
||||
// 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 {
|
||||
return err
|
||||
}
|
||||
@@ -86,9 +86,9 @@ func Run(ctx context.Context, r wazero.Runtime, compiled wazero.CompiledModule,
|
||||
return err
|
||||
}
|
||||
|
||||
// moduleBuilder returns a new wazero.ModuleBuilder
|
||||
func moduleBuilder(r wazero.Runtime) wazero.ModuleBuilder {
|
||||
return r.NewModuleBuilder("go").
|
||||
// hostModuleBuilder returns a new wazero.HostModuleBuilder
|
||||
func hostModuleBuilder(r wazero.Runtime) wazero.HostModuleBuilder {
|
||||
return r.NewHostModuleBuilder("go").
|
||||
ExportFunction(GetRandomData.Name(), GetRandomData).
|
||||
ExportFunction(Nanotime1.Name(), Nanotime1).
|
||||
ExportFunction(WasmExit.Name(), WasmExit).
|
||||
|
||||
@@ -47,12 +47,12 @@ type Builder interface {
|
||||
// Compile compiles the ModuleName module that can instantiated in any
|
||||
// namespace (wazero.Namespace).
|
||||
//
|
||||
// Note: This has the same effect as the same function on wazero.ModuleBuilder.
|
||||
Compile(context.Context, wazero.CompileConfig) (wazero.CompiledModule, error)
|
||||
// Note: This has the same effect as the same function on wazero.HostModuleBuilder.
|
||||
Compile(context.Context) (wazero.CompiledModule, error)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
@@ -63,21 +63,21 @@ func NewBuilder(r wazero.Runtime) Builder {
|
||||
|
||||
type builder struct{ r wazero.Runtime }
|
||||
|
||||
// moduleBuilder returns a new wazero.ModuleBuilder for ModuleName
|
||||
func (b *builder) moduleBuilder() wazero.ModuleBuilder {
|
||||
ret := b.r.NewModuleBuilder(ModuleName)
|
||||
// hostModuleBuilder returns a new wazero.HostModuleBuilder for ModuleName
|
||||
func (b *builder) hostModuleBuilder() wazero.HostModuleBuilder {
|
||||
ret := b.r.NewHostModuleBuilder(ModuleName)
|
||||
exportFunctions(ret)
|
||||
return ret
|
||||
}
|
||||
|
||||
// Compile implements Builder.Compile
|
||||
func (b *builder) Compile(ctx context.Context, config wazero.CompileConfig) (wazero.CompiledModule, error) {
|
||||
return b.moduleBuilder().Compile(ctx, config)
|
||||
func (b *builder) Compile(ctx context.Context) (wazero.CompiledModule, error) {
|
||||
return b.hostModuleBuilder().Compile(ctx)
|
||||
}
|
||||
|
||||
// Instantiate implements Builder.Instantiate
|
||||
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
|
||||
@@ -109,7 +109,7 @@ func (b *builder) Instantiate(ctx context.Context, ns wazero.Namespace) (api.Clo
|
||||
|
||||
// exportFunctions adds all go functions that implement wasi.
|
||||
// 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
|
||||
// map can't guarantee that.
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#functions
|
||||
|
||||
@@ -4,7 +4,10 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/testing/require"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
binaryformat "github.com/tetratelabs/wazero/internal/wasm/binary"
|
||||
)
|
||||
|
||||
var testMem = []byte{
|
||||
@@ -37,11 +40,13 @@ func Test_Benchmark_EnvironGet(t *testing.T) {
|
||||
}
|
||||
|
||||
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()).
|
||||
ExportMemoryWithMax("memory", 1, 1).
|
||||
Compile(testCtx, wazero.NewCompileConfig())
|
||||
compiled, err := r.CompileModule(testCtx, binaryformat.EncodeModule(&wasm.Module{
|
||||
MemorySection: &wasm.Memory{Min: 1, Max: 1},
|
||||
ExportSection: []*wasm.Export{{Name: "memory", Type: api.ExternTypeMemory}},
|
||||
}), wazero.NewCompileConfig())
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -34,8 +34,7 @@ func requireProxyModule(t *testing.T, config wazero.ModuleConfig) (api.Module, a
|
||||
|
||||
r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfigInterpreter())
|
||||
|
||||
wasiModuleCompiled, err := (&builder{r}).moduleBuilder().
|
||||
Compile(ctx, wazero.NewCompileConfig())
|
||||
wasiModuleCompiled, err := (&builder{r}).hostModuleBuilder().Compile(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = r.InstantiateModule(ctx, wasiModuleCompiled, config)
|
||||
@@ -65,8 +64,7 @@ func requireErrnoNosys(t *testing.T, funcName string, params ...uint64) string {
|
||||
defer r.Close(ctx)
|
||||
|
||||
// Instantiate the wasi module.
|
||||
wasiModuleCompiled, err := (&builder{r}).moduleBuilder().
|
||||
Compile(ctx, wazero.NewCompileConfig())
|
||||
wasiModuleCompiled, err := (&builder{r}).hostModuleBuilder().Compile(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = r.InstantiateModule(ctx, wasiModuleCompiled, wazero.NewModuleConfig())
|
||||
|
||||
@@ -210,7 +210,7 @@ func TestCompiler_SliceAllocatedOnHeap(t *testing.T) {
|
||||
// Trigger relocation of goroutine stack because at this point we have the majority of
|
||||
// goroutine stack unused after recursive call.
|
||||
runtime.GC()
|
||||
}}, nil, map[string]*wasm.Memory{}, map[string]*wasm.Global{}, enabledFeatures)
|
||||
}}, nil, enabledFeatures)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = s.Engine.CompileModule(testCtx, hm)
|
||||
|
||||
@@ -197,7 +197,7 @@ func TestMustCallFromSP(t *testing.T) {
|
||||
defer r.Close(testCtx)
|
||||
|
||||
funcName := "i64i32i32i32i32_i64i32_withSP"
|
||||
im, err := r.NewModuleBuilder("go").
|
||||
im, err := r.NewHostModuleBuilder("go").
|
||||
ExportFunction(funcName, MustCallFromSP(true, wasm.NewGoFunc(
|
||||
funcName, funcName,
|
||||
[]string{"v", "mAddr", "mLen", "argsArray", "argsLen"},
|
||||
|
||||
@@ -215,7 +215,7 @@ func createRuntime(b *testing.B, config wazero.RuntimeConfig) wazero.Runtime {
|
||||
|
||||
r := wazero.NewRuntimeWithConfig(testCtx, config)
|
||||
|
||||
_, err := r.NewModuleBuilder("env").
|
||||
_, err := r.NewHostModuleBuilder("env").
|
||||
ExportFunction("get_random_string", getRandomString).
|
||||
Instantiate(testCtx, r)
|
||||
if err != nil {
|
||||
|
||||
@@ -99,7 +99,7 @@ func testReftypeImports(t *testing.T, r wazero.Runtime) {
|
||||
}
|
||||
|
||||
hostObj := &dog{name: "hello"}
|
||||
host, err := r.NewModuleBuilder("host").
|
||||
host, err := r.NewHostModuleBuilder("host").
|
||||
ExportFunctions(map[string]interface{}{
|
||||
"externref": func(externrefFromRefNull uintptr) uintptr {
|
||||
require.Zero(t, externrefFromRefNull)
|
||||
@@ -176,7 +176,7 @@ func testUnreachable(t *testing.T, r wazero.Runtime) {
|
||||
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)
|
||||
|
||||
module, err := r.InstantiateModuleFromBinary(testCtx, unreachableWasm)
|
||||
@@ -199,7 +199,7 @@ func testRecursiveEntry(t *testing.T, r wazero.Runtime) {
|
||||
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)
|
||||
|
||||
module, err := r.InstantiateModuleFromBinary(testCtx, recursiveWasm)
|
||||
@@ -222,7 +222,7 @@ func testHostFuncMemory(t *testing.T, r wazero.Runtime) {
|
||||
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)
|
||||
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)
|
||||
defer imported.Close(testCtx)
|
||||
|
||||
@@ -299,7 +299,7 @@ func testHostFunctionContextParameter(t *testing.T, r wazero.Runtime) {
|
||||
|
||||
for test := range fns {
|
||||
t.Run(test, func(t *testing.T) {
|
||||
imported, err := r.NewModuleBuilder(importedName).
|
||||
imported, err := r.NewHostModuleBuilder(importedName).
|
||||
ExportFunction("return_input", fns[test]).
|
||||
Instantiate(testCtx, r)
|
||||
require.NoError(t, err)
|
||||
@@ -369,7 +369,7 @@ func testHostFunctionNumericParameter(t *testing.T, r wazero.Runtime) {
|
||||
},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
imported, err := r.NewModuleBuilder(importedName).
|
||||
imported, err := r.NewHostModuleBuilder(importedName).
|
||||
ExportFunction("return_input", fns[test.name]).
|
||||
Instantiate(testCtx, r)
|
||||
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.
|
||||
importedCode, err = r.NewModuleBuilder(t.Name()+"-imported").
|
||||
ExportFunction("return_input", closeAndReturn).Compile(testCtx, compileConfig)
|
||||
importedCode, err = r.NewHostModuleBuilder(t.Name()+"-imported").
|
||||
ExportFunction("return_input", closeAndReturn).Compile(testCtx)
|
||||
require.NoError(t, err)
|
||||
|
||||
imported, err = r.InstantiateModule(testCtx, importedCode, moduleConfig)
|
||||
|
||||
@@ -49,7 +49,7 @@ func closeImportedModuleWhileInUse(t *testing.T, r wazero.Runtime) {
|
||||
require.NoError(t, imported.Close(testCtx))
|
||||
|
||||
// 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
|
||||
}).Instantiate(testCtx, r)
|
||||
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.
|
||||
imported, err := r.NewModuleBuilder(t.Name()+"-imported").
|
||||
imported, err := r.NewHostModuleBuilder(t.Name()+"-imported").
|
||||
ExportFunction("return_input", blockAndReturn).Instantiate(testCtx, r)
|
||||
require.NoError(t, err)
|
||||
defer imported.Close(testCtx)
|
||||
|
||||
@@ -93,18 +93,18 @@ func (r *wazeroRuntime) Compile(ctx context.Context, cfg *RuntimeConfig) (err er
|
||||
r.runtime = wazero.NewRuntimeWithConfig(ctx, r.config)
|
||||
if cfg.LogFn != nil {
|
||||
r.logFn = cfg.LogFn
|
||||
if r.env, err = r.runtime.NewModuleBuilder("env").
|
||||
ExportFunction("log", r.log).Compile(ctx, wazero.NewCompileConfig()); err != nil {
|
||||
if r.env, err = r.runtime.NewHostModuleBuilder("env").
|
||||
ExportFunction("log", r.log).Compile(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if cfg.EnvFReturnValue != 0 {
|
||||
if r.env, err = r.runtime.NewModuleBuilder("env").
|
||||
if r.env, err = r.runtime.NewHostModuleBuilder("env").
|
||||
ExportFunction("f",
|
||||
// Note: accepting (context.Context, api.Module) is the slowest type of host function with wazero.
|
||||
func(context.Context, api.Module, uint64) uint64 {
|
||||
return cfg.EnvFReturnValue
|
||||
},
|
||||
).Compile(ctx, wazero.NewCompileConfig()); err != nil {
|
||||
).Compile(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 untested because it is a lot of work to do that. The rationale to punt is this is a test-only internal
|
||||
|
||||
@@ -114,7 +114,7 @@ func (m *CallContext) close(ctx context.Context, exitCode uint32) (c bool, err e
|
||||
return false, nil
|
||||
}
|
||||
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)
|
||||
}
|
||||
return
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/wasmdebug"
|
||||
@@ -91,8 +90,6 @@ func NewHostModule(
|
||||
moduleName string,
|
||||
nameToGoFunc map[string]interface{},
|
||||
funcToNames map[string][]string,
|
||||
nameToMemory map[string]*Memory,
|
||||
nameToGlobal map[string]*Global,
|
||||
enabledFeatures api.CoreFeatures,
|
||||
) (m *Module, err error) {
|
||||
if moduleName != "" {
|
||||
@@ -101,52 +98,15 @@ func NewHostModule(
|
||||
m = &Module{}
|
||||
}
|
||||
|
||||
funcCount := uint32(len(nameToGoFunc))
|
||||
memoryCount := uint32(len(nameToMemory))
|
||||
globalCount := uint32(len(nameToGlobal))
|
||||
exportCount := funcCount + memoryCount + globalCount
|
||||
if exportCount > 0 {
|
||||
if exportCount := uint32(len(nameToGoFunc)); exportCount > 0 {
|
||||
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 {
|
||||
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.
|
||||
m.AssignModuleID([]byte(fmt.Sprintf("%s:%v:%v:%v:%v",
|
||||
moduleName, nameToGoFunc, nameToMemory, nameToGlobal, enabledFeatures)))
|
||||
m.AssignModuleID([]byte(fmt.Sprintf("%s:%v:%v", moduleName, nameToGoFunc, enabledFeatures)))
|
||||
m.BuildFunctionDefinitions()
|
||||
return
|
||||
}
|
||||
@@ -264,50 +224,6 @@ func addFuncs(
|
||||
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) {
|
||||
if len(results) > 1 {
|
||||
// Guard >1.0 feature multi-value
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/leb128"
|
||||
"github.com/tetratelabs/wazero/internal/testing/require"
|
||||
)
|
||||
|
||||
@@ -12,10 +11,6 @@ import (
|
||||
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 {
|
||||
return 0
|
||||
}
|
||||
@@ -37,8 +32,6 @@ func TestNewHostModule(t *testing.T) {
|
||||
tests := []struct {
|
||||
name, moduleName string
|
||||
nameToGoFunc map[string]interface{}
|
||||
nameToMemory map[string]*Memory
|
||||
nameToGlobal map[string]*Global
|
||||
expected *Module
|
||||
}{
|
||||
{
|
||||
@@ -91,91 +84,13 @@ func TestNewHostModule(t *testing.T) {
|
||||
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 {
|
||||
tc := tt
|
||||
|
||||
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)
|
||||
require.NoError(t, e)
|
||||
requireHostModuleEquals(t, tc.expected, m)
|
||||
@@ -216,8 +131,6 @@ func TestNewHostModule_Errors(t *testing.T) {
|
||||
tests := []struct {
|
||||
name, moduleName string
|
||||
nameToGoFunc map[string]interface{}
|
||||
nameToMemory map[string]*Memory
|
||||
nameToGlobal map[string]*Global
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
@@ -228,38 +141,15 @@ func TestNewHostModule_Errors(t *testing.T) {
|
||||
{
|
||||
name: "function has multiple results",
|
||||
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",
|
||||
},
|
||||
{
|
||||
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 {
|
||||
tc := tt
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -805,7 +805,7 @@ type Export struct {
|
||||
// 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.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
|
||||
// different from its defining module.
|
||||
|
||||
@@ -91,7 +91,7 @@ func TestModuleInstance_Memory(t *testing.T) {
|
||||
|
||||
func TestStore_Instantiate(t *testing.T) {
|
||||
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)
|
||||
|
||||
sysCtx := sys.DefaultContext(nil)
|
||||
@@ -169,7 +169,7 @@ func TestStore_CloseWithExitCode(t *testing.T) {
|
||||
func TestStore_hammer(t *testing.T) {
|
||||
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)
|
||||
|
||||
s, ns := newStore()
|
||||
@@ -223,7 +223,7 @@ func TestStore_Instantiate_Errors(t *testing.T) {
|
||||
const importedModuleName = "imported"
|
||||
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)
|
||||
|
||||
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) {
|
||||
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)
|
||||
|
||||
s, ns := newStore()
|
||||
|
||||
@@ -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
|
||||
// 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 {
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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"))
|
||||
return fmt.Errorf("%w (recovered by wazero)\nwasm stack trace:\n\t%s", runtimeErr, stack)
|
||||
} else { // Ex. panic("whoops")
|
||||
|
||||
@@ -13,7 +13,7 @@ func TestRuntime_Namespace(t *testing.T) {
|
||||
defer r.Close(testCtx)
|
||||
|
||||
// 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)
|
||||
|
||||
// Instantiate "env" into the runtime default namespace (base case)
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
//
|
||||
// module, _ := r.InstantiateModuleFromBinary(ctx, wasm)
|
||||
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:
|
||||
//
|
||||
@@ -30,8 +30,8 @@ type Runtime interface {
|
||||
// hello := func() {
|
||||
// fmt.Fprintln(stdout, "hello!")
|
||||
// }
|
||||
// _, err := r.NewModuleBuilder("env").ExportFunction("hello", hello).Instantiate(ctx, r)
|
||||
NewModuleBuilder(moduleName string) ModuleBuilder
|
||||
// _, err := r.NewHostModuleBuilder("env").ExportFunction("hello", hello).Instantiate(ctx, r)
|
||||
NewHostModuleBuilder(moduleName string) HostModuleBuilder
|
||||
|
||||
// CompileModule decodes the WebAssembly binary (%.wasm) or errs if invalid.
|
||||
// Any pre-compilation done after decoding wasm is dependent on RuntimeConfig or CompileConfig.
|
||||
|
||||
@@ -158,21 +158,20 @@ func TestRuntime_CompileModule_Errors(t *testing.T) {
|
||||
func TestModule_Memory(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
builder func(Runtime) ModuleBuilder
|
||||
wasm []byte
|
||||
expected bool
|
||||
expectedLen uint32
|
||||
}{
|
||||
{
|
||||
name: "no memory",
|
||||
builder: func(r Runtime) ModuleBuilder {
|
||||
return r.NewModuleBuilder(t.Name())
|
||||
},
|
||||
wasm: binaryformat.EncodeModule(&wasm.Module{}),
|
||||
},
|
||||
{
|
||||
name: "memory exported, one page",
|
||||
builder: func(r Runtime) ModuleBuilder {
|
||||
return r.NewModuleBuilder(t.Name()).ExportMemory("memory", 1)
|
||||
},
|
||||
wasm: binaryformat.EncodeModule(&wasm.Module{
|
||||
MemorySection: &wasm.Memory{Min: 1},
|
||||
ExportSection: []*wasm.Export{{Name: "memory", Type: api.ExternTypeMemory}},
|
||||
}),
|
||||
expected: true,
|
||||
expectedLen: 65536,
|
||||
},
|
||||
@@ -186,7 +185,7 @@ func TestModule_Memory(t *testing.T) {
|
||||
defer r.Close(testCtx)
|
||||
|
||||
// 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)
|
||||
|
||||
mem := module.ExportedMemory("memory")
|
||||
@@ -206,7 +205,6 @@ func TestModule_Global(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
module *wasm.Module // module as wat doesn't yet support globals
|
||||
builder func(Runtime) ModuleBuilder
|
||||
expected, expectedMutable bool
|
||||
}{
|
||||
{
|
||||
@@ -226,8 +224,16 @@ func TestModule_Global(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "global exported",
|
||||
builder: func(r Runtime) ModuleBuilder {
|
||||
return r.NewModuleBuilder(t.Name()).ExportGlobalI64("global", globalVal)
|
||||
module: &wasm.Module{
|
||||
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,
|
||||
},
|
||||
@@ -256,13 +262,7 @@ func TestModule_Global(t *testing.T) {
|
||||
r := NewRuntime(testCtx).(*runtime)
|
||||
defer r.Close(testCtx)
|
||||
|
||||
var m CompiledModule
|
||||
if tc.module != nil {
|
||||
m = &compiledModule{module: tc.module}
|
||||
} else {
|
||||
m, _ = tc.builder(r).Compile(testCtx, NewCompileConfig())
|
||||
}
|
||||
code := m.(*compiledModule)
|
||||
code := &compiledModule{module: tc.module}
|
||||
|
||||
err := r.store.Engine.CompileModule(testCtx, code.module)
|
||||
require.NoError(t, err)
|
||||
@@ -299,7 +299,7 @@ func TestRuntime_InstantiateModule_UsesContext(t *testing.T) {
|
||||
require.Equal(t, testCtx, ctx)
|
||||
}
|
||||
|
||||
_, err := r.NewModuleBuilder("env").
|
||||
_, err := r.NewHostModuleBuilder("env").
|
||||
ExportFunction("start", start).
|
||||
Instantiate(testCtx, r)
|
||||
require.NoError(t, err)
|
||||
@@ -376,7 +376,7 @@ func TestRuntime_InstantiateModuleFromBinary_ErrorOnStart(t *testing.T) {
|
||||
panic(errors.New("ice cream"))
|
||||
}
|
||||
|
||||
host, err := r.NewModuleBuilder("").
|
||||
host, err := r.NewHostModuleBuilder("").
|
||||
ExportFunction("start", start).
|
||||
Instantiate(testCtx, r)
|
||||
require.NoError(t, err)
|
||||
@@ -428,7 +428,7 @@ func TestRuntime_InstantiateModule_ExitError(t *testing.T) {
|
||||
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)
|
||||
|
||||
one := uint32(1)
|
||||
|
||||
Reference in New Issue
Block a user