Currently, we have custom code in wapc-go because our library forces a failure when a module that uses WASI doesn't define a "_start" function. Using the same pragmatism that resulted in us not enforcing the WASI table, this makes the "_start" function optional. This doesn't add a flag as the spec is not a proper version anyway (snapshot-01), so there's no need to further complicate configuration. If a "_start" function exists, we enforce it is of the proper signature and succeeds. Otherwise, we allow it to be absent. Signed-off-by: Adrian Cole <adrian@tetrate.io> Co-authored-by: Takeshi Yoneda <takeshi@tetrate.io>
121 lines
4.7 KiB
Go
121 lines
4.7 KiB
Go
package wazero
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
internalwasi "github.com/tetratelabs/wazero/internal/wasi"
|
|
internalwasm "github.com/tetratelabs/wazero/internal/wasm"
|
|
"github.com/tetratelabs/wazero/wasi"
|
|
"github.com/tetratelabs/wazero/wasm"
|
|
)
|
|
|
|
// WASISnapshotPreview1 are functions importable as the module name wasi.ModuleSnapshotPreview1
|
|
func WASISnapshotPreview1() *Module {
|
|
_, fns := internalwasi.SnapshotPreview1Functions()
|
|
m, err := internalwasm.NewHostModule(wasi.ModuleSnapshotPreview1, fns)
|
|
if err != nil {
|
|
panic(fmt.Errorf("BUG: %w", err))
|
|
}
|
|
return &Module{name: wasi.ModuleSnapshotPreview1, module: m}
|
|
}
|
|
|
|
// StartWASICommandFromSource instantiates a module from the WebAssembly 1.0 (20191205) text or binary source or errs if
|
|
// invalid. Once instantiated, this starts its WASI Command function ("_start").
|
|
//
|
|
// Ex.
|
|
// r := wazero.NewRuntime()
|
|
// wasi, _ := r.NewHostModule(wazero.WASISnapshotPreview1())
|
|
// defer wasi.Close()
|
|
//
|
|
// module, _ := StartWASICommandFromSource(r, source)
|
|
// defer module.Close()
|
|
//
|
|
// Note: This is a convenience utility that chains Runtime.CompileModule with StartWASICommand.
|
|
// See StartWASICommandWithConfig
|
|
func StartWASICommandFromSource(r Runtime, source []byte) (wasm.Module, error) {
|
|
if decoded, err := r.CompileModule(source); err != nil {
|
|
return nil, err
|
|
} else {
|
|
return StartWASICommand(r, decoded)
|
|
}
|
|
}
|
|
|
|
// StartWASICommand instantiates the module and starts its WASI Command function ("_start") if present. The return value
|
|
// are all exported functions in the module. This errs if the module doesn't export a memory named "memory", or there
|
|
// are any instantiation or function call errors. On success, other modules can import wasi.ModuleSnapshotPreview1.
|
|
//
|
|
// Ex.
|
|
// r := wazero.NewRuntime()
|
|
// wasi, _ := r.NewHostModule(wazero.WASISnapshotPreview1())
|
|
// defer wasi.Close()
|
|
//
|
|
// decoded, _ := r.CompileModule(source)
|
|
// module, _ := StartWASICommand(r, decoded)
|
|
// defer module.Close()
|
|
//
|
|
// ## "memory" export
|
|
// WASI snapshot-01 requires exporting a memory named "memory", and wazero enforces this as nearly all functions use
|
|
// memory to implement multiple returns. StartWASICommand errs if there is no memory exported as "memory".
|
|
//
|
|
// ## "_start" function export
|
|
// WASI snapshot-01 requires exporting a function named "_start" for WASI command, but wazero does not enforce this. If it is defined,
|
|
// it is called directly after any module-defined start function, in the runtime context (RuntimeConfig.WithContext).
|
|
//
|
|
// ## "__indirect_function_table" function export
|
|
// WASI snapshot-01 requires exporting a table named "__indirect_function_table", but wazero does not enforce this.
|
|
//
|
|
// Note: All TinyGo Wasm are WASI commands. They initialize memory on "_start" and import "fd_write" to implement panic.
|
|
// See StartWASICommandWithConfig
|
|
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/design/application-abi.md#current-unstable-abi
|
|
func StartWASICommand(r Runtime, module *Module) (wasm.Module, error) {
|
|
return startWASICommandWithSysContext(r, module, internalwasm.DefaultSysContext())
|
|
}
|
|
|
|
// StartWASICommandWithConfig is like StartWASICommand, except you can override configuration based on the importing
|
|
// module. For example, you can use this to define different args depending on the importing module.
|
|
//
|
|
// r := wazero.NewRuntime()
|
|
// wasi, _ := r.NewHostModule(wazero.WASISnapshotPreview1())
|
|
// mod, _ := r.CompileModule(source)
|
|
//
|
|
// // Initialize base configuration:
|
|
// sys := wazero.NewSysConfig().WithStdout(buf)
|
|
//
|
|
// // Assign different configuration on each instantiation
|
|
// module, _ := StartWASICommandWithConfig(r, mod.WithName("rotate"), sys.WithArgs("rotate", "angle=90", "dir=cw"))
|
|
//
|
|
// Note: Config is copied during instantiation: Later changes to config do not affect the instantiated result.
|
|
// See StartWASICommand
|
|
func StartWASICommandWithConfig(r Runtime, module *Module, config *SysConfig) (mod wasm.Module, err error) {
|
|
var sys *internalwasm.SysContext
|
|
if sys, err = config.toSysContext(); err != nil {
|
|
return
|
|
}
|
|
return startWASICommandWithSysContext(r, module, sys)
|
|
}
|
|
|
|
func startWASICommandWithSysContext(r Runtime, module *Module, sys *internalwasm.SysContext) (mod wasm.Module, err error) {
|
|
if err = internalwasi.ValidateWASICommand(module.module, module.name); err != nil {
|
|
return
|
|
}
|
|
|
|
internal, ok := r.(*runtime)
|
|
if !ok {
|
|
err = fmt.Errorf("unsupported Runtime implementation: %s", r)
|
|
return
|
|
}
|
|
|
|
if mod, err = internal.store.Instantiate(internal.ctx, module.module, module.name, sys); err != nil {
|
|
return
|
|
}
|
|
|
|
start := mod.ExportedFunction(internalwasi.FunctionStart)
|
|
if start == nil {
|
|
return
|
|
}
|
|
if _, err = start.Call(mod.WithContext(internal.ctx)); err != nil {
|
|
err = fmt.Errorf("module[%s] function[%s] failed: %w", module.name, internalwasi.FunctionStart, err)
|
|
}
|
|
return
|
|
}
|