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