github.com/tetratelabs/wazero@v1.2.1/experimental/gojs/gojs.go (about)

     1  // Package gojs allows you to run wasm binaries compiled by Go when
     2  // `GOARCH=wasm GOOS=js`. See https://wazero.io/languages/go/ for more.
     3  //
     4  // # Experimental
     5  //
     6  // Go defines js "EXPERIMENTAL... exempt from the Go compatibility promise."
     7  // Accordingly, wazero cannot guarantee this will work from release to release,
     8  // or that usage will be relatively free of bugs. Moreover, `GOOS=wasi` will
     9  // happen, and once that's available in two releases wazero will remove this
    10  // package.
    11  //
    12  // Due to these concerns and the relatively high implementation overhead, most
    13  // will choose TinyGo instead of gojs.
    14  package gojs
    15  
    16  import (
    17  	"context"
    18  	"net/http"
    19  
    20  	"github.com/tetratelabs/wazero"
    21  	"github.com/tetratelabs/wazero/api"
    22  	. "github.com/tetratelabs/wazero/internal/gojs"
    23  	internalconfig "github.com/tetratelabs/wazero/internal/gojs/config"
    24  	. "github.com/tetratelabs/wazero/internal/gojs/run"
    25  	"github.com/tetratelabs/wazero/internal/wasm"
    26  )
    27  
    28  // MustInstantiate calls Instantiate or panics on error.
    29  //
    30  // This is a simpler function for those who know the module "go" is not
    31  // already instantiated, and don't need to unload it.
    32  func MustInstantiate(ctx context.Context, r wazero.Runtime) {
    33  	if _, err := Instantiate(ctx, r); err != nil {
    34  		panic(err)
    35  	}
    36  }
    37  
    38  // Instantiate instantiates the "go" module, used by `GOARCH=wasm GOOS=js`,
    39  // into the runtime.
    40  //
    41  // # Notes
    42  //
    43  //   - Failure cases are documented on wazero.Runtime InstantiateModule.
    44  //   - Closing the wazero.Runtime has the same effect as closing the result.
    45  //   - To add more functions to the "env" module, use FunctionExporter.
    46  func Instantiate(ctx context.Context, r wazero.Runtime) (api.Closer, error) {
    47  	builder := r.NewHostModuleBuilder("go")
    48  	NewFunctionExporter().ExportFunctions(builder)
    49  	return builder.Instantiate(ctx)
    50  }
    51  
    52  // FunctionExporter configures the functions in the "go" module used by
    53  // `GOARCH=wasm GOOS=js`.
    54  type FunctionExporter interface {
    55  	// ExportFunctions builds functions to export with a
    56  	// wazero.HostModuleBuilder named "go".
    57  	ExportFunctions(wazero.HostModuleBuilder)
    58  }
    59  
    60  // NewFunctionExporter returns a FunctionExporter object.
    61  func NewFunctionExporter() FunctionExporter {
    62  	return &functionExporter{}
    63  }
    64  
    65  type functionExporter struct{}
    66  
    67  // ExportFunctions implements FunctionExporter.ExportFunctions
    68  func (e *functionExporter) ExportFunctions(builder wazero.HostModuleBuilder) {
    69  	hfExporter := builder.(wasm.HostFuncExporter)
    70  
    71  	hfExporter.ExportHostFunc(GetRandomData)
    72  	hfExporter.ExportHostFunc(Nanotime1)
    73  	hfExporter.ExportHostFunc(WasmExit)
    74  	hfExporter.ExportHostFunc(CopyBytesToJS)
    75  	hfExporter.ExportHostFunc(ValueCall)
    76  	hfExporter.ExportHostFunc(ValueGet)
    77  	hfExporter.ExportHostFunc(ValueIndex)
    78  	hfExporter.ExportHostFunc(ValueLength)
    79  	hfExporter.ExportHostFunc(ValueNew)
    80  	hfExporter.ExportHostFunc(ValueSet)
    81  	hfExporter.ExportHostFunc(WasmWrite)
    82  	hfExporter.ExportHostFunc(ResetMemoryDataView)
    83  	hfExporter.ExportHostFunc(Walltime)
    84  	hfExporter.ExportHostFunc(ScheduleTimeoutEvent)
    85  	hfExporter.ExportHostFunc(ClearTimeoutEvent)
    86  	hfExporter.ExportHostFunc(FinalizeRef)
    87  	hfExporter.ExportHostFunc(StringVal)
    88  	hfExporter.ExportHostFunc(ValueDelete)
    89  	hfExporter.ExportHostFunc(ValueSetIndex)
    90  	hfExporter.ExportHostFunc(ValueInvoke)
    91  	hfExporter.ExportHostFunc(ValuePrepareString)
    92  	hfExporter.ExportHostFunc(ValueInstanceOf)
    93  	hfExporter.ExportHostFunc(ValueLoadString)
    94  	hfExporter.ExportHostFunc(CopyBytesToGo)
    95  	hfExporter.ExportHostFunc(Debug)
    96  }
    97  
    98  // Config extends wazero.ModuleConfig with GOOS=js specific extensions.
    99  // Use NewConfig to create an instance.
   100  type Config interface {
   101  	// WithOSWorkdir sets the initial working directory used to Run Wasm to
   102  	// the value of os.Getwd instead of the default of root "/".
   103  	//
   104  	// Here's an example that overrides this to the current directory:
   105  	//
   106  	//	err = gojs.Run(ctx, r, compiled, gojs.NewConfig(moduleConfig).
   107  	//			WithOSWorkdir())
   108  	//
   109  	// Note: To use this feature requires mounting the real root directory via
   110  	// wazero.FSConfig `WithDirMount`. On windows, this root must be the same drive
   111  	// as the value of os.Getwd. For example, it would be an error to mount `C:\`
   112  	// as the guest path "", while the current directory is inside `D:\`.
   113  	WithOSWorkdir() Config
   114  
   115  	// WithOSUser allows the guest to see the current user's uid, gid, euid and
   116  	// groups, instead of zero for each value.
   117  	//
   118  	// Here's an example that uses the real user's IDs:
   119  	//
   120  	//	err = gojs.Run(ctx, r, compiled, gojs.NewConfig(moduleConfig).
   121  	//			WithOSUser())
   122  	//
   123  	// Note: This has no effect on windows.
   124  	WithOSUser() Config
   125  
   126  	// WithRoundTripper sets the http.RoundTripper used to Run Wasm.
   127  	//
   128  	// For example, if the code compiled via `GOARCH=wasm GOOS=js` uses
   129  	// http.RoundTripper, you can avoid failures by assigning an implementation
   130  	// like so:
   131  	//
   132  	//	err = gojs.Run(ctx, r, compiled, gojs.NewConfig(moduleConfig).
   133  	//			WithRoundTripper(ctx, http.DefaultTransport))
   134  	WithRoundTripper(http.RoundTripper) Config
   135  }
   136  
   137  // NewConfig returns a Config that can be used for configuring module instantiation.
   138  func NewConfig(moduleConfig wazero.ModuleConfig) Config {
   139  	return &cfg{moduleConfig: moduleConfig, internal: internalconfig.NewConfig()}
   140  }
   141  
   142  type cfg struct {
   143  	moduleConfig wazero.ModuleConfig
   144  	internal     *internalconfig.Config
   145  }
   146  
   147  func (c *cfg) clone() *cfg {
   148  	return &cfg{moduleConfig: c.moduleConfig, internal: c.internal.Clone()}
   149  }
   150  
   151  // WithOSWorkdir implements Config.WithOSWorkdir
   152  func (c *cfg) WithOSWorkdir() Config {
   153  	ret := c.clone()
   154  	ret.internal.OsWorkdir = true
   155  	return ret
   156  }
   157  
   158  // WithOSUser implements Config.WithOSUser
   159  func (c *cfg) WithOSUser() Config {
   160  	ret := c.clone()
   161  	ret.internal.OsUser = true
   162  	return ret
   163  }
   164  
   165  // WithRoundTripper implements Config.WithRoundTripper
   166  func (c *cfg) WithRoundTripper(rt http.RoundTripper) Config {
   167  	ret := c.clone()
   168  	ret.internal.Rt = rt
   169  	return ret
   170  }
   171  
   172  // Run instantiates a new module and calls "run" with the given config.
   173  //
   174  // # Parameters
   175  //
   176  //   - ctx: context to use when instantiating the module and calling "run".
   177  //   - r: runtime to instantiate both the host and guest (compiled) module in.
   178  //   - compiled: guest binary compiled with `GOARCH=wasm GOOS=js`
   179  //   - config: the Config to use including wazero.ModuleConfig or extensions of
   180  //     it.
   181  //
   182  // # Example
   183  //
   184  // After compiling your Wasm binary with wazero.Runtime's `CompileModule`, run
   185  // it like below:
   186  //
   187  //	// Instantiate host functions needed by gojs
   188  //	gojs.MustInstantiate(ctx, r)
   189  //
   190  //	// Assign any configuration relevant for your compiled wasm.
   191  //	config := gojs.NewConfig(wazero.NewConfig())
   192  //
   193  //	// Run your wasm, notably handing any ExitError
   194  //	err = gojs.Run(ctx, r, compiled, config)
   195  //	if exitErr, ok := err.(*sys.ExitError); ok && exitErr.ExitCode() != 0 {
   196  //		log.Panicln(err)
   197  //	} else if !ok {
   198  //		log.Panicln(err)
   199  //	}
   200  //
   201  // # Notes
   202  //
   203  //   - Wasm generated by `GOARCH=wasm GOOS=js` is very slow to compile: Use
   204  //     wazero.RuntimeConfig with wazero.CompilationCache when re-running the
   205  //     same binary.
   206  //   - The guest module is closed after being run.
   207  func Run(ctx context.Context, r wazero.Runtime, compiled wazero.CompiledModule, moduleConfig Config) error {
   208  	c := moduleConfig.(*cfg)
   209  	_, err := RunAndReturnState(ctx, r, compiled, c.moduleConfig, c.internal)
   210  	return err
   211  }