Skip to content

Commit ebab3d3

Browse files
committed
Change task.start/return to take a core function type and use flat params/results
1 parent 7338423 commit ebab3d3

File tree

5 files changed

+83
-49
lines changed

5 files changed

+83
-49
lines changed

design/mvp/Binary.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -272,8 +272,8 @@ canon ::= 0x00 0x00 f:<core:funcidx> opts:<opts> ft:<typeidx> => (canon lift
272272
| 0x04 rt:<typeidx> => (canon resource.rep rt (core func))
273273
| 0x05 ft:<typeidx> => (canon thread.spawn ft (core func))
274274
| 0x06 => (canon thread.hw_concurrency (core func))
275-
| 0x08 => (canon task.start (core func))
276-
| 0x09 => (canon task.return (core func))
275+
| 0x08 ft:<core:typeidx> => (canon task.start ft (core func))
276+
| 0x09 ft:<core:typeidx> => (canon task.return ft (core func))
277277
| 0x0a => (canon task.wait (core func))
278278
| 0x0b => (canon task.poll (core func))
279279
| 0x0c => (canon task.yield (core func))

design/mvp/CanonicalABI.md

+33-14
Original file line numberDiff line numberDiff line change
@@ -1866,12 +1866,14 @@ def lower_heap_values(cx, vs, ts, out_param):
18661866
tuple_value = {str(i): v for i,v in enumerate(vs)}
18671867
if out_param is None:
18681868
ptr = cx.opts.realloc(0, 0, alignment(tuple_type), elem_size(tuple_type))
1869+
flat_vals = [ptr]
18691870
else:
18701871
ptr = out_param.next('i32')
1872+
flat_vals = []
18711873
trap_if(ptr != align_to(ptr, alignment(tuple_type)))
18721874
trap_if(ptr + elem_size(tuple_type) > len(cx.opts.memory))
18731875
store(cx, tuple_value, tuple_type, ptr)
1874-
return [ptr]
1876+
return flat_vals
18751877
```
18761878
The `may_leave` flag is guarded by `canon_lower` below to prevent a component
18771879
from calling out of the component while in the middle of lowering, ensuring
@@ -2205,43 +2207,60 @@ component instance defining a resource can access its representation.
22052207

22062208
For a canonical definition:
22072209
```wasm
2208-
(canon task.start (core func $f))
2210+
(canon task.start $ft (core func $f))
22092211
```
22102212
validation specifies:
2211-
* `$f` is given type `(func (param i32))`
2213+
* `$f` is given type `$ft`, which validation requires to be a (core) function type
22122214

22132215
Calling `$f` invokes the following function which extracts the arguments from the
22142216
caller and lowers them into the current instance:
22152217
```python
2216-
async def canon_task_start(task, i):
2218+
async def canon_task_start(task, core_ft, flat_args):
2219+
assert(len(core_ft.params) == len(flat_args))
22172220
trap_if(task.opts.sync)
2221+
trap_if(core_ft != flatten_functype(CanonicalOptions(), FuncType([], task.ft.params), 'lower'))
22182222
task.start()
2219-
lower_async_values(task, task.start_thunk(), task.ft.param_types(), CoreValueIter([i]))
2220-
return []
2223+
args = task.start_thunk()
2224+
flat_results = lower_sync_values(task, MAX_FLAT_RESULTS, args, task.ft.param_types(), CoreValueIter(flat_args))
2225+
assert(len(core_ft.results) == len(flat_results))
2226+
return flat_results
22212227
```
2222-
The call to the `Task.start` (defined above) ensures that `canon task.start` is
2223-
called exactly once, before `canon task.return`, before an async call finishes.
2228+
An expected implementation of `task.start` would generate a core wasm function
2229+
for each `async`-lifted export that performs the fused copy of the arguments
2230+
into the caller, storing the index of this function in the `Task` structure and
2231+
using `call_indirect` to perform the function-type-equality check required
2232+
here. The call to `Task.start` (defined above) ensures that `canon task.start`
2233+
is called exactly once, before `canon task.return`, before an async call
2234+
finishes.
22242235

22252236
### 🔀 `canon task.return`
22262237

22272238
For a canonical definition:
22282239
```wasm
2229-
(canon task.return (core func $f))
2240+
(canon task.return $ft (core func $f))
22302241
```
22312242
validation specifies:
2232-
* `$f` is given type `(func (param i32))`
2243+
* `$f` is given type `$ft`, which validation requires to be a (core) function type
22332244

22342245
Calling `$f` invokes the following function which lifts the results from the
22352246
current instance and passes them to the caller:
22362247
```python
2237-
async def canon_task_return(task, i):
2248+
async def canon_task_return(task, core_ft, flat_args):
2249+
assert(len(core_ft.params) == len(flat_args))
22382250
trap_if(task.opts.sync)
2251+
trap_if(core_ft != flatten_functype(CanonicalOptions(), FuncType(task.ft.results, []), 'lower'))
22392252
task.return_()
2240-
task.return_thunk(lift_async_values(task, CoreValueIter([i]), task.ft.result_types()))
2253+
results = lift_sync_values(task, MAX_FLAT_PARAMS, CoreValueIter(flat_args), task.ft.result_types())
2254+
task.return_thunk(results)
2255+
assert(len(core_ft.results) == 0)
22412256
return []
22422257
```
2243-
The call to `Task.return_` (defined above) ensures that `canon task.return` is
2244-
called exactly once, after `canon task.start`, before an async call finishes.
2258+
An expected implementation of `task.return` would generate a core wasm function
2259+
for each `async`-lifted export that performs the fused copy of the results into
2260+
the caller, storing the index of this function in the `Task` structure and
2261+
using `call_indirect` to perform the function-type-equality check required
2262+
here. The call to `Task.return_` (defined above) ensures that `canon task.return`
2263+
is called exactly once, after `canon task.start`, before an async call finishes.
22452264

22462265
### 🔀 `canon task.wait`
22472266

design/mvp/Explainer.md

+21-16
Original file line numberDiff line numberDiff line change
@@ -1306,8 +1306,8 @@ canon ::= ...
13061306
| (canon resource.new <typeidx> (core func <id>?))
13071307
| (canon resource.drop <typeidx> async? (core func <id>?))
13081308
| (canon resource.rep <typeidx> (core func <id>?))
1309-
| (canon task.start (core func <id>?)) 🔀
1310-
| (canon task.return (core func <id>?)) 🔀
1309+
| (canon task.start <core:typeidx> (core func <id>?)) 🔀
1310+
| (canon task.return <core:typeidx> (core func <id>?)) 🔀
13111311
| (canon task.wait (core func <id>?)) 🔀
13121312
| (canon task.poll (core func <id>?)) 🔀
13131313
| (canon task.yield (core func <id>?)) 🔀
@@ -1367,21 +1367,26 @@ transferring ownership of the newly-created resource to the export's caller.
13671367
See the [async explainer](Async.md) for high-level context and terminology
13681368
and the [Canonical ABI explainer] for detailed runtime semantics.
13691369

1370-
The `task.start` built-in has type `[i32] -> []` where the `i32` is a pointer
1371-
into a linear memory buffer that will receive the arguments of the call to
1372-
the current task. This built-in must be called from an `async`-lifted export
1373-
exactly once per export activation. Delaying the call to `task.start` allows
1374-
the async callee to exert *backpressure* on the caller. (See also
1375-
[Starting](Async.md#starting) in the async explainer and [`canon_task_start`]
1376-
in the Canonical ABI explainer.)
1377-
1378-
The `task.return` built-in has type `[i32] -> []` where the `i32` is a pointer
1379-
to a linear memory buffer containing the value to be returned from the current
1370+
The `task.start` built-in returns the arguments to the currently-executing
13801371
task. This built-in must be called from an `async`-lifted export exactly once
1381-
per export activation after `task.start`. After calling `task.return`, the
1382-
callee can continue executing for an arbitrary amount of time before returning
1383-
to the caller. (See also [Returning](Async.md#returning) in the async explainer
1384-
and [`canon_task_return`] in the Canonical ABI explainer.)
1372+
per export activation. Delaying the call to `task.start` allows the async
1373+
callee to exert *backpressure* on the caller. The `canon task.start` definition
1374+
takes the type index of a core function type and produces a core function with
1375+
exactly that type. When called, the declared core function type is checked
1376+
to match the lowered function type of a component-level function returning the
1377+
parameter types of the current task. (See also [Starting](Async.md#starting) in
1378+
the async explainer and [`canon_task_start`] in the Canonical ABI explainer.)
1379+
1380+
The `task.return` built-in takes as parameters the result values of the
1381+
currently-executing task. This built-in must be called from an `async`-lifted
1382+
export exactly once per export activation after `task.start`. After calling
1383+
`task.return`, the callee can continue executing for an arbitrary amount of
1384+
time before returning to the caller. The `canon task.return` definition takes
1385+
the type index of a core function type and produces a core function with
1386+
exactly that type. When called, the declared core function type is checked
1387+
to match the lowered function type of a component-level function taking the
1388+
result types of the current task. (See also [Returning](Async.md#returning) in
1389+
the async explainer and [`canon_task_return`] in the Canonical ABI explainer.)
13851390

13861391
The `task.wait` built-in has type `[i32] -> [i32]`, returning an event and
13871392
storing the 4-byte payload of the event at the address passed as parameter.

design/mvp/canonical-abi/definitions.py

+18-6
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ class ModuleType(ExternType):
4949
class CoreFuncType(CoreExternType):
5050
params: list[str]
5151
results: list[str]
52+
def __eq__(self, other):
53+
return self.params == other.params and self.results == other.results
5254

5355
@dataclass
5456
class CoreMemoryType(CoreExternType):
@@ -1332,12 +1334,14 @@ def lower_heap_values(cx, vs, ts, out_param):
13321334
tuple_value = {str(i): v for i,v in enumerate(vs)}
13331335
if out_param is None:
13341336
ptr = cx.opts.realloc(0, 0, alignment(tuple_type), elem_size(tuple_type))
1337+
flat_vals = [ptr]
13351338
else:
13361339
ptr = out_param.next('i32')
1340+
flat_vals = []
13371341
trap_if(ptr != align_to(ptr, alignment(tuple_type)))
13381342
trap_if(ptr + elem_size(tuple_type) > len(cx.opts.memory))
13391343
store(cx, tuple_value, tuple_type, ptr)
1340-
return [ptr]
1344+
return flat_vals
13411345

13421346
### `canon lift`
13431347

@@ -1464,18 +1468,26 @@ async def canon_resource_rep(rt, task, i):
14641468

14651469
### `canon task.start`
14661470

1467-
async def canon_task_start(task, i):
1471+
async def canon_task_start(task, core_ft, flat_args):
1472+
assert(len(core_ft.params) == len(flat_args))
14681473
trap_if(task.opts.sync)
1474+
trap_if(core_ft != flatten_functype(CanonicalOptions(), FuncType([], task.ft.params), 'lower'))
14691475
task.start()
1470-
lower_async_values(task, task.start_thunk(), task.ft.param_types(), CoreValueIter([i]))
1471-
return []
1476+
args = task.start_thunk()
1477+
flat_results = lower_sync_values(task, MAX_FLAT_RESULTS, args, task.ft.param_types(), CoreValueIter(flat_args))
1478+
assert(len(core_ft.results) == len(flat_results))
1479+
return flat_results
14721480

14731481
### `canon task.return`
14741482

1475-
async def canon_task_return(task, i):
1483+
async def canon_task_return(task, core_ft, flat_args):
1484+
assert(len(core_ft.params) == len(flat_args))
14761485
trap_if(task.opts.sync)
1486+
trap_if(core_ft != flatten_functype(CanonicalOptions(), FuncType(task.ft.results, []), 'lower'))
14771487
task.return_()
1478-
task.return_thunk(lift_async_values(task, CoreValueIter([i]), task.ft.result_types()))
1488+
results = lift_sync_values(task, MAX_FLAT_PARAMS, CoreValueIter(flat_args), task.ft.result_types())
1489+
task.return_thunk(results)
1490+
assert(len(core_ft.results) == 0)
14791491
return []
14801492

14811493
### `canon task.wait`

design/mvp/canonical-abi/run_tests.py

+9-11
Original file line numberDiff line numberDiff line change
@@ -482,8 +482,8 @@ async def test_async_to_async():
482482
eager_ft = FuncType([], [U8()])
483483
async def core_eager_producer(task, args):
484484
assert(len(args) == 0)
485-
[] = await canon_task_start(task, 1000)
486-
[] = await canon_task_return(task, 43)
485+
[] = await canon_task_start(task, CoreFuncType([],[]), [])
486+
[] = await canon_task_return(task, CoreFuncType(['i32'],[]), [43])
487487
return []
488488
eager_callee = partial(canon_lift, producer_opts, producer_inst, core_eager_producer, eager_ft)
489489

@@ -505,11 +505,9 @@ async def blocking_callee(entered, start_thunk, return_thunk):
505505
async def consumer(task, args):
506506
assert(len(args) == 0)
507507

508-
ptr = consumer_heap.realloc(0, 0, 1, 1)
509-
[] = await canon_task_start(task, ptr)
510-
b = consumer_heap.memory[ptr]
511-
assert(b == True)
508+
[b] = await canon_task_start(task, CoreFuncType([],['i32']), [])
512509

510+
ptr = consumer_heap.realloc(0, 0, 1, 1)
513511
[ret] = await canon_lower(consumer_opts, eager_callee, eager_ft, task, [0, ptr])
514512
assert(ret == 0)
515513
u8 = consumer_heap.memory[ptr]
@@ -565,7 +563,7 @@ async def dtor(task, args):
565563
assert(callidx == 1)
566564
assert(task.num_async_subtasks == 0)
567565

568-
[] = await canon_task_return(task, 42)
566+
[] = await canon_task_return(task, CoreFuncType(['i32'],[]), [42])
569567
return []
570568

571569
ft = FuncType([Bool()],[U8()])
@@ -602,7 +600,7 @@ async def producer(fut, entered, start_thunk, return_thunk):
602600
consumer_ft = FuncType([],[U32()])
603601
async def consumer(task, args):
604602
assert(len(args) == 0)
605-
[] = await canon_task_start(task, 0)
603+
[] = await canon_task_start(task, CoreFuncType([],[]), [])
606604

607605
[ret] = await canon_lower(opts, producer1, producer_ft, task, [0, 0])
608606
assert(ret == (1 | (AsyncCallState.STARTED << 30)))
@@ -624,7 +622,7 @@ async def callback(task, args):
624622
assert(args[0] == 43)
625623
assert(args[1] == AsyncCallState.DONE)
626624
assert(args[2] == 2)
627-
[] = await canon_task_return(task, 83)
625+
[] = await canon_task_return(task, CoreFuncType(['i32'],[]), [83])
628626
return [0]
629627

630628
consumer_inst = ComponentInstance()
@@ -701,8 +699,8 @@ async def consumer(task, args):
701699

702700
assert(task.poll() is None)
703701

704-
await canon_task_start(task, 0)
705-
await canon_task_return(task, 83)
702+
await canon_task_start(task, CoreFuncType([],[]), [])
703+
await canon_task_return(task, CoreFuncType(['i32'],[]), [83])
706704
return []
707705

708706
consumer_inst = ComponentInstance()

0 commit comments

Comments
 (0)