Skip to content

Commit

Permalink
[vm] Specialize List.generate factory constructor invocations
Browse files Browse the repository at this point in the history
List.generate could return both growable and fixed-size lists.
This change specializes invocations of List.generate when value of
'growable' argument is known (constant or omitted), so it becomes
possible to infer actual type returned by the factory.

This becomes more important with null safety as List.generate is used
more often to initialize lists of non-nullable elements.

Migrated NNBD benchmarks in AOT mode on x64:
Sudoku +11%
DartMicroBenchMM.{Min,Max}Lib +11-13%
DartMicroBenchMM.{Min,Max}Code +19-27%
ForInGeneratedLoop +19%
ForEachLoop +85%
ForInLoop +64%
ForLoop +680%

This change also includes test for inferred types of various List
constructors.

Change-Id: I801231b0a70e3aa8fb14ec9fe749f1dd420b1b9c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/153388
Reviewed-by: Aske Simon Christensen <askesc@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
  • Loading branch information
alexmarkov authored and commit-bot@chromium.org committed Jul 8, 2020
1 parent 255c6fa commit bfd1582
Show file tree
Hide file tree
Showing 10 changed files with 299 additions and 24 deletions.
4 changes: 4 additions & 0 deletions pkg/front_end/lib/src/fasta/source/source_loader.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1296,18 +1296,22 @@ class List<E> extends Iterable {
factory List() => null;
factory List.unmodifiable(elements) => null;
factory List.filled(int length, E fill, {bool growable = false}) => null;
factory List.generate(int length, E generator(int index),
{bool growable = true}) => null;
void add(E) {}
E operator [](int index) => null;
}
class _GrowableList<E> {
factory _GrowableList() => null;
factory _GrowableList.filled() => null;
factory _GrowableList.generate(int length, E generator(int index)) => null;
}
class _List<E> {
factory _List() => null;
factory _List.filled() => null;
factory _List.generate(int length, E generator(int index)) => null;
}
class MapEntry<K, V> {
Expand Down
80 changes: 60 additions & 20 deletions pkg/vm/lib/transformations/list_factory_specializer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,33 +16,47 @@ import 'package:kernel/core_types.dart' show CoreTypes;
/// new List.filled(n, x, growable: true) => new _GrowableList.filled(n, x)
/// new List.filled(n, null) => new _List(n)
/// new List.filled(n, x) => new _List.filled(n, x)
/// new List.generate(n, y) => new _GrowableList.generate(n, y)
/// new List.generate(n, y, growable: false) => new _List.generate(n, y)
///
class ListFactorySpecializer {
final Procedure _defaultListFactory;
final Procedure _listFilledFactory;
final Procedure _listGenerateFactory;
final Procedure _growableListFactory;
final Procedure _growableListFilledFactory;
final Procedure _growableListGenerateFactory;
final Procedure _fixedListFactory;
final Procedure _fixedListFilledFactory;
final Procedure _fixedListGenerateFactory;

ListFactorySpecializer(CoreTypes coreTypes)
: _defaultListFactory =
coreTypes.index.getMember('dart:core', 'List', ''),
_listFilledFactory =
coreTypes.index.getMember('dart:core', 'List', 'filled'),
_listGenerateFactory =
coreTypes.index.getMember('dart:core', 'List', 'generate'),
_growableListFactory =
coreTypes.index.getMember('dart:core', '_GrowableList', ''),
_growableListFilledFactory =
coreTypes.index.getMember('dart:core', '_GrowableList', 'filled'),
_growableListGenerateFactory =
coreTypes.index.getMember('dart:core', '_GrowableList', 'generate'),
_fixedListFactory = coreTypes.index.getMember('dart:core', '_List', ''),
_fixedListFilledFactory =
coreTypes.index.getMember('dart:core', '_List', 'filled') {
coreTypes.index.getMember('dart:core', '_List', 'filled'),
_fixedListGenerateFactory =
coreTypes.index.getMember('dart:core', '_List', 'generate') {
assert(_defaultListFactory.isFactory);
assert(_listFilledFactory.isFactory);
assert(_listGenerateFactory.isFactory);
assert(_growableListFactory.isFactory);
assert(_growableListFilledFactory.isFactory);
assert(_growableListGenerateFactory.isFactory);
assert(_fixedListFactory.isFactory);
assert(_fixedListFilledFactory.isFactory);
assert(_fixedListGenerateFactory.isFactory);
}

TreeNode transformStaticInvocation(StaticInvocation node) {
Expand All @@ -64,25 +78,10 @@ class ListFactorySpecializer {
final fill = args.positional[1];
final fillingWithNull = fill is NullLiteral ||
(fill is ConstantExpression && fill.constant is NullConstant);
bool growable;
if (args.named.isEmpty) {
growable = false;
} else {
final namedArg = args.named.single;
assert(namedArg.name == 'growable');
final value = namedArg.value;
if (value is BoolLiteral) {
growable = value.value;
} else if (value is ConstantExpression) {
final constant = value.constant;
if (constant is BoolConstant) {
growable = constant.value;
} else {
return node;
}
} else {
return node;
}
final bool growable =
_getConstantOptionalArgument(args, 'growable', false);
if (growable == null) {
return node;
}
if (growable) {
if (fillingWithNull) {
Expand All @@ -105,8 +104,49 @@ class ListFactorySpecializer {
..fileOffset = node.fileOffset;
}
}
} else if (target == _listGenerateFactory) {
final args = node.arguments;
assert(args.positional.length == 2);
final length = args.positional[0];
final generator = args.positional[1];
final bool growable =
_getConstantOptionalArgument(args, 'growable', true);
if (growable == null) {
return node;
}
if (growable) {
return StaticInvocation(_growableListGenerateFactory,
Arguments([length, generator], types: args.types))
..fileOffset = node.fileOffset;
} else {
return StaticInvocation(_fixedListGenerateFactory,
Arguments([length, generator], types: args.types))
..fileOffset = node.fileOffset;
}
}

return node;
}

/// Returns constant value of the only optional argument in [args],
/// or null if it is not a constant. Returns [defaultValue] if optional
/// argument is not passed. Argument is asserted to have the given [name].
bool /*?*/ _getConstantOptionalArgument(
Arguments args, String name, bool defaultValue) {
if (args.named.isEmpty) {
return defaultValue;
}
final namedArg = args.named.single;
assert(namedArg.name == name);
final value = namedArg.value;
if (value is BoolLiteral) {
return value.value;
} else if (value is ConstantExpression) {
final constant = value.constant;
if (constant is BoolConstant) {
return constant.value;
}
}
return null;
}
}
52 changes: 52 additions & 0 deletions pkg/vm/testcases/transformations/type_flow/transformer/lists.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

nonConstant() => int.parse('1') == 1;

class A {
final literal1 = <int>[];
final literal2 = [1, 2, 3];
final constLiteral1 = const <int>[];
final constLiteral2 = const [1, 2];
final defaultConstructor1 = List<int>();
final defaultConstructor2 = List<int>(3);
final filledFactory1 = List<int>.filled(2, 0);
final filledFactory2 = List<int>.filled(2, 0, growable: true);
final filledFactory3 = List<int>.filled(2, 0, growable: false);
final filledFactory4 = List<int>.filled(2, 0, growable: nonConstant());
final filledFactory5 = List<int>.filled(2, null);
final filledFactory6 = List<int>.filled(2, null, growable: true);
final filledFactory7 = List<int>.filled(2, null, growable: false);
final filledFactory8 = List<int>.filled(2, null, growable: nonConstant());
final generateFactory1 = List<int>.generate(2, (i) => i);
final generateFactory2 = List<int>.generate(2, (i) => i, growable: true);
final generateFactory3 = List<int>.generate(2, (i) => i, growable: false);
final generateFactory4 =
List<int>.generate(2, (i) => i, growable: nonConstant());
final generateFactory5 = List<List<int>>.generate(2, (_) => <int>[]);
}

main() {
A x = A();
// Make sure fields are not tree-shaken.
print(x.literal1);
print(x.literal2);
print(x.constLiteral1);
print(x.constLiteral2);
print(x.defaultConstructor1);
print(x.defaultConstructor2);
print(x.filledFactory1);
print(x.filledFactory2);
print(x.filledFactory3);
print(x.filledFactory4);
print(x.filledFactory5);
print(x.filledFactory6);
print(x.filledFactory7);
print(x.filledFactory8);
print(x.generateFactory1);
print(x.generateFactory2);
print(x.generateFactory3);
print(x.generateFactory4);
print(x.generateFactory5);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
library #lib;
import self as self;
import "dart:core" as core;
import "dart:_internal" as _in;

class A extends core::Object {
[@vm.inferred-type.metadata=dart.core::_GrowableList<dart.core::int*>] [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:1,getterSelectorId:2] final field core::List<core::int*>* literal1 = <core::int*>[];
[@vm.inferred-type.metadata=dart.core::_GrowableList<dart.core::int*>] [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:3,getterSelectorId:4] final field core::List<core::int*>* literal2 = <core::int*>[1, 2, 3];
[@vm.inferred-type.metadata=dart.core::_ImmutableList (value: const <dart.core::int*>[])] [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:5,getterSelectorId:6] final field core::List<core::int*>* constLiteral1 = #C1;
[@vm.inferred-type.metadata=dart.core::_ImmutableList (value: const <dart.core::int*>[1, 2])] [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:7,getterSelectorId:8] final field core::List<core::int*>* constLiteral2 = #C4;
[@vm.inferred-type.metadata=dart.core::_GrowableList<dart.core::int*>] [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:9,getterSelectorId:10] final field core::List<core::int*>* defaultConstructor1 = [@vm.inferred-type.metadata=dart.core::_GrowableList<dart.core::int*>] core::_GrowableList::•<core::int*>(0);
[@vm.inferred-type.metadata=dart.core::_List<dart.core::int*>] [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:11,getterSelectorId:12] final field core::List<core::int*>* defaultConstructor2 = [@vm.inferred-type.metadata=dart.core::_List<dart.core::int*>] core::_List::•<core::int*>(3);
[@vm.inferred-type.metadata=dart.core::_List<dart.core::int*>] [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:13,getterSelectorId:14] final field core::List<core::int*>* filledFactory1 = [@vm.inferred-type.metadata=dart.core::_List<dart.core::int*>] core::_List::filled<core::int*>(2, 0);
[@vm.inferred-type.metadata=dart.core::_GrowableList<dart.core::int*>] [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:15,getterSelectorId:16] final field core::List<core::int*>* filledFactory2 = [@vm.inferred-type.metadata=dart.core::_GrowableList<dart.core::int*>] core::_GrowableList::filled<core::int*>(2, 0);
[@vm.inferred-type.metadata=dart.core::_List<dart.core::int*>] [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:17,getterSelectorId:18] final field core::List<core::int*>* filledFactory3 = [@vm.inferred-type.metadata=dart.core::_List<dart.core::int*>] core::_List::filled<core::int*>(2, 0);
[@vm.inferred-type.metadata=!] [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:19,getterSelectorId:20] final field core::List<core::int*>* filledFactory4 = let core::int* #t1 = 2 in let core::int* #t2 = 0 in let core::bool #t3 = _in::unsafeCast<core::bool>([@vm.inferred-type.metadata=dart.core::bool] self::nonConstant()) in [@vm.inferred-type.metadata=!] core::List::filled<core::int*>(#t1, #t2, #t3);
[@vm.inferred-type.metadata=dart.core::_List<dart.core::int*>] [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:21,getterSelectorId:22] final field core::List<core::int*>* filledFactory5 = [@vm.inferred-type.metadata=dart.core::_List<dart.core::int*>] core::_List::•<core::int*>(2);
[@vm.inferred-type.metadata=dart.core::_GrowableList<dart.core::int*>] [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:23,getterSelectorId:24] final field core::List<core::int*>* filledFactory6 = [@vm.inferred-type.metadata=dart.core::_GrowableList<dart.core::int*>] core::_GrowableList::•<core::int*>(2);
[@vm.inferred-type.metadata=dart.core::_List<dart.core::int*>] [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:25,getterSelectorId:26] final field core::List<core::int*>* filledFactory7 = [@vm.inferred-type.metadata=dart.core::_List<dart.core::int*>] core::_List::•<core::int*>(2);
[@vm.inferred-type.metadata=!] [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:27,getterSelectorId:28] final field core::List<core::int*>* filledFactory8 = let core::int* #t4 = 2 in let core::Null? #t5 = null in let core::bool #t6 = _in::unsafeCast<core::bool>([@vm.inferred-type.metadata=dart.core::bool] self::nonConstant()) in [@vm.inferred-type.metadata=!] core::List::filled<core::int*>(#t4, #t5, #t6);
[@vm.inferred-type.metadata=dart.core::_GrowableList<dart.core::int*>] [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:29,getterSelectorId:30] final field core::List<core::int*>* generateFactory1 = [@vm.inferred-type.metadata=dart.core::_GrowableList<dart.core::int*>] core::_GrowableList::generate<core::int*>(2, (core::int* i) → core::int* => i);
[@vm.inferred-type.metadata=dart.core::_GrowableList<dart.core::int*>] [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:31,getterSelectorId:32] final field core::List<core::int*>* generateFactory2 = [@vm.inferred-type.metadata=dart.core::_GrowableList<dart.core::int*>] core::_GrowableList::generate<core::int*>(2, (core::int* i) → core::int* => i);
[@vm.inferred-type.metadata=dart.core::_List<dart.core::int*>] [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:33,getterSelectorId:34] final field core::List<core::int*>* generateFactory3 = [@vm.inferred-type.metadata=dart.core::_List<dart.core::int*>] core::_List::generate<core::int*>(2, (core::int* i) → core::int* => i);
[@vm.inferred-type.metadata=!] [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:35,getterSelectorId:36] final field core::List<core::int*>* generateFactory4 = let core::int* #t7 = 2 in let (core::int*) →* core::int* #t8 = (core::int* i) → core::int* => i in let core::bool #t9 = _in::unsafeCast<core::bool>([@vm.inferred-type.metadata=dart.core::bool] self::nonConstant()) in [@vm.inferred-type.metadata=!] core::List::generate<core::int*>(#t7, #t8, #t9);
[@vm.inferred-type.metadata=dart.core::_GrowableList<dart.core::List<dart.core::int*>*>] [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:37,getterSelectorId:38] final field core::List<core::List<core::int*>*>* generateFactory5 = [@vm.inferred-type.metadata=dart.core::_GrowableList<dart.core::List<dart.core::int*>*>] core::_GrowableList::generate<core::List<core::int*>*>(2, (core::int* _) → core::List<core::int*>* => <core::int*>[]);
synthetic constructor •() → self::A*
: super core::Object::•()
;
}
static method nonConstant() → dynamic
return [@vm.direct-call.metadata=dart.core::_IntegerImplementation.==] [@vm.inferred-type.metadata=dart.core::bool (skip check)] [@vm.inferred-type.metadata=int] core::int::parse("1").{core::num::==}(1);
static method main() → dynamic {
self::A* x = new self::A::•();
core::print([@vm.direct-call.metadata=#lib::A.literal1] [@vm.inferred-type.metadata=dart.core::_GrowableList<dart.core::int*>] x.{self::A::literal1});
core::print([@vm.direct-call.metadata=#lib::A.literal2] [@vm.inferred-type.metadata=dart.core::_GrowableList<dart.core::int*>] x.{self::A::literal2});
core::print([@vm.direct-call.metadata=#lib::A.constLiteral1] [@vm.inferred-type.metadata=dart.core::_ImmutableList (value: const <dart.core::int*>[])] x.{self::A::constLiteral1});
core::print([@vm.direct-call.metadata=#lib::A.constLiteral2] [@vm.inferred-type.metadata=dart.core::_ImmutableList (value: const <dart.core::int*>[1, 2])] x.{self::A::constLiteral2});
core::print([@vm.direct-call.metadata=#lib::A.defaultConstructor1] [@vm.inferred-type.metadata=dart.core::_GrowableList<dart.core::int*>] x.{self::A::defaultConstructor1});
core::print([@vm.direct-call.metadata=#lib::A.defaultConstructor2] [@vm.inferred-type.metadata=dart.core::_List<dart.core::int*>] x.{self::A::defaultConstructor2});
core::print([@vm.direct-call.metadata=#lib::A.filledFactory1] [@vm.inferred-type.metadata=dart.core::_List<dart.core::int*>] x.{self::A::filledFactory1});
core::print([@vm.direct-call.metadata=#lib::A.filledFactory2] [@vm.inferred-type.metadata=dart.core::_GrowableList<dart.core::int*>] x.{self::A::filledFactory2});
core::print([@vm.direct-call.metadata=#lib::A.filledFactory3] [@vm.inferred-type.metadata=dart.core::_List<dart.core::int*>] x.{self::A::filledFactory3});
core::print([@vm.direct-call.metadata=#lib::A.filledFactory4] [@vm.inferred-type.metadata=!] x.{self::A::filledFactory4});
core::print([@vm.direct-call.metadata=#lib::A.filledFactory5] [@vm.inferred-type.metadata=dart.core::_List<dart.core::int*>] x.{self::A::filledFactory5});
core::print([@vm.direct-call.metadata=#lib::A.filledFactory6] [@vm.inferred-type.metadata=dart.core::_GrowableList<dart.core::int*>] x.{self::A::filledFactory6});
core::print([@vm.direct-call.metadata=#lib::A.filledFactory7] [@vm.inferred-type.metadata=dart.core::_List<dart.core::int*>] x.{self::A::filledFactory7});
core::print([@vm.direct-call.metadata=#lib::A.filledFactory8] [@vm.inferred-type.metadata=!] x.{self::A::filledFactory8});
core::print([@vm.direct-call.metadata=#lib::A.generateFactory1] [@vm.inferred-type.metadata=dart.core::_GrowableList<dart.core::int*>] x.{self::A::generateFactory1});
core::print([@vm.direct-call.metadata=#lib::A.generateFactory2] [@vm.inferred-type.metadata=dart.core::_GrowableList<dart.core::int*>] x.{self::A::generateFactory2});
core::print([@vm.direct-call.metadata=#lib::A.generateFactory3] [@vm.inferred-type.metadata=dart.core::_List<dart.core::int*>] x.{self::A::generateFactory3});
core::print([@vm.direct-call.metadata=#lib::A.generateFactory4] [@vm.inferred-type.metadata=!] x.{self::A::generateFactory4});
core::print([@vm.direct-call.metadata=#lib::A.generateFactory5] [@vm.inferred-type.metadata=dart.core::_GrowableList<dart.core::List<dart.core::int*>*>] x.{self::A::generateFactory5});
}

0 comments on commit bfd1582

Please sign in to comment.