@@ -167,7 +167,6 @@ class CanonicalOptions(LiftLowerOptions):
167167 post_return: Optional[Callable] = None
168168 sync: bool = True # = !canonopt.async
169169 callback: Optional[Callable] = None
170- always_task_return: bool = False
171170```
172171(Note that the ` async ` ` canonopt ` is inverted to ` sync ` here for the practical
173172reason that ` async ` is a keyword and most branches below want to start with the
@@ -3093,27 +3092,30 @@ Each call to `canon lift` creates a new `Task` and waits to enter the component
30933092instance, allowing the component instance to express backpressure before
30943093lowering the arguments into the callee's memory.
30953094
3096- In the synchronous case, if ` always-task-return ` ABI option is set, the lifted
3097- core wasm code must call ` canon_task_return ` to return a value before returning
3098- to ` canon_lift ` (or else there will be a trap in ` Task.exit ` ), which allows the
3099- core wasm to do cleanup and finalization before returning. Otherwise, if
3100- ` always-task-return ` is * not* set, ` canon_lift ` will implicitly call
3101- ` canon_task_return ` when core wasm returns and then make a second call into the
3102- ` post-return ` function to let core wasm do cleanup and finalization. In the
3103- future, ` post-return ` and the option to not set ` always-task-return ` may be
3104- deprecated and removed.
3095+ In the synchronous case, ` canon_lift ` first calls into the lifted core
3096+ function, passing the lowered core flat parameters and receiving the core flat
3097+ results to be lifted. Once the core results are lifted, ` canon_lift ` optionally
3098+ makes a second call into any supplied ` post-return ` function, passing the flat
3099+ results as arguments so that the guest code and free any allocations associated
3100+ with compound return values.
31053101``` python
31063102 if opts.sync:
31073103 flat_results = await call_and_trap_on_throw(callee, task, flat_args)
3108- if not opts.always_task_return:
3109- assert (types_match_values(flat_ft.results, flat_results))
3110- results = lift_flat_values(cx, MAX_FLAT_RESULTS , CoreValueIter(flat_results), ft.result_types())
3111- task.return_(results)
3112- if opts.post_return is not None :
3113- [] = await call_and_trap_on_throw(opts.post_return, task, flat_results)
3104+ assert (types_match_values(flat_ft.results, flat_results))
3105+ results = lift_flat_values(cx, MAX_FLAT_RESULTS , CoreValueIter(flat_results), ft.result_types())
3106+ task.return_(results)
3107+ if opts.post_return is not None :
3108+ task.inst.may_leave = False
3109+ [] = await call_and_trap_on_throw(opts.post_return, task, flat_results)
3110+ task.inst.may_leave = True
31143111 task.exit()
31153112 return
31163113```
3114+ By clearing ` may_leave ` for the duration of the ` post-return ` call, the
3115+ Canonical ABI ensures that synchronously-lowered calls to synchronously-lifted
3116+ functions can always be implemented by a plain synchronous function call
3117+ without the need for fibers which would otherwise be necessary if the
3118+ ` post-return ` function performed a blocking operation.
31173119
31183120In both of the asynchronous cases below (` callback ` and non-` callback ` ),
31193121` canon_task_return ` must be called (as checked by ` Task.exit ` ).
@@ -3476,14 +3478,18 @@ wasm state and passes them to the caller via `Task.return_`:
34763478``` python
34773479async def canon_task_return (task , result_type , opts : LiftOptions, flat_args ):
34783480 trap_if(not task.inst.may_leave)
3479- trap_if(task.opts.sync and not task.opts.always_task_return )
3481+ trap_if(task.opts.sync)
34803482 trap_if(result_type != task.ft.results)
34813483 trap_if(not LiftOptions.equal(opts, task.opts))
34823484 cx = LiftLowerContext(opts, task.inst, task)
34833485 results = lift_flat_values(cx, MAX_FLAT_PARAMS , CoreValueIter(flat_args), task.ft.result_types())
34843486 task.return_(results)
34853487 return []
34863488```
3489+ The ` trap_if(task.opts.sync) ` prevents ` task.return ` from being called by
3490+ synchronously-lifted functions (which return their value by returning from the
3491+ lifted core function).
3492+
34873493The ` trap_if(result_type != task.ft.results) ` guard ensures that, in a
34883494component with multiple exported functions of different types, ` task.return ` is
34893495not called with a mismatched result type (which, due to indirect control flow,
@@ -3518,10 +3524,14 @@ current task have already been dropped (and trapping in `Task.cancel` if not).
35183524``` python
35193525async def canon_task_cancel (task ):
35203526 trap_if(not task.inst.may_leave)
3521- trap_if(task.opts.sync and not task.opts.always_task_return )
3527+ trap_if(task.opts.sync)
35223528 task.cancel()
35233529 return []
35243530```
3531+ The ` trap_if(task.opts.sync) ` prevents ` task.cancel ` from being called by
3532+ synchronously-lifted functions (which must always return a value by returning
3533+ from the lifted core function).
3534+
35253535` Task.cancel ` also traps if there has been no cancellation request (in which
35263536case the callee expects to receive a return value) or if the task has already
35273537returned a value or already called ` task.cancel ` .
@@ -3541,7 +3551,6 @@ Calling `$f` calls `Task.yield_` to allow other tasks to execute:
35413551``` python
35423552async def canon_yield (sync , task ):
35433553 trap_if(not task.inst.may_leave)
3544- trap_if(task.opts.callback and not sync)
35453554 event_code,_,_ = await task.yield_(sync)
35463555 match event_code:
35473556 case EventCode.NONE :
@@ -3559,11 +3568,6 @@ Because other tasks can execute, a subtask can be cancelled while executing
35593568generators should handle cancellation the same way as when receiving the
35603569` TASK_CANCELLED ` event from ` waitable-set.wait ` .
35613570
3562- The guard preventing ` async ` use of ` task.poll ` when a ` callback ` has
3563- been used preserves the invariant that producer toolchains using
3564- ` callback ` never need to handle multiple overlapping callback
3565- activations.
3566-
35673571
35683572### 🔀 ` canon waitable-set.new `
35693573
@@ -3599,7 +3603,6 @@ returning its `EventCode` and writing the payload values into linear memory:
35993603``` python
36003604async def canon_waitable_set_wait (sync , mem , task , si , ptr ):
36013605 trap_if(not task.inst.may_leave)
3602- trap_if(task.opts.callback and not sync)
36033606 s = task.inst.table.get(si)
36043607 trap_if(not isinstance (s, WaitableSet))
36053608 e = await task.wait_for_event(s, sync)
@@ -3623,10 +3626,6 @@ though, the automatic backpressure (applied by `Task.enter`) will ensure there
36233626is only ever at most once synchronously-lifted task executing in a component
36243627instance at a time.
36253628
3626- The guard preventing ` async ` use of ` wait ` when a ` callback ` has been used
3627- preserves the invariant that producer toolchains using ` callback ` never need to
3628- handle multiple overlapping callback activations.
3629-
36303629
36313630### 🔀 ` canon waitable-set.poll `
36323631
@@ -3644,7 +3643,6 @@ same way as `wait`.
36443643``` python
36453644async def canon_waitable_set_poll (sync , mem , task , si , ptr ):
36463645 trap_if(not task.inst.may_leave)
3647- trap_if(task.opts.callback and not sync)
36483646 s = task.inst.table.get(si)
36493647 trap_if(not isinstance (s, WaitableSet))
36503648 e = await task.poll_for_event(s, sync)
@@ -3653,10 +3651,6 @@ async def canon_waitable_set_poll(sync, mem, task, si, ptr):
36533651When ` async ` is set, ` poll_for_event ` can yield to other tasks (in this or other
36543652components) as part of polling for an event.
36553653
3656- The guard preventing ` async ` use of ` poll_for_event ` when a ` callback ` has been
3657- used preserves the invariant that producer toolchains using ` callback ` never
3658- need to handle multiple overlapping callback activations.
3659-
36603654
36613655### 🔀 ` canon waitable-set.drop `
36623656
0 commit comments