github.com/tetratelabs/wazero@v1.7.3-0.20240513003603-48f702e154b5/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/tetratelabs/wazero"
    24  	"github.com/tetratelabs/wazero/api"
    25  	"github.com/tetratelabs/wazero/experimental/sys"
    26  	"github.com/tetratelabs/wazero/internal/wasip1"
    27  	"github.com/tetratelabs/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  
   229  // writeOffsetsAndNullTerminatedValues is used to write NUL-terminated values
   230  // for args or environ, given a pre-defined bytesLen (which includes NUL
   231  // terminators).
   232  func writeOffsetsAndNullTerminatedValues(mem api.Memory, values [][]byte, offsets, bytes, bytesLen uint32) sys.Errno {
   233  	// The caller may not place bytes directly after offsets, so we have to
   234  	// read them independently.
   235  	valuesLen := len(values)
   236  	offsetsLen := uint32(valuesLen * 4) // uint32Le
   237  	offsetsBuf, ok := mem.Read(offsets, offsetsLen)
   238  	if !ok {
   239  		return sys.EFAULT
   240  	}
   241  	bytesBuf, ok := mem.Read(bytes, bytesLen)
   242  	if !ok {
   243  		return sys.EFAULT
   244  	}
   245  
   246  	// Loop through the values, first writing the location of its data to
   247  	// offsetsBuf[oI], then its NUL-terminated data at bytesBuf[bI]
   248  	var oI, bI uint32
   249  	for _, value := range values {
   250  		// Go can't guarantee inlining as there's not //go:inline directive.
   251  		// This inlines uint32 little-endian encoding instead.
   252  		bytesOffset := bytes + bI
   253  		offsetsBuf[oI] = byte(bytesOffset)
   254  		offsetsBuf[oI+1] = byte(bytesOffset >> 8)
   255  		offsetsBuf[oI+2] = byte(bytesOffset >> 16)
   256  		offsetsBuf[oI+3] = byte(bytesOffset >> 24)
   257  		oI += 4 // size of uint32 we just wrote
   258  
   259  		// Write the next value to memory with a NUL terminator
   260  		copy(bytesBuf[bI:], value)
   261  		bI += uint32(len(value))
   262  		bytesBuf[bI] = 0 // NUL terminator
   263  		bI++
   264  	}
   265  
   266  	return 0
   267  }
   268  
   269  func newHostFunc(
   270  	name string,
   271  	goFunc wasiFunc,
   272  	paramTypes []api.ValueType,
   273  	paramNames ...string,
   274  ) *wasm.HostFunc {
   275  	return &wasm.HostFunc{
   276  		ExportName:  name,
   277  		Name:        name,
   278  		ParamTypes:  paramTypes,
   279  		ParamNames:  paramNames,
   280  		ResultTypes: []api.ValueType{i32},
   281  		ResultNames: []string{"errno"},
   282  		Code:        wasm.Code{GoFunc: goFunc},
   283  	}
   284  }
   285  
   286  // wasiFunc special cases that all WASI functions return a single Errno
   287  // result. The returned value will be written back to the stack at index zero.
   288  type wasiFunc func(ctx context.Context, mod api.Module, params []uint64) sys.Errno
   289  
   290  // Call implements the same method as documented on api.GoModuleFunction.
   291  func (f wasiFunc) Call(ctx context.Context, mod api.Module, stack []uint64) {
   292  	// Write the result back onto the stack
   293  	errno := f(ctx, mod, stack)
   294  	if errno != 0 {
   295  		stack[0] = uint64(wasip1.ToErrno(errno))
   296  	} else { // special case ass ErrnoSuccess is zero
   297  		stack[0] = 0
   298  	}
   299  }
   300  
   301  // stubFunction stubs for GrainLang per #271.
   302  func stubFunction(name string, paramTypes []wasm.ValueType, paramNames ...string) *wasm.HostFunc {
   303  	return &wasm.HostFunc{
   304  		ExportName:  name,
   305  		Name:        name,
   306  		ParamTypes:  paramTypes,
   307  		ParamNames:  paramNames,
   308  		ResultTypes: []api.ValueType{i32},
   309  		ResultNames: []string{"errno"},
   310  		Code: wasm.Code{
   311  			GoFunc: api.GoModuleFunc(func(_ context.Context, _ api.Module, stack []uint64) { stack[0] = uint64(wasip1.ErrnoNosys) }),
   312  		},
   313  	}
   314  }