Skip to content

Commit c268d15

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

File tree

5 files changed

+87
-93
lines changed

5 files changed

+87
-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: 30 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):
@@ -566,8 +567,13 @@ def trap_if_on_the_stack(self, inst):
566567
def needs_exclusive(self):
567568
return not self.opts.async_ or self.opts.callback
568569

570+
def must_not_suspend(self):
571+
return not self.ft.async_ and self.state != Task.State.RESOLVED
572+
569573
def enter(self, thread):
570574
assert(thread in self.threads and thread.task is self)
575+
if not self.ft.async_:
576+
return True
571577
def has_backpressure():
572578
return self.inst.backpressure > 0 or (self.needs_exclusive() and self.inst.exclusive)
573579
if has_backpressure() or self.inst.num_waiting_to_enter > 0:
@@ -584,6 +590,8 @@ def has_backpressure():
584590

585591
def exit(self):
586592
assert(len(self.threads) > 0)
593+
if not self.ft.async_:
594+
return
587595
if self.needs_exclusive():
588596
assert(self.inst.exclusive)
589597
self.inst.exclusive = False
@@ -2023,12 +2031,17 @@ def thread_func(thread):
20232031
inst.exclusive = False
20242032
match code:
20252033
case CallbackCode.YIELD:
2026-
event = task.yield_until(lambda: not inst.exclusive, thread, cancellable = True)
2034+
if thread.task.must_not_suspend():
2035+
event = (EventCode.NONE, 0, 0)
2036+
else:
2037+
event = task.yield_until(lambda: not inst.exclusive, thread, cancellable = True)
20272038
case CallbackCode.WAIT:
2039+
trap_if(thread.task.must_not_suspend())
20282040
wset = inst.table.get(si)
20292041
trap_if(not isinstance(wset, WaitableSet))
20302042
event = task.wait_until(lambda: not inst.exclusive, thread, wset, cancellable = True)
20312043
case CallbackCode.POLL:
2044+
trap_if(thread.task.must_not_suspend())
20322045
wset = inst.table.get(si)
20332046
trap_if(not isinstance(wset, WaitableSet))
20342047
event = task.poll_until(lambda: not inst.exclusive, thread, wset, cancellable = True)
@@ -2069,6 +2082,7 @@ def call_and_trap_on_throw(callee, thread, args):
20692082

20702083
def canon_lower(opts, ft, callee: FuncInst, thread, flat_args):
20712084
trap_if(not thread.task.inst.may_leave)
2085+
trap_if(ft.async_ and not opts.async_ and thread.task.must_not_suspend())
20722086
subtask = Subtask()
20732087
cx = LiftLowerContext(opts, thread.task.inst, subtask)
20742088

@@ -2108,6 +2122,7 @@ def on_resolve(result):
21082122
flat_results = lower_flat_values(cx, max_flat_results, result, ft.result_type(), flat_args)
21092123

21102124
subtask.callee = callee(thread.task, on_start, on_resolve)
2125+
assert(ft.async_ or subtask.resolved())
21112126

21122127
if not opts.async_:
21132128
if not subtask.resolved():
@@ -2142,31 +2157,30 @@ def canon_resource_new(rt, thread, rep):
21422157

21432158
### `canon resource.drop`
21442159

2145-
def canon_resource_drop(rt, async_, thread, i):
2160+
def canon_resource_drop(rt, thread, i):
21462161
trap_if(not thread.task.inst.may_leave)
21472162
inst = thread.task.inst
21482163
h = inst.table.remove(i)
21492164
trap_if(not isinstance(h, ResourceHandle))
21502165
trap_if(h.rt is not rt)
21512166
trap_if(h.num_lends != 0)
2152-
flat_results = [] if not async_ else [0]
21532167
if h.own:
21542168
assert(h.borrow_scope is None)
21552169
if inst is rt.impl:
21562170
if rt.dtor:
21572171
rt.dtor(h.rep)
21582172
else:
21592173
if rt.dtor:
2160-
caller_opts = CanonicalOptions(async_ = async_)
2174+
caller_opts = CanonicalOptions(async_ = False)
21612175
callee_opts = CanonicalOptions(async_ = rt.dtor_async, callback = rt.dtor_callback)
2162-
ft = FuncType([U32Type()],[])
2176+
ft = FuncType([U32Type()],[], async_ = False)
21632177
callee = partial(canon_lift, callee_opts, rt.impl, ft, rt.dtor)
2164-
flat_results = canon_lower(caller_opts, ft, callee, thread, [h.rep])
2178+
[] = canon_lower(caller_opts, ft, callee, thread, [h.rep])
21652179
else:
21662180
thread.task.trap_if_on_the_stack(rt.impl)
21672181
else:
21682182
h.borrow_scope.num_borrows -= 1
2169-
return flat_results
2183+
return []
21702184

21712185
### `canon resource.rep`
21722186

@@ -2244,6 +2258,7 @@ def canon_waitable_set_new(thread):
22442258

22452259
def canon_waitable_set_wait(cancellable, mem, thread, si, ptr):
22462260
trap_if(not thread.task.inst.may_leave)
2261+
trap_if(thread.task.must_not_suspend())
22472262
wset = thread.task.inst.table.get(si)
22482263
trap_if(not isinstance(wset, WaitableSet))
22492264
event = thread.task.wait_until(lambda: True, thread, wset, cancellable)
@@ -2260,6 +2275,7 @@ def unpack_event(mem, thread, ptr, e: EventTuple):
22602275

22612276
def canon_waitable_set_poll(cancellable, mem, thread, si, ptr):
22622277
trap_if(not thread.task.inst.may_leave)
2278+
trap_if(thread.task.must_not_suspend())
22632279
wset = thread.task.inst.table.get(si)
22642280
trap_if(not isinstance(wset, WaitableSet))
22652281
event = thread.task.poll_until(lambda: True, thread, wset, cancellable)
@@ -2294,6 +2310,7 @@ def canon_waitable_join(thread, wi, si):
22942310

22952311
def canon_subtask_cancel(async_, thread, i):
22962312
trap_if(not thread.task.inst.may_leave)
2313+
trap_if(not async_ and thread.task.must_not_suspend())
22972314
subtask = thread.task.inst.table.get(i)
22982315
trap_if(not isinstance(subtask, Subtask))
22992316
trap_if(subtask.resolve_delivered())
@@ -2350,6 +2367,7 @@ def canon_stream_write(stream_t, opts, thread, i, ptr, n):
23502367

23512368
def stream_copy(EndT, BufferT, event_code, stream_t, opts, thread, i, ptr, n):
23522369
trap_if(not thread.task.inst.may_leave)
2370+
trap_if(not opts.async_ and thread.task.must_not_suspend())
23532371
e = thread.task.inst.table.get(i)
23542372
trap_if(not isinstance(e, EndT))
23552373
trap_if(e.shared.t != stream_t.t)
@@ -2401,6 +2419,7 @@ def canon_future_write(future_t, opts, thread, i, ptr):
24012419

24022420
def future_copy(EndT, BufferT, event_code, future_t, opts, thread, i, ptr):
24032421
trap_if(not thread.task.inst.may_leave)
2422+
trap_if(not opts.async_ and thread.task.must_not_suspend())
24042423
e = thread.task.inst.table.get(i)
24052424
trap_if(not isinstance(e, EndT))
24062425
trap_if(e.shared.t != future_t.t)
@@ -2451,6 +2470,7 @@ def canon_future_cancel_write(future_t, async_, thread, i):
24512470

24522471
def cancel_copy(EndT, event_code, stream_or_future_t, async_, thread, i):
24532472
trap_if(not thread.task.inst.may_leave)
2473+
trap_if(not async_ and thread.task.must_not_suspend())
24542474
e = thread.task.inst.table.get(i)
24552475
trap_if(not isinstance(e, EndT))
24562476
trap_if(e.shared.t != stream_or_future_t.t)
@@ -2527,6 +2547,7 @@ def canon_thread_switch_to(cancellable, thread, i):
25272547

25282548
def canon_thread_suspend(cancellable, thread):
25292549
trap_if(not thread.task.inst.may_leave)
2550+
trap_if(thread.task.must_not_suspend())
25302551
suspend_result = thread.task.suspend(thread, cancellable)
25312552
return [suspend_result]
25322553

@@ -2554,6 +2575,8 @@ def canon_thread_yield_to(cancellable, thread, i):
25542575

25552576
def canon_thread_yield(cancellable, thread):
25562577
trap_if(not thread.task.inst.may_leave)
2578+
if thread.task.must_not_suspend():
2579+
return [SuspsendResult.COMPLETED]
25572580
event_code,_,_ = thread.task.yield_until(lambda: True, thread, cancellable)
25582581
match event_code:
25592582
case EventCode.NONE:

0 commit comments

Comments
 (0)