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:
197
wasm.go
197
wasm.go
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user