1package wazero
2
3import (
4 "context"
5
6 "github.com/tetratelabs/wazero/api"
7 "github.com/tetratelabs/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//
36// # Notes
37//
38// - This is an interface for decoupling, not third-party implementations.
39// All implementations are in wazero.
40type 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// - The semantics of host functions assumes the existence of an "importing module" because, for example, the host function needs access to
183// the memory of the importing module. Therefore, direct use of ExportedFunction is forbidden for host modules.
184// Practically speaking, it is usually meaningless to directly call a host function from Go code as it is already somewhere in Go code.
185type HostModuleBuilder interface {
186 // Note: until golang/go#5860, we can't use example tests to embed code in interface godocs.
187
188 // NewFunctionBuilder begins the definition of a host function.
189 NewFunctionBuilder() HostFunctionBuilder
190
191 // Compile returns a CompiledModule that can be instantiated by Runtime.
192 Compile(context.Context) (CompiledModule, error)
193
194 // Instantiate is a convenience that calls Compile, then Runtime.InstantiateModule.
195 // This can fail for reasons documented on Runtime.InstantiateModule.
196 //
197 // Here's an example:
198 //
199 // ctx := context.Background()
200 // r := wazero.NewRuntime(ctx)
201 // defer r.Close(ctx) // This closes everything this Runtime created.
202 //
203 // hello := func() {
204 // println("hello!")
205 // }
206 // env, _ := r.NewHostModuleBuilder("env").
207 // NewFunctionBuilder().WithFunc(hello).Export("hello").
208 // Instantiate(ctx)
209 //
210 // # Notes
211 //
212 // - Closing the Runtime has the same effect as closing the result.
213 // - Fields in the builder are copied during instantiation: Later changes do not affect the instantiated result.
214 // - To avoid using configuration defaults, use Compile instead.
215 Instantiate(context.Context) (api.Module, error)
216}
217
218// hostModuleBuilder implements HostModuleBuilder
219type hostModuleBuilder struct {
220 r *runtime
221 moduleName string
222 exportNames []string
223 nameToHostFunc map[string]*wasm.HostFunc
224}
225
226// NewHostModuleBuilder implements Runtime.NewHostModuleBuilder
227func (r *runtime) NewHostModuleBuilder(moduleName string) HostModuleBuilder {
228 return &hostModuleBuilder{
229 r: r,
230 moduleName: moduleName,
231 nameToHostFunc: map[string]*wasm.HostFunc{},
232 }
233}
234
235// hostFunctionBuilder implements HostFunctionBuilder
236type hostFunctionBuilder struct {
237 b *hostModuleBuilder
238 fn interface{}
239 name string
240 paramNames []string
241 resultNames []string
242}
243
244// WithGoFunction implements HostFunctionBuilder.WithGoFunction
245func (h *hostFunctionBuilder) WithGoFunction(fn api.GoFunction, params, results []api.ValueType) HostFunctionBuilder {
246 h.fn = &wasm.HostFunc{ParamTypes: params, ResultTypes: results, Code: wasm.Code{GoFunc: fn}}
247 return h
248}
249
250// WithGoModuleFunction implements HostFunctionBuilder.WithGoModuleFunction
251func (h *hostFunctionBuilder) WithGoModuleFunction(fn api.GoModuleFunction, params, results []api.ValueType) HostFunctionBuilder {
252 h.fn = &wasm.HostFunc{ParamTypes: params, ResultTypes: results, Code: wasm.Code{GoFunc: fn}}
253 return h
254}
255
256// WithFunc implements HostFunctionBuilder.WithFunc
257func (h *hostFunctionBuilder) WithFunc(fn interface{}) HostFunctionBuilder {
258 h.fn = fn
259 return h
260}
261
262// WithName implements HostFunctionBuilder.WithName
263func (h *hostFunctionBuilder) WithName(name string) HostFunctionBuilder {
264 h.name = name
265 return h
266}
267
268// WithParameterNames implements HostFunctionBuilder.WithParameterNames
269func (h *hostFunctionBuilder) WithParameterNames(names ...string) HostFunctionBuilder {
270 h.paramNames = names
271 return h
272}
273
274// WithResultNames implements HostFunctionBuilder.WithResultNames
275func (h *hostFunctionBuilder) WithResultNames(names ...string) HostFunctionBuilder {
276 h.resultNames = names
277 return h
278}
279
280// Export implements HostFunctionBuilder.Export
281func (h *hostFunctionBuilder) Export(exportName string) HostModuleBuilder {
282 var hostFn *wasm.HostFunc
283 if fn, ok := h.fn.(*wasm.HostFunc); ok {
284 hostFn = fn
285 } else {
286 hostFn = &wasm.HostFunc{Code: wasm.Code{GoFunc: h.fn}}
287 }
288
289 // Assign any names from the builder
290 hostFn.ExportName = exportName
291 if h.name != "" {
292 hostFn.Name = h.name
293 }
294 if len(h.paramNames) != 0 {
295 hostFn.ParamNames = h.paramNames
296 }
297 if len(h.resultNames) != 0 {
298 hostFn.ResultNames = h.resultNames
299 }
300
301 h.b.ExportHostFunc(hostFn)
302 return h.b
303}
304
305// ExportHostFunc implements wasm.HostFuncExporter
306func (b *hostModuleBuilder) ExportHostFunc(fn *wasm.HostFunc) {
307 if _, ok := b.nameToHostFunc[fn.ExportName]; !ok { // add a new name
308 b.exportNames = append(b.exportNames, fn.ExportName)
309 }
310 b.nameToHostFunc[fn.ExportName] = fn
311}
312
313// NewFunctionBuilder implements HostModuleBuilder.NewFunctionBuilder
314func (b *hostModuleBuilder) NewFunctionBuilder() HostFunctionBuilder {
315 return &hostFunctionBuilder{b: b}
316}
317
318// Compile implements HostModuleBuilder.Compile
319func (b *hostModuleBuilder) Compile(ctx context.Context) (CompiledModule, error) {
320 module, err := wasm.NewHostModule(b.moduleName, b.exportNames, b.nameToHostFunc, b.r.enabledFeatures)
321 if err != nil {
322 return nil, err
323 } else if err = module.Validate(b.r.enabledFeatures); err != nil {
324 return nil, err
325 }
326
327 c := &compiledModule{module: module, compiledEngine: b.r.store.Engine}
328 listeners, err := buildFunctionListeners(ctx, module)
329 if err != nil {
330 return nil, err
331 }
332
333 if err = b.r.store.Engine.CompileModule(ctx, module, listeners, false); err != nil {
334 return nil, err
335 }
336
337 // typeIDs are static and compile-time known.
338 typeIDs, err := b.r.store.GetFunctionTypeIDs(module.TypeSection)
339 if err != nil {
340 return nil, err
341 }
342 c.typeIDs = typeIDs
343
344 return c, nil
345}
346
347// hostModuleInstance is a wrapper around api.Module that prevents calling ExportedFunction.
348type hostModuleInstance struct{ api.Module }
349
350// ExportedFunction implements api.Module ExportedFunction.
351func (h hostModuleInstance) ExportedFunction(name string) api.Function {
352 panic("calling ExportedFunction is forbidden on host modules. See the note on ExportedFunction interface")
353}
354
355// Instantiate implements HostModuleBuilder.Instantiate
356func (b *hostModuleBuilder) Instantiate(ctx context.Context) (api.Module, error) {
357 if compiled, err := b.Compile(ctx); err != nil {
358 return nil, err
359 } else {
360 compiled.(*compiledModule).closeWithModule = true
361 m, err := b.r.InstantiateModule(ctx, compiled, NewModuleConfig())
362 if err != nil {
363 return nil, err
364 }
365 return hostModuleInstance{m}, nil
366 }
367}