summaryrefslogtreecommitdiff
path: root/vendor/github.com/tetratelabs/wazero/runtime.go
diff options
context:
space:
mode:
authorLibravatar kim <89579420+NyaaaWhatsUpDoc@users.noreply.github.com>2024-05-27 15:46:15 +0000
committerLibravatar GitHub <noreply@github.com>2024-05-27 17:46:15 +0200
commit1e7b32490dfdccddd04f46d4b0416b48d749d51b (patch)
tree62a11365933a5a11e0800af64cbdf9172e5e6e7a /vendor/github.com/tetratelabs/wazero/runtime.go
parent[chore] Small styling + link issues (#2933) (diff)
downloadgotosocial-1e7b32490dfdccddd04f46d4b0416b48d749d51b.tar.xz
[experiment] add alternative wasm sqlite3 implementation available via build-tag (#2863)
This allows for building GoToSocial with [SQLite transpiled to WASM](https://github.com/ncruces/go-sqlite3) and accessed through [Wazero](https://wazero.io/).
Diffstat (limited to 'vendor/github.com/tetratelabs/wazero/runtime.go')
-rw-r--r--vendor/github.com/tetratelabs/wazero/runtime.go374
1 files changed, 374 insertions, 0 deletions
diff --git a/vendor/github.com/tetratelabs/wazero/runtime.go b/vendor/github.com/tetratelabs/wazero/runtime.go
new file mode 100644
index 000000000..d1f0a1a31
--- /dev/null
+++ b/vendor/github.com/tetratelabs/wazero/runtime.go
@@ -0,0 +1,374 @@
+package wazero
+
+import (
+ "context"
+ "fmt"
+ "sync/atomic"
+
+ "github.com/tetratelabs/wazero/api"
+ experimentalapi "github.com/tetratelabs/wazero/experimental"
+ "github.com/tetratelabs/wazero/internal/expctxkeys"
+ internalsock "github.com/tetratelabs/wazero/internal/sock"
+ internalsys "github.com/tetratelabs/wazero/internal/sys"
+ "github.com/tetratelabs/wazero/internal/wasm"
+ binaryformat "github.com/tetratelabs/wazero/internal/wasm/binary"
+ "github.com/tetratelabs/wazero/sys"
+)
+
+// Runtime allows embedding of WebAssembly modules.
+//
+// The below is an example of basic initialization:
+//
+// ctx := context.Background()
+// r := wazero.NewRuntime(ctx)
+// defer r.Close(ctx) // This closes everything this Runtime created.
+//
+// mod, _ := r.Instantiate(ctx, wasm)
+//
+// # Notes
+//
+// - This is an interface for decoupling, not third-party implementations.
+// All implementations are in wazero.
+// - Closing this closes any CompiledModule or Module it instantiated.
+type Runtime interface {
+ // Instantiate instantiates a module from the WebAssembly binary (%.wasm)
+ // with default configuration, which notably calls the "_start" function,
+ // if it exists.
+ //
+ // Here's an example:
+ // ctx := context.Background()
+ // r := wazero.NewRuntime(ctx)
+ // defer r.Close(ctx) // This closes everything this Runtime created.
+ //
+ // mod, _ := r.Instantiate(ctx, wasm)
+ //
+ // # Notes
+ //
+ // - See notes on InstantiateModule for error scenarios.
+ // - See InstantiateWithConfig for configuration overrides.
+ Instantiate(ctx context.Context, source []byte) (api.Module, error)
+
+ // InstantiateWithConfig instantiates a module from the WebAssembly binary
+ // (%.wasm) or errs for reasons including exit or validation.
+ //
+ // Here's an example:
+ // ctx := context.Background()
+ // r := wazero.NewRuntime(ctx)
+ // defer r.Close(ctx) // This closes everything this Runtime created.
+ //
+ // mod, _ := r.InstantiateWithConfig(ctx, wasm,
+ // wazero.NewModuleConfig().WithName("rotate"))
+ //
+ // # Notes
+ //
+ // - See notes on InstantiateModule for error scenarios.
+ // - If you aren't overriding defaults, use Instantiate.
+ // - This is a convenience utility that chains CompileModule with
+ // InstantiateModule. To instantiate the same source multiple times,
+ // use CompileModule as InstantiateModule avoids redundant decoding
+ // and/or compilation.
+ InstantiateWithConfig(ctx context.Context, source []byte, config ModuleConfig) (api.Module, error)
+
+ // NewHostModuleBuilder lets you create modules out of functions defined in Go.
+ //
+ // Below defines and instantiates a module named "env" with one function:
+ //
+ // ctx := context.Background()
+ // hello := func() {
+ // fmt.Fprintln(stdout, "hello!")
+ // }
+ // _, err := r.NewHostModuleBuilder("env").
+ // NewFunctionBuilder().WithFunc(hello).Export("hello").
+ // Instantiate(ctx, r)
+ //
+ // Note: empty `moduleName` is not allowed.
+ NewHostModuleBuilder(moduleName string) HostModuleBuilder
+
+ // CompileModule decodes the WebAssembly binary (%.wasm) or errs if invalid.
+ // Any pre-compilation done after decoding wasm is dependent on RuntimeConfig.
+ //
+ // There are two main reasons to use CompileModule instead of Instantiate:
+ // - Improve performance when the same module is instantiated multiple times under different names
+ // - Reduce the amount of errors that can occur during InstantiateModule.
+ //
+ // # Notes
+ //
+ // - The resulting module name defaults to what was binary from the custom name section.
+ // - Any pre-compilation done after decoding the source is dependent on RuntimeConfig.
+ //
+ // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#name-section%E2%91%A0
+ CompileModule(ctx context.Context, binary []byte) (CompiledModule, error)
+
+ // InstantiateModule instantiates the module or errs for reasons including
+ // exit or validation.
+ //
+ // Here's an example:
+ // mod, _ := n.InstantiateModule(ctx, compiled, wazero.NewModuleConfig().
+ // WithName("prod"))
+ //
+ // # Errors
+ //
+ // While CompiledModule is pre-validated, there are a few situations which
+ // can cause an error:
+ // - The module name is already in use.
+ // - The module has a table element initializer that resolves to an index
+ // outside the Table minimum size.
+ // - The module has a start function, and it failed to execute.
+ // - The module was compiled to WASI and exited with a non-zero exit
+ // code, you'll receive a sys.ExitError.
+ // - RuntimeConfig.WithCloseOnContextDone was enabled and a context
+ // cancellation or deadline triggered before a start function returned.
+ InstantiateModule(ctx context.Context, compiled CompiledModule, config ModuleConfig) (api.Module, error)
+
+ // CloseWithExitCode closes all the modules that have been initialized in this Runtime with the provided exit code.
+ // An error is returned if any module returns an error when closed.
+ //
+ // Here's an example:
+ // ctx := context.Background()
+ // r := wazero.NewRuntime(ctx)
+ // defer r.CloseWithExitCode(ctx, 2) // This closes everything this Runtime created.
+ //
+ // // Everything below here can be closed, but will anyway due to above.
+ // _, _ = wasi_snapshot_preview1.InstantiateSnapshotPreview1(ctx, r)
+ // mod, _ := r.Instantiate(ctx, wasm)
+ CloseWithExitCode(ctx context.Context, exitCode uint32) error
+
+ // Module returns an instantiated module in this runtime or nil if there aren't any.
+ Module(moduleName string) api.Module
+
+ // Closer closes all compiled code by delegating to CloseWithExitCode with an exit code of zero.
+ api.Closer
+}
+
+// NewRuntime returns a runtime with a configuration assigned by NewRuntimeConfig.
+func NewRuntime(ctx context.Context) Runtime {
+ return NewRuntimeWithConfig(ctx, NewRuntimeConfig())
+}
+
+// NewRuntimeWithConfig returns a runtime with the given configuration.
+func NewRuntimeWithConfig(ctx context.Context, rConfig RuntimeConfig) Runtime {
+ config := rConfig.(*runtimeConfig)
+ var engine wasm.Engine
+ var cacheImpl *cache
+ if c := config.cache; c != nil {
+ // If the Cache is configured, we share the engine.
+ cacheImpl = c.(*cache)
+ engine = cacheImpl.initEngine(config.engineKind, config.newEngine, ctx, config.enabledFeatures)
+ } else {
+ // Otherwise, we create a new engine.
+ engine = config.newEngine(ctx, config.enabledFeatures, nil)
+ }
+ store := wasm.NewStore(config.enabledFeatures, engine)
+ return &runtime{
+ cache: cacheImpl,
+ store: store,
+ enabledFeatures: config.enabledFeatures,
+ memoryLimitPages: config.memoryLimitPages,
+ memoryCapacityFromMax: config.memoryCapacityFromMax,
+ dwarfDisabled: config.dwarfDisabled,
+ storeCustomSections: config.storeCustomSections,
+ ensureTermination: config.ensureTermination,
+ }
+}
+
+// runtime allows decoupling of public interfaces from internal representation.
+type runtime struct {
+ store *wasm.Store
+ cache *cache
+ enabledFeatures api.CoreFeatures
+ memoryLimitPages uint32
+ memoryCapacityFromMax bool
+ dwarfDisabled bool
+ storeCustomSections bool
+
+ // closed is the pointer used both to guard moduleEngine.CloseWithExitCode and to store the exit code.
+ //
+ // The update value is 1 + exitCode << 32. This ensures an exit code of zero isn't mistaken for never closed.
+ //
+ // Note: Exclusively reading and updating this with atomics guarantees cross-goroutine observations.
+ // See /RATIONALE.md
+ closed atomic.Uint64
+
+ ensureTermination bool
+}
+
+// Module implements Runtime.Module.
+func (r *runtime) Module(moduleName string) api.Module {
+ if len(moduleName) == 0 {
+ return nil
+ }
+ return r.store.Module(moduleName)
+}
+
+// CompileModule implements Runtime.CompileModule
+func (r *runtime) CompileModule(ctx context.Context, binary []byte) (CompiledModule, error) {
+ if err := r.failIfClosed(); err != nil {
+ return nil, err
+ }
+
+ internal, err := binaryformat.DecodeModule(binary, r.enabledFeatures,
+ r.memoryLimitPages, r.memoryCapacityFromMax, !r.dwarfDisabled, r.storeCustomSections)
+ if err != nil {
+ return nil, err
+ } else if err = internal.Validate(r.enabledFeatures); err != nil {
+ // TODO: decoders should validate before returning, as that allows
+ // them to err with the correct position in the wasm binary.
+ return nil, err
+ }
+
+ // Now that the module is validated, cache the memory definitions.
+ // TODO: lazy initialization of memory definition.
+ internal.BuildMemoryDefinitions()
+
+ c := &compiledModule{module: internal, compiledEngine: r.store.Engine}
+
+ // typeIDs are static and compile-time known.
+ typeIDs, err := r.store.GetFunctionTypeIDs(internal.TypeSection)
+ if err != nil {
+ return nil, err
+ }
+ c.typeIDs = typeIDs
+
+ listeners, err := buildFunctionListeners(ctx, internal)
+ if err != nil {
+ return nil, err
+ }
+ internal.AssignModuleID(binary, listeners, r.ensureTermination)
+ if err = r.store.Engine.CompileModule(ctx, internal, listeners, r.ensureTermination); err != nil {
+ return nil, err
+ }
+ return c, nil
+}
+
+func buildFunctionListeners(ctx context.Context, internal *wasm.Module) ([]experimentalapi.FunctionListener, error) {
+ // Test to see if internal code are using an experimental feature.
+ fnlf := ctx.Value(expctxkeys.FunctionListenerFactoryKey{})
+ if fnlf == nil {
+ return nil, nil
+ }
+ factory := fnlf.(experimentalapi.FunctionListenerFactory)
+ importCount := internal.ImportFunctionCount
+ listeners := make([]experimentalapi.FunctionListener, len(internal.FunctionSection))
+ for i := 0; i < len(listeners); i++ {
+ listeners[i] = factory.NewFunctionListener(internal.FunctionDefinition(uint32(i) + importCount))
+ }
+ return listeners, nil
+}
+
+// failIfClosed returns an error if CloseWithExitCode was called implicitly (by Close) or explicitly.
+func (r *runtime) failIfClosed() error {
+ if closed := r.closed.Load(); closed != 0 {
+ return fmt.Errorf("runtime closed with exit_code(%d)", uint32(closed>>32))
+ }
+ return nil
+}
+
+// Instantiate implements Runtime.Instantiate
+func (r *runtime) Instantiate(ctx context.Context, binary []byte) (api.Module, error) {
+ return r.InstantiateWithConfig(ctx, binary, NewModuleConfig())
+}
+
+// InstantiateWithConfig implements Runtime.InstantiateWithConfig
+func (r *runtime) InstantiateWithConfig(ctx context.Context, binary []byte, config ModuleConfig) (api.Module, error) {
+ if compiled, err := r.CompileModule(ctx, binary); err != nil {
+ return nil, err
+ } else {
+ compiled.(*compiledModule).closeWithModule = true
+ return r.InstantiateModule(ctx, compiled, config)
+ }
+}
+
+// InstantiateModule implements Runtime.InstantiateModule.
+func (r *runtime) InstantiateModule(
+ ctx context.Context,
+ compiled CompiledModule,
+ mConfig ModuleConfig,
+) (mod api.Module, err error) {
+ if err = r.failIfClosed(); err != nil {
+ return nil, err
+ }
+
+ code := compiled.(*compiledModule)
+ config := mConfig.(*moduleConfig)
+
+ // Only add guest module configuration to guests.
+ if !code.module.IsHostModule {
+ if sockConfig, ok := ctx.Value(internalsock.ConfigKey{}).(*internalsock.Config); ok {
+ config.sockConfig = sockConfig
+ }
+ }
+
+ var sysCtx *internalsys.Context
+ if sysCtx, err = config.toSysContext(); err != nil {
+ return
+ }
+
+ name := config.name
+ if !config.nameSet && code.module.NameSection != nil && code.module.NameSection.ModuleName != "" {
+ name = code.module.NameSection.ModuleName
+ }
+
+ // Instantiate the module.
+ mod, err = r.store.Instantiate(ctx, code.module, name, sysCtx, code.typeIDs)
+ if err != nil {
+ // If there was an error, don't leak the compiled module.
+ if code.closeWithModule {
+ _ = code.Close(ctx) // don't overwrite the error
+ }
+ return
+ }
+
+ if closeNotifier, ok := ctx.Value(expctxkeys.CloseNotifierKey{}).(experimentalapi.CloseNotifier); ok {
+ mod.(*wasm.ModuleInstance).CloseNotifier = closeNotifier
+ }
+
+ // Attach the code closer so that anything afterward closes the compiled
+ // code when closing the module.
+ if code.closeWithModule {
+ mod.(*wasm.ModuleInstance).CodeCloser = code
+ }
+
+ // Now, invoke any start functions, failing at first error.
+ for _, fn := range config.startFunctions {
+ start := mod.ExportedFunction(fn)
+ if start == nil {
+ continue
+ }
+ if _, err = start.Call(ctx); err != nil {
+ _ = mod.Close(ctx) // Don't leak the module on error.
+
+ if se, ok := err.(*sys.ExitError); ok {
+ if se.ExitCode() == 0 { // Don't err on success.
+ err = nil
+ }
+ return // Don't wrap an exit error
+ }
+ err = fmt.Errorf("module[%s] function[%s] failed: %w", name, fn, err)
+ return
+ }
+ }
+ return
+}
+
+// Close implements api.Closer embedded in Runtime.
+func (r *runtime) Close(ctx context.Context) error {
+ return r.CloseWithExitCode(ctx, 0)
+}
+
+// CloseWithExitCode implements Runtime.CloseWithExitCode
+//
+// Note: it also marks the internal `closed` field
+func (r *runtime) CloseWithExitCode(ctx context.Context, exitCode uint32) error {
+ closed := uint64(1) + uint64(exitCode)<<32 // Store exitCode as high-order bits.
+ if !r.closed.CompareAndSwap(0, closed) {
+ return nil
+ }
+ err := r.store.CloseWithExitCode(ctx, exitCode)
+ if r.cache == nil {
+ // Close the engine if the cache is not configured, which means that this engine is scoped in this runtime.
+ if errCloseEngine := r.store.Engine.Close(); errCloseEngine != nil {
+ return errCloseEngine
+ }
+ }
+ return err
+}