wa-lang.org/wazero@v1.0.2/runtime.go (about) 1 package wazero 2 3 import ( 4 "bytes" 5 "context" 6 "errors" 7 8 "wa-lang.org/wazero/api" 9 experimentalapi "wa-lang.org/wazero/experimental" 10 "wa-lang.org/wazero/internal/version" 11 "wa-lang.org/wazero/internal/wasm" 12 binaryformat "wa-lang.org/wazero/internal/wasm/binary" 13 ) 14 15 // Runtime allows embedding of WebAssembly modules. 16 // 17 // The below is an example of basic initialization: 18 // 19 // ctx := context.Background() 20 // r := wazero.NewRuntime(ctx) 21 // defer r.Close(ctx) // This closes everything this Runtime created. 22 // 23 // module, _ := r.InstantiateModuleFromBinary(ctx, wasm) 24 type Runtime interface { 25 // NewHostModuleBuilder lets you create modules out of functions defined in Go. 26 // 27 // Below defines and instantiates a module named "env" with one function: 28 // 29 // ctx := context.Background() 30 // hello := func() { 31 // fmt.Fprintln(stdout, "hello!") 32 // } 33 // _, err := r.NewHostModuleBuilder("env"). 34 // NewFunctionBuilder().WithFunc(hello).Export("hello"). 35 // Instantiate(ctx, r) 36 NewHostModuleBuilder(moduleName string) HostModuleBuilder 37 38 // CompileModule decodes the WebAssembly binary (%.wasm) or errs if invalid. 39 // Any pre-compilation done after decoding wasm is dependent on RuntimeConfig. 40 // 41 // There are two main reasons to use CompileModule instead of InstantiateModuleFromBinary: 42 // - Improve performance when the same module is instantiated multiple times under different names 43 // - Reduce the amount of errors that can occur during InstantiateModule. 44 // 45 // # Notes 46 // 47 // - The resulting module name defaults to what was binary from the custom name section. 48 // - Any pre-compilation done after decoding the source is dependent on RuntimeConfig. 49 // 50 // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#name-section%E2%91%A0 51 CompileModule(ctx context.Context, binary []byte) (CompiledModule, error) 52 53 // InstantiateModuleFromBinary instantiates a module from the WebAssembly binary (%.wasm) or errs if invalid. 54 // 55 // Here's an example: 56 // ctx := context.Background() 57 // r := wazero.NewRuntime(ctx) 58 // defer r.Close(ctx) // This closes everything this Runtime created. 59 // 60 // module, _ := r.InstantiateModuleFromBinary(ctx, wasm) 61 // 62 // # Notes 63 // 64 // - This is a convenience utility that chains CompileModule with InstantiateModule. To instantiate the same 65 // source multiple times, use CompileModule as InstantiateModule avoids redundant decoding and/or compilation. 66 // - To avoid using configuration defaults, use InstantiateModule instead. 67 InstantiateModuleFromBinary(ctx context.Context, source []byte) (api.Module, error) 68 69 // Namespace is the default namespace of this runtime, and is embedded for convenience. Most users will only use the 70 // default namespace. 71 // 72 // Advanced use cases can use NewNamespace to redefine modules of the same name. For example, to allow different 73 // modules to define their own stateful "env" module. 74 Namespace 75 76 // NewNamespace creates an empty namespace which won't conflict with any other namespace including the default. 77 // This is more efficient than multiple runtimes, as namespaces share a compiler cache. 78 // 79 // In simplest case, a namespace won't conflict if another has a module with the same name: 80 // b := assemblyscript.NewBuilder(r) 81 // m1, _ := b.InstantiateModule(ctx, r.NewNamespace(ctx)) 82 // m2, _ := b.InstantiateModule(ctx, r.NewNamespace(ctx)) 83 // 84 // This is also useful for different modules that import the same module name (like "env"), but need different 85 // configuration or state. For example, one with trace logging enabled and another disabled: 86 // b := assemblyscript.NewBuilder(r) 87 // 88 // // m1 has trace logging disabled 89 // ns1 := r.NewNamespace(ctx) 90 // _ = b.InstantiateModule(ctx, ns1) 91 // m1, _ := ns1.InstantiateModule(ctx, compiled, config) 92 // 93 // // m2 has trace logging enabled 94 // ns2 := r.NewNamespace(ctx) 95 // _ = b.WithTraceToStdout().InstantiateModule(ctx, ns2) 96 // m2, _ := ns2.InstantiateModule(ctx, compiled, config) 97 // 98 // # Notes 99 // 100 // - The returned namespace does not inherit any modules from the runtime default namespace. 101 // - Closing the returned namespace closes any modules in it. 102 // - Closing this runtime also closes the namespace returned from this function. 103 NewNamespace(context.Context) Namespace 104 105 // CloseWithExitCode closes all the modules that have been initialized in this Runtime with the provided exit code. 106 // An error is returned if any module returns an error when closed. 107 // 108 // Here's an example: 109 // ctx := context.Background() 110 // r := wazero.NewRuntime(ctx) 111 // defer r.CloseWithExitCode(ctx, 2) // This closes everything this Runtime created. 112 // 113 // // Everything below here can be closed, but will anyway due to above. 114 // _, _ = wasi_snapshot_preview1.InstantiateSnapshotPreview1(ctx, r) 115 // mod, _ := r.InstantiateModuleFromBinary(ctx, wasm) 116 CloseWithExitCode(ctx context.Context, exitCode uint32) error 117 118 // Closer closes all namespace and compiled code by delegating to CloseWithExitCode with an exit code of zero. 119 api.Closer 120 } 121 122 // NewRuntime returns a runtime with a configuration assigned by NewRuntimeConfig. 123 func NewRuntime(ctx context.Context) Runtime { 124 return NewRuntimeWithConfig(ctx, NewRuntimeConfig()) 125 } 126 127 // NewRuntimeWithConfig returns a runtime with the given configuration. 128 func NewRuntimeWithConfig(ctx context.Context, rConfig RuntimeConfig) Runtime { 129 if v := ctx.Value(version.WazeroVersionKey{}); v == nil { 130 ctx = context.WithValue(ctx, version.WazeroVersionKey{}, wazeroVersion) 131 } 132 config := rConfig.(*runtimeConfig) 133 store, ns := wasm.NewStore(config.enabledFeatures, config.newEngine(ctx, config.enabledFeatures)) 134 return &runtime{ 135 store: store, 136 ns: &namespace{store: store, ns: ns}, 137 enabledFeatures: config.enabledFeatures, 138 memoryLimitPages: config.memoryLimitPages, 139 memoryCapacityFromMax: config.memoryCapacityFromMax, 140 isInterpreter: config.isInterpreter, 141 } 142 } 143 144 // runtime allows decoupling of public interfaces from internal representation. 145 type runtime struct { 146 store *wasm.Store 147 ns *namespace 148 enabledFeatures api.CoreFeatures 149 memoryLimitPages uint32 150 memoryCapacityFromMax bool 151 isInterpreter bool 152 compiledModules []*compiledModule 153 } 154 155 // NewNamespace implements Runtime.NewNamespace. 156 func (r *runtime) NewNamespace(ctx context.Context) Namespace { 157 return &namespace{store: r.store, ns: r.store.NewNamespace(ctx)} 158 } 159 160 // Module implements Namespace.Module embedded by Runtime. 161 func (r *runtime) Module(moduleName string) api.Module { 162 return r.ns.Module(moduleName) 163 } 164 165 // CompileModule implements Runtime.CompileModule 166 func (r *runtime) CompileModule(ctx context.Context, binary []byte) (CompiledModule, error) { 167 if binary == nil { 168 return nil, errors.New("binary == nil") 169 } 170 171 if len(binary) < 4 || !bytes.Equal(binary[0:4], binaryformat.Magic) { 172 return nil, errors.New("invalid binary") 173 } 174 175 internal, err := binaryformat.DecodeModule(binary, r.enabledFeatures, r.memoryLimitPages, r.memoryCapacityFromMax) 176 if err != nil { 177 return nil, err 178 } else if err = internal.Validate(r.enabledFeatures); err != nil { 179 // TODO: decoders should validate before returning, as that allows 180 // them to err with the correct position in the wasm binary. 181 return nil, err 182 } 183 184 internal.AssignModuleID(binary) 185 186 // Now that the module is validated, cache the function and memory definitions. 187 internal.BuildFunctionDefinitions() 188 internal.BuildMemoryDefinitions() 189 190 c := &compiledModule{module: internal, compiledEngine: r.store.Engine} 191 192 listeners, err := buildListeners(ctx, internal) 193 if err != nil { 194 return nil, err 195 } 196 197 if err = r.store.Engine.CompileModule(ctx, internal, listeners); err != nil { 198 return nil, err 199 } 200 201 r.compiledModules = append(r.compiledModules, c) 202 return c, nil 203 } 204 205 func buildListeners(ctx context.Context, internal *wasm.Module) ([]experimentalapi.FunctionListener, error) { 206 // Test to see if internal code are using an experimental feature. 207 fnlf := ctx.Value(experimentalapi.FunctionListenerFactoryKey{}) 208 if fnlf == nil { 209 return nil, nil 210 } 211 factory := fnlf.(experimentalapi.FunctionListenerFactory) 212 importCount := internal.ImportFuncCount() 213 listeners := make([]experimentalapi.FunctionListener, len(internal.FunctionSection)) 214 for i := 0; i < len(listeners); i++ { 215 listeners[i] = factory.NewListener(internal.FunctionDefinitionSection[uint32(i)+importCount]) 216 } 217 return listeners, nil 218 } 219 220 // InstantiateModuleFromBinary implements Runtime.InstantiateModuleFromBinary 221 func (r *runtime) InstantiateModuleFromBinary(ctx context.Context, binary []byte) (api.Module, error) { 222 if compiled, err := r.CompileModule(ctx, binary); err != nil { 223 return nil, err 224 } else { 225 compiled.(*compiledModule).closeWithModule = true 226 return r.InstantiateModule(ctx, compiled, NewModuleConfig()) 227 } 228 } 229 230 // InstantiateModule implements Namespace.InstantiateModule embedded by Runtime. 231 func (r *runtime) InstantiateModule( 232 ctx context.Context, 233 compiled CompiledModule, 234 mConfig ModuleConfig, 235 ) (api.Module, error) { 236 return r.ns.InstantiateModule(ctx, compiled, mConfig) 237 } 238 239 // Close implements api.Closer embedded in Runtime. 240 func (r *runtime) Close(ctx context.Context) error { 241 return r.CloseWithExitCode(ctx, 0) 242 } 243 244 // CloseWithExitCode implements Runtime.CloseWithExitCode 245 func (r *runtime) CloseWithExitCode(ctx context.Context, exitCode uint32) error { 246 err := r.store.CloseWithExitCode(ctx, exitCode) 247 for _, c := range r.compiledModules { 248 if e := c.Close(ctx); e != nil && err == nil { 249 err = e 250 } 251 } 252 return err 253 }