blob: d28f38541e62fe7071b25db56454c40b6c581438 [file] [log] [blame]
// Copyright (c) 2012, 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 '../common.dart';
import '../common/codegen.dart' show CodegenRegistry;
import '../common/names.dart' show Selectors;
import '../common/tasks.dart' show Measurer, CompilerTask;
import '../constants/constant_system.dart' as constant_system;
import '../constants/values.dart';
import '../common_elements.dart' show JCommonElements;
import '../elements/entities.dart';
import '../elements/types.dart';
import '../inferrer/abstract_value_domain.dart';
import '../inferrer/types.dart';
import '../js_backend/field_analysis.dart'
show FieldAnalysisData, JFieldAnalysis;
import '../js_backend/backend.dart' show CodegenInputs;
import '../js_backend/native_data.dart' show NativeData;
import '../js_backend/runtime_types_codegen.dart';
import '../js_model/type_recipe.dart'
show
TypeRecipe,
TypeExpressionRecipe,
TypeRecipeAndEnvironmentStructure,
TypeRecipeDomain,
TypeRecipeDomainImpl;
import '../js_backend/specialized_checks.dart';
import '../native/behavior.dart';
import '../options.dart';
import '../universe/selector.dart' show Selector;
import '../universe/side_effects.dart' show SideEffects;
import '../universe/use.dart' show StaticUse;
import '../util/util.dart';
import '../world.dart' show JClosedWorld;
import 'interceptor_simplifier.dart';
import 'logging.dart';
import 'nodes.dart';
import 'types.dart';
import 'types_propagation.dart';
import 'value_range_analyzer.dart';
import 'value_set.dart';
abstract class OptimizationPhase {
String get name;
void visitGraph(HGraph graph);
}
class SsaOptimizerTask extends CompilerTask {
final CompilerOptions _options;
Map<HInstruction, Range> ranges = <HInstruction, Range>{};
Map<MemberEntity, OptimizationTestLog> loggersForTesting;
SsaOptimizerTask(Measurer measurer, this._options) : super(measurer);
@override
String get name => 'SSA optimizer';
void optimize(
MemberEntity member,
HGraph graph,
CodegenInputs codegen,
JClosedWorld closedWorld,
GlobalTypeInferenceResults globalInferenceResults,
CodegenRegistry registry) {
void runPhase(OptimizationPhase phase) {
measureSubtask(phase.name, () => phase.visitGraph(graph));
codegen.tracer.traceGraph(phase.name, graph);
assert(graph.isValid(), 'Graph not valid after ${phase.name}');
}
bool trustPrimitives = _options.trustPrimitives;
Set<HInstruction> boundsChecked = new Set<HInstruction>();
SsaCodeMotion codeMotion;
SsaLoadElimination loadElimination;
TypeRecipeDomain typeRecipeDomain = TypeRecipeDomainImpl();
OptimizationTestLog log;
if (retainDataForTesting) {
loggersForTesting ??= {};
loggersForTesting[member] = log = new OptimizationTestLog();
}
measure(() {
List<OptimizationPhase> phases = <OptimizationPhase>[
// Run trivial instruction simplification first to optimize
// some patterns useful for type conversion.
new SsaInstructionSimplifier(
globalInferenceResults,
_options,
codegen.rtiSubstitutions,
closedWorld,
typeRecipeDomain,
registry,
log),
new SsaTypeConversionInserter(closedWorld),
new SsaRedundantPhiEliminator(),
new SsaDeadPhiEliminator(),
new SsaTypePropagator(globalInferenceResults,
closedWorld.commonElements, closedWorld, log),
// After type propagation, more instructions can be
// simplified.
new SsaInstructionSimplifier(
globalInferenceResults,
_options,
codegen.rtiSubstitutions,
closedWorld,
typeRecipeDomain,
registry,
log),
new SsaCheckInserter(trustPrimitives, closedWorld, boundsChecked),
new SsaInstructionSimplifier(
globalInferenceResults,
_options,
codegen.rtiSubstitutions,
closedWorld,
typeRecipeDomain,
registry,
log),
new SsaCheckInserter(trustPrimitives, closedWorld, boundsChecked),
new SsaTypePropagator(globalInferenceResults,
closedWorld.commonElements, closedWorld, log),
// Run a dead code eliminator before LICM because dead
// interceptors are often in the way of LICM'able instructions.
new SsaDeadCodeEliminator(closedWorld, this),
new SsaGlobalValueNumberer(closedWorld.abstractValueDomain),
// After GVN, some instructions might need their type to be
// updated because they now have different inputs.
new SsaTypePropagator(globalInferenceResults,
closedWorld.commonElements, closedWorld, log),
codeMotion = new SsaCodeMotion(closedWorld.abstractValueDomain),
loadElimination = new SsaLoadElimination(closedWorld),
new SsaRedundantPhiEliminator(),
new SsaDeadPhiEliminator(),
// After GVN and load elimination the same value may be used in code
// controlled by a test on the value, so redo 'conversion insertion' to
// learn from the refined type.
new SsaTypeConversionInserter(closedWorld),
new SsaTypePropagator(globalInferenceResults,
closedWorld.commonElements, closedWorld, log),
new SsaValueRangeAnalyzer(closedWorld, this),
// Previous optimizations may have generated new
// opportunities for instruction simplification.
new SsaInstructionSimplifier(
globalInferenceResults,
_options,
codegen.rtiSubstitutions,
closedWorld,
typeRecipeDomain,
registry,
log),
new SsaCheckInserter(trustPrimitives, closedWorld, boundsChecked),
];
phases.forEach(runPhase);
// Simplifying interceptors is not strictly just an optimization, it is
// required for implementation correctness because the code generator
// assumes it is always performed.
runPhase(new SsaSimplifyInterceptors(closedWorld, member.enclosingClass));
SsaDeadCodeEliminator dce = new SsaDeadCodeEliminator(closedWorld, this);
runPhase(dce);
if (codeMotion.movedCode ||
dce.eliminatedSideEffects ||
dce.newGvnCandidates ||
loadElimination.newGvnCandidates) {
phases = <OptimizationPhase>[
new SsaTypePropagator(globalInferenceResults,
closedWorld.commonElements, closedWorld, log),
new SsaGlobalValueNumberer(closedWorld.abstractValueDomain),
new SsaCodeMotion(closedWorld.abstractValueDomain),
new SsaValueRangeAnalyzer(closedWorld, this),
new SsaInstructionSimplifier(
globalInferenceResults,
_options,
codegen.rtiSubstitutions,
closedWorld,
typeRecipeDomain,
registry,
log),
new SsaCheckInserter(trustPrimitives, closedWorld, boundsChecked),
new SsaSimplifyInterceptors(closedWorld, member.enclosingClass),
new SsaDeadCodeEliminator(closedWorld, this),
];
} else {
phases = <OptimizationPhase>[
new SsaTypePropagator(globalInferenceResults,
closedWorld.commonElements, closedWorld, log),
// Run the simplifier to remove unneeded type checks inserted by
// type propagation.
new SsaInstructionSimplifier(
globalInferenceResults,
_options,
codegen.rtiSubstitutions,
closedWorld,
typeRecipeDomain,
registry,
log),
];
}
phases.forEach(runPhase);
});
}
}
/// Returns `true` if [mask] represents only types that have a length that
/// cannot change. The current implementation is conservative for the purpose
/// of identifying gvn-able lengths and mis-identifies some unions of fixed
/// length indexables (see TODO) as not fixed length.
bool isFixedLength(AbstractValue mask, JClosedWorld closedWorld) {
AbstractValueDomain abstractValueDomain = closedWorld.abstractValueDomain;
if (abstractValueDomain.isContainer(mask) &&
abstractValueDomain.getContainerLength(mask) != null) {
// A container on which we have inferred the length.
return true;
}
// TODO(sra): Recognize any combination of fixed length indexables.
if (abstractValueDomain.isFixedArray(mask).isDefinitelyTrue ||
abstractValueDomain.isStringOrNull(mask).isDefinitelyTrue ||
abstractValueDomain.isTypedArray(mask).isDefinitelyTrue) {
return true;
}
return false;
}
/// If both inputs to known operations are available execute the operation at
/// compile-time.
class SsaInstructionSimplifier extends HBaseVisitor
implements OptimizationPhase {
// We don't produce constant-folded strings longer than this unless they have
// a single use. This protects against exponentially large constant folded
// strings.
static const MAX_SHARED_CONSTANT_FOLDED_STRING_LENGTH = 512;
@override
final String name = "SsaInstructionSimplifier";
final GlobalTypeInferenceResults _globalInferenceResults;
final CompilerOptions _options;
final RuntimeTypesSubstitutions _rtiSubstitutions;
final JClosedWorld _closedWorld;
final TypeRecipeDomain _typeRecipeDomain;
final CodegenRegistry _registry;
final OptimizationTestLog _log;
HGraph _graph;
SsaInstructionSimplifier(
this._globalInferenceResults,
this._options,
this._rtiSubstitutions,
this._closedWorld,
this._typeRecipeDomain,
this._registry,
this._log);
JCommonElements get commonElements => _closedWorld.commonElements;
AbstractValueDomain get _abstractValueDomain =>
_closedWorld.abstractValueDomain;
NativeData get _nativeData => _closedWorld.nativeData;
@override
void visitGraph(HGraph visitee) {
_graph = visitee;
visitDominatorTree(visitee);
}
@override
visitBasicBlock(HBasicBlock block) {
simplifyPhis(block);
HInstruction instruction = block.first;
while (instruction != null) {
HInstruction next = instruction.next;
HInstruction replacement = instruction.accept(this);
if (replacement != instruction) {
block.rewrite(instruction, replacement);
// The intersection of double and int return conflicting, and
// because of our number implementation for JavaScript, it
// might be that an operation thought to return double, can be
// simplified to an int. For example:
// `2.5 * 10`.
if (!(replacement
.isNumberOrNull(_abstractValueDomain)
.isDefinitelyTrue &&
instruction
.isNumberOrNull(_abstractValueDomain)
.isDefinitelyTrue)) {
// If we can replace [instruction] with [replacement], then
// [replacement]'s type can be narrowed.
AbstractValue newType = _abstractValueDomain.intersection(
replacement.instructionType, instruction.instructionType);
replacement.instructionType = newType;
}
// If the replacement instruction does not know its
// source element, use the source element of the
// instruction.
replacement.sourceElement ??= instruction.sourceElement;
replacement.sourceInformation ??= instruction.sourceInformation;
if (!replacement.isInBasicBlock()) {
// The constant folding can return an instruction that is already
// part of the graph (like an input), so we only add the replacement
// if necessary.
block.addAfter(instruction, replacement);
// Visit the replacement as the next instruction in case it
// can also be constant folded away.
next = replacement;
}
block.remove(instruction);
}
instruction = next;
}
}
// Simplify some CFG diamonds to equivalent expressions.
simplifyPhis(HBasicBlock block) {
// Is [block] the join point for a simple diamond that generates a single
// phi node?
if (block.phis.isEmpty) return;
HPhi phi = block.phis.first;
if (phi.next != null) return;
if (block.predecessors.length != 2) return;
assert(phi.inputs.length == 2);
HBasicBlock b1 = block.predecessors[0];
HBasicBlock b2 = block.predecessors[1];
HBasicBlock dominator = block.dominator;
if (!(b1.dominator == dominator && b2.dominator == dominator)) return;
// Extract the controlling condition.
HInstruction controlFlow = dominator.last;
if (controlFlow is! HIf) return;
HInstruction test = controlFlow.inputs.single;
if (test.usedBy.length > 1) return;
bool negated = false;
while (test is HNot) {
test = test.inputs.single;
if (test.usedBy.length > 1) return;
negated = !negated;
}
if (test is! HIdentity) return;
HInstruction tested;
if (test.inputs[0].isNull(_abstractValueDomain).isDefinitelyTrue) {
tested = test.inputs[1];
} else if (test.inputs[1].isNull(_abstractValueDomain).isDefinitelyTrue) {
tested = test.inputs[0];
} else {
return;
}
HInstruction whenNullValue = phi.inputs[negated ? 1 : 0];
HInstruction whenNotNullValue = phi.inputs[negated ? 0 : 1];
HBasicBlock whenNullBlock = block.predecessors[negated ? 1 : 0];
HBasicBlock whenNotNullBlock = block.predecessors[negated ? 0 : 1];
// If 'x' is nullable boolean,
//
// x == null ? false : x ---> x == true
//
// This ofen comes from the dart code `x ?? false`.
if (_sameOrRefinementOf(tested, whenNotNullValue) &&
_isBoolConstant(whenNullValue, false) &&
whenNotNullValue
.isBooleanOrNull(_abstractValueDomain)
.isDefinitelyTrue &&
_mostlyEmpty(whenNullBlock) &&
_mostlyEmpty(whenNotNullBlock)) {
HInstruction trueConstant = _graph.addConstantBool(true, _closedWorld);
HInstruction replacement = new HIdentity(
tested, trueConstant, null, _abstractValueDomain.boolType)
..sourceElement = phi.sourceElement
..sourceInformation = phi.sourceInformation;
block.rewrite(phi, replacement);
block.addAtEntry(replacement);
block.removePhi(phi);
return;
}
// If 'x'is nullable boolean,
//
// x == null ? true : x ---> !(x == false)
//
// This ofen comes from the dart code `x ?? true`.
if (_sameOrRefinementOf(tested, whenNotNullValue) &&
_isBoolConstant(whenNullValue, true) &&
whenNotNullValue
.isBooleanOrNull(_abstractValueDomain)
.isDefinitelyTrue &&
_mostlyEmpty(whenNullBlock) &&
_mostlyEmpty(whenNotNullBlock)) {
HInstruction falseConstant = _graph.addConstantBool(false, _closedWorld);
HInstruction compare = new HIdentity(
tested, falseConstant, null, _abstractValueDomain.boolType);
block.addAtEntry(compare);
HInstruction replacement =
new HNot(compare, _abstractValueDomain.boolType)
..sourceElement = phi.sourceElement
..sourceInformation = phi.sourceInformation;
block.rewrite(phi, replacement);
block.addAfter(compare, replacement);
block.removePhi(phi);
return;
}
// TODO(sra): Consider other simple diamonds, e.g. with the addition of a special instruction,
//
// s == null ? "" : s ---> s || "".
return;
}
bool _isBoolConstant(HInstruction node, bool value) {
if (node is HConstant) {
ConstantValue c = node.constant;
if (c is BoolConstantValue) {
return c.boolValue == value;
}
}
return false;
}
bool _sameOrRefinementOf(HInstruction base, HInstruction insn) {
if (base == insn) return true;
if (insn is HTypeKnown) return _sameOrRefinementOf(base, insn.checkedInput);
return false;
}
bool _mostlyEmpty(HBasicBlock block) {
for (HInstruction insn = block.first; insn != null; insn = insn.next) {
if (insn is HTypeKnown) continue;
if (insn is HGoto) return true;
return false;
}
return true;
}
@override
HInstruction visitInstruction(HInstruction node) {
return node;
}
ConstantValue getConstantFromType(HInstruction node) {
if (node.isValue(_abstractValueDomain) &&
node.isNull(_abstractValueDomain).isDefinitelyFalse) {
ConstantValue value =
_abstractValueDomain.getPrimitiveValue(node.instructionType);
if (value.isBool) {
return value;
}
// TODO(het): consider supporting other values (short strings?)
}
return null;
}
void propagateConstantValueToUses(HInstruction node) {
if (node.usedBy.isEmpty) return;
ConstantValue value = getConstantFromType(node);
if (value != null) {
HConstant constant = _graph.addConstant(value, _closedWorld);
for (HInstruction user in node.usedBy.toList()) {
user.changeUse(node, constant);
}
}
}
@override
HInstruction visitParameterValue(HParameterValue node) {
// [HParameterValue]s are either the value of the parameter (in fully SSA
// converted code), or the mutable variable containing the value (in
// incompletely SSA converted code, e.g. methods containing exceptions).
//
// If the parameter is used as a mutable variable we cannot replace the
// variable with a value.
//
// If the parameter is used as a mutable variable but never written, first
// convert to a value parameter.
if (node.usedAsVariable()) {
if (!node.usedBy.every((user) => user is HLocalGet)) return node;
// Trivial SSA-conversion. Replace all HLocalGet instructions with the
// parameter.
for (HLocalGet user in node.usedBy.toList()) {
user.block.rewrite(user, node);
user.block.remove(user);
}
}
propagateConstantValueToUses(node);
return node;
}
@override
HInstruction visitNot(HNot node) {
List<HInstruction> inputs = node.inputs;
assert(inputs.length == 1);
HInstruction input = inputs[0];
if (input is HConstant) {
HConstant constant = input;
bool isTrue = constant.constant.isTrue;
return _graph.addConstantBool(!isTrue, _closedWorld);
} else if (input is HNot) {
return input.inputs[0];
}
return node;
}
@override
HInstruction visitInvokeUnary(HInvokeUnary node) {
HInstruction folded = foldUnary(node.operation(), node.operand);
return folded != null ? folded : node;
}
HInstruction foldUnary(
constant_system.UnaryOperation operation, HInstruction operand) {
if (operand is HConstant) {
HConstant receiver = operand;
ConstantValue folded = operation.fold(receiver.constant);
if (folded != null) return _graph.addConstant(folded, _closedWorld);
}
return null;
}
HInstruction tryOptimizeLengthInterceptedGetter(HInvokeDynamic node) {
HInstruction actualReceiver = node.inputs[1];
if (actualReceiver
.isIndexablePrimitive(_abstractValueDomain)
.isDefinitelyTrue) {
if (actualReceiver.isConstantString()) {
HConstant constantInput = actualReceiver;
StringConstantValue constant = constantInput.constant;
return _graph.addConstantInt(constant.length, _closedWorld);
} else if (actualReceiver.isConstantList()) {
HConstant constantInput = actualReceiver;
ListConstantValue constant = constantInput.constant;
return _graph.addConstantInt(constant.length, _closedWorld);
}
bool isFixed =
isFixedLength(actualReceiver.instructionType, _closedWorld);
AbstractValue actualType = node.instructionType;
AbstractValue resultType = _abstractValueDomain.positiveIntType;
// If we already have computed a more specific type, keep that type.
if (_abstractValueDomain
.isInstanceOfOrNull(actualType, commonElements.jsUInt31Class)
.isDefinitelyTrue) {
resultType = _abstractValueDomain.uint31Type;
} else if (_abstractValueDomain
.isInstanceOfOrNull(actualType, commonElements.jsUInt32Class)
.isDefinitelyTrue) {
resultType = _abstractValueDomain.uint32Type;
}
HGetLength result =
new HGetLength(actualReceiver, resultType, isAssignable: !isFixed);
return result;
} else if (actualReceiver.isConstantMap()) {
HConstant constantInput = actualReceiver;
MapConstantValue constant = constantInput.constant;
return _graph.addConstantInt(constant.length, _closedWorld);
}
return null;
}
HInstruction handleInterceptedCall(HInvokeDynamic node) {
// Try constant folding the instruction.
constant_system.Operation operation = node.specializer.operation();
if (operation != null) {
HInstruction instruction = node.inputs.length == 2
? foldUnary(operation, node.inputs[1])
: foldBinary(operation, node.inputs[1], node.inputs[2]);
if (instruction != null) return instruction;
}
// Try converting the instruction to a builtin instruction.
HInstruction instruction = node.specializer.tryConvertToBuiltin(node,
_graph, _globalInferenceResults, commonElements, _closedWorld, _log);
if (instruction != null) {
return instruction;
}
Selector selector = node.selector;
AbstractValue mask = node.receiverType;
HInstruction input = node.inputs[1];
bool applies(MemberEntity element) {
return selector.applies(element) &&
(mask == null ||
_abstractValueDomain
.isTargetingMember(mask, element, selector.memberName)
.isPotentiallyTrue);
}
if (selector.isCall || selector.isOperator) {
FunctionEntity target;
if (input.isExtendableArray(_abstractValueDomain).isDefinitelyTrue) {
if (applies(commonElements.jsArrayRemoveLast)) {
target = commonElements.jsArrayRemoveLast;
} else if (applies(commonElements.jsArrayAdd)) {
// The codegen special cases array calls, but does not
// inline argument type checks.
if (!_closedWorld.annotationsData
.getParameterCheckPolicy(commonElements.jsArrayAdd)
.isEmitted ||
input is HLiteralList) {
target = commonElements.jsArrayAdd;
}
}
} else if (input.isStringOrNull(_abstractValueDomain).isDefinitelyTrue) {
if (commonElements.appliesToJsStringSplit(
selector, mask, _abstractValueDomain)) {
return handleStringSplit(node);
} else if (applies(commonElements.jsStringOperatorAdd)) {
// `operator+` is turned into a JavaScript '+' so we need to
// make sure the receiver and the argument are not null.
// TODO(sra): Do this via [node.specializer].
HInstruction argument = node.inputs[2];
if (argument.isString(_abstractValueDomain).isDefinitelyTrue &&
input.isNull(_abstractValueDomain).isDefinitelyFalse) {
return new HStringConcat(input, argument, node.instructionType);
}
} else if (applies(commonElements.jsStringToString) &&
input.isNull(_abstractValueDomain).isDefinitelyFalse) {
return input;
}
}
if (target != null) {
// TODO(ngeoffray): There is a strong dependency between codegen
// and this optimization that the dynamic invoke does not need an
// interceptor. We currently need to keep a
// HInvokeDynamicMethod and not create a HForeign because
// HForeign is too opaque for the SsaCheckInserter (that adds a
// bounds check on removeLast). Once we start inlining, the
// bounds check will become explicit, so we won't need this
// optimization.
HInvokeDynamicMethod result = new HInvokeDynamicMethod(
node.selector,
node.receiverType,
node.inputs.sublist(1), // Drop interceptor.
node.instructionType,
node.typeArguments,
node.sourceInformation,
isIntercepted: false);
result.element = target;
return result;
}
} else if (selector.isGetter) {
if (commonElements.appliesToJsIndexableLength(selector)) {
HInstruction optimized = tryOptimizeLengthInterceptedGetter(node);
if (optimized != null) return optimized;
}
}
return node;
}
HInstruction handleStringSplit(HInvokeDynamic node) {
HInstruction argument = node.inputs[2];
if (!argument.isString(_abstractValueDomain).isDefinitelyTrue) {
return node;
}
// Replace `s.split$1(pattern)` with
//
// t1 = s.split(pattern);
// t2 = String;
// t3 = JSArray<t2>;
// t4 = setRuntimeTypeInfo(t1, t3);
//
AbstractValue resultMask = _abstractValueDomain.growableListType;
HInvokeDynamicMethod splitInstruction = new HInvokeDynamicMethod(
node.selector,
node.receiverType,
node.inputs.sublist(1), // Drop interceptor.
resultMask,
const <DartType>[],
node.sourceInformation,
isIntercepted: false)
..element = commonElements.jsStringSplit
..setAllocation(true);
if (!_closedWorld.rtiNeed
.classNeedsTypeArguments(commonElements.jsArrayClass)) {
return splitInstruction;
}
node.block.addBefore(node, splitInstruction);
HInstruction typeInfo;
if (_options.experimentNewRti) {
typeInfo = HLoadType.type(
_closedWorld.elementEnvironment.createInterfaceType(
commonElements.jsArrayClass, [commonElements.stringType]),
_abstractValueDomain.dynamicType);
node.block.addBefore(node, typeInfo);
} else {
HInstruction stringTypeInfo = new HTypeInfoExpression(
TypeInfoExpressionKind.COMPLETE,
_closedWorld.elementEnvironment
.getThisType(commonElements.stringClass),
<HInstruction>[],
_abstractValueDomain.dynamicType);
node.block.addBefore(node, stringTypeInfo);
typeInfo = new HTypeInfoExpression(
TypeInfoExpressionKind.INSTANCE,
_closedWorld.elementEnvironment
.getThisType(commonElements.jsArrayClass),
<HInstruction>[stringTypeInfo],
_abstractValueDomain.dynamicType);
node.block.addBefore(node, typeInfo);
}
HInvokeStatic tagInstruction = new HInvokeStatic(
commonElements.setRuntimeTypeInfo,
<HInstruction>[splitInstruction, typeInfo],
resultMask,
const <DartType>[]);
// 'Linear typing' trick: [tagInstruction] is the only use of the
// [splitInstruction], so it becomes the sole alias.
// TODO(sra): Build this knowledge into alias analysis.
tagInstruction.setAllocation(true);
return tagInstruction;
}
@override
HInstruction visitInvokeDynamicMethod(HInvokeDynamicMethod node) {
propagateConstantValueToUses(node);
if (node.isInterceptedCall) {
HInstruction folded = handleInterceptedCall(node);
if (folded != node) return folded;
}
HInstruction receiver = node.getDartReceiver(_closedWorld);
AbstractValue receiverType = receiver.instructionType;
MemberEntity element =
_closedWorld.locateSingleMember(node.selector, receiverType);
// TODO(ngeoffray): Also fold if it's a getter or variable.
if (element != null &&
element.isFunction &&
// If we found out that the only target is an implicitly called
// [:noSuchMethod:] we just ignore it.
node.selector.applies(element)) {
FunctionEntity method = element;
if (_nativeData.isNativeMember(method)) {
HInstruction folded = tryInlineNativeMethod(node, method);
if (folded != null) return folded;
} else {
// TODO(ngeoffray): If the method has optional parameters,
// we should pass the default values.
ParameterStructure parameters = method.parameterStructure;
if (parameters.totalParameters == node.selector.argumentCount &&
parameters.typeParameters ==
node.selector.callStructure.typeArgumentCount) {
node.element = method;
}
}
return node;
}
// Replace method calls through fields with a closure call on the value of
// the field. This usually removes the demand for the call-through stub and
// makes the field load available to further optimization, e.g. LICM.
if (element != null &&
element.isField &&
element.name == node.selector.name) {
FieldEntity field = element;
if (!_nativeData.isNativeMember(field) &&
!node.isCallOnInterceptor(_closedWorld)) {
// Insertion point for the closure call.
HInstruction insertionPoint = node;
HInstruction load;
FieldAnalysisData fieldData =
_closedWorld.fieldAnalysis.getFieldData(field);
if (fieldData.isEffectivelyConstant) {
// The field is elided and replace it with its constant value.
if (_abstractValueDomain.isNull(receiverType).isPotentiallyTrue) {
// The receiver is potentially `null` so we insert a null receiver
// guard to trigger a null pointer exception.
//
// This could be inserted unconditionally and removed by later
// optimizations if unnecessary, but performance we do it
// conditionally here.
// TODO(35996): Replace with null receiver guard instruction.
HInstruction dummyGet = new HFieldGet(null, receiver,
_abstractValueDomain.dynamicType, node.sourceInformation,
isAssignable: false);
_log?.registerFieldCall(node, dummyGet);
node.block.addBefore(node, dummyGet);
insertionPoint = dummyGet;
}
ConstantValue value = fieldData.constantValue;
load = _graph.addConstant(value, _closedWorld,
sourceInformation: node.sourceInformation);
_log?.registerConstantFieldCall(node, field, load);
} else {
AbstractValue type = AbstractValueFactory.inferredTypeForMember(
field, _globalInferenceResults);
load = new HFieldGet(field, receiver, type, node.sourceInformation);
_log?.registerFieldCall(node, load);
node.block.addBefore(node, load);
insertionPoint = load;
}
Selector callSelector = new Selector.callClosureFrom(node.selector);
List<HInstruction> inputs = <HInstruction>[load]
..addAll(node.inputs.skip(node.isInterceptedCall ? 2 : 1));
DartType fieldType =
_closedWorld.elementEnvironment.getFieldType(field);
HInstruction closureCall = new HInvokeClosure(
callSelector,
_abstractValueDomain
.createFromStaticType(fieldType, nullable: true)
.abstractValue,
inputs,
node.instructionType,
node.typeArguments)
..sourceInformation = node.sourceInformation;
node.block.addAfter(insertionPoint, closureCall);
return closureCall;
}
}
return node;
}
HInstruction tryInlineNativeMethod(
HInvokeDynamicMethod node, FunctionEntity method) {
// We can replace the call to the native class interceptor method (target)
// if the target does no conversions or useful type checks.
if (_options.disableInlining) return null;
if (_closedWorld.annotationsData.hasNoInline(method)) {
return null;
}
if (!node.isInterceptedCall) return null;
FunctionType type = _closedWorld.elementEnvironment.getFunctionType(method);
if (type.namedParameters.isNotEmpty) return null;
// The call site might omit optional arguments. The inlined code must
// preserve the number of arguments, so check only the actual arguments.
bool canInline = true;
List<HInstruction> inputs = node.inputs;
int inputPosition = 2; // Skip interceptor and receiver.
void checkParameterType(DartType parameterType) {
if (!canInline) return;
if (inputPosition >= inputs.length) return;
HInstruction input = inputs[inputPosition++];
if (parameterType.unaliased.isFunctionType) {
// Must call the target since it contains a function conversion.
canInline = false;
return;
}
// If the target has no checks don't let a bad type stop us inlining.
if (!_closedWorld.annotationsData
.getParameterCheckPolicy(method)
.isEmitted) {
return;
}
AbstractValue parameterAbstractValue = _abstractValueDomain
.getAbstractValueForNativeMethodParameterType(parameterType);
if (parameterAbstractValue == null ||
_abstractValueDomain
.isIn(input.instructionType, parameterAbstractValue)
.isPotentiallyFalse) {
canInline = false;
return;
}
}
type.parameterTypes.forEach(checkParameterType);
type.optionalParameterTypes.forEach(checkParameterType);
assert(type.namedParameterTypes.isEmpty);
if (!canInline) return null;
// Strengthen instruction type from annotations to help optimize
// dependent instructions.
NativeBehavior nativeBehavior = _nativeData.getNativeMethodBehavior(method);
AbstractValue returnType =
AbstractValueFactory.fromNativeBehavior(nativeBehavior, _closedWorld);
HInvokeDynamicMethod result = new HInvokeDynamicMethod(
node.selector,
node.receiverType,
inputs.sublist(1), // Drop interceptor.
returnType,
node.typeArguments,
node.sourceInformation);
result.element = method;
_registry.registerStaticUse(new StaticUse.methodInlining(method, null));
return result;
}
@override
HInstruction visitBoundsCheck(HBoundsCheck node) {
HInstruction index = node.index;
if (index.isInteger(_abstractValueDomain).isDefinitelyTrue) {
return node;
}
if (index.isConstant()) {
HConstant constantInstruction = index;
assert(!constantInstruction.constant.isInt);
if (!constant_system.isInt(constantInstruction.constant)) {
// -0.0 is a double but will pass the runtime integer check.
node.staticChecks = HBoundsCheck.ALWAYS_FALSE;
}
}
return node;
}
HInstruction foldBinary(constant_system.BinaryOperation operation,
HInstruction left, HInstruction right) {
if (left is HConstant && right is HConstant) {
HConstant op1 = left;
HConstant op2 = right;
ConstantValue folded = operation.fold(op1.constant, op2.constant);
if (folded != null) return _graph.addConstant(folded, _closedWorld);
}
return null;
}
@override
HInstruction visitAdd(HAdd node) {
HInstruction left = node.left;
HInstruction right = node.right;
// We can only perform this rewriting on Integer, as it is not
// valid for -0.0.
if (left.isInteger(_abstractValueDomain).isDefinitelyTrue &&
right.isInteger(_abstractValueDomain).isDefinitelyTrue) {
if (left is HConstant && left.constant.isZero) return right;
if (right is HConstant && right.constant.isZero) return left;
}
return super.visitAdd(node);
}
@override
HInstruction visitMultiply(HMultiply node) {
HInstruction left = node.left;
HInstruction right = node.right;
if (left.isNumber(_abstractValueDomain).isDefinitelyTrue &&
right.isNumber(_abstractValueDomain).isDefinitelyTrue) {
if (left is HConstant && left.constant.isOne) return right;
if (right is HConstant && right.constant.isOne) return left;
}
return super.visitMultiply(node);
}
@override
HInstruction visitInvokeBinary(HInvokeBinary node) {
HInstruction left = node.left;
HInstruction right = node.right;
constant_system.BinaryOperation operation = node.operation();
HConstant folded = foldBinary(operation, left, right);
if (folded != null) return folded;
return node;
}
@override
HInstruction visitRelational(HRelational node) {
return super.visitRelational(node);
}
HInstruction handleIdentityCheck(HRelational node) {
HInstruction left = node.left;
HInstruction right = node.right;
AbstractValue leftType = left.instructionType;
AbstractValue rightType = right.instructionType;
HInstruction makeTrue() => _graph.addConstantBool(true, _closedWorld);
HInstruction makeFalse() => _graph.addConstantBool(false, _closedWorld);
// Intersection of int and double return conflicting, so
// we don't optimize on numbers to preserve the runtime semantics.
if (!(left.isNumberOrNull(_abstractValueDomain).isDefinitelyTrue &&
right.isNumberOrNull(_abstractValueDomain).isDefinitelyTrue)) {
if (_abstractValueDomain
.areDisjoint(leftType, rightType)
.isDefinitelyTrue) {
return makeFalse();
}
}
if (left.isNull(_abstractValueDomain).isDefinitelyTrue &&
right.isNull(_abstractValueDomain).isDefinitelyTrue) {
return makeTrue();
}
HInstruction compareConstant(HConstant constant, HInstruction input) {
if (constant.constant.isTrue) {
return input;
} else {
return new HNot(input, _abstractValueDomain.boolType);
}
}
if (left.isConstantBoolean() &&
right.isBoolean(_abstractValueDomain).isDefinitelyTrue) {
return compareConstant(left, right);
}
if (right.isConstantBoolean() &&
left.isBoolean(_abstractValueDomain).isDefinitelyTrue) {
return compareConstant(right, left);
}
if (identical(left.nonCheck(), right.nonCheck())) {
// Avoid constant-folding `identical(x, x)` when `x` might be double. The
// dart2js runtime has not always been consistent with the Dart
// specification (section 16.0.1), which makes distinctions on NaNs and
// -0.0 that are hard to implement efficiently.
if (left.isIntegerOrNull(_abstractValueDomain).isDefinitelyTrue) {
return makeTrue();
}
if (left.isPrimitiveNumber(_abstractValueDomain).isDefinitelyFalse) {
return makeTrue();
}
}
return null;
}
@override
HInstruction visitIdentity(HIdentity node) {
HInstruction newInstruction = handleIdentityCheck(node);
return newInstruction == null ? super.visitIdentity(node) : newInstruction;
}
void simplifyCondition(
HBasicBlock block, HInstruction condition, bool value) {
// `excludePhiOutEdges: true` prevents replacing a partially dominated phi
// node input with a constant. This tends to add unnecessary assignments, by
// transforming the following, which has phi(false, x),
//
// if (x) { init(); x = false; }
//
// into this, which has phi(false, false)
//
// if (x) { init(); x = false; } else { x = false; }
//
// which is further simplified to:
//
// if (x) { init(); }
// ...
// x = false;
//
// This is mostly harmless (if a little confusing) but does cause a lot of
// `x = false;` copies to be inserted when a loop body has many continue
// statements or ends with a switch.
DominatedUses uses =
DominatedUses.of(condition, block.first, excludePhiOutEdges: true);
if (uses.isEmpty) return;
uses.replaceWith(_graph.addConstantBool(value, _closedWorld));
}
@override
HInstruction visitIf(HIf node) {
HInstruction condition = node.condition;
if (condition.isConstant()) return node;
bool isNegated = condition is HNot;
if (isNegated) {
condition = condition.inputs[0];
} else {
// It is possible for LICM to move a negated version of the
// condition out of the loop where it used. We still want to
// simplify the nested use of the condition in that case, so
// we look for all dominating negated conditions and replace
// nested uses of them with true or false.
Iterable<HInstruction> dominating = condition.usedBy
.where((user) => user is HNot && user.dominates(node));
dominating.forEach((hoisted) {
simplifyCondition(node.thenBlock, hoisted, false);
simplifyCondition(node.elseBlock, hoisted, true);
});
}
simplifyCondition(node.thenBlock, condition, !isNegated);
simplifyCondition(node.elseBlock, condition, isNegated);
return node;
}
@override
HInstruction visitIs(HIs node) {
DartType type = node.typeExpression;
if (!node.isRawCheck) {
return node;
} else if (type.isTypedef) {
return node;
} else if (type.isFunctionType) {
return node;
} else if (type.isFutureOr) {
return node;
}
if (type == commonElements.objectType || type.treatAsDynamic) {
return _graph.addConstantBool(true, _closedWorld);
}
InterfaceType interfaceType = type;
ClassEntity element = interfaceType.element;
HInstruction expression = node.expression;
if (expression.isInteger(_abstractValueDomain).isDefinitelyTrue) {
if (element == commonElements.intClass ||
element == commonElements.numClass ||
commonElements.isNumberOrStringSupertype(element)) {
return _graph.addConstantBool(true, _closedWorld);
} else if (element == commonElements.doubleClass) {
// We let the JS semantics decide for that check. Currently
// the code we emit will always return true.
return node;
} else {
return _graph.addConstantBool(false, _closedWorld);
}
} else if (expression.isDouble(_abstractValueDomain).isDefinitelyTrue) {
if (element == commonElements.doubleClass ||
element == commonElements.numClass ||
commonElements.isNumberOrStringSupertype(element)) {
return _graph.addConstantBool(true, _closedWorld);
} else if (element == commonElements.intClass) {
// We let the JS semantics decide for that check. Currently
// the code we emit will return true for a double that can be
// represented as a 31-bit integer and for -0.0.
return node;
} else {
return _graph.addConstantBool(false, _closedWorld);
}
} else if (expression.isNumber(_abstractValueDomain).isDefinitelyTrue) {
if (element == commonElements.numClass) {
return _graph.addConstantBool(true, _closedWorld);
} else {
// We cannot just return false, because the expression may be of
// type int or double.
}
} else if (expression
.isPrimitiveNumber(_abstractValueDomain)
.isPotentiallyTrue &&
element == commonElements.intClass) {
// We let the JS semantics decide for that check.
return node;
// We need the [:hasTypeArguments:] check because we don't have
// the notion of generics in the backend. For example, [:this:] in
// a class [:A<T>:], is currently always considered to have the
// raw type.
} else if (!RuntimeTypesSubstitutions.hasTypeArguments(type)) {
AbstractValue expressionMask = expression.instructionType;
AbstractBool isInstanceOf =
_abstractValueDomain.isInstanceOf(expressionMask, element);
if (isInstanceOf.isDefinitelyTrue) {
return _graph.addConstantBool(true, _closedWorld);
} else if (isInstanceOf.isDefinitelyFalse) {
return _graph.addConstantBool(false, _closedWorld);
}
}
return node;
}
@override
HInstruction visitTypeConversion(HTypeConversion node) {
if (node.isRedundant(_closedWorld)) return node.checkedInput;
// Simplify 'as T' where T is a simple type.
DartType checkedType = node.typeExpression;
if (checkedType is TypeVariableType && node.inputs.length == 2) {
HInstruction rep = node.typeRepresentation;
if (rep is HTypeInfoExpression &&
rep.kind == TypeInfoExpressionKind.COMPLETE &&
rep.inputs.isEmpty) {
DartType type = rep.dartType;
if (type.isInterfaceType && type.treatAsRaw) {
return node.checkedInput.convertType(_closedWorld, type, node.kind)
..sourceInformation = node.sourceInformation;
}
}
}
return node;
}
@override
HInstruction visitPrimitiveCheck(HPrimitiveCheck node) {
if (node.isRedundant(_closedWorld)) return node.checkedInput;
return node;
}
@override
HInstruction visitBoolConversion(HBoolConversion node) {
if (node.isRedundant(_closedWorld)) return node.checkedInput;
return node;
}
@override
HInstruction visitTypeKnown(HTypeKnown node) {
return node.isRedundant(_closedWorld) ? node.checkedInput : node;
}
FieldEntity findConcreteFieldForDynamicAccess(
HInvokeDynamicField node, HInstruction receiver) {
AbstractValue receiverType = receiver.instructionType;
MemberEntity member = node.element is FieldEntity
? node.element
: _closedWorld.locateSingleMember(node.selector, receiverType);
return member is FieldEntity ? member : null;
}
@override
HInstruction visitFieldGet(HFieldGet node) {
if (node.isNullCheck) return node;
var receiver = node.receiver;
// HFieldGet of a constructed constant can be replaced with the constant's
// field.
if (receiver is HConstant) {
ConstantValue constant = receiver.constant;
if (constant.isConstructedObject) {
ConstructedConstantValue constructedConstant = constant;
Map<FieldEntity, ConstantValue> fields = constructedConstant.fields;
ConstantValue value = fields[node.element];
if (value != null) {
return _graph.addConstant(value, _closedWorld);
}
}
}
return node;
}
@override
HInstruction visitGetLength(HGetLength node) {
HInstruction receiver = node.receiver;
if (_graph.allocatedFixedLists.contains(receiver)) {
// TODO(ngeoffray): checking if the second input is an integer
// should not be necessary but it currently makes it easier for
// other optimizations to reason about a fixed length constructor
// that we know takes an int.
if (receiver.inputs[0].isInteger(_abstractValueDomain).isDefinitelyTrue) {
return receiver.inputs[0];
}
} else if (receiver.isConstantList()) {
HConstant constantReceiver = receiver;
ListConstantValue constant = constantReceiver.constant;
return _graph.addConstantInt(constant.length, _closedWorld);
} else if (receiver.isConstantString()) {
HConstant constantReceiver = receiver;
StringConstantValue constant = constantReceiver.constant;
return _graph.addConstantInt(constant.length, _closedWorld);
} else {
AbstractValue type = receiver.instructionType;
if (_abstractValueDomain.isContainer(type) &&
_abstractValueDomain.getContainerLength(type) != null) {
HInstruction constant = _graph.addConstantInt(
_abstractValueDomain.getContainerLength(type), _closedWorld);
if (_abstractValueDomain.isNull(type).isPotentiallyTrue) {
// If the container can be null, we update all uses of the length
// access to use the constant instead, but keep the length access in
// the graph, to ensure we still have a null check.
node.block.rewrite(node, constant);
return node;
} else {
return constant;
}
}
}
if (node.isAssignable &&
isFixedLength(receiver.instructionType, _closedWorld)) {
// The input type has changed to fixed-length so change to an unassignable
// HGetLength to allow more GVN optimizations.
return new HGetLength(receiver, node.instructionType,
isAssignable: false);
}
return node;
}
@override
HInstruction visitIndex(HIndex node) {
if (node.receiver.isConstantList() && node.index.isConstantInteger()) {
HConstant instruction = node.receiver;
ListConstantValue list = instruction.constant;
List<ConstantValue> entries = list.entries;
HConstant indexInstruction = node.index;
IntConstantValue indexConstant = indexInstruction.constant;
int index = indexConstant.intValue.toInt();
if (index >= 0 && index < entries.length) {
return _graph.addConstant(entries[index], _closedWorld);
}
}
return node;
}
@override
HInstruction visitInvokeDynamicGetter(HInvokeDynamicGetter node) {
propagateConstantValueToUses(node);
if (node.isInterceptedCall) {
HInstruction folded = handleInterceptedCall(node);
if (folded != node) return folded;
}
HInstruction receiver = node.getDartReceiver(_closedWorld);
AbstractValue receiverType = receiver.instructionType;
FieldEntity field = node.element is FieldEntity
? node.element
: findConcreteFieldForDynamicAccess(node, receiver);
if (field != null) {
FieldAnalysisData fieldData =
_closedWorld.fieldAnalysis.getFieldData(field);
if (fieldData.isEffectivelyConstant) {
// The field is elided and replace it with its constant value.
if (_abstractValueDomain.isNull(receiverType).isPotentiallyTrue) {
// The receiver is potentially `null` so we insert a null receiver
// guard to trigger a null pointer exception.
//
// This could be inserted unconditionally and removed by later
// optimizations if unnecessary, but performance we do it
// conditionally here.
// TODO(35996): Replace with null receiver guard instruction.
HInstruction dummyGet = new HFieldGet(null, receiver,
_abstractValueDomain.dynamicType, node.sourceInformation,
isAssignable: false);
_log?.registerFieldGet(node, dummyGet);
node.block.addBefore(node, dummyGet);
}
ConstantValue constant = fieldData.constantValue;
HConstant result = _graph.addConstant(constant, _closedWorld,
sourceInformation: node.sourceInformation);
_log?.registerConstantFieldGet(node, field, result);
return result;
} else {
HFieldGet result = _directFieldGet(receiver, field, node);
_log?.registerFieldGet(node, result);
return result;
}
}
node.element ??=
_closedWorld.locateSingleMember(node.selector, receiverType);
if (node.element != null &&
node.element.name == node.selector.name &&
node.element.isFunction) {
// A property extraction getter, aka a tear-off.
node.sideEffects.clearAllDependencies();
node.sideEffects.clearAllSideEffects();
node.setUseGvn(); // We don't care about identity of tear-offs.
}
return node;
}
HInstruction _directFieldGet(
HInstruction receiver, FieldEntity field, HInstruction node) {
bool isAssignable = !_closedWorld.fieldNeverChanges(field);
AbstractValue type;
if (_nativeData.isNativeClass(field.enclosingClass)) {
type = AbstractValueFactory.fromNativeBehavior(
_nativeData.getNativeFieldLoadBehavior(field), _closedWorld);
} else {
// TODO(johnniwinther): Use the potentially more precise type of the
// node + find a test that shows its usefulness.
// type = _abstractValueDomain.intersection(
// node.instructionType,
// AbstractValueFactory.inferredTypeForMember(
// field, _globalInferenceResults));
type = AbstractValueFactory.inferredTypeForMember(
field, _globalInferenceResults);
}
return new HFieldGet(field, receiver, type, node.sourceInformation,
isAssignable: isAssignable);
}
@override
HInstruction visitInvokeDynamicSetter(HInvokeDynamicSetter node) {
if (node.isInterceptedCall) {
HInstruction folded = handleInterceptedCall(node);
if (folded != node) return folded;
}
HInstruction receiver = node.getDartReceiver(_closedWorld);
FieldEntity field = findConcreteFieldForDynamicAccess(node, receiver);
if (field == null || !field.isAssignable) return node;
// Use `node.inputs.last` in case the call follows the interceptor calling
// convention, but is not a call on an interceptor.
HInstruction value = node.inputs.last;
HInstruction assignField() {
if (_closedWorld.fieldAnalysis.getFieldData(field).isElided) {
_log?.registerFieldSet(node);
return value;
} else {
HFieldSet result =
new HFieldSet(_abstractValueDomain, field, receiver, value)
..sourceInformation = node.sourceInformation;
_log?.registerFieldSet(node, result);
return result;
}
}
if (!_closedWorld.annotationsData
.getParameterCheckPolicy(field)
.isEmitted) {
return assignField();
}
DartType fieldType = _closedWorld.elementEnvironment.getFieldType(field);
if (_options.experimentNewRti) {
AbstractValueWithPrecision checkedType =
_abstractValueDomain.createFromStaticType(fieldType, nullable: true);
if (checkedType.isPrecise &&
_abstractValueDomain
.isIn(value.instructionType, checkedType.abstractValue)
.isDefinitelyTrue) {
return assignField();
}
// TODO(sra): Implement inlining of setters with checks for new rti. The
// check and field assignmeny for the setter should inlined if this is the
// only call to the setter, or the current function already computes the
// type of the field.
node.needsCheck = true;
return node;
}
if (!fieldType.treatAsRaw ||
fieldType.isTypeVariable ||
fieldType.unaliased.isFunctionType ||
fieldType.unaliased.isFutureOr) {
// We cannot generate the correct type representation here, so don't
// inline this access.
// TODO(sra): If the input is such that we don't need a type check, we
// can skip the test an generate the HFieldSet.
node.needsCheck = true;
return node;
}
HInstruction other =
value.convertType(_closedWorld, fieldType, HTypeConversion.TYPE_CHECK);
if (other != value) {
node.block.addBefore(node, other);
value = other;
}
return assignField();
}
@override
HInstruction visitInvokeClosure(HInvokeClosure node) {
HInstruction closure = node.getDartReceiver(_closedWorld);
// Replace indirect call to static method tear-off closure with direct call
// to static method.
if (closure is HConstant) {
ConstantValue constant = closure.constant;
if (constant is FunctionConstantValue) {
FunctionEntity target = constant.element;
ParameterStructure parameterStructure = target.parameterStructure;
if (parameterStructure.callStructure == node.selector.callStructure) {
// TODO(sra): Handle adding optional arguments default values.
assert(!node.isInterceptedCall);
return new HInvokeStatic(target, node.inputs.skip(1).toList(),
node.instructionType, node.typeArguments)
..sourceInformation = node.sourceInformation;
}
}
}
return node;
}
@override
HInstruction visitInvokeStatic(HInvokeStatic node) {
propagateConstantValueToUses(node);
MemberEntity element = node.element;
if (element == commonElements.identicalFunction) {
if (node.inputs.length == 2) {
return new HIdentity(
node.inputs[0], node.inputs[1], null, _abstractValueDomain.boolType)
..sourceInformation = node.sourceInformation;
}
} else if (element == commonElements.setRuntimeTypeInfo) {
if (node.inputs.length == 2) {
return handleArrayTypeInfo(node);
}
} else if (element == commonElements.checkConcurrentModificationError) {
if (node.inputs.length == 2) {
HInstruction firstArgument = node.inputs[0];
if (firstArgument is HConstant) {
HConstant constant = firstArgument;
if (constant.constant.isTrue) return constant;
}
}
} else if (commonElements.isCheckInt(element)) {
if (node.inputs.length == 1) {
HInstruction argument = node.inputs[0];
if (argument.isInteger(_abstractValueDomain).isDefinitelyTrue) {
return argument;
}
}
} else if (commonElements.isCheckNum(element)) {
if (node.inputs.length == 1) {
HInstruction argument = node.inputs[0];
if (argument.isNumber(_abstractValueDomain).isDefinitelyTrue) {
return argument;
}
}
} else if (commonElements.isCheckString(element)) {
if (node.inputs.length == 1) {
HInstruction argument = node.inputs[0];
if (argument.isString(_abstractValueDomain).isDefinitelyTrue) {
return argument;
}
}
} else if (element == commonElements.assertHelper ||
element == commonElements.assertTest) {
if (node.inputs.length == 1) {
HInstruction argument = node.inputs[0];
if (argument is HConstant) {
ConstantValue constant = argument.constant;
if (constant.isBool) {
bool value = constant.isTrue;
if (element == commonElements.assertTest) {
// `assertTest(argument)` effectively negates the argument.
return _graph.addConstantBool(!value, _closedWorld);
}
// `assertHelper(true)` is a no-op, other values throw.
if (value) return argument;
}
}
}
}
return node;
}
HInstruction handleArrayTypeInfo(HInvokeStatic node) {
// If type information is not needed, use the raw Array.
HInstruction source = node.inputs[0];
if (source.usedBy.length != 1) return node;
if (source.isArray(_abstractValueDomain).isPotentiallyFalse) {
return node;
}
for (HInstruction user in node.usedBy) {
if (user is HGetLength) continue;
if (user is HIndex) continue;
// Bounds check escapes the array, but we don't care.
if (user is HBoundsCheck) continue;
// Interceptor only escapes the Array if array passed to an intercepted
// method.
if (user is HInterceptor) continue;
if (user is HInvokeStatic) {
MemberEntity element = user.element;
if (element == commonElements.checkConcurrentModificationError) {
// CME check escapes the array, but we don't care.
continue;
}
}
if (user is HInvokeDynamicGetter) {
String name = user.selector.name;
// These getters don't use the Array type.
if (name == 'last') continue;
if (name == 'first') continue;
}
// TODO(sra): Implement a more general algorithm - many methods don't use
// the element type.
return node;
}
return source;
}
@override
HInstruction visitStringConcat(HStringConcat node) {
// Simplify string concat:
//
// "" + R -> R
// L + "" -> L
// "L" + "R" -> "LR"
// (prefix + "L") + "R" -> prefix + "LR"
//
StringConstantValue getString(HInstruction instruction) {
if (!instruction.isConstantString()) return null;
HConstant constant = instruction;
return constant.constant;
}
StringConstantValue leftString = getString(node.left);
if (leftString != null && leftString.stringValue.length == 0) {
return node.right;
}
StringConstantValue rightString = getString(node.right);
if (rightString == null) return node;
if (rightString.stringValue.length == 0) return node.left;
HInstruction prefix = null;
if (leftString == null) {
if (node.left is! HStringConcat) return node;
HStringConcat leftConcat = node.left;
// Don't undo CSE.
if (leftConcat.usedBy.length != 1) return node;
prefix = leftConcat.left;
leftString = getString(leftConcat.right);
if (leftString == null) return node;
}
if (leftString.stringValue.length + rightString.stringValue.length >
MAX_SHARED_CONSTANT_FOLDED_STRING_LENGTH) {
if (node.usedBy.length > 1) return node;
}
HInstruction folded = _graph.addConstant(
constant_system
.createString(leftString.stringValue + rightString.stringValue),
_closedWorld);
if (prefix == null) return folded;
return new HStringConcat(prefix, folded, _abstractValueDomain.stringType);
}
@override
HInstruction visitStringify(HStringify node) {
HInstruction input = node.inputs[0];
if (input.isString(_abstractValueDomain).isDefinitelyTrue) {
return input;
}
HInstruction asString(String string) =>
_graph.addConstant(constant_system.createString(string), _closedWorld);
HInstruction tryConstant() {
if (!input.isConstant()) return null;
HConstant constant = input;
if (!constant.constant.isPrimitive) return null;
PrimitiveConstantValue value = constant.constant;
if (value is IntConstantValue) {
// Only constant-fold int.toString() when Dart and JS results the same.
// TODO(18103): We should be able to remove this work-around when issue
// 18103 is resolved by providing the correct string.
if (!value.isUInt32()) return null;
return asString('${value.intValue}');
}
if (value is BoolConstantValue) {
return asString(value.boolValue ? 'true' : 'false');
}
if (value is NullConstantValue) {
return asString('null');
}
if (value is DoubleConstantValue) {
// TODO(sra): It seems unlikely that all dart2js host implementations
// produce exactly the same characters as all JavaScript targets.
return asString('${value.doubleValue}');
}
return null;
}
HInstruction tryToString() {
// If the `toString` method is guaranteed to return a string we can call
// it directly. Keep the stringifier for primitives (since they have fast
// path code in the stringifier) and for classes requiring interceptors
// (since SsaInstructionSimplifier runs after SsaSimplifyInterceptors).
if (input.isPrimitive(_abstractValueDomain).isPotentiallyTrue) {
return null;
}
if (input.isNull(_abstractValueDomain).isPotentiallyTrue) {
return null;
}
Selector selector = Selectors.toString_;
AbstractValue toStringType =
AbstractValueFactory.inferredResultTypeForSelector(
selector, input.instructionType, _globalInferenceResults);
if (_abstractValueDomain
.containsOnlyType(
toStringType, _closedWorld.commonElements.jsStringClass)
.isPotentiallyFalse) {
return null;
}
// All intercepted classes extend `Interceptor`, so if the receiver can't
// be a class extending `Interceptor` then it can be called directly.
if (_abstractValueDomain
.isInterceptor(input.instructionType)
.isDefinitelyFalse) {
var inputs = <HInstruction>[input, input]; // [interceptor, receiver].
HInstruction result = new HInvokeDynamicMethod(
selector,
input.instructionType, // receiver type.
inputs,
toStringType,
const <DartType>[],
node.sourceInformation,
isIntercepted: true);
return result;
}
return null;
}
return tryConstant() ?? tryToString() ?? node;
}
@override
HInstruction visitOneShotInterceptor(HOneShotInterceptor node) {
return handleInterceptedCall(node);
}
bool needsSubstitutionForTypeVariableAccess(ClassEntity cls) {
if (_closedWorld.isUsedAsMixin(cls)) return true;
return _closedWorld.classHierarchy.anyStrictSubclassOf(cls,
(ClassEntity subclass) {
return !_rtiSubstitutions.isTrivialSubstitution(subclass, cls);
});
}
@override
HInstruction visitTypeInfoExpression(HTypeInfoExpression node) {
// Identify the case where the type info expression would be of the form:
//
// [getTypeArgumentByIndex(this, 0), .., getTypeArgumentByIndex(this, k)]
//
// and k is the number of type arguments of 'this'. We can simply copy the
// list from 'this'.
HInstruction tryCopyInfo() {
if (node.kind != TypeInfoExpressionKind.INSTANCE) return null;
HInstruction source;
int expectedIndex = 0;
for (HInstruction argument in node.inputs) {
if (argument is HTypeInfoReadVariable) {
ClassEntity context = DartTypes.getClassContext(argument.variable);
HInstruction nextSource = argument.object;
if (nextSource is HRef) {
HRef ref = nextSource;
nextSource = ref.value;
}
if (nextSource is HThis) {
if (source == null) {
source = nextSource;
ClassEntity thisClass = nextSource.sourceElement.enclosingClass;
InterfaceType thisType =
_closedWorld.elementEnvironment.getThisType(thisClass);
if (node.inputs.length != thisType.typeArguments.length) {
return null;
}
if (needsSubstitutionForTypeVariableAccess(thisClass)) {
return null;
}
if (context != null &&
!_rtiSubstitutions.isTrivialSubstitution(
thisClass, context)) {
// If inlining, the [context] is not the same as [thisClass].
// If this is the case, then the substitution must be trivial.
// Consider this:
//
// class A {
// final Object value;
// A(this.value);
// }
// class B<T> extends A {
// B(T value) : super(value);
// T get value => super.value as T;
// }
// class C<S> extends B<List<S>> {
// C(List<S> value) : super(value);
// S get first => value.first;
// }
//
// If `B.value` is inlined into `C.first` the type info
// expression is the list `[B.T]` on a `this` of type `C<S>`.
// Since the substitution from C to B is not trivial
// (S -> List<S>) this type info expression cannot be replaced
// with the type arguments `C<S>` (it would yield [S] instead of
// [List<S>]).
return null;
}
}
if (nextSource != source) return null;
// Check that the index is the one we expect.
int index = argument.variable.element.index;
if (index != expectedIndex++) return null;
} else {
// TODO(sra): Handle non-this cases (i.e. inlined code). Some
// non-this cases will have a TypeMask that does not need
// substitution, even though the general case does, e.g. inlining a
// method on an exact class.
return null;
}
} else {
return null;
}
}
if (source == null) return null;
return new HTypeInfoReadRaw(source, _abstractValueDomain.dynamicType);
}
// TODO(sra): Consider fusing type expression trees with no type variables,
// as these could be represented like constants.
return tryCopyInfo() ?? node;
}
@override
HInstruction visitTypeInfoReadVariable(HTypeInfoReadVariable node) {
TypeVariableType variable = node.variable;
ClassEntity contextClass = variable.element.typeDeclaration;
HInstruction object = node.object;
HInstruction finishGroundType(InterfaceType groundType) {
InterfaceType typeAtVariable =
_closedWorld.dartTypes.asInstanceOf(groundType, contextClass);
if (typeAtVariable != null) {
int index = variable.element.index;
DartType typeArgument = typeAtVariable.typeArguments[index];
HInstruction replacement = new HTypeInfoExpression(
TypeInfoExpressionKind.COMPLETE,
typeArgument,
const <HInstruction>[],
_abstractValueDomain.dynamicType,
isTypeVariableReplacement: true);
return replacement;
}
return node;
}
/// Read the type variable from an allocation of type [createdClass], where
/// [selectTypeArgumentFromObjectCreation] extracts the type argument from
/// the allocation for factory constructor call.
HInstruction finishSubstituted(ClassEntity createdClass,
HInstruction selectTypeArgumentFromObjectCreation(int index)) {
InterfaceType thisType = _closedWorld.dartTypes.getThisType(createdClass);
HInstruction instructionForTypeVariable(TypeVariableType tv) {
return selectTypeArgumentFromObjectCreation(
thisType.typeArguments.indexOf(tv));
}
DartType type = _closedWorld.dartTypes
.asInstanceOf(thisType, contextClass)
.typeArguments[variable.element.index];
if (type is TypeVariableType) {
return instructionForTypeVariable(type);
}
List<HInstruction> arguments = <HInstruction>[];
type.forEachTypeVariable((v) {
arguments.add(instructionForTypeVariable(v));
});
HInstruction replacement = new HTypeInfoExpression(
TypeInfoExpressionKind.COMPLETE,
type,
arguments,
_abstractValueDomain.dynamicType,
isTypeVariableReplacement: true);
return replacement;
}
// Type variable evaluated in the context of a constant can be replaced with
// a ground term type.
if (object is HConstant) {
ConstantValue value = object.constant;
if (value is ConstructedConstantValue) {
return finishGroundType(value.type);
}
return node;
}
// Look for an allocation with type information and re-write type variable
// as a function of the type parameters of the allocation. This effectively
// store-forwards a type variable read through an allocation.
// Match:
//
// HCreate(ClassElement,
// [arg_i,
// ...,
// HTypeInfoExpression(t_0, t_1, t_2, ...)]);
//
// The `t_i` are the values of the type parameters of ClassElement.
if (object is HCreate) {
void registerInstantiations() {
// Forwarding the type variable references might cause the HCreate to
// become dead. This breaks the algorithm for generating the per-type
// runtime type information, so we instantiate them here in case the
// HCreate becomes dead.
object.instantiatedTypes?.forEach(_registry.registerInstantiation);
}
if (object.hasRtiInput) {
HInstruction typeInfo = object.rtiInput;
if (typeInfo is HTypeInfoExpression) {
registerInstantiations();
return finishSubstituted(
object.element, (int index) => typeInfo.inputs[index]);
}
} else {
// Non-generic type (which extends or mixes in a generic type, for
// example CodeUnits extends UnmodifiableListBase<int>). Also used for
// raw-type when the type parameters are elided.
registerInstantiations();
return finishSubstituted(
object.element,
// If there are type arguments, all type arguments are 'dynamic'.
(int i) => _graph.addConstantNull(_closedWorld));
}
}
// TODO(sra): Factory constructors pass type arguments after the value
// arguments. The [selectTypeArgumentFromObjectCreation] argument of
// [finishSubstituted] indexes into these type arguments.
// Try to remove the interceptor. The interceptor is used for accessing the
// substitution methods.
if (node.isIntercepted) {
// If we don't need the substitution methods then we don't need the
// interceptor to access them.
if (!needsSubstitutionForTypeVariableAccess(contextClass)) {
return new HTypeInfoReadVariable.noInterceptor(
variable, object, node.instructionType);
}
// All intercepted classes extend `Interceptor`, so if the receiver can't
// be a class extending `Interceptor` then the substitution methods can be
// called directly. (We don't care about Null since contexts reading class
// type variables originate from instance methods.)
if (_abstractValueDomain
.isInterceptor(object.instructionType)
.isDefinitelyFalse) {
return new HTypeInfoReadVariable.noInterceptor(
variable, object, node.instructionType);
}
}
return node;
}
@override
HInstruction visitTypeEval(HTypeEval node) {
HInstruction environment = node.inputs.single;
if (_typeRecipeDomain.isIdentity(node.typeExpression, node.envStructure)) {
return environment;
}
if (environment is HLoadType) {
TypeRecipe result = _typeRecipeDomain.foldLoadEval(
environment.typeExpression, node.envStructure, node.typeExpression);
if (result != null) return HLoadType(result, node.instructionType);
return node;
}
if (environment is HTypeBind) {
HInstruction bindings = environment.inputs.last;
if (bindings is HLoadType) {
// env.bind(LoadType(T)).eval(...1...) --> env.eval(...T...)
TypeRecipeAndEnvironmentStructure result =
_typeRecipeDomain.foldBindLoadEval(bindings.typeExpression,
node.envStructure, node.typeExpression);
if (result != null) {
HInstruction previousEnvironment = environment.inputs.first;
return HTypeEval(previousEnvironment, result.environmentStructure,
result.recipe, node.instructionType);
}
}
// TODO(sra): LoadType(T).bind(E).eval(...1...) --> E.eval(...0...)
return node;
}
if (environment is HTypeEval) {
TypeRecipeAndEnvironmentStructure result = _typeRecipeDomain.foldEvalEval(
environment.envStructure,
environment.typeExpression,
node.envStructure,
node.typeExpression);
if (result != null) {
HInstruction previousEnvironment = environment.inputs.first;
return HTypeEval(previousEnvironment, result.environmentStructure,
result.recipe, node.instructionType);
}
return node;
}
if (environment is HInstanceEnvironment) {
HInstruction instance = environment.inputs.single;
AbstractValue instanceAbstractValue = instance.instructionType;
ClassEntity instanceClass =
_abstractValueDomain.getExactClass(instanceAbstractValue);
if (instanceClass == null) {
// All the subclasses of JSArray are JSArray at runtime.
ClassEntity jsArrayClass = _closedWorld.commonElements.jsArrayClass;
if (_abstractValueDomain
.isInstanceOf(instanceAbstractValue, jsArrayClass)
.isDefinitelyTrue) {
instanceClass = jsArrayClass;
}
}
if (instanceClass != null) {
if (_typeRecipeDomain.isReconstruction(
instanceClass, node.envStructure, node.typeExpression)) {
return environment;
}
}
}
return node;
}
@override
HInstruction visitTypeBind(HTypeBind node) {
// TODO(sra): env1.eval(X).bind(env1.eval(Y)) --> env1.eval(...X...Y...)
return node;
}
@override
HInstruction visitAsCheck(HAsCheck node) {
if (node.isRedundant(_closedWorld)) return node.checkedInput;
// See if this check can be lowered to a simple one.
HInstruction typeInput = node.typeInput;
if (typeInput is HLoadType) {
TypeExpressionRecipe recipe = typeInput.typeExpression;
DartType dartType = recipe.type;
MemberEntity specializedCheck = SpecializedChecks.findAsCheck(
dartType, node.isTypeError, _closedWorld.commonElements);
if (specializedCheck != null) {
AbstractValueWithPrecision checkedType =
_abstractValueDomain.createFromStaticType(dartType, nullable: true);
return HAsCheckSimple(node.checkedInput, dartType, checkedType,
node.isTypeError, specializedCheck, node.instructionType);
}
if (dartType is DynamicType) {
return node.checkedInput;
}
}
return node;
}
@override
HInstruction visitAsCheckSimple(HAsCheckSimple node) {
if (node.isRedundant(_closedWorld)) return node.checkedInput;
return node;
}
@override
HInstruction visitIsTest(HIsTest node) {
AbstractValueWithPrecision checkedAbstractValue = node.checkedAbstractValue;
HInstruction checkedInput = node.checkedInput;
AbstractValue inputType = checkedInput.instructionType;
AbstractBool isIn = _abstractValueDomain.isIn(
inputType, checkedAbstractValue.abstractValue);
if (isIn.isDefinitelyFalse) {
return _graph.addConstantBool(false, _closedWorld);
}
if (!checkedAbstractValue.isPrecise) return node;
if (isIn.isDefinitelyTrue) {
return _graph.addConstantBool(true, _closedWorld);
}
return node;
}
@override
HInstruction visitInstanceEnvironment(HInstanceEnvironment node) {
HInstruction instance = node.inputs.single;
// Store-forward instance types of created instances and constant instances.
//
// Forwarding the type might cause the instance (HCreate, constant etc) to
// become dead. This might cause us to lose track of that fact that there
// are type expressions from within the instance's class scope, so breaking
// the algorithm for generating the per-type runtime type information. The
// fix is to register the classes as created here in case the instance
// becomes dead.
//
// TODO(sra): It would be cleaner to track on HLoadType, HTypeEval, etc
// which class scope(s) they originated from. If the type expressions become
// dead, the references to the scope type variables become dead.
if (instance is HCreate) {
if (instance.hasRtiInput) {
instance.instantiatedTypes?.forEach(_registry.registerInstantiation);
return instance.rtiInput;
}
InterfaceType instanceType =
_closedWorld.elementEnvironment.getThisType(instance.element);
if (instanceType.typeArguments.length == 0) {
instance.instantiatedTypes?.forEach(_registry.registerInstantiation);
return HLoadType.type(instanceType, instance.instructionType);
}
return node;
}
if (instance is HConstant) {
ConstantValue constantValue = instance.constant;
if (constantValue is ConstructedConstantValue) {
_registry.registerInstantiation(constantValue.type);
return HLoadType.type(constantValue.type, instance.instructionType);
}
if (constantValue is ListConstantValue) {
InterfaceType type = constantValue.type;
_registry.registerInstantiation(type);
return HLoadType.type(type, instance.instructionType);
}
return node;
}
if (instance is HInvokeStatic &&
instance.element == commonElements.setRuntimeTypeInfo) {
// TODO(sra): What is the 'instantiated type' we should be registering as
// discussed above? Perhaps it should be carried on HLiteralList.
return instance.inputs.last;
}
return node;
}
}
class SsaCheckInserter extends HBaseVisitor implements OptimizationPhase {
final Set<HInstruction> boundsChecked;
final bool trustPrimitives;
final JClosedWorld closedWorld;
@override
final String name = "SsaCheckInserter";
HGraph graph;
SsaCheckInserter(this.trustPrimitives, this.closedWorld, this.boundsChecked);
AbstractValueDomain get _abstractValueDomain =>
closedWorld.abstractValueDomain;
@override
void visitGraph(HGraph graph) {
this.graph = graph;
// In --trust-primitives mode we don't add bounds checks. This is better
// than trying to remove them later as the limit expression would become
// dead and require DCE.
if (trustPrimitives) return;
visitDominatorTree(graph);
}
@override
void visitBasicBlock(HBasicBlock block) {
HInstruction instruction = block.first;
while (instruction != null) {
HInstruction next = instruction.next;
instruction = instruction.accept(this);
instruction = next;
}
}
HBoundsCheck insertBoundsCheck(
HInstruction indexNode, HInstruction array, HInstruction indexArgument) {
HGetLength length = new HGetLength(
array, closedWorld.abstractValueDomain.positiveIntType,
isAssignable: !isFixedLength(array.instructionType, closedWorld));
indexNode.block.addBefore(indexNode, length);
AbstractValue type =
indexArgument.isPositiveInteger(_abstractValueDomain).isDefinitelyTrue
? indexArgument.instructionType
: closedWorld.abstractValueDomain.positiveIntType;
HBoundsCheck check = new HBoundsCheck(indexArgument, length, array, type)
..sourceInformation = indexNode.sourceInformation;
indexNode.block.addBefore(indexNode, check);
// If the index input to the bounds check was not known to be an integer
// then we replace its uses with the bounds check, which is known to be an
// integer. However, if the input was already an integer we don't do this
// because putting in a check instruction might obscure the real nature of
// the index eg. if it is a constant. The range information from the
// BoundsCheck instruction is attached to the input directly by
// visitBoundsCheck in the SsaValueRangeAnalyzer.
if (indexArgument.isInteger(_abstractValueDomain).isPotentiallyFalse) {
indexArgument.replaceAllUsersDominatedBy(indexNode, check);
}
boundsChecked.add(indexNode);
return check;
}
@override
void visitIndex(HIndex node) {
if (boundsChecked.contains(node)) return;
HInstruction index = node.index;
index = insertBoundsCheck(node, node.receiver, index);
}
@override
void visitIndexAssign(HIndexAssign node) {
if (boundsChecked.contains(node)) return;
HInstruction index = node.index;
index = insertBoundsCheck(node, node.receiver, index);
}
@override
void visitInvokeDynamicMethod(HInvokeDynamicMethod node) {
MemberEntity element = node.element;
if (node.isInterceptedCall) return;
if (element != closedWorld.commonElements.jsArrayRemoveLast) return;
if (boundsChecked.contains(node)) return;
// `0` is the index we want to check, but we want to report `-1`, as if we
// executed `a[a.length-1]`
HBoundsCheck check = insertBoundsCheck(
node, node.receiver, graph.addConstantInt(0, closedWorld));
HInstruction minusOne = graph.addConstantInt(-1, closedWorld);
check.inputs.add(minusOne);
minusOne.usedBy.add(check);
}
}
class SsaDeadCodeEliminator extends HGraphVisitor implements OptimizationPhase {
@override
final String name = "SsaDeadCodeEliminator";
final JClosedWorld closedWorld;
final SsaOptimizerTask optimizer;
HGraph _graph;
SsaLiveBlockAnalyzer analyzer;
Map<HInstruction, bool> trivialDeadStoreReceivers =
new Maplet<HInstruction, bool>();
bool eliminatedSideEffects = false;
bool newGvnCandidates = false;
SsaDeadCodeEliminator(this.closedWorld, this.optimizer);
AbstractValueDomain get _abstractValueDomain =>
closedWorld.abstractValueDomain;
HInstruction zapInstructionCache;
HInstruction get zapInstruction {
if (zapInstructionCache == null) {
// A constant with no type does not pollute types at phi nodes.
ConstantValue constant = const UnreachableConstantValue();
zapInstructionCache = analyzer.graph.addConstant(constant, closedWorld);
}
return zapInstructionCache;
}
/// Returns true of [foreign] will throw an noSuchMethod error if
/// receiver is `null` before having any other side-effects.
bool templateThrowsNSMonNull(HForeignCode foreign, HInstruction receiver) {
if (foreign.inputs.length < 1) return false;
if (foreign.inputs.first != receiver) return false;
if (foreign.throwBehavior.isNullNSMGuard) return true;
return false;
}
/// Returns whether the next throwing instruction that may have side
/// effects after [instruction], throws [NoSuchMethodError] on the
/// same receiver of [instruction].
bool hasFollowingThrowingNSM(HInstruction instruction) {
HInstruction receiver = instruction.getDartReceiver(closedWorld);
HInstruction current = instruction.next;
do {
if ((current.getDartReceiver(closedWorld) == receiver) &&
current.canThrow(_abstractValueDomain)) {
return true;
}
if (current is HForeignCode &&
templateThrowsNSMonNull(current, receiver)) {
return true;
}
if (current.canThrow(_abstractValueDomain) ||
current.sideEffects.hasSideEffects()) {
return false;
}
HInstruction next = current.next;
if (next == null) {
// We do not merge blocks in our SSA graph, so if this block just jumps
// to a single successor, visit the successor, avoiding back-edges.
HBasicBlock successor;
if (current is HGoto) {
successor = current.block.successors.single;
} else if (current is HIf) {
// We also leave HIf nodes in place when one branch is dead.
HInstruction condition = current.inputs.first;
if (condition is HConstant) {
bool isTrue = condition.constant.isTrue;
successor = isTrue ? current.thenBlock : current.elseBlock;
assert(!analyzer.isDeadBlock(successor));
}
}
if (successor != null && successor.id > current.block.id) {
next = successor.first;
}
}
current = next;
} while (current != null);
return false;
}
bool isTrivialDeadStoreReceiver(HInstruction instruction) {
// For an allocation, if all the loads are dead (awaiting removal after
// SsaLoadElimination) and the only other uses are stores, then the
// allocation does not escape which makes all the stores dead too.
bool isDeadUse(HInstruction use) {
if (use is HFieldSet) {
// The use must be the receiver. Even if the use is also the argument,
// i.e. a.x = a, the store is still dead if all other uses are dead.
if (use.getDartReceiver(closedWorld) == instruction) return true;
} else if (use is HFieldGet) {
assert(use.getDartReceiver(closedWorld) == instruction);
if (isDeadCode(use)) return true;
}
return false;
}
return instruction.isAllocation(_abstractValueDomain) &&
instruction.isPure(_abstractValueDomain) &&
trivialDeadStoreReceivers.putIfAbsent(
instruction, () => instruction.usedBy.every(isDeadUse));
}
bool isTrivialDeadStore(HInstruction instruction) {
return instruction is HFieldSet &&
isTrivialDeadStoreReceiver(instruction.getDartReceiver(closedWorld));
}
bool isDeadCode(HInstruction instruction) {
if (!instruction.usedBy.isEmpty) return false;
if (isTrivialDeadStore(instruction)) return true;
if (instruction.sideEffects.hasSideEffects()) return false;
if (instruction.canThrow(_abstractValueDomain)) {
if (instruction.onlyThrowsNSM() && hasFollowingThrowingNSM(instruction)) {
// [instruction] is a null reciever guard that is followed by an
// instruction that fails the same way (by accessing a property of
// `null` or `undefined`).
return true;
}
return false;
}
if (instruction is HParameterValue) return false;
if (instruction is HLocalSet) return false;
return true;
}
@override
void visitGraph(HGraph graph) {
_graph = graph;
analyzer = new SsaLiveBlockAnalyzer(graph, closedWorld, optimizer);
analyzer.analyze();
visitPostDominatorTree(graph);
cleanPhis();
}
@override
void visitBasicBlock(HBasicBlock block) {
bool isDeadBlock = analyzer.isDeadBlock(block);
block.isLive = !isDeadBlock;
simplifyControlFlow(block);
// Start from the last non-control flow instruction in the block.
HInstruction instruction = block.last.previous;
while (instruction != null) {
var previous = instruction.previous;
if (isDeadBlock) {
eliminatedSideEffects =
eliminatedSideEffects || instruction.sideEffects.hasSideEffects();
removeUsers(instruction);
block.remove(instruction);
} else if (isDeadCode(instruction)) {
block.remove(instruction);
}
instruction = previous;
}
block.forEachPhi(simplifyPhi);
evacuateTakenBranch(block);
}
void simplifyPhi(HPhi phi) {
// If the phi is of the form `phi(x, HTypeKnown(x))`, it does not strengthen
// `x`. We can replace the phi with `x` to potentially make the HTypeKnown
// refinement node dead and potentially make a HIf control no HPhis.
// TODO(sra): Implement version of this test that works for a subgraph of
// HPhi nodes.
HInstruction base = null;
bool seenUnrefinedBase = false;
for (HInstruction input in phi.inputs) {
HInstruction value = input;
while (value is HTypeKnown) {
HTypeKnown refinement = value;
value = refinement.checkedInput;
}
if (value == input) seenUnrefinedBase = true;
base ??= value;
if (base != value) return;
}
if (seenUnrefinedBase) {
HBasicBlock block = phi.block;
block.rewrite(phi, base);
block.removePhi(phi);
}
}
void simplifyControlFlow(HBasicBlock block) {
HControlFlow instruction = block.last;
if (instruction is HIf) {
HInstruction condition = instruction.condition;
if (condition.isConstant()) return;
// We want to remove an if-then-else diamond when the then- and else-
// branches are empty and the condition does not control a HPhi. We cannot
// change the CFG structure so we replace the HIf condition with a
// constant. This may leave the original condition unused. i.e. a
// candidate for being dead code.
List<HBasicBlock> dominated = block.dominatedBlocks;
// Diamond-like control flow dominates the then-, else- and join- blocks.
if (dominated.length != 3) return;
HBasicBlock join = dominated.last;
if (!join.phis.isEmpty) return; // condition controls a phi.
// Ignore exit block - usually the join in `if (...) return ...`
if (join.isExitBlock()) return;
int thenSize = measureEmptyInterval(instruction.thenBlock, join);
if (thenSize == null) return;
int elseSize = measureEmptyInterval(instruction.elseBlock, join);
if (elseSize == null) return;
// Pick the 'live' branch to be the smallest subgraph.
bool value = thenSize <= elseSize;
HInstruction newCondition = _graph.addConstantBool(value, closedWorld);
instruction.inputs[0] = newCondition;
condition.usedBy.remove(instruction);
newCondition.usedBy.add(instruction);
}
}
/// Returns the number of blocks from [start] up to but not including [end].
/// Returns `null` if any of the blocks is non-empty (other than control
/// flow). Returns `null` if there is an exit from the region other than
/// [end] or via control flow other than HGoto and HIf.
int measureEmptyInterval(HBasicBlock start, HBasicBlock end) {
if (start.first != start.last) return null; // start is not empty.
// Do a simple single-block region test first.
if (start.last is HGoto &&
start.successors.length == 1 &&
start.successors.single == end) {
return 1;
}
// TODO(sra): Implement fuller test.
return null;
}
/// If [block] is an always-taken branch, move the code from the taken branch
/// into [block]. This has the effect of making the instructions available for
/// further optimizations by moving them to a position that dominates the join
/// point of the if-then-else.
// TODO(29475): Delete dead blocks instead.
void evacuateTakenBranch(HBasicBlock block) {
if (!block.isLive) return;
HControlFlow branch = block.last;
if (branch is HIf) {
if (branch.thenBlock.isLive == branch.elseBlock.isLive) return;
assert(branch.condition.isConstant());
HBasicBlock target =
branch.thenBlock.isLive ? branch.thenBlock : branch.elseBlock;
HInstruction instruction = target.first;
while (!instruction.isControlFlow()) {
HInstruction next = instruction.next;
if (instruction is HTypeKnown && instruction.isPinned) break;
// It might be worth re-running GVN optimizations if we hoisted a
// GVN-able instructions from [target] into [block].
newGvnCandidates = newGvnCandidates || instruction.useGvn();
instruction.block.detach(instruction);
block.moveAtExit(instruction);
instruction = next;
}
}
}
void cleanPhis() {
L:
for (HBasicBlock block in _graph.blocks) {
List<HBasicBlock> predecessors = block.predecessors;
// Zap all inputs to phis that correspond to dead blocks.
block.forEachPhi((HPhi phi) {
for (int i = 0; i < phi.inputs.length; ++i) {
if (!predecessors[i].isLive && phi.inputs[i] != zapInstruction) {
replaceInput(i, phi, zapInstruction);
}
}
});
if (predecessors.length < 2) continue L;
// Find the index of the single live predecessor if it exists.
int indexOfLive = -1;
for (int i = 0; i < predecessors.length; i++) {
if (predecessors[i].isLive) {
if (indexOfLive >= 0) continue L;
indexOfLive = i;
}
}
// Run through the phis of the block and replace them with their input
// that comes from the only live predecessor if that dominates the phi.
//
// TODO(sra): If the input is directly in the only live predecessor, it
// might be possible to move it into [block] (e.g. all its inputs are
// dominating.)
block.forEachPhi((HPhi phi) {
HInstruction replacement =
(indexOfLive >= 0) ? phi.inputs[indexOfLive] : zapInstruction;
if (replacement.dominates(phi)) {
block.rewrite(phi, replacement);
block.removePhi(phi);
if (replacement.sourceElement == null &&
phi.sourceElement != null &&
replacement is! HThis) {
replacement.sourceElement = phi.sourceElement;
}
}
});
}
}
void replaceInput(int i, HInstruction from, HInstruction by) {
from.inputs[i].usedBy.remove(from);
from.inputs[i] = by;
by.usedBy.add(from);
}
void removeUsers(HInstruction instruction) {
instruction.usedBy.forEach((user) {
removeInput(user, instruction);
});
instruction.usedBy.clear();
}
void removeInput(HInstruction user, HInstruction input) {
List<HInstruction> inputs = user.inputs;
for (int i = 0, length = inputs.length; i < length; i++) {
if (input == inputs[i]) {
user.inputs[i] = zapInstruction;
zapInstruction.usedBy.add(user);
}
}
}
}
class SsaLiveBlockAnalyzer extends HBaseVisitor {
final HGraph graph;
final Set<HBasicBlock> live = new Set<HBasicBlock>();
final List<HBasicBlock> worklist = <HBasicBlock>[];
final SsaOptimizerTask optimizer;
final JClosedWorld closedWorld;
SsaLiveBlockAnalyzer(this.graph, this.closedWorld, this.optimizer);
AbstractValueDomain get _abstractValueDomain =>
closedWorld.abstractValueDomain;
Map<HInstruction, Range> get ranges => optimizer.ranges;
bool isDeadBlock(HBasicBlock block) => !live.contains(block);
void analyze() {
markBlockLive(graph.entry);
while (!worklist.isEmpty) {
HBasicBlock live = worklist.removeLast();
live.last.accept(this);
}
}
void markBlockLive(HBasicBlock block) {
if (!live.contains(block)) {
worklist.add(block);
live.add(block);
}
}
@override
void visitControlFlow(HControlFlow instruction) {
instruction.block.successors.forEach(markBlockLive);
}
@override
void visitIf(HIf instruction) {
HInstruction condition = instruction.condition;
if (condition.isConstant()) {
if (condition.isConstantTrue()) {
markBlockLive(instruction.thenBlock);
} else {
markBlockLive(instruction.elseBlock);
}
} else {
visitControlFlow(instruction);
}
}
@override
void visitSwitch(HSwitch node) {
if (node.expression.isInteger(_abstractValueDomain).isDefinitelyTrue) {
Range switchRange = ranges[node.expression];
if (switchRange != null &&
switchRange.lower is IntValue &&
switchRange.upper is IntValue) {
IntValue lowerValue = switchRange.lower;
IntValue upperValue = switchRange.upper;
BigInt lower = lowerValue.value;
BigInt upper = upperValue.value;
Set<BigInt> liveLabels = new Set<BigInt>();
for (int pos = 1; pos < node.inputs.length; pos++) {
HConstant input = node.inputs[pos];
if (!input.isConstantInteger()) continue;
IntConstantValue constant = input.constant;
BigInt label = constant.intValue;
if (!liveLabels.contains(label) && label <= upper && label >= lower) {
markBlockLive(node.block.successors[pos - 1]);
liveLabels.add(label);
}
}
if (new BigInt.from(liveLabels.length) != upper - lower + BigInt.one) {
markBlockLive(node.defaultTarget);
}
return;
}
}
visitControlFlow(node);
}
}
class SsaDeadPhiEliminator implements OptimizationPhase {
@override
final String name = "SsaDeadPhiEliminator";
@override
void visitGraph(HGraph graph) {
final List<HPhi> worklist = <HPhi>[];
// A set to keep track of the live phis that we found.
final Set<HPhi> livePhis = new Set<HPhi>();
// Add to the worklist all live phis: phis referenced by non-phi
// instructions.
for (final block in graph.blocks) {
block.forEachPhi((HPhi phi) {
for (final user in phi.usedBy) {
if (user is! HPhi) {
worklist.add(phi);
livePhis.add(phi);
break;
}
}
});
}
// Process the worklist by propagating liveness to phi inputs.
while (!worklist.isEmpty) {
HPhi phi = worklist.removeLast();
for (final input in phi.inputs) {
if (input is HPhi && !livePhis.contains(input)) {
worklist.add(input);
livePhis.add(input);
}
}
}
// Remove phis that are not live.
// Traverse in reverse order to remove phis with no uses before the
// phis that they might use.
// NOTICE: Doesn't handle circular references, but we don't currently
// create any.
List<HBasicBlock> blocks = graph.blocks;
for (int i = blocks.length - 1; i >= 0; i--) {
HBasicBlock block = blocks[i];
HPhi current = block.phis.first;
HPhi next = null;
while (current != null) {
next = current.next;
if (!livePhis.contains(current)
// TODO(ahe): Not sure the following is correct.
&&
current.usedBy.isEmpty) {
block.removePhi(current);
}
current = next;
}
}
}
}
class SsaRedundantPhiEliminator implements OptimizationPhase {
@override
final String name = "SsaRedundantPhiEliminator";
@override
void visitGraph(HGraph graph) {
final List<HPhi> worklist = <HPhi>[];
// Add all phis in the worklist.
for (final block in graph.blocks) {
block.forEachPhi((HPhi phi) => worklist.add(phi));
}
while (!worklist.isEmpty) {
HPhi phi = worklist.removeLast();
// If the phi has already been processed, continue.
if (!phi.isInBasicBlock()) continue;
// Find if the inputs of the phi are the same instruction.
// The builder ensures that phi.inputs[0] cannot be the phi
// itself.
assert(!identical(phi.inputs[0], phi));
HInstruction candidate = phi.inputs[0];
for (int i = 1; i < phi.inputs.length; i++) {
HInstruction input = phi.inputs[i];
// If the input is the phi, the phi is still candidate for
// elimination.
if (!identical(input, candidate) && !identical(input, phi)) {
candidate = null;
break;
}
}
// If the inputs are not the same, continue.
if (candidate == null) continue;
// Because we're updating the users of this phi, we may have new
// phis candidate for elimination. Add phis that used this phi
// to the worklist.
for (final user in phi.usedBy) {
if (user is HPhi) worklist.add(user);
}
phi.block.rewrite(phi, candidate);
phi.block.removePhi(phi);
if (candidate.sourceElement == null &&
phi.sourceElement != null &&
candidate is! HThis) {
candidate.sourceElement = phi.sourceElement;
}
}
}
}
class GvnWorkItem {
final HBasicBlock block;
final ValueSet valueSet;
GvnWorkItem(this.block, this.valueSet);
}
class SsaGlobalValueNumberer implements OptimizationPhase {
final AbstractValueDomain _abstractValueDomain;
@override
final String name = "SsaGlobalValueNumberer";
final Set<int> visited;
List<int> blockChangesFlags;
List<int> loopChangesFlags;
SsaGlobalValueNumberer(this._abstractValueDomain) : visited = new Set<int>();
@override
void visitGraph(HGraph graph) {
computeChangesFlags(graph);
moveLoopInvariantCode(graph);
List<GvnWorkItem> workQueue = <GvnWorkItem>[
new GvnWorkItem(graph.entry, new ValueSet())
];
do {
GvnWorkItem item = workQueue.removeLast();
visitBasicBlock(item.block, item.valueSet, workQueue);
} while (!workQueue.isEmpty);
}
void moveLoopInvariantCode(HGraph graph) {
for (int i = graph.blocks.length - 1; i >= 0; i--) {
HBasicBlock block = graph.blocks[i];
if (block.isLoopHeader()) {
int changesFlags = loopChangesFlags[block.id];
HLoopInformation info = block.loopInformation;
// Iterate over all blocks of this loop. Note that blocks in
// inner loops are not visited here, but we know they
// were visited before because we are iterating in post-order.
// So instructions that are GVN'ed in an inner loop are in their
// loop entry, and [info.blocks] contains this loop entry.
moveLoopInvariantCodeFromBlock(block, block, changesFlags);
for (HBasicBlock other in info.blocks) {
moveLoopInvariantCodeFromBlock(other, block, changesFlags);
}
}
}
}
void moveLoopInvariantCodeFromBlock(
HBasicBlock block, HBasicBlock loopHeader, int changesFlags) {
assert(block.parentLoopHeader == loopHeader || block == loopHeader);
HBasicBlock preheader = loopHeader.predecessors[0];
int dependsFlags = SideEffects.computeDependsOnFlags(changesFlags);
HInstruction instruction = block.first;
bool isLoopAlwaysTaken() {
HInstruction instruction = loopHeader.last;
assert(instruction is HGoto || instruction is HLoopBranch);
return instruction is HGoto || instruction.inputs[0].isConstantTrue();
}
bool firstInstructionInLoop = block == loopHeader
// Compensate for lack of code motion.
||
(blockChangesFlags[loopHeader.id] == 0 &&
isLoopAlwaysTaken() &&
loopHeader.successors[0] == block);
while (instruction != null) {
HInstruction next = instruction.next;
if (instruction.useGvn() &&
instruction.isMovable &&
(!instruction.canThrow(_abstractValueDomain) ||
firstInstructionInLoop) &&
!instruction.sideEffects.dependsOn(dependsFlags)) {
bool loopInvariantInputs = true;
List<HInstruction> inputs = instruction.inputs;
for (int i = 0, length = inputs.length; i < length; i++) {
if (isInputDefinedAfterDominator(inputs[i], preheader)) {
loopInvariantInputs = false;
break;
}
}
// If the inputs are loop invariant, we can move the
// instruction from the current block to the pre-header block.
if (loopInvariantInputs) {
block.detach(instruction);
preheader.moveAtExit(instruction);
} else {
firstInstructionInLoop = false;
}
}
int oldChangesFlags = changesFlags;
changesFlags |= instruction.sideEffects.getChangesFlags();
if (oldChangesFlags != changesFlags) {
dependsFlags = SideEffects.computeDependsOnFlags(changesFlags);
}
instruction = next;
}
}
bool isInputDefinedAfterDominator(HInstruction input, HBasicBlock dominator) {
return input.block.id > dominator.id;
}
void visitBasicBlock(
HBasicBlock block, ValueSet values, List<GvnWorkItem> workQueue) {
HInstruction instruction = block.first;
if (block.isLoopHeader()) {
int flags = loopChangesFlags[block.id];
values.kill(flags);
}
while (instruction != null) {
HInstruction next = instruction.next;
int flags = instruction.sideEffects.getChangesFlags();
assert(flags == 0 || !instruction.useGvn());
values.kill(flags);
if (instruction.useGvn()) {
HInstruction other = values.lookup(instruction);
if (other != null) {
assert(other.gvnEquals(instruction) && instruction.gvnEquals(other));
block.rewriteWithBetterUser(instruction, other);
block.remove(instruction);
} else {
values.add(instruction);
}
}
instruction = next;
}
List<HBasicBlock> dominatedBlocks = block.dominatedBlocks;
for (int i = 0, length = dominatedBlocks.length; i < length; i++) {
HBasicBlock dominated = dominatedBlocks[i];
// No need to copy the value set for the last child.
ValueSet successorValues = (i == length - 1) ? values : values.copy();
// If we have no values in our set, we do not have to kill
// anything. Also, if the range of block ids from the current
// block to the dominated block is empty, there is no blocks on
// any path from the current block to the dominated block so we
// don't have to do anything either.
assert(block.id < dominated.id);
if (!successorValues.isEmpty && block.id + 1 < dominated.id) {
visited.clear();
List<HBasicBlock> workQueue = <HBasicBlock>[dominated];
int changesFlags = 0;
do {
HBasicBlock current = workQueue.removeLast();
changesFlags |=
getChangesFlagsForDominatedBlock(block, current, workQueue);
} while (!workQueue.isEmpty);
successorValues.kill(changesFlags);
}
workQueue.add(new GvnWorkItem(dominated, successorValues));