wa-lang.org/wazero@v1.0.2/imports/wasi_snapshot_preview1/wasi.go (about)

     1  // Package wasi_snapshot_preview1 contains Go-defined functions to access
     2  // system calls, such as opening a file, similar to Go's x/sys package. These
     3  // are accessible from WebAssembly-defined functions via importing ModuleName.
     4  // All WASI functions return a single Errno result: ErrnoSuccess on success.
     5  //
     6  // e.g. Call Instantiate before instantiating any wasm binary that imports
     7  // "wasi_snapshot_preview1", Otherwise, it will error due to missing imports.
     8  //
     9  //	ctx := context.Background()
    10  //	r := wazero.NewRuntime(ctx)
    11  //	defer r.Close(ctx) // This closes everything this Runtime created.
    12  //
    13  //	wasi_snapshot_preview1.MustInstantiate(ctx, r)
    14  //	mod, _ := r.InstantiateModuleFromBinary(ctx, wasm)
    15  //
    16  // See https://github.com/WebAssembly/WASI
    17  package wasi_snapshot_preview1
    18  
    19  import (
    20  	"context"
    21  
    22  	"wa-lang.org/wazero"
    23  	"wa-lang.org/wazero/api"
    24  	"wa-lang.org/wazero/internal/wasm"
    25  )
    26  
    27  // ModuleName is the module name WASI functions are exported into.
    28  //
    29  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md
    30  const (
    31  	ModuleName = "wasi_snapshot_preview1"
    32  	i32, i64   = wasm.ValueTypeI32, wasm.ValueTypeI64
    33  )
    34  
    35  // MustInstantiate calls Instantiate or panics on error.
    36  //
    37  // This is a simpler function for those who know the module ModuleName is not
    38  // already instantiated, and don't need to unload it.
    39  func MustInstantiate(ctx context.Context, r wazero.Runtime) {
    40  	if _, err := Instantiate(ctx, r); err != nil {
    41  		panic(err)
    42  	}
    43  }
    44  
    45  // Instantiate instantiates the ModuleName module into the runtime default
    46  // namespace.
    47  //
    48  // # Notes
    49  //
    50  //   - Failure cases are documented on wazero.Namespace InstantiateModule.
    51  //   - Closing the wazero.Runtime has the same effect as closing the result.
    52  //   - To instantiate into another wazero.Namespace, use NewBuilder instead.
    53  func Instantiate(ctx context.Context, r wazero.Runtime) (api.Closer, error) {
    54  	return NewBuilder(r).Instantiate(ctx, r)
    55  }
    56  
    57  // Builder configures the ModuleName module for later use via Compile or Instantiate.
    58  type Builder interface {
    59  	// Compile compiles the ModuleName module that can instantiated in any
    60  	// namespace (wazero.Namespace).
    61  	//
    62  	// Note: This has the same effect as the same function on wazero.HostModuleBuilder.
    63  	Compile(context.Context) (wazero.CompiledModule, error)
    64  
    65  	// Instantiate instantiates the ModuleName module into the given namespace.
    66  	//
    67  	// Note: This has the same effect as the same function on wazero.HostModuleBuilder.
    68  	Instantiate(context.Context, wazero.Namespace) (api.Closer, error)
    69  }
    70  
    71  // NewBuilder returns a new Builder.
    72  func NewBuilder(r wazero.Runtime) Builder {
    73  	return &builder{r}
    74  }
    75  
    76  type builder struct{ r wazero.Runtime }
    77  
    78  // hostModuleBuilder returns a new wazero.HostModuleBuilder for ModuleName
    79  func (b *builder) hostModuleBuilder() wazero.HostModuleBuilder {
    80  	ret := b.r.NewHostModuleBuilder(ModuleName)
    81  	exportFunctions(ret)
    82  	return ret
    83  }
    84  
    85  // Compile implements Builder.Compile
    86  func (b *builder) Compile(ctx context.Context) (wazero.CompiledModule, error) {
    87  	return b.hostModuleBuilder().Compile(ctx)
    88  }
    89  
    90  // Instantiate implements Builder.Instantiate
    91  func (b *builder) Instantiate(ctx context.Context, ns wazero.Namespace) (api.Closer, error) {
    92  	return b.hostModuleBuilder().Instantiate(ctx, ns)
    93  }
    94  
    95  // FunctionExporter exports functions into a wazero.HostModuleBuilder.
    96  type FunctionExporter interface {
    97  	ExportFunctions(wazero.HostModuleBuilder)
    98  }
    99  
   100  // NewFunctionExporter returns a new FunctionExporter. This is used for the
   101  // following two use cases:
   102  //   - Overriding a builtin function with an alternate implementation.
   103  //   - Exporting functions to the module "wasi_unstable" for legacy code.
   104  //
   105  // # Example of overriding default behavior
   106  //
   107  //	// Export the default WASI functions.
   108  //	wasiBuilder := r.NewHostModuleBuilder(ModuleName)
   109  //	wasi_snapshot_preview1.NewFunctionExporter().ExportFunctions(wasiBuilder)
   110  //
   111  //	// Subsequent calls to NewFunctionBuilder override built-in exports.
   112  //	wasiBuilder.NewFunctionBuilder().
   113  //		WithFunc(func(ctx context.Context, mod api.Module, exitCode uint32) {
   114  //		// your custom logic
   115  //		}).Export("proc_exit")
   116  //
   117  // # Example of using the old module name for WASI
   118  //
   119  //	// Instantiate the current WASI functions under the wasi_unstable
   120  //	// instead of wasi_snapshot_preview1.
   121  //	wasiBuilder := r.NewHostModuleBuilder("wasi_unstable")
   122  //	wasi_snapshot_preview1.NewFunctionExporter().ExportFunctions(wasiBuilder)
   123  //	_, err := wasiBuilder.Instantiate(testCtx, r)
   124  func NewFunctionExporter() FunctionExporter {
   125  	return &functionExporter{}
   126  }
   127  
   128  type functionExporter struct{}
   129  
   130  // ExportFunctions implements FunctionExporter.ExportFunctions
   131  func (functionExporter) ExportFunctions(builder wazero.HostModuleBuilder) {
   132  	exportFunctions(builder)
   133  }
   134  
   135  // ## Translation notes
   136  // ### String
   137  // WebAssembly 1.0 has no string type, so any string input parameter expands to two uint32 parameters: offset
   138  // and length.
   139  //
   140  // ### iovec_array
   141  // `iovec_array` is encoded as two uin32le values (i32): offset and count.
   142  //
   143  // ### Result
   144  // Each result besides Errno is always an uint32 parameter. WebAssembly 1.0 can have up to one result,
   145  // which is already used by Errno. This forces other results to be parameters. A result parameter is a memory
   146  // offset to write the result to. As memory offsets are uint32, each parameter representing a result is uint32.
   147  //
   148  // ### Errno
   149  // The WASI specification is sometimes ambiguous resulting in some runtimes interpreting the same function ways.
   150  // Errno mappings are not defined in WASI, yet, so these mappings are best efforts by maintainers. When in doubt
   151  // about portability, first look at /RATIONALE.md and if needed an issue on
   152  // https://github.com/WebAssembly/WASI/issues
   153  //
   154  // ## Memory
   155  // In WebAssembly 1.0 (20191205), there may be up to one Memory per store, which means api.Memory is always the
   156  // wasm.Store Memories index zero: `store.Memories[0].Buffer`
   157  //
   158  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md
   159  // See https://github.com/WebAssembly/WASI/issues/215
   160  // See https://wwa.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-instances%E2%91%A0.
   161  
   162  // exportFunctions adds all go functions that implement wasi.
   163  // These should be exported in the module named ModuleName.
   164  func exportFunctions(builder wazero.HostModuleBuilder) {
   165  	exporter := builder.(wasm.HostFuncExporter)
   166  
   167  	// Note: these are ordered per spec for consistency even if the resulting
   168  	// map can't guarantee that.
   169  	// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#functions
   170  	exporter.ExportHostFunc(argsGet)
   171  	exporter.ExportHostFunc(argsSizesGet)
   172  	exporter.ExportHostFunc(environGet)
   173  	exporter.ExportHostFunc(environSizesGet)
   174  	exporter.ExportHostFunc(clockResGet)
   175  	exporter.ExportHostFunc(clockTimeGet)
   176  	exporter.ExportHostFunc(fdAdvise)
   177  	exporter.ExportHostFunc(fdAllocate)
   178  	exporter.ExportHostFunc(fdClose)
   179  	exporter.ExportHostFunc(fdDatasync)
   180  	exporter.ExportHostFunc(fdFdstatGet)
   181  	exporter.ExportHostFunc(fdFdstatSetFlags)
   182  	exporter.ExportHostFunc(fdFdstatSetRights)
   183  	exporter.ExportHostFunc(fdFilestatGet)
   184  	exporter.ExportHostFunc(fdFilestatSetSize)
   185  	exporter.ExportHostFunc(fdFilestatSetTimes)
   186  	exporter.ExportHostFunc(fdPread)
   187  	exporter.ExportHostFunc(fdPrestatGet)
   188  	exporter.ExportHostFunc(fdPrestatDirName)
   189  	exporter.ExportHostFunc(fdPwrite)
   190  	exporter.ExportHostFunc(fdRead)
   191  	exporter.ExportHostFunc(fdReaddir)
   192  	exporter.ExportHostFunc(fdRenumber)
   193  	exporter.ExportHostFunc(fdSeek)
   194  	exporter.ExportHostFunc(fdSync)
   195  	exporter.ExportHostFunc(fdTell)
   196  	exporter.ExportHostFunc(fdWrite)
   197  	exporter.ExportHostFunc(pathCreateDirectory)
   198  	exporter.ExportHostFunc(pathFilestatGet)
   199  	exporter.ExportHostFunc(pathFilestatSetTimes)
   200  	exporter.ExportHostFunc(pathLink)
   201  	exporter.ExportHostFunc(pathOpen)
   202  	exporter.ExportHostFunc(pathReadlink)
   203  	exporter.ExportHostFunc(pathRemoveDirectory)
   204  	exporter.ExportHostFunc(pathRename)
   205  	exporter.ExportHostFunc(pathSymlink)
   206  	exporter.ExportHostFunc(pathUnlinkFile)
   207  	exporter.ExportHostFunc(pollOneoff)
   208  	exporter.ExportHostFunc(procExit)
   209  	exporter.ExportHostFunc(procRaise)
   210  	exporter.ExportHostFunc(schedYield)
   211  	exporter.ExportHostFunc(randomGet)
   212  	exporter.ExportHostFunc(sockAccept)
   213  	exporter.ExportHostFunc(sockRecv)
   214  	exporter.ExportHostFunc(sockSend)
   215  	exporter.ExportHostFunc(sockShutdown)
   216  }
   217  
   218  func writeOffsetsAndNullTerminatedValues(ctx context.Context, mem api.Memory, values []string, offsets, bytes uint32) Errno {
   219  	for _, value := range values {
   220  		// Write current offset and advance it.
   221  		if !mem.WriteUint32Le(ctx, offsets, bytes) {
   222  			return ErrnoFault
   223  		}
   224  		offsets += 4 // size of uint32
   225  
   226  		// Write the next value to memory with a NUL terminator
   227  		if !mem.Write(ctx, bytes, []byte(value)) {
   228  			return ErrnoFault
   229  		}
   230  		bytes += uint32(len(value))
   231  		if !mem.WriteByte(ctx, bytes, 0) {
   232  			return ErrnoFault
   233  		}
   234  		bytes++
   235  	}
   236  
   237  	return ErrnoSuccess
   238  }
   239  
   240  // wasiFunc special cases that all WASI functions return a single Errno
   241  // result. The returned value will be written back to the stack at index zero.
   242  type wasiFunc func(ctx context.Context, mod api.Module, params []uint64) Errno
   243  
   244  // Call implements the same method as documented on api.GoModuleFunction.
   245  func (f wasiFunc) Call(ctx context.Context, mod api.Module, stack []uint64) {
   246  	// Write the result back onto the stack
   247  	stack[0] = uint64(f(ctx, mod, stack))
   248  }
   249  
   250  // stubFunction stubs for GrainLang per #271.
   251  func stubFunction(name string, paramTypes []wasm.ValueType, paramNames []string) *wasm.HostFunc {
   252  	return &wasm.HostFunc{
   253  		Name:        name,
   254  		ExportNames: []string{name},
   255  		ParamTypes:  paramTypes,
   256  		ParamNames:  paramNames,
   257  		ResultTypes: []wasm.ValueType{i32},
   258  		Code: &wasm.Code{
   259  			IsHostFunction: true,
   260  			Body:           []byte{wasm.OpcodeI32Const, byte(ErrnoNosys), wasm.OpcodeEnd},
   261  		},
   262  	}
   263  }