runtime.go

  1package wazero
  2
  3import (
  4	"context"
  5	"fmt"
  6	"sync/atomic"
  7
  8	"github.com/tetratelabs/wazero/api"
  9	experimentalapi "github.com/tetratelabs/wazero/experimental"
 10	"github.com/tetratelabs/wazero/internal/engine/interpreter"
 11	"github.com/tetratelabs/wazero/internal/engine/wazevo"
 12	"github.com/tetratelabs/wazero/internal/expctxkeys"
 13	"github.com/tetratelabs/wazero/internal/platform"
 14	internalsock "github.com/tetratelabs/wazero/internal/sock"
 15	internalsys "github.com/tetratelabs/wazero/internal/sys"
 16	"github.com/tetratelabs/wazero/internal/wasm"
 17	binaryformat "github.com/tetratelabs/wazero/internal/wasm/binary"
 18	"github.com/tetratelabs/wazero/sys"
 19)
 20
 21// Runtime allows embedding of WebAssembly modules.
 22//
 23// The below is an example of basic initialization:
 24//
 25//	ctx := context.Background()
 26//	r := wazero.NewRuntime(ctx)
 27//	defer r.Close(ctx) // This closes everything this Runtime created.
 28//
 29//	mod, _ := r.Instantiate(ctx, wasm)
 30//
 31// # Notes
 32//
 33//   - This is an interface for decoupling, not third-party implementations.
 34//     All implementations are in wazero.
 35//   - Closing this closes any CompiledModule or Module it instantiated.
 36type Runtime interface {
 37	// Instantiate instantiates a module from the WebAssembly binary (%.wasm)
 38	// with default configuration, which notably calls the "_start" function,
 39	// if it exists.
 40	//
 41	// Here's an example:
 42	//	ctx := context.Background()
 43	//	r := wazero.NewRuntime(ctx)
 44	//	defer r.Close(ctx) // This closes everything this Runtime created.
 45	//
 46	//	mod, _ := r.Instantiate(ctx, wasm)
 47	//
 48	// # Notes
 49	//
 50	//   - See notes on InstantiateModule for error scenarios.
 51	//   - See InstantiateWithConfig for configuration overrides.
 52	Instantiate(ctx context.Context, source []byte) (api.Module, error)
 53
 54	// InstantiateWithConfig instantiates a module from the WebAssembly binary
 55	// (%.wasm) or errs for reasons including exit or validation.
 56	//
 57	// Here's an example:
 58	//	ctx := context.Background()
 59	//	r := wazero.NewRuntime(ctx)
 60	//	defer r.Close(ctx) // This closes everything this Runtime created.
 61	//
 62	//	mod, _ := r.InstantiateWithConfig(ctx, wasm,
 63	//		wazero.NewModuleConfig().WithName("rotate"))
 64	//
 65	// # Notes
 66	//
 67	//   - See notes on InstantiateModule for error scenarios.
 68	//   - If you aren't overriding defaults, use Instantiate.
 69	//   - This is a convenience utility that chains CompileModule with
 70	//     InstantiateModule. To instantiate the same source multiple times,
 71	//     use CompileModule as InstantiateModule avoids redundant decoding
 72	//     and/or compilation.
 73	InstantiateWithConfig(ctx context.Context, source []byte, config ModuleConfig) (api.Module, error)
 74
 75	// NewHostModuleBuilder lets you create modules out of functions defined in Go.
 76	//
 77	// Below defines and instantiates a module named "env" with one function:
 78	//
 79	//	ctx := context.Background()
 80	//	hello := func() {
 81	//		fmt.Fprintln(stdout, "hello!")
 82	//	}
 83	//	_, err := r.NewHostModuleBuilder("env").
 84	//		NewFunctionBuilder().WithFunc(hello).Export("hello").
 85	//		Instantiate(ctx, r)
 86	//
 87	// Note: empty `moduleName` is not allowed.
 88	NewHostModuleBuilder(moduleName string) HostModuleBuilder
 89
 90	// CompileModule decodes the WebAssembly binary (%.wasm) or errs if invalid.
 91	// Any pre-compilation done after decoding wasm is dependent on RuntimeConfig.
 92	//
 93	// There are two main reasons to use CompileModule instead of Instantiate:
 94	//   - Improve performance when the same module is instantiated multiple times under different names
 95	//   - Reduce the amount of errors that can occur during InstantiateModule.
 96	//
 97	// # Notes
 98	//
 99	//   - The resulting module name defaults to what was binary from the custom name section.
100	//   - Any pre-compilation done after decoding the source is dependent on RuntimeConfig.
101	//
102	// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#name-section%E2%91%A0
103	CompileModule(ctx context.Context, binary []byte) (CompiledModule, error)
104
105	// InstantiateModule instantiates the module or errs for reasons including
106	// exit or validation.
107	//
108	// Here's an example:
109	//	mod, _ := n.InstantiateModule(ctx, compiled, wazero.NewModuleConfig().
110	//		WithName("prod"))
111	//
112	// # Errors
113	//
114	// While CompiledModule is pre-validated, there are a few situations which
115	// can cause an error:
116	//   - The module name is already in use.
117	//   - The module has a table element initializer that resolves to an index
118	//     outside the Table minimum size.
119	//   - The module has a start function, and it failed to execute.
120	//   - The module was compiled to WASI and exited with a non-zero exit
121	//     code, you'll receive a sys.ExitError.
122	//   - RuntimeConfig.WithCloseOnContextDone was enabled and a context
123	//     cancellation or deadline triggered before a start function returned.
124	InstantiateModule(ctx context.Context, compiled CompiledModule, config ModuleConfig) (api.Module, error)
125
126	// CloseWithExitCode closes all the modules that have been initialized in this Runtime with the provided exit code.
127	// An error is returned if any module returns an error when closed.
128	//
129	// Here's an example:
130	//	ctx := context.Background()
131	//	r := wazero.NewRuntime(ctx)
132	//	defer r.CloseWithExitCode(ctx, 2) // This closes everything this Runtime created.
133	//
134	//	// Everything below here can be closed, but will anyway due to above.
135	//	_, _ = wasi_snapshot_preview1.InstantiateSnapshotPreview1(ctx, r)
136	//	mod, _ := r.Instantiate(ctx, wasm)
137	CloseWithExitCode(ctx context.Context, exitCode uint32) error
138
139	// Module returns an instantiated module in this runtime or nil if there aren't any.
140	Module(moduleName string) api.Module
141
142	// Closer closes all compiled code by delegating to CloseWithExitCode with an exit code of zero.
143	api.Closer
144}
145
146// NewRuntime returns a runtime with a configuration assigned by NewRuntimeConfig.
147func NewRuntime(ctx context.Context) Runtime {
148	return NewRuntimeWithConfig(ctx, NewRuntimeConfig())
149}
150
151// NewRuntimeWithConfig returns a runtime with the given configuration.
152func NewRuntimeWithConfig(ctx context.Context, rConfig RuntimeConfig) Runtime {
153	config := rConfig.(*runtimeConfig)
154	configKind := config.engineKind
155	configEngine := config.newEngine
156	if configKind == engineKindAuto {
157		if platform.CompilerSupports(config.enabledFeatures) {
158			configKind = engineKindCompiler
159		} else {
160			configKind = engineKindInterpreter
161		}
162	}
163	if configEngine == nil {
164		if configKind == engineKindCompiler {
165			configEngine = wazevo.NewEngine
166		} else {
167			configEngine = interpreter.NewEngine
168		}
169	}
170	var engine wasm.Engine
171	var cacheImpl *cache
172	if c := config.cache; c != nil {
173		// If the Cache is configured, we share the engine.
174		cacheImpl = c.(*cache)
175		engine = cacheImpl.initEngine(configKind, configEngine, ctx, config.enabledFeatures)
176	} else {
177		// Otherwise, we create a new engine.
178		engine = configEngine(ctx, config.enabledFeatures, nil)
179	}
180	store := wasm.NewStore(config.enabledFeatures, engine)
181	return &runtime{
182		cache:                 cacheImpl,
183		store:                 store,
184		enabledFeatures:       config.enabledFeatures,
185		memoryLimitPages:      config.memoryLimitPages,
186		memoryCapacityFromMax: config.memoryCapacityFromMax,
187		dwarfDisabled:         config.dwarfDisabled,
188		storeCustomSections:   config.storeCustomSections,
189		ensureTermination:     config.ensureTermination,
190	}
191}
192
193// runtime allows decoupling of public interfaces from internal representation.
194type runtime struct {
195	store                 *wasm.Store
196	cache                 *cache
197	enabledFeatures       api.CoreFeatures
198	memoryLimitPages      uint32
199	memoryCapacityFromMax bool
200	dwarfDisabled         bool
201	storeCustomSections   bool
202
203	// closed is the pointer used both to guard moduleEngine.CloseWithExitCode and to store the exit code.
204	//
205	// The update value is 1 + exitCode << 32. This ensures an exit code of zero isn't mistaken for never closed.
206	//
207	// Note: Exclusively reading and updating this with atomics guarantees cross-goroutine observations.
208	// See /RATIONALE.md
209	closed atomic.Uint64
210
211	ensureTermination bool
212}
213
214// Module implements Runtime.Module.
215func (r *runtime) Module(moduleName string) api.Module {
216	if len(moduleName) == 0 {
217		return nil
218	}
219	m := r.store.Module(moduleName)
220	if m == nil {
221		return nil
222	} else if m.Source.IsHostModule {
223		return hostModuleInstance{m}
224	}
225	return m
226}
227
228// CompileModule implements Runtime.CompileModule
229func (r *runtime) CompileModule(ctx context.Context, binary []byte) (CompiledModule, error) {
230	if err := r.failIfClosed(); err != nil {
231		return nil, err
232	}
233
234	internal, err := binaryformat.DecodeModule(binary, r.enabledFeatures,
235		r.memoryLimitPages, r.memoryCapacityFromMax, !r.dwarfDisabled, r.storeCustomSections)
236	if err != nil {
237		return nil, err
238	} else if err = internal.Validate(r.enabledFeatures); err != nil {
239		// TODO: decoders should validate before returning, as that allows
240		// them to err with the correct position in the wasm binary.
241		return nil, err
242	}
243
244	// Now that the module is validated, cache the memory definitions.
245	// TODO: lazy initialization of memory definition.
246	internal.BuildMemoryDefinitions()
247
248	c := &compiledModule{module: internal, compiledEngine: r.store.Engine}
249
250	// typeIDs are static and compile-time known.
251	typeIDs, err := r.store.GetFunctionTypeIDs(internal.TypeSection)
252	if err != nil {
253		return nil, err
254	}
255	c.typeIDs = typeIDs
256
257	listeners, err := buildFunctionListeners(ctx, internal)
258	if err != nil {
259		return nil, err
260	}
261	internal.AssignModuleID(binary, listeners, r.ensureTermination)
262	if err = r.store.Engine.CompileModule(ctx, internal, listeners, r.ensureTermination); err != nil {
263		return nil, err
264	}
265	return c, nil
266}
267
268func buildFunctionListeners(ctx context.Context, internal *wasm.Module) ([]experimentalapi.FunctionListener, error) {
269	// Test to see if internal code are using an experimental feature.
270	fnlf := ctx.Value(expctxkeys.FunctionListenerFactoryKey{})
271	if fnlf == nil {
272		return nil, nil
273	}
274	factory := fnlf.(experimentalapi.FunctionListenerFactory)
275	importCount := internal.ImportFunctionCount
276	listeners := make([]experimentalapi.FunctionListener, len(internal.FunctionSection))
277	for i := 0; i < len(listeners); i++ {
278		listeners[i] = factory.NewFunctionListener(internal.FunctionDefinition(uint32(i) + importCount))
279	}
280	return listeners, nil
281}
282
283// failIfClosed returns an error if CloseWithExitCode was called implicitly (by Close) or explicitly.
284func (r *runtime) failIfClosed() error {
285	if closed := r.closed.Load(); closed != 0 {
286		return fmt.Errorf("runtime closed with exit_code(%d)", uint32(closed>>32))
287	}
288	return nil
289}
290
291// Instantiate implements Runtime.Instantiate
292func (r *runtime) Instantiate(ctx context.Context, binary []byte) (api.Module, error) {
293	return r.InstantiateWithConfig(ctx, binary, NewModuleConfig())
294}
295
296// InstantiateWithConfig implements Runtime.InstantiateWithConfig
297func (r *runtime) InstantiateWithConfig(ctx context.Context, binary []byte, config ModuleConfig) (api.Module, error) {
298	if compiled, err := r.CompileModule(ctx, binary); err != nil {
299		return nil, err
300	} else {
301		compiled.(*compiledModule).closeWithModule = true
302		return r.InstantiateModule(ctx, compiled, config)
303	}
304}
305
306// InstantiateModule implements Runtime.InstantiateModule.
307func (r *runtime) InstantiateModule(
308	ctx context.Context,
309	compiled CompiledModule,
310	mConfig ModuleConfig,
311) (mod api.Module, err error) {
312	if err = r.failIfClosed(); err != nil {
313		return nil, err
314	}
315
316	code := compiled.(*compiledModule)
317	config := mConfig.(*moduleConfig)
318
319	// Only add guest module configuration to guests.
320	if !code.module.IsHostModule {
321		if sockConfig, ok := ctx.Value(internalsock.ConfigKey{}).(*internalsock.Config); ok {
322			config.sockConfig = sockConfig
323		}
324	}
325
326	var sysCtx *internalsys.Context
327	if sysCtx, err = config.toSysContext(); err != nil {
328		return nil, err
329	}
330
331	name := config.name
332	if !config.nameSet && code.module.NameSection != nil && code.module.NameSection.ModuleName != "" {
333		name = code.module.NameSection.ModuleName
334	}
335
336	// Instantiate the module.
337	mod, err = r.store.Instantiate(ctx, code.module, name, sysCtx, code.typeIDs)
338	if err != nil {
339		// If there was an error, don't leak the compiled module.
340		if code.closeWithModule {
341			_ = code.Close(ctx) // don't overwrite the error
342		}
343		return nil, err
344	}
345
346	if closeNotifier, ok := ctx.Value(expctxkeys.CloseNotifierKey{}).(experimentalapi.CloseNotifier); ok {
347		mod.(*wasm.ModuleInstance).CloseNotifier = closeNotifier
348	}
349
350	// Attach the code closer so that anything afterward closes the compiled
351	// code when closing the module.
352	if code.closeWithModule {
353		mod.(*wasm.ModuleInstance).CodeCloser = code
354	}
355
356	// Now, invoke any start functions, failing at first error.
357	for _, fn := range config.startFunctions {
358		start := mod.ExportedFunction(fn)
359		if start == nil {
360			continue
361		}
362		if _, err = start.Call(ctx); err != nil {
363			_ = mod.Close(ctx) // Don't leak the module on error.
364
365			if se, ok := err.(*sys.ExitError); ok {
366				if se.ExitCode() == 0 { // Don't err on success.
367					err = nil
368				}
369				return // Don't wrap an exit error
370			}
371			err = fmt.Errorf("module[%s] function[%s] failed: %w", name, fn, err)
372			return
373		}
374	}
375	return
376}
377
378// Close implements api.Closer embedded in Runtime.
379func (r *runtime) Close(ctx context.Context) error {
380	return r.CloseWithExitCode(ctx, 0)
381}
382
383// CloseWithExitCode implements Runtime.CloseWithExitCode
384//
385// Note: it also marks the internal `closed` field
386func (r *runtime) CloseWithExitCode(ctx context.Context, exitCode uint32) error {
387	closed := uint64(1) + uint64(exitCode)<<32 // Store exitCode as high-order bits.
388	if !r.closed.CompareAndSwap(0, closed) {
389		return nil
390	}
391	err := r.store.CloseWithExitCode(ctx, exitCode)
392	if r.cache == nil {
393		// Close the engine if the cache is not configured, which means that this engine is scoped in this runtime.
394		if errCloseEngine := r.store.Engine.Close(); errCloseEngine != nil {
395			return errCloseEngine
396		}
397	}
398	return err
399}