blob: 3b1632db4b00cabd12b09f41dc11a679d9f643bb [file] [log] [blame]
// Copyright (c) 2016, 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.
// @dart = 2.10
import 'package:js_runtime/synced/embedded_names.dart';
import 'package:js_shared/synced/embedded_names.dart'
show JsBuiltin, JsGetName, TYPES;
import 'package:kernel/ast.dart' as ir;
import '../closure.dart';
import '../common.dart';
import '../common/codegen.dart' show CodegenRegistry;
import '../common/elements.dart';
import '../common/names.dart';
import '../constants/constant_system.dart' as constant_system;
import '../constants/values.dart';
import '../deferred_load/output_unit.dart' show OutputUnit;
import '../dump_info.dart';
import '../elements/entities.dart';
import '../elements/jumps.dart';
import '../elements/names.dart';
import '../elements/types.dart';
import '../inferrer/abstract_value_domain.dart';
import '../inferrer/types.dart';
import '../io/source_information.dart';
import '../ir/class_relation.dart';
import '../ir/static_type.dart';
import '../ir/static_type_provider.dart';
import '../ir/util.dart';
import '../js/js.dart' as js;
import '../js_backend/backend.dart' show FunctionInlineCache;
import '../js_backend/field_analysis.dart'
show FieldAnalysisData, JFieldAnalysis;
import '../js_backend/interceptor_data.dart';
import '../js_backend/inferred_data.dart';
import '../js_backend/namer.dart' show ModularNamer;
import '../js_backend/native_data.dart';
import '../js_backend/runtime_types_resolution.dart';
import '../js_emitter/code_emitter_task.dart' show ModularEmitter;
import '../js_model/class_type_variable_access.dart';
import '../js_model/element_map.dart';
import '../js_model/elements.dart' show JGeneratorBody;
import '../js_model/js_strategy.dart';
import '../js_model/locals.dart' show GlobalLocalsMap, JumpVisitor;
import '../js_model/type_recipe.dart';
import '../kernel/invocation_mirror_constants.dart';
import '../native/behavior.dart';
import '../native/js.dart';
import '../options.dart';
import '../tracer.dart';
import '../universe/call_structure.dart';
import '../universe/feature.dart';
import '../universe/member_usage.dart' show MemberAccess;
import '../universe/selector.dart';
import '../universe/target_checks.dart' show TargetChecks;
import '../universe/use.dart' show ConstantUse, StaticUse, TypeUse;
import '../world.dart';
import 'branch_builder.dart';
import 'jump_handler.dart';
import 'locals_handler.dart';
import 'loop_handler.dart';
import 'metrics.dart';
import 'nodes.dart';
import 'string_builder.dart';
import 'switch_continue_analysis.dart';
import 'type_builder.dart';
// TODO(johnniwinther): Merge this with [KernelInliningState].
class StackFrame {
final StackFrame parent;
final MemberEntity member;
final AsyncMarker asyncMarker;
final KernelToLocalsMap localsMap;
// [ir.Let] and [ir.LocalInitializer] bindings.
final Map<ir.VariableDeclaration, HInstruction> letBindings;
final KernelToTypeInferenceMap typeInferenceMap;
final SourceInformationBuilder sourceInformationBuilder;
final StaticTypeProvider staticTypeProvider;
StackFrame(
this.parent,
this.member,
this.asyncMarker,
this.localsMap,
this.letBindings,
this.typeInferenceMap,
this.sourceInformationBuilder,
this.staticTypeProvider);
}
class KernelSsaGraphBuilder extends ir.Visitor<void> with ir.VisitorVoidMixin {
/// Holds the resulting SSA graph.
final HGraph graph = HGraph();
/// True if the builder is processing nodes inside a try statement. This is
/// important for generating control flow out of a try block like returns or
/// breaks.
bool _inTryStatement = false;
/// Used to track the locals while building the graph.
LocalsHandler localsHandler;
/// A stack of instructions.
///
/// We build the SSA graph by simulating a stack machine.
List<HInstruction> stack = [];
/// The count of nested loops we are currently building.
///
/// The loop nesting is consulted when inlining a function invocation. The
/// inlining heuristics take this information into account.
int loopDepth = 0;
/// A mapping from jump targets to their handlers.
Map<JumpTarget, JumpHandler> jumpTargets = {};
final CompilerOptions options;
final DiagnosticReporter reporter;
final ModularEmitter _emitter;
final ModularNamer _namer;
final MemberEntity targetElement;
final MemberEntity _initialTargetElement;
final JClosedWorld closedWorld;
final CodegenRegistry registry;
final ClosureData _closureDataLookup;
final Tracer _tracer;
/// A stack of [InterfaceType]s that have been seen during inlining of
/// factory constructors. These types are preserved in [HInvokeStatic]s and
/// [HCreate]s inside the inline code and registered during code generation
/// for these nodes.
// TODO(karlklose): consider removing this and keeping the (substituted) types
// of the type variables in an environment (like the [LocalsHandler]).
final List<InterfaceType> _currentImplicitInstantiations = [];
/// Used to report information about inlining (which occurs while building the
/// SSA graph), when dump-info is enabled.
final InfoReporter _infoReporter;
final SsaMetrics _metrics;
HInstruction _rethrowableException;
final SourceInformationStrategy _sourceInformationStrategy;
final JsToElementMap _elementMap;
final GlobalTypeInferenceResults globalInferenceResults;
LoopHandler _loopHandler;
TypeBuilder _typeBuilder;
/// True if we are visiting the expression of a throw statement; we assume
/// this is a slow path.
bool _inExpressionOfThrow = false;
final List<KernelInliningState> _inliningStack = [];
Local _returnLocal;
DartType _returnType;
StackFrame _currentFrame;
final FunctionInlineCache _inlineCache;
final InlineDataCache _inlineDataCache;
final ir.Member _memberContextNode;
KernelSsaGraphBuilder(
this.options,
this.reporter,
this._initialTargetElement,
InterfaceType instanceType,
this._infoReporter,
this._metrics,
this._elementMap,
this.globalInferenceResults,
this.closedWorld,
this.registry,
this._namer,
this._emitter,
this._tracer,
this._sourceInformationStrategy,
this._inlineCache,
this._inlineDataCache)
: this.targetElement = _effectiveTargetElementFor(_initialTargetElement),
this._closureDataLookup = closedWorld.closureDataLookup,
_memberContextNode =
_elementMap.getMemberContextNode(_initialTargetElement) {
_enterFrame(targetElement, null);
_loopHandler = KernelLoopHandler(this);
_typeBuilder = KernelTypeBuilder(this, _elementMap);
graph.element = targetElement;
graph.sourceInformation =
_sourceInformationBuilder.buildVariableDeclaration();
localsHandler = LocalsHandler(this, targetElement, targetElement,
instanceType, _nativeData, _interceptorData);
}
KernelToLocalsMap get _localsMap => _currentFrame.localsMap;
Map<ir.VariableDeclaration, HInstruction> get _letBindings =>
_currentFrame.letBindings;
JCommonElements get _commonElements => _elementMap.commonElements;
JElementEnvironment get _elementEnvironment => _elementMap.elementEnvironment;
JFieldAnalysis get _fieldAnalysis => closedWorld.fieldAnalysis;
KernelToTypeInferenceMap get _typeInferenceMap =>
_currentFrame.typeInferenceMap;
SourceInformationBuilder get _sourceInformationBuilder =>
_currentFrame.sourceInformationBuilder;
AbstractValueDomain get _abstractValueDomain =>
closedWorld.abstractValueDomain;
NativeData get _nativeData => closedWorld.nativeData;
InterceptorData get _interceptorData => closedWorld.interceptorData;
RuntimeTypesNeed get _rtiNeed => closedWorld.rtiNeed;
GlobalLocalsMap get _globalLocalsMap =>
globalInferenceResults.globalLocalsMap;
InferredData get _inferredData => globalInferenceResults.inferredData;
DartTypes get dartTypes => closedWorld.dartTypes;
void push(HInstruction instruction) {
add(instruction);
stack.add(instruction);
}
HInstruction pop() {
return stack.removeLast();
}
/// Pushes a boolean checking [expression] against null.
pushCheckNull(HInstruction expression) {
push(HIdentity(expression, graph.addConstantNull(closedWorld),
_abstractValueDomain.boolType));
}
HBasicBlock _current;
/// The current block to add instructions to. Might be null, if we are
/// visiting dead code, but see [_isReachable].
HBasicBlock get current => _current;
void set current(c) {
_isReachable = c != null;
_current = c;
}
/// The most recently opened block. Has the same value as [current] while
/// the block is open, but unlike [current], it isn't cleared when the
/// current block is closed.
HBasicBlock lastOpenedBlock;
/// Indicates whether the current block is dead (because it has a throw or a
/// return further up). If this is false, then [current] may be null. If the
/// block is dead then it may also be aborted, but for simplicity we only
/// abort on statement boundaries, not in the middle of expressions. See
/// [isAborted].
bool _isReachable = true;
HLocalValue lastAddedParameter;
Map<Local, HInstruction> parameters = {};
Set<Local> elidedParameters;
HBasicBlock addNewBlock() {
HBasicBlock block = graph.addNewBlock();
// If adding a new block during building of an expression, it is due to
// conditional expressions or short-circuit logical operators.
return block;
}
void open(HBasicBlock block) {
block.open();
current = block;
lastOpenedBlock = block;
}
HBasicBlock close(HControlFlow end) {
HBasicBlock result = current;
current.close(end);
current = null;
return result;
}
HBasicBlock _closeAndGotoExit(HControlFlow end) {
HBasicBlock result = current;
current.close(end);
current = null;
result.addSuccessor(graph.exit);
return result;
}
void goto(HBasicBlock from, HBasicBlock to) {
from.close(HGoto(_abstractValueDomain));
from.addSuccessor(to);
}
bool isAborted() {
return current == null;
}
/// Creates a new block, transitions to it from any current block, and
/// opens the new block.
HBasicBlock openNewBlock() {
HBasicBlock newBlock = addNewBlock();
if (!isAborted()) goto(current, newBlock);
open(newBlock);
return newBlock;
}
void add(HInstruction instruction) {
current.add(instruction);
}
HLocalValue addParameter(Entity parameter, AbstractValue type,
{bool isElided = false}) {
HLocalValue result = isElided
? HLocalValue(parameter, type)
: HParameterValue(parameter, type);
if (lastAddedParameter == null) {
graph.entry.addBefore(graph.entry.first, result);
} else {
graph.entry.addAfter(lastAddedParameter, result);
}
lastAddedParameter = result;
return result;
}
HSubGraphBlockInformation wrapStatementGraph(SubGraph statements) {
if (statements == null) return null;
return HSubGraphBlockInformation(statements);
}
HSubExpressionBlockInformation wrapExpressionGraph(SubExpression expression) {
if (expression == null) return null;
return HSubExpressionBlockInformation(expression);
}
HLiteralList _buildLiteralList(List<HInstruction> inputs) {
return HLiteralList(inputs, _abstractValueDomain.growableListType);
}
/// Called when control flow is about to change, in which case we need to
/// specify special successors if we are already in a try/catch/finally block.
void _handleInTryStatement() {
if (!_inTryStatement) return;
HBasicBlock block = close(HExitTry(_abstractValueDomain));
HBasicBlock newBlock = graph.addNewBlock();
block.addSuccessor(newBlock);
open(newBlock);
}
/// Helper to implement JS_GET_FLAG.
///
/// The concrete SSA graph builder will extract a flag parameter from the
/// JS_GET_FLAG call and then push a boolean result onto the stack. This
/// function provides the boolean value corresponding to the given [flagName].
/// If [flagName] is not recognized, this function returns `null` and the
/// concrete SSA builder reports an error.
bool _getFlagValue(String flagName) {
switch (flagName) {
case 'MINIFIED':
return options.enableMinification;
case 'MUST_RETAIN_METADATA':
return false;
case 'USE_CONTENT_SECURITY_POLICY':
return options.features.useContentSecurityPolicy.isEnabled;
case 'VARIANCE':
return options.enableVariance;
case 'LEGACY':
return options.useLegacySubtyping;
case 'PRINT_LEGACY_STARS':
return options.printLegacyStars;
default:
return null;
}
}
StaticType _getStaticType(ir.Expression node) {
// TODO(johnniwinther): Substitute the type by the this type and type
// arguments of the current frame.
ir.DartType type = _currentFrame.staticTypeProvider.getStaticType(node);
return StaticType(
_elementMap.getDartType(type), computeClassRelationFromType(type));
}
StaticType _getStaticForInIteratorType(ir.ForInStatement node) {
// TODO(johnniwinther): Substitute the type by the this type and type
// arguments of the current frame.
ir.DartType type =
_currentFrame.staticTypeProvider.getForInIteratorType(node);
return StaticType(
_elementMap.getDartType(type), computeClassRelationFromType(type));
}
static MemberEntity _effectiveTargetElementFor(MemberEntity member) {
if (member is JGeneratorBody) return member.function;
return member;
}
void _enterFrame(
MemberEntity member, SourceInformation callSourceInformation) {
AsyncMarker asyncMarker = AsyncMarker.SYNC;
ir.FunctionNode function = getFunctionNode(_elementMap, member);
if (function != null) {
asyncMarker = getAsyncMarker(function);
}
_currentFrame = StackFrame(
_currentFrame,
member,
asyncMarker,
_globalLocalsMap.getLocalsMap(member),
{},
KernelToTypeInferenceMapImpl(member, globalInferenceResults),
_currentFrame != null
? _currentFrame.sourceInformationBuilder
.forContext(member, callSourceInformation)
: _sourceInformationStrategy.createBuilderForContext(member),
_elementMap.getStaticTypeProvider(member));
}
void _leaveFrame() {
_currentFrame = _currentFrame.parent;
}
HGraph build() {
return reporter.withCurrentElement(_localsMap.currentMember, () {
// TODO(het): no reason to do this here...
HInstruction.idCounter = 0;
MemberDefinition definition =
_elementMap.getMemberDefinition(_initialTargetElement);
switch (definition.kind) {
case MemberKind.regular:
case MemberKind.closureCall:
ir.Node target = definition.node;
if (target is ir.Procedure) {
if (target.isExternal) {
_buildExternalFunctionNode(targetElement,
_ensureDefaultArgumentValues(target, target.function));
} else {
_buildFunctionNode(targetElement,
_ensureDefaultArgumentValues(target, target.function));
}
} else if (target is ir.Field) {
FieldAnalysisData fieldData =
closedWorld.fieldAnalysis.getFieldData(targetElement);
if (fieldData.initialValue != null) {
registry.registerConstantUse(
ConstantUse.init(fieldData.initialValue));
if (targetElement.isStatic || targetElement.isTopLevel) {
/// No code is created for this field: All references inline the
/// constant value.
return null;
}
} else if (fieldData.isLazy) {
// The generated initializer needs be wrapped in the cyclic-error
// helper.
registry.registerStaticUse(StaticUse.staticInvoke(
closedWorld.commonElements.cyclicThrowHelper,
CallStructure.ONE_ARG));
registry.registerStaticUse(StaticUse.staticInvoke(
closedWorld.commonElements.throwLateFieldADI,
CallStructure.ONE_ARG));
}
if (targetElement.isInstanceMember) {
if (fieldData.isEffectivelyFinal ||
!closedWorld.annotationsData
.getParameterCheckPolicy(targetElement)
.isEmitted) {
// No need for a checked setter.
return null;
}
}
if (targetElement.isInstanceMember) {
_buildInstanceFieldSetter(target);
} else {
_buildStaticFieldInitializer(target);
}
} else if (target is ir.LocalFunction) {
_buildFunctionNode(targetElement,
_ensureDefaultArgumentValues(null, target.function));
} else {
throw 'No case implemented to handle target: '
'$target for $targetElement';
}
break;
case MemberKind.constructor:
ir.Constructor constructor = definition.node;
_ensureDefaultArgumentValues(constructor, constructor.function);
_buildConstructor(targetElement, constructor);
break;
case MemberKind.constructorBody:
ir.Constructor constructor = definition.node;
_ensureDefaultArgumentValues(constructor, constructor.function);
_buildConstructorBody(constructor);
break;
case MemberKind.closureField:
// Closure fields have no setter and therefore never require any code.
return null;
case MemberKind.signature:
ir.Node target = definition.node;
ir.FunctionNode originalClosureNode;
if (target is ir.Procedure) {
originalClosureNode = target.function;
} else if (target is ir.LocalFunction) {
originalClosureNode = target.function;
} else {
failedAt(
targetElement,
"Unexpected function signature: "
"$targetElement inside a non-closure: $target");
}
_buildMethodSignatureNewRti(originalClosureNode);
break;
case MemberKind.generatorBody:
_buildGeneratorBody(
_initialTargetElement, _functionNodeOf(definition.node));
break;
}
assert(graph.isValid(), "Invalid graph for $_initialTargetElement.");
if (_tracer.isEnabled) {
MemberEntity member = _initialTargetElement;
String name = member.name;
if (member.isInstanceMember ||
member.isConstructor ||
member.isStatic) {
name = "${member.enclosingClass.name}.$name";
if (definition.kind == MemberKind.constructorBody) {
name += " (body)";
}
}
_tracer.traceCompilation(name);
_tracer.traceGraph('builder', graph);
}
return graph;
});
}
ir.FunctionNode _functionNodeOf(ir.TreeNode node) {
if (node is ir.Member) return node.function;
if (node is ir.LocalFunction) return node.function;
return null;
}
ir.FunctionNode _ensureDefaultArgumentValues(
ir.Member member, ir.FunctionNode function) {
// Register all [function]'s default argument values.
//
// Default values might be (or contain) functions that are not referenced
// from anywhere else so we need to ensure these are enqueued. Stubs and
// `Function.apply` data are created after the codegen queue is closed, so
// we force these functions into the queue by registering the constants as
// used in advance. See language/cyclic_default_values_test.dart for an
// example.
//
// TODO(sra): We could be more precise if stubs and `Function.apply` data
// were generated by the codegen enqueuer. In practice even in huge programs
// there are only very small number of constants created here that are not
// actually used.
void _registerDefaultValue(ir.VariableDeclaration node) {
ConstantValue constantValue = _elementMap
.getConstantValue(member, node.initializer, implicitNull: true);
assert(
constantValue != null,
failedAt(_elementMap.getMethod(function.parent),
'No constant computed for $node'));
registry?.registerConstantUse(ConstantUse.init(constantValue));
}
function.positionalParameters
.skip(function.requiredParameterCount)
.forEach(_registerDefaultValue);
function.namedParameters.forEach(_registerDefaultValue);
return function;
}
void _buildInstanceFieldSetter(ir.Field node) {
assert(!node.isStatic);
FieldEntity field = _elementMap.getMember(node);
_openFunction(field, checks: TargetChecks.none);
HInstruction thisInstruction = localsHandler.readThis(
sourceInformation: _sourceInformationBuilder.buildGet(node));
// Use dynamic type because the type computed by the inferrer is
// narrowed to the type annotation.
HInstruction parameter =
HParameterValue(field, _abstractValueDomain.dynamicType);
// Add the parameter as the last instruction of the entry block.
// If the method is intercepted, we want the actual receiver
// to be the first parameter.
graph.entry.addBefore(graph.entry.last, parameter);
DartType type = _getDartTypeIfValid(node.type);
HInstruction value = _typeBuilder.potentiallyCheckOrTrustTypeOfParameter(
field, parameter, type);
// TODO(sra): Pass source information to
// [potentiallyCheckOrTrustTypeOfParameter].
// TODO(sra): The source information should indicate the field and
// possibly its type but not the initializer.
value.sourceInformation ??= _sourceInformationBuilder.buildSet(node);
value = _potentiallyAssertNotNull(field, node, value, type);
if (!_fieldAnalysis.getFieldData(field).isElided) {
add(HFieldSet(_abstractValueDomain, field, thisInstruction, value));
}
_closeFunction();
}
void _buildStaticFieldInitializer(ir.Field node) {
assert(node.isStatic);
graph.isLazyInitializer = true;
FieldEntity field = _elementMap.getMember(node);
_openFunction(field, checks: TargetChecks.none);
if (node.initializer != null) {
node.initializer.accept(this);
HInstruction fieldValue = pop();
HInstruction checkInstruction =
_typeBuilder.potentiallyCheckOrTrustTypeOfAssignment(
field, fieldValue, _getDartTypeIfValid(node.type));
stack.add(checkInstruction);
} else {
stack.add(graph.addConstantNull(closedWorld));
}
HInstruction value = pop();
_closeAndGotoExit(HReturn(_abstractValueDomain, value,
_sourceInformationBuilder.buildReturn(node)));
_closeFunction();
}
DartType _getDartTypeIfValid(ir.DartType type) {
if (type is ir.InvalidType) return dartTypes.dynamicType();
return _elementMap.getDartType(type);
}
/// Pops the most recent instruction from the stack and ensures that it is a
/// non-null bool.
HInstruction popBoolified() {
HInstruction value = pop();
return _typeBuilder.potentiallyCheckOrTrustTypeOfCondition(
_currentFrame.member, value);
}
/// Extend current method parameters with parameters for the class type
/// parameters. If the class has type parameters but does not need them, bind
/// to `dynamic` (represented as `null`) so the bindings are available for
/// building types up the inheritance chain of generative constructors.
void _addClassTypeVariablesIfNeeded(MemberEntity member) {
if (!member.isConstructor && member is! ConstructorBodyEntity) {
return;
}
ClassEntity cls = member.enclosingClass;
InterfaceType thisType = _elementEnvironment.getThisType(cls);
if (thisType.typeArguments.isEmpty) {
return;
}
bool needsTypeArguments = _rtiNeed.classNeedsTypeArguments(cls);
thisType.typeArguments.forEach((DartType _typeVariable) {
TypeVariableType typeVariableType = _typeVariable;
HInstruction param;
if (needsTypeArguments) {
param = addParameter(
typeVariableType.element, _abstractValueDomain.nonNullType);
} else {
// Unused, so bind to `dynamic`.
param = graph.addConstantNull(closedWorld);
}
Local local = localsHandler.getTypeVariableAsLocal(typeVariableType);
localsHandler.directLocals[local] = param;
});
}
/// Extend current method parameters with parameters for the function type
/// variables.
void _addFunctionTypeVariablesIfNeeded(MemberEntity member) {
if (member is! FunctionEntity) return;
FunctionEntity function = member;
List<TypeVariableType> typeVariables =
_elementEnvironment.getFunctionTypeVariables(function);
if (typeVariables.isEmpty) {
return;
}
bool needsTypeArguments = _rtiNeed.methodNeedsTypeArguments(function);
bool elideTypeParameters = function.parameterStructure.typeParameters == 0;
for (TypeVariableType typeVariable in typeVariables) {
HInstruction param;
bool erased = false;
if (elideTypeParameters) {
// Add elided type parameters.
param = _computeTypeArgumentDefaultValue(function, typeVariable);
erased = true;
} else if (needsTypeArguments) {
param = addParameter(
typeVariable.element, _abstractValueDomain.nonNullType);
} else {
// Unused, so bind to bound.
param = _computeTypeArgumentDefaultValue(function, typeVariable);
erased = true;
}
Local local = localsHandler.getTypeVariableAsLocal(typeVariable);
localsHandler.directLocals[local] = param;
if (!erased) {
_functionTypeParameterLocals.add(local);
}
}
}
// Locals for function type parameters that can be forwarded, in argument
// position order.
final List<Local> _functionTypeParameterLocals = [];
/// Builds a generative constructor.
///
/// Generative constructors are built in stages, in effect inlining the
/// initializers and constructor bodies up the inheritance chain.
///
/// 1. Extend method parameters with parameters the class's type parameters.
///
/// 2. Add type checks for value parameters (might need result of (1)).
///
/// 3. Walk inheritance chain to build bindings for type parameters of
/// superclasses and mixed-in classes.
///
/// 4. Collect initializer values. Walk up inheritance chain to collect field
/// initializers from field declarations, initializing parameters and
/// initializer.
///
/// 5. Create reified type information for instance.
///
/// 6. Allocate instance and assign initializers and reified type information
/// to fields by calling JavaScript constructor.
///
/// 7. Walk inheritance chain to call or inline constructor bodies.
///
/// All the bindings are put in the constructor's locals handler. The
/// implication is that a class cannot be extended or mixed-in twice. If we in
/// future support repeated uses of a mixin class, we should do so by cloning
/// the mixin class in the Kernel input.
void _buildConstructor(ConstructorEntity constructor, ir.Constructor node) {
SourceInformation sourceInformation =
_sourceInformationBuilder.buildCreate(node);
ClassEntity cls = constructor.enclosingClass;
if (_inliningStack.isEmpty) {
_openFunction(constructor,
functionNode: node.function,
parameterStructure: constructor.parameterStructure,
checks: TargetChecks.none);
}
// [constructorData.fieldValues] accumulates the field initializer values,
// which may be overwritten by initializer-list initializers.
ConstructorData constructorData = ConstructorData();
_buildInitializers(node, constructorData);
List<HInstruction> constructorArguments = [];
// Doing this instead of fieldValues.forEach because we haven't defined the
// order of the arguments here. We can define that with JElements.
bool isCustomElement = _nativeData.isNativeOrExtendsNative(cls) &&
!_nativeData.isJsInteropClass(cls);
InterfaceType thisType = _elementEnvironment.getThisType(cls);
List<FieldEntity> fields = [];
_elementEnvironment.forEachInstanceField(cls,
(ClassEntity enclosingClass, FieldEntity member) {
HInstruction value = constructorData.fieldValues[member];
FieldAnalysisData fieldData = _fieldAnalysis.getFieldData(member);
if (value == null) {
assert(
fieldData.isInitializedInAllocator ||
isCustomElement ||
reporter.hasReportedError,
'No initializer value for field ${member}');
} else {
if (!fieldData.isElided) {
fields.add(member);
DartType type = _elementEnvironment.getFieldType(member);
type = localsHandler.substInContext(type);
constructorArguments.add(_typeBuilder
.potentiallyCheckOrTrustTypeOfAssignment(member, value, type));
}
}
});
_addImplicitInstantiation(thisType);
List<DartType> instantiatedTypes =
List<InterfaceType>.from(_currentImplicitInstantiations);
HInstruction newObject;
if (isCustomElement) {
// Bulk assign to the initialized fields.
newObject = graph.explicitReceiverParameter;
// Null guard ensures an error if we are being called from an explicit
// 'new' of the constructor instead of via an upgrade. It is optimized out
// if there are field initializers.
newObject = HNullCheck(newObject,
_abstractValueDomain.excludeNull(newObject.instructionType))
..sourceInformation = sourceInformation;
add(newObject);
for (int i = 0; i < fields.length; i++) {
add(HFieldSet(_abstractValueDomain, fields[i], newObject,
constructorArguments[i]));
}
} else {
// Create the runtime type information, if needed.
bool needsTypeArguments =
closedWorld.rtiNeed.classNeedsTypeArguments(cls);
if (needsTypeArguments) {
InterfaceType thisType = _elementEnvironment.getThisType(cls);
HInstruction typeArgument = _typeBuilder.analyzeTypeArgumentNewRti(
thisType, sourceElement,
sourceInformation: sourceInformation);
constructorArguments.add(typeArgument);
}
newObject = HCreate(cls, constructorArguments,
_abstractValueDomain.createNonNullExact(cls), sourceInformation,
instantiatedTypes: instantiatedTypes,
hasRtiInput: needsTypeArguments);
add(newObject);
}
_removeImplicitInstantiation(thisType);
HInstruction interceptor;
// Generate calls to the constructor bodies.
for (ir.Constructor body in constructorData.constructorChain.reversed) {
if (_isEmptyStatement(body.function.body)) continue;
List<HInstruction> bodyCallInputs = [];
if (isCustomElement) {
if (interceptor == null) {
ConstantValue constant = InterceptorConstantValue(cls);
interceptor = graph.addConstant(constant, closedWorld);
}
bodyCallInputs.add(interceptor);
}
bodyCallInputs.add(newObject);
// Pass uncaptured arguments first, captured arguments in a box, then type
// arguments.
ConstructorEntity inlinedConstructor = _elementMap.getConstructor(body);
_inlinedFrom(
inlinedConstructor, _sourceInformationBuilder.buildCall(body, body),
() {
ConstructorBodyEntity constructorBody =
_elementMap.getConstructorBody(body);
void handleParameter(ir.VariableDeclaration node,
{/*required*/ bool isElided}) {
if (isElided) return;
Local parameter = _localsMap.getLocalVariable(node);
// If [parameter] is boxed, it will be a field in the box passed as
// the last parameter. So no need to directly pass it.
if (!localsHandler.isBoxed(parameter)) {
bodyCallInputs.add(localsHandler.readLocal(parameter));
}
}
// Provide the parameters to the generative constructor body.
forEachOrderedParameter(_elementMap, constructorBody, handleParameter);
// If there are locals that escape (i.e. mutated in closures), we pass the
// box to the constructor.
CapturedScope scopeData =
_closureDataLookup.getCapturedScope(constructorBody);
if (scopeData.requiresContextBox) {
bodyCallInputs.add(localsHandler.readLocal(scopeData.contextBox));
}
// Pass type arguments.
ClassEntity inlinedConstructorClass = constructorBody.enclosingClass;
if (closedWorld.rtiNeed
.classNeedsTypeArguments(inlinedConstructorClass)) {
InterfaceType thisType =
_elementEnvironment.getThisType(inlinedConstructorClass);
for (DartType typeVariable in thisType.typeArguments) {
DartType result = localsHandler.substInContext(typeVariable);
HInstruction argument =
_typeBuilder.analyzeTypeArgument(result, sourceElement);
bodyCallInputs.add(argument);
}
}
if (!isCustomElement && // TODO(13836): Fix inlining.
_tryInlineMethod(constructorBody, null, null, bodyCallInputs, null,
node, sourceInformation)) {
pop();
} else {
_invokeConstructorBody(body, bodyCallInputs,
_sourceInformationBuilder.buildDeclaration(constructor));
}
});
}
if (_inliningStack.isEmpty) {
_closeAndGotoExit(
HReturn(_abstractValueDomain, newObject, sourceInformation));
_closeFunction();
} else {
localsHandler.updateLocal(_returnLocal, newObject,
sourceInformation: sourceInformation);
}
}
static bool _isEmptyStatement(ir.Statement body) {
if (body is ir.EmptyStatement) return true;
if (body is ir.Block) return body.statements.every(_isEmptyStatement);
return false;
}
void _invokeConstructorBody(ir.Constructor constructor,
List<HInstruction> inputs, SourceInformation sourceInformation) {
MemberEntity constructorBody = _elementMap.getConstructorBody(constructor);
HInvokeConstructorBody invoke = HInvokeConstructorBody(constructorBody,
inputs, _abstractValueDomain.nonNullType, sourceInformation);
add(invoke);
}
/// Sets context for generating code that is the result of inlining
/// [inlinedTarget].
void _inlinedFrom(MemberEntity inlinedTarget,
SourceInformation callSourceInformation, f()) {
reporter.withCurrentElement(inlinedTarget, () {
_enterFrame(inlinedTarget, callSourceInformation);
var result = f();
_leaveFrame();
return result;
});
}
void _ensureTypeVariablesForInitializers(
ConstructorData constructorData, ClassEntity enclosingClass) {
if (!constructorData.includedClasses.add(enclosingClass)) return;
if (_rtiNeed.classNeedsTypeArguments(enclosingClass)) {
// If [enclosingClass] needs RTI, we have to give a value to its type
// parameters. For a super constructor call, the type is the supertype
// of current class. For a redirecting constructor, the type is the
// current type. [LocalsHandler.substInContext] takes care of both.
InterfaceType thisType = _elementEnvironment.getThisType(enclosingClass);
InterfaceType type = localsHandler.substInContext(thisType);
List<DartType> arguments = type.typeArguments;
List<DartType> typeVariables = thisType.typeArguments;
assert(arguments.length == typeVariables.length);
Iterator<DartType> variables = typeVariables.iterator;
type.typeArguments.forEach((DartType argument) {
variables.moveNext();
TypeVariableType typeVariable = variables.current;
localsHandler.updateLocal(
localsHandler.getTypeVariableAsLocal(typeVariable),
_typeBuilder.analyzeTypeArgument(argument, sourceElement));
});
}
}
/// Collects the values for field initializers for the direct fields of
/// [clazz].
void _collectFieldValues(ir.Class clazz, ConstructorData constructorData) {
ClassEntity cls = _elementMap.getClass(clazz);
_elementEnvironment.forEachDirectInstanceField(cls, (FieldEntity field) {
_ensureTypeVariablesForInitializers(
constructorData, field.enclosingClass);
MemberDefinition definition = _elementMap.getMemberDefinition(field);
ir.Field node;
switch (definition.kind) {
case MemberKind.regular:
node = definition.node;
break;
default:
failedAt(field, "Unexpected member definition $definition.");
}
bool ignoreAllocatorAnalysis = false;
if (_nativeData.isNativeOrExtendsNative(cls)) {
// @Native classes have 'fields' which are really getters/setter. Do
// not try to initialize e.g. 'tagName'.
if (_nativeData.isNativeClass(cls)) return;
// Fields that survive this test are fields of custom elements.
ignoreAllocatorAnalysis = true;
}
if (ignoreAllocatorAnalysis ||
!_fieldAnalysis.getFieldData(field).isInitializedInAllocator) {
final initializer = node.initializer;
if (initializer == null) {
constructorData.fieldValues[field] =
graph.addConstantNull(closedWorld);
} else {
// Compile the initializer in the context of the field so we know that
// class type parameters are accessed as values.
// TODO(sra): It would be sufficient to know the context was a field
// initializer.
_inlinedFrom(
field, _sourceInformationBuilder.buildAssignment(initializer),
() {
initializer.accept(this);
constructorData.fieldValues[field] = pop();
});
}
}
});
}
static bool _isRedirectingConstructor(ir.Constructor constructor) =>
constructor.initializers
.any((initializer) => initializer is ir.RedirectingInitializer);
/// Collects field initializers all the way up the inheritance chain.
void _buildInitializers(
ir.Constructor constructor, ConstructorData constructorData) {
assert(
_elementMap.getConstructor(constructor) == _localsMap.currentMember,
failedAt(
_localsMap.currentMember,
'Expected ${_localsMap.currentMember} '
'but found ${_elementMap.getConstructor(constructor)}.'));
constructorData.constructorChain.add(constructor);
if (!_isRedirectingConstructor(constructor)) {
// Compute values for field initializers, but only if this is not a
// redirecting constructor, since the target will compute the fields.
_collectFieldValues(constructor.enclosingClass, constructorData);
}
var foundSuperOrRedirectCall = false;
for (var initializer in constructor.initializers) {
if (initializer is ir.FieldInitializer) {
FieldEntity field = _elementMap.getField(initializer.field);
if (!_fieldAnalysis.getFieldData(field).isInitializedInAllocator) {
initializer.value.accept(this);
constructorData.fieldValues[field] = pop();
}
} else if (initializer is ir.SuperInitializer) {
assert(!foundSuperOrRedirectCall);
foundSuperOrRedirectCall = true;
_inlineSuperInitializer(initializer, constructorData, constructor);
} else if (initializer is ir.RedirectingInitializer) {
assert(!foundSuperOrRedirectCall);
foundSuperOrRedirectCall = true;
_inlineRedirectingInitializer(
initializer, constructorData, constructor);
} else if (initializer is ir.LocalInitializer) {
// LocalInitializer is like a let-expression that is in scope for the
// rest of the initializers.
ir.VariableDeclaration variable = initializer.variable;
assert(variable.isFinal);
variable.initializer.accept(this);
HInstruction value = pop();
// TODO(sra): Apply inferred type information.
_letBindings[variable] = value;
} else if (initializer is ir.AssertInitializer) {
initializer.statement.accept(this);
} else if (initializer is ir.InvalidInitializer) {
assert(false, 'ir.InvalidInitializer not handled');
} else {
assert(false, 'Unhandled initializer ir.${initializer.runtimeType}');
}
}
if (!foundSuperOrRedirectCall) {
assert(
_elementMap.getClass(constructor.enclosingClass) ==
_elementMap.commonElements.objectClass ||
constructor.initializers.any(_ErroneousInitializerVisitor.check),
'All constructors should have super- or redirecting- initializers,'
' except Object()'
' ${constructor.initializers}');
}
}
List<HInstruction> _normalizeAndBuildArguments(
ir.Member member, ir.FunctionNode function, ir.Arguments arguments) {
List<HInstruction> builtArguments = [];
var positionalIndex = 0;
function.positionalParameters.forEach((ir.VariableDeclaration parameter) {
if (positionalIndex < arguments.positional.length) {
arguments.positional[positionalIndex++].accept(this);
builtArguments.add(pop());
} else {
builtArguments.add(_defaultValueForParameter(member, parameter));
}
});
// Evaluate named arguments in given order.
Map<String, HInstruction> namedArguments = _visitNamedArguments(arguments);
// And add them to `builtArguments` in calling-convention order.
function.namedParameters.toList()
..sort(namedOrdering)
..forEach((ir.VariableDeclaration parameter) {
var argument = namedArguments[parameter.name];
argument ??= _defaultValueForParameter(member, parameter);
builtArguments.add(argument);
});
return builtArguments;
}
/// Inlines the given redirecting [constructor]'s initializers by collecting
/// its field values and building its constructor initializers. We visit super
/// constructors all the way up to the [Object] constructor.
void _inlineRedirectingInitializer(ir.RedirectingInitializer initializer,
ConstructorData constructorData, ir.Constructor caller) {
ir.Constructor superOrRedirectConstructor = initializer.target;
List<HInstruction> arguments = _normalizeAndBuildArguments(
superOrRedirectConstructor,
superOrRedirectConstructor.function,
initializer.arguments);
// Redirecting initializer already has [localsHandler] bindings for type
// parameters from the redirecting constructor.
// For redirecting constructors, the fields will be initialized later by the
// effective target, so we don't do it here.
_inlineSuperOrRedirectCommon(initializer, superOrRedirectConstructor,
arguments, constructorData, caller);
}
/// Inlines the given super [constructor]'s initializers by collecting its
/// field values and building its constructor initializers. We visit super
/// constructors all the way up to the [Object] constructor.
void _inlineSuperInitializer(ir.SuperInitializer initializer,
ConstructorData constructorData, ir.Constructor caller) {
ir.Constructor target = initializer.target;
List<HInstruction> arguments = _normalizeAndBuildArguments(
target, target.function, initializer.arguments);
ir.Class callerClass = caller.enclosingClass;
ir.Supertype supertype = callerClass.supertype;
// The class of the super-constructor may not be the supertype class. In
// this case, we must go up the class hierarchy until we reach the class
// containing the super-constructor.
while (supertype.classNode != target.enclosingClass) {
// Fields from unnamed mixin application classes (ie Object&Foo) get
// "collected" with the regular supertype fields, so we must bind type
// parameters from both the supertype and the supertype's mixin classes
// before collecting the field values.
_collectFieldValues(supertype.classNode, constructorData);
supertype = supertype.classNode.supertype;
}
supertype = supertype.classNode.supertype;
_inlineSuperOrRedirectCommon(
initializer, target, arguments, constructorData, caller);
}
void _inlineSuperOrRedirectCommon(
ir.Initializer initializer,
ir.Constructor constructor,
List<HInstruction> arguments,
ConstructorData constructorData,
ir.Constructor caller) {
var index = 0;
ConstructorEntity element = _elementMap.getConstructor(constructor);
MemberEntity oldScopeMember = localsHandler.scopeMember;
_inlinedFrom(
element, _sourceInformationBuilder.buildCall(initializer, initializer),
() {
void handleParameter(ir.VariableDeclaration node) {
Local parameter = _localsMap.getLocalVariable(node);
HInstruction argument = arguments[index++];
// Because we are inlining the initializer, we must update
// what was given as parameter. This will be used in case
// there is a parameter check expression in the initializer.
parameters[parameter] = argument;
localsHandler.updateLocal(parameter, argument);
}
constructor.function.positionalParameters.forEach(handleParameter);
constructor.function.namedParameters.toList()
..sort(namedOrdering)
..forEach(handleParameter);
_ensureTypeVariablesForInitializers(
constructorData, element.enclosingClass);
// Set the locals handler state as if we were inlining the constructor.
localsHandler.setupScope(element);
localsHandler.enterScope(_closureDataLookup.getCapturedScope(element),
_sourceInformationBuilder.buildDeclaration(element));
_buildInitializers(constructor, constructorData);
});
localsHandler.setupScope(oldScopeMember);
}
/// Constructs a special signature function for a closure.
void _buildMethodSignatureNewRti(ir.FunctionNode /*!*/ originalClosureNode) {
// The signature function has no corresponding ir.Node, so we just use the
// targetElement to set up the type environment.
_openFunction(targetElement, checks: TargetChecks.none);
FunctionType functionType =
_elementMap.getFunctionType(originalClosureNode);
HInstruction rti =
_typeBuilder.analyzeTypeArgumentNewRti(functionType, sourceElement);
close(HReturn(_abstractValueDomain, rti,
_sourceInformationBuilder.buildReturn(originalClosureNode)))
.addSuccessor(graph.exit);
_closeFunction();
}
/// Builds generative constructor body.
void _buildConstructorBody(ir.Constructor constructor) {
FunctionEntity constructorBody =
_elementMap.getConstructorBody(constructor);
_openFunction(constructorBody,
functionNode: constructor.function,
parameterStructure: constructorBody.parameterStructure,
checks: TargetChecks.none);
constructor.function.body.accept(this);
_closeFunction();
}
/// Builds an SSA graph for FunctionNodes, found in FunctionExpressions and
/// Procedures.
void _buildFunctionNode(
FunctionEntity function, ir.FunctionNode functionNode) {
if (functionNode.asyncMarker != ir.AsyncMarker.Sync) {
_buildGenerator(function, functionNode);
return;
}
_openFunction(function,
functionNode: functionNode,
parameterStructure: function.parameterStructure,
checks: _checksForFunction(function));
if (options.experimentUnreachableMethodsThrow) {
var emptyParameters = parameters.values.where((parameter) =>
_abstractValueDomain
.isEmpty(parameter.instructionType)
.isDefinitelyTrue);
if (emptyParameters.length > 0) {
_addComment('${emptyParameters} inferred as [empty]');
add(HInvokeStatic(_commonElements.assertUnreachableMethod, [],
_abstractValueDomain.dynamicType, const []));
_closeFunction();
return;
}
}
functionNode.body.accept(this);
_closeFunction();
}
/// Adds a JavaScript comment to the output. The comment will be omitted in
/// minified mode. Each line in [text] is preceded with `//` and indented.
/// Use sparingly. In order for the comment to be retained it is modeled as
/// having side effects which will inhibit code motion.
// TODO(sra): Figure out how to keep comment anchored without effects.
void _addComment(String text) {
add(HForeignCode(js.js.statementTemplateYielding(js.Comment(text)),
_abstractValueDomain.dynamicType, [],
isStatement: true));
}
/// Builds an SSA graph for a sync*/async/async* generator. We generate a
/// entry function which tail-calls a body function. The entry contains
/// per-invocation checks and the body, which is later transformed, contains
/// the re-entrant 'state machine' code.
void _buildGenerator(FunctionEntity function, ir.FunctionNode functionNode) {
_openFunction(function,
functionNode: functionNode,
parameterStructure: function.parameterStructure,
checks: _checksForFunction(function));
// Prepare to tail-call the body.
// Is 'buildAsyncBody' the best location for the entry?
var sourceInformation = _sourceInformationBuilder.buildAsyncBody();
// Forward all the parameters to the body.
List<HInstruction> inputs = [];
if (graph.thisInstruction != null) {
inputs.add(graph.thisInstruction);
}
if (graph.explicitReceiverParameter != null) {
inputs.add(graph.explicitReceiverParameter);
}
for (Local local in parameters.keys) {
if (!elidedParameters.contains(local)) {
inputs.add(localsHandler.readLocal(local));
}
}
for (Local local in _functionTypeParameterLocals) {
inputs.add(localsHandler.readLocal(local));
}
// Add the type parameter for the generator's element type.
DartType elementType = _elementEnvironment.getAsyncOrSyncStarElementType(
function.asyncMarker, _returnType);
// TODO(sra): [elementType] can contain free type variables that are erased
// due to no rtiNeed. We will get getter code if these type variables are
// substituted with an <any> or <erased> type.
if (elementType.containsFreeTypeVariables) {
// Type must be computed in the entry function, where the type variables
// are in scope, and passed to the body function.
inputs.add(_typeBuilder.analyzeTypeArgumentNewRti(elementType, function));
} else {
// Types with no type variables can be emitted as part of the generator,
// avoiding an extra argument.
if (_generatedEntryIsEmpty()) {
// If the entry function is empty (e.g. no argument checks) and the type
// can be generated in body, 'inline' the body by generating it in
// place. This works because the subsequent transformation of the code
// is 'correct' for the empty entry function code.
graph.needsAsyncRewrite = true;
graph.asyncElementType = elementType;
functionNode.body.accept(this);
_closeFunction();
return;
}
}
JGeneratorBody body = _elementMap.getGeneratorBody(function);
push(HInvokeGeneratorBody(
body,
inputs,
_abstractValueDomain.dynamicType, // TODO: better type.
sourceInformation));
_closeAndGotoExit(HReturn(_abstractValueDomain, pop(), sourceInformation));
_closeFunction();
}
/// Builds an SSA graph for a sync*/async/async* generator body.
void _buildGeneratorBody(
JGeneratorBody function, ir.FunctionNode functionNode) {
FunctionEntity entry = function.function;
_openFunction(entry,
functionNode: functionNode,
parameterStructure: function.parameterStructure,
checks: TargetChecks.none);
graph.needsAsyncRewrite = true;
if (!function.elementType.containsFreeTypeVariables) {
// We can generate the element type in place
graph.asyncElementType = function.elementType;
}
functionNode.body.accept(this);
_closeFunction();
}
bool _generatedEntryIsEmpty() {
HBasicBlock block = current;
// If `block.id` is not 1 then we generated some control flow.
if (block.id != 1) return false;
for (HInstruction node = block.first; node != null; node = node.next) {
if (node is HGoto) continue;
if (node is HLoadType) continue; // Orphaned if check is redundant.
return false;
}
return true;
}
void _potentiallyAddFunctionParameterTypeChecks(MemberEntity member,
ir.FunctionNode function, TargetChecks targetChecks) {
// Put the type checks in the first successor of the entry,
// because that is where the type guards will also be inserted.
// This way we ensure that a type guard will dominate the type
// check.
if (targetChecks.checkTypeParameters) {
_checkTypeVariableBounds(targetElement);
}
MemberDefinition definition =
_elementMap.getMemberDefinition(targetElement);
bool nodeIsConstructorBody = definition.kind == MemberKind.constructorBody;
void _handleParameter(ir.VariableDeclaration variable) {
Local local = _localsMap.getLocalVariable(variable);
if (nodeIsConstructorBody &&
_closureDataLookup
.getCapturedScope(targetElement)
.isBoxedVariable(_localsMap, local)) {
// If local is boxed, then `variable` will be a field inside the box
// passed as the last parameter, so no need to update our locals
// handler or check types at this point.
return;
}
if (elidedParameters.contains(local)) {
// Elided parameters are initialized to a default value that is
// statically checked.
return;
}
HInstruction newParameter = localsHandler.readLocal(local);
assert(newParameter != null, "No initial instruction for ${local}.");
DartType type = _getDartTypeIfValid(variable.type);
if (targetChecks.checkAllParameters ||
(targetChecks.checkCovariantParameters &&
(variable.isCovariantByClass ||
variable.isCovariantByDeclaration))) {
newParameter = _typeBuilder.potentiallyCheckOrTrustTypeOfParameter(
targetElement, newParameter, type);
} else {
newParameter = _typeBuilder.trustTypeOfParameter(
targetElement, newParameter, type);
}
// TODO(sra): Hoist out of loop.
newParameter =
_potentiallyAssertNotNull(member, variable, newParameter, type);
localsHandler.updateLocal(local, newParameter);
}
function.positionalParameters.forEach(_handleParameter);
function.namedParameters.toList().forEach(_handleParameter);
}
void _checkTypeVariableBounds(FunctionEntity method) {
if (_rtiNeed.methodNeedsTypeArguments(method) &&
closedWorld.annotationsData.getParameterCheckPolicy(method).isEmitted) {
ir.FunctionNode function = getFunctionNode(_elementMap, method);
for (ir.TypeParameter typeParameter in function.typeParameters) {
Local local = _localsMap.getLocalTypeVariableEntity(_elementMap
.getTypeVariableType(
ir.TypeParameterType(typeParameter, ir.Nullability.nonNullable))
.element);
HInstruction newParameter = localsHandler.directLocals[local];
DartType bound = _getDartTypeIfValid(typeParameter.bound);
if (!dartTypes.isTopType(bound)) {
registry.registerTypeUse(TypeUse.typeVariableBoundCheck(bound));
// TODO(sigmund): method name here is not minified, should it be?
_checkTypeBound(newParameter, bound, local.name, method.name);
}
}
}
}
/// In mixed mode, inserts an assertion of the form `assert(x != null)` for
/// parameters in opt-in libraries that have a static type that cannot be
/// nullable under a strong interpretation.
HInstruction _potentiallyAssertNotNull(MemberEntity member,
ir.TreeNode context, HInstruction value, DartType type) {
if (!options.enableNullAssertions) return value;
if (!_isNonNullableByDefault(context)) return value;
if (!dartTypes.isNonNullableIfSound(type)) return value;
// `operator==` is usually augmented to handle a `null`-argument before this
// test would be inserted. There are a few exceptions (Object,
// Interceptor), where the body of the `==` method is designed to handle a
// `null` argument. In the usual case the null assertion is unnecessary and
// will be optimized away. In the exception cases a null assertion would be
// incorrect. Either way we should not do a null-assertion on the parameter
// of any `operator==` method.
if (member.name == '==') return value;
if (options.enableUserAssertions) {
pushCheckNull(value);
push(HNot(pop(), _abstractValueDomain.boolType));
var sourceInformation = _sourceInformationBuilder.buildAssert(context);
_pushStaticInvocation(
_commonElements.assertHelper,
[pop()],
_typeInferenceMap.getReturnTypeOf(_commonElements.assertHelper),
const <DartType>[],
sourceInformation: sourceInformation);
pop();
return value;
} else {
HInstruction nullCheck = HNullCheck(
value, _abstractValueDomain.excludeNull(value.instructionType))
..sourceInformation = value.sourceInformation;
add(nullCheck);
return nullCheck;
}
}
bool _isNonNullableByDefault(ir.TreeNode node) {
if (node is ir.Library) return node.isNonNullableByDefault;
return _isNonNullableByDefault(node.parent);
}
/// Builds an SSA graph for FunctionNodes of external methods. This produces a
/// graph for a method with Dart calling conventions that forwards to the
/// actual external method.
void _buildExternalFunctionNode(
FunctionEntity function, ir.FunctionNode functionNode) {
assert(functionNode.body == null);
bool isJsInterop = closedWorld.nativeData.isJsInteropMember(function);
_openFunction(function,
functionNode: functionNode,
parameterStructure: function.parameterStructure,
checks: _checksForFunction(function));
if (closedWorld.nativeData.isNativeMember(targetElement)) {
List<HInstruction> inputs = [];
if (targetElement.isInstanceMember) {
inputs.add(localsHandler.readThis(
sourceInformation:
_sourceInformationBuilder.buildGet(functionNode)));
}
void handleParameter(ir.VariableDeclaration param) {
Local local = _localsMap.getLocalVariable(param);
// Convert Dart function to JavaScript function.
HInstruction argument = localsHandler.readLocal(local);
ir.DartType type = param.type;
if (!isJsInterop && type is ir.FunctionType) {
int arity = type.positionalParameters.length;
_pushStaticInvocation(
_commonElements.closureConverter,
[argument, graph.addConstantInt(arity, closedWorld)],
_abstractValueDomain.dynamicType,
const <DartType>[],
sourceInformation: null);
argument = pop();
}
inputs.add(argument);
}
for (int position = 0;
position < function.parameterStructure.positionalParameters;
position++) {
handleParameter(functionNode.positionalParameters[position]);
}
if (functionNode.namedParameters.isNotEmpty) {
List<ir.VariableDeclaration> namedParameters = functionNode
.namedParameters
// Filter elided parameters.
.where((p) =>
function.parameterStructure.namedParameters.contains(p.name))
.toList();
// Sort by file offset to visit parameters in declaration order.
namedParameters.sort(nativeOrdering);
namedParameters.forEach(handleParameter);
}
NativeBehavior nativeBehavior =
_nativeData.getNativeMethodBehavior(function);
AbstractValue returnType =
_typeInferenceMap.typeFromNativeBehavior(nativeBehavior, closedWorld);
push(HInvokeExternal(targetElement, inputs, returnType, nativeBehavior,
sourceInformation: null));
HInstruction value = pop();
// TODO(johnniwinther): Provide source information.
if (options.nativeNullAssertions) {
if (_isNonNullableByDefault(functionNode)) {
DartType type = _getDartTypeIfValid(functionNode.returnType);
if (dartTypes.isNonNullableIfSound(type) &&
nodeIsInWebLibrary(functionNode)) {
push(HNullCheck(value, _abstractValueDomain.excludeNull(returnType),
sticky: true));
value = pop();
}
}
}
if (targetElement.isSetter) {
_closeAndGotoExit(HGoto(_abstractValueDomain));
} else {
_emitReturn(value, _sourceInformationBuilder.buildReturn(functionNode));
}
}
_closeFunction();
}
void _addImplicitInstantiation(DartType type) {
if (type != null) {
_currentImplicitInstantiations.add(type);
}
}
void _removeImplicitInstantiation(DartType type) {
if (type != null) {
_currentImplicitInstantiations.removeLast();
}
}
TargetChecks _checksForFunction(FunctionEntity function) {
if (!function.isInstanceMember) {
// Static methods with no tear-off can be generated with no checks.
MemberAccess access = closedWorld.getMemberAccess(function);
if (access != null && access.reads.isEmpty) {
return TargetChecks.none;
}
}
// TODO(sra): Instance methods can be generated with reduced checks if
// called only from non-dynamic call-sites.
return TargetChecks.dynamicChecks;
}
void _openFunction(MemberEntity member,
{ir.FunctionNode functionNode,
ParameterStructure parameterStructure,
/*required*/ TargetChecks checks}) {
assert(checks != null);
Map<Local, AbstractValue> parameterMap = {};
List<ir.VariableDeclaration> elidedParameters = [];
Set<Local> elidedParameterSet = Set();
if (functionNode != null) {
assert(parameterStructure != null);
void handleParameter(ir.VariableDeclaration node,
{bool isOptional, bool isElided}) {
Local local = _localsMap.getLocalVariable(node);
if (isElided) {
elidedParameters.add(node);
elidedParameterSet.add(local);
}
parameterMap[local] =
_typeInferenceMap.getInferredTypeOfParameter(local);
}
forEachOrderedParameterByFunctionNode(
functionNode, parameterStructure, handleParameter);
_returnType = _elementMap.getDartType(functionNode.returnType);
}
HBasicBlock block = graph.addNewBlock();
// Create `graph.entry` as an initially empty block. `graph.entry` is
// treated specially (holding parameters, local variables and constants)
// but cannot receive constants before it has been closed. By closing it
// here, we can use constants in the code that sets up the function.
open(graph.entry);
close(HGoto(_abstractValueDomain)).addSuccessor(block);
open(block);
localsHandler.startFunction(targetElement, parameterMap, elidedParameterSet,
_sourceInformationBuilder.buildDeclaration(targetElement),
isGenerativeConstructorBody: targetElement is ConstructorBodyEntity);
ir.Member memberContextNode = _elementMap.getMemberContextNode(member);
for (ir.VariableDeclaration node in elidedParameters) {
Local local = _localsMap.getLocalVariable(node);
localsHandler.updateLocal(
local, _defaultValueForParameter(memberContextNode, node));
}
_addClassTypeVariablesIfNeeded(member);
_addFunctionTypeVariablesIfNeeded(member);
// If [member] is `operator==` we explicitly add a null check at the
// beginning of the method. This is to avoid having call sites do the null
// check. The null check is added before the argument type checks since in
// strong mode, the parameter type might be non-nullable.
if (member.name == '==') {
if (functionNode == null)
throw StateError("'==' should have functionNode");
if (!_commonElements.operatorEqHandlesNullArgument(member)) {
_handleIf(
visitCondition: () {
HParameterValue parameter = parameters.values.first;
push(HIdentity(parameter, graph.addConstantNull(closedWorld),
_abstractValueDomain.boolType));
},
visitThen: () {
_closeAndGotoExit(HReturn(
_abstractValueDomain,
graph.addConstantBool(false, closedWorld),
_sourceInformationBuilder.buildReturn(functionNode)));
},
visitElse: null,
sourceInformation: _sourceInformationBuilder.buildIf(functionNode));
}
}
if (functionNode != null) {
_potentiallyAddFunctionParameterTypeChecks(member, functionNode, checks);
}
_insertCoverageCall(member);
}
void _closeFunction() {
if (!isAborted()) _closeAndGotoExit(HGoto(_abstractValueDomain));
graph.finalize(_abstractValueDomain);
}
@override
void defaultNode(ir.Node node) {
throw UnsupportedError("Unhandled node $node (${node.runtimeType})");
}
/// Returns the current source element. This is used by the type builder.
// TODO(efortuna): Update this when we implement inlining.
// TODO(sra): Re-implement type builder using Kernel types and the
// `target` for context.
MemberEntity get sourceElement => _currentFrame.member;
@override
void visitCheckLibraryIsLoaded(ir.CheckLibraryIsLoaded checkLoad) {
ImportEntity import = _elementMap.getImport(checkLoad.import);
String loadId = closedWorld.outputUnitData.getImportDeferName(
_elementMap.getSpannable(targetElement, checkLoad), import);
HInstruction prefixConstant = graph.addConstantString(loadId, closedWorld);
_pushStaticInvocation(
_commonElements.checkDeferredIsLoaded,
[prefixConstant],
_typeInferenceMap
.getReturnTypeOf(_commonElements.checkDeferredIsLoaded),
const <DartType>[],
sourceInformation: null);
}
@override
void visitLoadLibrary(ir.LoadLibrary loadLibrary) {
String loadId = closedWorld.outputUnitData.getImportDeferName(
_elementMap.getSpannable(targetElement, loadLibrary),
_elementMap.getImport(loadLibrary.import));
// TODO(efortuna): Source information!
push(HInvokeStatic(
_commonElements.loadDeferredLibrary,
[graph.addConstantString(loadId, closedWorld)],
_abstractValueDomain.nonNullType,
const <DartType>[],
targetCanThrow: false));
}
@override
void visitBlock(ir.Block block) {
assert(!isAborted());
// [block] can be unreachable at the beginning of a block if an
// ir.BlockExpression that is a subexpression of an expression that contains
// a throwing prior subexpression, e.g. `[throw e, {...[]}]`.
if (!_isReachable) return;
for (ir.Statement statement in block.statements) {
statement.accept(this);
if (!_isReachable) {
// The block has been aborted by a return or a throw.
if (stack.isNotEmpty) {
reporter.internalError(
NO_LOCATION_SPANNABLE, 'Non-empty instruction stack.');
}
return;
}
}
assert(!current.isClosed());
if (stack.isNotEmpty) {
reporter.internalError(
NO_LOCATION_SPANNABLE, 'Non-empty instruction stack');
}
}
@override
void visitEmptyStatement(ir.EmptyStatement node) {
// Empty statement adds no instructions to current block.
}
@override
void visitExpressionStatement(ir.ExpressionStatement node) {
if (!_isReachable) return;
ir.Expression expression = node.expression;
if (expression is ir.Throw && _inliningStack.isEmpty) {
_visitThrowExpression(expression.expression);
_handleInTryStatement();
SourceInformation sourceInformation =
_sourceInformationBuilder.buildThrow(node.expression);
_closeAndGotoExit(HThrow(_abstractValueDomain, pop(), sourceInformation));
} else {
expression.accept(this);
pop();
}
}
@override
void visitConstantExpression(ir.ConstantExpression node) {
ConstantValue value =
_elementMap.getConstantValue(_memberContextNode, node);
SourceInformation sourceInformation =
_sourceInformationBuilder.buildGet(node);
if (!closedWorld.outputUnitData
.hasOnlyNonDeferredImportPathsToConstant(targetElement, value)) {
OutputUnit outputUnit =
closedWorld.outputUnitData.outputUnitForConstant(value);
ConstantValue deferredConstant =
DeferredGlobalConstantValue(value, outputUnit);
registry.registerConstantUse(ConstantUse.deferred(deferredConstant));
stack.add(graph.addDeferredConstant(
deferredConstant, sourceInformation, closedWorld));
} else {
stack.add(graph.addConstant(value, closedWorld,
sourceInformation: sourceInformation));
}
}
@override
void visitReturnStatement(ir.ReturnStatement node) {
SourceInformation sourceInformation =
_sourceInformationBuilder.buildReturn(node);
HInstruction value = null;
if (node.expression != null) {
node.expression.accept(this);
value = pop();
if (_currentFrame.asyncMarker == AsyncMarker.ASYNC) {
// TODO(johnniwinther): Is this special-casing of async still needed
// or should we use the general check below?
/*if (options.enableTypeAssertions &&
!isValidAsyncReturnType(_returnType)) {
generateTypeError(
"Async function returned a Future,"
" was declared to return a ${_returnType}.",
sourceInformation);
pop();
return;
}*/
} else {
value = _typeBuilder.potentiallyCheckOrTrustTypeOfAssignment(
_currentFrame.member, value, _returnType);
}
}
_handleInTryStatement();
if (_inliningStack.isEmpty && targetElement.isSetter) {
if (node.parent is ir.FunctionNode) {
// An arrow function definition of a setter has a ReturnStatement as a
// body, e.g. "set foo(x) => this._x = x;". There is no way to access
// the returned value, so don't emit a return.
return;
}
}
_emitReturn(value, sourceInformation);
}
@override
void visitForStatement(ir.ForStatement node) {
assert(_isReachable);
assert(node.body != null);
void buildInitializer() {
for (ir.VariableDeclaration declaration in node.variables) {
declaration.accept(this);
}
}
HInstruction buildCondition() {
if (node.condition == null) {
return graph.addConstantBool(true, closedWorld);
}
node.condition.accept(this);
return popBoolified();
}
void buildUpdate() {
for (ir.Expression expression in node.updates) {
expression.accept(this);
assert(!isAborted());
// The result of the update instruction isn't used, and can just
// be dropped.
pop();
}
}
void buildBody() {
node.body.accept(this);
}
JumpTarget jumpTarget = _localsMap.getJumpTargetForFor(node);
_loopHandler.handleLoop(
node,
_closureDataLookup.getCapturedLoopScope(node),
jumpTarget,
buildInitializer,
buildCondition,
buildUpdate,
buildBody,
_sourceInformationBuilder.buildLoop(node));
}
@override
void visitForInStatement(ir.ForInStatement node) {
if (node.isAsync) {
_buildAsyncForIn(node);
} else if (_typeInferenceMap.isJsIndexableIterator(
node, _abstractValueDomain)) {
// If the expression being iterated over is a JS indexable type, we can
// generate an optimized version of for-in that uses indexing.
_buildForInIndexable(node);
} else {
_buildForInIterator(node);
}
}
/// Builds the graph for a for-in node with an indexable expression.
///
/// In this case we build:
///
/// int end = a.length;
/// for (int i = 0;
/// i < a.length;
/// checkConcurrentModificationError(a.length == end, a), ++i) {
/// <declaredIdentifier> = a[i];
/// <body>
/// }
void _buildForInIndexable(ir.ForInStatement node) {
SyntheticLocal indexVariable = localsHandler.createLocal('_i');
// These variables are shared by initializer, condition, body and update.
HInstruction array; // Set in buildInitializer.
bool isFixed; // Set in buildInitializer.
HInstruction originalLength = null; // Set for growable lists.
HInstruction buildGetLength(SourceInformation sourceInformation) {
HGetLength result = HGetLength(
array, _abstractValueDomain.positiveIntType,
isAssignable: !isFixed)
..sourceInformation = sourceInformation;
add(result);
return result;
}
void buildConcurrentModificationErrorCheck() {
if (originalLength == null) return;
// The static call checkConcurrentModificationError() is expanded in
// codegen to:
//
// array.length == _end || throwConcurrentModificationError(array)
//
SourceInformation sourceInformation =
_sourceInformationBuilder.buildForInMoveNext(node);
HInstruction length = buildGetLength(sourceInformation);
push(HIdentity(length, originalLength, _abstractValueDomain.boolType)
..sourceInformation = sourceInformation);
_pushStaticInvocation(
_commonElements.checkConcurrentModificationError,
[pop(), array],
_typeInferenceMap.getReturnTypeOf(
_commonElements.checkConcurrentModificationError),
const <DartType>[],
sourceInformation: sourceInformation);
pop();
}
void buildInitializer() {
SourceInformation sourceInformation =
_sourceInformationBuilder.buildForInIterator(node);
node.iterable.accept(this);
array = pop();
isFixed = _abstractValueDomain
.isFixedLengthJsIndexable(array.instructionType)
.isDefinitelyTrue;
localsHandler.updateLocal(
indexVariable, graph.addConstantInt(0, closedWorld),
sourceInformation: sourceInformation);
originalLength = buildGetLength(sourceInformation);
}
HInstruction buildCondition() {
SourceInformation sourceInformation =
_sourceInformationBuilder.buildForInMoveNext(node);
HInstruction index = localsHandler.readLocal(indexVariable,
sourceInformation: sourceInformation);
HInstruction length = buildGetLength(sourceInformation);
HInstruction compare = HLess(index, length, _abstractValueDomain.boolType)
..sourceInformation = sourceInformation;
add(compare);
return compare;
}
void buildBody() {
// If we had mechanically inlined ArrayIterator.moveNext(), it would have
// inserted the ConcurrentModificationError check as part of the
// condition. It is not necessary on the first iteration since there is
// no code between calls to `get iterator` and `moveNext`, so the test is
// moved to the loop update.
// Find a type for the element. Use the element type of the indexer of the
// array, as this is stronger than the Iterator's `get current` type, for
// example, `get current` includes null.
// TODO(sra): The element type of a container type mask might be better.
AbstractValue type = _typeInferenceMap.inferredIndexType(node);
SourceInformation sourceInformation =
_sourceInformationBuilder.buildForInCurrent(node);
HInstruction index = localsHandler.readLocal(indexVariable,
sourceInformation: sourceInformation);
// No bound check is necessary on indexer as it is immediately guarded by
// the condition.
HInstruction value = HIndex(array, index, type)
..sourceInformation = sourceInformation;
add(value);
Local loopVariableLocal = _localsMap.getLocalVariable(node.variable);
localsHandler.updateLocal(loopVariableLocal, value,
sourceInformation: sourceInformation);
// Hint to name loop value after name of loop variable.
if (loopVariableLocal is! SyntheticLocal) {
value.sourceElement ??= loopVariableLocal;
}
node.body.accept(this);
}
void buildUpdate() {
// See buildBody as to why we check here.
buildConcurrentModificationErrorCheck();
// TODO(sra): It would be slightly shorter to generate `a[i++]` in the
// body (and that more closely follows what an inlined iterator would do)
// but the code is horrible as `i+1` is carried around the loop in an
// additional variable.
SourceInformation sourceInformation =
_sourceInformationBuilder.buildForInSet(node);
HInstruction index = localsHandler.readLocal(indexVariable,
sourceInformation: sourceInformation);
HInstruction one = graph.addConstantInt(1, closedWorld);
HInstruction addInstruction =
HAdd(index, one, _abstractValueDomain.positiveIntType)
..sourceInformation = sourceInformation;
add(addInstruction);
localsHandler.updateLocal(indexVariable, addInstruction,
sourceInformation: sourceInformation);
}
_loopHandler.handleLoop(
node,
_closureDataLookup.getCapturedLoopScope(node),
_localsMap.getJumpTargetForForIn(node),
buildInitializer,
buildCondition,
buildUpdate,
buildBody,
_sourceInformationBuilder.buildLoop(node));
}
void _buildForInIterator(ir.ForInStatement node) {
// Generate a structure equivalent to:
// Iterator<E> $iter = <iterable>.iterator;
// while ($iter.moveNext()) {
// <variable> = $iter.current;
// <body>
// }
// The iterator is shared between initializer, condition and body.
HInstruction iterator;
StaticType iteratorType = _getStaticForInIteratorType(node);
void buildInitializer() {
AbstractValue receiverType = _typeInferenceMap.typeOfIterator(node);
node.iterable.accept(this);
HInstruction receiver = pop();
_pushDynamicInvocation(
node,
_getStaticType(node.iterable),
receiverType,
Selectors.iterator,
[receiver],
const <DartType>[],
_sourceInformationBuilder.buildForInIterator(node));
iterator = pop();
}
HInstruction buildCondition() {
AbstractValue receiverType =
_typeInferenceMap.typeOfIteratorMoveNext(node);
_pushDynamicInvocation(
node,
iteratorType,
receiverType,
Selectors.moveNext,
[iterator],
const <DartType>[],
_sourceInformationBuilder.buildForInMoveNext(node));
return popBoolified();
}
void buildBody() {
SourceInformation sourceInformation =
_sourceInformationBuilder.buildForInCurrent(node);
AbstractValue receiverType =
_typeInferenceMap.typeOfIteratorCurrent(node);
_pushDynamicInvocation(node, iteratorType, receiverType,
Selectors.current, [iterator], const <DartType>[], sourceInformation);
Local loopVariableLocal = _localsMap.getLocalVariable(node.variable);
HInstruction value = _typeBuilder.potentiallyCheckOrTrustTypeOfAssignment(
_currentFrame.member, pop(), _getDartTypeIfValid(node.variable.type));
localsHandler.updateLocal(loopVariableLocal, value,
sourceInformation: sourceInformation);
// Hint to name loop value after name of loop variable.
if (loopVariableLocal is! SyntheticLocal) {
value.sourceElement ??= loopVariableLocal;
}
node.body.accept(this);
}
_loopHandler.handleLoop(
node,
_closureDataLookup.getCapturedLoopScope(node),
_localsMap.getJumpTargetForForIn(node),
buildInitializer,
buildCondition,
() {},
buildBody,
_sourceInformationBuilder.buildLoop(node));
}
void _buildAsyncForIn(ir.ForInStatement node) {
// The async-for is implemented with a StreamIterator.
HInstruction streamIterator;
node.iterable.accept(this);
List<HInstruction> arguments = [pop()];
ClassEntity cls = _commonElements.streamIterator;
DartType typeArg = _elementMap.getDartType(node.variable.type);
InterfaceType instanceType =
localsHandler.substInContext(dartTypes.interfaceType(cls, [typeArg]));
// TODO(johnniwinther): This should be the exact type.
StaticType staticInstanceType =
StaticType(instanceType, ClassRelation.subtype);
_addImplicitInstantiation(instanceType);
SourceInformation sourceInformation =
_sourceInformationBuilder.buildForInIterator(node);
// TODO(johnniwinther): Pass type arguments to constructors like calling
// a generic method.
if (_rtiNeed.classNeedsTypeArguments(cls)) {
_addTypeArguments(arguments, [typeArg], sourceInformation);
}
ConstructorEntity constructor = _commonElements.streamIteratorConstructor;
_pushStaticInvocation(constructor, arguments,
_typeInferenceMap.getReturnTypeOf(constructor), const <DartType>[],
instanceType: instanceType, sourceInformation: sourceInformation);
streamIterator = pop();
void buildInitializer() {}
HInstruction buildCondition() {
AbstractValue receiverType =
_typeInferenceMap.typeOfIteratorMoveNext(node);
_pushDynamicInvocation(
node,
staticInstanceType,
receiverType,
Selectors.moveNext,
[streamIterator],
const <DartType>[],
_sourceInformationBuilder.buildForInMoveNext(node));
HInstruction future = pop();
push(HAwait(future, _abstractValueDomain.dynamicType));
return popBoolified();
}
void buildBody() {
AbstractValue receiverType =
_typeInferenceMap.typeOfIteratorCurrent(node);
_pushDynamicInvocation(
node,
staticInstanceType,
receiverType,
Selectors.current,
[streamIterator],
const <DartType>[],
_sourceInformationBuilder.buildForInIterator(node));
localsHandler.updateLocal(
_localsMap.getLocalVariable(node.variable), pop());
node.body.accept(this);
}
void buildUpdate() {}
// Creates a synthetic try/finally block in case anything async goes amiss.
TryCatchFinallyBuilder tryBuilder =
TryCatchFinallyBuilder(this, _sourceInformationBuilder.buildLoop(node));
// Build fake try body:
_loopHandler.handleLoop(
node,
_closureDataLookup.getCapturedLoopScope(node),
_localsMap.getJumpTargetForForIn(node),
buildInitializer,
buildCondition,
buildUpdate,
buildBody,
_sourceInformationBuilder.buildLoop(node));
void finalizerFunction() {
_pushDynamicInvocation(
node,
staticInstanceType,
null,
Selectors.cancel,
[streamIterator],
const <DartType>[],
_sourceInformationBuilder
// ignore:deprecated_member_use_from_same_package
.buildGeneric(node));
add(HAwait(pop(), _abstractValueDomain.dynamicType));
}
tryBuilder
..closeTryBody()
..buildFinallyBlock(finalizerFunction)
..cleanUp();
}
HInstruction _callSetRuntimeTypeInfo(HInstruction typeInfo,
HInstruction newObject, SourceInformation sourceInformation) {
// Set the runtime type information on the object.
FunctionEntity typeInfoSetterFn = _commonElements.setArrayType;
// TODO(efortuna): Insert source information in this static invocation.
_pushStaticInvocation(typeInfoSetterFn, [newObject, typeInfo],
_abstractValueDomain.dynamicType, const <DartType>[],
sourceInformation: sourceInformation);
// The new object will now be referenced through the
// `setArrayType` call. We therefore set the type of that
// instruction to be of the object's type.
assert(
stack.last is HInvokeStatic || stack.last == newObject,
failedAt(
CURRENT_ELEMENT_SPANNABLE,
"Unexpected `stack.last`: Found ${stack.last}, "
"expected ${newObject} or an HInvokeStatic. "
"State: typeInfo=$typeInfo, stack=$stack."));
stack.last.instructionType = newObject.instructionType;
return pop();
}
@override
void visitWhileStatement(ir.WhileStatement node) {
assert(_isReachable);
HInstruction buildCondition() {
node.condition.accept(this);
return popBoolified();
}
_loopHandler.handleLoop(
node,
_closureDataLookup.getCapturedLoopScope(node),
_localsMap.getJumpTargetForWhile(node),
() {},
buildCondition,
() {}, () {
node.body.accept(this);
}, _sourceInformationBuilder.buildLoop(node));
}
@override
void visitDoStatement(ir.DoStatement node) {
SourceInformation sourceInformation =
_sourceInformationBuilder.buildLoop(node);
// TODO(efortuna): I think this can be rewritten using
// LoopHandler.handleLoop with some tricks about when the "update" happens.
LocalsHandler savedLocals = LocalsHandler.from(localsHandler);
CapturedLoopScope loopClosureInfo =
_closureDataLookup.getCapturedLoopScope(node);
localsHandler.startLoop(loopClosureInfo, sourceInformation);
JumpTarget target = _localsMap.getJumpTargetForDo(node);
JumpHandler jumpHandler = _loopHandler.beginLoopHeader(node, target);
HLoopInformation loopInfo = current.loopInformation;
HBasicBlock loopEntryBlock = current;
HBasicBlock bodyEntryBlock = current;
bool hasContinues = target != null && target.isContinueTarget;
if (hasContinues) {
// Add extra block to hang labels on.
// It doesn't currently work if they are on the same block as the
// HLoopInfo. The handling of HLabeledBlockInformation will visit a
// SubGraph that starts at the same block again, so the HLoopInfo is
// either handled twice, or it's handled after the labeled block info,
// both of which generate the wrong code.
// Using a separate block is just a simple workaround.
bodyEntryBlock = openNewBlock();
}
localsHandler.enterLoopBody(loopClosureInfo, sourceInformation);
node.body.accept(this);
// If there are no continues we could avoid the creation of the condition
// block. This could also lead to a block having multiple entries and exits.
HBasicBlock bodyExitBlock;
bool isAbortingBody = false;
if (current != null) {
bodyExitBlock = close(HGoto(_abstractValueDomain));
} else {
isAbortingBody = true;
bodyExitBlock = lastOpenedBlock;
}
SubExpression conditionExpression;
bool loopIsDegenerate = isAbortingBody && !hasContinues;
if (!loopIsDegenerate) {
HBasicBlock conditionBlock = addNewBlock();
List<LocalsHandler> continueHandlers = <LocalsHandler>[];
jumpHandler
.forEachContinue((HContinue instruction, LocalsHandler locals) {
instruction.block.addSuccessor(conditionBlock);
continueHandlers.add(locals);
});
if (!isAbortingBody) {
bodyExitBlock.addSuccessor(conditionBlock);
}
if (!continueHandlers.isEmpty) {
if (!isAbortingBody) continueHandlers.add(localsHandler);
localsHandler =
savedLocals.mergeMultiple(continueHandlers, conditionBlock);
SubGraph bodyGraph = SubGraph(bodyEntryBlock, bodyExitBlock);
List<LabelDefinition> labels = jumpHandler.labels;
HSubGraphBlockInformation bodyInfo =
HSubGraphBlockInformation(bodyGraph);
HLabeledBlockInformation info;
if (!labels.isEmpty) {
info = HLabeledBlockInformation(bodyInfo, labels, isContinue: true);
} else {
info = HLabeledBlockInformation.implicit(bodyInfo, target,
isContinue: true);
}
bodyEntryBlock.setBlockFlow(info, conditionBlock);
}
open(conditionBlock);
node.condition.accept(this);
assert(!isAborted());
HInstruction conditionInstruction = popBoolified();
HBasicBlock conditionEndBlock = close(HLoopBranch(_abstractValueDomain,
conditionInstruction, HLoopBranch.DO_WHILE_LOOP));
HBasicBlock avoidCriticalEdge = addNewBlock();
conditionEndBlock.addSuccessor(avoidCriticalEdge);
open(avoidCriticalEdge);
close(HGoto(_abstractValueDomain));
avoidCriticalEdge.addSuccessor(loopEntryBlock); // The back-edge.
conditionExpression = SubExpression(conditionBlock, conditionEndBlock);
// Avoid a critical edge from the condition to the loop-exit body.
HBasicBlock conditionExitBlock = addNewBlock();
open(conditionExitBlock);
close(HGoto(_abstractValueDomain));
conditionEndBlock.addSuccessor(conditionExitBlock);
_loopHandler.endLoop(
loopEntryBlock, conditionExitBlock, jumpHandler, localsHandler);
loopEntryBlock.postProcessLoopHeader();
SubGraph bodyGraph = SubGraph(loopEntryBlock, bodyExitBlock);
HLoopBlockInformation loopBlockInfo = HLoopBlockInformation(
HLoopBlockInformation.DO_WHILE_LOOP,
null,
wrapExpressionGraph(conditionExpression),
wrapStatementGraph(bodyGraph),
null,
loopEntryBlock.loopInformation.target,
loopEntryBlock.loopInformation.labels,
sourceInformation);
loopEntryBlock.setBlockFlow(loopBlockInfo, current);
loopInfo.loopBlockInformation = loopBlockInfo;
} else {
// Since the loop has no back edge, we remove the loop information on the
// header.
loopEntryBlock.loopInformation = null;
if (jumpHandler.hasAnyBreak()) {
// Null branchBlock because the body of the do-while loop always aborts,
// so we never get to the condition.
_loopHandler.endLoop(loopEntryBlock, null, jumpHandler, localsHandler);
// Since the body of the loop has a break, we attach a synthesized label
// to the body.
SubGraph bodyGraph = SubGraph(bodyEntryBlock, bodyExitBlock);
JumpTarget target = _localsMap.getJumpTargetForDo(node);
final label = target.addLabel('loop', isBreakTarget: true);
HLabeledBlockInformation info = HLabeledBlockInformation(
HSubGraphBlockInformation(bodyGraph), <LabelDefinition>[label]);
loopEntryBlock.setBlockFlow(info, current);
jumpHandler.forEachBreak((HBreak breakInstruction, _) {
HBasicBlock block = breakInstruction.block;
block.addAtExit(
HBreak.toLabel(_abstractValueDomain, label, sourceInformation));
block.remove(breakInstruction);
});
}
}
jumpHandler.close();
}
@override
void visitIfStatement(ir.IfStatement node) {
_handleIf(
visitCondition: () => node.condition.accept(this),
visitThen: () => node.then.accept(this),
visitElse: () => node.otherwise?.accept(this),
sourceInformation: _sourceInformationBuilder.buildIf(node));
}
void _handleIf(
{ir.Node node,
void visitCondition(),
void visitThen(),
void visitElse(),
SourceInformation sourceInformation}) {
SsaBranchBuilder branchBuilder = SsaBranchBuilder(this,
node == null ? null : _elementMap.getSpannable(targetElement, node));
branchBuilder.handleIf(visitCondition, visitThen, visitElse,
sourceInformation: sourceInformation);
}
@override
void visitAsExpression(ir.AsExpression node) {
// Recognize these special cases, where expression e has static type `T?`:
//
// e as T
// (e as dynamic) as T
//
// These patterns can only fail if `e` results in a `null` value. The
// second pattern occurs when `e as dynamic` is used get an implicit
// downcast in order to make use of the different policies for explicit and
// implicit downcasts.
//
// The pattern match is syntactic which ensures the type bindings are
// consistent, i.e. from the same instance of a type variable scope.
ir.Expression operand = _skipCastsToDynamic(node.operand);
operand.accept(this);
bool isNullRemovalPattern = false;
StaticType operandType = _getStaticType(operand);
DartType type = _elementMap.getDartType(node.type);
if (!node.isCovarianceCheck) {
if (_elementMap.types.isSubtype(operandType.type, type)) {
// Skip unneeded casts.
return;
}
if (_elementMap.types
.isSubtype(operandType.type, _elementMap.types.nullableType(type))) {
isNullRemovalPattern = true;
}
}
SourceInformation sourceInformation =
_sourceInformationBuilder.buildAs(node);
HInstruction expressionInstruction = pop();
if (node.type is ir.InvalidType) {
_generateTypeError('invalid type', sourceInformation);
return;
}
CheckPolicy policy;
if (node.isTypeError) {
policy = closedWorld.annotationsData
.getImplicitDowncastCheckPolicy(_currentFrame.member);
} else {
policy = closedWorld.annotationsData
.getExplicitCastCheckPolicy(_currentFrame.member);
}
if (!policy.isEmitted) {
stack.add(expressionInstruction);
return;
}
void generateCheck() {
HInstruction converted = _typeBuilder.buildAsCheck(
expressionInstruction, localsHandler.substInContext(type),
isTypeError: node.isTypeError, sourceInformation: sourceInformation);
if (converted != expressionInstruction) {
add(converted);
}
stack.add(converted);
}
if (isNullRemovalPattern) {
// Generate a conditional to test only `null` values:
//
// temp = e;
// temp == null ? temp as T : temp
SsaBranchBuilder(this).handleConditional(
() {
push(HIdentity(
expressionInstruction,
graph.addConstantNull(closedWorld),
_abstractValueDomain.boolType));
},
generateCheck,
() {
stack.add(expressionInstruction);
});
} else {
generateCheck();
}
}
static ir.Expression _skipCastsToDynamic(ir.Expression node) {
if (node is ir.AsExpression && node.type is ir.DynamicType) {
return _skipCastsToDynamic(node.operand);
}
return node;
}
@override
void visitNullCheck(ir.NullCheck node) {
node.operand.accept(this);
HInstruction expression = pop();
SourceInformation sourceInformation =
_sourceInformationBuilder.buildUnary(node);
push(HNullCheck(expression,
_abstractValueDomain.excludeNull(expression.instructionType))
..sourceInformation = sourceInformation);
}
void _generateError(FunctionEntity function, String message,
AbstractValue typeMask, SourceInformation sourceInformation) {
HInstruction errorMessage = graph.addConstantString(message, closedWorld);
_pushStaticInvocation(
function, [errorMessage], typeMask, const <DartType>[],
sourceInformation: sourceInformation);
}
void _generateTypeError(String message, SourceInformation sourceInformation) {
_generateError(
_commonElements.throwTypeError,
message,
_typeInferenceMap.getReturnTypeOf(_commonElements.throwTypeError),
sourceInformation);
}
void _generateUnsupportedError(
String message, SourceInformation sourceInformation) {
_generateError(
_commonElements.throwUnsupportedError,
message,
_typeInferenceMap
.getReturnTypeOf(_commonElements.throwUnsupportedError),
sourceInformation);
}
@override
void visitAssertStatement(ir.AssertStatement node) {
if (!options.enableUserAssertions) return;
var sourceInformation = _sourceInformationBuilder.buildAssert(node);
if (node.message == null) {
node.condition.accept(this);
_pushStaticInvocation(
_commonElements.assertHelper,
[pop()],
_typeInferenceMap.getReturnTypeOf(_commonElements.assertHelper),
const <DartType>[],
sourceInformation: sourceInformation);
pop();
return;
}
// if (assertTest(condition)) assertThrow(message);
void buildCondition() {
node.condition.accept(this);
_pushStaticInvocation(
_commonElements.assertTest,
[pop()],
_typeInferenceMap.getReturnTypeOf(_commonElements.assertTest),
const <DartType>[],
sourceInformation: sourceInformation);
}
void fail() {
node.message.accept(this);
_pushStaticInvocation(
_commonElements.assertThrow,
[pop()],
_typeInferenceMap.getReturnTypeOf(_commonElements.assertThrow),
const <DartType>[],
sourceInformation: sourceInformation);
pop();
}
_handleIf(visitCondition: buildCondition, visitThen: fail);
}
/// Creates a [JumpHandler] for a statement. The node must be a jump
/// target. If there are no breaks or continues targeting the statement,
/// a special "null handler" is returned.
///
/// [isLoopJump] is true when the jump handler is for a loop. This is used
/// to distinguish the synthesized loop created for a switch statement with
/// continue statements from simple switch statements.
JumpHandler createJumpHandler(ir.TreeNode node, JumpTarget target,
{bool isLoopJump = false}) {
if (target == null) {
// No breaks or continues to this node.
return NullJumpHandler(reporter);
}
if (isLoopJump && node is ir.SwitchStatement) {
return KernelSwitchCaseJumpHandler(this, target, node, _localsMap);
}
return JumpHandler(this, target);
}
@override
void visitBreakStatement(ir.BreakStatement node) {
assert(!isAborted());
_handleInTryStatement();
JumpTarget target = _localsMap.getJumpTargetForBreak(node);
assert(target != null);
JumpHandler handler = jumpTargets[target];
assert(handler != null);
SourceInformation sourceInformation =
_sourceInformationBuilder.buildGoto(node);
if (_localsMap.generateContinueForBreak(node)) {
if (handler.labels.isNotEmpty) {
handler.generateContinue(sourceInformation, handler.labels.first);
} else {
handler.generateContinue(sourceInformation);
}
} else {
if (handler.labels.isNotEmpty) {
handler.generateBreak(sourceInformation, handler.labels.first);
} else {
handler.generateBreak(sourceInformation);
}
}
}
@override
void visitLabeledStatement(ir.LabeledStatement node) {
ir.Statement body = node.body;
if (JumpVisitor.canBeBreakTarget(body)) {
// loops and switches handle breaks on their own
body.accept(this);
return;
}
JumpTarget jumpTarget = _localsMap.getJumpTargetForLabel(node);
if (jumpTarget == null) {
// The label is not needed.
body.accept(this);
return;
}
JumpHandler handler = createJumpHandler(node, jumpTarget);
LocalsHandler beforeLocals = LocalsHandler.from(localsHandler);
HBasicBlock newBlock = openNewBlock();
body.accept(this);
SubGraph bodyGraph = SubGraph(newBlock, lastOpenedBlock);
HBasicBlock joinBlock = graph.addNewBlock();
List<LocalsHandler> breakHandlers = [];
handler.forEachBreak((HBreak breakInstruction, LocalsHandler locals) {
breakInstruction.block.addSuccessor(joinBlock);
breakHandlers.add(locals);
});
if (!isAborted()) {
goto(current, joinBlock);
breakHandlers.add(localsHandler);
}
open(joinBlock);
localsHandler = beforeLocals.mergeMultiple(breakHandlers, joinBlock);
// There was at least one reachable break, so the label is needed.
newBlock.setBlockFlow(
HLabeledBlockInformation(
HSubGraphBlockInformation(bodyGraph), handler.labels),
joinBlock);
handler.close();
}
/// Loop through the cases in a switch and create a mapping of case
/// expressions to constants.
Map<ir.Expression, ConstantValue> _buildSwitchCaseConstants(
ir.SwitchStatement switchStatement) {
Map<ir.Expression, ConstantValue> constants = {};
for (ir.SwitchCase switchCase in switchStatement.cases) {
for (ir.Expression caseExpression in switchCase.expressions) {
ConstantValue constant =
_elementMap.getConstantValue(_memberContextNode, caseExpression);
constants[caseExpression] = constant;
}
}
return constants;
}
@override
void visitContinueSwitchStatement(ir.ContinueSwitchStatement node) {
_handleInTryStatement();
JumpTarget target = _localsMap.getJumpTargetForContinueSwitch(node);
assert(target != null);
JumpHandler handler = jumpTargets[target];
assert(handler != null);
assert(target.labels.isNotEmpty);
handler.generateContinue(
_sourceInformationBuilder.buildGoto(node), target.labels.first);
}
@override
void visitSwitchStatement(ir.SwitchStatement node) {
SourceInformation sourceInformation =
_sourceInformationBuilder.buildSwitch(node);
// The switch case indices must match those computed in
// [KernelSwitchCaseJumpHandler].
bool hasContinue = false;
Map<ir.SwitchCase, int> caseIndex = {};
int switchIndex = 1;
bool hasDefault = false;
for (ir.SwitchCase switchCase in node.cases) {
if (_isDefaultCase(switchCase)) {
hasDefault = true;
}
if (SwitchContinueAnalysis.containsContinue(switchCase.body)) {
hasContinue = true;
}
caseIndex[switchCase] = switchIndex;
switchIndex++;
}
JumpHandler jumpHandler =
createJumpHandler(node, _localsMap.getJumpTargetForSwitch(node));
if (!hasContinue) {
// If the switch statement has no switch cases targeted by continue
// statements we encode the switch statement directly.
_buildSimpleSwitchStatement(node, jumpHandler, sourceInformation);
} else {
_buildComplexSwitchStatement(
node, jumpHandler, caseIndex, hasDefault, sourceInformation);
}
}
/// Helper for building switch statements.
static bool _isDefaultCase(ir.SwitchCase switchCase) =>
switchCase == null || switchCase.isDefault;
/// Helper for building switch statements.
HInstruction _buildExpression(ir.SwitchStatement switchStatement) {
switchStatement.expression.accept(this);
return pop();
}
/// Helper method for creating the list of constants that make up the
/// switch case branches.
List<ConstantValue> _getSwitchConstants(
ir.SwitchStatement parentSwitch, ir.SwitchCase switchCase) {
Map<ir.Expression, ConstantValue> constantsLookup =
_buildSwitchCaseConstants(parentSwitch);
List<ConstantValue> constantList = [];
if (switchCase != null) {
for (var expression in switchCase.expressions) {
constantList.add(constantsLookup[expression]);
}
}
return constantList;
}
/// Builds a simple switch statement which does not handle uses of continue
/// statements to labeled switch cases.
void _buildSimpleSwitchStatement(ir.SwitchStatement switchStatement,
JumpHandler jumpHandler, SourceInformation sourceInformation) {
void buildSwitchCase(ir.SwitchCase switchCase) {
switchCase.body.accept(this);
}
_handleSwitch(
switchStatement,
jumpHandler,
_buildExpression,
switchStatement.cases,
_getSwitchConstants,
_isDefaultCase,
buildSwitchCase,
sourceInformation);
jumpHandler.close();
}
/// Builds a switch statement that can handle arbitrary uses of continue
/// statements to labeled switch cases.
void _buildComplexSwitchStatement(
ir.SwitchStatement switchStatement,
JumpHandler jumpHandler,
Map<ir.SwitchCase, int> caseIndex,
bool hasDefault,
SourceInformation sourceInformation) {
// If the switch statement has switch cases targeted by continue
// statements we create the following encoding:
//
// switch (e) {
// l_1: case e0: s_1; break;
// l_2: case e1: s_2; continue l_i;
// ...
// l_n: default: s_n; continue l_j;
// }
//
// is encoded as
//
// var target;
// switch (e) {
// case e1: target = 1; break;
// case e2: target = 2; break;
// ...
// default: target = n; break;
// }
// l: while (true) {
// switch (target) {
// case 1: s_1; break l;
// case 2: s_2; target = i; continue l;
// ...
// case n: s_n; target = j; continue l;
// }
// }
//
// This is because JS does not have this same "continue label" semantics so
// we encode it in the form of a state machine.
JumpTarget switchTarget =
_localsMap.getJumpTargetForSwitch(switchStatement);
localsHandler.updateLocal(switchTarget, graph.addConstantNull(closedWorld));
var switchCases = switchStatement.cases;
if (!hasDefault) {
// Use null as the marker for a synthetic default clause.
// The synthetic default is added because otherwise there would be no
// good place to give a default value to the local.
switchCases = List<ir.SwitchCase>.from(switchCases);
switchCases.add(null);
}
void buildSwitchCase(ir.SwitchCase switchCase) {
SourceInformation caseSourceInformation = sourceInformation;
if (switchCase != null) {
caseSourceInformation = _sourceInformationBuilder.buildGoto(switchCase);
// Generate 'target = i; break;' for switch case i.
int index = caseIndex[switchCase];
HInstruction value = graph.addConstantInt(index, closedWorld);
localsHandler.updateLocal(switchTarget, value,
sourceInformation: caseSourceInformation);
} else {
// Generate synthetic default case 'target = null; break;'.
HInstruction nullValue = graph.addConstantNull(closedWorld);
localsHandler.updateLocal(switchTarget, nullValue,
sourceInformation: caseSourceInformation);
}
jumpTargets[switchTarget].generateBreak(caseSourceInformation);
}
_handleSwitch(
switchStatement,
jumpHandler,
_buildExpression,
switchCases,
_getSwitchConstants,
_isDefaultCase,
buildSwitchCase,
sourceInformation);
jumpHandler.close();
HInstruction buildCondition() => graph.addConstantBool(true, closedWorld);
void buildSwitch() {
HInstruction buildExpression(ir.SwitchStatement notUsed) {
return localsHandler.readLocal(switchTarget);
}
List<ConstantValue> getConstants(
ir.SwitchStatement parentSwitch, ir.SwitchCase switchCase) {
return [constant_system.createIntFromInt(caseIndex[switchCase])];
}
void buildSwitchCase(ir.SwitchCase switchCase) {
switchCase.body.accept(this);
if (!isAborted()) {
// Ensure that we break the loop if the case falls through. (This
// is only possible for the last case.)
jumpTargets[switchTarget].generateBreak(sourceInformation);
}
}
// Pass a [NullJumpHandler] because the target for the contained break
// is not the generated switch statement but instead the loop generated
// in the call to [handleLoop] below.
_handleSwitch(
switchStatement, // nor is buildExpression.
NullJumpHandler(reporter),
buildExpression,
switchStatement.cases,
getConstants,
(_) => false, // No case is default.
buildSwitchCase,
sourceInformation);
}
void buildLoop() {
_loopHandler.handleLoop(
switchStatement,
_closureDataLookup.getCapturedLoopScope(switchStatement),
switchTarget,
() {},
buildCondition,
() {},
buildSwitch,
_sourceInformationBuilder.buildLoop(switchStatement));
}
if (hasDefault) {
buildLoop();
} else {
// If the switch statement has no default case, surround the loop with
// a test of the target. So:
// `if (target) while (true) ...` If there's no default case, target is
// null, so we don't drop into the while loop.
void buildCondition() {
js.Template code = js.js.parseForeignJS('#');
push(HForeignCode(code, _abstractValueDomain.boolType,
[localsHandler.readLocal(switchTarget)],
nativeBehavior: NativeBehavior.PURE));
}
_handleIf(
node: switchStatement,
visitCondition: buildCondition,
visitThen: buildLoop,
visitElse: () => {},
sourceInformation: sourceInformation);
}
}
/// Creates a switch statement.
///
/// [jumpHandler] is the [JumpHandler] for the created switch statement.
/// [buildSwitchCase] creates the statements for the switch case.
void _handleSwitch(
ir.SwitchStatement switchStatement,
JumpHandler jumpHandler,
HInstruction buildExpression(ir.SwitchStatement statement),
List<ir.SwitchCase> switchCases,
List<ConstantValue> getConstants(
ir.SwitchStatement parentSwitch, ir.SwitchCase switchCase),
bool isDefaultCase(ir.SwitchCase switchCase),
void buildSwitchCase(ir.SwitchCase switchCase),
SourceInformation sourceInformation) {
HBasicBlock expressionStart = openNewBlock();
HInstruction expression = buildExpression(switchStatement);
if (switchCases.isEmpty) {
return;
}
HSwitch switchInstruction = HSwitch(_abstractValueDomain, [expression]);
HBasicBlock expressionEnd = close(switchInstruction);
LocalsHandler savedLocals = localsHandler;
List<HStatementInformation> statements = [];
bool hasDefault = false;
for (ir.SwitchCase switchCase in switchCases) {
HBasicBlock block = graph.addNewBlock();
for (ConstantValue constant
in getConstants(switchStatement, switchCase)) {
HConstant hConstant = graph.addConstant(constant, closedWorld);
switchInstruction.inputs.add(hConstant);
hConstant.usedBy.add(switchInstruction);
expressionEnd.addSuccessor(block);
}
if (isDefaultCase(switchCase)) {
// An HSwitch has n inputs and n+1 successors, the last being the
// default case.
expressionEnd.addSuccessor(block);
hasDefault = true;
}
open(block);
localsHandler = LocalsHandler.from(savedLocals);
buildSwitchCase(switchCase);
if (!isAborted() &&
// TODO(johnniwinther): Reinsert this if `isReachable` is no longer
// set to `false` when `_tryInlineMethod` sees an always throwing
// method.
//switchCase == switchCases.last &&
!isDefaultCase(switchCase)) {
// If there is no default, we will add one later to avoid
// the critical edge. So we generate a break statement to make
// sure the last case does not fall through to the default case.
jumpHandler.generateBreak(sourceInformation);
}
statements
.add(HSubGraphBlockInformation(SubGraph(block, lastOpenedBlock)));
}
// Add a join-block if necessary.
// We create [joinBlock] early, and then go through the cases that might
// want to jump to it. In each case, if we add [joinBlock] as a successor
// of another block, we also add an element to [caseHandlers] that is used
// to create the phis in [joinBlock].
// If we never jump to the join block, [caseHandlers] will stay empty, and
// the join block is never added to the graph.
HBasicBlock joinBlock = HBasicBlock();
List<LocalsHandler> caseHandlers = [];
jumpHandler.forEachBreak((HBreak instruction, LocalsHandler locals) {
instruction.block.addSuccessor(joinBlock);
caseHandlers.add(locals);
});
jumpHandler.forEachContinue((HContinue instruction, LocalsHandler locals) {
assert(
false,
failedAt(_elementMap.getSpannable(targetElement, switchStatement),
'Continue cannot target a switch.'));
});
if (!isAborted()) {
current.close(HGoto(_abstractValueDomain));
lastOpenedBlock.addSuccessor(joinBlock);
caseHandlers.add(localsHandler);
}
if (!hasDefault) {
// Always create a default case, to avoid a critical edge in the
// graph.
HBasicBlock defaultCase = addNewBlock();
expressionEnd.addSuccessor(defaultCase);
open(defaultCase);
close(HGoto(_abstractValueDomain));
defaultCase.addSuccessor(joinBlock);
caseHandlers.add(savedLocals);
statements
.add(HSubGraphBlockInformation(SubGraph(defaultCase, defaultCase)));
}
assert(caseHandlers.length == joinBlock.predecessors.length);
if (caseHandlers.isNotEmpty) {
graph.addBlock(joinBlock);
open(joinBlock);
if (caseHandlers.length == 1) {
localsHandler = caseHandlers[0];
} else {
localsHandler = savedLocals.mergeMultiple(caseHandlers, joinBlock);
}
} else {
// The joinBlock is not used.
joinBlock = null;
}
HSubExpressionBlockInformation expressionInfo =
HSubExpressionBlockInformation(
SubExpression(expressionStart, expressionEnd));
expressionStart.setBlockFlow(
HSwitchBlockInformation(expressionInfo, statements, jumpHandler.target,
jumpHandler.labels, sourceInformation),
joinBlock);
jumpHandler.close();
}
@override