Files
wazero/wasm.go
Crypt Keeper 0186e41b27 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>
2022-02-23 16:41:32 +08:00

172 lines
5.5 KiB
Go

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"
)
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.
//
// 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
}
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
}
// 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
// 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{}
}
// 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)
}