github.com/bananabytelabs/wazero@v0.0.0-20240105073314-54b22a776da8/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.Instantiate(ctx, wasm)
    15  //
    16  // See https://github.com/WebAssembly/WASI
    17  package wasi_snapshot_preview1
    18  
    19  import (
    20  	"context"
    21  	"encoding/binary"
    22  
    23  	"github.com/bananabytelabs/wazero"
    24  	"github.com/bananabytelabs/wazero/api"
    25  	"github.com/bananabytelabs/wazero/experimental/sys"
    26  	"github.com/bananabytelabs/wazero/internal/wasip1"
    27  	"github.com/bananabytelabs/wazero/internal/wasm"
    28  )
    29  
    30  // ModuleName is the module name WASI functions are exported into.
    31  //
    32  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md
    33  const ModuleName = wasip1.InternalModuleName
    34  
    35  const i32, i64 = wasm.ValueTypeI32, wasm.ValueTypeI64
    36  
    37  var le = binary.LittleEndian
    38  
    39  // MustInstantiate calls Instantiate or panics on error.
    40  //
    41  // This is a simpler function for those who know the module ModuleName is not
    42  // already instantiated, and don't need to unload it.
    43  func MustInstantiate(ctx context.Context, r wazero.Runtime) {
    44  	if _, err := Instantiate(ctx, r); err != nil {
    45  		panic(err)
    46  	}
    47  }
    48  
    49  // Instantiate instantiates the ModuleName module into the runtime.
    50  //
    51  // # Notes
    52  //
    53  //   - Failure cases are documented on wazero.Runtime InstantiateModule.
    54  //   - Closing the wazero.Runtime has the same effect as closing the result.
    55  func Instantiate(ctx context.Context, r wazero.Runtime) (api.Closer, error) {
    56  	return NewBuilder(r).Instantiate(ctx)
    57  }
    58  
    59  // Builder configures the ModuleName module for later use via Compile or Instantiate.
    60  //
    61  // # Notes
    62  //
    63  //   - This is an interface for decoupling, not third-party implementations.
    64  //     All implementations are in wazero.
    65  type Builder interface {
    66  	// Compile compiles the ModuleName module. Call this before Instantiate.
    67  	//
    68  	// Note: This has the same effect as the same function on wazero.HostModuleBuilder.
    69  	Compile(context.Context) (wazero.CompiledModule, error)
    70  
    71  	// Instantiate instantiates the ModuleName module and returns a function to close it.
    72  	//
    73  	// Note: This has the same effect as the same function on wazero.HostModuleBuilder.
    74  	Instantiate(context.Context) (api.Closer, error)
    75  }
    76  
    77  // NewBuilder returns a new Builder.
    78  func NewBuilder(r wazero.Runtime) Builder {
    79  	return &builder{r}
    80  }
    81  
    82  type builder struct{ r wazero.Runtime }
    83  
    84  // hostModuleBuilder returns a new wazero.HostModuleBuilder for ModuleName
    85  func (b *builder) hostModuleBuilder() wazero.HostModuleBuilder {
    86  	ret := b.r.NewHostModuleBuilder(ModuleName)
    87  	exportFunctions(ret)
    88  	return ret
    89  }
    90  
    91  // Compile implements Builder.Compile
    92  func (b *builder) Compile(ctx context.Context) (wazero.CompiledModule, error) {
    93  	return b.hostModuleBuilder().Compile(ctx)
    94  }
    95  
    96  // Instantiate implements Builder.Instantiate
    97  func (b *builder) Instantiate(ctx context.Context) (api.Closer, error) {
    98  	return b.hostModuleBuilder().Instantiate(ctx)
    99  }
   100  
   101  // FunctionExporter exports functions into a wazero.HostModuleBuilder.
   102  //
   103  // # Notes
   104  //
   105  //   - This is an interface for decoupling, not third-party implementations.
   106  //     All implementations are in wazero.
   107  type FunctionExporter interface {
   108  	ExportFunctions(wazero.HostModuleBuilder)
   109  }
   110  
   111  // NewFunctionExporter returns a new FunctionExporter. This is used for the
   112  // following two use cases:
   113  //   - Overriding a builtin function with an alternate implementation.
   114  //   - Exporting functions to the module "wasi_unstable" for legacy code.
   115  //
   116  // # Example of overriding default behavior
   117  //
   118  //	// Export the default WASI functions.
   119  //	wasiBuilder := r.NewHostModuleBuilder(ModuleName)
   120  //	wasi_snapshot_preview1.NewFunctionExporter().ExportFunctions(wasiBuilder)
   121  //
   122  //	// Subsequent calls to NewFunctionBuilder override built-in exports.
   123  //	wasiBuilder.NewFunctionBuilder().
   124  //		WithFunc(func(ctx context.Context, mod api.Module, exitCode uint32) {
   125  //		// your custom logic
   126  //		}).Export("proc_exit")
   127  //
   128  // # Example of using the old module name for WASI
   129  //
   130  //	// Instantiate the current WASI functions under the wasi_unstable
   131  //	// instead of wasi_snapshot_preview1.
   132  //	wasiBuilder := r.NewHostModuleBuilder("wasi_unstable")
   133  //	wasi_snapshot_preview1.NewFunctionExporter().ExportFunctions(wasiBuilder)
   134  //	_, err := wasiBuilder.Instantiate(testCtx, r)
   135  func NewFunctionExporter() FunctionExporter {
   136  	return &functionExporter{}
   137  }
   138  
   139  type functionExporter struct{}
   140  
   141  // ExportFunctions implements FunctionExporter.ExportFunctions
   142  func (functionExporter) ExportFunctions(builder wazero.HostModuleBuilder) {
   143  	exportFunctions(builder)
   144  }
   145  
   146  // ## Translation notes
   147  // ### String
   148  // WebAssembly 1.0 has no string type, so any string input parameter expands to two uint32 parameters: offset
   149  // and length.
   150  //
   151  // ### iovec_array
   152  // `iovec_array` is encoded as two uin32le values (i32): offset and count.
   153  //
   154  // ### Result
   155  // Each result besides Errno is always an uint32 parameter. WebAssembly 1.0 can have up to one result,
   156  // which is already used by Errno. This forces other results to be parameters. A result parameter is a memory
   157  // offset to write the result to. As memory offsets are uint32, each parameter representing a result is uint32.
   158  //
   159  // ### Errno
   160  // The WASI specification is sometimes ambiguous resulting in some runtimes interpreting the same function ways.
   161  // Errno mappings are not defined in WASI, yet, so these mappings are best efforts by maintainers. When in doubt
   162  // about portability, first look at /RATIONALE.md and if needed an issue on
   163  // https://github.com/WebAssembly/WASI/issues
   164  //
   165  // ## Memory
   166  // In WebAssembly 1.0 (20191205), there may be up to one Memory per store, which means api.Memory is always the
   167  // wasm.Store Memories index zero: `store.Memories[0].Buffer`
   168  //
   169  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md
   170  // See https://github.com/WebAssembly/WASI/issues/215
   171  // See https://wwa.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-instances%E2%91%A0.
   172  
   173  // exportFunctions adds all go functions that implement wasi.
   174  // These should be exported in the module named ModuleName.
   175  func exportFunctions(builder wazero.HostModuleBuilder) {
   176  	exporter := builder.(wasm.HostFuncExporter)
   177  
   178  	// Note: these are ordered per spec for consistency even if the resulting
   179  	// map can't guarantee that.
   180  	// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#functions
   181  	exporter.ExportHostFunc(argsGet)
   182  	exporter.ExportHostFunc(argsSizesGet)
   183  	exporter.ExportHostFunc(environGet)
   184  	exporter.ExportHostFunc(environSizesGet)
   185  	exporter.ExportHostFunc(clockResGet)
   186  	exporter.ExportHostFunc(clockTimeGet)
   187  	exporter.ExportHostFunc(fdAdvise)
   188  	exporter.ExportHostFunc(fdAllocate)
   189  	exporter.ExportHostFunc(fdClose)
   190  	exporter.ExportHostFunc(fdDatasync)
   191  	exporter.ExportHostFunc(fdFdstatGet)
   192  	exporter.ExportHostFunc(fdFdstatSetFlags)
   193  	exporter.ExportHostFunc(fdFdstatSetRights)
   194  	exporter.ExportHostFunc(fdFilestatGet)
   195  	exporter.ExportHostFunc(fdFilestatSetSize)
   196  	exporter.ExportHostFunc(fdFilestatSetTimes)
   197  	exporter.ExportHostFunc(fdPread)
   198  	exporter.ExportHostFunc(fdPrestatGet)
   199  	exporter.ExportHostFunc(fdPrestatDirName)
   200  	exporter.ExportHostFunc(fdPwrite)
   201  	exporter.ExportHostFunc(fdRead)
   202  	exporter.ExportHostFunc(fdReaddir)
   203  	exporter.ExportHostFunc(fdRenumber)
   204  	exporter.ExportHostFunc(fdSeek)
   205  	exporter.ExportHostFunc(fdSync)
   206  	exporter.ExportHostFunc(fdTell)
   207  	exporter.ExportHostFunc(fdWrite)
   208  	exporter.ExportHostFunc(pathCreateDirectory)
   209  	exporter.ExportHostFunc(pathFilestatGet)
   210  	exporter.ExportHostFunc(pathFilestatSetTimes)
   211  	exporter.ExportHostFunc(pathLink)
   212  	exporter.ExportHostFunc(pathOpen)
   213  	exporter.ExportHostFunc(pathReadlink)
   214  	exporter.ExportHostFunc(pathRemoveDirectory)
   215  	exporter.ExportHostFunc(pathRename)
   216  	exporter.ExportHostFunc(pathSymlink)
   217  	exporter.ExportHostFunc(pathUnlinkFile)
   218  	exporter.ExportHostFunc(pollOneoff)
   219  	exporter.ExportHostFunc(procExit)
   220  	exporter.ExportHostFunc(procRaise)
   221  	exporter.ExportHostFunc(schedYield)
   222  	exporter.ExportHostFunc(randomGet)
   223  	exporter.ExportHostFunc(sockAccept)
   224  	exporter.ExportHostFunc(sockRecv)
   225  	exporter.ExportHostFunc(sockSend)
   226  	exporter.ExportHostFunc(sockShutdown)
   227  
   228  	exporter.ExportHostFunc(sockSendTo)
   229  	exporter.ExportHostFunc(sockRecvFrom)
   230  	exporter.ExportHostFunc(sockGetPeerAddr)
   231  	exporter.ExportHostFunc(sockGetLocalAddr)
   232  	exporter.ExportHostFunc(sockGetSockOpt)
   233  	exporter.ExportHostFunc(sockSetSockOpt)
   234  	exporter.ExportHostFunc(sockOpen)
   235  	exporter.ExportHostFunc(sockConnect)
   236  }
   237  
   238  // writeOffsetsAndNullTerminatedValues is used to write NUL-terminated values
   239  // for args or environ, given a pre-defined bytesLen (which includes NUL
   240  // terminators).
   241  func writeOffsetsAndNullTerminatedValues(mem api.Memory, values [][]byte, offsets, bytes, bytesLen uint32) sys.Errno {
   242  	// The caller may not place bytes directly after offsets, so we have to
   243  	// read them independently.
   244  	valuesLen := len(values)
   245  	offsetsLen := uint32(valuesLen * 4) // uint32Le
   246  	offsetsBuf, ok := mem.Read(offsets, offsetsLen)
   247  	if !ok {
   248  		return sys.EFAULT
   249  	}
   250  	bytesBuf, ok := mem.Read(bytes, bytesLen)
   251  	if !ok {
   252  		return sys.EFAULT
   253  	}
   254  
   255  	// Loop through the values, first writing the location of its data to
   256  	// offsetsBuf[oI], then its NUL-terminated data at bytesBuf[bI]
   257  	var oI, bI uint32
   258  	for _, value := range values {
   259  		// Go can't guarantee inlining as there's not //go:inline directive.
   260  		// This inlines uint32 little-endian encoding instead.
   261  		bytesOffset := bytes + bI
   262  		offsetsBuf[oI] = byte(bytesOffset)
   263  		offsetsBuf[oI+1] = byte(bytesOffset >> 8)
   264  		offsetsBuf[oI+2] = byte(bytesOffset >> 16)
   265  		offsetsBuf[oI+3] = byte(bytesOffset >> 24)
   266  		oI += 4 // size of uint32 we just wrote
   267  
   268  		// Write the next value to memory with a NUL terminator
   269  		copy(bytesBuf[bI:], value)
   270  		bI += uint32(len(value))
   271  		bytesBuf[bI] = 0 // NUL terminator
   272  		bI++
   273  	}
   274  
   275  	return 0
   276  }
   277  
   278  func newHostFunc(
   279  	name string,
   280  	goFunc wasiFunc,
   281  	paramTypes []api.ValueType,
   282  	paramNames ...string,
   283  ) *wasm.HostFunc {
   284  	return &wasm.HostFunc{
   285  		ExportName:  name,
   286  		Name:        name,
   287  		ParamTypes:  paramTypes,
   288  		ParamNames:  paramNames,
   289  		ResultTypes: []api.ValueType{i32},
   290  		ResultNames: []string{"errno"},
   291  		Code:        wasm.Code{GoFunc: goFunc},
   292  	}
   293  }
   294  
   295  // wasiFunc special cases that all WASI functions return a single Errno
   296  // result. The returned value will be written back to the stack at index zero.
   297  type wasiFunc func(ctx context.Context, mod api.Module, params []uint64) sys.Errno
   298  
   299  // Call implements the same method as documented on api.GoModuleFunction.
   300  func (f wasiFunc) Call(ctx context.Context, mod api.Module, stack []uint64) {
   301  	// Write the result back onto the stack
   302  	errno := f(ctx, mod, stack)
   303  	if errno != 0 {
   304  		stack[0] = uint64(wasip1.ToErrno(errno))
   305  	} else { // special case ass ErrnoSuccess is zero
   306  		stack[0] = 0
   307  	}
   308  }
   309  
   310  // stubFunction stubs for GrainLang per #271.
   311  func stubFunction(name string, paramTypes []wasm.ValueType, paramNames ...string) *wasm.HostFunc {
   312  	return &wasm.HostFunc{
   313  		ExportName:  name,
   314  		Name:        name,
   315  		ParamTypes:  paramTypes,
   316  		ParamNames:  paramNames,
   317  		ResultTypes: []api.ValueType{i32},
   318  		ResultNames: []string{"errno"},
   319  		Code: wasm.Code{
   320  			GoFunc: api.GoModuleFunc(func(_ context.Context, _ api.Module, stack []uint64) { stack[0] = uint64(wasip1.ErrnoNosys) }),
   321  		},
   322  	}
   323  }