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  }