Consolidates API to config input types (#280)

This makes instantiating host or wasm-defined modules similar, by using
XXXConfig types. Doing so also allows configuration engines to seed
properties.

Since decoded modules are only usable during instantiation, this pushes
decoding inside those functions. By doing so, the API is easier to use
as it has less choices and less errors to catch. Detection is done
internally by peeking at the magic number.

See https://github.com/tetratelabs/wazero/issues/279

Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
Crypt Keeper
2022-02-23 16:41:32 +08:00
committed by GitHub
parent d6cf0c4faf
commit 0186e41b27
22 changed files with 345 additions and 343 deletions

197
wasm.go
View File

@@ -1,66 +1,171 @@
package wazero
import (
"bytes"
"context"
"errors"
"fmt"
internalwasm "github.com/tetratelabs/wazero/internal/wasm"
"github.com/tetratelabs/wazero/internal/wasm/binary"
"github.com/tetratelabs/wazero/internal/wasm/interpreter"
"github.com/tetratelabs/wazero/internal/wasm/jit"
"github.com/tetratelabs/wazero/internal/wasm/text"
"github.com/tetratelabs/wazero/wasm"
)
// DecodeModule parses the configured source into a Module. This function returns when the source is exhausted or
// an error occurs. The result can be initialized for use via InstantiateModule.
type Engine struct {
e internalwasm.Engine
}
func NewEngineInterpreter() *Engine {
return &Engine{e: interpreter.NewEngine()}
}
func NewEngineJIT() *Engine { // TODO: compiler?
return &Engine{e: jit.NewEngine()}
}
// StoreConfig allows customization of a Store via NewStoreWithConfig
type StoreConfig struct {
// Context is the default context used to initialize the module. Defaults to context.Background.
//
// Notes:
// * If the Module defines a start function, this is used to invoke it.
// * This is the outer-most ancestor of wasm.ModuleContext Context() during wasm.HostFunction invocations.
// * This is the default context of wasm.Function when callers pass nil.
//
// See https://www.w3.org/TR/wasm-core-1/#start-function%E2%91%A0
Context context.Context
// Engine defaults to NewEngineInterpreter
Engine *Engine
}
func NewStore() wasm.Store {
return internalwasm.NewStore(context.Background(), interpreter.NewEngine())
}
// NewStoreWithConfig returns a store with the given configuration.
func NewStoreWithConfig(config *StoreConfig) wasm.Store {
ctx := config.Context
if ctx == nil {
ctx = context.Background()
}
engine := config.Engine
if engine == nil {
engine = NewEngineInterpreter()
}
return internalwasm.NewStore(ctx, engine.e)
}
// ModuleConfig defines the WebAssembly 1.0 (MVP) module to instantiate.
type ModuleConfig struct {
// Name defaults to what's decoded from the custom name section and can be overridden WithName.
// See https://www.w3.org/TR/wasm-core-1/#name-section%E2%91%A0
Name string
// Source is the WebAssembly 1.0 (MVP) text or binary encoding of the module.
Source []byte
}
// InstantiateModule instantiates the module namespace or errs if the configuration was invalid.
//
// Here's a description of the return values:
// * result is the module parsed or nil on error
// * err is a FormatError invoking the parser, dangling block comments or unexpected characters.
// See binary.DecodeModule and text.DecodeModule
type DecodeModule func(source []byte) (result *Module, err error)
var DecodeModuleBinary DecodeModule = func(source []byte) (*Module, error) {
return decodeModule(binary.DecodeModule, source)
}
var DecodeModuleText DecodeModule = func(source []byte) (*Module, error) {
return decodeModule(text.DecodeModule, source)
}
func decodeModule(decoder internalwasm.DecodeModule, source []byte) (*Module, error) {
m, err := decoder(source)
// Ex.
// exports, _ := wazero.InstantiateModule(wazero.NewStore(), &wazero.ModuleConfig{Source: wasm})
//
// Note: StoreConfig.Context is used for any WebAssembly 1.0 (MVP) Start Function.
func InstantiateModule(store wasm.Store, module *ModuleConfig) (wasm.ModuleExports, error) {
internal, ok := store.(*internalwasm.Store)
if !ok {
return nil, fmt.Errorf("unsupported Store implementation: %s", store)
}
m, name, err := decodeModule(module)
if err != nil {
return nil, err
}
var name string
if m.NameSection != nil {
return internal.Instantiate(m, name)
}
func decodeModule(module *ModuleConfig) (m *internalwasm.Module, name string, err error) {
if module.Source == nil {
err = errors.New("source == nil")
return
}
if len(module.Source) < 8 { // Ex. less than magic+version in binary or '(module)' in text
err = errors.New("invalid source")
return
}
// Peek to see if this is a binary or text format
if bytes.Equal(module.Source[0:4], binary.Magic) {
m, err = binary.DecodeModule(module.Source)
} else {
m, err = text.DecodeModule(module.Source)
}
if err != nil {
return
}
name = module.Name
if name == "" && m.NameSection != nil {
name = m.NameSection.ModuleName
}
return &Module{wasm: m, name: name}, nil
return
}
// EncodeModule encodes the given module into a byte slice depending on the format of the implementation.
// See binary.EncodeModule
type EncodeModule func(m *Module) []byte
// HostModuleConfig are WebAssembly 1.0 (MVP) exports from the host bound to a module name used by InstantiateHostModule.
type HostModuleConfig struct {
// Name is the module name that these exports can be imported with. Ex. wasi.ModuleSnapshotPreview1
Name string
var EncodeModuleBinary EncodeModule = func(m *Module) []byte {
return encodeModule(binary.EncodeModule, m)
// Functions adds functions written in Go, which a WebAssembly Module can import.
//
// The key is the name to export and the value is the func. Ex. WASISnapshotPreview1
//
// Noting a context exception described later, all parameters or result types must match WebAssembly 1.0 (MVP) value
// types. This means uint32, uint64, float32 or float64. Up to one result can be returned.
//
// Ex. This is a valid host function:
//
// addInts := func(x uint32, uint32) uint32 {
// return x + y
// }
//
// Host functions may also have an initial parameter (param[0]) of type context.Context or wasm.ModuleContext.
//
// Ex. This uses a Go Context:
//
// addInts := func(ctx context.Context, x uint32, uint32) uint32 {
// // add a little extra if we put some in the context!
// return x + y + ctx.Value(extraKey).(uint32)
// }
//
// The most sophisticated context is wasm.ModuleContext, which allows access to the Go context, but also
// allows writing to memory. This is important because there are only numeric types in Wasm. The only way to share other
// data is via writing memory and sharing offsets.
//
// Ex. This reads the parameters from!
//
// addInts := func(ctx wasm.ModuleContext, offset uint32) uint32 {
// x, _ := ctx.Memory().ReadUint32Le(offset)
// y, _ := ctx.Memory().ReadUint32Le(offset + 4) // 32 bits == 4 bytes!
// return x + y
// }
//
// See https://www.w3.org/TR/wasm-core-1/#host-functions%E2%91%A2
Functions map[string]interface{}
}
func encodeModule(encoder internalwasm.EncodeModule, m *Module) []byte {
return encoder(m.wasm)
}
// Module is a WebAssembly 1.0 (MVP) module decoded (DecodeModule) from a valid source.
type Module struct {
name string
wasm *internalwasm.Module
}
// Name defaults to what's decoded from the custom name section and can be overridden WithName.
// See https://www.w3.org/TR/wasm-core-1/#name-section%E2%91%A0
func (m *Module) Name() string {
return m.name
}
// WithName overwrites Name
func (m *Module) WithName(name string) *Module {
m.name = name
return m
// InstantiateHostModule instantiates the module namespace from the host or errs if the configuration was invalid.
//
// Ex.
// store := wazero.NewStore()
// wasiExports, _ := wazero.InstantiateHostModule(store, wazero.WASISnapshotPreview1())
//
func InstantiateHostModule(store wasm.Store, hostModule *HostModuleConfig) (wasm.HostExports, error) {
internal, ok := store.(*internalwasm.Store)
if !ok {
return nil, fmt.Errorf("unsupported Store implementation: %s", store)
}
return internal.ExportHostFunctions(hostModule.Name, hostModule.Functions)
}