From e24b77fd210096adec58b7132eeb4fedd1abef9c Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 1 Apr 2025 09:01:02 -0700 Subject: [PATCH] Separately gate `error-context` from async MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- design/mvp/Binary.md | 8 +-- design/mvp/CanonicalABI.md | 78 ++++++++----------------- design/mvp/Explainer.md | 41 ++++++------- design/mvp/canonical-abi/definitions.py | 61 +++++++------------ design/mvp/canonical-abi/run_tests.py | 68 +++++++++------------ 5 files changed, 96 insertions(+), 160 deletions(-) diff --git a/design/mvp/Binary.md b/design/mvp/Binary.md index 8d5034b8..64cbe3bd 100644 --- a/design/mvp/Binary.md +++ b/design/mvp/Binary.md @@ -190,7 +190,7 @@ primvaltype ::= 0x7f => bool | 0x75 => f64 | 0x74 => char | 0x73 => string - | 0x64 => error-context ๐Ÿ”€ + | 0x64 => error-context ๐Ÿ“ defvaltype ::= pvt: => pvt | 0x72 lt*:vec() => (record (field lt)*) (if |lt*| > 0) | 0x71 case*:vec() => (variant case+) (if |case*| > 0) @@ -307,9 +307,9 @@ canon ::= 0x00 0x00 f: opts: ft: => (canon lift | 0x19 t: async?: => (canon future.cancel-write async? (core func)) ๐Ÿ”€ | 0x1a t: => (canon future.close-readable t (core func)) ๐Ÿ”€ | 0x1b t: => (canon future.close-writable t (core func)) ๐Ÿ”€ - | 0x1c opts: => (canon error-context.new opts (core func)) ๐Ÿ”€ - | 0x1d opts: => (canon error-context.debug-message opts (core func)) ๐Ÿ”€ - | 0x1e => (canon error-context.drop (core func)) ๐Ÿ”€ + | 0x1c opts: => (canon error-context.new opts (core func)) ๐Ÿ“ + | 0x1d opts: => (canon error-context.debug-message opts (core func)) ๐Ÿ“ + | 0x1e => (canon error-context.drop (core func)) ๐Ÿ“ | 0x1f => (canon waitable-set.new (core func)) ๐Ÿ”€ | 0x20 async?:? m: => (canon waitable-set.wait async? (memory m) (core func)) ๐Ÿ”€ | 0x21 async?:? m: => (canon waitable-set.poll async? (memory m) (core func)) ๐Ÿ”€ diff --git a/design/mvp/CanonicalABI.md b/design/mvp/CanonicalABI.md index 7afbe050..9eaed1ed 100644 --- a/design/mvp/CanonicalABI.md +++ b/design/mvp/CanonicalABI.md @@ -52,9 +52,9 @@ being specified here. * [`canon {stream,future}.{read,write}`](#-canon-streamfuturereadwrite) ๐Ÿ”€ * [`canon {stream,future}.cancel-{read,write}`](#-canon-streamfuturecancel-readwrite) ๐Ÿ”€ * [`canon {stream,future}.close-{readable,writable}`](#-canon-streamfutureclose-readablewritable) ๐Ÿ”€ - * [`canon error-context.new`](#-canon-error-contextnew) ๐Ÿ”€ - * [`canon error-context.debug-message`](#-canon-error-contextdebug-message) ๐Ÿ”€ - * [`canon error-context.drop`](#-canon-error-contextdrop) ๐Ÿ”€ + * [`canon error-context.new`](#-canon-error-contextnew) ๐Ÿ“ + * [`canon error-context.debug-message`](#-canon-error-contextdebug-message) ๐Ÿ“ + * [`canon error-context.drop`](#-canon-error-contextdrop) ๐Ÿ“ * [`canon thread.spawn_ref`](#-canon-threadspawn_ref) ๐Ÿงต * [`canon thread.spawn_indirect`](#-canon-threadspawn_indirect) ๐Ÿงต * [`canon thread.available_parallelism`](#-canon-threadavailable_parallelism) ๐Ÿงต @@ -1062,9 +1062,8 @@ class ReadableStream: t: ValType read: Callable[[WritableBuffer, OnPartialCopy, OnCopyDone], Literal['done','blocked']] cancel: Callable[[], None] - close: Callable[[Optional[ErrorContext]]] + close: Callable[[]] closed: Callable[[], bool] - closed_with: Callable[[], Optional[ErrorContext]] ``` The key operation is `read` which works as follows: * `read` is non-blocking, returning `'blocked'` if it would have blocked. @@ -1100,7 +1099,6 @@ class in chunks, starting with the fields and initialization: class ReadableStreamGuestImpl(ReadableStream): impl: ComponentInstance closed_: bool - maybe_errctx: Optional[ErrorContext] pending_buffer: Optional[Buffer] pending_on_partial_copy: Optional[OnPartialCopy] pending_on_copy_done: Optional[OnCopyDone] @@ -1109,7 +1107,6 @@ class ReadableStreamGuestImpl(ReadableStream): self.t = t self.impl = inst self.closed_ = False - self.maybe_errctx = None self.reset_pending() def reset_pending(self): @@ -1135,19 +1132,14 @@ been returned: def cancel(self): self.reset_and_notify_pending() - def close(self, maybe_errctx): + def close(self): if not self.closed_: self.closed_ = True - self.maybe_errctx = maybe_errctx if self.pending_buffer: self.reset_and_notify_pending() def closed(self): return self.closed_ - - def closed_with(self): - assert(self.closed_) - return self.maybe_errctx ``` While the abstract `ReadableStream` interface *allows* `cancel` to return without having returned ownership of the buffer (which, in general, is @@ -1212,9 +1204,9 @@ class StreamEnd(Waitable): self.stream = stream self.copying = False - def drop(self, maybe_errctx): + def drop(self): trap_if(self.copying) - self.stream.close(maybe_errctx) + self.stream.close() Waitable.drop(self) class ReadableStreamEnd(StreamEnd): @@ -1251,11 +1243,11 @@ class FutureEnd(StreamEnd): assert(buffer.remain() == 1) def on_copy_done_wrapper(): if buffer.remain() == 0: - self.stream.close(maybe_errctx = None) + self.stream.close() on_copy_done() ret = copy_op(buffer, on_partial_copy = None, on_copy_done = on_copy_done_wrapper) if ret == 'done' and buffer.remain() == 0: - self.stream.close(maybe_errctx = None) + self.stream.close() return ret class ReadableFutureEnd(FutureEnd): @@ -1266,9 +1258,8 @@ class WritableFutureEnd(FutureEnd): paired: bool = False def copy(self, src, on_partial_copy, on_copy_done): return self.close_after_copy(self.stream.write, src, on_copy_done) - def drop(self, maybe_errctx): - trap_if(not self.stream.closed() and not maybe_errctx) - FutureEnd.drop(self, maybe_errctx) + def drop(self): + FutureEnd.drop(self) ``` The `future.{read,write}` built-ins fix the buffer length to `1`, ensuring the `assert(buffer.remain() == 1)` holds. Because of this, there are no partial @@ -3607,14 +3598,7 @@ def pack_copy_result(task, buffer, e): assert(not (buffer.progress & CLOSED)) return buffer.progress else: - if (maybe_errctx := e.stream.closed_with()): - errctxi = task.inst.error_contexts.add(maybe_errctx) - assert(errctxi != 0) - else: - errctxi = 0 - assert(errctxi <= Table.MAX_LENGTH < BLOCKED) - assert(not (errctxi & CLOSED)) - return errctxi | CLOSED + return CLOSED ``` The order of tests here indicates that, if some progress was made and then the 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, performing the guards and bookkeeping defined by `{Readable,Writable}{Stream,Future}End.drop()` above. ```python -async def canon_stream_close_readable(t, task, i, errctxi): - return await close(ReadableStreamEnd, t, task, i, errctxi) +async def canon_stream_close_readable(t, task, i): + return await close(ReadableStreamEnd, t, task, i) -async def canon_stream_close_writable(t, task, hi, errctxi): - return await close(WritableStreamEnd, t, task, hi, errctxi) +async def canon_stream_close_writable(t, task, hi): + return await close(WritableStreamEnd, t, task, hi) -async def canon_future_close_readable(t, task, i, errctxi): - return await close(ReadableFutureEnd, t, task, i, errctxi) +async def canon_future_close_readable(t, task, i): + return await close(ReadableFutureEnd, t, task, i) -async def canon_future_close_writable(t, task, hi, errctxi): - return await close(WritableFutureEnd, t, task, hi, errctxi) +async def canon_future_close_writable(t, task, hi): + return await close(WritableFutureEnd, t, task, hi) -async def close(EndT, t, task, hi, errctxi): +async def close(EndT, t, task, hi): trap_if(not task.inst.may_leave) e = task.inst.waitables.remove(hi) - if errctxi == 0: - maybe_errctx = None - else: - maybe_errctx = task.inst.error_contexts.get(errctxi) trap_if(not isinstance(e, EndT)) trap_if(e.stream.t != t) - e.drop(maybe_errctx) + e.drop() return [] ``` -Passing a non-zero `errctxi` index indicates that this stream end is being -closed due to an error, with the given `error-context` providing information -that can be printed to aid in debugging. While, as explained above, the -*contents* of the `error-context` value are non-deterministic (and may, e.g., -be empty), the presence or absence of an `error-context` value is semantically -meaningful for distinguishing between success or failure. Concretely, the -packed `i32` returned by `{stream,future}.{read,write}` operations indicates -success or failure by whether the `error-context` index is `0` or not. -### ๐Ÿ”€ `canon error-context.new` +### ๐Ÿ“ `canon error-context.new` For a canonical definition: ```wat @@ -3780,7 +3752,7 @@ are not checked. (Note that `host_defined_transformation` is not defined by the Canonical ABI and stands for an arbitrary host-defined function.) -### ๐Ÿ”€ `canon error-context.debug-message` +### ๐Ÿ“ `canon error-context.debug-message` For a canonical definition: ```wat @@ -3808,7 +3780,7 @@ async def canon_error_context_debug_message(opts, task, i, ptr): Note that `ptr` points to an 8-byte region of memory into which will be stored the pointer and length of the debug string (allocated via `opts.realloc`). -### ๐Ÿ”€ `canon error-context.drop` +### ๐Ÿ“ `canon error-context.drop` For a canonical definition: ```wat diff --git a/design/mvp/Explainer.md b/design/mvp/Explainer.md index f71f7401..8ecd038e 100644 --- a/design/mvp/Explainer.md +++ b/design/mvp/Explainer.md @@ -54,6 +54,7 @@ implemented, considered stable and included in a future milestone: * ๐ŸšŸ: using `async` with `canon lift` without `callback` (stackful lift) * ๐Ÿงต: threading built-ins * ๐Ÿ”ง: fixed-length lists +* ๐Ÿ“: the `error-context` type (Based on the previous [scoping and layering] proposal to the WebAssembly CG, this repo merges and supersedes the [module-linking] and [interface-types] @@ -546,7 +547,7 @@ defvaltype ::= bool | s8 | u8 | s16 | u16 | s32 | u32 | s64 | u64 | f32 | f64 | char | string - | error-context ๐Ÿ”€ + | error-context ๐Ÿ“ | (record (field "