Skip to content
Open
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
49 changes: 27 additions & 22 deletions design/mvp/CanonicalABI.md
Original file line number Diff line number Diff line change
Expand Up @@ -1551,7 +1551,7 @@ class GuestBuffer(Buffer):

def __init__(self, t, cx, ptr, length):
trap_if(length > Buffer.MAX_LENGTH)
if t and length > 0:
if t is not None:
trap_if(ptr != align_to(ptr, alignment(t, cx.opts.memory.ptr_type())))
trap_if(ptr + length * elem_size(t, cx.opts.memory.ptr_type()) > len(cx.opts.memory))
self.cx = cx
Expand Down Expand Up @@ -1584,11 +1584,13 @@ class WritableGuestBuffer(GuestBuffer, WritableBuffer):
assert(all(v == () for v in vs))
self.progress += len(vs)
```
When `t` is `None` (arising from `stream` and `future` with empty element
types), the core-wasm-supplied `ptr` is entirely ignored, while the `length`
and `progress` are still semantically meaningful. Source bindings may represent
this case with a generic stream/future of [unit] type or a distinct type that
conveys events without values.
As with `load_list_from_range` below, the alignment and range checks are
performed even when `length` is `0`. When `t` is `None` (arising from `stream`
and `future` with empty element types), the core-wasm-supplied `ptr` is
entirely ignored, while the `length` and `progress` are still semantically
meaningful. Source bindings may represent this case with a generic
stream/future of [unit] type or a distinct type that conveys events without
values.

The `load_list_from_valid_range` and `store_list_into_valid_range` functions
that do all the heavy lifting are shared with function parameter/result lifting
Expand Down Expand Up @@ -1691,30 +1693,33 @@ the code below:
assert(self.has_pending_event())
elif self.other.buffer is None:
self.buffer = buffer
elif buffer.remain() > 0 and self.other.buffer.remain() > 0:
else:
trap_if(self.owner and self.owner is self.other.owner and not none_or_number_type(self.t))
n = min(buffer.remain(), self.other.buffer.remain())
if is_read:
buffer.write(self.other.buffer.read(n))
else:
self.other.buffer.write(buffer.read(n))
self.notify(buffer.progress)
self.other.notify(self.other.buffer.progress)
if self.other.buffer.remain() == 0:
if buffer.remain() > 0 and self.other.buffer.remain() > 0:
n = min(buffer.remain(), self.other.buffer.remain())
if is_read:
buffer.write(self.other.buffer.read(n))
else:
self.other.buffer.write(buffer.read(n))
self.notify(buffer.progress)
self.other.notify(self.other.buffer.progress)
if self.other.buffer.remain() == 0:
self.other.buffer = None
elif buffer.remain() > 0 or (is_read and self.other.buffer.remain() == 0):
self.other.notify(progress = 0)
self.other.buffer = None
elif buffer.remain() > 0 or (is_read and self.other.buffer.remain() == 0):
self.other.notify(progress = 0)
self.other.buffer = None
self.buffer = buffer
else:
self.notify(progress = 0)
self.buffer = buffer
else:
self.notify(progress = 0)
```
As a temporary measure (until [lazy lowering] obviates the problem), there is
also a trap when both the `read` and `write` come from the same component
instance and there is a non-empty, non-number element type. The reason for this
trap is that when lifting and lowering can alias the same memory, the eager
interleaving semantics of copying compound values would otherwise be complex to
precisely specify and implement.
precisely specify and implement. The trap is checked whenever both ends have
provided a buffer (cases 3, 4 and 5), even if no elements end up being copied,
so that whether a trap occurs doesn't depend on the buffer lengths.

The `End.cancel` method is called by `{stream,future}.cancel-{read,write}` to
transition an end from `COPYING` (set by `End.copy` above) to `CANCELLING_COPY`.
Expand Down
3 changes: 2 additions & 1 deletion design/mvp/Explainer.md
Original file line number Diff line number Diff line change
Expand Up @@ -2006,7 +2006,8 @@ enum copy-result {
The `stream.read` and `stream.write` built-ins take the matching [readable or
writable end] of a stream as the first parameter and, if `T` is present, a
buffer for the `T` values to be read from or written to. If `T` is not present,
the buffer parameter is ignored.
the buffer parameter is ignored. Otherwise, the buffer's alignment and bounds
are checked even when its length is `0`.

If the return value is a `stream-result`, then the `progress` field indicates how
many `T` elements were read or written from the given buffer before the
Expand Down
33 changes: 17 additions & 16 deletions design/mvp/canonical-abi/definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -895,7 +895,7 @@ class GuestBuffer(Buffer):

def __init__(self, t, cx, ptr, length):
trap_if(length > Buffer.MAX_LENGTH)
if t and length > 0:
if t is not None:
trap_if(ptr != align_to(ptr, alignment(t, cx.opts.memory.ptr_type())))
trap_if(ptr + length * elem_size(t, cx.opts.memory.ptr_type()) > len(cx.opts.memory))
self.cx = cx
Expand Down Expand Up @@ -962,23 +962,24 @@ def copy(self, buffer: Buffer, is_read: bool):
assert(self.has_pending_event())
elif self.other.buffer is None:
self.buffer = buffer
elif buffer.remain() > 0 and self.other.buffer.remain() > 0:
else:
trap_if(self.owner and self.owner is self.other.owner and not none_or_number_type(self.t))
n = min(buffer.remain(), self.other.buffer.remain())
if is_read:
buffer.write(self.other.buffer.read(n))
else:
self.other.buffer.write(buffer.read(n))
self.notify(buffer.progress)
self.other.notify(self.other.buffer.progress)
if self.other.buffer.remain() == 0:
if buffer.remain() > 0 and self.other.buffer.remain() > 0:
n = min(buffer.remain(), self.other.buffer.remain())
if is_read:
buffer.write(self.other.buffer.read(n))
else:
self.other.buffer.write(buffer.read(n))
self.notify(buffer.progress)
self.other.notify(self.other.buffer.progress)
if self.other.buffer.remain() == 0:
self.other.buffer = None
elif buffer.remain() > 0 or (is_read and self.other.buffer.remain() == 0):
self.other.notify(progress = 0)
self.other.buffer = None
elif buffer.remain() > 0 or (is_read and self.other.buffer.remain() == 0):
self.other.notify(progress = 0)
self.other.buffer = None
self.buffer = buffer
else:
self.notify(progress = 0)
self.buffer = buffer
else:
self.notify(progress = 0)

def cancel(self):
assert(self.state == End.State.COPYING)
Expand Down
8 changes: 4 additions & 4 deletions design/mvp/canonical-abi/run_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -1978,7 +1978,7 @@ def core_func1(args):
result,n = unpack_result(mem1[retp+4])
assert(n == 4 and result == CopyResult.COMPLETED)

[ret] = canon_stream_write(StreamType(U8Type()), opts1, wsi, 12345, 0)
[ret] = canon_stream_write(StreamType(U8Type()), opts1, wsi, 0, 0)
assert(ret == definitions.BLOCKED)

fut4.set()
Expand All @@ -1988,7 +1988,7 @@ def core_func1(args):
assert(mem1[retp+0] == wsi)
assert(mem1[retp+4] == 0)

[ret] = canon_stream_write(StreamType(U8Type()), opts1, wsi, 12345, 0)
[ret] = canon_stream_write(StreamType(U8Type()), opts1, wsi, 0, 0)
assert(ret == 0)

[errctxi] = canon_error_context_new(opts1, 0, 0)
Expand Down Expand Up @@ -2032,7 +2032,7 @@ def core_func2(args):
fut2.set()
thread.wait_until(fut3.is_set)

[ret] = canon_stream_read(StreamType(U8Type()), opts2, rsi, 12345, 0)
[ret] = canon_stream_read(StreamType(U8Type()), opts2, rsi, 0, 0)
assert(ret == 0)

mem2[0:8] = bytes(8)
Expand All @@ -2047,7 +2047,7 @@ def core_func2(args):

thread.wait_until(fut4.is_set)

[ret] = canon_stream_read(StreamType(U8Type()), opts2, rsi, 12345, 0)
[ret] = canon_stream_read(StreamType(U8Type()), opts2, rsi, 0, 0)
assert(ret == definitions.BLOCKED)

[event] = canon_waitable_set_wait(MemInst(mem2, 'i32'), seti, retp)
Expand Down
Loading
Loading