From 8d7cb435db6c16ac4ed543c647a4ec9b36ab5f01 Mon Sep 17 00:00:00 2001 From: Leaf Petersen Date: Tue, 4 Dec 2018 16:29:17 -0800 Subject: [PATCH 1/2] Add initial draft of static immutability --- .../feature-specification.md | 167 ++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 working/0125-static-immutability/feature-specification.md diff --git a/working/0125-static-immutability/feature-specification.md b/working/0125-static-immutability/feature-specification.md new file mode 100644 index 0000000000..52ced3acd4 --- /dev/null +++ b/working/0125-static-immutability/feature-specification.md @@ -0,0 +1,167 @@ +# Shared immutable objects + +leafp@google.com + +Status: Draft + +This describes a possible solution for: + - [Communication between isolates](https://github.com/dart-lang/language/issues/124) + - [Building immutable collections](https://github.com/dart-lang/language/issues/117) + - [Unwanted mutation of lists in Flutter](https://github.com/dart-lang/sdk/issues/27755) + +## Summary + +This describes a way to declare classes that produce deeply immutable object +graphs that are shared across isolates. + +## Syntax + +We add a section to class headers for expressing class and generic constraints, +along with an "immutable" constraint. + +```dart +class Value extends Scalar implements Constant is immutable + where T is immutable { +} +``` + +Mixin declarations may also be marked `immutable`. + +Generic method headers may also express generic constraints. + +``` +foo(Value v) { + +} +``` + +### Alternative syntax + +Instead of adding constraints, a simpler approach is to add a marker interface +`Immutable`. The property expressed by the constraint `T is immutable` then +becomes expressed by `implements Immutable` in the case of a class, or `T +extends Immutable` in the case of a type variable `T`. + +## Static checking +A class marked with `immutable` is subject to the following additional static +checks. + +- Every field in an immutable class (including any superclass fields) must be + final. +- Every field in an immutable class (including any superclass fields) must have + a static type which is immutable. +- Every other class which implements the interface of an immutable class + (including via extension or mixing in) must also be immutable. + +The types `int`, `double`, `bool`, `String`, `Type`, and `Symbol` are considered +immutable. + +## Allocation of immutable objects + +Immutable objects are allocated as usual in an isolate local +nursery. (Alternatively, it might be preferable to maintain a separate isolate +local shared object nursery for allocating only shared objects). However, when +they are tenured, they are tenured to a global heap which is shared by all +isolates in the process, and which is inhabited solely by immutable shared +objects. + +The shared object heap cannot have pointers into the isolate local heaps, and so +garbage collection of an isolate local heap does not require coordination with +other isolates. + +The isolate local heap can have pointers into the shared global heap, and so +either these must be tracked via write barriers and treated as roots when +collecting the shared global heap, or else collection of the shared global heap +might require cross-isolate coordination. + +Tenuring objects into the shared global heap requires locking or pausing +isolates. Bulk reservation of allocation regions could potentially be used to +mitigate this. + +Issue: It is possible that a large object may need to be tenured before it has +been fully initialized. This would allow writes into the shared heap. This +should not be problematic semantically since the object cannot be visible in +other isolates prior to initialization, but it may complicate the GC model. +This does not seem deeply problematic - a number of solutions seem plausible. + +## Sharing of immutable objects + +The SendPort class is extended with a new method `void share(T message)` which given a reference to an immutable object graph, +shares that reference with all receivers of the SentPort. Note that the object +is not copied since it and all sub-components of it are in the shared heap. + +An object which is shared before it has been tenured will likely need to be +tenured when it is shared. + +It should be the case that every object is fully initialized before it can be +shared. The intent of the static checks specified above are to guarantee this. + +It should be the case that no object that has been shared can be mutated. The +intent of the static checks specified above are to guarantee this. + +## Immutable collections + +The following additional immutable classes are added to the core libraries: +`ImmutableList` which implements `List`, `ImmutableMap` which implements `Map`, +and `ImmutableSet` which implements `Set`. + +### Collection initialization +Instances of these collections may be allocated and assigned to local variables +in a modifiable state. Mutation operations may be performed on such an instance +up until the first point at which the instance escapes (that is, is captured by +a closure, is assigned to another variable or setter, or is passed as a +parameter). It is a static error if a mutation operation is performed on an +instance of one of these classes: + - at any point not intra-procedurally dominated by the allocation point of the + instance + - at any point where the instance escapes along any path from the allocation + point to the mutation operation. + +Instances that are allocated to initialize fields or top level variables are +always initialized in an umodifiable state. + +### Runtime immutability +As with the result of the current `List.unmodifiable` constructor, mutation +operations on an instance of an immutable collection shall throw (except in the +limited cases described in the initialization section above). Note that the +static checks described above prevent mutation operations from being accessed on +an instance of immutable type. However, the immutable collections implement +their mutable interfaces, and hence the mutation operations may be reached by +subsuming into the mutable type. + +### Literals + +A collection literal which appears in a context where the static type required +by the context is an immutable collection type shall be allocated as an +immutable collection. + +``` +ImmutableList l = [ 3 ]; +``` +Question: Do we need additional syntax for the case where a static type context +is not required? + +``` + var l = ^[3]; +``` + +## Immutable functions + +There is no way to describe the type of an immutable function. If important, we +could add a type for immutable closures. A function is immutable if every free +variable of the function is immutable. + +## Immutable top type + +There is no top type for immutable types. It might be useful to have a type +`Immutable`, to express the type of fields of immutable objects which are +intended to hold instances of multiple types which do not otherwise share a +common super-interface. + +## Javascript + +Currently, isolates are not supported in Javascript. If we revisit that, we are +unlikely to be able to support this in full on the web. It is possible that we +may be able to define a subset of immutable objects which can be implemented as +a layer over shared typed data buffers. From e92316539dde24ddf6b667b4c26e3428af6fecef Mon Sep 17 00:00:00 2001 From: Leaf Petersen Date: Thu, 6 Dec 2018 12:47:45 -0800 Subject: [PATCH 2/2] Add some notes reflecting feedback --- .../feature-specification.md | 69 ++++++++++++++++++- 1 file changed, 66 insertions(+), 3 deletions(-) diff --git a/working/0125-static-immutability/feature-specification.md b/working/0125-static-immutability/feature-specification.md index 52ced3acd4..2c8db3086f 100644 --- a/working/0125-static-immutability/feature-specification.md +++ b/working/0125-static-immutability/feature-specification.md @@ -35,13 +35,28 @@ foo(Value v) { } ``` -### Alternative syntax +### Alternative syntax 1 Instead of adding constraints, a simpler approach is to add a marker interface `Immutable`. The property expressed by the constraint `T is immutable` then becomes expressed by `implements Immutable` in the case of a class, or `T extends Immutable` in the case of a type variable `T`. +### Alternative syntax 2 + +Instead of adding general constraints, we could expose a dedicated syntax. For +example, this proposal from @yjbanov. + +```dart +data Value extends Scalar { +} + +foo(Value v) { +} + +``` + + ## Static checking A class marked with `immutable` is subject to the following additional static checks. @@ -56,6 +71,14 @@ checks. The types `int`, `double`, `bool`, `String`, `Type`, and `Symbol` are considered immutable. +## Generated methods + +We may wish to consider automatically generating hashCode and equality methods +for immutable classes (possibly with caching of hashCode). + +We may wish to consider automatically generating functional update methods (or +providing some other form of functional update). + ## Allocation of immutable objects Immutable objects are allocated as usual in an isolate local @@ -121,6 +144,16 @@ instance of one of these classes: Instances that are allocated to initialize fields or top level variables are always initialized in an umodifiable state. +Question: Is this functionality needed? With spread collections, many patterns +will be expressible directly as a literal. + +Question: Is this sufficient? The analysis as specified is brittle: you cannot +factor out initialization code into a different scope from the allocation. We +could add type level support for tracking uninitialized instances, but this +raises the footprint of this feature substantially. + +Qustion: Should this functionality be extended to user classes? + ### Runtime immutability As with the result of the current `List.unmodifiable` constructor, mutation operations on an instance of an immutable collection shall throw (except in the @@ -146,11 +179,38 @@ is not required? var l = ^[3]; ``` +### Alternative collection approach + +Instead of making `ImmutableList` a subtype of `List`, we could make it either +an unrelated type, or a supertype of `List`. + +#### `ImmutableList` is a supertype +If `ImmutableList` is a supertype of `List`, then immutability is no longer type +based. If we wish to enforce deep immutability, then there would need to be +runtime checks during initialization, which may be expensive (particularly in +the case of collections). Alternatively, we could simply not enforce deep +immutability statically, and instead dynamically traverse an object grap before +sharing it to check for immutability. This is expensive, but perhaps marginally +less so than copying. + +Another downside of this approach is that existing APIs that take `Lists` but +only read them cannot be re-used with an `ImmutableList`. A wrapper can help +with this. + +A benefit of this is that changing APIs (especially Flutter APIs) to take +`ImmutableList` as an argument would be non-breaking. + +#### `ImmutableList` is an unrelated type + +If `ImmutableList` is unrelated to `List`, then we have the same issue with +re-using existing APIs. However, we retain all of the benefits of type based +immutability. + ## Immutable functions There is no way to describe the type of an immutable function. If important, we could add a type for immutable closures. A function is immutable if every free -variable of the function is immutable. +variable of the function is immutable. ## Immutable top type @@ -161,7 +221,10 @@ common super-interface. ## Javascript -Currently, isolates are not supported in Javascript. If we revisit that, we are +There are no issues with supporting immutable objects on the web, but the +ability to support communication between isolates is limited. Currently, +isolates are not supported at all in Javascript. If we revisit that, we are unlikely to be able to support this in full on the web. It is possible that we may be able to define a subset of immutable objects which can be implemented as a layer over shared typed data buffers. +