Modifies the serialization strategy used for dynamic modules. Emits 2 dill files, main.dill and main.opt.dill.
main.dill serialized before TFA runs and is used by the CFE to compile the dynamic module faster (it doesn't have to recompile the main module libraries from source).
main.opt.dill is serialized at the end of the compilation of the main module and contains a copy of all the main module libraries after TFA. This dill is used by dart2wasm to produce consistent references into the main module.
Also emit new record classes into a distinct library so that all downstream code will appropriately emit the new code without special logic.
Change-Id: Idf7a87d2014dad513c753330743f753312aa472f
Tested: Just updating signature, no testing needed.
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/422342
Reviewed-by: Martin Kustermann <kustermann@google.com>
In preparation of a merge with the Dart2Js format in a follow up PR.
- Treat default values as passed arguments - `foo(4)` is the same as
`foo()` for all intents and purposes for a `foo([int i = 4])`.
- Add signatures to lookup, as dart2js forgets them during
compilation.
- Encode non-constant arguments as `null`, while a `null` passed as an
argument is a `NullConstant`.
Tested:pkg/vm/test/transformations/record_use_test.dart
Change-Id: Ifd2597d8d1b979627fa45fd4736a762031c17216
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/415620
Commit-Queue: Moritz Sümmermann <mosum@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
Auto-Submit: Moritz Sümmermann <mosum@google.com>
Dynamic module validation now accepts reference to classes and members
from 'dart:*' libraries if they are annotated with
dyn-module:language-impl:* pragmas. This matches the behavior of
dynamic interface annotator.
This is a follow-up to
https://dart-review.googlesource.com/c/sdk/+/418663.
Also, when checking for possible dynamic overrides in TFA,
treat _enumToString from dart:core as a public name because
front-end desugars enums into classes which override
private _enumToString method in a different library.
TEST=pkg/dynamic_modules/test/data/enum
Fixes b/395992622
Change-Id: I45ed9855f806224a510fa4e56962607de3371eb0
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/423820
Reviewed-by: Slava Egorov <vegorov@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Nate Biggs <natebiggs@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
This change adds pragmas which can be used in dart:* libraries to
specify classes and members which are used by the compiler
to implement certain language features. Compiler can insert
references to those classes and members when desugaring language
constructs, so dynamic module code can use these classes and members
implicitly even if they are private.
Previously, we required to specify these members in the
dynamic_interface.yaml before dynamic modules could use them.
However, this is cumbersome and error-prone.
Now, the following pragmas are recognized:
@pragma('dyn-module:language-impl:extendable')
@pragma('dyn-module:language-impl:can-be-overridden')
@pragma('dyn-module:language-impl:callable')
If dynamic interface yaml file is specified during compilation,
then classes/members annotated with these pragmas are treated as if
they are used in corresponding sections of dynamic_interface.yaml.
If dynamic interface yaml file is not specified, these pragmas
are ignored.
Bug: b/395992622
TEST=existing
CoreLibraryReviewExempt: no API changes, only adding pragmas.
Change-Id: I6606467fc5e5264a2565d7eb8e8baccc2eebea84
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/418663
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Generated Record `==` methods begin by doing a type check on the argument. Today this is implemented as an interface subtype check like `other is <ThisRecordType>`. This generally ends up being a class ID check. For dynamic modules, the class ID is not statically known so the type test fails.
Instead for dynamic modules we can have the `==` do a full check against the RTI object using `_checkInstance`. This will correctly check the structure of the type rather than just the class ID.
TEST=N/A small signature change.
Change-Id: I86832d2bcdf0e95bd85267c016e5e69b002bcb68
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/416323
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
TFA tree shaker removes initializers of fields which cannot be accessed
but retained due to an entry point pragma / dynamic interface.
For late final fields with initializers that changes semantics as
late final fields without initializers have an implicit setter.
Such setter is not accounted in the kernel AST
(Field.setterReference == null and Field.hasSetter == false) and
it doesn't get an assigned selector id, which causes a crash in
dispatch table generator.
The change fixes this bug by retaining initializers for late final
fields. The initializer code is still replaced with
throw "Attempt to execute code removed by Dart AOT compiler (TFA)".
TEST=pkg/vm/testcases/transformations/type_flow/transformer/regress_b404559785.dart
Bug: b/404559785
Change-Id: Id998a715ea75a4768414997ce66bcdd1d4f19189
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/416720
Commit-Queue: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Slava Egorov <vegorov@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Depfiles escape backslashes with backslashes to be able to encode
spaces in paths. And they have backslashes on windows and forward
slashes on the other platforms.
Change the test to only check for the directory and file names, not
the path separators.
TEST=pkg/vm/test/kernel_front_end_test.dart
Change-Id: I15927d036fa07fe0a03a6d2340a241453f777794
Cq-Include-Trybots: luci.dart.try:pkg-win-release-try,pkg-win-release-arm64-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/398901
Reviewed-by: Derek Xu <derekx@google.com>
Commit-Queue: Daco Harkes <dacoharkes@google.com>
There is no need to restrict the recording to annotations.
It also opens up more interesting use cases, for example recording instances of `const StringAsset('id').load()` if the class `StringAsset` has the `RecordUse` annotation.
Also includes a small refactoring in a separate commit.
TESTED=pkg/vm/testcases/transformations/record_use/instance_not_annotation.dart
Change-Id: I262995760a8ba8bc14c9cf01e5eb1b47f1278282
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/387700
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Moritz Sümmermann <mosum@google.com>
- Rename resource identifiers in the VM to usage recordings.
- Use package:record_use for serialization.
- Rename and use the experimental flag for this feature.
- Recognize tear-offs and top-level methods as well.
Next steps:
- Add constant instance recording.
- Expose API in package:native_assets_cli's link callback.
TEST=pkg/vm/test/transformations/record_use_test.dart
Change-Id: I8af3625165f78925ae943711245af93a239d1012
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/383040
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Moritz Sümmermann <mosum@google.com>
Adds a `pragma('vm:entry-point')` to the synthesized library and makes
the tree-shakers respect that pragma on `library`s.
TEST=tests/ffi/native_assets/asset_relative_test.dart
Bug: Found in g3.
Change-Id: I1bc63b42ab867a5f1d2e7f9da4842de59f3d5c1d
Cq-Include-Trybots: dart/try:vm-aot-asan-linux-release-x64-try,vm-aot-linux-debug-x64-try,vm-aot-linux-debug-x64c-try,vm-aot-mac-release-arm64-try,vm-aot-mac-release-x64-try,vm-aot-msan-linux-release-x64-try,vm-aot-obfuscate-linux-release-x64-try,vm-aot-optimization-level-linux-release-x64-try,vm-aot-tsan-linux-release-x64-try,vm-aot-ubsan-linux-release-x64-try,vm-aot-win-debug-arm64-try,vm-aot-win-debug-x64-try,vm-aot-win-debug-x64c-try,vm-asan-linux-release-arm64-try,vm-msan-linux-release-arm64-try,vm-tsan-linux-release-arm64-try,vm-ubsan-linux-release-arm64-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/377442
Auto-Submit: Daco Harkes <dacoharkes@google.com>
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Commit-Queue: Daco Harkes <dacoharkes@google.com>
The loading units were taken from the definitions instead of the
references.
This CL fixes that and adds test cases.
Add some doc comments and references to TODOs.
TEST=pkg/vm/test/transformations/resource_identifier_test.dart
Change-Id: I59a640344206555ae1b190521ee3e36d52199700
Cq-Include-Trybots: dart/try:pkg-linux-debug-try,pkg-linux-release-arm64-try,pkg-mac-release-try,pkg-mac-release-arm64-try,pkg-win-release-try,pkg-win-release-arm64-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/364103
Commit-Queue: Daco Harkes <dacoharkes@google.com>
Reviewed-by: Moritz Sümmermann <mosum@google.com>
Removed 'sound null safety' flags from kernel service as
VM no longer supports unsound mode.
Also cleaned up unused 'original working directory' from messages
sent from the VM to kernel service.
TEST=ci
Change-Id: Idca5fc3276ea182d08ba8969aa494a8ad6b96b70
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/363141
Reviewed-by: Siva Annamalai <asiva@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
Canonical type erasure was used when running without sound null
safety. VM no longer supports unsound mode, so canonical type erasure
can be dropped.
This is a preparation to switching platform kernel file from
nnbd-agnostic mode to null-safe.
Also
* pkg/vm/test/modular_kernel_plus_aot_test.dart is migrated to
sound mode;
* test cases for unsound mode are removed from
pkg/dartdev/test/commands/compile_test.dart.
TEST=ci
Change-Id: I2ed0b84cffd650bbaecf4ee99f23da2fdc09de71
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/362162
Commit-Queue: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Siva Annamalai <asiva@google.com>
When testing for an empty type, TFA can take allocated classes into
account even if type is not specialized. This check is quick
and more precise.
As a result, TFA can eliminate certain calls when their argument types
don't have allocated subtypes. This is important for dart2wasm
as it doesn't handle interface calls without any dispatch targets.
TEST=co19/Language/Generics/scope_t05
Fixes https://github.com/dart-lang/sdk/issues/52873
Change-Id: Iad39e478df7882127f39164893455b68fdbf44ac
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/356306
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
The annotation takes metadata of type `Object?`, to allow for custom
data such as strings, ints, double, or bool. The parser, however,
assumed only strings.
Tested:pkg/vm/test/transformations/resource_identifier_test.dart
Change-Id: If813b2b0e5f002306bcfa6eb6195a666d1a72972
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/355922
Reviewed-by: Daco Harkes <dacoharkes@google.com>
Reviewed-by: Jake Macdonald <jakemac@google.com>
Commit-Queue: Moritz Sümmermann <mosum@google.com>
This is especially effective for collection / iterators / ...
in our corelib code. It may also benefit more devirtualization
opportunities.
class Foo<T> extends Bar with A<T>, B<T> ... {}
This results in around
* -0.99% in flutter_gallery_app_so_size armv8 flutter-release-sizeopt
* -0.35% in flute complex in wasm-O4
TEST=pkg/vm/test/transformations/mixin_deduplication_test
Change-Id: I8a48b8dc46d94240b79000b2b16797b4044ba330
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/356400
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
The modular parts in package:vm/modular/** will only depend on
* package:kernel
* package:front_end
=> Only for auto-generated error code messages
=> Those error codes can be their own dart_library() in g3
Based on https://dart-review.googlesource.com/c/sdk/+/353980, with
changes:
* Exclude package:kernel from "no exports" CFE presubmit.
* Add package:vm/modular to CFE "allowed deps".
* Fix import in `front_end/tool/perf_common.dart` and various others.
Tested: no code changes, only moves.
Change-Id: I9a44ac6ee05478812a9c8af31f6c4bbcf44b7c42
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/354221
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Morgan :) <davidmorgan@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
When the number of different invocations with the same selector is too
large (due to different arguments), TFA creates an approximate
invocation which uses raw (static) types of parameters.
A new approximate invocation was not queried in the cache and but
could match an existing invocation. This causes a problem as
_DependencyTracker keeps invocations as a Set<_Invocation>, so
duplicate approximate invocation may overwrite an existing
invocation in the dependency tracker, effectively breaking
the dependency chain and preventing invalidation, which could
cause incorrect analysis results and incorrect tree shaking.
This change fixes this bug by adding a lookup of the approximate
invocation in the cache, so duplicate invocation is not created.
Also, TFA heuristic thresholds are extracted to a separate
class to make TFA more configurable and allow writing unit tests
with approximations.
TEST=pkg/vm/testcases/transformations/type_flow/transformer/duplicate_approximate_invocation.dart
TEST=manually verified repro from b/322387862 without workaround.
Bug: b/322387862.
Change-Id: I1ff899f925d539b45f764d709e77c5800884ab4e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/349940
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
This is a reland of commit 66d3eedb33
g3 breakage was fixed in https://dart-review.googlesource.com/c/sdk/+/346940.
Original change's description:
> [vm/aot/tfa] Analyze function calls
>
> This change adds analysis of function calls when closure target
> of a call can be inferred. Local functions are now analyzed separately
> from enclosing members. Inferred result type of function calls is now
> used in the AOT compiler.
>
> Time of AOT compilation step 2 (TFA) on a large Flutter application:
> Before: 59.448s
> After: 61.870s (+4%)
>
> Maintaining hierarchy of function types and analysis of all function
> calls is not feasible as it causes unbearable increase in AOT
> compilation time.
TEST=existing
Issue: https://github.com/dart-lang/sdk/issues/39692
> Change-Id: Ieb4d5dce23868b5ab5c87fa1e77e49b85fd656fe
> Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/345083
> Reviewed-by: Slava Egorov <vegorov@google.com>
> Reviewed-by: Martin Kustermann <kustermann@google.com>
> Commit-Queue: Alexander Markov <alexmarkov@google.com>
Change-Id: I05377121ffa7e56748c8a5ab58551e2b1caef70b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/346946
Commit-Queue: Slava Egorov <vegorov@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
Reviewed-by: Slava Egorov <vegorov@google.com>
This reverts commit 66d3eedb33.
Reason for revert: breaks google3 (b/320642692)
Original change's description:
> [vm/aot/tfa] Analyze function calls
>
> This change adds analysis of function calls when closure target
> of a call can be inferred. Local functions are now analyzed separately
> from enclosing members. Inferred result type of function calls is now
> used in the AOT compiler.
>
> Time of AOT compilation step 2 (TFA) on a large Flutter application:
> Before: 59.448s
> After: 61.870s (+4%)
>
> Maintaining hierarchy of function types and analysis of all function
> calls is not feasible as it causes unbearable increase in AOT
> compilation time.
>
> TEST=existing
> Issue: https://github.com/dart-lang/sdk/issues/39692
>
> Change-Id: Ieb4d5dce23868b5ab5c87fa1e77e49b85fd656fe
> Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/345083
> Reviewed-by: Slava Egorov <vegorov@google.com>
> Reviewed-by: Martin Kustermann <kustermann@google.com>
> Commit-Queue: Alexander Markov <alexmarkov@google.com>
Issue: https://github.com/dart-lang/sdk/issues/39692
Change-Id: Ieb9104f4263e19ef9e5bd749e935f6c2dbec3046
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/346780
Reviewed-by: Martin Kustermann <kustermann@google.com>
Reviewed-by: Slava Egorov <vegorov@google.com>
Bot-Commit: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com>
Commit-Queue: Emmanuel Pellereau <emmanuelp@google.com>
This change adds analysis of function calls when closure target
of a call can be inferred. Local functions are now analyzed separately
from enclosing members. Inferred result type of function calls is now
used in the AOT compiler.
Time of AOT compilation step 2 (TFA) on a large Flutter application:
Before: 59.448s
After: 61.870s (+4%)
Maintaining hierarchy of function types and analysis of all function
calls is not feasible as it causes unbearable increase in AOT
compilation time.
TEST=existing
Issue: https://github.com/dart-lang/sdk/issues/39692
Change-Id: Ieb4d5dce23868b5ab5c87fa1e77e49b85fd656fe
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/345083
Reviewed-by: Slava Egorov <vegorov@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
If a static field or getter is annotated with
@pragma("vm:platform-const-if", <cond>) and <cond> const evaluates
to true, then uses of the static field or getter are const evaluated
when a target operating system is available. If <cond> const evaluates
to any other value, then the annotation is ignored.
For example, when runtime-only code is guarded like the following,
using Flutter's kDebugMode constant, then debug mode Flutter programs
can alter the defaultTargetPlatform for testing, but in release mode,
the defaultTargetPlatform getter is const evaluated and code guarded
with defaultTargetPlatform checks can be eliminated if unreachable:
@pragma("vm:platform-const-if", !kDebugMode)
TargetPlatform get defaultTargetPlatform {
...
assert(() {
if (Platform.environment.containsKey('FLUTTER_TEST')) {
result = TestPlatform.android;
}
return true;
}());
if (kDebugMode &&
platform.debugDefaultTargetPlatformOverride != null) {
result = platform.debugDefaultTargetPlatformOverride;
}
...
}
TEST=pkg/vm/test/transformations/vm_constant_evaluator
Change-Id: I55b88502a908c56cf42a761dd06741f15c8a23d9
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/333220
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Chloe Stefantsova <cstefantsova@google.com>
Commit-Queue: Tess Strickland <sstrickl@google.com>
This is a reland of commit 262311772b
When running with sound null safety, reducing a logical expression to
the right hand side if the left hand side is constant and does not short
circuit is valid, as the right hand side is guaranteed not to evaluate
to null.
In other modes, however, the right hand side may evaluate to null.
Thus, in thos modes, we return the original node if the RHS is not a
constant boolean value, so that the operator can perform whatever null
checking is required.
Fixes: https://github.com/dart-lang/sdk/issues/54029
Original change's description:
> Reland "[pkg/vm] Handle switch statements in unreachable code eliminator."
>
> This is a reland of commit 92bf76d9e8
>
> In the original CL, the changes to the UCE assumed all
> SwitchStatements were exhaustive. Now if a SwitchStatement isn't
> explicitly or implicitly (via a default case) exhaustive and no case
> matches the tested constant, the SwitchStatement is properly removed.
>
> In addition, if a guaranteed to match case is found, more than one
> cases remain (thus the SwitchStatement is not removed or replaced with
> the single case's body), and the default case is removed, then the
> resulting SwitchStatement is marked as explicitly exhaustive, as this
> serves as a signal to backends that they do not need to handle the
> possibility of no case matching in the absence of a default case.
>
> Original change's description:
> > [pkg/vm] Handle switch statements in unreachable code eliminator.
> >
> > Namely, if the tested expression for a switch statement is constant,
> > then we can remove any constant cases where the constants differ,
> > and if all but a single case is removed, we can replace the switch
> > with the case body.
> >
> > If constant functions are not enabled, then getters annotated with
> > @pragma("vm:platform-const") are still evaluated with the constant
> > function evaluation machinery, but only those and no others (including
> > any functions called within an annotated getter). This way, functions
> > can be annotated with @pragma("vm:platform-const") without having to
> > rewrite them to be a single returned expression.
> >
> > TEST=pkg/vm/test/transformations/unreachable_code_elimination
> > pkg/vm/test/transformations/vm_constant_evaluator
> >
> > Issue: https://github.com/dart-lang/sdk/issues/50473
> > Issue: https://github.com/dart-lang/sdk/issues/31969
> > Change-Id: Ie290d2f1f469326238d66c3d9631f8e696685ff0
> > Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/332760
> > Commit-Queue: Tess Strickland <sstrickl@google.com>
> > Reviewed-by: Chloe Stefantsova <cstefantsova@google.com>
> > Reviewed-by: Alexander Markov <alexmarkov@google.com>
>
> TEST=pkg/vm/test/transformations/unreachable_code_elimination
> pkg/vm/test/transformations/vm_constant_evaluator
>
> Issue: https://github.com/dart-lang/sdk/issues/50473
> Issue: https://github.com/dart-lang/sdk/issues/31969
> Cq-Include-Trybots: luci.dart.try:vm-aot-linux-release-x64-try,vm-aot-mac-release-arm64-try
> Change-Id: I557ca933808012e670e306f2d880221a0d7dd670
> Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/334224
> Reviewed-by: Chloe Stefantsova <cstefantsova@google.com>
> Reviewed-by: Alexander Markov <alexmarkov@google.com>
> Commit-Queue: Tess Strickland <sstrickl@google.com>
TEST=pkg/vm/test/transformations/unreachable_code_elimination
pkg/vm/test/transformations/vm_constant_evaluator
Change-Id: Ia51b7c5f3b51f57a6a306551fe74b47e0cba3c23
Cq-Include-Trybots: luci.dart.try:vm-aot-linux-release-x64-try,vm-aot-mac-release-arm64-try,vm-kernel-precomp-linux-release-x64-try,vm-kernel-linux-release-x64-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/335828
Reviewed-by: Chloe Stefantsova <cstefantsova@google.com>
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Commit-Queue: Tess Strickland <sstrickl@google.com>
vm.
This identifies all calls to static methods annotated with
`@ResourceIdentifier`, collects the constant arguments passed to the
method, and writes the results into a file.
The purpose of this feature is to be able to pass the recorded
information to packages in a post-compilation step, allowing them to
remove or modify assets based on the actual usage in the code prior to
bundling in the final application.
Tested: pkg/vm/test/transformations/resource_identifier_test.dart
Change-Id: I58cb313b66ee23c1d154dcc242547723a1ced359
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/329961
Commit-Queue: Moritz Sümmermann <mosum@google.com>
Reviewed-by: Nate Bosch <nbosch@google.com>
This CL passes the offset and uri of the file (here called a script uri
as opposed to a library uri, the two will be different if we're in a
part) when doing expression compilation.
This CL only passes the data, but doesn't actually use it.
Future CL(s) will use this data to calculate the static type of
available variables which is needed for an upcomming feature.
TEST=Existing tests.
CoreLibraryReviewExempt: Not changing SDK APIs.
Change-Id: I67ead461ab4bb9341424e693946f3e4afe35ce92
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/329322
Commit-Queue: Jens Johansen <jensj@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Alexander Markov <alexmarkov@google.com>