[dart2js] Kernel transformer expansion for List.generate

Simple calls to `List.generate` are expanded into a list allocation
and a loop. This generates better code for several reasons:

 - There is no overhead for the function argument (closure allocation,
   closure type, closure class)

 - Global type inference is more precise since each List.generate list
   is tracked separately, and the assignments in the loop give better
   inference to the collection's element type.

To get precise element type inference, there are two new JSArray
constructors. Global type inference starts with the element type being
bottom for these elements, avoiding spurious nulls in the inferred
type.

Change-Id: I5efb90651ae3f9eb2e81af556704960cdf0b75c5
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/168770
Commit-Queue: Stephen Adams <sra@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
This commit is contained in:
Stephen Adams 2020-11-11 04:27:22 +00:00 committed by commit-bot@chromium.org
parent 8c495e075d
commit 8c13fa841e
21 changed files with 962 additions and 27 deletions

View file

@ -595,6 +595,13 @@ abstract class JCommonElements implements CommonElements {
/// compilation.
bool isNamedListConstructor(String name, ConstructorEntity element);
/// Returns `true` if [element] is the named constructor of `JSArray`,
/// e.g. `JSArray.fixed`.
///
/// This will not resolve the constructor if it hasn't been seen yet during
/// compilation.
bool isNamedJSArrayConstructor(String name, ConstructorEntity element);
bool isDefaultEqualityImplementation(MemberEntity element);
/// Returns `true` if [selector] applies to `JSIndexable.length`.
@ -885,6 +892,14 @@ class CommonElementsImpl
bool isNamedListConstructor(String name, ConstructorEntity element) =>
element.name == name && element.enclosingClass == listClass;
/// Returns `true` if [element] is the [name]d constructor of `JSArray`.
///
/// This will not resolve the constructor if it hasn't been seen yet during
/// compilation.
@override
bool isNamedJSArrayConstructor(String name, ConstructorEntity element) =>
element.name == name && element.enclosingClass == jsArrayClass;
@override
DynamicType get dynamicType => _env.dynamicType;

View file

@ -1296,10 +1296,11 @@ class KernelTypeGraphBuilder extends ir.Visitor<TypeInformation> {
// We have something like `List.empty(growable: true)`.
TypeInformation baseType =
_listBaseType(arguments, defaultGrowable: false);
TypeInformation elementType = _types.nonNullEmpty(); // No elements!
return _inferrer.concreteTypes.putIfAbsent(
node,
() => _types.allocateList(
baseType, node, _analyzedMember, _types.nonNullEmpty(), 0));
baseType, node, _analyzedMember, elementType, 0));
}
if (commonElements.isNamedListConstructor('of', constructor) ||
commonElements.isNamedListConstructor('from', constructor)) {
@ -1314,6 +1315,44 @@ class KernelTypeGraphBuilder extends ir.Visitor<TypeInformation> {
() => _types.allocateList(
baseType, node, _analyzedMember, elementType));
}
// `JSArray.fixed` corresponds to `new Array(length)`, which is a list
// filled with `null`.
if (commonElements.isNamedJSArrayConstructor('fixed', constructor)) {
int length = _findLength(arguments);
TypeInformation elementType = _types.nullType;
return _inferrer.concreteTypes.putIfAbsent(
node,
() => _types.allocateList(_types.fixedListType, node, _analyzedMember,
elementType, length));
}
// `JSArray.allocateFixed` creates an array with 'no elements'. The contract
// is that the caller will assign a value to each member before any element
// is accessed. We can start tracking the element type as 'bottom'.
if (commonElements.isNamedJSArrayConstructor(
'allocateFixed', constructor)) {
int length = _findLength(arguments);
TypeInformation elementType = _types.nonNullEmpty();
return _inferrer.concreteTypes.putIfAbsent(
node,
() => _types.allocateList(_types.fixedListType, node, _analyzedMember,
elementType, length));
}
// `JSArray.allocateGrowable` creates an array with 'no elements'. The
// contract is that the caller will assign a value to each member before any
// element is accessed. We can start tracking the element type as 'bottom'.
if (commonElements.isNamedJSArrayConstructor(
'allocateGrowable', constructor)) {
int length = _findLength(arguments);
TypeInformation elementType = _types.nonNullEmpty();
return _inferrer.concreteTypes.putIfAbsent(
node,
() => _types.allocateList(_types.growableListType, node,
_analyzedMember, elementType, length));
}
if (_isConstructorOfTypedArraySubclass(constructor)) {
// We have something like `Uint32List(len)`.
int length = _findLength(arguments);

View file

@ -17,6 +17,7 @@ import 'package:kernel/target/changed_structure_notifier.dart';
import 'package:kernel/target/targets.dart';
import 'invocation_mirror_constants.dart';
import 'transformations/lowering.dart' as lowering show transformLibraries;
const Iterable<String> _allowedDartSchemePaths = const <String>[
'async',
@ -82,6 +83,12 @@ class Dart2jsTarget extends Target {
@override
List<String> get extraRequiredLibraries => _requiredLibraries[name];
@override
List<String> get extraIndexedLibraries => const [
'dart:_interceptors',
'dart:_js_helper',
];
@override
bool mayDefineRestrictedType(Uri uri) =>
uri.scheme == 'dart' &&
@ -118,6 +125,9 @@ class Dart2jsTarget extends Target {
diagnosticReporter as DiagnosticReporter<Message, LocatedMessage>)
.visitLibrary(library);
}
lowering.transformLibraries(
libraries, coreTypes, hierarchy, flags.enableNullSafety);
logger?.call("Lowering transformations performed");
}
@override

View file

@ -0,0 +1,39 @@
// 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.
import 'package:kernel/kernel.dart';
import 'package:kernel/class_hierarchy.dart' show ClassHierarchy;
import 'package:kernel/core_types.dart';
import 'list_factory_specializer.dart';
typedef SpecializerTransformer = TreeNode Function(
StaticInvocation node, Member contextMember);
abstract class BaseSpecializer {
// Populated in constructors of subclasses.
final Map<Member, SpecializerTransformer> transformers = {};
}
class FactorySpecializer extends BaseSpecializer {
final ListFactorySpecializer _listFactorySpecializer;
FactorySpecializer(CoreTypes coreTypes, ClassHierarchy hierarchy)
: _listFactorySpecializer = ListFactorySpecializer(coreTypes, hierarchy) {
transformers.addAll(_listFactorySpecializer.transformers);
}
TreeNode transformStaticInvocation(
StaticInvocation invocation, Member contextMember) {
final target = invocation.target;
if (target == null) {
return invocation;
}
final transformer = transformers[target];
if (transformer != null) {
return transformer(invocation, contextMember);
}
return invocation;
}
}

View file

@ -0,0 +1,343 @@
// 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.
import 'package:kernel/ast.dart';
import 'package:kernel/class_hierarchy.dart' show ClassHierarchy;
import 'package:kernel/clone.dart' show CloneVisitorNotMembers;
import 'package:kernel/core_types.dart' show CoreTypes;
import 'factory_specializer.dart';
/// Replaces invocation of List factory constructors.
///
/// Expands `List.generate` to a loop when the function argument is a function
/// expression (immediate closure).
///
class ListFactorySpecializer extends BaseSpecializer {
final CoreTypes coreTypes;
final ClassHierarchy hierarchy;
final Class _intClass;
final Class _jsArrayClass;
final Procedure _listGenerateFactory;
final Procedure _arrayAllocateFixedFactory;
final Procedure _arrayAllocateGrowableFactory;
ListFactorySpecializer(this.coreTypes, this.hierarchy)
: _listGenerateFactory =
coreTypes.index.getMember('dart:core', 'List', 'generate'),
_arrayAllocateFixedFactory = coreTypes.index
.getMember('dart:_interceptors', 'JSArray', 'allocateFixed'),
_arrayAllocateGrowableFactory = coreTypes.index
.getMember('dart:_interceptors', 'JSArray', 'allocateGrowable'),
_jsArrayClass =
coreTypes.index.getClass('dart:_interceptors', 'JSArray'),
_intClass = coreTypes.index.getClass('dart:core', 'int') {
assert(_listGenerateFactory.isFactory);
assert(_arrayAllocateGrowableFactory.isFactory);
assert(_arrayAllocateFixedFactory.isFactory);
transformers.addAll({
_listGenerateFactory: transformListGenerateFactory,
});
}
Member _intPlus;
Member get intPlus =>
_intPlus ??= hierarchy.getInterfaceMember(_intClass, Name('+'));
Member _intLess;
Member get intLess =>
_intLess ??= hierarchy.getInterfaceMember(_intClass, Name('<'));
Member _jsArrayIndexSet;
Member get jsArrayIndexSet => _jsArrayIndexSet ??=
hierarchy.getInterfaceMember(_jsArrayClass, Name('[]='));
/// Replace calls to `List.generate(length, (i) => e)` with an expansion
///
/// BlockExpression
/// Block
/// var _length = <length>;
/// var _list = List.allocate(_length);
/// for (var _i = 0; _i < _length; _i++) {
/// _list[_i] = e;
/// }
/// => _list
///
/// Declines to expand if:
/// - the function argument is not a simple closure,
/// - the `growable:` argument cannot be determined.
TreeNode transformListGenerateFactory(
StaticInvocation node, Member contextMember) {
final args = node.arguments;
assert(args.positional.length == 2);
final length = args.positional[0];
final generator = args.positional[1];
final bool growable =
_getConstantNamedOptionalArgument(args, 'growable', true);
if (growable == null) return node;
if (generator is! FunctionExpression) return node;
if (!ListGenerateLoopBodyInliner.suitableFunctionExpression(generator)) {
return node;
}
final intType = contextMember.isNonNullableByDefault
? coreTypes.intLegacyRawType
: coreTypes.intNonNullableRawType;
// If the length is a constant, use the constant directly so that the
// inferrer can see the constant length.
int /*?*/ lengthConstant = _getLengthArgument(args);
VariableDeclaration lengthVariable;
Expression getLength() {
if (lengthConstant != null) return IntLiteral(lengthConstant);
lengthVariable ??= VariableDeclaration('_length',
initializer: length, isFinal: true, type: intType)
..fileOffset = node.fileOffset;
return VariableGet(lengthVariable)..fileOffset = node.fileOffset;
}
TreeNode allocation = StaticInvocation(
growable ? _arrayAllocateGrowableFactory : _arrayAllocateFixedFactory,
Arguments(
[getLength()],
types: args.types,
))
..fileOffset = node.fileOffset;
final listVariable = VariableDeclaration(
_listNameFromContext(node),
initializer: allocation,
isFinal: true,
type: InterfaceType(
_jsArrayClass, Nullability.nonNullable, [...args.types]),
)..fileOffset = node.fileOffset;
final indexVariable = VariableDeclaration(
_indexNameFromContext(generator),
initializer: IntLiteral(0),
type: intType,
)..fileOffset = node.fileOffset;
indexVariable.fileOffset = (generator as FunctionExpression)
.function
.positionalParameters
.first
.fileOffset;
final loop = ForStatement(
// initializers: _i = 0
[indexVariable],
// condition: _i < _length
MethodInvocation(
VariableGet(indexVariable)..fileOffset = node.fileOffset,
Name('<'),
Arguments([getLength()]),
)..interfaceTarget = intLess,
// updates: _i++
[
VariableSet(
indexVariable,
MethodInvocation(
VariableGet(indexVariable)..fileOffset = node.fileOffset,
Name('+'),
Arguments([IntLiteral(1)]),
)..interfaceTarget = intPlus,
)..fileOffset = node.fileOffset,
],
// body, e.g. _list[_i] = expression;
_loopBody(node.fileOffset, listVariable, indexVariable, generator),
)..fileOffset = node.fileOffset;
return BlockExpression(
Block([
if (lengthVariable != null) lengthVariable,
listVariable,
loop,
]),
VariableGet(listVariable)..fileOffset = node.fileOffset,
);
}
Statement _loopBody(
int constructorFileOffset,
VariableDeclaration listVariable,
VariableDeclaration indexVariable,
FunctionExpression generator) {
final inliner = ListGenerateLoopBodyInliner(
this, constructorFileOffset, listVariable, generator.function);
inliner.bind(indexVariable);
return inliner.run();
}
/// Returns constant value of the first argument in [args], or null if it is
/// not a constant.
int /*?*/ _getLengthArgument(Arguments args) {
if (args.positional.length < 1) return null;
final value = args.positional.first;
if (value is IntLiteral) {
return value.value;
} else if (value is ConstantExpression) {
final constant = value.constant;
if (constant is IntConstant) {
return constant.value;
}
}
return null;
}
/// Returns constant value of the only named optional argument in [args], or
/// null if it is not a bool constant. Returns [defaultValue] if optional
/// argument is not passed. Argument is asserted to have the given [name].
bool /*?*/ _getConstantNamedOptionalArgument(
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;
}
/// Choose a name for the `_list` temporary. If the `List.generate` expression
/// is an initializer for a variable, use that name so that dart2js can try to
/// use one JavaScript variable with the source name for 'both' variables.
String _listNameFromContext(Expression node) {
TreeNode parent = node.parent;
if (parent is VariableDeclaration) return parent.name;
return '_list';
}
String _indexNameFromContext(FunctionExpression generator) {
final function = generator.function;
String /*?*/ candidate = function.positionalParameters.first.name;
if (candidate == null || candidate == '' || candidate == '_') return '_i';
return candidate;
}
}
/// Inliner for function expressions of `List.generate` calls.
class ListGenerateLoopBodyInliner extends CloneVisitorNotMembers {
final ListFactorySpecializer listFactorySpecializer;
/// Offset for the constructor call, used for all nodes that carry the value of the list.
final int constructorFileOffset;
final VariableDeclaration listVariable;
final FunctionNode function;
VariableDeclaration argument;
VariableDeclaration parameter;
int functionNestingLevel = 0;
ListGenerateLoopBodyInliner(this.listFactorySpecializer,
this.constructorFileOffset, this.listVariable, this.function);
static bool suitableFunctionExpression(FunctionExpression node) {
final function = node.function;
// These conditions should be satisfied by language rules.
if (function.typeParameters.isNotEmpty) return false;
if (function.requiredParameterCount != 1) return false;
if (function.positionalParameters.length != 1) return false;
if (function.namedParameters.isNotEmpty) return false;
final body = function.body;
// For now, only arrow functions.
if (body is ReturnStatement) return true;
return false;
}
void bind(VariableDeclaration argument) {
// The [argument] is the loop index variable. In the general case this needs
// to be copied to a variable for the closure parameter as that is a
// separate location that may be mutated. In the usual case the closure
// parameter is not modified. We use the same name for the parameter and
// argument to help dart2js allocate both locations to the same JavaScript
// variable. The argument is usually named after the closure parameter.
final closureParameter = function.positionalParameters.single;
parameter = VariableDeclaration(argument.name,
initializer: VariableGet(argument)..fileOffset = argument.fileOffset,
type: closureParameter.type)
..fileOffset = closureParameter.fileOffset;
this.argument = argument;
variables[closureParameter] = parameter;
}
Statement run() {
final body = cloneInContext(function.body);
return Block([parameter, body]);
}
@override
visitReturnStatement(ReturnStatement node) {
// Do the default for return statements in nested functions.
if (functionNestingLevel > 0) return super.visitReturnStatement(node);
// We don't use a variable for the returned value. In the simple case it is
// not necessary, and it is not clear that the rules for definite assignment
// are not a perfect match for the locations of return statements. Instead
// we expand
//
// return expression;
//
// to
//
// list[index] = expression;
//
// TODO(sra): Currently this inliner accepts only arrow functions (a single
// return). If a wider variety is accepted, we might need to break after the
// assignment to 'exit' the inlined code.
final expression = node.expression;
final value = expression == null ? NullLiteral() : clone(expression);
// TODO(sra): Indicate that this indexed setter is safe.
return ExpressionStatement(
MethodInvocation(
VariableGet(listVariable)..fileOffset = constructorFileOffset,
Name('[]='),
Arguments([
VariableGet(argument)..fileOffset = node.fileOffset,
value,
]),
)
..interfaceTarget = listFactorySpecializer.jsArrayIndexSet
..isInvariant = true
..isBoundsSafe = true
..fileOffset = constructorFileOffset,
);
}
/// Nested functions.
@override
visitFunctionNode(FunctionNode node) {
functionNestingLevel++;
final cloned = super.visitFunctionNode(node);
functionNestingLevel--;
return cloned;
}
@override
visitVariableGet(VariableGet node) {
// Unmapped variables are from an outer scope.
var mapped = variables[node.variable] ?? node.variable;
return VariableGet(mapped, visitOptionalType(node.promotedType))
..fileOffset = node.fileOffset;
}
@override
visitVariableSet(VariableSet node) {
// Unmapped variables are from an outer scope.
var mapped = variables[node.variable] ?? node.variable;
return VariableSet(mapped, clone(node.value))..fileOffset = node.fileOffset;
}
}

View file

@ -0,0 +1,56 @@
// 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.
import 'package:kernel/ast.dart';
import 'package:kernel/class_hierarchy.dart' show ClassHierarchy;
import 'package:kernel/core_types.dart' show CoreTypes;
import 'package:kernel/type_environment.dart'
show StaticTypeContext, TypeEnvironment;
import 'factory_specializer.dart';
/// dart2js-specific lowering transformations and optimizations combined into a
/// single transformation pass.
///
/// Each transformation is applied locally to AST nodes of certain types after
/// transforming children nodes.
void transformLibraries(List<Library> libraries, CoreTypes coreTypes,
ClassHierarchy hierarchy, bool nullSafety) {
final transformer = _Lowering(coreTypes, hierarchy, nullSafety);
libraries.forEach(transformer.visitLibrary);
}
class _Lowering extends Transformer {
final TypeEnvironment env;
final bool nullSafety;
final FactorySpecializer factorySpecializer;
Member _currentMember;
StaticTypeContext _cachedStaticTypeContext;
_Lowering(CoreTypes coreTypes, ClassHierarchy hierarchy, this.nullSafety)
: env = TypeEnvironment(coreTypes, hierarchy),
factorySpecializer = FactorySpecializer(coreTypes, hierarchy);
// ignore: unused_element
StaticTypeContext get _staticTypeContext =>
_cachedStaticTypeContext ??= StaticTypeContext(_currentMember, env);
@override
defaultMember(Member node) {
_currentMember = node;
_cachedStaticTypeContext = null;
final result = super.defaultMember(node);
_currentMember = null;
_cachedStaticTypeContext = null;
return result;
}
@override
visitStaticInvocation(StaticInvocation node) {
node.transformChildren(this);
return factorySpecializer.transformStaticInvocation(node, _currentMember);
}
}

View file

@ -3882,7 +3882,10 @@ class KernelSsaGraphBuilder extends ir.Visitor {
if (_abstractValueDomain.isFixedArray(resultType).isDefinitelyTrue) {
// These constructors all take a length as the first argument.
if (_commonElements.isNamedListConstructor('filled', function) ||
_commonElements.isNamedListConstructor('generate', function)) {
_commonElements.isNamedListConstructor('generate', function) ||
_commonElements.isNamedJSArrayConstructor('fixed', function) ||
_commonElements.isNamedJSArrayConstructor(
'allocateFixed', function)) {
isFixedList = true;
}
}

View file

@ -192,6 +192,9 @@ class IndexAssignSpecializer extends InvokeDynamicSpecializer {
HInstruction value,
JCommonElements commonElements,
JClosedWorld closedWorld) {
if (instruction.isInvariant) {
return true;
}
// Handle typed arrays by recognizing the exact implementation of `[]=` and
// checking if [value] has the appropriate type.
if (instruction.element != null) {

View file

@ -34,6 +34,7 @@ dev_dependencies:
package_config: any
path: any
source_maps: any
test: any
# Unpublished packages that can be used via path dependency
async_helper:
path: ../async_helper

View file

@ -23,6 +23,7 @@ main() {
listGenerateGrowable,
listGenerateFixed,
listGenerateEither,
listGenerateBigClosure,
listOfDefault,
listOfGrowable,
listOfFixed,
@ -72,24 +73,37 @@ get listFilledEither => List.filled(5, 'e', growable: boolFlag);
// -------- List.generate --------
/*member: listGenerateDefault:Container([exact=JSExtendableArray], element: [exact=JSString], length: 8)*/
get listGenerateDefault => List.generate(
8, /*[exact=JSString]*/ (/*[subclass=JSPositiveInt]*/ i) => 'x$i');
get listGenerateDefault => List
. /*update: Container([exact=JSExtendableArray], element: [exact=JSString], length: 8)*/ generate(
8, (i) => 'x$i');
/*member: listGenerateGrowable:Container([exact=JSExtendableArray], element: [exact=JSString], length: 8)*/
get listGenerateGrowable => List.generate(
8, /*[exact=JSString]*/ (/*[subclass=JSPositiveInt]*/ i) => 'g$i',
growable: true);
get listGenerateGrowable => List
. /*update: Container([exact=JSExtendableArray], element: [exact=JSString], length: 8)*/ generate(
8, (i) => 'g$i',
growable: true);
/*member: listGenerateFixed:Container([exact=JSFixedArray], element: [exact=JSString], length: 8)*/
get listGenerateFixed => List.generate(
8, /*[exact=JSString]*/ (/*[subclass=JSPositiveInt]*/ i) => 'f$i',
growable: false);
get listGenerateFixed => List
. /*update: Container([exact=JSFixedArray], element: [exact=JSString], length: 8)*/ generate(
8, (i) => 'f$i',
growable: false);
/*member: listGenerateEither:Container([subclass=JSMutableArray], element: [exact=JSString], length: 8)*/
get listGenerateEither => List.generate(
8, /*[exact=JSString]*/ (/*[subclass=JSPositiveInt]*/ i) => 'e$i',
growable: boolFlag);
/*member: listGenerateBigClosure:Container([exact=JSExtendableArray], element: [exact=JSString], length: 8)*/
get listGenerateBigClosure => List.generate(
8,
/*[exact=JSString]*/ (/*[subclass=JSPositiveInt]*/ i) {
if (i /*invoke: [subclass=JSPositiveInt]*/ == 1) return 'one';
if (i /*invoke: [subclass=JSPositiveInt]*/ == 2) return 'two';
return '$i';
},
);
// -------- List.of --------
/*member: listOfDefault:Container([exact=JSExtendableArray], element: [null|subclass=Object], length: null)*/

View file

@ -0,0 +1,135 @@
// 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.
import 'dart:async';
import 'dart:io';
import 'package:front_end/src/api_unstable/dart2js.dart'
show
CompilerOptions,
DiagnosticMessage,
computePlatformBinariesLocation,
kernelForProgram,
parseExperimentalArguments,
parseExperimentalFlags;
import 'package:kernel/ast.dart';
import 'package:kernel/text/ast_to_text.dart' show Printer;
import 'package:kernel/target/targets.dart';
import 'package:test/test.dart';
import 'package:compiler/src/kernel/dart2js_target.dart' show Dart2jsTarget;
/// Environment define to update expectation files on failures.
const kUpdateExpectations = 'updateExpectations';
/// Environment define to dump actual results alongside expectations.
const kDumpActualResult = 'dump.actual.result';
class TestingDart2jsTarget extends Dart2jsTarget {
TestingDart2jsTarget(TargetFlags flags) : super('dart2js', flags);
}
Future<Component> compileTestCaseToKernelProgram(Uri sourceUri,
{Target target,
bool enableSuperMixins = false,
List<String> experimentalFlags,
Map<String, String> environmentDefines}) async {
final platformKernel =
computePlatformBinariesLocation().resolve('dart2js_platform.dill');
target ??= TestingDart2jsTarget(TargetFlags());
environmentDefines ??= <String, String>{};
final options = CompilerOptions()
..target = target
..additionalDills = <Uri>[platformKernel]
..environmentDefines = environmentDefines
..explicitExperimentalFlags =
parseExperimentalFlags(parseExperimentalArguments(experimentalFlags),
onError: (String message) {
throw message;
})
..onDiagnostic = (DiagnosticMessage message) {
fail("Compilation error: ${message.plainTextFormatted.join('\n')}");
};
final Component component =
(await kernelForProgram(sourceUri, options)).component;
// Make sure the library name is the same and does not depend on the order
// of test cases.
component.mainMethod.enclosingLibrary.name = '#lib';
return component;
}
String kernelLibraryToString(Library library) {
final buffer = StringBuffer();
Printer(buffer, showMetadata: true).writeLibraryFile(library);
return buffer
.toString()
.replaceAll(library.importUri.toString(), library.name);
}
String kernelComponentToString(Component component) {
final buffer = StringBuffer();
Printer(buffer, showMetadata: true).writeComponentFile(component);
final mainLibrary = component.mainMethod.enclosingLibrary;
return buffer
.toString()
.replaceAll(mainLibrary.importUri.toString(), mainLibrary.name);
}
class Difference {
final int line;
final String actual;
final String expected;
Difference(this.line, this.actual, this.expected);
}
Difference findFirstDifference(String actual, String expected) {
final actualLines = actual.split('\n');
final expectedLines = expected.split('\n');
int i = 0;
for (; i < actualLines.length && i < expectedLines.length; ++i) {
if (actualLines[i] != expectedLines[i]) {
return Difference(i + 1, actualLines[i], expectedLines[i]);
}
}
return Difference(i + 1, i < actualLines.length ? actualLines[i] : '<END>',
i < expectedLines.length ? expectedLines[i] : '<END>');
}
void compareResultWithExpectationsFile(Uri source, String actual) {
final expectFile = File(source.toFilePath() + '.expect');
final expected = expectFile.existsSync() ? expectFile.readAsStringSync() : '';
if (actual != expected) {
if (bool.fromEnvironment(kUpdateExpectations)) {
expectFile.writeAsStringSync(actual);
print(" Updated $expectFile");
} else {
if (bool.fromEnvironment(kDumpActualResult)) {
File(source.toFilePath() + '.actual').writeAsStringSync(actual);
}
Difference diff = findFirstDifference(actual, expected);
fail("""
Result is different for the test case $source
The first difference is at line ${diff.line}.
Actual: ${diff.actual}
Expected: ${diff.expected}
This failure can be caused by changes in the front-end if it starts generating
different kernel AST for the same Dart programs.
In order to re-generate expectations run tests with -D$kUpdateExpectations=true VM option:
sdk/bin/dart -D$kUpdateExpectations=true pkg/compiler/test/kernel/goldens_test.dart
In order to dump actual results into .actual files run tests with -D$kDumpActualResult=true VM option.
""");
}
}
}

View file

@ -0,0 +1,15 @@
// 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.
var list1 = List<int>.generate(10, (i) => i);
var list2 = List<int>.generate(10, (i) => i, growable: true);
var list3 = List<int>.generate(10, (i) => i, growable: false);
var list4 = List<int>.generate(10, (i) => i, growable: someGrowable);
bool someGrowable = true;
void main() {
someGrowable = !someGrowable;
print([list1, list2, list3, list4]);
}

View file

@ -0,0 +1,32 @@
library #lib;
import self as self;
import "dart:core" as core;
import "dart:_interceptors" as _in;
static field core::List<core::int*>* list1 = block {
final _in::JSArray<core::int*> _list = _in::JSArray::allocateGrowable<core::int*>(10);
for (core::int i = 0; i.{core::num::<}(10); i = i.{core::num::+}(1)) {
core::int* i = i;
_list.{_in::JSArray::[]=}{Invariant,BoundsSafe}(i, i);
}
} =>_list;
static field core::List<core::int*>* list2 = block {
final _in::JSArray<core::int*> _list = _in::JSArray::allocateGrowable<core::int*>(10);
for (core::int i = 0; i.{core::num::<}(10); i = i.{core::num::+}(1)) {
core::int* i = i;
_list.{_in::JSArray::[]=}{Invariant,BoundsSafe}(i, i);
}
} =>_list;
static field core::List<core::int*>* list3 = block {
final _in::JSArray<core::int*> _list = _in::JSArray::allocateFixed<core::int*>(10);
for (core::int i = 0; i.{core::num::<}(10); i = i.{core::num::+}(1)) {
core::int* i = i;
_list.{_in::JSArray::[]=}{Invariant,BoundsSafe}(i, i);
}
} =>_list;
static field core::List<core::int*>* list4 = core::List::generate<core::int*>(10, (core::int* i) → core::int* => i, growable: self::someGrowable);
static field core::bool* someGrowable = true;
static method main() → void {
self::someGrowable = !self::someGrowable;
core::print(<core::List<core::int*>*>[self::list1, self::list2, self::list3, self::list4]);
}

View file

@ -0,0 +1,8 @@
// 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.
void main() {
// 'nested' List.generate calls.
print(List.generate(10, (i) => List.generate(i, (i) => i + 1)));
}

View file

@ -0,0 +1,21 @@
library #lib;
import self as self;
import "dart:core" as core;
import "dart:_interceptors" as _in;
static method main() → void {
core::print( block {
final _in::JSArray<core::List<core::int*>*> _list = _in::JSArray::allocateGrowable<core::List<core::int*>*>(10);
for (core::int i = 0; i.{core::num::<}(10); i = i.{core::num::+}(1)) {
core::int* i = i;
_list.{_in::JSArray::[]=}{Invariant,BoundsSafe}(i, block {
final core::int _length = i;
final _in::JSArray<core::int*> _list = _in::JSArray::allocateGrowable<core::int*>(_length);
for (core::int i = 0; i.{core::num::<}(_length); i = i.{core::num::+}(1)) {
core::int* i = i;
_list.{_in::JSArray::[]=}{Invariant,BoundsSafe}(i, i.{core::num::+}(1));
}
} =>_list);
}
} =>_list);
}

View file

@ -0,0 +1,46 @@
// 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.
import 'dart:io';
import 'package:kernel/target/targets.dart';
import 'package:kernel/ast.dart';
import 'package:kernel/kernel.dart';
import 'package:test/test.dart';
import 'common_test_utils.dart';
final String testRootDir = Platform.script.resolve('.').toFilePath();
runTestCase(
Uri source, List<String> experimentalFlags, bool enableNullSafety) async {
final target =
TestingDart2jsTarget(TargetFlags(enableNullSafety: enableNullSafety));
Component component = await compileTestCaseToKernelProgram(source,
target: target, experimentalFlags: experimentalFlags);
String actual = kernelLibraryToString(component.mainMethod.enclosingLibrary);
compareResultWithExpectationsFile(source, actual);
}
main() {
group('goldens', () {
final testCasesDir = new Directory(testRootDir + '/data');
for (var entry
in testCasesDir.listSync(recursive: true, followLinks: false)) {
final path = entry.path;
if (path.endsWith('.dart')) {
final bool enableNullSafety = path.endsWith('_nnbd_strong.dart');
final bool enableNNBD = enableNullSafety || path.endsWith('_nnbd.dart');
final List<String> experimentalFlags = [
if (enableNNBD) 'non-nullable',
];
test(path,
() => runTestCase(entry.uri, experimentalFlags, enableNullSafety));
}
}
}, timeout: Timeout.none);
}

View file

@ -1,12 +1,12 @@
ERROR|COMPILE_TIME_ERROR|INCONSISTENT_INHERITANCE|lib/_internal/js_runtime/lib/interceptors.dart|1659|7|5|Superinterfaces don't have a valid override for '&': JSNumber.& (num Function(num)), int.& (int Function(int)).
ERROR|COMPILE_TIME_ERROR|INCONSISTENT_INHERITANCE|lib/_internal/js_runtime/lib/interceptors.dart|1659|7|5|Superinterfaces don't have a valid override for '<<': JSNumber.<< (num Function(num)), int.<< (int Function(int)).
ERROR|COMPILE_TIME_ERROR|INCONSISTENT_INHERITANCE|lib/_internal/js_runtime/lib/interceptors.dart|1659|7|5|Superinterfaces don't have a valid override for '>>': JSNumber.>> (num Function(num)), int.>> (int Function(int)).
ERROR|COMPILE_TIME_ERROR|INCONSISTENT_INHERITANCE|lib/_internal/js_runtime/lib/interceptors.dart|1659|7|5|Superinterfaces don't have a valid override for '\|': JSNumber.\| (num Function(num)), int.\| (int Function(int)).
ERROR|COMPILE_TIME_ERROR|INCONSISTENT_INHERITANCE|lib/_internal/js_runtime/lib/interceptors.dart|1659|7|5|Superinterfaces don't have a valid override for '^': JSNumber.^ (num Function(num)), int.^ (int Function(int)).
ERROR|COMPILE_TIME_ERROR|RETURN_OF_INVALID_TYPE|lib/_internal/js_runtime/lib/interceptors.dart|1514|14|45|A value of type 'double' can't be returned from method '%' because it has a return type of 'JSNumber'.
ERROR|COMPILE_TIME_ERROR|RETURN_OF_INVALID_TYPE|lib/_internal/js_runtime/lib/interceptors.dart|1516|14|45|A value of type 'double' can't be returned from method '%' because it has a return type of 'JSNumber'.
ERROR|COMPILE_TIME_ERROR|UNDEFINED_OPERATOR|lib/_internal/js_runtime/lib/interceptors.dart|1676|28|1|The operator '&' isn't defined for the type 'JSInt'.
ERROR|COMPILE_TIME_ERROR|UNDEFINED_OPERATOR|lib/_internal/js_runtime/lib/interceptors.dart|1678|27|1|The operator '&' isn't defined for the type 'JSInt'.
ERROR|COMPILE_TIME_ERROR|UNDEFINED_OPERATOR|lib/_internal/js_runtime/lib/interceptors.dart|1681|17|1|The operator '&' isn't defined for the type 'JSInt'.
ERROR|COMPILE_TIME_ERROR|UNDEFINED_OPERATOR|lib/_internal/js_runtime/lib/interceptors.dart|1686|18|1|The operator '&' isn't defined for the type 'JSInt'.
ERROR|COMPILE_TIME_ERROR|UNDEFINED_OPERATOR|lib/_internal/js_runtime/lib/interceptors.dart|1686|44|1|The operator '&' isn't defined for the type 'JSInt'.
ERROR|COMPILE_TIME_ERROR|INCONSISTENT_INHERITANCE|lib/_internal/js_runtime/lib/interceptors.dart|1706|7|5|Superinterfaces don't have a valid override for '&': JSNumber.& (num Function(num)), int.& (int Function(int)).
ERROR|COMPILE_TIME_ERROR|INCONSISTENT_INHERITANCE|lib/_internal/js_runtime/lib/interceptors.dart|1706|7|5|Superinterfaces don't have a valid override for '<<': JSNumber.<< (num Function(num)), int.<< (int Function(int)).
ERROR|COMPILE_TIME_ERROR|INCONSISTENT_INHERITANCE|lib/_internal/js_runtime/lib/interceptors.dart|1706|7|5|Superinterfaces don't have a valid override for '>>': JSNumber.>> (num Function(num)), int.>> (int Function(int)).
ERROR|COMPILE_TIME_ERROR|INCONSISTENT_INHERITANCE|lib/_internal/js_runtime/lib/interceptors.dart|1706|7|5|Superinterfaces don't have a valid override for '\|': JSNumber.\| (num Function(num)), int.\| (int Function(int)).
ERROR|COMPILE_TIME_ERROR|INCONSISTENT_INHERITANCE|lib/_internal/js_runtime/lib/interceptors.dart|1706|7|5|Superinterfaces don't have a valid override for '^': JSNumber.^ (num Function(num)), int.^ (int Function(int)).
ERROR|COMPILE_TIME_ERROR|RETURN_OF_INVALID_TYPE|lib/_internal/js_runtime/lib/interceptors.dart|1561|14|45|A value of type 'double' can't be returned from method '%' because it has a return type of 'JSNumber'.
ERROR|COMPILE_TIME_ERROR|RETURN_OF_INVALID_TYPE|lib/_internal/js_runtime/lib/interceptors.dart|1563|14|45|A value of type 'double' can't be returned from method '%' because it has a return type of 'JSNumber'.
ERROR|COMPILE_TIME_ERROR|UNDEFINED_OPERATOR|lib/_internal/js_runtime/lib/interceptors.dart|1723|28|1|The operator '&' isn't defined for the type 'JSInt'.
ERROR|COMPILE_TIME_ERROR|UNDEFINED_OPERATOR|lib/_internal/js_runtime/lib/interceptors.dart|1725|27|1|The operator '&' isn't defined for the type 'JSInt'.
ERROR|COMPILE_TIME_ERROR|UNDEFINED_OPERATOR|lib/_internal/js_runtime/lib/interceptors.dart|1728|17|1|The operator '&' isn't defined for the type 'JSInt'.
ERROR|COMPILE_TIME_ERROR|UNDEFINED_OPERATOR|lib/_internal/js_runtime/lib/interceptors.dart|1733|18|1|The operator '&' isn't defined for the type 'JSInt'.
ERROR|COMPILE_TIME_ERROR|UNDEFINED_OPERATOR|lib/_internal/js_runtime/lib/interceptors.dart|1733|44|1|The operator '&' isn't defined for the type 'JSInt'.

View file

@ -103,6 +103,24 @@ class CloneVisitorNotMembers implements TreeVisitor<TreeNode> {
return result;
}
/// Root entry point for cloning a subtree within the same context where the
/// file offsets are valid.
T cloneInContext<T extends TreeNode>(T node) {
assert(_activeFileUri == null);
_activeFileUri = _activeFileUriFromContext(node);
final TreeNode result = clone<T>(node);
_activeFileUri = null;
return result;
}
Uri _activeFileUriFromContext(TreeNode node) {
while (node != null) {
if (node is FileUriNode && node.fileUri != null) return node.fileUri;
node = node.parent;
}
return null;
}
DartType visitType(DartType type) {
return substitute(type, typeSubstitution);
}

View file

@ -26,7 +26,9 @@ class JSArray<E> extends Interceptor implements List<E>, JSIndexable<E> {
return new JSArray<E>.fixed(length);
}
/// Returns a fresh JavaScript Array, marked as fixed-length.
/// Returns a fresh JavaScript Array, marked as fixed-length. The holes in the
/// array yield `undefined`, making the Dart List appear to be filled with
/// `null` values.
///
/// [length] must be a non-negative integer.
factory JSArray.fixed(int length) {
@ -36,8 +38,34 @@ class JSArray<E> extends Interceptor implements List<E>, JSIndexable<E> {
if (length is! int) {
throw new ArgumentError.value(length, 'length', 'is not an integer');
}
// The JavaScript Array constructor with one argument throws if
// the value is not a UInt32. Give a better error message.
// The JavaScript Array constructor with one argument throws if the value is
// not a UInt32 but the error message does not contain the bad value. Give a
// better error message.
int maxJSArrayLength = 0xFFFFFFFF;
if (length < 0 || length > maxJSArrayLength) {
throw new RangeError.range(length, 0, maxJSArrayLength, 'length');
}
return new JSArray<E>.markFixed(JS('', 'new Array(#)', length));
}
/// Returns a fresh JavaScript Array, marked as fixed-length. The Array is
/// allocated but no elements are assigned.
///
/// All elements of the array must be assigned before the array is valid. This
/// is essentially the same as `JSArray.fixed` except that global type
/// inference starts with bottom for the element type.
///
/// [length] must be a non-negative integer.
factory JSArray.allocateFixed(int length) {
// Explicit type test is necessary to guard against JavaScript conversions
// in unchecked mode, and against `new Array(null)` which creates a single
// element Array containing `null`.
if (length is! int) {
throw new ArgumentError.value(length, 'length', 'is not an integer');
}
// The JavaScript Array constructor with one argument throws if the value is
// not a UInt32 but the error message does not contain the bad value. Give a
// better error message.
int maxJSArrayLength = 0xFFFFFFFF;
if (length < 0 || length > maxJSArrayLength) {
throw new RangeError.range(length, 0, maxJSArrayLength, 'length');
@ -48,9 +76,11 @@ class JSArray<E> extends Interceptor implements List<E>, JSIndexable<E> {
/// Returns a fresh growable JavaScript Array of zero length length.
factory JSArray.emptyGrowable() => new JSArray<E>.markGrowable(JS('', '[]'));
/// Returns a fresh growable JavaScript Array with initial length.
/// Returns a fresh growable JavaScript Array with initial length. The holes
/// in the array yield `undefined`, making the Dart List appear to be filled
/// with `null` values.
///
/// [validatedLength] must be a non-negative integer.
/// [length] must be a non-negative integer.
factory JSArray.growable(int length) {
// Explicit type test is necessary to guard against JavaScript conversions
// in unchecked mode.
@ -60,6 +90,23 @@ class JSArray<E> extends Interceptor implements List<E>, JSIndexable<E> {
return new JSArray<E>.markGrowable(JS('', 'new Array(#)', length));
}
/// Returns a fresh growable JavaScript Array with initial length. The Array
/// is allocated but no elements are assigned.
///
/// All elements of the array must be assigned before the array is valid. This
/// is essentially the same as `JSArray.growable` except that global type
/// inference starts with bottom for the element type.
///
/// [length] must be a non-negative integer.
factory JSArray.allocateGrowable(int length) {
// Explicit type test is necessary to guard against JavaScript conversions
// in unchecked mode.
if ((length is! int) || (length < 0)) {
throw new ArgumentError('Length must be a non-negative integer: $length');
}
return new JSArray<E>.markGrowable(JS('', 'new Array(#)', length));
}
/// Constructor for adding type parameters to an existing JavaScript Array.
/// The compiler specially recognizes this constructor.
///

View file

@ -0,0 +1,69 @@
// 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.
import 'package:expect/expect.dart';
dynamic function = 'function';
dynamic growable = 'growable';
dynamic length = 'length';
// Use top level dynamic variables so this call is not lowered. This function
// and the callers (test1, test2) are not inlined to prevent store-forwarding of
// the top-level variables.
@pragma('dart2js:noInline')
List<T> general<T>() => List<T>.generate(length, function, growable: growable);
void main() {
function = (int i) => i;
growable = true;
length = 5;
test1();
int k = 0;
function = (int u) => seq3(u += 10, k += 100, () => u += k + 100000);
growable = false;
length = 5;
test2();
}
@pragma('dart2js:noInline')
void test1() {
// Simple test.
final r1 = List<num>.generate(5, (i) => i, growable: true);
final r2 = general<num>();
Expect.equals('[0, 1, 2, 3, 4]', '$r1');
Expect.equals('[0, 1, 2, 3, 4]', '$r2');
}
// A sequence of two operations in expression form, returning the last value.
T seq2<T>(dynamic a, T b) => b;
T seq3<T>(dynamic a, dynamic b, T c) => c;
@pragma('dart2js:noInline')
void test2() {
// Test with a complex environment.
int c = 0;
final r1 = List<int Function()>.generate(
5,
(i) => seq3(i += 10, c += 100, () => i += c + 100000),
growable: false,
);
final r2 = general<int Function()>();
final e12 = r1[2];
final e22 = r2[2];
final s123 = [e12(), e12(), e12()];
final s223 = [e22(), e22(), e22()];
// 'i' is bound to the loop variable (2 for element at [2]).
// 'i' is incremented by 10, so the low digits are '12'
// 'c' is shared by all closures, and has been incremented to 500 at end of
// construction, so each call increments 'i' by 100500.
Expect.equals('[100512, 201012, 301512]', '$s123');
Expect.equals('[100512, 201012, 301512]', '$s123');
}

View file

@ -0,0 +1,21 @@
// 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.
import 'package:expect/expect.dart';
void main() {
test(0, '[]');
test(1, '[[[1]]]');
test(2, '[[[1]], [[2], [3, 4]]]');
test(3, '[[[1]], [[2], [3, 4]], [[5], [6, 7], [8, 9, 10]]]');
}
void test(int i, String expected) {
// Many nested closures with shadowing variables.
int c = 0;
final r = List.generate(
i, (i) => List.generate(i + 1, (i) => List.generate(i + 1, (i) => ++c)));
Expect.equals(expected, '$r');
}