Skip to content

Commit 91fa97e

Browse files
committed
Make 'async' part of the function type, not a hint
1 parent 66c9256 commit 91fa97e

File tree

5 files changed

+88
-93
lines changed

5 files changed

+88
-93
lines changed

design/mvp/Binary.md

Lines changed: 1 addition & 1 deletion
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)) 🔀

design/mvp/Explainer.md

Lines changed: 11 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -568,7 +568,7 @@ valtype ::= <typeidx>
568568
| <defvaltype>
569569
resourcetype ::= (resource (rep i32) (dtor <funcidx>)?)
570570
| (resource (rep i32) (dtor async <funcidx> (callback <funcidx>)?)?) 🚝
571-
functype ::= (func (param "<label>" <valtype>)* (result <valtype>)?)
571+
functype ::= (func async? (param "<label>" <valtype>)* (result <valtype>)?)
572572
componenttype ::= (component <componentdecl>*)
573573
instancetype ::= (instance <instancedecl>*)
574574
componentdecl ::= <importdecl>
@@ -737,11 +737,8 @@ are useful for:
737737

738738
A `future<T>` asynchronously delivers exactly one `T` value from a source to a
739739
destination, unless the destination signals that it doesn't want the `T` value
740-
any more. Because [all imports can be called asynchronously][summary], futures
741-
are not necessary to express a traditional `async` function -- all functions
742-
are effectively `async`. Instead futures are useful in more advanced scenarios
743-
where a parameter or result value may not be ready at the same time as the
744-
other synchronous parameters or results.
740+
any more. When function types are marked [`async`][summary] directly,
741+
...TODO... `future<T>` is only needed to express *additional* concurrency.
745742

746743
The `T` element type of `stream` and `future` is an optional `valtype`. As with
747744
variant-case payloads and function results, when `T` is absent, the "value(s)"
@@ -793,7 +790,9 @@ shared-nothing functions, resources, components, and component instances:
793790

794791
The `func` type constructor describes a component-level function definition
795792
that takes a list of `valtype` parameters with [strongly-unique] names and
796-
optionally returns a `valtype`.
793+
optionally returns a `valtype`. The optional `async` effect type indicates that
794+
the function may block (calling a blocking built-in like `waitable-set.wait` or
795+
synchronously calling an imported `async` function).
797796

798797
The `resource` type constructor creates a fresh type for each instance of the
799798
containing component (with "freshness" and its interaction with general
@@ -1413,7 +1412,6 @@ dynamically interact with Canonical ABI entities like resources,
14131412
canon ::= ...
14141413
| (canon resource.new <typeidx> (core func <id>?))
14151414
| (canon resource.drop <typeidx> (core func <id>?))
1416-
| (canon resource.drop <typeidx> async (core func <id>?)) 🚝
14171415
| (canon resource.rep <typeidx> (core func <id>?))
14181416
| (canon context.get <valtype> <u32> (core func <id>?)) 🔀
14191417
| (canon context.set <valtype> <u32> (core func <id>?)) 🔀
@@ -1482,32 +1480,16 @@ For details, see [`canon_resource_new`] in the Canonical ABI explainer.
14821480

14831481
###### `resource.drop`
14841482

1485-
When the `async` immediate is false:
1486-
14871483
| Synopsis | |
14881484
| -------------------------- | ---------------------------------- |
14891485
| Approximate WIT signature | `func<T>(t: T)` |
14901486
| Canonical ABI signature | `[t:i32] -> []` |
14911487

1492-
🚝 When the `async` immediate is true:
1493-
1494-
| Synopsis | |
1495-
| -------------------------- | ---------------------------------- |
1496-
| Approximate WIT signature | `func<T>(t: T) -> option<subtask>` |
1497-
| Canonical ABI signature | `[t:i32] -> [i32]` |
1498-
14991488
The `resource.drop` built-in drops a resource handle `t` (with resource type
15001489
`T`). If the dropped handle owns the resource, the resource's `dtor` is called,
15011490
if present. Validation only allows `resource.rep T` to be used within the
15021491
component that defined `T`.
15031492

1504-
When the `async` immediate is true, the returned value indicates whether the
1505-
drop completed eagerly, or if not, identifies the in-progress drop.
1506-
1507-
In the Canonical ABI, the returned `i32` is either `0` (if the drop completed
1508-
eagerly) or the index of the in-progress drop subtask (representing the
1509-
in-progress `dtor` call).
1510-
15111493
For details, see [`canon_resource_drop`] in the Canonical ABI explainer.
15121494

15131495
###### `resource.rep`
@@ -2521,12 +2503,9 @@ importname ::= <exportname>
25212503
| <urlname>
25222504
| <hashname>
25232505
plainname ::= <label>
2524-
| '[async]' <label> 🔀
25252506
| '[constructor]' <label>
25262507
| '[method]' <label> '.' <label>
2527-
| '[async method]' <label> '.' <label> 🔀
25282508
| '[static]' <label> '.' <label>
2529-
| '[async static]' <label> '.' <label> 🔀
25302509
label ::= <fragment>
25312510
| <label> '-' <fragment>
25322511
fragment ::= <word>
@@ -2671,10 +2650,10 @@ annotations trigger additional type-validation rules (listed in
26712650
* Similarly, an import or export named `[method]R.foo` must be a function whose
26722651
first parameter must be `(param "self" (borrow $R))`.
26732652

2674-
When a function is annotated with `async`, bindings generators are expected to
2653+
When a function's type is `async`, bindings generators are expected to
26752654
emit whatever asynchronous language construct is appropriate (such as an
2676-
`async` function in JS, Python or Rust). Note the absence of
2677-
`[async constructor]`. See the [concurrency explainer] for more details.
2655+
`async` function in JS, Python or Rust). See the [concurrency explainer] for
2656+
more details.
26782657

26792658
The `label` production used inside `plainname` as well as the labels of
26802659
`record` and `variant` types are required to have [kebab case]. The reason for
@@ -2806,7 +2785,8 @@ Thus, the following names are strongly-unique:
28062785
* `foo`, `foo-bar`, `[constructor]foo`, `[method]foo.bar`, `[method]foo.baz`
28072786

28082787
but attempting to add *any* of the following names would be a validation error:
2809-
* `foo`, `foo-BAR`, `[constructor]foo-BAR`, `[method]foo.foo`, `[async]foo`, `[method]foo.BAR`
2788+
* `foo`, `foo-BAR`, `[constructor]foo-BAR`, `[method]foo.foo`,
2789+
`[static]foo.foo`, `[method]foo.BAR`
28102790

28112791
Note that additional validation rules involving types apply to names with
28122792
annotations. For example, the validation rules for `[constructor]foo` require

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 is
1454+
allowed to block and thus the caller should emit the appropriate asynchronous
1455+
source-language bindings (e.g., in JS, Python, C# or Rust, an `async` WIT
1456+
function would emit an `async` JS/Python/C#/Rust function). (For more details,
1457+
see the [concurrency explainer](Concurrency.md).)
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: 31 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):
@@ -557,8 +558,13 @@ def trap_if_on_the_stack(self, inst):
557558
def needs_exclusive(self):
558559
return not self.opts.async_ or self.opts.callback
559560

561+
def must_not_suspend(self):
562+
return not self.ft.async_ and self.state != Task.State.RESOLVED
563+
560564
def enter(self, thread):
561565
assert(thread in self.threads and thread.task is self)
566+
if not self.ft.async_:
567+
return True
562568
def has_backpressure():
563569
return self.inst.backpressure > 0 or (self.needs_exclusive() and self.inst.exclusive)
564570
if has_backpressure() or self.inst.num_waiting_to_enter > 0:
@@ -575,6 +581,8 @@ def has_backpressure():
575581

576582
def exit(self):
577583
assert(len(self.threads) > 0)
584+
if not self.ft.async_:
585+
return
578586
if self.needs_exclusive():
579587
assert(self.inst.exclusive)
580588
self.inst.exclusive = False
@@ -2010,12 +2018,17 @@ def thread_func(thread):
20102018
inst.exclusive = False
20112019
match code:
20122020
case CallbackCode.YIELD:
2013-
event = task.yield_until(lambda: not inst.exclusive, thread, cancellable = True)
2021+
if thread.task.must_not_suspend():
2022+
event = (EventCode.NONE, 0, 0)
2023+
else:
2024+
event = task.yield_until(lambda: not inst.exclusive, thread, cancellable = True)
20142025
case CallbackCode.WAIT:
2026+
trap_if(thread.task.must_not_suspend())
20152027
wset = inst.table.get(si)
20162028
trap_if(not isinstance(wset, WaitableSet))
20172029
event = task.wait_until(lambda: not inst.exclusive, thread, wset, cancellable = True)
20182030
case CallbackCode.POLL:
2031+
trap_if(thread.task.must_not_suspend())
20192032
wset = inst.table.get(si)
20202033
trap_if(not isinstance(wset, WaitableSet))
20212034
event = task.poll_until(lambda: not inst.exclusive, thread, wset, cancellable = True)
@@ -2056,6 +2069,7 @@ def call_and_trap_on_throw(callee, thread, args):
20562069

20572070
def canon_lower(opts, ft, callee: FuncInst, thread, flat_args):
20582071
trap_if(not thread.task.inst.may_leave)
2072+
trap_if(ft.async_ and not opts.async_ and thread.task.must_not_suspend())
20592073
subtask = Subtask()
20602074
cx = LiftLowerContext(opts, thread.task.inst, subtask)
20612075

@@ -2095,6 +2109,7 @@ def on_resolve(result):
20952109
flat_results = lower_flat_values(cx, max_flat_results, result, ft.result_type(), flat_args)
20962110

20972111
subtask.callee = callee(thread.task, on_start, on_resolve)
2112+
assert(ft.async_ or subtask.resolved())
20982113

20992114
if not opts.async_:
21002115
if not subtask.resolved():
@@ -2129,31 +2144,30 @@ def canon_resource_new(rt, thread, rep):
21292144

21302145
### `canon resource.drop`
21312146

2132-
def canon_resource_drop(rt, async_, thread, i):
2147+
def canon_resource_drop(rt, thread, i):
21332148
trap_if(not thread.task.inst.may_leave)
21342149
inst = thread.task.inst
21352150
h = inst.table.remove(i)
21362151
trap_if(not isinstance(h, ResourceHandle))
21372152
trap_if(h.rt is not rt)
21382153
trap_if(h.num_lends != 0)
2139-
flat_results = [] if not async_ else [0]
21402154
if h.own:
21412155
assert(h.borrow_scope is None)
21422156
if inst is rt.impl:
21432157
if rt.dtor:
21442158
rt.dtor(h.rep)
21452159
else:
21462160
if rt.dtor:
2147-
caller_opts = CanonicalOptions(async_ = async_)
2161+
caller_opts = CanonicalOptions(async_ = False)
21482162
callee_opts = CanonicalOptions(async_ = rt.dtor_async, callback = rt.dtor_callback)
2149-
ft = FuncType([U32Type()],[])
2163+
ft = FuncType([U32Type()],[], async_ = False)
21502164
callee = partial(canon_lift, callee_opts, rt.impl, ft, rt.dtor)
2151-
flat_results = canon_lower(caller_opts, ft, callee, thread, [h.rep])
2165+
[] = canon_lower(caller_opts, ft, callee, thread, [h.rep])
21522166
else:
21532167
thread.task.trap_if_on_the_stack(rt.impl)
21542168
else:
21552169
h.borrow_scope.num_borrows -= 1
2156-
return flat_results
2170+
return []
21572171

21582172
### `canon resource.rep`
21592173

@@ -2231,6 +2245,7 @@ def canon_waitable_set_new(thread):
22312245

22322246
def canon_waitable_set_wait(cancellable, mem, thread, si, ptr):
22332247
trap_if(not thread.task.inst.may_leave)
2248+
trap_if(thread.task.must_not_suspend())
22342249
wset = thread.task.inst.table.get(si)
22352250
trap_if(not isinstance(wset, WaitableSet))
22362251
event = thread.task.wait_until(lambda: True, thread, wset, cancellable)
@@ -2247,6 +2262,7 @@ def unpack_event(mem, thread, ptr, e: EventTuple):
22472262

22482263
def canon_waitable_set_poll(cancellable, mem, thread, si, ptr):
22492264
trap_if(not thread.task.inst.may_leave)
2265+
trap_if(thread.task.must_not_suspend())
22502266
wset = thread.task.inst.table.get(si)
22512267
trap_if(not isinstance(wset, WaitableSet))
22522268
event = thread.task.poll_until(lambda: True, thread, wset, cancellable)
@@ -2281,6 +2297,7 @@ def canon_waitable_join(thread, wi, si):
22812297

22822298
def canon_subtask_cancel(async_, thread, i):
22832299
trap_if(not thread.task.inst.may_leave)
2300+
trap_if(not async_ and thread.task.must_not_suspend())
22842301
subtask = thread.task.inst.table.get(i)
22852302
trap_if(not isinstance(subtask, Subtask))
22862303
trap_if(subtask.resolve_delivered())
@@ -2337,6 +2354,7 @@ def canon_stream_write(stream_t, opts, thread, i, ptr, n):
23372354

23382355
def stream_copy(EndT, BufferT, event_code, stream_t, opts, thread, i, ptr, n):
23392356
trap_if(not thread.task.inst.may_leave)
2357+
trap_if(not opts.async_ and thread.task.must_not_suspend())
23402358
e = thread.task.inst.table.get(i)
23412359
trap_if(not isinstance(e, EndT))
23422360
trap_if(e.shared.t != stream_t.t)
@@ -2388,6 +2406,7 @@ def canon_future_write(future_t, opts, thread, i, ptr):
23882406

23892407
def future_copy(EndT, BufferT, event_code, future_t, opts, thread, i, ptr):
23902408
trap_if(not thread.task.inst.may_leave)
2409+
trap_if(not opts.async_ and thread.task.must_not_suspend())
23912410
e = thread.task.inst.table.get(i)
23922411
trap_if(not isinstance(e, EndT))
23932412
trap_if(e.shared.t != future_t.t)
@@ -2438,6 +2457,8 @@ def canon_future_cancel_write(future_t, async_, thread, i):
24382457

24392458
def cancel_copy(EndT, event_code, stream_or_future_t, async_, thread, i):
24402459
trap_if(not thread.task.inst.may_leave)
2460+
# TODO: maybe allow?
2461+
trap_if(not async_ and thread.task.must_not_suspend())
24412462
e = thread.task.inst.table.get(i)
24422463
trap_if(not isinstance(e, EndT))
24432464
trap_if(e.shared.t != stream_or_future_t.t)
@@ -2521,6 +2542,7 @@ def canon_thread_switch_to(cancellable, thread, i):
25212542

25222543
def canon_thread_suspend(cancellable, thread):
25232544
trap_if(not thread.task.inst.may_leave)
2545+
trap_if(thread.task.must_not_suspend())
25242546
if not thread.task.suspend(thread, cancellable):
25252547
assert(cancellable)
25262548
return [SuspendResult.CANCELLED]
@@ -2554,6 +2576,8 @@ def canon_thread_yield_to(cancellable, thread, i):
25542576

25552577
def canon_thread_yield(cancellable, thread):
25562578
trap_if(not thread.task.inst.may_leave)
2579+
if thread.task.must_not_suspend():
2580+
return [SuspsendResult.COMPLETED]
25572581
event_code,_,_ = thread.task.yield_until(lambda: True, thread, cancellable)
25582582
match event_code:
25592583
case EventCode.NONE:

0 commit comments

Comments
 (0)