@@ -166,7 +166,6 @@ class CanonicalOptions(LiftLowerOptions):
166
166
post_return: Optional[Callable] = None
167
167
sync: bool = True # = !canonopt.async
168
168
callback: Optional[Callable] = None
169
- always_task_return: bool = False
170
169
```
171
170
(Note that the ` async ` ` canonopt ` is inverted to ` sync ` here for the practical
172
171
reason that ` async ` is a keyword and most branches below want to start with the
@@ -3003,27 +3002,30 @@ Each call to `canon lift` creates a new `Task` and waits to enter the component
3003
3002
instance, allowing the component instance to express backpressure before
3004
3003
lowering the arguments into the callee's memory.
3005
3004
3006
- In the synchronous case, if ` always-task-return ` ABI option is set, the lifted
3007
- core wasm code must call ` canon_task_return ` to return a value before returning
3008
- to ` canon_lift ` (or else there will be a trap in ` Task.exit ` ), which allows the
3009
- core wasm to do cleanup and finalization before returning. Otherwise, if
3010
- ` always-task-return ` is * not* set, ` canon_lift ` will implicitly call
3011
- ` canon_task_return ` when core wasm returns and then make a second call into the
3012
- ` post-return ` function to let core wasm do cleanup and finalization. In the
3013
- future, ` post-return ` and the option to not set ` always-task-return ` may be
3014
- deprecated and removed.
3005
+ In the synchronous case, ` canon_lift ` first calls into the lifted core
3006
+ function, passing the lowered core flat parameters and receiving the core flat
3007
+ results to be lifted. Once the core results are lifted, ` canon_lift ` optionally
3008
+ makes a second call into any supplied ` post-return ` function, passing the flat
3009
+ results as arguments so that the guest code and free any allocations associated
3010
+ with compound return values.
3015
3011
``` python
3016
3012
if opts.sync:
3017
3013
flat_results = await call_and_trap_on_throw(callee, task, flat_args)
3018
- if not opts.always_task_return:
3019
- assert (types_match_values(flat_ft.results, flat_results))
3020
- results = lift_flat_values(cx, MAX_FLAT_RESULTS , CoreValueIter(flat_results), ft.result_types())
3021
- task.return_(results)
3022
- if opts.post_return is not None :
3023
- [] = await call_and_trap_on_throw(opts.post_return, task, flat_results)
3014
+ assert (types_match_values(flat_ft.results, flat_results))
3015
+ results = lift_flat_values(cx, MAX_FLAT_RESULTS , CoreValueIter(flat_results), ft.result_types())
3016
+ task.return_(results)
3017
+ if opts.post_return is not None :
3018
+ task.inst.may_leave = False
3019
+ [] = await call_and_trap_on_throw(opts.post_return, task, flat_results)
3020
+ task.inst.may_leave = True
3024
3021
task.exit()
3025
3022
return
3026
3023
```
3024
+ By clearing ` may_leave ` for the duration of the ` post-return ` call, the
3025
+ Canonical ABI ensures that synchronously-lowered calls to synchronously-lifted
3026
+ functions can always be implemented by a plain synchronous function call
3027
+ without the need for fibers which would otherwise be necessary if the
3028
+ ` post-return ` function performed a blocking operation.
3027
3029
3028
3030
In both of the asynchronous cases below (` callback ` and non-` callback ` ),
3029
3031
` canon_task_return ` must be called (as checked by ` Task.exit ` ).
@@ -3386,14 +3388,18 @@ wasm state and passes them to the caller via `Task.return_`:
3386
3388
``` python
3387
3389
async def canon_task_return (task , result_type , opts : LiftOptions, flat_args ):
3388
3390
trap_if(not task.inst.may_leave)
3389
- trap_if(task.opts.sync and not task.opts.always_task_return )
3391
+ trap_if(task.opts.sync)
3390
3392
trap_if(result_type != task.ft.results)
3391
3393
trap_if(not LiftOptions.equal(opts, task.opts))
3392
3394
cx = LiftLowerContext(opts, task.inst, task)
3393
3395
results = lift_flat_values(cx, MAX_FLAT_PARAMS , CoreValueIter(flat_args), task.ft.result_types())
3394
3396
task.return_(results)
3395
3397
return []
3396
3398
```
3399
+ The ` trap_if(task.opts.sync) ` prevents ` task.return ` from being called by
3400
+ synchronously-lifted functions (which return their value by returning from the
3401
+ lifted core function).
3402
+
3397
3403
The ` trap_if(result_type != task.ft.results) ` guard ensures that, in a
3398
3404
component with multiple exported functions of different types, ` task.return ` is
3399
3405
not called with a mismatched result type (which, due to indirect control flow,
@@ -3428,10 +3434,14 @@ current task have already been dropped (and trapping in `Task.cancel` if not).
3428
3434
``` python
3429
3435
async def canon_task_cancel (task ):
3430
3436
trap_if(not task.inst.may_leave)
3431
- trap_if(task.opts.sync and not task.opts.always_task_return )
3437
+ trap_if(task.opts.sync)
3432
3438
task.cancel()
3433
3439
return []
3434
3440
```
3441
+ The ` trap_if(task.opts.sync) ` prevents ` task.cancel ` from being called by
3442
+ synchronously-lifted functions (which must always return a value by returning
3443
+ from the lifted core function).
3444
+
3435
3445
` Task.cancel ` also traps if there has been no cancellation request (in which
3436
3446
case the callee expects to receive a return value) or if the task has already
3437
3447
returned a value or already called ` task.cancel ` .
@@ -3451,7 +3461,6 @@ Calling `$f` calls `Task.yield_` to allow other tasks to execute:
3451
3461
``` python
3452
3462
async def canon_yield (sync , task ):
3453
3463
trap_if(not task.inst.may_leave)
3454
- trap_if(task.opts.callback and not sync)
3455
3464
event_code,_,_ = await task.yield_(sync)
3456
3465
match event_code:
3457
3466
case EventCode.NONE :
@@ -3469,11 +3478,6 @@ Because other tasks can execute, a subtask can be cancelled while executing
3469
3478
generators should handle cancellation the same way as when receiving the
3470
3479
` TASK_CANCELLED ` event from ` waitable-set.wait ` .
3471
3480
3472
- The guard preventing ` async ` use of ` task.poll ` when a ` callback ` has
3473
- been used preserves the invariant that producer toolchains using
3474
- ` callback ` never need to handle multiple overlapping callback
3475
- activations.
3476
-
3477
3481
3478
3482
### 🔀 ` canon waitable-set.new `
3479
3483
@@ -3509,7 +3513,6 @@ returning its `EventCode` and writing the payload values into linear memory:
3509
3513
``` python
3510
3514
async def canon_waitable_set_wait (sync , mem , task , si , ptr ):
3511
3515
trap_if(not task.inst.may_leave)
3512
- trap_if(task.opts.callback and not sync)
3513
3516
s = task.inst.table.get(si)
3514
3517
trap_if(not isinstance (s, WaitableSet))
3515
3518
e = await task.wait_for_event(s, sync)
@@ -3533,10 +3536,6 @@ though, the automatic backpressure (applied by `Task.enter`) will ensure there
3533
3536
is only ever at most once synchronously-lifted task executing in a component
3534
3537
instance at a time.
3535
3538
3536
- The guard preventing ` async ` use of ` wait ` when a ` callback ` has been used
3537
- preserves the invariant that producer toolchains using ` callback ` never need to
3538
- handle multiple overlapping callback activations.
3539
-
3540
3539
3541
3540
### 🔀 ` canon waitable-set.poll `
3542
3541
@@ -3554,7 +3553,6 @@ same way as `wait`.
3554
3553
``` python
3555
3554
async def canon_waitable_set_poll (sync , mem , task , si , ptr ):
3556
3555
trap_if(not task.inst.may_leave)
3557
- trap_if(task.opts.callback and not sync)
3558
3556
s = task.inst.table.get(si)
3559
3557
trap_if(not isinstance (s, WaitableSet))
3560
3558
e = await task.poll_for_event(s, sync)
@@ -3563,10 +3561,6 @@ async def canon_waitable_set_poll(sync, mem, task, si, ptr):
3563
3561
When ` async ` is set, ` poll_for_event ` can yield to other tasks (in this or other
3564
3562
components) as part of polling for an event.
3565
3563
3566
- The guard preventing ` async ` use of ` poll_for_event ` when a ` callback ` has been
3567
- used preserves the invariant that producer toolchains using ` callback ` never
3568
- need to handle multiple overlapping callback activations.
3569
-
3570
3564
3571
3565
### 🔀 ` canon waitable-set.drop `
3572
3566
0 commit comments