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