Skip to content

Commit f60eb5a

Browse files
authored
Separately gate error-context from async (#489)
This commit adds a new emoji-gate of 📝 to gate the `error-context` type separately from async. This additionally removes integration of `error-context` with built-in future/stream types where they can still be closed but are no longer closed with an optional error.
1 parent f7b29a6 commit f60eb5a

File tree

5 files changed

+96
-160
lines changed

5 files changed

+96
-160
lines changed

design/mvp/Binary.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ primvaltype ::= 0x7f => bool
190190
| 0x75 => f64
191191
| 0x74 => char
192192
| 0x73 => string
193-
| 0x64 => error-context 🔀
193+
| 0x64 => error-context 📝
194194
defvaltype ::= pvt:<primvaltype> => pvt
195195
| 0x72 lt*:vec(<labelvaltype>) => (record (field lt)*) (if |lt*| > 0)
196196
| 0x71 case*:vec(<case>) => (variant case+) (if |case*| > 0)
@@ -307,9 +307,9 @@ canon ::= 0x00 0x00 f:<core:funcidx> opts:<opts> ft:<typeidx> => (canon lift
307307
| 0x19 t:<typeidx> async?:<async?> => (canon future.cancel-write async? (core func)) 🔀
308308
| 0x1a t:<typeidx> => (canon future.close-readable t (core func)) 🔀
309309
| 0x1b t:<typeidx> => (canon future.close-writable t (core func)) 🔀
310-
| 0x1c opts:<opts> => (canon error-context.new opts (core func)) 🔀
311-
| 0x1d opts:<opts> => (canon error-context.debug-message opts (core func)) 🔀
312-
| 0x1e => (canon error-context.drop (core func)) 🔀
310+
| 0x1c opts:<opts> => (canon error-context.new opts (core func)) 📝
311+
| 0x1d opts:<opts> => (canon error-context.debug-message opts (core func)) 📝
312+
| 0x1e => (canon error-context.drop (core func)) 📝
313313
| 0x1f => (canon waitable-set.new (core func)) 🔀
314314
| 0x20 async?:<async>? m:<core:memidx> => (canon waitable-set.wait async? (memory m) (core func)) 🔀
315315
| 0x21 async?:<async>? m:<core:memidx> => (canon waitable-set.poll async? (memory m) (core func)) 🔀

design/mvp/CanonicalABI.md

Lines changed: 25 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ being specified here.
5252
* [`canon {stream,future}.{read,write}`](#-canon-streamfuturereadwrite) 🔀
5353
* [`canon {stream,future}.cancel-{read,write}`](#-canon-streamfuturecancel-readwrite) 🔀
5454
* [`canon {stream,future}.close-{readable,writable}`](#-canon-streamfutureclose-readablewritable) 🔀
55-
* [`canon error-context.new`](#-canon-error-contextnew) 🔀
56-
* [`canon error-context.debug-message`](#-canon-error-contextdebug-message) 🔀
57-
* [`canon error-context.drop`](#-canon-error-contextdrop) 🔀
55+
* [`canon error-context.new`](#-canon-error-contextnew) 📝
56+
* [`canon error-context.debug-message`](#-canon-error-contextdebug-message) 📝
57+
* [`canon error-context.drop`](#-canon-error-contextdrop) 📝
5858
* [`canon thread.spawn_ref`](#-canon-threadspawn_ref) 🧵
5959
* [`canon thread.spawn_indirect`](#-canon-threadspawn_indirect) 🧵
6060
* [`canon thread.available_parallelism`](#-canon-threadavailable_parallelism) 🧵
@@ -1062,9 +1062,8 @@ class ReadableStream:
10621062
t: ValType
10631063
read: Callable[[WritableBuffer, OnPartialCopy, OnCopyDone], Literal['done','blocked']]
10641064
cancel: Callable[[], None]
1065-
close: Callable[[Optional[ErrorContext]]]
1065+
close: Callable[[]]
10661066
closed: Callable[[], bool]
1067-
closed_with: Callable[[], Optional[ErrorContext]]
10681067
```
10691068
The key operation is `read` which works as follows:
10701069
* `read` is non-blocking, returning `'blocked'` if it would have blocked.
@@ -1100,7 +1099,6 @@ class in chunks, starting with the fields and initialization:
11001099
class ReadableStreamGuestImpl(ReadableStream):
11011100
impl: ComponentInstance
11021101
closed_: bool
1103-
maybe_errctx: Optional[ErrorContext]
11041102
pending_buffer: Optional[Buffer]
11051103
pending_on_partial_copy: Optional[OnPartialCopy]
11061104
pending_on_copy_done: Optional[OnCopyDone]
@@ -1109,7 +1107,6 @@ class ReadableStreamGuestImpl(ReadableStream):
11091107
self.t = t
11101108
self.impl = inst
11111109
self.closed_ = False
1112-
self.maybe_errctx = None
11131110
self.reset_pending()
11141111

11151112
def reset_pending(self):
@@ -1135,19 +1132,14 @@ been returned:
11351132
def cancel(self):
11361133
self.reset_and_notify_pending()
11371134

1138-
def close(self, maybe_errctx):
1135+
def close(self):
11391136
if not self.closed_:
11401137
self.closed_ = True
1141-
self.maybe_errctx = maybe_errctx
11421138
if self.pending_buffer:
11431139
self.reset_and_notify_pending()
11441140

11451141
def closed(self):
11461142
return self.closed_
1147-
1148-
def closed_with(self):
1149-
assert(self.closed_)
1150-
return self.maybe_errctx
11511143
```
11521144
While the abstract `ReadableStream` interface *allows* `cancel` to return
11531145
without having returned ownership of the buffer (which, in general, is
@@ -1212,9 +1204,9 @@ class StreamEnd(Waitable):
12121204
self.stream = stream
12131205
self.copying = False
12141206

1215-
def drop(self, maybe_errctx):
1207+
def drop(self):
12161208
trap_if(self.copying)
1217-
self.stream.close(maybe_errctx)
1209+
self.stream.close()
12181210
Waitable.drop(self)
12191211

12201212
class ReadableStreamEnd(StreamEnd):
@@ -1251,11 +1243,11 @@ class FutureEnd(StreamEnd):
12511243
assert(buffer.remain() == 1)
12521244
def on_copy_done_wrapper():
12531245
if buffer.remain() == 0:
1254-
self.stream.close(maybe_errctx = None)
1246+
self.stream.close()
12551247
on_copy_done()
12561248
ret = copy_op(buffer, on_partial_copy = None, on_copy_done = on_copy_done_wrapper)
12571249
if ret == 'done' and buffer.remain() == 0:
1258-
self.stream.close(maybe_errctx = None)
1250+
self.stream.close()
12591251
return ret
12601252

12611253
class ReadableFutureEnd(FutureEnd):
@@ -1266,9 +1258,8 @@ class WritableFutureEnd(FutureEnd):
12661258
paired: bool = False
12671259
def copy(self, src, on_partial_copy, on_copy_done):
12681260
return self.close_after_copy(self.stream.write, src, on_copy_done)
1269-
def drop(self, maybe_errctx):
1270-
trap_if(not self.stream.closed() and not maybe_errctx)
1271-
FutureEnd.drop(self, maybe_errctx)
1261+
def drop(self):
1262+
FutureEnd.drop(self)
12721263
```
12731264
The `future.{read,write}` built-ins fix the buffer length to `1`, ensuring the
12741265
`assert(buffer.remain() == 1)` holds. Because of this, there are no partial
@@ -3607,14 +3598,7 @@ def pack_copy_result(task, buffer, e):
36073598
assert(not (buffer.progress & CLOSED))
36083599
return buffer.progress
36093600
else:
3610-
if (maybe_errctx := e.stream.closed_with()):
3611-
errctxi = task.inst.error_contexts.add(maybe_errctx)
3612-
assert(errctxi != 0)
3613-
else:
3614-
errctxi = 0
3615-
assert(errctxi <= Table.MAX_LENGTH < BLOCKED)
3616-
assert(not (errctxi & CLOSED))
3617-
return errctxi | CLOSED
3601+
return CLOSED
36183602
```
36193603
The order of tests here indicates that, if some progress was made and then the
36203604
stream was closed, only the progress is reported and the `CLOSED` status is
@@ -3705,41 +3689,29 @@ the given index from the current component instance's `waitable` table,
37053689
performing the guards and bookkeeping defined by
37063690
`{Readable,Writable}{Stream,Future}End.drop()` above.
37073691
```python
3708-
async def canon_stream_close_readable(t, task, i, errctxi):
3709-
return await close(ReadableStreamEnd, t, task, i, errctxi)
3692+
async def canon_stream_close_readable(t, task, i):
3693+
return await close(ReadableStreamEnd, t, task, i)
37103694

3711-
async def canon_stream_close_writable(t, task, hi, errctxi):
3712-
return await close(WritableStreamEnd, t, task, hi, errctxi)
3695+
async def canon_stream_close_writable(t, task, hi):
3696+
return await close(WritableStreamEnd, t, task, hi)
37133697

3714-
async def canon_future_close_readable(t, task, i, errctxi):
3715-
return await close(ReadableFutureEnd, t, task, i, errctxi)
3698+
async def canon_future_close_readable(t, task, i):
3699+
return await close(ReadableFutureEnd, t, task, i)
37163700

3717-
async def canon_future_close_writable(t, task, hi, errctxi):
3718-
return await close(WritableFutureEnd, t, task, hi, errctxi)
3701+
async def canon_future_close_writable(t, task, hi):
3702+
return await close(WritableFutureEnd, t, task, hi)
37193703

3720-
async def close(EndT, t, task, hi, errctxi):
3704+
async def close(EndT, t, task, hi):
37213705
trap_if(not task.inst.may_leave)
37223706
e = task.inst.waitables.remove(hi)
3723-
if errctxi == 0:
3724-
maybe_errctx = None
3725-
else:
3726-
maybe_errctx = task.inst.error_contexts.get(errctxi)
37273707
trap_if(not isinstance(e, EndT))
37283708
trap_if(e.stream.t != t)
3729-
e.drop(maybe_errctx)
3709+
e.drop()
37303710
return []
37313711
```
3732-
Passing a non-zero `errctxi` index indicates that this stream end is being
3733-
closed due to an error, with the given `error-context` providing information
3734-
that can be printed to aid in debugging. While, as explained above, the
3735-
*contents* of the `error-context` value are non-deterministic (and may, e.g.,
3736-
be empty), the presence or absence of an `error-context` value is semantically
3737-
meaningful for distinguishing between success or failure. Concretely, the
3738-
packed `i32` returned by `{stream,future}.{read,write}` operations indicates
3739-
success or failure by whether the `error-context` index is `0` or not.
37403712

37413713

3742-
### 🔀 `canon error-context.new`
3714+
### 📝 `canon error-context.new`
37433715

37443716
For a canonical definition:
37453717
```wat
@@ -3780,7 +3752,7 @@ are not checked. (Note that `host_defined_transformation` is not defined by the
37803752
Canonical ABI and stands for an arbitrary host-defined function.)
37813753

37823754

3783-
### 🔀 `canon error-context.debug-message`
3755+
### 📝 `canon error-context.debug-message`
37843756

37853757
For a canonical definition:
37863758
```wat
@@ -3808,7 +3780,7 @@ async def canon_error_context_debug_message(opts, task, i, ptr):
38083780
Note that `ptr` points to an 8-byte region of memory into which will be stored
38093781
the pointer and length of the debug string (allocated via `opts.realloc`).
38103782

3811-
### 🔀 `canon error-context.drop`
3783+
### 📝 `canon error-context.drop`
38123784

38133785
For a canonical definition:
38143786
```wat

design/mvp/Explainer.md

Lines changed: 18 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ implemented, considered stable and included in a future milestone:
5454
* 🚟: using `async` with `canon lift` without `callback` (stackful lift)
5555
* 🧵: threading built-ins
5656
* 🔧: fixed-length lists
57+
* 📝: the `error-context` type
5758

5859
(Based on the previous [scoping and layering] proposal to the WebAssembly CG,
5960
this repo merges and supersedes the [module-linking] and [interface-types]
@@ -546,7 +547,7 @@ defvaltype ::= bool
546547
| s8 | u8 | s16 | u16 | s32 | u32 | s64 | u64
547548
| f32 | f64
548549
| char | string
549-
| error-context 🔀
550+
| error-context 📝
550551
| (record (field "<label>" <valtype>)+)
551552
| (variant (case "<label>" <valtype>?)+)
552553
| (list <valtype>)
@@ -605,14 +606,14 @@ sets of abstract values:
605606
| `u8`, `u16`, `u32`, `u64` | integers in the range [0, 2<sup>N</sup>-1] |
606607
| `f32`, `f64` | [IEEE754] floating-point numbers, with a single NaN value |
607608
| `char` | [Unicode Scalar Values] |
608-
| `error-context` | an immutable, non-deterministic, host-defined value meant to aid in debugging |
609+
| `error-context` 📝 | an immutable, non-deterministic, host-defined value meant to aid in debugging |
609610
| `record` | heterogeneous [tuples] of named values |
610611
| `variant` | heterogeneous [tagged unions] of named values |
611612
| `list` | homogeneous, variable- or fixed-length [sequences] of values |
612613
| `own` | a unique, opaque address of a resource that will be destroyed when this value is dropped |
613614
| `borrow` | an opaque address of a resource that must be dropped before the current export call returns |
614-
| `stream` | an asynchronously-passed list of homogeneous values |
615-
| `future` | an asynchronously-passed single value |
615+
| `stream` 🔀 | an asynchronously-passed list of homogeneous values |
616+
| `future` 🔀 | an asynchronously-passed single value |
616617

617618
How these abstract values are produced and consumed from Core WebAssembly
618619
values and linear memory is configured by the component via *canonical lifting
@@ -637,7 +638,7 @@ a single NaN value. And boolean values in core wasm are usually represented as
637638
`i32`s where operations interpret all-zeros as `false`, while at the
638639
component-level there is a `bool` type with `true` and `false` values.
639640

640-
##### 🔀 Error Context type
641+
##### 📝 Error Context type
641642

642643
Values of `error-context` type are immutable, non-deterministic, host-defined
643644
and meant to be propagated from failure sources to callers in order to aid in
@@ -1438,9 +1439,9 @@ canon ::= ...
14381439
| (canon future.cancel-write <typeidx> async? (core func <id>?)) 🔀
14391440
| (canon future.close-readable <typeidx> (core func <id>?)) 🔀
14401441
| (canon future.close-writable <typeidx> (core func <id>?)) 🔀
1441-
| (canon error-context.new <canonopt>* (core func <id>?))
1442-
| (canon error-context.debug-message <canonopt>* (core func <id>?))
1443-
| (canon error-context.drop (core func <id>?))
1442+
| (canon error-context.new <canonopt>* (core func <id>?)) 📝
1443+
| (canon error-context.debug-message <canonopt>* (core func <id>?)) 📝
1444+
| (canon error-context.drop (core func <id>?)) 📝
14441445
| (canon thread.spawn_ref <typeidx> (core func <id>?)) 🧵
14451446
| (canon thread.spawn_indirect <typeidx> <core:tableidx> (core func <id>?)) 🧵
14461447
| (canon thread.available_parallelism (core func <id>?)) 🧵
@@ -1780,7 +1781,7 @@ enum read-status {
17801781
blocked,
17811782
17821783
// The end of the stream has been reached.
1783-
closed(option<error-context>),
1784+
closed,
17841785
}
17851786
```
17861787

@@ -1816,8 +1817,6 @@ the Canonical ABI explainer for details.)
18161817
`read-status` and `write-status` are lowered in the Canonical ABI as:
18171818
- The value `0xffff_ffff` represents `blocked`.
18181819
- Otherwise, if the bit `0x8000_0000` is set, the value represents `closed`.
1819-
For `read-status`, the remaining bits `0x7fff_ffff` contain the index of an
1820-
`error-context` in the instance's `error-context` table.
18211820
- Otherwise, the value represents `complete` and contains the number of
18221821
element read or written.
18231822

@@ -1883,24 +1882,20 @@ delivered to indicate the completion of the `read` or `write`. (See
18831882

18841883
| Synopsis | |
18851884
| ----------------------------------------------------- | ---------------------------------------------------------------- |
1886-
| Approximate WIT signature for `stream.close-readable` | `func<T>(e: readable-stream-end<T>, err: option<error-context>)` |
1887-
| Approximate WIT signature for `stream.close-writable` | `func<T>(e: writable-stream-end<T>, err: option<error-context>)` |
1888-
| Approximate WIT signature for `future.close-readable` | `func<T>(e: readable-future-end<T>, err: option<error-context>)` |
1889-
| Approximate WIT signature for `future.close-writable` | `func<T>(e: writable-future-end<T>, err: option<error-context>)` |
1885+
| Approximate WIT signature for `stream.close-readable` | `func<T>(e: readable-stream-end<T>)` |
1886+
| Approximate WIT signature for `stream.close-writable` | `func<T>(e: writable-stream-end<T>)` |
1887+
| Approximate WIT signature for `future.close-readable` | `func<T>(e: readable-future-end<T>)` |
1888+
| Approximate WIT signature for `future.close-writable` | `func<T>(e: writable-future-end<T>)` |
18901889
| Canonical ABI signature | `[end:i32 err:i32] -> []` |
18911890

18921891
The `{stream,future}.close-{readable,writable}` built-ins remove the indicated
18931892
[stream or future] from the current component instance's table of [waitables],
18941893
trapping if the stream or future has a mismatched direction or type or are in
18951894
the middle of a `read` or `write`.
18961895

1897-
In the Canonical ABI, an `err` value of `0` represents `none`, and a non-zero
1898-
value represents `some` of the index of an `error-context` in the instance's
1899-
table. (See also [the `close` built-ins] in the Canonical ABI explainer.)
1900-
1901-
##### 🔀 Error Context built-ins
1896+
##### 📝 Error Context built-ins
19021897

1903-
###### `error-context.new`
1898+
###### 📝 `error-context.new`
19041899

19051900
| Synopsis | |
19061901
| -------------------------------- | ---------------------------------------- |
@@ -1915,7 +1910,7 @@ In the Canonical ABI, the returned value is an index into a
19151910
per-component-instance table. (See also [`canon_error_context_new`] in the
19161911
Canonical ABI explainer.)
19171912

1918-
###### `error-context.debug-message`
1913+
###### 📝 `error-context.debug-message`
19191914

19201915
| Synopsis | |
19211916
| -------------------------------- | --------------------------------------- |
@@ -1930,7 +1925,7 @@ In the Canonical ABI, it writes the debug message into `ptr` as an 8-byte
19301925
`<canonopt>*` immediates. (See also [`canon_error_context_debug_message`] in
19311926
the Canonical ABI explainer.)
19321927

1933-
###### `error-context.drop`
1928+
###### 📝 `error-context.drop`
19341929

19351930
| Synopsis | |
19361931
| -------------------------------- | ----------------------------- |

0 commit comments

Comments
 (0)