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 }