Implement promotion via initialization in flow analysis.

Change-Id: I10a5f6bfeb5a05dd144db5212c9f31796f5a2385
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/145502
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Paul Berry <paulberry@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
Konstantin Shcheglov 2020-04-30 16:26:27 +00:00 committed by commit-bot@chromium.org
parent a144fedbba
commit cf3a034210
9 changed files with 249 additions and 74 deletions

View file

@ -1482,9 +1482,8 @@ class FlowModel<Variable, Type> {
VariableModel<Variable, Type> infoForVar = variableInfo[variable];
if (infoForVar == null) return this;
Type declaredType = typeOperations.variableType(variable);
VariableModel<Variable, Type> newInfoForVar =
infoForVar.write(declaredType, writtenType, typeOperations);
infoForVar.write(variable, writtenType, typeOperations);
if (identical(newInfoForVar, infoForVar)) return this;
return _updateVariableInfo(variable, newInfoForVar);
@ -1655,6 +1654,11 @@ abstract class TypeOperations<Variable, Type> {
/// consideration by an instance check.
Type factor(Type from, Type what);
/// Return `true` if the [variable] is a local variable (not a formal
/// parameter), and it has no declared type (no explicit type, and not
/// initializer).
bool isLocalVariableWithoutDeclaredType(Variable variable);
/// Returns `true` if [type1] and [type2] are the same type.
bool isSameType(Type type1, Type type2);
@ -1836,13 +1840,18 @@ class VariableModel<Variable, Type> {
/// Returns a new [VariableModel] reflecting the fact that the variable was
/// just written to.
VariableModel<Variable, Type> write(Type declaredType, Type writtenType,
VariableModel<Variable, Type> write(Variable variable, Type writtenType,
TypeOperations<Variable, Type> typeOperations) {
if (writeCaptured) {
return new VariableModel<Variable, Type>(
promotedTypes, tested, true, false, writeCaptured);
}
if (_isPromotableViaInitialization(typeOperations, variable)) {
return new VariableModel<Variable, Type>(
[writtenType], tested, true, false, writeCaptured);
}
List<Type> newPromotedTypes;
if (promotedTypes == null) {
newPromotedTypes = null;
@ -1863,15 +1872,19 @@ class VariableModel<Variable, Type> {
}
}
}
Type declaredType = typeOperations.variableType(variable);
newPromotedTypes = _tryPromoteToTypeOfInterest(
typeOperations, declaredType, newPromotedTypes, writtenType);
if (identical(promotedTypes, newPromotedTypes) && assigned) return this;
List<Type> newTested;
if (newPromotedTypes == null && promotedTypes != null) {
newTested = const [];
} else {
newTested = tested;
}
return new VariableModel<Variable, Type>(
newPromotedTypes, newTested, true, false, writeCaptured);
}
@ -1883,6 +1896,26 @@ class VariableModel<Variable, Type> {
null, const [], assigned, false, true);
}
/// We say that a variable `x` is promotable via initialization given
/// variable model `VM` if `x` is a local variable (not a formal parameter)
/// and:
/// * VM = VariableModel(declared, promoted, tested,
/// assigned, unassigned, captured)
/// * and `captured` is false
/// * and `promoted` is empty
/// * and `x` is declared with no explicit type and no initializer
/// * and `assigned` is false and `unassigned` is true
bool _isPromotableViaInitialization<Variable>(
TypeOperations<Variable, Type> typeOperations,
Variable variable,
) {
return !writeCaptured &&
!assigned &&
unassigned &&
promotedTypes == null &&
typeOperations.isLocalVariableWithoutDeclaredType(variable);
}
/// Determines whether a variable with the given [promotedTypes] should be
/// promoted to [writtenType] based on types of interest. If it should,
/// returns an updated promotion chain; otherwise returns [promotedTypes]

View file

@ -2266,6 +2266,21 @@ main() {
});
});
});
test('promote via initialization', () {
var h = _Harness();
var x = _Var('x', null, isLocalVariableWithoutDeclaredType: true);
var s1 = FlowModel<_Var, _Type>(true).declare(x, false);
expect(s1.variableInfo, {
x: _matchVariableModel(chain: null),
});
var s2 = s1.write(x, _Type('int'), h);
expect(s2.variableInfo, {
x: _matchVariableModel(chain: ['int']),
});
});
});
group('declare', () {
@ -2832,9 +2847,14 @@ main() {
const emptyMap = const <_Var, VariableModel<_Var, _Type>>{};
VariableModel<_Var, _Type> model(List<_Type> promotionChain,
[List<_Type> typesOfInterest]) =>
VariableModel<_Var, _Type>(promotionChain,
typesOfInterest ?? promotionChain ?? [], false, true, false);
{List<_Type> typesOfInterest, bool assigned = false}) =>
VariableModel<_Var, _Type>(
promotionChain,
typesOfInterest ?? promotionChain ?? [],
assigned,
!assigned,
false,
);
group('without input reuse', () {
test('promoted with unpromoted', () {
@ -2963,18 +2983,18 @@ main() {
test('assigned', () {
var h = _Harness();
var intQModel = model([intQType]);
var writtenModel = intQModel.write(intQType, _Type('Object?'), h);
var p1 = {x: writtenModel, y: writtenModel, z: intQModel, w: intQModel};
var p2 = {x: writtenModel, y: intQModel, z: writtenModel, w: intQModel};
var unassigned = model(null, assigned: false);
var assigned = model(null, assigned: true);
var p1 = {x: assigned, y: assigned, z: unassigned, w: unassigned};
var p2 = {x: assigned, y: unassigned, z: assigned, w: unassigned};
var joined = FlowModel.joinVariableInfo(h, p1, p2, emptyMap);
expect(joined, {
x: same(writtenModel),
x: same(assigned),
y: _matchVariableModel(
chain: null, assigned: false, unassigned: false),
z: _matchVariableModel(
chain: null, assigned: false, unassigned: false),
w: same(intQModel)
w: same(unassigned)
});
});
@ -3322,6 +3342,11 @@ class _Harness implements TypeOperations<_Var, _Type> {
if_(isNotType(variableRead(variable), type), ifTrue);
}
@override
bool isLocalVariableWithoutDeclaredType(_Var variable) {
return variable.isLocalVariableWithoutDeclaredType;
}
/// Creates a [LazyExpression] representing an `is!` check, checking whether
/// [subExpression] has the given [type].
LazyExpression isNotType(LazyExpression subExpression, String type) {
@ -3475,10 +3500,14 @@ class _Type {
class _Var {
final String name;
final _Type type;
final bool isLocalVariableWithoutDeclaredType;
_Var(this.name, this.type);
_Var(
this.name,
this.type, {
this.isLocalVariableWithoutDeclaredType = false,
});
@override
String toString() => '$type $name';

View file

@ -0,0 +1,62 @@
// Copyright (c) 2019, 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.
// The tests in this file exercise various kinds of constructs that initialize a
// variable at its declaration site. We verify that the variable is not
// considered "written to" for purposes of defeating type promotions inside
// closures.
parameter(Object a) {
if (a is int) {
() {
/*int*/ a;
};
}
}
localParameter() {
localFunction(Object a) {
if (a is int) {
() {
/*int*/ a;
};
}
}
(Object b) {
if (b is int) {
() {
/*int*/ b;
};
}
};
}
localVariable() {
Object a = 1;
if (a is int) {
() {
/*int*/ a;
};
}
}
class MyStackTrace implements StackTrace {
noSuchMethod(invocation) => super.noSuchMethod(invocation);
}
catchParameters() {
try {} on Object catch (e, st) {
if (e is int) {
() {
/*int*/ e;
};
}
if (st is MyStackTrace) {
() {
/*MyStackTrace*/ st;
};
}
}
}

View file

@ -1,62 +1,95 @@
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// 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.
// The tests in this file exercise various kinds of constructs that initialize a
// variable at its declaration site. We verify that the variable is not
// considered "written to" for purposes of defeating type promotions inside
// closures.
parameter(Object a) {
if (a is int) {
() {
/*int*/ a;
};
}
}
localParameter() {
localFunction(Object a) {
if (a is int) {
() {
/*int*/ a;
};
}
}
(Object b) {
if (b is int) {
() {
/*int*/ b;
};
}
};
}
// The tests in this file exercise "promotable via initialization" part of
// the flow analysis specification.
localVariable() {
Object a = 1;
if (a is int) {
() {
/*int*/ a;
};
}
var x;
x = 1;
/*int*/ x;
x = 2.3;
x;
}
class MyStackTrace implements StackTrace {
noSuchMethod(invocation) => super.noSuchMethod(invocation);
localVariable_hasInitializer(num a) {
var x = a;
x = 1;
x;
}
catchParameters() {
try {} on Object catch (e, st) {
if (e is int) {
() {
/*int*/ e;
};
}
if (st is MyStackTrace) {
() {
/*MyStackTrace*/ st;
};
}
}
localVariable_hasTypeAnnotation() {
num x;
x = 1;
x;
}
localVariable_hasTypeAnnotation_dynamic() {
dynamic x;
x = 1;
x;
}
localVariable_ifElse_differentTypes(bool a) {
var x;
if (a) {
x = 0;
/*int*/ x;
} else {
x = 1.2;
/*double*/ x;
}
x;
}
localVariable_ifElse_sameTypes(bool a) {
var x;
if (a) {
x = 0;
/*int*/ x;
} else {
x = 1;
/*int*/ x;
}
/*int*/ x;
}
localVariable_notDefinitelyUnassigned(bool a) {
var x;
if (a) {
x = 1.2;
}
x = 1;
x;
}
localVariable_notDefinitelyUnassigned_hasLocalFunction() {
var x;
void f() {
// Note, no assignment to 'x', but because 'x' is assigned somewhere in
// the enclosing function, it is not definitely unassigned in 'f'.
// So, when we join after 'f' declaration, we make 'x' not definitely
// unassigned in the enclosing function as well.
}
f();
x = 1;
x;
}
parameter(x) {
x = 1;
x;
}
parameterLocal() {
void f(x) {
x = 1;
x;
}
f(0);
}

View file

@ -355,6 +355,13 @@ class TypeSystemTypeOperations
return typeSystem.factor(from, what);
}
@override
bool isLocalVariableWithoutDeclaredType(PromotableElement variable) {
return variable is LocalVariableElement &&
variable.hasImplicitType &&
variable.initializer == null;
}
@override
bool isSameType(covariant TypeImpl type1, covariant TypeImpl type2) {
return type1 == type2;

View file

@ -16,6 +16,7 @@ import 'package:kernel/ast.dart'
NamedType,
NeverType,
Nullability,
Statement,
TreeNode,
TypeParameter,
TypeParameterType,
@ -35,6 +36,8 @@ import '../builder/constructor_builder.dart';
import '../kernel/forest.dart';
import '../kernel/internal_ast.dart' show VariableDeclarationImpl;
import '../kernel/kernel_builder.dart'
show ClassHierarchyBuilder, ImplicitFieldType;
@ -260,6 +263,14 @@ class TypeOperationsCfe
return factorType(typeEnvironment, from, what);
}
@override
bool isLocalVariableWithoutDeclaredType(VariableDeclaration variable) {
return variable is VariableDeclarationImpl &&
variable.parent is Statement &&
variable.isImplicitlyTyped &&
!variable.hasDeclaredInitializer;
}
// TODO(dmitryas): Consider checking for mutual subtypes instead of ==.
@override
bool isSameType(DartType type1, DartType type2) => type1 == type2;

View file

@ -25,6 +25,13 @@ class DecoratedTypeOperations
return from;
}
@override
bool isLocalVariableWithoutDeclaredType(PromotableElement variable) {
return variable is LocalVariableElement &&
variable.hasImplicitType &&
variable.initializer == null;
}
@override
bool isSameType(DecoratedType type1, DecoratedType type2) {
return type1 == type2;

View file

@ -40391,7 +40391,7 @@ class KeyEvent extends _WrappedEvent implements KeyboardEvent {
view = window;
}
var eventObj;
dynamic eventObj;
// Currently this works on everything but Safari. Safari throws an
// "Attempting to change access mechanism for an unconfigurable property"

View file

@ -55,7 +55,6 @@
},
"org-dartlang-sdk:///sdk_nnbd/lib/_internal/js_runtime/lib/native_typed_data.dart": {
"Dynamic invocation of '>'.": 1,
"Dynamic invocation of '|'.": 3,
"Dynamic invocation of '>='.": 1
},
"org-dartlang-sdk:///sdk_nnbd/lib/_internal/js_runtime/lib/async_patch.dart": {
@ -82,7 +81,6 @@
"Dynamic invocation of 'remove'.": 2,
"Dynamic update to 'dart.dom.html::_innerHtml'.": 1,
"Dynamic access of 'firstChild'.": 2,
"Dynamic invocation of 'append'.": 1,
"Dynamic access of 'tagName'.": 2,
"Dynamic invocation of 'call'.": 2,
"Dynamic invocation of 'dart.dom.html::_initKeyboardEvent'.": 1,
@ -100,11 +98,6 @@
"org-dartlang-sdk:///sdk_nnbd/lib/html/html_common/filtered_element_list.dart": {
"Dynamic invocation of 'remove'.": 1
},
"org-dartlang-sdk:///sdk_nnbd/lib/indexed_db/dart2js/indexed_db_dart2js.dart": {
"Dynamic access of 'onUpgradeNeeded'.": 1,
"Dynamic invocation of 'listen'.": 2,
"Dynamic access of 'onBlocked'.": 1
},
"org-dartlang-sdk:///sdk_nnbd/lib/io/directory_impl.dart": {
"Dynamic invocation of '[]'.": 10
},