Combines Store+Engine into Runtime
This simplifies state management and the amount of terminology end-users need to learn by using one concept `Runtime` instead of two: `Engine` and `Store`. This bridges the concepts to the specification by still having `wazero.Runtime` implement `wasm.Store`. The net result is that we can know for sure which "engine" is used when decoding. This allows us a lot of flexibility especially pre-compilation when JIT is possible. This also changes the default to JIT based on compiler flags so that downstream projects like wapc-go don't have to do this individually (including tracking of which OS+Arch have JIT). Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
289
wasm.go
289
wasm.go
@@ -2,214 +2,137 @@ 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/2019/REC-wasm-core-1-20191205/#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 (20191205) 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/2019/REC-wasm-core-1-20191205/#name-section%E2%91%A0
|
||||
Name string
|
||||
// Source is the WebAssembly 1.0 (20191205) text or binary encoding of the module.
|
||||
Source []byte
|
||||
|
||||
validatedSource []byte
|
||||
decodedModule *internalwasm.Module
|
||||
}
|
||||
|
||||
// Validate eagerly decodes the Source and errs if it is invalid.
|
||||
//
|
||||
// This is used to pre-flight check and cache the module for later instantiation.
|
||||
func (m *ModuleConfig) Validate() (err error) {
|
||||
mod, err := decodeModule(m)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// TODO: decoders should validate before returning, as that allows
|
||||
// them to err with the correct source position.
|
||||
err = mod.Validate()
|
||||
return
|
||||
}
|
||||
|
||||
// WithName returns a new instance which overrides the Name, but keeps any internal cache made by Validate.
|
||||
func (m *ModuleConfig) WithName(moduleName string) *ModuleConfig {
|
||||
return &ModuleConfig{
|
||||
Name: moduleName,
|
||||
Source: m.Source,
|
||||
validatedSource: m.validatedSource,
|
||||
decodedModule: m.decodedModule,
|
||||
}
|
||||
}
|
||||
|
||||
// InstantiateModule instantiates the module namespace or errs if the configuration was invalid.
|
||||
// Runtime allows embedding of WebAssembly 1.0 (20191205) modules.
|
||||
//
|
||||
// Ex.
|
||||
// exports, _ := wazero.InstantiateModule(wazero.NewStore(), &wazero.ModuleConfig{Source: wasm})
|
||||
// r := wazero.NewRuntime()
|
||||
// decoded, _ := r.DecodeModule(source)
|
||||
// module, _ := r.NewModule(decoded)
|
||||
//
|
||||
// Note: StoreConfig.Context is used for any WebAssembly 1.0 (20191205) 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, err := decodeModule(module)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/
|
||||
type Runtime interface {
|
||||
wasm.Store
|
||||
|
||||
if err = m.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return internal.Instantiate(m, getModuleName(module.Name, m))
|
||||
// DecodeModule decodes the WebAssembly 1.0 (20191205) text or binary source or errs if invalid.
|
||||
//
|
||||
// Note: the name defaults to what was decoded from the custom name section.
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#name-section%E2%91%A0
|
||||
DecodeModule(source []byte) (*DecodedModule, error)
|
||||
|
||||
// NewModuleFromSource instantiates a module from the WebAssembly 1.0 (20191205) text or binary source or errs if
|
||||
// invalid.
|
||||
//
|
||||
// Ex.
|
||||
// module, _ := wazero.NewRuntime().NewModuleFromSource(source)
|
||||
//
|
||||
// Note: This is a convenience utility that chains DecodeModule with NewModule. To instantiate the same source
|
||||
// multiple times, use DecodeModule as NewModule avoids redundant decoding and/or compilation.
|
||||
NewModuleFromSource(source []byte) (wasm.Module, error)
|
||||
|
||||
// NewModule instantiates the module namespace or errs if the configuration was invalid.
|
||||
//
|
||||
// Ex.
|
||||
// r := wazero.NewRuntime()
|
||||
// decoded, _ := r.DecodeModule(source)
|
||||
// module, _ := r.NewModule(decoded)
|
||||
//
|
||||
// Note: The last value of RuntimeConfig.WithContext is used for any WebAssembly 1.0 (20191205) Start Function.
|
||||
NewModule(module *DecodedModule) (wasm.Module, error)
|
||||
|
||||
// TODO: RemoveModule
|
||||
|
||||
// NewHostModule instantiates the module namespace from the host or errs if the configuration was invalid.
|
||||
//
|
||||
// Ex.
|
||||
// r := wazero.NewRuntime()
|
||||
// wasiExports, _ := r.NewHostModule(wazero.WASISnapshotPreview1())
|
||||
NewHostModule(hostModule *HostModuleConfig) (wasm.HostModule, error)
|
||||
|
||||
// TODO: RemoveHostModule
|
||||
}
|
||||
|
||||
// getModuleName returns the ModuleName from the internalwasm.NameSection if the input name was empty.
|
||||
func getModuleName(name string, m *internalwasm.Module) string {
|
||||
if name == "" && m.NameSection != nil {
|
||||
return m.NameSection.ModuleName
|
||||
}
|
||||
return name
|
||||
func NewRuntime() Runtime {
|
||||
return NewRuntimeWithConfig(NewRuntimeConfig())
|
||||
}
|
||||
|
||||
func decodeModule(module *ModuleConfig) (m *internalwasm.Module, err error) {
|
||||
if module.Source == nil {
|
||||
err = errors.New("source == nil")
|
||||
return
|
||||
// NewRuntimeWithConfig returns a runtime with the given configuration.
|
||||
func NewRuntimeWithConfig(config *RuntimeConfig) Runtime {
|
||||
return &runtime{store: internalwasm.NewStore(config.ctx, config.engine)}
|
||||
}
|
||||
|
||||
// runtime allows decoupling of public interfaces from internal representation.
|
||||
type runtime struct {
|
||||
store *internalwasm.Store
|
||||
}
|
||||
|
||||
// Module implements wasm.Store Module
|
||||
func (r *runtime) Module(moduleName string) wasm.Module {
|
||||
return r.store.Module(moduleName)
|
||||
}
|
||||
|
||||
// HostModule implements wasm.Store HostModule
|
||||
func (r *runtime) HostModule(moduleName string) wasm.HostModule {
|
||||
return r.store.HostModule(moduleName)
|
||||
}
|
||||
|
||||
// DecodeModule implements Runtime.DecodeModule
|
||||
func (r *runtime) DecodeModule(source []byte) (*DecodedModule, error) {
|
||||
if source == nil {
|
||||
return nil, errors.New("source == nil")
|
||||
}
|
||||
|
||||
if len(module.Source) < 8 { // Ex. less than magic+version in binary or '(module)' in text
|
||||
err = errors.New("invalid source")
|
||||
return
|
||||
}
|
||||
|
||||
// Check if this source was already decoded
|
||||
if bytes.Equal(module.Source, module.validatedSource) {
|
||||
m = module.decodedModule
|
||||
return
|
||||
if len(source) < 8 { // Ex. less than magic+version in binary or '(module)' in text
|
||||
return nil, errors.New("invalid source")
|
||||
}
|
||||
|
||||
// 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)
|
||||
var decoder internalwasm.DecodeModule
|
||||
if bytes.Equal(source[0:4], binary.Magic) {
|
||||
decoder = binary.DecodeModule
|
||||
} else {
|
||||
m, err = text.DecodeModule(module.Source)
|
||||
decoder = text.DecodeModule
|
||||
}
|
||||
|
||||
internal, err := decoder(source)
|
||||
if err != nil {
|
||||
return
|
||||
return nil, err
|
||||
} else if err = internal.Validate(); err != nil {
|
||||
// TODO: decoders should validate before returning, as that allows
|
||||
// them to err with the correct source position.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Cache as tools like wapc-go re-instantiate the same module many times.
|
||||
module.validatedSource = module.Source
|
||||
module.decodedModule = m
|
||||
return
|
||||
}
|
||||
|
||||
// HostModuleConfig are WebAssembly 1.0 (20191205) 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 (20191205) 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/2019/REC-wasm-core-1-20191205/#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)
|
||||
result := &DecodedModule{module: internal}
|
||||
if internal.NameSection != nil {
|
||||
result.name = internal.NameSection.ModuleName
|
||||
}
|
||||
return internal.ExportHostFunctions(hostModule.Name, hostModule.Functions)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// NewModuleFromSource implements Runtime.NewModuleFromSource
|
||||
func (r *runtime) NewModuleFromSource(source []byte) (wasm.Module, error) {
|
||||
if decoded, err := r.DecodeModule(source); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return r.NewModule(decoded)
|
||||
}
|
||||
}
|
||||
|
||||
// NewModule implements Runtime.NewModule
|
||||
func (r *runtime) NewModule(module *DecodedModule) (wasm.Module, error) {
|
||||
return r.store.Instantiate(module.module, module.name)
|
||||
}
|
||||
|
||||
// NewHostModule implements Runtime.NewHostModule
|
||||
func (r *runtime) NewHostModule(hostModule *HostModuleConfig) (wasm.HostModule, error) {
|
||||
return r.store.NewHostModule(hostModule.Name, hostModule.Functions)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user