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

     1  package wasi_snapshot_preview1
     2  
     3  import (
     4  	"context"
     5  	"encoding/binary"
     6  
     7  	"wa-lang.org/wazero/api"
     8  	internalsys "wa-lang.org/wazero/internal/sys"
     9  	"wa-lang.org/wazero/internal/wasm"
    10  )
    11  
    12  const functionPollOneoff = "poll_oneoff"
    13  
    14  // https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-eventtype-enumu8
    15  const (
    16  	// eventTypeClock is the timeout event named "name".
    17  	eventTypeClock = iota
    18  	// eventTypeFdRead is the data available event named "fd_read".
    19  	eventTypeFdRead
    20  	// eventTypeFdWrite is the capacity available event named "fd_write".
    21  	eventTypeFdWrite
    22  )
    23  
    24  // pollOneoff is the WASI function named functionPollOneoff that concurrently
    25  // polls for the occurrence of a set of events.
    26  //
    27  // # Parameters
    28  //
    29  //   - in: pointer to the subscriptions (48 bytes each)
    30  //   - out: pointer to the resulting events (32 bytes each)
    31  //   - nsubscriptions: count of subscriptions, zero returns ErrnoInval.
    32  //   - resultNevents: count of events.
    33  //
    34  // Result (Errno)
    35  //
    36  // The return value is ErrnoSuccess except the following error conditions:
    37  //   - ErrnoInval: the parameters are invalid
    38  //   - ErrnoNotsup: a parameters is valid, but not yet supported.
    39  //   - ErrnoFault: there is not enough memory to read the subscriptions or
    40  //     write results.
    41  //
    42  // # Notes
    43  //
    44  //   - Since the `out` pointer nests Errno, the result is always ErrnoSuccess.
    45  //   - importPollOneoff shows this signature in the WebAssembly 1.0 Text Format.
    46  //   - This is similar to `poll` in POSIX.
    47  //
    48  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#poll_oneoff
    49  // See https://linux.die.net/man/3/poll
    50  var pollOneoff = &wasm.HostFunc{
    51  	ExportNames: []string{functionPollOneoff},
    52  	Name:        functionPollOneoff,
    53  	ParamTypes:  []api.ValueType{i32, i32, i32, i32},
    54  	ParamNames:  []string{"in", "out", "nsubscriptions", "result.nevents"},
    55  	ResultTypes: []api.ValueType{i32},
    56  	Code: &wasm.Code{
    57  		IsHostFunction: true,
    58  		GoFunc:         wasiFunc(pollOneoffFn),
    59  	},
    60  }
    61  
    62  func pollOneoffFn(ctx context.Context, mod api.Module, params []uint64) Errno {
    63  	in := uint32(params[0])
    64  	out := uint32(params[1])
    65  	nsubscriptions := uint32(params[2])
    66  	resultNevents := uint32(params[3])
    67  
    68  	if nsubscriptions == 0 {
    69  		return ErrnoInval
    70  	}
    71  
    72  	mem := mod.Memory()
    73  
    74  	// Ensure capacity prior to the read loop to reduce error handling.
    75  	inBuf, ok := mem.Read(ctx, in, nsubscriptions*48)
    76  	if !ok {
    77  		return ErrnoFault
    78  	}
    79  	outBuf, ok := mem.Read(ctx, out, nsubscriptions*32)
    80  	if !ok {
    81  		return ErrnoFault
    82  	}
    83  
    84  	// Eagerly write the number of events which will equal subscriptions unless
    85  	// there's a fault in parsing (not processing).
    86  	if !mod.Memory().WriteUint32Le(ctx, resultNevents, nsubscriptions) {
    87  		return ErrnoFault
    88  	}
    89  
    90  	// Loop through all subscriptions and write their output.
    91  	for sub := uint32(0); sub < nsubscriptions; sub++ {
    92  		inOffset := sub * 48
    93  		outOffset := sub * 32
    94  
    95  		var errno Errno
    96  		eventType := inBuf[inOffset+8] // +8 past userdata
    97  		switch eventType {
    98  		case eventTypeClock: // handle later
    99  			// +8 past userdata +8 name alignment
   100  			errno = processClockEvent(ctx, mod, inBuf[inOffset+8+8:])
   101  		case eventTypeFdRead, eventTypeFdWrite:
   102  			// +8 past userdata +4 FD alignment
   103  			errno = processFDEvent(ctx, mod, eventType, inBuf[inOffset+8+4:])
   104  		default:
   105  			return ErrnoInval
   106  		}
   107  
   108  		// Write the event corresponding to the processed subscription.
   109  		// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-event-struct
   110  		copy(outBuf, inBuf[inOffset:inOffset+8]) // userdata
   111  		outBuf[outOffset+8] = byte(errno)        // uint16, but safe as < 255
   112  		outBuf[outOffset+9] = 0
   113  		binary.LittleEndian.PutUint32(outBuf[outOffset+10:], uint32(eventType))
   114  		// TODO: When FD events are supported, write outOffset+16
   115  	}
   116  	return ErrnoSuccess
   117  }
   118  
   119  // processClockEvent supports only relative name events, as that's what's used
   120  // to implement sleep in various compilers including Rust, Zig and TinyGo.
   121  func processClockEvent(ctx context.Context, mod api.Module, inBuf []byte) Errno {
   122  	_ /* ID */ = binary.LittleEndian.Uint32(inBuf[0:8])          // See below
   123  	timeout := binary.LittleEndian.Uint64(inBuf[8:16])           // nanos if relative
   124  	_ /* precision */ = binary.LittleEndian.Uint64(inBuf[16:24]) // Unused
   125  	flags := binary.LittleEndian.Uint16(inBuf[24:32])
   126  
   127  	// subclockflags has only one flag defined:  subscription_clock_abstime
   128  	switch flags {
   129  	case 0: // relative time
   130  	case 1: // subscription_clock_abstime
   131  		return ErrnoNotsup
   132  	default: // subclockflags has only one flag defined.
   133  		return ErrnoInval
   134  	}
   135  
   136  	// https://linux.die.net/man/3/clock_settime says relative timers are
   137  	// unaffected. Since this function only supports relative timeout, we can
   138  	// skip name ID validation and use a single sleep function.
   139  
   140  	sysCtx := mod.(*wasm.CallContext).Sys
   141  	sysCtx.Nanosleep(ctx, int64(timeout))
   142  	return ErrnoSuccess
   143  }
   144  
   145  // processFDEvent returns a validation error or ErrnoNotsup as file or socket
   146  // subscriptions are not yet supported.
   147  func processFDEvent(ctx context.Context, mod api.Module, eventType byte, inBuf []byte) Errno {
   148  	fd := binary.LittleEndian.Uint32(inBuf)
   149  	sysCtx := mod.(*wasm.CallContext).Sys
   150  
   151  	// Choose the best error, which falls back to unsupported, until we support
   152  	// files.
   153  	errno := ErrnoNotsup
   154  	if eventType == eventTypeFdRead && internalsys.FdReader(ctx, sysCtx, fd) == nil {
   155  		errno = ErrnoBadf
   156  	} else if eventType == eventTypeFdWrite && internalsys.FdWriter(ctx, sysCtx, fd) == nil {
   157  		errno = ErrnoBadf
   158  	}
   159  
   160  	return errno
   161  }