Skip to content

Commit c27a99f

Browse files
committed
Make 'async' part of the function type, not a hint
1 parent 0352151 commit c27a99f

File tree

7 files changed

+567
-339
lines changed

7 files changed

+567
-339
lines changed

design/mvp/Binary.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ valtype ::= i:<typeidx> => i
216216
resourcetype ::= 0x3f 0x7f f?:<funcidx>? => (resource (rep i32) (dtor f)?)
217217
| 0x3e 0x7f f:<funcidx> cb?:<funcidx>? => (resource (rep i32) (dtor async f (callback cb)?)) 🚝
218218
functype ::= 0x40 ps:<paramlist> rs:<resultlist> => (func ps rs)
219+
| 0x43 ps:<paramlist> rs:<resultlist> => (func async ps rs)
219220
paramlist ::= lt*:vec(<labelvaltype>) => (param lt)*
220221
resultlist ::= 0x00 t:<valtype> => (result t)
221222
| 0x01 0x00 =>
@@ -288,7 +289,6 @@ canon ::= 0x00 0x00 f:<core:funcidx> opts:<opts> ft:<typeidx> => (canon lift
288289
| 0x01 0x00 f:<funcidx> opts:<opts> => (canon lower f opts (core func))
289290
| 0x02 rt:<typeidx> => (canon resource.new rt (core func))
290291
| 0x03 rt:<typeidx> => (canon resource.drop rt (core func))
291-
| 0x07 rt:<typeidx> => (canon resource.drop rt async (core func)) 🚝
292292
| 0x04 rt:<typeidx> => (canon resource.rep rt (core func))
293293
| 0x08 => (canon backpressure.set (core func)) 🔀✕
294294
| 0x24 => (canon backpressure.inc (core func)) 🔀
@@ -515,7 +515,8 @@ named once.
515515

516516
* The opcodes (for types, canon built-ins, etc) should be re-sorted
517517
* The two `depname` cases should be merged into one (`dep=<...>`)
518-
* The two `list` type codes should be merged into one with an optional immediate.
518+
* The two `list` type codes should be merged into one with an optional immediate
519+
and similarly for `func`.
519520
* The `0x00` variant of `importname'` and `exportname'` will be removed. Any
520521
remaining variant(s) will be renumbered or the prefix byte will be removed or
521522
repurposed.

design/mvp/CanonicalABI.md

Lines changed: 121 additions & 37 deletions
Large diffs are not rendered by default.

design/mvp/Concurrency.md

Lines changed: 242 additions & 153 deletions
Large diffs are not rendered by default.

design/mvp/Explainer.md

Lines changed: 68 additions & 68 deletions
Large diffs are not rendered by default.

design/mvp/WIT.md

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1450,16 +1450,11 @@ named-type-list ::= ϵ
14501450
named-type ::= id ':' ty
14511451
```
14521452

1453-
The optional `async` hint in a WIT function type indicates that the callee
1454-
is expected to block and thus the caller should emit whatever asynchronous
1455-
language bindings are appropriate (e.g., in JS, Python, C# or Rust, an `async`
1456-
WIT function would emit an `async` JS/Python/C#/Rust function). Because `async`
1457-
is just a hint and not enforced by the runtime, it is technically possible for
1458-
a non-`async` callee to block. In that case, though, it is the *callee's* fault
1459-
for any resultant loss of concurrency, not the caller's. Thus, `async` is
1460-
primarily intended to document expectations in a way that can be taken
1461-
advantage of by bindings generators. (For more details, see the [concurrency
1462-
explainer](Concurrency.md).)
1453+
The optional `async` prefix in a WIT function type indicates that the callee
1454+
may block and thus the caller should use the async ABI and asynchronous
1455+
source-language bindings (e.g., `async` functions in JS, Python, C# or Rust) if
1456+
concurrency execution is desired. For more details, see the [concurrency
1457+
explainer](Concurrency.md#summary).
14631458

14641459

14651460
## Item: `use`
@@ -1689,8 +1684,8 @@ resource-method ::= func-item
16891684
| 'constructor' param-list ';'
16901685
```
16911686

1692-
The optional `async` hint on `static` functions has the same meaning as
1693-
in a non-`static` `func-item`.
1687+
The optional `async` on `static` functions has the same meaning as in a
1688+
non-`static` `func-item`.
16941689

16951690
The syntax for handle types is presented [below](#handles).
16961691

design/mvp/canonical-abi/definitions.py

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ class InstanceType(ExternType):
8888
class FuncType(ExternType):
8989
params: list[tuple[str,ValType]]
9090
result: list[ValType|tuple[str,ValType]]
91+
async_: bool = False
9192
def param_types(self):
9293
return self.extract_types(self.params)
9394
def result_type(self):
@@ -402,6 +403,7 @@ def resume(self, suspend_result = SuspendResult.NOT_CANCELLED):
402403
assert(not self.running())
403404

404405
def suspend(self, cancellable) -> SuspendResult:
406+
assert(self.task.may_block())
405407
assert(self.running() and not self.cancellable and self.suspend_result is None)
406408
self.cancellable = cancellable
407409
self.parent_lock.release()
@@ -420,6 +422,7 @@ def resume_later(self):
420422
self.task.inst.store.pending.append(self)
421423

422424
def suspend_until(self, ready_func, cancellable = False) -> SuspendResult:
425+
assert(self.task.may_block())
423426
assert(self.running())
424427
if ready_func() and not DETERMINISTIC_PROFILE and random.randint(0,1):
425428
return SuspendResult.NOT_CANCELLED
@@ -566,8 +569,13 @@ def trap_if_on_the_stack(self, inst):
566569
def needs_exclusive(self):
567570
return not self.opts.async_ or self.opts.callback
568571

572+
def may_block(self):
573+
return self.ft.async_ or self.state == Task.State.RESOLVED
574+
569575
def enter(self, thread):
570576
assert(thread in self.threads and thread.task is self)
577+
if not self.ft.async_:
578+
return True
571579
def has_backpressure():
572580
return self.inst.backpressure > 0 or (self.needs_exclusive() and self.inst.exclusive)
573581
if has_backpressure() or self.inst.num_waiting_to_enter > 0:
@@ -584,6 +592,8 @@ def has_backpressure():
584592

585593
def exit(self):
586594
assert(len(self.threads) > 0)
595+
if not self.ft.async_:
596+
return
587597
if self.needs_exclusive():
588598
assert(self.inst.exclusive)
589599
self.inst.exclusive = False
@@ -2028,12 +2038,17 @@ def thread_func(thread):
20282038
inst.exclusive = False
20292039
match code:
20302040
case CallbackCode.YIELD:
2031-
event = task.yield_until(lambda: not inst.exclusive, thread, cancellable = True)
2041+
if task.may_block():
2042+
event = task.yield_until(lambda: not inst.exclusive, thread, cancellable = True)
2043+
else:
2044+
event = (EventCode.NONE, 0, 0)
20322045
case CallbackCode.WAIT:
2046+
trap_if(not task.may_block())
20332047
wset = inst.table.get(si)
20342048
trap_if(not isinstance(wset, WaitableSet))
20352049
event = task.wait_until(lambda: not inst.exclusive, thread, wset, cancellable = True)
20362050
case CallbackCode.POLL:
2051+
trap_if(not task.may_block())
20372052
wset = inst.table.get(si)
20382053
trap_if(not isinstance(wset, WaitableSet))
20392054
event = task.poll_until(lambda: not inst.exclusive, thread, wset, cancellable = True)
@@ -2074,6 +2089,8 @@ def call_and_trap_on_throw(callee, thread, args):
20742089

20752090
def canon_lower(opts, ft, callee: FuncInst, thread, flat_args):
20762091
trap_if(not thread.task.inst.may_leave)
2092+
trap_if(not thread.task.may_block() and ft.async_ and not opts.async_)
2093+
20772094
subtask = Subtask()
20782095
cx = LiftLowerContext(opts, thread.task.inst, subtask)
20792096

@@ -2113,6 +2130,7 @@ def on_resolve(result):
21132130
flat_results = lower_flat_values(cx, max_flat_results, result, ft.result_type(), flat_args)
21142131

21152132
subtask.callee = callee(thread.task, on_start, on_resolve)
2133+
assert(ft.async_ or subtask.state == Subtask.State.RETURNED)
21162134

21172135
if not opts.async_:
21182136
if not subtask.resolved():
@@ -2147,31 +2165,30 @@ def canon_resource_new(rt, thread, rep):
21472165

21482166
### `canon resource.drop`
21492167

2150-
def canon_resource_drop(rt, async_, thread, i):
2168+
def canon_resource_drop(rt, thread, i):
21512169
trap_if(not thread.task.inst.may_leave)
21522170
inst = thread.task.inst
21532171
h = inst.table.remove(i)
21542172
trap_if(not isinstance(h, ResourceHandle))
21552173
trap_if(h.rt is not rt)
21562174
trap_if(h.num_lends != 0)
2157-
flat_results = [] if not async_ else [0]
21582175
if h.own:
21592176
assert(h.borrow_scope is None)
21602177
if inst is rt.impl:
21612178
if rt.dtor:
21622179
rt.dtor(h.rep)
21632180
else:
21642181
if rt.dtor:
2165-
caller_opts = CanonicalOptions(async_ = async_)
2182+
caller_opts = CanonicalOptions(async_ = False)
21662183
callee_opts = CanonicalOptions(async_ = rt.dtor_async, callback = rt.dtor_callback)
2167-
ft = FuncType([U32Type()],[])
2184+
ft = FuncType([U32Type()],[], async_ = False)
21682185
callee = partial(canon_lift, callee_opts, rt.impl, ft, rt.dtor)
2169-
flat_results = canon_lower(caller_opts, ft, callee, thread, [h.rep])
2186+
[] = canon_lower(caller_opts, ft, callee, thread, [h.rep])
21702187
else:
21712188
thread.task.trap_if_on_the_stack(rt.impl)
21722189
else:
21732190
h.borrow_scope.num_borrows -= 1
2174-
return flat_results
2191+
return []
21752192

21762193
### `canon resource.rep`
21772194

@@ -2249,6 +2266,7 @@ def canon_waitable_set_new(thread):
22492266

22502267
def canon_waitable_set_wait(cancellable, mem, thread, si, ptr):
22512268
trap_if(not thread.task.inst.may_leave)
2269+
trap_if(not thread.task.may_block())
22522270
wset = thread.task.inst.table.get(si)
22532271
trap_if(not isinstance(wset, WaitableSet))
22542272
event = thread.task.wait_until(lambda: True, thread, wset, cancellable)
@@ -2265,6 +2283,7 @@ def unpack_event(mem, thread, ptr, e: EventTuple):
22652283

22662284
def canon_waitable_set_poll(cancellable, mem, thread, si, ptr):
22672285
trap_if(not thread.task.inst.may_leave)
2286+
trap_if(not thread.task.may_block())
22682287
wset = thread.task.inst.table.get(si)
22692288
trap_if(not isinstance(wset, WaitableSet))
22702289
event = thread.task.poll_until(lambda: True, thread, wset, cancellable)
@@ -2299,6 +2318,7 @@ def canon_waitable_join(thread, wi, si):
22992318

23002319
def canon_subtask_cancel(async_, thread, i):
23012320
trap_if(not thread.task.inst.may_leave)
2321+
trap_if(not thread.task.may_block() and not async_)
23022322
subtask = thread.task.inst.table.get(i)
23032323
trap_if(not isinstance(subtask, Subtask))
23042324
trap_if(subtask.resolve_delivered())
@@ -2355,6 +2375,8 @@ def canon_stream_write(stream_t, opts, thread, i, ptr, n):
23552375

23562376
def stream_copy(EndT, BufferT, event_code, stream_t, opts, thread, i, ptr, n):
23572377
trap_if(not thread.task.inst.may_leave)
2378+
trap_if(not thread.task.may_block() and not opts.async_)
2379+
23582380
e = thread.task.inst.table.get(i)
23592381
trap_if(not isinstance(e, EndT))
23602382
trap_if(e.shared.t != stream_t.t)
@@ -2406,6 +2428,8 @@ def canon_future_write(future_t, opts, thread, i, ptr):
24062428

24072429
def future_copy(EndT, BufferT, event_code, future_t, opts, thread, i, ptr):
24082430
trap_if(not thread.task.inst.may_leave)
2431+
trap_if(not thread.task.may_block() and not opts.async_)
2432+
24092433
e = thread.task.inst.table.get(i)
24102434
trap_if(not isinstance(e, EndT))
24112435
trap_if(e.shared.t != future_t.t)
@@ -2456,6 +2480,7 @@ def canon_future_cancel_write(future_t, async_, thread, i):
24562480

24572481
def cancel_copy(EndT, event_code, stream_or_future_t, async_, thread, i):
24582482
trap_if(not thread.task.inst.may_leave)
2483+
trap_if(not thread.task.may_block() and not async_)
24592484
e = thread.task.inst.table.get(i)
24602485
trap_if(not isinstance(e, EndT))
24612486
trap_if(e.shared.t != stream_or_future_t.t)
@@ -2532,6 +2557,7 @@ def canon_thread_switch_to(cancellable, thread, i):
25322557

25332558
def canon_thread_suspend(cancellable, thread):
25342559
trap_if(not thread.task.inst.may_leave)
2560+
trap_if(not thread.task.may_block())
25352561
suspend_result = thread.task.suspend(thread, cancellable)
25362562
return [suspend_result]
25372563

@@ -2559,6 +2585,8 @@ def canon_thread_yield_to(cancellable, thread, i):
25592585

25602586
def canon_thread_yield(cancellable, thread):
25612587
trap_if(not thread.task.inst.may_leave)
2588+
if not thread.task.may_block():
2589+
return [SuspendResult.NOT_CANCELLED]
25622590
event_code,_,_ = thread.task.yield_until(lambda: True, thread, cancellable)
25632591
match event_code:
25642592
case EventCode.NONE:

0 commit comments

Comments
 (0)