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}