wa-lang.org/wazero@v1.0.2/builder.go (about) 1 package wazero 2 3 import ( 4 "context" 5 6 "wa-lang.org/wazero/api" 7 "wa-lang.org/wazero/internal/wasm" 8 ) 9 10 // HostFunctionBuilder defines a host function (in Go), so that a 11 // WebAssembly binary (e.g. %.wasm file) can import and use it. 12 // 13 // Here's an example of an addition function: 14 // 15 // hostModuleBuilder.NewFunctionBuilder(). 16 // WithFunc(func(cxt context.Context, x, y uint32) uint32 { 17 // return x + y 18 // }). 19 // Export("add") 20 // 21 // # Memory 22 // 23 // All host functions act on the importing api.Module, including any memory 24 // exported in its binary (%.wasm file). If you are reading or writing memory, 25 // it is sand-boxed Wasm memory defined by the guest. 26 // 27 // Below, `m` is the importing module, defined in Wasm. `fn` is a host function 28 // added via Export. This means that `x` was read from memory defined in Wasm, 29 // not arbitrary memory in the process. 30 // 31 // fn := func(ctx context.Context, m api.Module, offset uint32) uint32 { 32 // x, _ := m.Memory().ReadUint32Le(ctx, offset) 33 // return x 34 // } 35 type HostFunctionBuilder interface { 36 // WithGoFunction is an advanced feature for those who need higher 37 // performance than WithFunc at the cost of more complexity. 38 // 39 // Here's an example addition function: 40 // 41 // builder.WithGoFunction(api.GoFunc(func(ctx context.Context, params []uint64) []uint64 { 42 // x, y := uint32(params[0]), uint32(params[1]) 43 // sum := x + y 44 // return []uint64{sum} 45 // }, []api.ValueType{api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}) 46 // 47 // As you can see above, defining in this way implies knowledge of which 48 // WebAssembly api.ValueType is appropriate for each parameter and result. 49 // 50 // See WithGoModuleFunction if you also need to access the calling module. 51 WithGoFunction(fn api.GoFunction, params, results []api.ValueType) HostFunctionBuilder 52 53 // WithGoModuleFunction is an advanced feature for those who need higher 54 // performance than WithFunc at the cost of more complexity. 55 // 56 // Here's an example addition function that loads operands from memory: 57 // 58 // builder.WithGoModuleFunction(api.GoModuleFunc(func(ctx context.Context, mod api.Module, params []uint64) []uint64 { 59 // mem := m.Memory() 60 // offset := uint32(params[0]) 61 // 62 // x, _ := mem.ReadUint32Le(ctx, offset) 63 // y, _ := mem.ReadUint32Le(ctx, offset + 4) // 32 bits == 4 bytes! 64 // sum := x + y 65 // 66 // return []uint64{sum} 67 // }, []api.ValueType{api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}) 68 // 69 // As you can see above, defining in this way implies knowledge of which 70 // WebAssembly api.ValueType is appropriate for each parameter and result. 71 // 72 // See WithGoFunction if you don't need access to the calling module. 73 WithGoModuleFunction(fn api.GoModuleFunction, params, results []api.ValueType) HostFunctionBuilder 74 75 // WithFunc uses reflect.Value to map a go `func` to a WebAssembly 76 // compatible Signature. An input that isn't a `func` will fail to 77 // instantiate. 78 // 79 // Here's an example of an addition function: 80 // 81 // builder.WithFunc(func(cxt context.Context, x, y uint32) uint32 { 82 // return x + y 83 // }) 84 // 85 // # Defining a function 86 // 87 // Except for the context.Context and optional api.Module, all parameters 88 // or result types must map to WebAssembly numeric value types. This means 89 // uint32, int32, uint64, int32 float32 or float64. 90 // 91 // api.Module may be specified as the second parameter, usually to access 92 // memory. This is important because there are only numeric types in Wasm. 93 // The only way to share other data is via writing memory and sharing 94 // offsets. 95 // 96 // builder.WithFunc(func(ctx context.Context, m api.Module, offset uint32) uint32 { 97 // mem := m.Memory() 98 // x, _ := mem.ReadUint32Le(ctx, offset) 99 // y, _ := mem.ReadUint32Le(ctx, offset + 4) // 32 bits == 4 bytes! 100 // return x + y 101 // }) 102 // 103 // This example propagates context properly when calling other functions 104 // exported in the api.Module: 105 // 106 // builder.WithFunc(func(ctx context.Context, m api.Module, offset, byteCount uint32) uint32 { 107 // fn = m.ExportedFunction("__read") 108 // results, err := fn(ctx, offset, byteCount) 109 // --snip-- 110 WithFunc(interface{}) HostFunctionBuilder 111 112 // WithName defines the optional module-local name of this function, e.g. 113 // "random_get" 114 // 115 // Note: This is not required to match the Export name. 116 WithName(name string) HostFunctionBuilder 117 118 // WithParameterNames defines optional parameter names of the function 119 // signature, e.x. "buf", "buf_len" 120 // 121 // Note: When defined, names must be provided for all parameters. 122 WithParameterNames(names ...string) HostFunctionBuilder 123 124 // Export exports this to the HostModuleBuilder as the given name, e.g. 125 // "random_get" 126 Export(name string) HostModuleBuilder 127 } 128 129 // HostModuleBuilder is a way to define host functions (in Go), so that a 130 // WebAssembly binary (e.g. %.wasm file) can import and use them. 131 // 132 // Specifically, this implements the host side of an Application Binary 133 // Interface (ABI) like WASI or AssemblyScript. 134 // 135 // For example, this defines and instantiates a module named "env" with one 136 // function: 137 // 138 // ctx := context.Background() 139 // r := wazero.NewRuntime(ctx) 140 // defer r.Close(ctx) // This closes everything this Runtime created. 141 // 142 // hello := func() { 143 // fmt.Fprintln(stdout, "hello!") 144 // } 145 // env, _ := r.NewHostModuleBuilder("env"). 146 // NewFunctionBuilder().WithFunc(hello).Export("hello"). 147 // Instantiate(ctx, r) 148 // 149 // If the same module may be instantiated multiple times, it is more efficient 150 // to separate steps. Here's an example: 151 // 152 // compiled, _ := r.NewHostModuleBuilder("env"). 153 // NewFunctionBuilder().WithFunc(getRandomString).Export("get_random_string"). 154 // Compile(ctx) 155 // 156 // env1, _ := r.InstantiateModule(ctx, compiled, wazero.NewModuleConfig().WithName("env.1")) 157 // env2, _ := r.InstantiateModule(ctx, compiled, wazero.NewModuleConfig().WithName("env.2")) 158 // 159 // See HostFunctionBuilder for valid host function signatures and other details. 160 // 161 // # Notes 162 // 163 // - HostModuleBuilder is mutable: each method returns the same instance for 164 // chaining. 165 // - methods do not return errors, to allow chaining. Any validation errors 166 // are deferred until Compile. 167 // - Insertion order is not retained. Anything defined by this builder is 168 // sorted lexicographically on Compile. 169 type HostModuleBuilder interface { 170 // Note: until golang/go#5860, we can't use example tests to embed code in interface godocs. 171 172 // NewFunctionBuilder begins the definition of a host function. 173 NewFunctionBuilder() HostFunctionBuilder 174 175 // Compile returns a CompiledModule that can instantiated in any namespace (Namespace). 176 // 177 // Note: Closing the Namespace has the same effect as closing the result. 178 Compile(context.Context) (CompiledModule, error) 179 180 // Instantiate is a convenience that calls Compile, then Namespace.InstantiateModule. 181 // This can fail for reasons documented on Namespace.InstantiateModule. 182 // 183 // Here's an example: 184 // 185 // ctx := context.Background() 186 // r := wazero.NewRuntime(ctx) 187 // defer r.Close(ctx) // This closes everything this Runtime created. 188 // 189 // hello := func() { 190 // fmt.Fprintln(stdout, "hello!") 191 // } 192 // env, _ := r.NewHostModuleBuilder("env"). 193 // NewFunctionBuilder().WithFunc(hello).Export("hello"). 194 // Instantiate(ctx, r) 195 // 196 // # Notes 197 // 198 // - Closing the Namespace has the same effect as closing the result. 199 // - Fields in the builder are copied during instantiation: Later changes do not affect the instantiated result. 200 // - To avoid using configuration defaults, use Compile instead. 201 Instantiate(context.Context, Namespace) (api.Module, error) 202 } 203 204 // hostModuleBuilder implements HostModuleBuilder 205 type hostModuleBuilder struct { 206 r *runtime 207 moduleName string 208 nameToGoFunc map[string]interface{} 209 funcToNames map[string][]string 210 } 211 212 // NewHostModuleBuilder implements Runtime.NewHostModuleBuilder 213 func (r *runtime) NewHostModuleBuilder(moduleName string) HostModuleBuilder { 214 return &hostModuleBuilder{ 215 r: r, 216 moduleName: moduleName, 217 nameToGoFunc: map[string]interface{}{}, 218 funcToNames: map[string][]string{}, 219 } 220 } 221 222 // hostFunctionBuilder implements HostFunctionBuilder 223 type hostFunctionBuilder struct { 224 b *hostModuleBuilder 225 fn interface{} 226 name string 227 paramNames []string 228 } 229 230 // WithGoFunction implements HostFunctionBuilder.WithGoFunction 231 func (h *hostFunctionBuilder) WithGoFunction(fn api.GoFunction, params, results []api.ValueType) HostFunctionBuilder { 232 h.fn = &wasm.HostFunc{ 233 ParamTypes: params, 234 ResultTypes: results, 235 Code: &wasm.Code{IsHostFunction: true, GoFunc: fn}, 236 } 237 return h 238 } 239 240 // WithGoModuleFunction implements HostFunctionBuilder.WithGoModuleFunction 241 func (h *hostFunctionBuilder) WithGoModuleFunction(fn api.GoModuleFunction, params, results []api.ValueType) HostFunctionBuilder { 242 h.fn = &wasm.HostFunc{ 243 ParamTypes: params, 244 ResultTypes: results, 245 Code: &wasm.Code{IsHostFunction: true, GoFunc: fn}, 246 } 247 return h 248 } 249 250 // WithFunc implements HostFunctionBuilder.WithFunc 251 func (h *hostFunctionBuilder) WithFunc(fn interface{}) HostFunctionBuilder { 252 h.fn = fn 253 return h 254 } 255 256 // WithName implements HostFunctionBuilder.WithName 257 func (h *hostFunctionBuilder) WithName(name string) HostFunctionBuilder { 258 h.name = name 259 return h 260 } 261 262 // WithParameterNames implements HostFunctionBuilder.WithParameterNames 263 func (h *hostFunctionBuilder) WithParameterNames(names ...string) HostFunctionBuilder { 264 h.paramNames = names 265 return h 266 } 267 268 // Export implements HostFunctionBuilder.Export 269 func (h *hostFunctionBuilder) Export(exportName string) HostModuleBuilder { 270 if h.name == "" { 271 h.name = exportName 272 } 273 if fn, ok := h.fn.(*wasm.HostFunc); ok { 274 if fn.Name == "" { 275 fn.Name = h.name 276 } 277 fn.ParamNames = h.paramNames 278 fn.ExportNames = []string{exportName} 279 } 280 h.b.nameToGoFunc[exportName] = h.fn 281 if len(h.paramNames) > 0 { 282 h.b.funcToNames[exportName] = append([]string{h.name}, h.paramNames...) 283 } 284 return h.b 285 } 286 287 // ExportHostFunc implements wasm.HostFuncExporter 288 func (b *hostModuleBuilder) ExportHostFunc(fn *wasm.HostFunc) { 289 b.nameToGoFunc[fn.ExportNames[0]] = fn 290 } 291 292 // ExportProxyFunc implements wasm.ProxyFuncExporter 293 func (b *hostModuleBuilder) ExportProxyFunc(fn *wasm.ProxyFunc) { 294 b.nameToGoFunc[fn.Name()] = fn 295 } 296 297 // NewFunctionBuilder implements HostModuleBuilder.NewFunctionBuilder 298 func (b *hostModuleBuilder) NewFunctionBuilder() HostFunctionBuilder { 299 return &hostFunctionBuilder{b: b} 300 } 301 302 // Compile implements HostModuleBuilder.Compile 303 func (b *hostModuleBuilder) Compile(ctx context.Context) (CompiledModule, error) { 304 module, err := wasm.NewHostModule(b.moduleName, b.nameToGoFunc, b.funcToNames, b.r.enabledFeatures) 305 if err != nil { 306 return nil, err 307 } else if err = module.Validate(b.r.enabledFeatures); err != nil { 308 return nil, err 309 } 310 311 c := &compiledModule{module: module, compiledEngine: b.r.store.Engine} 312 listeners, err := buildListeners(ctx, module) 313 if err != nil { 314 return nil, err 315 } 316 317 if err = b.r.store.Engine.CompileModule(ctx, module, listeners); err != nil { 318 return nil, err 319 } 320 321 return c, nil 322 } 323 324 // Instantiate implements HostModuleBuilder.Instantiate 325 func (b *hostModuleBuilder) Instantiate(ctx context.Context, ns Namespace) (api.Module, error) { 326 if compiled, err := b.Compile(ctx); err != nil { 327 return nil, err 328 } else { 329 compiled.(*compiledModule).closeWithModule = true 330 return ns.InstantiateModule(ctx, compiled, NewModuleConfig()) 331 } 332 }