Skip to content

Commit 22e082c

Browse files
committed
Simplify canon_lower presentation (no change)
1 parent 2dee75d commit 22e082c

2 files changed

Lines changed: 74 additions & 53 deletions

File tree

design/mvp/CanonicalABI.md

Lines changed: 53 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1542,13 +1542,15 @@ class Subtask(Waitable):
15421542
on_cancel: Optional[OnCancel]
15431543
lenders: Optional[list[ResourceHandle]]
15441544
cancellation_requested: bool
1545+
flat_results: list[CoreValType]
15451546

15461547
def __init__(self):
15471548
Waitable.__init__(self)
15481549
self.state = Subtask.State.STARTING
15491550
self.on_cancel = None
15501551
self.lenders = []
15511552
self.cancellation_requested = False
1553+
self.flat_results = []
15521554
```
15531555

15541556
The `state` field of `Subtask` tracks the callee's progression from the initial
@@ -1582,6 +1584,13 @@ that were initially incremented.
15821584
lending_handle.num_lends += 1
15831585
self.lenders.append(lending_handle)
15841586

1587+
def resolve(self, state, flat_results):
1588+
assert(state == Subtask.State.RETURNED or flat_results == [])
1589+
assert(not self.resolved())
1590+
self.state = state
1591+
self.flat_results = flat_results
1592+
assert(self.resolved())
1593+
15851594
def deliver_resolve(self):
15861595
assert(not self.resolve_delivered() and self.resolved())
15871596
for h in self.lenders:
@@ -3804,23 +3813,19 @@ along with the component instance being instantiated. These are then passed into
38043813
`canon_lower` every time the generated `CoreFuncInst` is called, along with the
38053814
runtime Core WebAssembly arguments.
38063815

3807-
Based on this, `canon_lower` is defined in chunks as follows. First, each call
3808-
to `canon_lower` creates a new `Subtask`. However, this `Subtask` is only added
3809-
to the current component instance's `handles` table (below) if `async` is
3810-
specified *and* `callee` blocks. In any case, this `Subtask` is used as the
3811-
`LiftLowerContext.borrow_scope` for `borrow` arguments, ensuring that owned
3812-
handles are not dropped before `Subtask.deliver_resolve` is called (below).
3816+
Based on this, `canon_lower` is defined in chunks as follows. First, like most
3817+
Canonical ABI functions callable from Core WebAssembly, lowered imports may not
3818+
be called during `post-return` or `realloc`:
38133819
```python
38143820
def canon_lower(callee, ft, opts, flat_args: list[CoreValType]) -> list[CoreValType]:
38153821
thread = current_thread()
38163822
trap_if(not thread.task.inst.may_leave)
3817-
subtask = Subtask()
3818-
cx = LiftLowerContext(opts, thread.task.inst, subtask)
38193823
```
38203824

3821-
The next chunk makes the call to `callee` using the `opts` immediates of the
3822-
`canon lower` definition to configure `lift_flat_values` and `lower_flat_values`
3823-
(both defined above) and the current instance as the `caller`.
3825+
The component-level function type maps to a Core WebAssembly function type using
3826+
different flattening constants based on whether the sync or async ABI is used.
3827+
The values of `max_flat_{params,results}` mirror the flattening logic in
3828+
`flatten_functype` above:
38243829
```python
38253830
flat_ft = flatten_functype(opts, ft, 'lower')
38263831
assert(types_match_values(flat_ft.params, flat_args))
@@ -3832,44 +3837,51 @@ The next chunk makes the call to `callee` using the `opts` immediates of the
38323837
else:
38333838
max_flat_params = MAX_FLAT_ASYNC_PARAMS
38343839
max_flat_results = 0
3840+
```
38353841

3836-
on_progress = lambda:()
3837-
flat_results = None
3842+
Next, the call is officially started by calling `callee` with the required
3843+
`OnStart` and `OnResolve` callbacks (described above) that lift arguments and
3844+
lower results if not cancelled. The `maybe_on_progress` callback starts as a
3845+
no-op but is switched to `on_progress` (defined below) in the `async` case if
3846+
`callee` [blocks], so that progress updates are asynchronously delivered to the
3847+
caller.
3848+
```python
3849+
subtask = Subtask()
3850+
cx = LiftLowerContext(opts, thread.task.inst, subtask)
3851+
maybe_on_progress = lambda:()
38383852

38393853
def on_start():
3840-
on_progress()
3854+
maybe_on_progress()
38413855
assert(subtask.state == Subtask.State.STARTING)
38423856
subtask.state = Subtask.State.STARTED
38433857
return lift_flat_values(cx, max_flat_params, flat_args, ft.param_types())
38443858

38453859
def on_resolve(result):
3846-
on_progress()
3860+
maybe_on_progress()
38473861
if result is None:
38483862
assert(subtask.cancellation_requested)
38493863
if subtask.state == Subtask.State.STARTING:
3850-
subtask.state = Subtask.State.CANCELLED_BEFORE_STARTED
3864+
subtask.resolve(Subtask.State.CANCELLED_BEFORE_STARTED, [])
38513865
else:
38523866
assert(subtask.state == Subtask.State.STARTED)
3853-
subtask.state = Subtask.State.CANCELLED_BEFORE_RETURNED
3867+
subtask.resolve(Subtask.State.CANCELLED_BEFORE_RETURNED, [])
38543868
else:
38553869
assert(subtask.state == Subtask.State.STARTED)
3856-
subtask.state = Subtask.State.RETURNED
3857-
nonlocal flat_results
38583870
flat_results = lower_flat_values(cx, max_flat_results, result, ft.result_type(), flat_args)
3871+
subtask.resolve(Subtask.State.RETURNED, flat_results)
38593872

38603873
subtask.on_cancel = callee(on_start, on_resolve, caller = thread.task.inst)
38613874
assert(ft.async_ or subtask.state == Subtask.State.RETURNED)
38623875
```
3863-
The `Subtask.state` field is updated by the callbacks to keep track of the
3864-
call progress. The `on_progress` variable starts as a no-op, but is used by the
3865-
`async` case below to trigger event delivery.
3876+
According to the `FuncInst` calling contract, if `callee` [blocks], it must
3877+
immediately return an `OnCancel` callback which the code above stores in the
3878+
`Subtask` to enable subsequent requests for cancellation. As asserted above, if
3879+
the `callee`'s function type does not declare the `async` effect, `callee` must
3880+
not block before returning a value.
38663881

3867-
According to the `FuncInst` calling contract, the call to `callee` should never
3868-
"block" (i.e., wait on I/O). If the `callee` *would* block, it will instead
3869-
return an `OnCancel` callback which is stored in the `Subtask` (so that it can
3870-
be used to request cancellation in the future). Furthermore, if the function
3871-
type does not have the `async` effect, the function *must* have returned a
3872-
value.
3882+
Note that, for component-to-component calls, the `caller` of the `FuncInst` is
3883+
the current component instance. This information is used by `may_enter_from` to
3884+
determine when to trap because `callee` is being synchronously reentered.
38733885

38743886
In the synchronous case (when the `async` `canonopt` is not set), if the
38753887
`callee` blocked before calling `on_resolve`, the synchronous caller's thread
@@ -3885,9 +3897,9 @@ use a plain synchronous function call instead, as expected.
38853897
if not opts.async_:
38863898
if not subtask.resolved():
38873899
thread.wait_until(subtask.resolved)
3888-
assert(types_match_values(flat_ft.results, flat_results))
38893900
subtask.deliver_resolve()
3890-
return flat_results
3901+
assert(types_match_values(flat_ft.results, subtask.flat_results))
3902+
return subtask.flat_results
38913903
```
38923904
The call to `Subtask.deliver_resolve` decrements the counters on handles that
38933905
were lent for `borrow`ed parameters during the call. These counters are
@@ -3906,8 +3918,8 @@ reserved.
39063918
```python
39073919
else:
39083920
if subtask.resolved():
3909-
assert(flat_results == [])
39103921
subtask.deliver_resolve()
3922+
assert(subtask.flat_results == [])
39113923
return [Subtask.State.RETURNED]
39123924
else:
39133925
subtaski = thread.task.inst.handles.add(subtask)
@@ -3917,20 +3929,21 @@ reserved.
39173929
subtask.deliver_resolve()
39183930
return (EventCode.SUBTASK, subtaski, subtask.state)
39193931
subtask.set_pending_event(subtask_event)
3932+
maybe_on_progress = on_progress
39203933
assert(0 < subtaski <= Table.MAX_LENGTH < 2**28)
39213934
assert(0 <= subtask.state < 2**4)
39223935
return [subtask.state | (subtaski << 4)]
39233936
```
39243937
When `on_start` and `on_resolve` are called after this initial `async`-lowered
3925-
call returns, the `on_progress` callback (called by `on_start` and `on_resolve`)
3926-
will set a pending event on the `Subtask` (which derives `Waitable`) so that it
3927-
can be waited on via `waitable-set.{wait,poll}` or, if a `callback` is used, by
3928-
returning to the event loop. If `on_start` is called followed by `on_resolve`
3929-
before core wasm receives the first event, core wasm will only receive the
3930-
second event, not two events. Note `Subtask.drop` prevents (via trap) a
3931-
`Subtask` from being dropped before `on_resolve` is called and the event is
3932-
delivered to core wasm to ensure that `Subtask.deliver_resolve` always performs
3933-
its lend-count accounting.
3938+
call returns, the `on_progress` callback (called by `on_start` and `on_resolve`
3939+
above) will set a pending event on the `Subtask` (which derives `Waitable`) so
3940+
that it can be waited on via `waitable-set.{wait,poll}` or, if a `callback` is
3941+
used, by returning to the event loop. If `on_start` is called followed by
3942+
`on_resolve` before core wasm receives the first event, core wasm will only
3943+
receive the second event, not two events. Note `Subtask.drop` prevents (via
3944+
trap) a `Subtask` from being dropped before `on_resolve` is called and the event
3945+
is delivered to core wasm to ensure that `Subtask.deliver_resolve` always
3946+
performs its lend-count accounting.
39343947

39353948

39363949
### `canon resource.new`

design/mvp/canonical-abi/definitions.py

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -850,13 +850,15 @@ class State(IntEnum):
850850
on_cancel: Optional[OnCancel]
851851
lenders: Optional[list[ResourceHandle]]
852852
cancellation_requested: bool
853+
flat_results: list[CoreValType]
853854

854855
def __init__(self):
855856
Waitable.__init__(self)
856857
self.state = Subtask.State.STARTING
857858
self.on_cancel = None
858859
self.lenders = []
859860
self.cancellation_requested = False
861+
self.flat_results = []
860862

861863
def resolved(self):
862864
match self.state:
@@ -873,6 +875,13 @@ def add_lender(self, lending_handle):
873875
lending_handle.num_lends += 1
874876
self.lenders.append(lending_handle)
875877

878+
def resolve(self, state, flat_results):
879+
assert(state == Subtask.State.RETURNED or flat_results == [])
880+
assert(not self.resolved())
881+
self.state = state
882+
self.flat_results = flat_results
883+
assert(self.resolved())
884+
876885
def deliver_resolve(self):
877886
assert(not self.resolve_delivered() and self.resolved())
878887
for h in self.lenders:
@@ -2216,8 +2225,6 @@ def call_and_trap_on_throw(callee, args):
22162225
def canon_lower(callee, ft, opts, flat_args: list[CoreValType]) -> list[CoreValType]:
22172226
thread = current_thread()
22182227
trap_if(not thread.task.inst.may_leave)
2219-
subtask = Subtask()
2220-
cx = LiftLowerContext(opts, thread.task.inst, subtask)
22212228

22222229
flat_ft = flatten_functype(opts, ft, 'lower')
22232230
assert(types_match_values(flat_ft.params, flat_args))
@@ -2230,43 +2237,43 @@ def canon_lower(callee, ft, opts, flat_args: list[CoreValType]) -> list[CoreValT
22302237
max_flat_params = MAX_FLAT_ASYNC_PARAMS
22312238
max_flat_results = 0
22322239

2233-
on_progress = lambda:()
2234-
flat_results = None
2240+
subtask = Subtask()
2241+
cx = LiftLowerContext(opts, thread.task.inst, subtask)
2242+
maybe_on_progress = lambda:()
22352243

22362244
def on_start():
2237-
on_progress()
2245+
maybe_on_progress()
22382246
assert(subtask.state == Subtask.State.STARTING)
22392247
subtask.state = Subtask.State.STARTED
22402248
return lift_flat_values(cx, max_flat_params, flat_args, ft.param_types())
22412249

22422250
def on_resolve(result):
2243-
on_progress()
2251+
maybe_on_progress()
22442252
if result is None:
22452253
assert(subtask.cancellation_requested)
22462254
if subtask.state == Subtask.State.STARTING:
2247-
subtask.state = Subtask.State.CANCELLED_BEFORE_STARTED
2255+
subtask.resolve(Subtask.State.CANCELLED_BEFORE_STARTED, [])
22482256
else:
22492257
assert(subtask.state == Subtask.State.STARTED)
2250-
subtask.state = Subtask.State.CANCELLED_BEFORE_RETURNED
2258+
subtask.resolve(Subtask.State.CANCELLED_BEFORE_RETURNED, [])
22512259
else:
22522260
assert(subtask.state == Subtask.State.STARTED)
2253-
subtask.state = Subtask.State.RETURNED
2254-
nonlocal flat_results
22552261
flat_results = lower_flat_values(cx, max_flat_results, result, ft.result_type(), flat_args)
2262+
subtask.resolve(Subtask.State.RETURNED, flat_results)
22562263

22572264
subtask.on_cancel = callee(on_start, on_resolve, caller = thread.task.inst)
22582265
assert(ft.async_ or subtask.state == Subtask.State.RETURNED)
22592266

22602267
if not opts.async_:
22612268
if not subtask.resolved():
22622269
thread.wait_until(subtask.resolved)
2263-
assert(types_match_values(flat_ft.results, flat_results))
22642270
subtask.deliver_resolve()
2265-
return flat_results
2271+
assert(types_match_values(flat_ft.results, subtask.flat_results))
2272+
return subtask.flat_results
22662273
else:
22672274
if subtask.resolved():
2268-
assert(flat_results == [])
22692275
subtask.deliver_resolve()
2276+
assert(subtask.flat_results == [])
22702277
return [Subtask.State.RETURNED]
22712278
else:
22722279
subtaski = thread.task.inst.handles.add(subtask)
@@ -2276,6 +2283,7 @@ def subtask_event():
22762283
subtask.deliver_resolve()
22772284
return (EventCode.SUBTASK, subtaski, subtask.state)
22782285
subtask.set_pending_event(subtask_event)
2286+
maybe_on_progress = on_progress
22792287
assert(0 < subtaski <= Table.MAX_LENGTH < 2**28)
22802288
assert(0 <= subtask.state < 2**4)
22812289
return [subtask.state | (subtaski << 4)]

0 commit comments

Comments
 (0)