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>
172 lines
5.5 KiB
Go
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)
|
|
}
|