@@ -166,7 +166,6 @@ class CanonicalOptions(LiftLowerOptions):
166166 post_return: Optional[Callable] = None
167167 sync: bool = True # = !canonopt.async
168168 callback: Optional[Callable] = None
169- always_task_return: bool = False
170169```
171170(Note that the ` async ` ` canonopt ` is inverted to ` sync ` here for the practical
172171reason 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
30033002instance, allowing the component instance to express backpressure before
30043003lowering the arguments into the callee's memory.
30053004
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.
30153011``` python
30163012 if opts.sync:
30173013 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
30243021 task.exit()
30253022 return
30263023```
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.
30273029
30283030In both of the asynchronous cases below (` callback ` and non-` callback ` ),
30293031` 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_`:
33863388``` python
33873389async def canon_task_return (task , result_type , opts : LiftOptions, flat_args ):
33883390 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)
33903392 trap_if(result_type != task.ft.results)
33913393 trap_if(not LiftOptions.equal(opts, task.opts))
33923394 cx = LiftLowerContext(opts, task.inst, task)
33933395 results = lift_flat_values(cx, MAX_FLAT_PARAMS , CoreValueIter(flat_args), task.ft.result_types())
33943396 task.return_(results)
33953397 return []
33963398```
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+
33973403The ` trap_if(result_type != task.ft.results) ` guard ensures that, in a
33983404component with multiple exported functions of different types, ` task.return ` is
33993405not 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).
34283434``` python
34293435async def canon_task_cancel (task ):
34303436 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)
34323438 task.cancel()
34333439 return []
34343440```
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+
34353445` Task.cancel ` also traps if there has been no cancellation request (in which
34363446case the callee expects to receive a return value) or if the task has already
34373447returned a value or already called ` task.cancel ` .
@@ -3451,7 +3461,6 @@ Calling `$f` calls `Task.yield_` to allow other tasks to execute:
34513461``` python
34523462async def canon_yield (sync , task ):
34533463 trap_if(not task.inst.may_leave)
3454- trap_if(task.opts.callback and not sync)
34553464 event_code,_,_ = await task.yield_(sync)
34563465 match event_code:
34573466 case EventCode.NONE :
@@ -3469,11 +3478,6 @@ Because other tasks can execute, a subtask can be cancelled while executing
34693478generators should handle cancellation the same way as when receiving the
34703479` TASK_CANCELLED ` event from ` waitable-set.wait ` .
34713480
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-
34773481
34783482### 🔀 ` canon waitable-set.new `
34793483
@@ -3509,7 +3513,6 @@ returning its `EventCode` and writing the payload values into linear memory:
35093513``` python
35103514async def canon_waitable_set_wait (sync , mem , task , si , ptr ):
35113515 trap_if(not task.inst.may_leave)
3512- trap_if(task.opts.callback and not sync)
35133516 s = task.inst.table.get(si)
35143517 trap_if(not isinstance (s, WaitableSet))
35153518 e = await task.wait_for_event(s, sync)
@@ -3533,10 +3536,6 @@ though, the automatic backpressure (applied by `Task.enter`) will ensure there
35333536is only ever at most once synchronously-lifted task executing in a component
35343537instance at a time.
35353538
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-
35403539
35413540### 🔀 ` canon waitable-set.poll `
35423541
@@ -3554,7 +3553,6 @@ same way as `wait`.
35543553``` python
35553554async def canon_waitable_set_poll (sync , mem , task , si , ptr ):
35563555 trap_if(not task.inst.may_leave)
3557- trap_if(task.opts.callback and not sync)
35583556 s = task.inst.table.get(si)
35593557 trap_if(not isinstance (s, WaitableSet))
35603558 e = await task.poll_for_event(s, sync)
@@ -3563,10 +3561,6 @@ async def canon_waitable_set_poll(sync, mem, task, si, ptr):
35633561When ` async ` is set, ` poll_for_event ` can yield to other tasks (in this or other
35643562components) as part of polling for an event.
35653563
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-
35703564
35713565### 🔀 ` canon waitable-set.drop `
35723566
0 commit comments