Skip to content

Commit adcf6f7

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

File tree

5 files changed

+84
-49
lines changed

5 files changed

+84
-49
lines changed

design/mvp/Binary.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -275,8 +275,8 @@ canon ::= 0x00 0x00 f:<core:funcidx> opts:<opts> ft:<typeidx> => (canon lift
275275
| 0x04 rt:<typeidx> => (canon resource.rep rt (core func))
276276
| 0x05 ft:<typeidx> => (canon thread.spawn ft (core func))
277277
| 0x06 => (canon thread.hw_concurrency (core func))
278-
| 0x08 => (canon task.start (core func))
279-
| 0x09 => (canon task.return (core func))
278+
| 0x08 ft:<core:typeidx> => (canon task.start ft (core func))
279+
| 0x09 ft:<core:typeidx> => (canon task.return ft (core func))
280280
| 0x0a => (canon task.wait (core func))
281281
| 0x0b => (canon task.poll (core func))
282282
| 0x0c => (canon task.yield (core func))

design/mvp/CanonicalABI.md

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1868,12 +1868,14 @@ def lower_heap_values(cx, vs, ts, out_param):
18681868
tuple_value = {str(i): v for i,v in enumerate(vs)}
18691869
if out_param is None:
18701870
ptr = cx.opts.realloc(0, 0, alignment(tuple_type), elem_size(tuple_type))
1871+
flat_vals = [ptr]
18711872
else:
18721873
ptr = out_param.next('i32')
1874+
flat_vals = []
18731875
trap_if(ptr != align_to(ptr, alignment(tuple_type)))
18741876
trap_if(ptr + elem_size(tuple_type) > len(cx.opts.memory))
18751877
store(cx, tuple_value, tuple_type, ptr)
1876-
return [ptr]
1878+
return flat_vals
18771879
```
18781880
The `may_leave` flag is guarded by `canon_lower` below to prevent a component
18791881
from calling out of the component while in the middle of lowering, ensuring
@@ -2207,43 +2209,61 @@ component instance defining a resource can access its representation.
22072209

22082210
For a canonical definition:
22092211
```wasm
2210-
(canon task.start (core func $f))
2212+
(canon task.start $ft (core func $f))
22112213
```
22122214
validation specifies:
2213-
* `$f` is given type `(func (param i32))`
2215+
* `$f` is given type `$ft`, which validation requires to be a (core) function type
22142216

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

22272238
### 🔀 `canon task.return`
22282239

22292240
For a canonical definition:
22302241
```wasm
2231-
(canon task.return (core func $f))
2242+
(canon task.return $ft (core func $f))
22322243
```
22332244
validation specifies:
2234-
* `$f` is given type `(func (param i32))`
2245+
* `$f` is given type `$ft`, which validation requires to be a (core) function type
22352246

22362247
Calling `$f` invokes the following function which lifts the results from the
22372248
current instance and passes them to the caller:
22382249
```python
2239-
async def canon_task_return(task, i):
2250+
async def canon_task_return(task, core_ft, flat_args):
2251+
assert(len(core_ft.params) == len(flat_args))
22402252
trap_if(task.opts.sync)
2253+
trap_if(core_ft != flatten_functype(CanonicalOptions(), FuncType(task.ft.results, []), 'lower'))
22412254
task.return_()
2242-
task.return_thunk(lift_async_values(task, CoreValueIter([i]), task.ft.result_types()))
2255+
results = lift_sync_values(task, MAX_FLAT_PARAMS, CoreValueIter(flat_args), task.ft.result_types())
2256+
task.return_thunk(results)
2257+
assert(len(core_ft.results) == 0)
22432258
return []
22442259
```
2245-
The call to `Task.return_` (defined above) ensures that `canon task.return` is
2246-
called exactly once, after `canon task.start`, before an async call finishes.
2260+
An expected implementation of `task.return` would generate a core wasm function
2261+
for each lowering of an `async`-lifted export that performs the fused copy of
2262+
the results into the caller, storing the index of this function in the `Task`
2263+
structure and using `call_indirect` to perform the function-type-equality check
2264+
required here. The call to `Task.return_` (defined above) ensures that `canon
2265+
task.return` is called exactly once, after `canon task.start`, before an async
2266+
call finishes.
22472267

22482268
### 🔀 `canon task.wait`
22492269

design/mvp/Explainer.md

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1313,8 +1313,8 @@ canon ::= ...
13131313
| (canon resource.new <typeidx> (core func <id>?))
13141314
| (canon resource.drop <typeidx> async? (core func <id>?))
13151315
| (canon resource.rep <typeidx> (core func <id>?))
1316-
| (canon task.start (core func <id>?)) 🔀
1317-
| (canon task.return (core func <id>?)) 🔀
1316+
| (canon task.start <core:typeidx> (core func <id>?)) 🔀
1317+
| (canon task.return <core:typeidx> (core func <id>?)) 🔀
13181318
| (canon task.wait (core func <id>?)) 🔀
13191319
| (canon task.poll (core func <id>?)) 🔀
13201320
| (canon task.yield (core func <id>?)) 🔀
@@ -1374,21 +1374,26 @@ transferring ownership of the newly-created resource to the export's caller.
13741374
See the [async explainer](Async.md) for high-level context and terminology
13751375
and the [Canonical ABI explainer] for detailed runtime semantics.
13761376

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

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

design/mvp/canonical-abi/definitions.py

Lines changed: 18 additions & 6 deletions
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):
@@ -1334,12 +1336,14 @@ def lower_heap_values(cx, vs, ts, out_param):
13341336
tuple_value = {str(i): v for i,v in enumerate(vs)}
13351337
if out_param is None:
13361338
ptr = cx.opts.realloc(0, 0, alignment(tuple_type), elem_size(tuple_type))
1339+
flat_vals = [ptr]
13371340
else:
13381341
ptr = out_param.next('i32')
1342+
flat_vals = []
13391343
trap_if(ptr != align_to(ptr, alignment(tuple_type)))
13401344
trap_if(ptr + elem_size(tuple_type) > len(cx.opts.memory))
13411345
store(cx, tuple_value, tuple_type, ptr)
1342-
return [ptr]
1346+
return flat_vals
13431347

13441348
### `canon lift`
13451349

@@ -1466,18 +1470,26 @@ async def canon_resource_rep(rt, task, i):
14661470

14671471
### `canon task.start`
14681472

1469-
async def canon_task_start(task, i):
1473+
async def canon_task_start(task, core_ft, flat_args):
1474+
assert(len(core_ft.params) == len(flat_args))
14701475
trap_if(task.opts.sync)
1476+
trap_if(core_ft != flatten_functype(CanonicalOptions(), FuncType([], task.ft.params), 'lower'))
14711477
task.start()
1472-
lower_async_values(task, task.start_thunk(), task.ft.param_types(), CoreValueIter([i]))
1473-
return []
1478+
args = task.start_thunk()
1479+
flat_results = lower_sync_values(task, MAX_FLAT_RESULTS, args, task.ft.param_types(), CoreValueIter(flat_args))
1480+
assert(len(core_ft.results) == len(flat_results))
1481+
return flat_results
14741482

14751483
### `canon task.return`
14761484

1477-
async def canon_task_return(task, i):
1485+
async def canon_task_return(task, core_ft, flat_args):
1486+
assert(len(core_ft.params) == len(flat_args))
14781487
trap_if(task.opts.sync)
1488+
trap_if(core_ft != flatten_functype(CanonicalOptions(), FuncType(task.ft.results, []), 'lower'))
14791489
task.return_()
1480-
task.return_thunk(lift_async_values(task, CoreValueIter([i]), task.ft.result_types()))
1490+
results = lift_sync_values(task, MAX_FLAT_PARAMS, CoreValueIter(flat_args), task.ft.result_types())
1491+
task.return_thunk(results)
1492+
assert(len(core_ft.results) == 0)
14811493
return []
14821494

14831495
### `canon task.wait`

design/mvp/canonical-abi/run_tests.py

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -491,8 +491,8 @@ async def test_async_to_async():
491491
eager_ft = FuncType([], [U8()])
492492
async def core_eager_producer(task, args):
493493
assert(len(args) == 0)
494-
[] = await canon_task_start(task, 1000)
495-
[] = await canon_task_return(task, 43)
494+
[] = await canon_task_start(task, CoreFuncType([],[]), [])
495+
[] = await canon_task_return(task, CoreFuncType(['i32'],[]), [43])
496496
return []
497497
eager_callee = partial(canon_lift, producer_opts, producer_inst, core_eager_producer, eager_ft)
498498

@@ -514,11 +514,9 @@ async def blocking_callee(entered, start_thunk, return_thunk):
514514
async def consumer(task, args):
515515
assert(len(args) == 0)
516516

517-
ptr = consumer_heap.realloc(0, 0, 1, 1)
518-
[] = await canon_task_start(task, ptr)
519-
b = consumer_heap.memory[ptr]
520-
assert(b == True)
517+
[b] = await canon_task_start(task, CoreFuncType([],['i32']), [])
521518

519+
ptr = consumer_heap.realloc(0, 0, 1, 1)
522520
[ret] = await canon_lower(consumer_opts, eager_callee, eager_ft, task, [0, ptr])
523521
assert(ret == 0)
524522
u8 = consumer_heap.memory[ptr]
@@ -574,7 +572,7 @@ async def dtor(task, args):
574572
assert(callidx == 1)
575573
assert(task.num_async_subtasks == 0)
576574

577-
[] = await canon_task_return(task, 42)
575+
[] = await canon_task_return(task, CoreFuncType(['i32'],[]), [42])
578576
return []
579577

580578
ft = FuncType([Bool()],[U8()])
@@ -611,7 +609,7 @@ async def producer(fut, entered, start_thunk, return_thunk):
611609
consumer_ft = FuncType([],[U32()])
612610
async def consumer(task, args):
613611
assert(len(args) == 0)
614-
[] = await canon_task_start(task, 0)
612+
[] = await canon_task_start(task, CoreFuncType([],[]), [])
615613

616614
[ret] = await canon_lower(opts, producer1, producer_ft, task, [0, 0])
617615
assert(ret == (1 | (AsyncCallState.STARTED << 30)))
@@ -633,7 +631,7 @@ async def callback(task, args):
633631
assert(args[0] == 43)
634632
assert(args[1] == AsyncCallState.DONE)
635633
assert(args[2] == 2)
636-
[] = await canon_task_return(task, 83)
634+
[] = await canon_task_return(task, CoreFuncType(['i32'],[]), [83])
637635
return [0]
638636

639637
consumer_inst = ComponentInstance()
@@ -710,8 +708,8 @@ async def consumer(task, args):
710708

711709
assert(task.poll() is None)
712710

713-
await canon_task_start(task, 0)
714-
await canon_task_return(task, 83)
711+
await canon_task_start(task, CoreFuncType([],[]), [])
712+
await canon_task_return(task, CoreFuncType(['i32'],[]), [83])
715713
return []
716714

717715
consumer_inst = ComponentInstance()

0 commit comments

Comments
 (0)