Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions compiler/test/stdlib/wasi.event.test.gr
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
module EventTest

from "int64" include Int64
from "result" include Result
from "wasi/event" include Event
from "wasi/file" include File
from "wasi/time" include Time

use Event.{
type Subscription,
type SubscriptionClock,
type SubscriptionContents,
}

let sleep = ns => {
use Int64.{ (*) }
let subscription = {
clock: Time.Monotonic,
timeout: ns,
precision: 1L,
flags: [],
}
Result.unwrap(
Event.pollOneoff(
[{ userdata: 0L, contents: SubscriptionClock(subscription) }]
)
)
void
}

let mut counter = 0L

use Int64.{ (+), (<), (>=) }
while (counter < 3L) {
counter += 1L
let mt1 = Result.unwrap(Time.monotonicTime())
sleep(20_000_000L)
assert Result.unwrap(Time.monotonicTime()) >= mt1 + 20_000_000L
}
1 change: 1 addition & 0 deletions compiler/test/suites/stdlib.re
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ describe("stdlib", ({test, testSkip}) => {
assertStdlib("stack.test");
assertStdlib("priorityqueue.test");
assertStdlib("string.test");
assertStdlib("wasi.event.test");
assertStdlib("wasi.file.test");
assertStdlib(~code=5, "wasi.process.test");
assertStdlib("wasi.random.test");
Expand Down
8 changes: 8 additions & 0 deletions stdlib/runtime/wasi.gr
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ provide foreign wasm proc_raise:
WasmI32 => WasmI32 from "wasi_snapshot_preview1"
provide foreign wasm sched_yield: () => WasmI32 from "wasi_snapshot_preview1"

// events
provide foreign wasm poll_oneoff:
(WasmI32, WasmI32, WasmI32, WasmI32) => WasmI32 from "wasi_snapshot_preview1"

// random
provide foreign wasm random_get:
(WasmI32, WasmI32) => WasmI32 from "wasi_snapshot_preview1"
Expand Down Expand Up @@ -155,6 +159,10 @@ provide let _FDFLAG_RSYNC = 8n
@unsafe
provide let _FDFLAG_SYNC = 16n

// sublock flags
@unsafe
provide let _SUBCLOCK_FLAG_CLOCK_ABSTIME = 1n

// whence
@unsafe
provide let _WHENCE_SET = 0n
Expand Down
12 changes: 12 additions & 0 deletions stdlib/runtime/wasi.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ proc_raise : WasmI32 => WasmI32
sched_yield : () => WasmI32
```

### Wasi.**poll_oneoff**

```grain
poll_oneoff : (WasmI32, WasmI32, WasmI32, WasmI32) => WasmI32
```

### Wasi.**random_get**

```grain
Expand Down Expand Up @@ -371,6 +377,12 @@ _FDFLAG_RSYNC : WasmI32
_FDFLAG_SYNC : WasmI32
```

### Wasi.**_SUBCLOCK_FLAG_CLOCK_ABSTIME**

```grain
_SUBCLOCK_FLAG_CLOCK_ABSTIME : WasmI32
```

### Wasi.**_WHENCE_SET**

```grain
Expand Down
299 changes: 299 additions & 0 deletions stdlib/wasi/event.gr
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
/**
* Subscriptions to events.
*
* @example import Event from "sys/event"
*/
module Event

from "runtime/unsafe/wasmi32" include WasmI32
use WasmI32.{ (+), (*), (|), ltU as (<), (==), (!=) }
from "runtime/unsafe/wasmi64" include WasmI64
from "runtime/unsafe/memory" include Memory
from "runtime/wasi" include Wasi
from "runtime/dataStructures" include DataStructures
use DataStructures.{ newInt32, newInt64, tagSimpleNumber, untagSimpleNumber }

from "list" include List
from "wasi/file" include File
from "wasi/time" include Time

/**
* @section Types: Types included in the Event module.
*/

/**
* User-provided value that may be attached to objects that is retained when
* extracted from the implementation.
*/
provide type Userdata = Int64

/**
* Type of a subscription to an event or its occurrence.
*/
provide enum EventType {
// The time value of clock `SubscriptionClock.id` has
// reached timestamp `SubscriptionClock.timeout`.
Clock,
// File descriptor `SubscriptionFdReadWrite.fileDescriptor` has data
// available for reading. This event always triggers for regular files.
FdRead,
// File descriptor `SubscriptionFdReadWrite.fileDescriptor` has capacity
// available for writing. This event always triggers for regular files.
FdWrite,
}

/**
* The state of the file descriptor subscribed to with
* `EventType.FdRead` or `EventType.FdWrite`.
*/
provide enum EventRWFlags {
// The peer of this socket has closed or disconnected.
FdReadWriteHangup,
}

/**
* The contents of an `event` when type is `EventType.FdRead` or
* `EventType.FdWrite`.
*/
provide record EventFdReadWrite {
// The number of bytes available for reading or writing.
nbytes: Int64,
// The state of the file descriptor.
flags: List<EventRWFlags>,
}

/**
* An event that occurred.
*/
provide record Event {
// User-provided value that got attached to `subscription::userdata`.
userdata: Userdata,
// If non-zero, an error that occurred while processing the subscription request.
error: Number,
// The type of event that occured
eventType: EventType,
// The contents of the event, if it is an `EventType.FdRead` or
// `EventType.FdWrite`. `EventType.Clock` events ignore this field.
fdReadWrite: Option<EventFdReadWrite>,
}

/**
* Flags determining how to interpret the timestamp provided in
* `SubscriptionClock.timeout`
*/
provide enum SubClockFlags {
// If set, treat the timestamp provided in
// `SubscriptionClock.timeout` as an absolute timestamp of clock
// `SubscriptionClock.id`. If clear, treat the timestamp
// provided in `SubscriptionClock.timeout` relative to the
// current time value of clock `SubscriptionClock.id`.
SubscriptionClockAbstime,
}

/**
* The contents of a `Subscription` when type is `EventType.Clock`.
*/
provide record SubscriptionClock {
// The clock against which to compare the timestamp.
clock: Time.Clock,
// The absolute or relative timestamp.
timeout: Int64,
// The amount of time that the implementation may wait additionally
// to coalesce with other events.
precision: Int64,
// Flags specifying whether the timeout is absolute or relative
flags: List<SubClockFlags>,
}

/**
* The contents of a `subscription` when type is type is
* `EventType.FdRead` or `EventType.FdWrite`.
*/
provide record SubscriptionFdReadwrite {
// The file descriptor on which to wait for it to become ready for reading or writing.
fileDescriptor: File.FileDescriptor,
}

provide enum SubscriptionContents {
SubscriptionClock(SubscriptionClock),
SubscriptionFdRead(SubscriptionFdReadwrite),
SubscriptionFdWrite(SubscriptionFdReadwrite),
}

/**
* Subscription to an event.
*/
provide record Subscription {
// User-provided value that is attached to the subscription in the
// implementation and returned through `Event.userdata`.
userdata: Userdata,
// The type of the event to which to subscribe, and its contents
contents: SubscriptionContents,
}

/**
* @section Values: Functions and constants included in the Event module.
*/
@unsafe
let eventRWFlagsToList = flags => {
if (flags == 0n) {
[]
} else if (flags == 1n) {
[FdReadWriteHangup]
} else {
fail "Unknown event flag"
}
}

@unsafe
let rec combineSubClockFlagsHelp = (acc, subclockFlags) => {
match (subclockFlags) {
[hd, ...tl] => {
let flag = match (hd) {
SubscriptionClockAbstime => Wasi._SUBCLOCK_FLAG_CLOCK_ABSTIME,
}
combineSubClockFlagsHelp(acc | flag, tl)
},
[] => acc,
}
}
@unsafe
let combineSubClockFlags = subclockFlags => {
combineSubClockFlagsHelp(0n, subclockFlags)
}

/**
* Concurrently poll for the occurrence of a set of events.
* @param subscriptions: The events to which to subscribe.
* @returns `Ok(events)` of the events which occured if successful or `Err(Exception)` otherwise
*/
@unsafe
provide let pollOneoff = subscriptions => {
// subscription layout
/*
userdata 8
eventtype 1
padding 7
sub(clock) 26 or sub(fd) 4
*/

let nSubscriptions = untagSimpleNumber(List.length(subscriptions))
let subsBuf = Memory.malloc(nSubscriptions * 48n)

let mut subscriptions = subscriptions
for (let mut i = 0n;; i += 48n) {
match (subscriptions) {
[] => break,
[{ contents, userdata }, ...tl] => {
subscriptions = tl

let subBuf = subsBuf + i

let userdata = WasmI64.load(WasmI32.fromGrain(userdata), 8n)
WasmI64.store(subBuf, userdata, 0n)

match (contents) {
SubscriptionClock({ clock, timeout, precision, flags }) => {
WasmI32.store8(subBuf, 0n, 8n)

let clockid = match (clock) {
Time.Realtime => Wasi._CLOCK_REALTIME,
Time.Monotonic => Wasi._CLOCK_MONOTONIC,
Time.ProcessCpuTime => Wasi._CLOCK_PROCESS_CPUTIME,
Time.ThreadCpuTime => Wasi._CLOCK_THREAD_CPUTIME,
}
WasmI32.store(subBuf, clockid, 16n)

let timeout = WasmI64.load(WasmI32.fromGrain(timeout), 8n)
WasmI64.store(subBuf, timeout, 24n)

let precision = WasmI64.load(WasmI32.fromGrain(precision), 8n)
WasmI64.store(subBuf, precision, 32n)

let flags = combineSubClockFlags(flags)
WasmI32.store16(subBuf, flags, 40n)
},
SubscriptionFdRead({ fileDescriptor }) => {
WasmI32.store8(subBuf, 1n, 8n)

let fd = match (fileDescriptor) {
File.FileDescriptor(n) => untagSimpleNumber(n),
}
WasmI32.store(subBuf, fd, 16n)

WasmI32.store(subsBuf, subBuf, i)
},
SubscriptionFdWrite({ fileDescriptor }) => {
WasmI32.store8(subBuf, 2n, 8n)

let fd = match (fileDescriptor) {
File.FileDescriptor(n) => untagSimpleNumber(n),
}
WasmI32.store(subBuf, fd, 16n)

WasmI32.store(subsBuf, subBuf, i)
},
}
},
}
}

let eventsBuf = Memory.malloc(nSubscriptions * 32n)

let nevents = Memory.malloc(4n)
let err = Wasi.poll_oneoff(subsBuf, eventsBuf, nSubscriptions, nevents)

Memory.free(subsBuf)

if (err != Wasi._ESUCCESS) {
Memory.free(eventsBuf)
Memory.free(nevents)
Err(Wasi.SystemError(tagSimpleNumber(err)))
} else {
let eventCount = WasmI32.load(nevents, 0n)
Memory.free(nevents)

let mut results = []

for (let mut i = 0n; i < eventCount; i += 1n) {
let event = eventsBuf + i * 32n
let userdata = WasmI32.toGrain(newInt64(WasmI64.load(event, 0n))): Int64
let error = tagSimpleNumber(WasmI32.load16U(event, 8n))
match (WasmI32.load8U(event, 10n)) {
0n => {
let event = { userdata, error, eventType: Clock, fdReadWrite: None }
results = [event, ...results]
},
1n => {
let nbytes = WasmI32.toGrain(newInt64(WasmI64.load(event, 16n))):
Int64
let flags = eventRWFlagsToList(WasmI32.load16U(event, 24n))
let event = {
userdata,
error,
eventType: FdRead,
fdReadWrite: Some({ nbytes, flags }),
}
results = [event, ...results]
},
2n => {
let nbytes = WasmI32.toGrain(newInt64(WasmI64.load(event, 16n))):
Int64
let flags = eventRWFlagsToList(WasmI32.load16U(event, 24n))
let event = {
userdata,
error,
eventType: FdWrite,
fdReadWrite: Some({ nbytes, flags }),
}
results = [event, ...results]
},
_ => fail "Unknown event type",
}
}

Memory.free(eventsBuf)

Ok(List.reverse(results))
}
}
Loading