@@ -388,32 +388,43 @@ learn its own index by calling the [`thread.index`] built-in.
388388
389389A suspended thread (identified by thread-table index) can be resumed at some
390390nondeterministic point in future via the [ ` thread.resume-later ` ] built-in. In
391- contrast, the [ ` thread.yield-to ` ] built-in switches execution to the given
392- thread immediately, leaving the * calling* thread to be resumed at some
393- nondeterministic point in the future. Lastly, the [ ` thread.switch-to ` ]
394- built-in switches execution to the given thread immediately, like ` yield-to ` ,
395- but leaves the calling thread in the "suspended" state. These three functions
396- can be used to resume both newly-created threads as well as threads that
397- executed and then suspended.
398-
399- In addition to threads entering the suspended state via ` thread.new-indirect `
400- and ` thread.switch-to ` , threads can also explicitly suspend themselves via the
401- [ ` thread.suspend ` ] built-in. Thus, there are three ways a thread * enters* the
402- suspended state and three ways a thread * exits* the suspended state (with
403- ` thread.switch-to ` serving in both categories). Together, these 5 thread
404- built-ins support both the "green thread" [ use cases] ( #goals ) where Core
405- WebAssembly code running inside the component wants to fully control thread
406- scheduling (via ` thread.switch-to ` and ` thread.suspend ` ) as well as the "host
407- thread" use cases where the Core WebAssembly code wants to let the containing
408- runtime nondeterministically schedule threads (via ` thread.resume-later ` or
409- ` thread.yield-to ` ).
410-
411- Lastly, since threads are cooperative, there is a [ ` thread.yield ` ] built-in
412- that can be called in the middle of long-running computations to allow the
413- runtime to nondeterministically switch execution to another thread.
414- ` thread.yield ` is equivalent to (but obviously more efficient than) creating a
415- new thread with a no-op function (via ` thread.new-indirect ` ) and then yielding
416- to it (via ` thread.yield-to ` ).
391+ contrast, the [ ` thread.yield-then-resume ` ] built-in switches execution to the
392+ given thread immediately, leaving the * calling* thread to be resumed at some
393+ nondeterministic point in the future. Lastly, the [ ` thread.suspend-then-resume ` ]
394+ built-in switches execution to the given thread immediately, like
395+ ` thread.yield-then-resume ` , but leaves the calling thread in the "suspended"
396+ state. These three functions can be used to resume both newly-created threads as
397+ well as threads that executed and then suspended.
398+
399+ Threads can also explicitly put themselves in the "suspended" state without
400+ specifying the other thread to run by calling the [ ` thread.suspend ` ] built-in.
401+ This is useful if a thread needs to wait on some condition that will be met by
402+ some unknown thread in the future (which will resume the suspended thread).
403+ Similarly, threads can explicitly put themselves in the "ready to run" state
404+ without specifying the other thread to run by calling [ ` thread.yield ` ] . This is
405+ useful if a thread has a long-running computation without I/O but still needs to
406+ allow other cooperative threads to make progress concurrently.
407+
408+ Lastly, in addition to being able to switch to "suspended" threads, threads can
409+ also switch to threads that are in a "ready to run" state by calling the
410+ [ ` thread.suspend-then-promote ` ] and [ ` thread.yield-then-promote ` ] built-ins
411+ which, like the ` thread.{suspend,yield}-then-resume ` built-ins, leave the
412+ calling thread in a "suspended" or "ready to run" state, resp. While the calling
413+ thread * may* know that the target thread is ready to run (e.g., because the
414+ target thread yielded or is waiting on a future/stream operation that the
415+ calling thread just completed), in general readiness may depend on
416+ nondeterministic external I/O and the calling thread just wants to yield its
417+ timeslice to the target thread * if* it's ready. Thus, if the target thread
418+ is * not* "ready to run", these built-ins fall back to the behavior of the
419+ untargeted ` thread.{suspend,yield} ` built-ins.
420+
421+ Together, these thread built-ins support both the "green thread" [ use
422+ cases] ( #goals ) , where Core WebAssembly code running inside the component wants
423+ to fully control thread scheduling (via the suspending and resuming built-ins)
424+ as well as the "host thread" use cases where the Core WebAssembly code wants to
425+ let the containing runtime nondeterministically schedule threads (via yielding
426+ and promoting built-ins).
427+
417428
418429### Thread-Local Storage
419430
@@ -475,39 +486,56 @@ traditional synchronous OS syscalls or an asynchronous `io_uring`. However,
475486when the callee is implemented by another component, the Component Model
476487defines exactly what counts as "blocking".
477488
478- At a high level, there are six ways for a call to a component export to block,
479- all of which are described above or below in more detail:
480- * calling an ` async ` -typed function import using the sync ABI
489+ There are several ways for the [ task] ( #threads-and-tasks ) created by calling a
490+ component export to potentially block:
481491* suspending the current thread via the
482- [ ` thread.suspend ` ] ( #thread-built-ins ) built-in
492+ [ ` thread.suspend{,then-promote} ` ] ( #thread-built-ins ) built-ins
483493* cooperatively yielding (e.g., during a long-running computation) via the
484- [ ` thread.yield ` ] ( #thread-built-ins ) built-in
494+ [ ` thread.yield{,-then-promote} ` ] ( #thread-built-ins ) built-ins
485495* waiting for one of a set of concurrent operations to complete via the
486496 [ ` waitable-set.wait ` ] ( #waitables-and-waitable-sets ) built-in
487- * waiting for a stream or future operation to complete via the
497+ * synchronously waiting for a stream or future operation to complete via the
488498 [ ` {stream,future}.{,cancel-}{read,write} ` ] ( #streams-and-futures ) built-ins
489- * waiting for a subtask to cooperatively cancel itself via the
499+ * synchronously waiting for a subtask to cooperatively cancel itself via the
490500 [ ` subtask.cancel ` ] ( #cancellation ) built-in
501+ * synchronously calling an ` async ` function (which may transitively block)
502+
503+ At each of these points, the [ current thread] ( #current-thread-and-task ) may
504+ block and be suspended, depending on the operation and the state of the world.
505+ Thus, each of these represents ** cooperative yield points** .
506+
507+ If the [ current task] ( #current-thread-and-task ) has already returned its value
508+ to the caller, then control flow is transferred back to the caller, which can
509+ now use the return value that was written to memory via the ABI.
510+
511+ However, if the current task has * not* yet returned a value, the behavior
512+ depends on the * function type* of the callee:
513+
514+ If the callee's function type is ` async ` , then the task is also considered to
515+ "block". According to the async ABI, control flow is then transferred to the
516+ innermost caller using the async ABI (noting that there may be any number of
517+ sync ABI calls on the stack between the innermost async ABI caller and the
518+ blocking callee).
519+
520+
521+ If instead the callee's function type is * not* ` async ` , then
491522
492- At each of these points, the [ current thread] ( #current-thread-and-task ) will be
523+ "blocked" if there is some other thread in the same component instance that
524+ is in the [ "ready to run"] ( #thread-built-ins ) state. In this case, the runtime
525+ switches to another thread (picking nondeterministically if there are multiple
526+ ready threads) and continues doing so until either
527+
528+
529+ However, the
530+ operation
493531suspended. Execution transfers to a caller's thread if there is one, or
494532otherwise back to the runtime, which may invoke new component exports or
495- nondeterministically resume a cooperative thread that is ready to run. Thus,
496- each of these represents ** cooperative yield points** .
497-
498- Additionally, each of these potentially-blocking operations will trap if the
499- [ current task's function type] ( #current-thread-and-task ) does not declare the
500- ` async ` effect, since only ` async ` -typed functions are allowed to block. As an
501- exception, to allow it to be called arbitrarily from anywhere, ` thread.yield `
502- does not trap but instead behaves as a no-op if the current task's function
503- type does not contain ` async ` .
533+ nondeterministically resume a cooperative thread that is ready to run.
504534
505535"Blocking" is [ specified in terms of] stack-switching, with a ` block ` effect
506536that suspends the current thread to produce a continuation that can be resumed
507537once the reason for blocking is addressed.
508538
509- The [ Canonical ABI explainer] defines the above behavior more precisely; search
510- for ` may_block ` to see all the relevant points.
511539
512540### Waitables and Waitable Sets
513541
@@ -678,12 +706,12 @@ degree of concurrency than synchronous exports. Stackful async exports ignore
678706the lock entirely and thus achieve the highest degree of (cooperative)
679707concurrency.
680708
681- Since non-` async ` functions are not allowed to block (including due to
682- backpressure) and also don't pile up like ` async ` functions, non-` async `
683- functions ignore backpressure (explicit and implicit) entirely. If a
684- component exports a mix of ` async ` and non-` async ` functions, code generation
685- must therefore be prepared to handle non-` async ` functions executing at
686- any cooperative yield point, even in the middle of a ` callback ` .
709+ Since non-` async ` functions are not allowed to [ block] ( #blocking ) (including due
710+ to backpressure) and also don't pile up like ` async ` functions, non-` async `
711+ functions ignore backpressure (explicit and implicit) entirely. If a component
712+ exports a mix of ` async ` and non-` async ` functions, code generation must
713+ therefore be prepared to handle non-` async ` functions executing at any
714+ cooperative yield point, even in the middle of a ` callback ` .
687715
688716Once a task is allowed to start according to these backpressure rules, its
689717arguments are lowered into the callee's linear memory and the task is in
@@ -713,9 +741,7 @@ the readable end passed for `in`) and `stream.write`s (of the writable end it
713741` stream.new ` ed) before exiting the task.
714742
715743Once ` task.return ` is called, the task is in the "returned" state. Calling
716- ` task.return ` when not in the "started" state traps. Once in a "returned" state,
717- non-` async ` functions may block using cooperative threads that were created
718- before the synchronous task's implicit thread returned.
744+ ` task.return ` when not in the "started" state traps.
719745
720746### Borrows
721747
@@ -1538,13 +1564,15 @@ comes after:
15381564[ `waitable-set.wait` ] : Explainer.md#-waitable-setwait
15391565[ `waitable-set.poll` ] : Explainer.md#-waitable-setpoll
15401566[ `waitable.join` ] : Explainer.md#-waitablejoin
1541- [ `thread.new-indirect` ] : Explainer.md#-threadnew-indirect
15421567[ `thread.index` ] : Explainer.md#-threadindex
1543- [ `thread.suspend` ] : Explainer.md#-threadsuspend
1544- [ `thread.switch-to` ] : Explainer.md#-threadswitch-to
1568+ [ `thread.new-indirect` ] : Explainer.md#-threadnew-indirect
15451569[ `thread.resume-later` ] : Explainer.md#-threadresume-later
1546- [ `thread.yield-to ` ] : Explainer.md#-threadyield-to
1570+ [ `thread.suspend ` ] : Explainer.md#-threadsuspend
15471571[ `thread.yield` ] : Explainer.md#-threadyield
1572+ [ `thread.suspend-then-resume` ] : Explainer.md#-threadsuspend-then-resume
1573+ [ `thread.yield-then-resume` ] : Explainer.md#-threadyield-then-resume
1574+ [ `thread.suspend-then-promote` ] : Explainer.md#-threadsuspend-then-promote
1575+ [ `thread.yield-then-promote` ] : Explainer.md#-threadyield-then-promote
15481576[ `{stream,future}.new` ] : Explainer.md#-streamnew-and-futurenew
15491577[ `{stream,future}.{read,write}` ] : Explainer.md#-streamread-and-streamwrite
15501578[ `stream.cancel-write` ] : Explainer.md#-streamcancel-read-streamcancel-write-futurecancel-read-and-futurecancel-write
0 commit comments