[dart2js] Use new method encoding in runtime type analysis.

Change-Id: I41e6b6314266203e37badcb084becd95894bdf6c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/352966
Commit-Queue: Mayank Patke <fishythefish@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
This commit is contained in:
Mayank Patke 2024-02-20 21:42:19 +00:00 committed by Commit Queue
parent 3f5a96bbf4
commit 2fc877a09a
3 changed files with 188 additions and 248 deletions

View file

@ -170,14 +170,7 @@ class ImpactBuilder extends ir.RecursiveVisitor implements ImpactRegistry {
@override
void visitStaticGet(ir.StaticGet node) {
final target = node.target;
if (target is ir.Procedure && target.kind == ir.ProcedureKind.Method) {
// TODO(johnniwinther): Remove this when dart2js uses the new method
// invocation encoding.
registerStaticTearOff(target, getDeferredImport(node));
} else {
registerStaticGet(node.target, getDeferredImport(node));
}
registerStaticGet(node.target, getDeferredImport(node));
}
@override
@ -602,10 +595,6 @@ class ImpactBuilder extends ir.RecursiveVisitor implements ImpactRegistry {
void visitDynamicGet(ir.DynamicGet node) {
final receiverType = node.receiver.getStaticType(staticTypeContext);
registerDynamicGet(receiverType, node.name);
if (node.name.text == Identifiers.runtimeType_) {
// This handles `runtimeType` access on `Never`.
handleRuntimeTypeGet(receiverType, node);
}
node.receiver.accept(this);
}
@ -708,11 +697,9 @@ class ImpactBuilder extends ir.RecursiveVisitor implements ImpactRegistry {
node.arguments.accept(this);
}
// TODO(johnniwinther): Change the key to `InstanceGet` when the old method
// invocation encoding is no longer used.
final Map<ir.Expression, RuntimeTypeUseData> _pendingRuntimeTypeUseData = {};
final Map<ir.InstanceGet, RuntimeTypeUseData> _pendingRuntimeTypeUseData = {};
void handleRuntimeTypeGet(ir.DartType receiverType, ir.Expression node) {
void handleRuntimeTypeGet(ir.DartType receiverType, ir.InstanceGet node) {
RuntimeTypeUseData data =
computeRuntimeTypeUse(_pendingRuntimeTypeUseData, node);
if (data.leftRuntimeTypeExpression == node) {

View file

@ -5,7 +5,6 @@
import 'package:kernel/ast.dart' as ir;
import '../common/names.dart';
import 'util.dart';
/// Enum for recognized use kinds of `Object.runtimeType`.
enum RuntimeTypeUseKind {
@ -29,9 +28,7 @@ class RuntimeTypeUseData {
final RuntimeTypeUseKind kind;
/// The property get for the left (or single) occurrence of `.runtimeType`.
// TODO(johnniwinther): Change this to `InstanceGet` when the old method
// invocation encoding is no longer used.
final ir.Expression leftRuntimeTypeExpression;
final ir.InstanceGet leftRuntimeTypeExpression;
/// The receiver expression.
final ir.Expression receiver;
@ -42,9 +39,7 @@ class RuntimeTypeUseData {
/// The property get for the right occurrence of `.runtimeType` when [kind]
/// is `RuntimeTypeUseKind.equals`.
// TODO(johnniwinther): Change this to `InstanceGet` when the old method
// invocation encoding is no longer used.
final ir.Expression? rightRuntimeTypeExpression;
final ir.InstanceGet? rightRuntimeTypeExpression;
/// The argument expression if [kind] is `RuntimeTypeUseKind.equals`.
final ir.Expression? argument;
@ -80,58 +75,41 @@ class RuntimeTypeUseData {
///
/// [cache] is used to ensure that only one [RuntimeTypeUseData] object is
/// created per case, even for the `==` case.
// TODO(johnniwinther): Change [cache] key and [node] to `InstanceGet` when the
// old method invocation encoding is no longer used.
RuntimeTypeUseData computeRuntimeTypeUse(
Map<ir.Expression, RuntimeTypeUseData> cache, ir.Expression node) {
Map<ir.InstanceGet, RuntimeTypeUseData> cache, ir.InstanceGet node) {
RuntimeTypeUseData? receiverData = cache[node];
if (receiverData != null) return receiverData;
/// Returns `true` if [node] is of the form `e.runtimeType`.
bool isGetRuntimeType(ir.TreeNode node) {
return node is ir.InstanceGet &&
node.name.text == Identifiers.runtimeType_ ||
node is ir.DynamicGet && node.name.text == Identifiers.runtimeType_;
}
assert(_isGetRuntimeType(node));
/// Returns `true` if [node] is of the form `e.toString()`.
bool isInvokeToString(ir.TreeNode? node) {
return node is ir.InstanceInvocation && node.name.text == 'toString';
}
assert(isGetRuntimeType(node));
// TODO(johnniwinther): Replace this with `node.receiver` when the old method
// invocation encoding is no longer used.
_RuntimeTypeAccess runtimeTypeAccess = _getRuntimeTypeAccess(node)!;
// TODO(johnniwinther): Change [receiverGet] and [argumentGet] to
// `InstanceGet` when the old method invocation encoding is no longer used.
// TODO(johnniwinther): Special-case `this.runtimeType`.
late final ir.Expression receiverGet;
late final ir.InstanceGet receiverGet;
late final ir.Expression receiver;
ir.Expression? argumentGet;
ir.InstanceGet? argumentGet;
ir.Expression? argument;
RuntimeTypeUseKind? kind;
final nodeParent = node.parent;
final nodeParentParent = nodeParent?.parent;
if (runtimeTypeAccess.receiver is ir.VariableGet &&
if (node.receiver is ir.VariableGet &&
nodeParent is ir.ConditionalExpression &&
nodeParentParent is ir.Let) {
NullAwareExpression? nullAware = getNullAwareExpression(nodeParentParent);
_NullAwareExpression? nullAware = getNullAwareExpression(nodeParentParent);
if (nullAware != null) {
// The node is of the form:
//
// let #t1 = e in #t1 == null ? null : #t1.runtimeType
// ^
if (nullAware.parent is ir.VariableDeclaration &&
nullAware.parent.parent is ir.Let) {
NullAwareExpression? outer =
getNullAwareExpression(nullAware.parent.parent!);
final nullAwareParent = nullAware.parent;
final nullAwareParentParent = nullAwareParent.parent;
if (nullAwareParent is ir.VariableDeclaration &&
nullAwareParentParent is ir.Let) {
_NullAwareExpression? outer =
getNullAwareExpression(nullAwareParentParent);
if (outer != null &&
outer.receiver == nullAware.let &&
isInvokeToString(outer.expression)) {
_isInvokeToString(outer.expression)) {
// Detected
//
// e?.runtimeType?.toString()
@ -146,17 +124,14 @@ RuntimeTypeUseData computeRuntimeTypeUse(
receiver = nullAware.receiver;
receiverGet = node;
}
} else if (_isObjectMethodInvocation(nullAware.parent)) {
_EqualsInvocation? equalsInvocation =
_getEqualsInvocation(nullAware.parent);
if (equalsInvocation != null &&
equalsInvocation.left == nullAware.let) {
} else if (_isObjectMethodInvocation(nullAwareParent)) {
if (nullAwareParent is ir.EqualsCall &&
nullAwareParent.left == nullAware.let) {
// Detected
//
// e0?.runtimeType == other
_RuntimeTypeAccess? otherGetRuntimeType =
_getRuntimeTypeAccess(equalsInvocation.right);
if (otherGetRuntimeType != null) {
if (_extractGetRuntimeType(nullAwareParent.right)
case final getRuntimeType?) {
// Detected
//
// e0?.runtimeType == e1.runtimeType
@ -169,31 +144,33 @@ RuntimeTypeUseData computeRuntimeTypeUse(
kind = RuntimeTypeUseKind.equals;
receiver = nullAware.receiver;
receiverGet = node;
argument = otherGetRuntimeType.receiver;
argumentGet = otherGetRuntimeType.node;
argument = getRuntimeType.receiver;
argumentGet = getRuntimeType;
}
NullAwareExpression? otherNullAware =
getNullAwareExpression(equalsInvocation.right);
if (otherNullAware != null &&
isGetRuntimeType(otherNullAware.expression)) {
// Detected
//
// e0?.runtimeType == e1?.runtimeType
// ^
// encoded as
//
// (let #t1 = e0 in #t1 == null ? null : #t1.runtimeType)
// ^
// .==(let #t2 = e1 in #t2 == null ? null : #t2.runtimeType)
//
kind = RuntimeTypeUseKind.equals;
receiver = nullAware.receiver;
receiverGet = node;
argument = otherNullAware.receiver;
argumentGet = otherNullAware.expression;
_NullAwareExpression? otherNullAware =
getNullAwareExpression(nullAwareParent.right);
if (otherNullAware != null) {
if (_extractGetRuntimeType(otherNullAware.expression)
case final getRuntimeType?) {
// Detected
//
// e0?.runtimeType == e1?.runtimeType
// ^
// encoded as
//
// (let #t1 = e0 in #t1 == null ? null : #t1.runtimeType)
// ^
// .==(let #t2 = e1 in #t2 == null ? null : #t2.runtimeType)
//
kind = RuntimeTypeUseKind.equals;
receiver = nullAware.receiver;
receiverGet = node;
argument = otherNullAware.receiver;
argumentGet = getRuntimeType;
}
}
} else if (isInvokeToString(nullAware.parent)) {
} else if (_isInvokeToString(nullAwareParent)) {
// Detected
//
// e?.runtimeType.toString()
@ -208,20 +185,17 @@ RuntimeTypeUseData computeRuntimeTypeUse(
receiver = nullAware.receiver;
receiverGet = node;
}
} else if (nullAware.parent is ir.Arguments &&
_isObjectMethodInvocation(nullAware.parent.parent)) {
_EqualsInvocation? equalsInvocation =
_getEqualsInvocation(nullAware.parent.parent);
if (equalsInvocation != null &&
equalsInvocation.right == nullAware.let) {
} else if (nullAwareParent is ir.Arguments &&
_isObjectMethodInvocation(nullAwareParentParent)) {
if (nullAwareParentParent is ir.EqualsCall &&
nullAwareParentParent.right == nullAware.let) {
// [nullAware] is the right hand side of ==.
_RuntimeTypeAccess? otherGetRuntimeType =
_getRuntimeTypeAccess(equalsInvocation.left);
NullAwareExpression? otherNullAware =
getNullAwareExpression(equalsInvocation.left);
_NullAwareExpression? otherNullAware =
getNullAwareExpression(nullAwareParentParent.left);
if (otherGetRuntimeType != null) {
if (_extractGetRuntimeType(nullAwareParentParent.left)
case final getRuntimeType?) {
// Detected
//
// e0.runtimeType == e1?.runtimeType
@ -232,31 +206,33 @@ RuntimeTypeUseData computeRuntimeTypeUse(
// let #t1 = e1 in #t1 == null ? null : #t1.runtimeType)
// ^
kind = RuntimeTypeUseKind.equals;
receiver = otherGetRuntimeType.receiver;
receiverGet = otherGetRuntimeType.node;
receiver = getRuntimeType.receiver;
receiverGet = getRuntimeType;
argument = nullAware.receiver;
argumentGet = node;
}
if (otherNullAware != null &&
isGetRuntimeType(otherNullAware.expression)) {
// Detected
//
// e0?.runtimeType == e1?.runtimeType
// ^
// encoded as
//
// (let #t1 = e0 in #t1 == null ? null : #t1.runtimeType)
// .==(let #t2 = e1 in #t2 == null ? null : #t2.runtimeType)
// ^
kind = RuntimeTypeUseKind.equals;
receiver = otherNullAware.receiver;
receiverGet = otherNullAware.expression;
argument = nullAware.receiver;
argumentGet = node;
if (otherNullAware != null) {
if (_extractGetRuntimeType(otherNullAware.expression)
case final getRuntimeType?) {
// Detected
//
// e0?.runtimeType == e1?.runtimeType
// ^
// encoded as
//
// (let #t1 = e0 in #t1 == null ? null : #t1.runtimeType)
// .==(let #t2 = e1 in #t2 == null ? null : #t2.runtimeType)
// ^
kind = RuntimeTypeUseKind.equals;
receiver = otherNullAware.receiver;
receiverGet = getRuntimeType;
argument = nullAware.receiver;
argumentGet = node;
}
}
}
} else if (nullAware.parent is ir.StringConcatenation) {
} else if (nullAwareParent is ir.StringConcatenation) {
// Detected
//
// '${e?.runtimeType}'
@ -284,8 +260,8 @@ RuntimeTypeUseData computeRuntimeTypeUse(
}
} else if (nodeParent is ir.VariableDeclaration &&
nodeParentParent is ir.Let) {
NullAwareExpression? nullAware = getNullAwareExpression(nodeParentParent);
if (nullAware != null && isInvokeToString(nullAware.expression)) {
_NullAwareExpression? nullAware = getNullAwareExpression(nodeParentParent);
if (nullAware != null && _isInvokeToString(nullAware.expression)) {
// Detected
//
// e.runtimeType?.toString()
@ -295,19 +271,16 @@ RuntimeTypeUseData computeRuntimeTypeUse(
// let #t1 = e.runtimeType in #t1 == null ? null : #t1.toString()
// ^
kind = RuntimeTypeUseKind.string;
receiver = runtimeTypeAccess.receiver;
receiver = node.receiver;
receiverGet = node;
}
} else if (_isObjectMethodInvocation(nodeParent)) {
_EqualsInvocation? equalsInvocation = _getEqualsInvocation(nodeParent);
if (equalsInvocation != null && equalsInvocation.left == node) {
if (nodeParent is ir.EqualsCall && nodeParent.left == node) {
// [node] is the left hand side of ==.
_RuntimeTypeAccess? otherGetRuntimeType =
_getRuntimeTypeAccess(equalsInvocation.right);
NullAwareExpression? nullAware =
getNullAwareExpression(equalsInvocation.right);
if (otherGetRuntimeType != null) {
_NullAwareExpression? nullAware =
getNullAwareExpression(nodeParent.right);
if (_extractGetRuntimeType(nodeParent.right) case final getRuntimeType?) {
// Detected
//
// e0.runtimeType == e1.runtimeType
@ -317,47 +290,47 @@ RuntimeTypeUseData computeRuntimeTypeUse(
// e0.runtimeType.==(e1.runtimeType)
// ^
kind = RuntimeTypeUseKind.equals;
receiver = runtimeTypeAccess.receiver;
receiver = node.receiver;
receiverGet = node;
argument = otherGetRuntimeType.receiver;
argumentGet = otherGetRuntimeType.node;
} else if (nullAware != null && isGetRuntimeType(nullAware.expression)) {
// Detected
//
// e0.runtimeType == e1?.runtimeType
// ^
// encoded as
//
// e0.runtimeType.==(
// ^
// let #t1 = e1 in #t1 == null ? null : #t1.runtimeType)
kind = RuntimeTypeUseKind.equals;
receiver = runtimeTypeAccess.receiver;
receiverGet = node;
argument = nullAware.receiver;
argumentGet = nullAware.expression;
argument = getRuntimeType.receiver;
argumentGet = getRuntimeType;
} else if (nullAware != null) {
if (_extractGetRuntimeType(nullAware.expression)
case final getRuntimeType?) {
// Detected
//
// e0.runtimeType == e1?.runtimeType
// ^
// encoded as
//
// e0.runtimeType.==(
// ^
// let #t1 = e1 in #t1 == null ? null : #t1.runtimeType)
kind = RuntimeTypeUseKind.equals;
receiver = node.receiver;
receiverGet = node;
argument = nullAware.receiver;
argumentGet = getRuntimeType;
}
}
} else if (isInvokeToString(nodeParent)) {
} else if (_isInvokeToString(nodeParent)) {
// Detected
//
// e.runtimeType.toString()
// ^
kind = RuntimeTypeUseKind.string;
receiver = runtimeTypeAccess.receiver;
receiver = node.receiver;
receiverGet = node;
}
} else if (nodeParent is ir.Arguments &&
_isObjectMethodInvocation(nodeParentParent)) {
_EqualsInvocation? _equalsInvocation =
_getEqualsInvocation(nodeParentParent);
if (_equalsInvocation != null && _equalsInvocation.right == node) {
if (nodeParentParent is ir.EqualsCall && nodeParentParent.right == node) {
// [node] is the right hand side of ==.
_RuntimeTypeAccess? otherGetRuntimeType =
_getRuntimeTypeAccess(_equalsInvocation.left);
NullAwareExpression? nullAware =
getNullAwareExpression(_equalsInvocation.left);
_NullAwareExpression? nullAware =
getNullAwareExpression(nodeParentParent.left);
if (otherGetRuntimeType != null) {
if (_extractGetRuntimeType(nodeParentParent.left)
case final getRuntimeType?) {
// Detected
//
// e0.runtimeType == e1.runtimeType
@ -367,25 +340,28 @@ RuntimeTypeUseData computeRuntimeTypeUse(
// e0.runtimeType.==(e1.runtimeType)
// ^
kind = RuntimeTypeUseKind.equals;
receiver = otherGetRuntimeType.receiver;
receiverGet = otherGetRuntimeType.node;
argument = runtimeTypeAccess.receiver;
argumentGet = node;
} else if (nullAware != null && isGetRuntimeType(nullAware.expression)) {
// Detected
//
// e0?.runtimeType == e1.runtimeType
// ^
// encoded as
//
// (let #t1 = e0 in #t1 == null ? null : #t1.runtimeType)
// .==(e1.runtimeType)
// ^
kind = RuntimeTypeUseKind.equals;
receiver = nullAware.receiver;
receiverGet = nullAware.expression;
argument = runtimeTypeAccess.receiver;
receiver = getRuntimeType.receiver;
receiverGet = getRuntimeType;
argument = node.receiver;
argumentGet = node;
} else if (nullAware != null) {
if (_extractGetRuntimeType(nullAware.expression)
case final getRuntimeType?) {
// Detected
//
// e0?.runtimeType == e1.runtimeType
// ^
// encoded as
//
// (let #t1 = e0 in #t1 == null ? null : #t1.runtimeType)
// .==(e1.runtimeType)
// ^
kind = RuntimeTypeUseKind.equals;
receiver = nullAware.receiver;
receiverGet = getRuntimeType;
argument = node.receiver;
argumentGet = node;
}
}
}
} else if (nodeParent is ir.StringConcatenation) {
@ -394,7 +370,7 @@ RuntimeTypeUseData computeRuntimeTypeUse(
// '${e.runtimeType}'
// ^
kind = RuntimeTypeUseKind.string;
receiver = runtimeTypeAccess.receiver;
receiver = node.receiver;
receiverGet = node;
}
@ -404,7 +380,7 @@ RuntimeTypeUseData computeRuntimeTypeUse(
// e.runtimeType
// ^
kind = RuntimeTypeUseKind.unknown;
receiver = runtimeTypeAccess.receiver;
receiver = node.receiver;
receiverGet = node;
}
@ -427,37 +403,60 @@ bool _isObjectMethodInvocation(ir.TreeNode? node) {
return node is ir.InstanceInvocation || node is ir.EqualsCall;
}
/// Returns the [_RuntimeTypeAccess] corresponding to [node] if it is an access
/// of `.runtimeType`, and `null` otherwise.
_RuntimeTypeAccess? _getRuntimeTypeAccess(ir.TreeNode node) {
if (node is ir.InstanceGet && node.name.text == 'runtimeType') {
return _RuntimeTypeAccess(node, node.receiver);
} else if (node is ir.DynamicGet && node.name.text == 'runtimeType') {
return _RuntimeTypeAccess(node, node.receiver);
/// Returns `true` if [node] is of the form `e.runtimeType` and `null`
/// otherwise.
bool _isGetRuntimeType(ir.InstanceGet node) =>
node.name.text == Identifiers.runtimeType_;
/// Returns `true` if [node] is of the form `e.toString()`.
bool _isInvokeToString(ir.TreeNode? node) =>
node is ir.InstanceInvocation && node.name.text == 'toString';
ir.InstanceGet? _extractGetRuntimeType(ir.TreeNode node) =>
node is ir.InstanceGet && _isGetRuntimeType(node) ? node : null;
/// Kernel encodes a null-aware expression `a?.b` as
///
/// let final #1 = a in #1 == null ? null : #1.b
///
/// [getNullAwareExpression] recognizes such expressions storing the result in
/// a [_NullAwareExpression] object.
///
/// [syntheticVariable] holds the synthesized `#1` variable. [expression] holds
/// the `#1.b` expression. [receiver] returns `a` expression. [parent] returns
/// the parent of the let node, i.e. the parent node of the original null-aware
/// expression. [let] returns the let node created for the encoding.
class _NullAwareExpression {
final ir.Let let;
final ir.VariableDeclaration syntheticVariable;
final ir.Expression expression;
_NullAwareExpression(this.let, this.syntheticVariable, this.expression);
ir.Expression get receiver => syntheticVariable.initializer!;
ir.TreeNode get parent => let.parent!;
@override
String toString() => let.toString();
}
_NullAwareExpression? getNullAwareExpression(ir.TreeNode node) {
if (node is ir.Let) {
ir.Expression body = node.body;
if (node.variable.name == null &&
node.variable.isFinal &&
body is ir.ConditionalExpression) {
final condition = body.condition;
if (condition is ir.EqualsNull) {
ir.Expression receiver = condition.expression;
if (receiver is ir.VariableGet && receiver.variable == node.variable) {
// We have
// let #t1 = e0 in #t1 == null ? null : e1
return _NullAwareExpression(node, node.variable, body.otherwise);
}
}
}
}
return null;
}
class _RuntimeTypeAccess {
final ir.Expression node;
final ir.Expression receiver;
_RuntimeTypeAccess(this.node, this.receiver);
}
/// Returns the [_EqualsInvocation] corresponding to [node] if it is a call to
/// of `==`, and `null` otherwise.
_EqualsInvocation? _getEqualsInvocation(ir.TreeNode? node) {
if (node is ir.EqualsCall) {
return _EqualsInvocation(node, node.left, node.right);
}
return null;
}
class _EqualsInvocation {
final ir.Expression node;
final ir.Expression left;
final ir.Expression right;
_EqualsInvocation(this.node, this.left, this.right);
}

View file

@ -101,52 +101,6 @@ bool isNullLiteral(ir.Expression node) {
(node is ir.ConstantExpression && node.constant is ir.NullConstant);
}
/// Kernel encodes a null-aware expression `a?.b` as
///
/// let final #1 = a in #1 == null ? null : #1.b
///
/// [getNullAwareExpression] recognizes such expressions storing the result in
/// a [NullAwareExpression] object.
///
/// [syntheticVariable] holds the synthesized `#1` variable. [expression] holds
/// the `#1.b` expression. [receiver] returns `a` expression. [parent] returns
/// the parent of the let node, i.e. the parent node of the original null-aware
/// expression. [let] returns the let node created for the encoding.
class NullAwareExpression {
final ir.Let let;
final ir.VariableDeclaration syntheticVariable;
final ir.Expression expression;
NullAwareExpression(this.let, this.syntheticVariable, this.expression);
ir.Expression get receiver => syntheticVariable.initializer!;
ir.TreeNode get parent => let.parent!;
@override
String toString() => let.toString();
}
NullAwareExpression? getNullAwareExpression(ir.TreeNode node) {
if (node is ir.Let) {
ir.Expression body = node.body;
if (node.variable.name == null &&
node.variable.isFinal &&
body is ir.ConditionalExpression) {
final condition = body.condition;
if (condition is ir.EqualsNull) {
ir.Expression receiver = condition.expression;
if (receiver is ir.VariableGet && receiver.variable == node.variable) {
// We have
// let #t1 = e0 in #t1 == null ? null : e1
return NullAwareExpression(node, node.variable, body.otherwise);
}
}
}
}
return null;
}
/// Check whether [node] is immediately guarded by a
/// [ir.CheckLibraryIsLoaded], and hence the node is a deferred access.
ir.LibraryDependency? getDeferredImport(ir.TreeNode node) {