github.com/bananabytelabs/wazero@v0.0.0-20240105073314-54b22a776da8/builder.go (about)

     1  package wazero
     2  
     3  import (
     4  	"context"
     5  
     6  	"github.com/bananabytelabs/wazero/api"
     7  	"github.com/bananabytelabs/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.
    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  }