@@ -26,9 +26,11 @@ Porting this code to Dart using `dart:ffi` is currently impossible, as FFI only
2626supports two specific callback types:
2727
2828- [`NativeCallable.isolateLocal`][native-callable-isolate-local]: native caller
29- must have an exclusive access to an isolate in which callback was created.
30- This type of callback works if Dart calls C and C calls back into Dart
31- synchronously. It also works if caller uses VM C API for entering isolates
29+ must have an exclusive access to an isolate in which callback was created - or
30+ isolate must be pinned (owned) by a thread which invokes the callback. In the
31+ later case FFI trampoline will take care of entering the target isolate even
32+ if needed. This type of callback works if Dart calls C and C calls back into
33+ Dart synchronously. It also works if caller uses VM C API for entering isolates
3234 (e.g.`Dart_EnterIsolate`/`Dart_ExitIsolate`).
3335- [`NativeCallable.listener`][native-callable-listener]: native caller
3436 effectively sends a message to the isolate which created the callback and does
@@ -167,59 +169,65 @@ classes are also deeply immutable: `SendPort`, `Capability`, `RegExp`,
167169
168170All compile time constants are deeply immutable instances.
169171
170- `TypedData` and `Struct` instances are deeply immutable when backed by native
171- (external) memory.
172+ `TypedData` and `Struct` instances are considered deeply immutable.
173+
174+ > [!IMPORTANT]
175+ >
176+ > There is a consideration here that not all `TypedData` instances can be
177+ > shared on the Web, where there is separation between `ArrayBuffer` and
178+ > `SharedArrayBuffer` exists.
179+
180+ [deeply immutable]: https://github.com/dart-lang/sdk/blob/bb59b5c72c52369e1b0d21940008c4be7e6d43b3/runtime/docs/deeply_immutable.md
181+
172182
173183Unmodifiable lists (`List.unmodifiable`) which contain deeply immutable
174184instances are deeply immutable.
175185
176- Closures which capture only `final` variables containing deeply immutable
177- instances are deeply immutable.
186+ Closures which capture only `final`, non-`late` variables containing deeply
187+ immutable instances are deeply immutable.
178188
179- Finally, instances of classes annotated with `@pragma('vm:deeply-immutable')`
180- are deeply immutable. It is a compile error if classes annotated with this
181- pragma contain non-`final` fields. It is an compile time error if static
182- type of field within annotated class excludes deeply immutable instances.
183- If the static type of a field in a deeply immutable class is not
184- deeply immutable type - then compiler must insert checks in the constructor to
185- guarantee that this field is initialized to a deeply immutable value.
189+ Finally, instances of classes _marked as deeply immutable_ by being annotated
190+ with `@pragma('vm:deeply-immutable')` are deeply immutable. For any class
191+ which is marked as deeply immutable it is a compile time error if:
186192
187- > [!IMPORTANT]
188- >
189- > **TODO** should we allow sharing of all `TypedData` (and by extension
190- > `Struct`) objects? This seems very convenient. There is a consideration
191- > here that not all `TypedData` instances can be shared on the Web, where
192- > there is separation between `ArrayBuffer` and `SharedArrayBuffer` exists.
193+ * a subclass of such class which is not itself marked as deeply immutable.
194+ * a superclass of such class is not `Object` or a class itself marked as
195+ deeply immutable.
196+ * such class contains contains non-`final` or `late final` instance variables.
193197
194- [deeply immutable]: https://github.com/dart-lang/sdk/blob/bb59b5c72c52369e1b0d21940008c4be7e6d43b3/runtime/docs/deeply_immutable.md
198+ Compiler must ensure that instance variables in deeply immutable instances
199+ are initialized with deeply immutable values. If this can't be guarateed
200+ statically then compiler must insert appropriate checks into the constructor
201+ to guarantee this invariant.
195202
196- ### Shared fields and variables (`@pragma('vm:shared')`).
203+ ### Shared variables (`@pragma('vm:shared')`).
197204
198- Static fields and global variables annotated with `@pragma('vm:shared')` are
205+ Static and global variables annotated with `@pragma('vm:shared')` are
199206shared across all isolates in the isolate group.
200207
201- A field or variable annotated with `@pragma('vm:shared')` can only contain
202- values which are deeply immutable objects.
208+ A variable annotated with `@pragma('vm:shared')` can only contain values
209+ which are deeply immutable objects.
203210
204- * It is a compile time error to annotate a field or variable the static type of
211+ * It is a compile time error to annotate a variable the static type of
205212 which excludes deeply immutable objects;
206- * If static type of a field is a super-type for both deeply immutable and
213+ * If static type of a variable is a super-type for both deeply immutable and
207214 non-deeply immutable objects then compiler will insert a runtime check
208- which ensures that values assigned to such field are deeply immutable.
209- * A field or variable annotated with `@pragma('vm:shared')` must be `final`.
215+ which ensures that values assigned to such variable are deeply immutable.
216+ * A variable annotated with `@pragma('vm:shared')` must be `final` and
217+ non-`late`.
210218
211219> [!NOTE]
212220>
213- > Restrictions imposed above are the same as ones imposed on field in deeply
214- > immutable classes.
221+ > Restrictions imposed above are the same as ones imposed on instance
222+ > variables in deeply immutable classes.
215223
216- Shared fields must guarantee atomic initialization: if multiple threads
217- access the same uninitialized field then only one thread will invoke the
218- initializer and initialize the field , all other threads will block until
219- initialization is complete.
224+ Shared static and global variables must guarantee atomic initialization: if
225+ multiple threads access the same uninitialized variable then only one
226+ thread will invoke the initializer and initialize the variable , all other
227+ threads will block until initialization is complete.
220228
221229Outside of initialization we however do **not** require strong (e.g.
222- sequentially consistent) atomicity when reading or writing shared fields .
230+ sequentially consistent) atomicity when reading or writing shared variables .
223231We only require that no thread can ever observe a partially initialized Dart
224232object. See [Memory Model](#memory-model) for more details.
225233
@@ -230,7 +238,7 @@ Today Dart runtime always executes Dart code within a specific isolate.
230238within specific _isolate group_ but outside of a specific isolate. When Dart
231239code is executed in such a way it can only access static state which is shared
232240between isolates (`@pragma('vm:shared')`) and attempts to access isolated state
233- will cause `FieldAccessError ` to be thrown.
241+ will cause `AccessError ` to be thrown.
234242
235243```dart
236244 /// Constructs a [NativeCallable] that can be invoked from any thread.
@@ -239,8 +247,8 @@ will cause `FieldAccessError` to be thrown.
239247 /// the [callback] will be executed within the isolate group
240248 /// of the [Isolate] which originally constructed the callable.
241249 /// Specifically, this means that an attempt to access any
242- /// static or global field which is not shared between
243- /// isolates in a group will result in a [FieldAccessError ].
250+ /// static or global variable which is not shared between
251+ /// isolates in a group will result in a [AccessError ].
244252 ///
245253 /// If an exception is thrown by the [callback], the
246254 /// native function will return the `exceptionalReturn`,
@@ -305,22 +313,115 @@ class Isolate {
305313 /// Throws [TimeoutException] if [timeout] has been reached while waiting
306314 /// to acquire exclusive access to the isolate.
307315 ///
308- /// Throws [StateError] if target isolate is owned by another thread and
309- /// thus can't be entered from a different thread.
316+ /// Throws an error if target isolate is pinned to another thread and
317+ /// thus can't be entered from this threadn. See [pinToCurrentThread] and
318+ /// [isPinnedToCurrentThread].
319+ ///
320+ /// Throws an error if the target isolate belongs to another
321+ /// isolate group.
322+ ///
323+ /// Throws an error if [f] is not deeply immutable.
324+ ///
325+ /// Throws an error if result returned by [f] is not deeply immutable.
326+ external R runSync<R>(R Function() f, {Duration? timeout});
327+
328+ /// Create a new isolate in the current isolate group.
329+ ///
330+ /// Similar to `Dart_CreateIsolateInGroup` Dart VM C API.
331+ ///
332+ /// The isolate has been created, but its event loop is not running.
333+ ///
334+ /// To start processing isolate's messages:
335+ ///
336+ /// * start isolate's event loop synchronously on the current thread
337+ /// by calling [Isolate.runEventLoopSync]
338+ /// * integrate isolate's event loop with an external event loop by
339+ /// registering event callback ([Isolate.onEvent]) to forward
340+ /// event notifications to an external event loop and then draining
341+ /// pending events ([Isolate.handleEvent]) from that event loop.
342+ external static Isolate create({String? debugName});
343+
344+ /// Shut down target isolate.
345+ ///
346+ /// Shutting down the isolate stops its event loop without processing
347+ /// any pending messages and closes all open receive ports owned by the
348+ /// isolate.
349+ ///
350+ /// This function will block until it acquires exclusive access to the
351+ /// target isolate. Isolate can only be entered for synchronous execution
352+ /// between turns of its event loop, when no other thread is
353+ /// executing code in the target isolate.
354+ external void shutDown();
355+
356+ /// Pin current isolate to the current OS thread.
357+ ///
358+ /// Once an isolate is pinned to an OS thread it cannot be
359+ /// entered by any other OS thread. An attempt to acquire
360+ /// exclusive access to it from another thread will fail with
361+ /// an error.
310362 ///
311- /// Throws [ArgumentError] if [f] is not deeply immutable .
363+ /// Equivalent to `Dart_SetCurrentThreadOwnsIsolate` Dart VM C API .
312364 ///
313- /// Throws [StateError] if result returned by [f] is not deeply immutable.
314- R runSync<R>(R Function() f, {Duration? timeout});
365+ /// Returns `true` on success and `false` otherwise (e.g. if target isolate
366+ /// is already pinned to another thread).
367+ external static bool pinToThread();
368+
369+ /// Whether the isolate is pinned to the current OS thread.
370+ ///
371+ /// Equivalent to `Dart_GetCurrentThreadOwnsIsolate` Dart VM C API.
372+ external bool get isPinnedToCurrentThread;
373+
374+ /// Run event loop for the target isolate synchronously on the current thread.
375+ ///
376+ /// This function will block until it acquires exclusive access to the
377+ /// target isolate. Isolate can only be entered for synchronous execution
378+ /// between turns of its event loop, when no other thread is
379+ /// executing code in the target isolate.
380+ ///
381+ /// This function will return once the isolate has no open keep-alive
382+ /// receive ports.
383+ ///
384+ /// The isolate will be marked as pinned to the current thread.
385+ ///
386+ /// Similar to `Dart_RunLoop` Dart VM C API, but unlike `Dart_RunLoop` this
387+ /// function executes isolate's event loop on the current thread instead
388+ /// of delegating it into the thread-pool.
389+ ///
390+ /// Throws an error if target isolate is pinned to another thread or already
391+ /// has an event loop running.
392+ external void runEventLoopSync();
393+
394+ /// Event notify callback for the isolate.
395+ ///
396+ /// Provided callback will be called once for every new event which isolate
397+ /// needs to react to. Pending events can be then later be drained
398+ /// by calling [Isolate.handleEvent].
399+ ///
400+ /// Provided [callback] must be deeply immutable and will be called
401+ /// on an arbitrary thread and not necessarily within any isolate. See
402+ /// [NativeCallable.isolateGroupBound].
403+ ///
404+ /// IMPORTANT: [Isolate.handleEvent] *MUST NOT* be called from the
405+ /// `callback` as this will cause a dead-locks of the Dart execution
406+ /// environment.
407+ ///
408+ /// Similar to `Dart_SetMessageNotifyCallback` Dart VM C API.
409+ external void set onEvent(void Function(Isolate) callback);
410+
411+ /// Handle at most one pending event for the isolate.
412+ ///
413+ /// This function does nothing if there are no pending events.
414+ ///
415+ /// This function will block until it acquires exclusive access to the
416+ /// target isolate. Isolate can only be entered for synchronous execution
417+ /// between turns of its event loop, when no other thread is
418+ /// executing code in the target isolate.
419+ ///
420+ /// Similar to `Dart_HandleMessage` Dart VM C API.
421+ external void handleEvent();
315422}
316423```
317424
318- ** TODO** : Furthermore we might want to facilitate integration with third-party
319- event-loops: e.g. allow to create isolate without scheduling its event loop on
320- our own thread pool and provide equivalents of ` Dart_SetMessageNotifyCallback `
321- and ` Dart_HandleMessage ` . Though maybe we should not bundle this all together
322- into one update.
323-
324425### Scoped thread local values
325426
326427``` dart
@@ -339,11 +440,11 @@ final class ScopedThreadLocal<T> {
339440 /// If this [ScopedThreadLocal] was uninitialized then it will be reset to this state
340441 /// when execution of [f] completes.
341442 ///
342- /// Throws [StateError] if this [ScopedThreadLocal] does not have an initializer.
443+ /// Throws an error if this [ScopedThreadLocal] does not have an initializer.
343444 external void runInitialized<R>(R Function(T) f);
344445
345446 /// Returns the value specified by the closest enclosing invocation of [with] or
346- /// throws [StateError] if this [ScopedThreadLocal] is not bound to a value.
447+ /// throws an error if this [ScopedThreadLocal] is not bound to a value.
347448 external T get value;
348449
349450 /// Returns whether this [ScopedThreadLocal] is bound to a value.
@@ -529,10 +630,10 @@ final class Foo implements Struct {
529630> [ !CAUTION]
530631>
531632> Support for ` AtomicInt ` in FFI structs is meant to enable atomic access to
532- > fields without requiring developers to go through ` Pointer ` based atomic APIs.
533- > It is ** not** meant as a way to interoperate with structs that contain
534- > ` std::atomic<int32_t> ` (C++) or ` _Atomic int32_t ` (C11) because these types
535- > don't have a defined ABI.
633+ > instance variables without requiring developers to go through ` Pointer ` based
634+ > atomic APIs. It is ** not** meant as a way to interoperate with structs that
635+ > contain ` std::atomic<int32_t> ` (C++) or ` _Atomic int32_t ` (C11) because these
636+ > types don't have a defined ABI.
536637
537638### Memory Model
538639
779880\forall i\leq j . \mathtt{Rel}(l, i) \leq_\mathtt{asw} \mathtt{Acq}(l, j)
780881$$
781882
782- ##### Shared fields
883+ ##### Shared instance variables
783884
784- There can only be a single initializing store for any shared field. All other
785- accesses are _ not_ required to be atomic. However per definition of
885+ There can only be a single initializing store for any shared instance variable.
886+ All other accesses are _ not_ required to be atomic. However per definition of
786887$\leq_ \mathtt{hb}$ relation all initializing stores happen-before other accesses
787888to the overlapping locations. This means that if one thread creates an object
788- and publishes it to another thread via a shared field - another thread can't
789- observe object in partially initialized state. Implementations can choose to
790- guarantee this property by inserting appropriate barriers when creating objects,
791- however that would be a waste for objects that are mostly used in an
792- isolate-local manner. Instead, given current restriction that only
793- deeply immutable objects can be placed into shared-fields
794- implementations can instead choose to implement shared fields using
889+ and publishes it to another thread via a shared instance variable - another
890+ thread can't observe object in partially initialized state. Implementations can
891+ choose to guarantee this property by inserting appropriate barriers when
892+ creating objects, however that would be a waste for objects that are mostly
893+ used in an isolate-local manner. Instead, given current restriction that only
894+ deeply immutable objects can be placed into shared instance variables
895+ implementations can instead choose to implement shared instance variables using
795896_ store-release_ and _ load-acquire_ atomic operations. This would guarantee
796897happens-before ordering for initializing stores. We however do not _ require_
797898such implementation and consequently developers can't rely on this in their
0 commit comments