blob: af985cdc512b300fd4053d9099a4c7b1c355654e [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.
import 'package:js_runtime/shared/embedded_names.dart';
import 'package:kernel/ast.dart' as ir;
import '../closure.dart';
import '../common.dart';
import '../common/codegen.dart' show CodegenRegistry;
import '../common/names.dart';
import '../common_elements.dart';
import '../constants/constant_system.dart' as constant_system;
import '../constants/values.dart';
import '../deferred_load.dart';
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/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.dart';
import '../js_backend/runtime_types_resolution.dart';
import '../js_emitter/code_emitter_task.dart' show ModularEmitter;
import '../js_model/locals.dart' show JumpVisitor;
import '../js_model/elements.dart' show JGeneratorBody;
import '../js_model/element_map.dart';
import '../js_model/js_strategy.dart';
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/side_effects.dart' show SideEffects;
import '../universe/target_checks.dart' show TargetChecks;
import '../universe/use.dart' show ConstantUse, StaticUse;
import '../world.dart';
import 'jump_handler.dart';
import 'kernel_string_builder.dart';
import 'locals_handler.dart';
import 'loop_handler.dart';
import 'nodes.dart';
import 'ssa_branch_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 {
/// Holds the resulting SSA graph.
final HGraph graph = new 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 = <HInstruction>[];
/// 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 = <JumpTarget, JumpHandler>{};
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;
final RuntimeTypesEncoder _rtiEncoder;
/// 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 = <InterfaceType>[];
/// Used to report information about inlining (which occurs while building the
/// SSA graph), when dump-info is enabled.
final InfoReporter _infoReporter;
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 = <KernelInliningState>[];
Local _returnLocal;
DartType _returnType;
bool _inLazyInitializerExpression = false;
StackFrame _currentFrame;
final FunctionInlineCache _inlineCache;
final InlineDataCache _inlineDataCache;
KernelSsaGraphBuilder(
this.options,
this.reporter,
this._initialTargetElement,
InterfaceType instanceType,
this._infoReporter,
this._elementMap,
this.globalInferenceResults,
this.closedWorld,
this.registry,
this._namer,
this._emitter,
this._tracer,
this._rtiEncoder,
this._sourceInformationStrategy,
this._inlineCache,
this._inlineDataCache)
: this.targetElement = _effectiveTargetElementFor(_initialTargetElement),
this._closureDataLookup = closedWorld.closureDataLookup {
_enterFrame(targetElement, null);
this._loopHandler = new KernelLoopHandler(this);
_typeBuilder = new KernelTypeBuilder(this, _elementMap);
graph.element = targetElement;
graph.sourceInformation =
_sourceInformationBuilder.buildVariableDeclaration();
this.localsHandler = new 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;
InferredData get _inferredData => globalInferenceResults.inferredData;
DartTypes get types => 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(new HIdentity(expression, graph.addConstantNull(closedWorld), null,
_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 = <Local, HInstruction>{};
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(new 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
? new HLocalValue(parameter, type)
: new 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 new HSubGraphBlockInformation(statements);
}
HSubExpressionBlockInformation wrapExpressionGraph(SubExpression expression) {
if (expression == null) return null;
return new HSubExpressionBlockInformation(expression);
}
HLiteralList _buildLiteralList(List<HInstruction> inputs) {
return new HLiteralList(inputs, _abstractValueDomain.growableListType);
}
HInstruction _callSetRuntimeTypeInfoWithTypeArguments(
InterfaceType type,
List<HInstruction> rtiInputs,
HInstruction newObject,
SourceInformation sourceInformation) {
if (!_rtiNeed.classNeedsTypeArguments(type.element)) {
return newObject;
}
HInstruction typeInfo = new HTypeInfoExpression(
TypeInfoExpressionKind.INSTANCE,
closedWorld.elementEnvironment.getThisType(type.element),
rtiInputs,
_abstractValueDomain.dynamicType);
add(typeInfo);
return _callSetRuntimeTypeInfo(typeInfo, newObject, sourceInformation);
}
/// 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(new 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.useContentSecurityPolicy;
case 'USE_NEW_RTI':
return options.experimentNewRti;
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 new 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 new 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 = new StackFrame(
_currentFrame,
member,
asyncMarker,
closedWorld.globalLocalsMap.getLocalsMap(member),
{},
new 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.function));
} else {
_buildFunctionNode(
targetElement, _ensureDefaultArgumentValues(target.function));
}
} else if (target is ir.Field) {
FieldAnalysisData fieldData =
closedWorld.fieldAnalysis.getFieldData(targetElement);
if (fieldData.initialValue != null) {
registry.registerConstantUse(
new 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(new StaticUse.staticInvoke(
closedWorld.commonElements.cyclicThrowHelper,
CallStructure.ONE_ARG));
}
if (targetElement.isInstanceMember) {
if (fieldData.isEffectivelyFinal ||
!closedWorld.annotationsData
.getParameterCheckPolicy(targetElement)
.isEmitted) {
// No need for a checked setter.
return null;
}
}
_buildField(target);
} else if (target is ir.LocalFunction) {
_buildFunctionNode(
targetElement, _ensureDefaultArgumentValues(target.function));
} else {
throw 'No case implemented to handle target: '
'$target for $targetElement';
}
break;
case MemberKind.constructor:
ir.Constructor constructor = definition.node;
_ensureDefaultArgumentValues(constructor.function);
_buildConstructor(targetElement, constructor);
break;
case MemberKind.constructorBody:
ir.Constructor constructor = definition.node;
_ensureDefaultArgumentValues(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");
}
if (options.experimentNewRti) {
_buildMethodSignatureNewRti(originalClosureNode);
} else {
_buildMethodSignature(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.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(node.initializer, implicitNull: true);
assert(
constantValue != null,
failedAt(_elementMap.getMethod(function.parent),
'No constant computed for $node'));
registry?.registerConstantUse(new ConstantUse.init(constantValue));
}
function.positionalParameters
.skip(function.requiredParameterCount)
.forEach(_registerDefaultValue);
function.namedParameters.forEach(_registerDefaultValue);
return function;
}
void _buildField(ir.Field node) {
_inLazyInitializerExpression = node.isStatic;
FieldEntity field = _elementMap.getMember(node);
_openFunction(field, checks: TargetChecks.none);
if (node.isInstanceMember &&
closedWorld.annotationsData.getParameterCheckPolicy(field).isEmitted) {
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 =
new 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);
HInstruction value = _typeBuilder.potentiallyCheckOrTrustTypeOfParameter(
field, parameter, _getDartTypeIfValid(node.type));
if (!_fieldAnalysis.getFieldData(field).isElided) {
add(new HFieldSet(_abstractValueDomain, field, thisInstruction, value));
}
} else {
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(new HReturn(_abstractValueDomain, value,
_sourceInformationBuilder.buildReturn(node)));
}
_closeFunction();
}
DartType _getDartTypeIfValid(ir.DartType type) {
if (type is ir.InvalidType) return null;
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.
///
/// TODO(johnniwinther): Do we need this?
/// If the method has type variables but does not need them, bind to `dynamic`
/// (represented as `null`).
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 _elementEnvironment.getFunctionTypeVariables(function)) {
HInstruction param;
if (elideTypeParameters) {
// Add elided type parameters.
param = _computeTypeArgumentDefaultValue(function, typeVariable);
} else if (needsTypeArguments) {
param = addParameter(
typeVariable.element, _abstractValueDomain.nonNullType);
} else {
// Unused, so bind to `dynamic`.
param = graph.addConstantNull(closedWorld);
}
Local local = localsHandler.getTypeVariableAsLocal(typeVariable);
localsHandler.directLocals[local] = param;
_functionTypeParameterLocals.add(local);
}
}
List<Local> _functionTypeParameterLocals = <Local>[];
/// 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 = <HInstruction>[];
// 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 = <FieldEntity>[];
_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 =
new 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.
add(new HFieldGet(
null, newObject, _abstractValueDomain.dynamicType, sourceInformation,
isAssignable: false));
for (int i = 0; i < fields.length; i++) {
add(new HFieldSet(_abstractValueDomain, fields[i], newObject,
constructorArguments[i]));
}
} else {
// Create the runtime type information, if needed.
bool needsTypeArguments =
closedWorld.rtiNeed.classNeedsTypeArguments(cls);
if (needsTypeArguments) {
if (options.experimentNewRti) {
InterfaceType thisType = _elementEnvironment.getThisType(cls);
HInstruction typeArgument =
_typeBuilder.analyzeTypeArgumentNewRti(thisType, sourceElement);
constructorArguments.add(typeArgument);
} else {
// Read the values of the type arguments and create a
// HTypeInfoExpression to set on the newly created object.
List<HInstruction> typeArguments = <HInstruction>[];
InterfaceType thisType = _elementEnvironment.getThisType(cls);
for (DartType typeVariable in thisType.typeArguments) {
HInstruction argument = localsHandler
.readLocal(localsHandler.getTypeVariableAsLocal(typeVariable));
typeArguments.add(argument);
}
HInstruction typeInfo = new HTypeInfoExpression(
TypeInfoExpressionKind.INSTANCE,
thisType,
typeArguments,
_abstractValueDomain.dynamicType);
add(typeInfo);
constructorArguments.add(typeInfo);
}
}
newObject = new 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 = <HInstruction>[];
if (isCustomElement) {
if (interceptor == null) {
ConstantValue constant = new 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, {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.context));
}
// 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(
new 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 = new 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) {
if (node.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(node.initializer), () {
node.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) {
// Assert in initializer is currently not supported in dart2js.
// TODO(johnniwinther): Support assert in initializer.
} 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.FunctionNode function, ir.Arguments arguments) {
var builtArguments = <HInstruction>[];
var positionalIndex = 0;
function.positionalParameters.forEach((ir.VariableDeclaration node) {
if (positionalIndex < arguments.positional.length) {
arguments.positional[positionalIndex++].accept(this);
builtArguments.add(pop());
} else {
ConstantValue constantValue =
_elementMap.getConstantValue(node.initializer, implicitNull: true);
assert(
constantValue != null,
failedAt(_elementMap.getMethod(function.parent),
'No constant computed for $node'));
builtArguments.add(graph.addConstant(constantValue, closedWorld));
}
});
function.namedParameters.toList()
..sort(namedOrdering)
..forEach((ir.VariableDeclaration node) {
var correspondingNamed = arguments.named
.firstWhere((named) => named.name == node.name, orElse: () => null);
if (correspondingNamed != null) {
correspondingNamed.value.accept(this);
builtArguments.add(pop());
} else {
ConstantValue constantValue = _elementMap
.getConstantValue(node.initializer, implicitNull: true);
assert(
constantValue != null,
failedAt(_elementMap.getMethod(function.parent),
'No constant computed for $node'));
builtArguments.add(graph.addConstant(constantValue, closedWorld));
}
});
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) {
var superOrRedirectConstructor = initializer.target;
var arguments = _normalizeAndBuildArguments(
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) {
var target = initializer.target;
var arguments =
_normalizeAndBuildArguments(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);
ScopeInfo oldScopeInfo = localsHandler.scopeInfo;
_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.
ScopeInfo newScopeInfo = _closureDataLookup.getScopeInfo(element);
localsHandler.scopeInfo = newScopeInfo;
localsHandler.enterScope(_closureDataLookup.getCapturedScope(element),
_sourceInformationBuilder.buildDeclaration(element));
_buildInitializers(constructor, constructorData);
});
localsHandler.scopeInfo = oldScopeInfo;
}
/// Constructs a special signature function for a closure. It is unique in
/// that no corresponding ir.Node actually exists for it. We just use the
/// targetElement.
void _buildMethodSignature(ir.FunctionNode originalClosureNode) {
_openFunction(targetElement, checks: TargetChecks.none);
List<HInstruction> typeArguments = <HInstruction>[];
// Add function type variables.
FunctionType functionType =
_elementMap.getFunctionType(originalClosureNode);
functionType.forEachTypeVariable((TypeVariableType typeVariableType) {
DartType result = localsHandler.substInContext(typeVariableType);
HInstruction argument =
_typeBuilder.analyzeTypeArgument(result, sourceElement);
typeArguments.add(argument);
});
push(new HTypeInfoExpression(
TypeInfoExpressionKind.COMPLETE,
_elementMap.getFunctionType(originalClosureNode),
typeArguments,
_abstractValueDomain.functionType));
HInstruction value = pop();
close(new HReturn(_abstractValueDomain, value,
_sourceInformationBuilder.buildReturn(originalClosureNode)))
.addSuccessor(graph.exit);
_closeFunction();
}
/// 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(new 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 a 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 [functionNode] 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.
if (function.name == '==') {
if (!_commonElements.operatorEqHandlesNullArgument(function)) {
_handleIf(
visitCondition: () {
HParameterValue parameter = parameters.values.first;
push(new HIdentity(parameter, graph.addConstantNull(closedWorld),
null, _abstractValueDomain.boolType));
},
visitThen: () {
_closeAndGotoExit(new HReturn(
_abstractValueDomain,
graph.addConstantBool(false, closedWorld),
_sourceInformationBuilder.buildReturn(functionNode)));
},
visitElse: null,
sourceInformation: _sourceInformationBuilder.buildIf(functionNode));
}
}
if (const bool.fromEnvironment('unreachable-throw')) {
var emptyParameters = parameters.values.where((p) =>
_abstractValueDomain.isEmpty(p.instructionType).isDefinitelyTrue);
if (emptyParameters.length > 0) {
_addComment('${emptyParameters} inferred as [empty]');
add(new HInvokeStatic(
_commonElements.assertUnreachableMethod,
<HInstruction>[],
_abstractValueDomain.dynamicType,
const <DartType>[]));
_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(new HForeignCode(js.js.statementTemplateYielding(new js.Comment(text)),
_abstractValueDomain.dynamicType, <HInstruction>[],
isStatement: true));
}
/// Builds a 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 = <HInstruction>[];
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);
if (elementType.containsFreeTypeVariables) {
// Type must be computed in the entry function, where the type variables
// are in scope, and passed to the body function.
if (options.experimentNewRti) {
inputs
.add(_typeBuilder.analyzeTypeArgumentNewRti(elementType, function));
} else {
inputs.add(_typeBuilder.analyzeTypeArgument(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(new HInvokeGeneratorBody(
body,
inputs,
_abstractValueDomain.dynamicType, // TODO: better type.
sourceInformation));
_closeAndGotoExit(
new HReturn(_abstractValueDomain, pop(), sourceInformation));
_closeFunction();
}
/// Builds a 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(
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(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;
}
HInstruction newParameter = localsHandler.directLocals[local];
assert(newParameter != null, "No initial instruction for ${local}.");
DartType type = _getDartTypeIfValid(variable.type);
if (targetChecks.checkAllParameters ||
(targetChecks.checkCovariantParameters &&
(variable.isGenericCovariantImpl || variable.isCovariant))) {
newParameter = _typeBuilder.potentiallyCheckOrTrustTypeOfParameter(
targetElement, newParameter, type);
} else {
newParameter = _typeBuilder.trustTypeOfParameter(newParameter, type);
}
localsHandler.directLocals[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.getLocalTypeVariable(
new ir.TypeParameterType(typeParameter), _elementMap);
HInstruction newParameter = localsHandler.directLocals[local];
DartType bound = _getDartTypeIfValid(typeParameter.bound);
if (!bound.isDynamic &&
!bound.isVoid &&
bound != _commonElements.objectType) {
if (options.experimentNewRti) {
_checkTypeBound(newParameter, bound, local.name);
} else {
_assertIsType(
newParameter,
bound,
"The type argument '",
"' is not a subtype of the type variable bound '",
"' of type variable '${local.name}' in '${method.name}'.");
}
}
}
}
}
/// Builds a SSA graph for FunctionNodes of external methods.
void _buildExternalFunctionNode(
FunctionEntity function, ir.FunctionNode functionNode) {
// TODO(johnniwinther): Non-js-interop external functions should
// throw a runtime error.
assert(functionNode.body == null);
_openFunction(function,
functionNode: functionNode,
parameterStructure: function.parameterStructure,
checks: _checksForFunction(function));
if (closedWorld.nativeData.isNativeMember(targetElement)) {
registry.registerNativeMethod(targetElement);
String nativeName;
if (closedWorld.nativeData.hasFixedBackendName(targetElement)) {
nativeName = closedWorld.nativeData.getFixedBackendName(targetElement);
} else {
nativeName = targetElement.name;
}
String templateReceiver = '';
List<String> templateArguments = <String>[];
List<HInstruction> inputs = <HInstruction>[];
if (targetElement.isInstanceMember) {
templateReceiver = '#.';
inputs.add(localsHandler.readThis(
sourceInformation:
_sourceInformationBuilder.buildGet(functionNode)));
}
void handleParameter(ir.VariableDeclaration param) {
templateArguments.add('#');
Local local = _localsMap.getLocalVariable(param);
// Convert Dart function to JavaScript function.
HInstruction argument = localsHandler.readLocal(local);
ir.DartType type = param.type;
if (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);
}
String arguments = templateArguments.join(',');
// TODO(sra): Use declared type or NativeBehavior type.
AbstractValue typeMask = _abstractValueDomain.dynamicType;
String template;
if (targetElement.isGetter) {
template = '${templateReceiver}$nativeName';
} else if (targetElement.isSetter) {
template = '${templateReceiver}$nativeName = ${arguments}';
} else {
template = '${templateReceiver}$nativeName(${arguments})';
}
push(new HForeignCode(
js.js.uncachedExpressionTemplate(template), typeMask, inputs,
effects: new SideEffects()));
// TODO(johnniwinther): Provide source information.
HInstruction value = pop();
if (targetElement.isSetter) {
value = graph.addConstantNull(closedWorld);
}
close(new HReturn(_abstractValueDomain, value,
_sourceInformationBuilder.buildReturn(functionNode)))
.addSuccessor(graph.exit);
}
// TODO(sra): Handle JS-interop methods.
_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,
TargetChecks checks}) {
assert(checks != null);
Map<Local, AbstractValue> parameterMap = {};
List<ir.VariableDeclaration> elidedParameters = [];
Set<Local> elidedParameterSet = new 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(new HGoto(_abstractValueDomain)).addSuccessor(block);
open(block);
localsHandler.startFunction(
targetElement,
_closureDataLookup.getScopeInfo(targetElement),
_closureDataLookup.getCapturedScope(targetElement),
parameterMap,
elidedParameterSet,
_sourceInformationBuilder.buildDeclaration(targetElement),
isGenerativeConstructorBody: targetElement is ConstructorBodyEntity);
for (ir.VariableDeclaration node in elidedParameters) {
Local local = _localsMap.getLocalVariable(node);
localsHandler.updateLocal(local, _defaultValueForParameter(node));
}
_addClassTypeVariablesIfNeeded(member);
_addFunctionTypeVariablesIfNeeded(member);
if (functionNode != null) {
_potentiallyAddFunctionParameterTypeChecks(functionNode, checks);
}
_insertCoverageCall(member);
}
void _closeFunction() {
if (!isAborted()) _closeAndGotoExit(new HGoto(_abstractValueDomain));
graph.finalize(_abstractValueDomain);
}
@override
void defaultNode(ir.Node node) {
throw new 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);
HInstruction uriConstant =
graph.addConstantString('${import.uri}', closedWorld);
_pushStaticInvocation(
_commonElements.checkDeferredIsLoaded,
[prefixConstant, uriConstant],
_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(new HInvokeStatic(
_commonElements.loadDeferredLibrary,
<HInstruction>[graph.addConstantString(loadId, closedWorld)],
_abstractValueDomain.nonNullType,
const <DartType>[],
targetCanThrow: false));
}
@override
void visitBlock(ir.Block block) {
assert(!isAborted());
if (!_isReachable) return; // This can only happen when inlining.
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(
new HThrow(_abstractValueDomain, pop(), sourceInformation));
} else {
expression.accept(this);
pop();
}
}
@override
void visitConstantExpression(ir.ConstantExpression node) {
ConstantValue value = _elementMap.getConstantValue(node);
SourceInformation sourceInformation =
_sourceInformationBuilder.buildGet(node);
if (!closedWorld.outputUnitData
.hasOnlyNonDeferredImportPathsToConstant(targetElement, value)) {
OutputUnit outputUnit =
closedWorld.outputUnitData.outputUnitForConstant(value);
ConstantValue deferredConstant =
new DeferredGlobalConstantValue(value, outputUnit);
registry.registerConstantUse(new 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;
if (node.expression == null) {
value = graph.addConstantNull(closedWorld);
} else {
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 ReturnStatemnt 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 = new 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(new HIdentity(
length, originalLength, null, _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 =
new HLess(index, length, null, _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);
HInstruction value = new HIndex(array, index, null, 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 =
new HAdd(index, one, null, _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,
<HInstruction>[receiver],
const <DartType>[],
_sourceInformationBuilder.buildForInIterator(node));
iterator = pop();
}
HInstruction buildCondition() {
AbstractValue receiverType =
_typeInferenceMap.typeOfIteratorMoveNext(node);
_pushDynamicInvocation(
node,
iteratorType,
receiverType,
Selectors.moveNext,
<HInstruction>[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(new InterfaceType(cls, [typeArg]));
// TODO(johnniwinther): This should be the exact type.
StaticType staticInstanceType =
new 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(new 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 = new 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(new 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.setRuntimeTypeInfo;
// TODO(efortuna): Insert source information in this static invocation.
_pushStaticInvocation(typeInfoSetterFn, <HInstruction>[newObject, typeInfo],
_abstractValueDomain.dynamicType, const <DartType>[],
sourceInformation: sourceInformation);
// The new object will now be referenced through the
// `setRuntimeTypeInfo` 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 = new 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(new 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 = new SubGraph(bodyEntryBlock, bodyExitBlock);
List<LabelDefinition> labels = jumpHandler.labels;
HSubGraphBlockInformation bodyInfo =
new HSubGraphBlockInformation(bodyGraph);
HLabeledBlockInformation info;
if (!labels.isEmpty) {
info =
new HLabeledBlockInformation(bodyInfo, labels, isContinue: true);
} else {
info = new HLabeledBlockInformation.implicit(bodyInfo, target,
isContinue: true);
}
bodyEntryBlock.setBlockFlow(info, conditionBlock);
}
open(conditionBlock);
node.condition.accept(this);
assert(!isAborted());
HInstruction conditionInstruction = popBoolified();
HBasicBlock conditionEndBlock = close(new HLoopBranch(
_abstractValueDomain,
conditionInstruction,
HLoopBranch.DO_WHILE_LOOP));
HBasicBlock avoidCriticalEdge = addNewBlock();
conditionEndBlock.addSuccessor(avoidCriticalEdge);
open(avoidCriticalEdge);
close(new HGoto(_abstractValueDomain));
avoidCriticalEdge.addSuccessor(loopEntryBlock); // The back-edge.
conditionExpression =
new SubExpression(conditionBlock, conditionEndBlock);
// Avoid a critical edge from the condition to the loop-exit body.
HBasicBlock conditionExitBlock = addNewBlock();
open(conditionExitBlock);
close(new HGoto(_abstractValueDomain));
conditionEndBlock.addSuccessor(conditionExitBlock);
_loopHandler.endLoop(
loopEntryBlock, conditionExitBlock, jumpHandler, localsHandler);
loopEntryBlock.postProcessLoopHeader();
SubGraph bodyGraph = new SubGraph(loopEntryBlock, bodyExitBlock);
HLoopBlockInformation loopBlockInfo = new 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 = new SubGraph(bodyEntryBlock, bodyExitBlock);
JumpTarget target = _localsMap.getJumpTargetForDo(node);
LabelDefinition label = target.addLabel('loop', isBreakTarget: true);
HLabeledBlockInformation info = new HLabeledBlockInformation(
new HSubGraphBlockInformation(bodyGraph), <LabelDefinition>[label]);
loopEntryBlock.setBlockFlow(info, current);
jumpHandler.forEachBreak((HBreak breakInstruction, _) {
HBasicBlock block = breakInstruction.block;
block.addAtExit(new 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 = new SsaBranchBuilder(this,
node == null ? null : _elementMap.getSpannable(targetElement, node));
branchBuilder.handleIf(visitCondition, visitThen, visitElse,
sourceInformation: sourceInformation);
}
@override
void visitAsExpression(ir.AsExpression node) {
ir.Expression operand = node.operand;
operand.accept(this);
StaticType operandType = _getStaticType(operand);
DartType type = _elementMap.getDartType(node.type);
if (_elementMap.types.isSubtype(operandType.type, type)) {
// Skip unneeded casts.
if (operand is! ir.PropertyGet) {
// TODO(johnniwinther): Support property get. Currently CFE inserts
// a seemingly unnecessary cast on tearoffs that contain type variables
// in contravariant positions. Since these casts are not marked we
// cannot easily detect when we actually need the cast. See test
// `language_2/instantiate_tearoff_after_contravariance_check_test`.
return;
}
}
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) {
HInstruction converted = _typeBuilder.buildTypeConversion(
expressionInstruction,
localsHandler.substInContext(type),
node.isTypeError
? HTypeConversion.TYPE_CHECK
: HTypeConversion.CAST_CHECK,
sourceInformation: sourceInformation);
if (converted != expressionInstruction) {
add(converted);
}
stack.add(converted);
} else {
stack.add(expressionInstruction);
}
}
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,
<HInstruction>[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,
<HInstruction>[pop()],
_typeInferenceMap.getReturnTypeOf(_commonElements.assertTest),
const <DartType>[],
sourceInformation: sourceInformation);
}
void fail() {
node.message.accept(this);
_pushStaticInvocation(
_commonElements.assertThrow,
<HInstruction>[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 new NullJumpHandler(reporter);
}
if (isLoopJump && node is ir.SwitchStatement) {
return new KernelSwitchCaseJumpHandler(this, target, node, _localsMap);
}
return new 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 = new LocalsHandler.from(localsHandler);
HBasicBlock newBlock = openNewBlock();
body.accept(this);
SubGraph bodyGraph = new SubGraph(newBlock, lastOpenedBlock);
HBasicBlock joinBlock = graph.addNewBlock();
List<LocalsHandler> breakHandlers = <LocalsHandler>[];
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(
new HLabeledBlockInformation(
new 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 =
new Map<ir.Expression, ConstantValue>();
for (ir.SwitchCase switchCase in switchStatement.cases) {
for (ir.Expression caseExpression in switchCase.expressions) {
ConstantValue constant = _elementMap.getConstantValue(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 = new Map<ir.SwitchCase, int>();
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 = <ConstantValue>[];
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 = new 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 <ConstantValue>[
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.
new 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(new 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 =
new HSwitch(_abstractValueDomain, <HInstruction>[expression]);
HBasicBlock expressionEnd = close(switchInstruction);
LocalsHandler savedLocals = localsHandler;
List<HStatementInformation> statements = <HStatementInformation>[];
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 = new 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(
new HSubGraphBlockInformation(new 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 = new HBasicBlock();
List<LocalsHandler> caseHandlers = <LocalsHandler>[];
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(new 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(new HGoto(_abstractValueDomain));
defaultCase.addSuccessor(joinBlock);
caseHandlers.add(savedLocals);
statements.add(new HSubGraphBlockInformation(
new 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 =
new HSubExpressionBlockInformation(
new SubExpression(expressionStart, expressionEnd));
expressionStart.setBlockFlow(
new HSwitchBlockInformation(expressionInfo, statements,
jumpHandler.target, jumpHandler.labels, sourceInformation),
joinBlock);
jumpHandler.close();
}
@override
void visitConditionalExpression(ir.ConditionalExpression node) {
SsaBranchBuilder brancher = new SsaBranchBuilder(this);
brancher.handleConditional(() => node.condition.accept(this),
() => node.then.accept(this), () => node.otherwise.accept(this));
}
@override
void visitLogicalExpression(ir.LogicalExpression node) {
SsaBranchBuilder brancher = new SsaBranchBuilder(this);
String operator = node.operator;
// ir.LogicalExpression claims to allow '??' as an operator but currently
// that is expanded into a let-tree.
assert(operator == '&&' || operator == '||');
_handleLogicalExpression(node.left, () => node.right.accept(this), brancher,
operator, _sourceInformationBuilder.buildBinary(node));
}
/// Optimizes logical binary expression where the left has the same logical
/// binary operator.
///
/// This method transforms the operator by optimizing the case where [left] is
/// a logical "and" or logical "or". Then it uses [branchBuilder] to build the
/// graph for the optimized expression.
///
/// For example, `(x && y) && z` is transformed into `x && (y && z)`:
///
void _handleLogicalExpression(
ir.Expression left,
void visitRight(),
SsaBranchBuilder brancher,
String operator,
SourceInformation sourceInformation) {
if (left is ir.LogicalExpression && left.operator == operator) {
ir.Expression innerLeft = left.left;
ir.Expression middle = left.right;
_handleLogicalExpression(
innerLeft,
() => _handleLogicalExpression(middle, visitRight, brancher, operator,
_sourceInformationBuilder.buildBinary(middle)),
brancher,
operator,
sourceInformation);
} else {
brancher.handleLogicalBinary(
() => left.accept(this), visitRight, sourceInformation,
isAnd: operator == '&&');
}
}
@override
void visitIntLiteral(ir.IntLiteral node) {
stack.add(graph.addConstantIntAsUnsigned(node.value, closedWorld));
}
@override
void visitDoubleLiteral(ir.DoubleLiteral node) {
stack.add(graph.addConstantDouble(node.value, closedWorld));
}
@override
void visitBoolLiteral(ir.BoolLiteral node) {
stack.add(graph.addConstantBool(node.value, closedWorld));
}
@override
void visitStringLiteral(ir.StringLiteral node) {
stack.add(graph.addConstantString(node.value, closedWorld));
}
@override
void visitSymbolLiteral(ir.SymbolLiteral node) {
stack.add(
graph.addConstant(_elementMap.getConstantValue(node), closedWorld));
registry?.registerConstSymbol(node.value);
}
@override
void visitNullLiteral(ir.NullLiteral node) {
stack.add(graph.addConstantNull(closedWorld));
}
/// Set the runtime type information if necessary.
HInstruction _setListRuntimeTypeInfoIfNeeded(HInstruction object,
InterfaceType type, SourceInformation sourceInformation) {
if (!_rtiNeed.classNeedsTypeArguments(type.element) || type.treatAsRaw) {
return object;
}
if (options.experimentNewRti) {
// [type] could be `List<T>`, so ensure it is `JSArray<T>`.
InterfaceType arrayType =
InterfaceType(_commonElements.jsArrayClass, type.typeArguments);
HInstruction rti =
_typeBuilder.analyzeTypeArgumentNewRti(arrayType, sourceElement);
// TODO(15489): Register at codegen.
registry?.registerInstantiation(type);
return _callSetRuntimeTypeInfo(rti, object, sourceInformation);
}
List<HInstruction> arguments = <HInstruction>[];
for (DartType argument in type.typeArguments) {
arguments.add(_typeBuilder.analyzeTypeArgument(argument, sourceElement));
}
// TODO(15489): Register at codegen.
registry?.registerInstantiation(type);
return _callSetRuntimeTypeInfoWithTypeArguments(
type, arguments, object, sourceInformation);
}
@override
void visitListLiteral(ir.ListLiteral node) {
HInstruction listInstruction;
if (node.isConst) {
listInstruction =
graph.addConstant(_elementMap.getConstantValue(node), closedWorld);
} else {
List<HInstruction> elements = <HInstruction>[];
for (ir.Expression element in node.expressions) {
element.accept(this);
elements.add(pop());
}
listInstruction = _buildLiteralList(elements);
add(listInstruction);
SourceInformation sourceInformation =
_sourceInformationBuilder.buildListLiteral(node);
InterfaceType type = localsHandler.substInContext(
_commonElements.listType(_elementMap.getDartType(node.typeArgument)));
listInstruction = _setListRuntimeTypeInfoIfNeeded(
listInstruction, type, sourceInformation);
}
AbstractValue type =
_typeInferenceMap.typeOfListLiteral(node, _abstractValueDomain);
if (_abstractValueDomain.containsAll(type).isDefinitelyFalse) {
listInstruction.instructionType = type;
}
stack.add(listInstruction);
}
@override
void visitSetLiteral(ir.SetLiteral node) {
if (node.isConst) {
stack.add(
graph.addConstant(_elementMap.getConstantValue(node), closedWorld));
return;
}
// The set literal constructors take the elements as a List.
List<HInstruction> elements = <HInstruction>[];
for (ir.Expression element in node.expressions) {
element.accept(this);
elements.add(pop());
}
// The constructor is a procedure because it's a factory.
FunctionEntity constructor;
List<HInstruction> inputs = <HInstruction>[];
if (elements.isEmpty) {
constructor = _commonElements.setLiteralConstructorEmpty;
} else {
constructor = _commonElements.setLiteralConstructor;
HLiteralList argList = _buildLiteralList(elements);
add(argList);
inputs.add(argList);
}
assert(
constructor is ConstructorEntity && constructor.isFactoryConstructor);
InterfaceType type = localsHandler.substInContext(
_commonElements.setType(_elementMap.getDartType(node.typeArgument)));
ClassEntity cls = constructor.enclosingClass;
if (_rtiNeed.classNeedsTypeArguments(cls)) {
List<HInstruction> typeInputs = <HInstruction>[];
type.typeArguments.forEach((DartType argument) {
typeInputs
.add(_typeBuilder.analyzeTypeArgument(argument, sourceElement));
});
// We lift this common call pattern into a helper function to save space
// in the output.
if (typeInputs.every((HInstruction input) =>
input.isNull(_abstractValueDomain).isDefinitelyTrue)) {
if (elements.isEmpty) {
constructor = _commonElements.setLiteralUntypedEmptyMaker;
} else {
constructor = _commonElements.setLiteralUntypedMaker;
}
} else {
inputs.addAll(typeInputs);
}
}
// If runtime type information is needed and the set literal has no type
// parameter, 'constructor' is a static function that forwards the call to
// the factory constructor without a type parameter.
assert(constructor.isFunction ||
(constructor is ConstructorEntity && constructor.isFactoryConstructor));
// The instruction type will always be a subtype of the setLiteralClass, but
// type inference might discover a more specific type or find nothing (in
// dart2js unit tests).
AbstractValue setType = _abstractValueDomain
.createNonNullSubtype(_commonElements.setLiteralClass);
AbstractValue returnTypeMask =
_typeInferenceMap.getReturnTypeOf(constructor);
AbstractValue instructionType =
_abstractValueDomain.intersection(setType, returnTypeMask);
_addImplicitInstantiation(type);
_pushStaticInvocation(
constructor, inputs, instructionType, const <DartType>[],
sourceInformation: _sourceInformationBuilder.buildNew(node));
_removeImplicitInstantiation(type);
}
@override
void visitMapLiteral(ir.MapLiteral node) {
if (node.isConst) {
stack.add(
graph.addConstant(_elementMap.getConstantValue(node), closedWorld));
return;
}
// The map literal constructors take the key-value pairs as a List
List<HInstruction> constructorArgs = <HInstruction>[];
for (ir.MapEntry mapEntry in node.entries) {
mapEntry.key.accept(this);
constructorArgs.add(pop());
mapEntry.value.accept(this);
constructorArgs.add(pop());
}
// The constructor is a procedure because it's a factory.
FunctionEntity constructor;
List<HInstruction> inputs = <HInstruction>[];
if (constructorArgs.isEmpty) {
constructor = _commonElements.mapLiteralConstructorEmpty;
} else {
constructor = _commonElements.mapLiteralConstructor;
HLiteralList argList = _buildLiteralList(constructorArgs);
add(argList);
inputs.add(argList);
}
assert(
constructor is ConstructorEntity && constructor.isFactoryConstructor);
InterfaceType type = localsHandler.substInContext(_commonElements.mapType(
_elementMap.getDartType(node.keyType),
_elementMap.getDartType(node.valueType)));
ClassEntity cls = constructor.enclosingClass;
if (_rtiNeed.classNeedsTypeArguments(cls)) {
List<HInstruction> typeInputs = <HInstruction>[];
type.typeArguments.forEach((DartType argument) {
typeInputs
.add(_typeBuilder.analyzeTypeArgument(argument, sourceElement));
});
// We lift this common call pattern into a helper function to save space
// in the output.
if (typeInputs.every((HInstruction input) =>
input.isNull(_abstractValueDomain).isDefinitelyTrue)) {
if (constructorArgs.isEmpty) {
constructor = _commonElements.mapLiteralUntypedEmptyMaker;
} else {
constructor = _commonElements.mapLiteralUntypedMaker;
}
} else {
inputs.addAll(typeInputs);
}
}
// If runtime type information is needed and the map literal has no type
// parameters, 'constructor' is a static function that forwards the call to
// the factory constructor without type parameters.
assert(constructor.isFunction ||
(constructor is ConstructorEntity && constructor.isFactoryConstructor));
// The instruction type will always be a subtype of the mapLiteralClass, but
// type inference might discover a more specific type, or find nothing (in
// dart2js unit tests).
AbstractValue mapType = _abstractValueDomain
.createNonNullSubtype(_commonElements.mapLiteralClass);
AbstractValue returnTypeMask =
_typeInferenceMap.getReturnTypeOf(constructor);
AbstractValue instructionType =
_abstractValueDomain.intersection(mapType, returnTypeMask);
_addImplicitInstantiation(type);
_pushStaticInvocation(
constructor, inputs, instructionType, const <DartType>[],
sourceInformation: _sourceInformationBuilder.buildNew(node));
_removeImplicitInstantiation(type);
}
@override
void visitMapEntry(ir.MapEntry node) {
failedAt(CURRENT_ELEMENT_SPANNABLE,
'ir.MapEntry should be handled in visitMapLiteral');
}
@override
void visitTypeLiteral(ir.TypeLiteral node) {
SourceInformation sourceInformation =
_sourceInformationBuilder.buildGet(node);
ir.DartType type = node.type;
if (type is ir.InterfaceType ||
type is ir.DynamicType ||
type is ir.TypedefType ||
type is ir.FunctionType) {
ConstantValue constant = _elementMap.getConstantValue(node);
stack.add(graph.addConstant(constant, closedWorld,
sourceInformation: sourceInformation));
return;
}
assert(
type is ir.TypeParameterType,
failedAt(
CURRENT_ELEMENT_SPANNABLE, "Unexpected type literal ${node}."));
// For other types (e.g. TypeParameterType, function types from expanded
// typedefs), look-up or construct a reified type representation and convert
// to a RuntimeType.
DartType dartType = _elementMap.getDartType(type);
dartType = localsHandler.substInContext(dartType);
HInstruction value = _typeBuilder.analyzeTypeArgument(
dartType, sourceElement,
sourceInformation: sourceInformation);
_pushStaticInvocation(
_commonElements.createRuntimeType,
<HInstruction>[value],
_typeInferenceMap.getReturnTypeOf(_commonElements.createRuntimeType),
const <DartType>[],
sourceInformation: sourceInformation);
}
@override
void visitStaticGet(ir.StaticGet node) {
ir.Member staticTarget = node.target;
SourceInformation sourceInformation =
_sourceInformationBuilder.buildGet(node);
if (staticTarget is ir.Procedure &&
staticTarget.kind == ir.ProcedureKind.Getter) {
FunctionEntity getter = _elementMap.getMember(staticTarget);
// Invoke the getter
_pushStaticInvocation(getter, const <HInstruction>[],
_typeInferenceMap.getReturnTypeOf(getter), const <DartType>[],
sourceInformation: sourceInformation);
} else if (staticTarget is ir.Field) {
FieldEntity field = _elementMap.getField(staticTarget);
FieldAnalysisData fieldData = _fieldAnalysis.getFieldData(field);
if (fieldData.isEager) {
push(new HStatic(field, _typeInferenceMap.getInferredTypeOf(field),
sourceInformation));
} else if (fieldData.isEffectivelyConstant) {
OutputUnit outputUnit =
closedWorld.outputUnitData.outputUnitForMember(field);
// TODO(sigmund): this is not equivalent to what the old FE does: if
// there is no prefix the old FE wouldn't treat this in any special
// way. Also, if the prefix points to a constant in the main output
// unit, the old FE would still generate a deferred wrapper here.
if (!closedWorld.outputUnitData
.hasOnlyNonDeferredImportPaths(targetElement, field)) {
ConstantValue deferredConstant = new DeferredGlobalConstantValue(
fieldData.initialValue, outputUnit);
registry
.registerConstantUse(new ConstantUse.deferred(deferredConstant));
stack.add(graph.addDeferredConstant(
deferredConstant, sourceInformation, closedWorld));
} else {
stack.add(graph.addConstant(fieldData.initialValue, closedWorld,
sourceInformation: sourceInformation));
}
} else {
assert(
fieldData.isLazy, "Unexpected field data for $field: $fieldData");
push(new HLazyStatic(field, _typeInferenceMap.getInferredTypeOf(field),
sourceInformation));
}
} else {
// TODO(johnniwinther): This is a constant tear off, so we should have
// created a constant value instead. Remove this case when we use CFE
// constants.
FunctionEntity member = _elementMap.getMember(staticTarget);
push(new HStatic(member, _typeInferenceMap.getInferredTypeOf(member),
sourceInformation));
}
}
@override
void visitStaticSet(ir.StaticSet node) {
node.value.accept(this);
HInstruction value = pop();
ir.Member staticTarget = node.target;
if (staticTarget is ir.Procedure) {
FunctionEntity setter = _elementMap.getMember(staticTarget);
// Invoke the setter
_pushStaticInvocation(setter, <HInstruction>[value],
_typeInferenceMap.getReturnTypeOf(setter), const <DartType>[],
sourceInformation: _sourceInformationBuilder.buildSet(node));
pop();
} else {
MemberEntity target = _elementMap.getMember(staticTarget);
if (!_fieldAnalysis.getFieldData(target).isElided) {
add(new HStaticStore(
_abstractValueDomain,
target,
_typeBuilder.potentiallyCheckOrTrustTypeOfAssignment(
target, value, _getDartTypeIfValid(staticTarget.setterType))));
}
}
stack.add(value);
}
@override
void visitPropertyGet(ir.PropertyGet node) {
node.receiver.accept(this);
HInstruction receiver = pop();
_pushDynamicInvocation(
node,
_getStaticType(node.receiver),
_typeInferenceMap.receiverTypeOfGet(node),
new Selector.getter(_elementMap.getName(node.name)),
<HInstruction>[receiver],
const <DartType>[],
_sourceInformationBuilder.buildGet(node));
}
@override
void visitVariableGet(ir.VariableGet node) {
ir.VariableDeclaration variable = node.variable;
HInstruction letBinding = _letBindings[variable];
if (letBinding != null) {
stack.add(letBinding);
return;
}
Local local = _localsMap.getLocalVariable(node.variable);
stack.add(localsHandler.readLocal(local,
sourceInformation: _sourceInformationBuilder.buildGet(node)));
}
@override
void visitPropertySet(ir.PropertySet node) {
node.receiver.accept(this);
HInstruction receiver = pop();
node.value.accept(this);
HInstruction value = pop();
_pushDynamicInvocation(
node,
_getStaticType(node.receiver),
_typeInferenceMap.receiverTypeOfSet(node, _abstractValueDomain),
new Selector.setter(_elementMap.getName(node.name)),
<HInstruction>[receiver, value],
const <DartType>[],
_sourceInformationBuilder.buildAssignment(node));
pop();
stack.add(value);
}
@override
void visitDirectPropertyGet(ir.DirectPropertyGet node) {
node.receiver.accept(this);
HInstruction receiver = pop();
// Fake direct call with a dynamic call.
// TODO(sra): Implement direct invocations properly.
_pushDynamicInvocation(
node,
_getStaticType(node.receiver),
_typeInferenceMap.receiverTypeOfDirectGet(node),
new Selector.getter(_elementMap.getMember(node.target).memberName),
<HInstruction>[receiver],
const <DartType>[],
_sourceInformationBuilder.buildGet(node));
}
@override
void visitDirectPropertySet(ir.DirectPropertySet node) {
throw new UnimplementedError('ir.DirectPropertySet');
}
@override
void visitDirectMethodInvocation(ir.DirectMethodInvocation node) {
throw new UnimplementedError('ir.DirectMethodInvocation');
}
@override
void visitSuperPropertySet(ir.SuperPropertySet node) {
SourceInformation sourceInformation =
_sourceInformationBuilder.buildAssignment(node);
node.value.accept(this);
HInstruction value = pop();
MemberEntity member = _elementMap
.getSuperMember(_currentFrame.member, node.name, setter: true);
if (member == null) {
_generateSuperNoSuchMethod(node, _elementMap.getSelector(node).name + "=",
<HInstruction>[value], const <DartType>[], sourceInformation);
} else {
_buildInvokeSuper(
_elementMap.getSelector(node),
_elementMap.getClass(_containingClass(node)),
member,
<HInstruction>[value],
const <DartType>[],
sourceInformation);
}
pop();
stack.add(value);
}
@override
void visitVariableSet(ir.VariableSet node) {
node.value.accept(this);
HInstruction value = pop();
_visitLocalSetter(
node.variable, value, _sourceInformationBuilder.buildAssignment(node));
}
@override
void visitVariableDeclaration(ir.VariableDeclaration node) {
Local local = _localsMap.getLocalVariable(node);
if (node.initializer == null) {
HInstruction initialValue = graph.addConstantNull(closedWorld);
localsHandler.updateLocal(local, initialValue);
} else if (node.isConst) {
ConstantValue constant = _elementMap.getConstantValue(node.initializer);
assert(constant != null, failedAt(CURRENT_ELEMENT_SPANNABLE));
HInstruction initialValue = graph.addConstant(constant, closedWorld);
localsHandler.updateLocal(local, initialValue);
} else {
node.initializer.accept(this);
HInstruction initialValue = pop();
_visitLocalSetter(
node, initialValue, _sourceInformationBuilder.buildAssignment(node));
// Ignore value
pop();
}
}
void _visitLocalSetter(ir.VariableDeclaration variable, HInstruction value,
SourceInformation sourceInformation) {
Local local = _localsMap.getLocalVariable(variable);
// Give the value a name if it doesn't have one already.
if (value.sourceElement == null) {
value.sourceElement = local;
}
stack.add(value);
localsHandler.updateLocal(
local,
_typeBuilder.potentiallyCheckOrTrustTypeOfAssignment(
_currentFrame.member, value, _getDartTypeIfValid(variable.type)),
sourceInformation: sourceInformation);
}
@override
void visitLet(ir.Let node) {
ir.VariableDeclaration variable = node.variable;
variable.initializer.accept(this);
HInstruction initializedValue = pop();
// TODO(sra): Apply inferred type information.
_letBindings[variable] = initializedValue;
node.body.accept(this);
}
@override
void visitBlockExpression(ir.BlockExpression node) {
node.body.accept(this);
node.value.accept(this);
}
/// Extracts the list of instructions for the positional subset of arguments.
List<HInstruction> _visitPositionalArguments(ir.Arguments arguments) {
List<HInstruction> result = <HInstruction>[];
for (ir.Expression argument in arguments.positional) {
argument.accept(this);
result.add(pop());
}
return result;
}
/// Builds the list of instructions for the expressions in the arguments to a
/// dynamic target (member function). Dynamic targets use stubs to add
/// defaulted arguments, so (unlike static targets) we do not add the default
/// values.
List<HInstruction> _visitArgumentsForDynamicTarget(
Selector selector, ir.Arguments arguments, List<DartType> typeArguments,
[SourceInformation sourceInformation]) {
List<HInstruction> values = _visitPositionalArguments(arguments);
if (arguments.named.isNotEmpty) {
Map<String, HInstruction> namedValues = <String, HInstruction>{};
for (ir.NamedExpression argument in arguments.named) {
argument.value.accept(this);
namedValues[argument.name] = pop();
}
for (String name in selector.callStructure.getOrderedNamedArguments()) {
values.add(namedValues[name]);
}
}
_addTypeArguments(values, typeArguments, sourceInformation);
return values;
}
/// Build the argument list for JS-interop invocations, which have slightly
/// different semantics than dart because of JS's null vs undefined and lack
/// of named arguments. Return null if the arguments could not be correctly
/// parsed because the user provided code with named parameters in a JS (non
/// factory) function.
List<HInstruction> _visitArgumentsForNativeStaticTarget(
ir.FunctionNode target, ir.Arguments arguments) {
// Visit arguments in source order, then re-order and fill in defaults.
var values = _visitPositionalArguments(arguments);
if (target.namedParameters.isNotEmpty) {
// Only anonymous factory constructors involving JS interop are allowed to
// have named parameters. Otherwise, throw an error.
FunctionEntity function = _elementMap.getMember(target.parent);
if (function is ConstructorEntity && function.isFactoryConstructor) {
// TODO(sra): Have a "CompiledArguments" structure to just update with
// what values we have rather than creating a map and de-populating it.
var namedValues = <String, HInstruction>{};
for (ir.NamedExpression argument in arguments.named) {
argument.value.accept(this);
namedValues[argument.name] = pop();
}
// Visit named arguments in parameter-position order, selecting provided
// or default value.
var namedParameters = target.namedParameters.toList();
namedParameters.sort(nativeOrdering);
for (ir.VariableDeclaration parameter in namedParameters) {
HInstruction value = namedValues[parameter.name];
values.add(value);
if (value != null) {
namedValues.remove(parameter.name);
}
}
assert(namedValues.isEmpty);
} else {
// Throw an error because JS cannot handle named parameters.
reporter.reportErrorMessage(
_elementMap.getSpannable(targetElement, target),
MessageKind.JS_INTEROP_METHOD_WITH_NAMED_ARGUMENTS,
{'method': function.name});
return null;
}
}
return values;
}
/// Fills [typeArguments] with the type arguments needed for [selector] and
/// returns the selector corresponding to the passed type arguments.
Selector _fillDynamicTypeArguments(
Selector selector, ir.Arguments arguments, List<DartType> typeArguments) {
if (selector.typeArgumentCount > 0) {
if (_rtiNeed.selectorNeedsTypeArguments(selector)) {
typeArguments.addAll(arguments.types.map(_elementMap.getDartType));
} else {
return selector.toNonGeneric();
}
}
return selector;
}
List<DartType> _getConstructorTypeArguments(
ConstructorEntity constructor, ir.Arguments arguments) {
// TODO(johnniwinther): Pass type arguments to constructors like calling
// a generic method.
return const <DartType>[];
}
// TODO(johnniwinther): Remove this when type arguments are passed to
// constructors like calling a generic method.
List<DartType> _getClassTypeArguments(
ClassEntity cls, ir.Arguments arguments) {
if (_rtiNeed.classNeedsTypeArguments(cls)) {
return arguments.types.map(_elementMap.getDartType).toList();
}
return const <DartType>[];
}
List<DartType> _getStaticTypeArguments(
FunctionEntity function, ir.Arguments arguments) {
if (_rtiNeed.methodNeedsTypeArguments(function)) {
return arguments.types.map(_elementMap.getDartType).toList();
}
return const <DartType>[];
}
/// Build argument list in canonical order for a static [target], including
/// filling in the default argument value.
List<HInstruction> _visitArgumentsForStaticTarget(
ir.FunctionNode target,
ParameterStructure parameterStructure,
ir.Arguments arguments,
List<DartType> typeArguments,
SourceInformation sourceInformation) {
// Visit arguments in source order, then re-order and fill in defaults.
List<HInstruction> values = _visitPositionalArguments(arguments);
while (values.length < parameterStructure.positionalParameters) {
ir.VariableDeclaration parameter =
target.positionalParameters[values.length];
values.add(_defaultValueForParameter(parameter));
}
if (parameterStructure.namedParameters.isNotEmpty) {
Map<String, HInstruction> namedValues = {};
for (ir.NamedExpression argument in arguments.named) {
argument.value.accept(this);
namedValues[argument.name] = pop();
}
// Visit named arguments in parameter-position order, selecting provided
// or default value.
// TODO(sra): Ensure the stored order is canonical so we don't have to
// sort. The old builder uses CallStructure.makeArgumentList which depends
// on the old element model.
List<ir.VariableDeclaration> namedParameters = target.namedParameters
// Filter elided parameters.
.where((p) => parameterStructure.namedParameters.contains(p.name))
.toList()
..sort(namedOrdering);
for (ir.VariableDeclaration parameter in namedParameters) {
HInstruction value = namedValues[parameter.name];
if (value == null) {
values.add(_defaultValueForParameter(parameter));
} else {
values.add(value);
namedValues.remove(parameter.name);
}
}
assert(namedValues.isEmpty);
} else {
assert(arguments.named.isEmpty);
}
_addTypeArguments(values, typeArguments, sourceInformation);
return values;
}
void _addTypeArguments(List<HInstruction> values,
List<DartType> typeArguments, SourceInformation sourceInformation) {
if (typeArguments.isEmpty) return;
for (DartType type in typeArguments) {
values.add(_typeBuilder.analyzeTypeArgument(type, sourceElement,
sourceInformation: sourceInformation));
}
}
HInstruction _defaultValueForParameter(ir.VariableDeclaration parameter) {
ConstantValue constant =
_elementMap.getConstantValue(parameter.initializer, implicitNull: true);
assert(constant != null, failedAt(CURRENT_ELEMENT_SPANNABLE));
return graph.addConstant(constant, closedWorld);
}
@override
void visitStaticInvocation(ir.StaticInvocation node) {
ir.Procedure target = node.target;
SourceInformation sourceInformation =
_sourceInformationBuilder.buildCall(node, node);
FunctionEntity function = _elementMap.getMember(target);
if (_commonElements.isForeignHelper(function)) {
_handleInvokeStaticForeign(node, function);
return;
}
if (_commonElements.isExtractTypeArguments(function) &&
_handleExtractTypeArguments(node, sourceInformation)) {
return;
}
AbstractValue typeMask = _typeInferenceMap.getReturnTypeOf(function);
List<DartType> typeArguments =
_getStaticTypeArguments(function, node.arguments);
List<HInstruction> arguments = closedWorld.nativeData
.isJsInteropMember(function)
? _visitArgumentsForNativeStaticTarget(target.function, node.arguments)
: _visitArgumentsForStaticTarget(
target.function,
function.parameterStructure,
node.arguments,
typeArguments,
sourceInformation);
// Error in the arguments provided. Do not process further.
if (arguments == null) {
stack.add(graph.addConstantNull(closedWorld)); // Result expected on stack
return;
}
if (function is ConstructorEntity && function.isFactoryConstructor) {
_handleInvokeFactoryConstructor(
node, function, typeMask, arguments, sourceInformation);
return;
}
// Static methods currently ignore the type parameters.
_pushStaticInvocation(function, arguments, typeMask, typeArguments,
sourceInformation: sourceInformation);
}
void _handleInvokeFactoryConstructor(
ir.StaticInvocation invocation,
ConstructorEntity function,
AbstractValue typeMask,
List<HInstruction> arguments,
SourceInformation sourceInformation) {
if (function.isExternal && function.isFromEnvironmentConstructor) {
if (invocation.isConst) {
// Just like all const constructors (see visitConstructorInvocation).
stack.add(graph.addConstant(
_elementMap.getConstantValue(invocation), closedWorld,
sourceInformation: sourceInformation));
} else {
_generateUnsupportedError(
'${function.enclosingClass.name}.${function.name} '
'can only be used as a const constructor',
sourceInformation);
}
return;
}
bool isFixedList = false; // Any fixed list, e.g. new List(10), UInt8List.
// Recognize `new List()` and `new List(n)`.
bool isFixedListConstructorCall = false;
bool isGrowableListConstructorCall = false;
if (_commonElements.isUnnamedListConstructor(function) &&
invocation.arguments.named.isEmpty) {
int argumentCount = invocation.arguments.positional.length;
isFixedListConstructorCall = argumentCount == 1;
isGrowableListConstructorCall = argumentCount == 0;
isFixedList = isFixedListConstructorCall;
}
InterfaceType instanceType = _elementMap.createInterfaceType(
invocation.target.enclosingClass, invocation.arguments.types);
AbstractValue resultType = typeMask;
bool isJSArrayTypedConstructor =
function == _commonElements.jsArrayTypedConstructor;
_inferredTypeOfNewList(ir.StaticInvocation node) {
return globalInferenceResults.typeOfNewList(node) ??
_abstractValueDomain.dynamicType;
}
if (isFixedListConstructorCall) {
assert(
// Arguments may include the type.
arguments.length == 1 || arguments.length == 2,
failedAt(
function,
"Unexpected arguments. "
"Expected 1-2 argument, actual: $arguments."));
HInstruction lengthInput = arguments.first;
if (lengthInput.isNumber(_abstractValueDomain).isPotentiallyFalse) {
HPrimitiveCheck conversion = new HPrimitiveCheck(
_commonElements.numType,
HPrimitiveCheck.ARGUMENT_TYPE_CHECK,
_abstractValueDomain.numType,
lengthInput,
sourceInformation);
add(conversion);
lengthInput = conversion;
}
js.Template code = js.js.parseForeignJS('new Array(#)');
var behavior = new NativeBehavior();
var expectedType =
_elementMap.getDartType(invocation.getStaticType(null));
behavior.typesInstantiated.add(expectedType);
behavior.typesReturned.add(expectedType);
// The allocation can throw only if the given length is a double or
// outside the unsigned 32 bit range.
// TODO(sra): Array allocation should be an instruction so that canThrow
// can depend on a length type discovered in optimization.
bool canThrow = true;
if (lengthInput.isUInt32(_abstractValueDomain).isDefinitelyTrue) {
canThrow = false;
}
var inferredType = _inferredTypeOfNewList(invocation);
resultType =
_abstractValueDomain.containsAll(inferredType).isPotentiallyTrue
? _abstractValueDomain.fixedListType
: inferredType;
HForeignCode foreign = new HForeignCode(
code, resultType, <HInstruction>[lengthInput],
nativeBehavior: behavior,
throwBehavior:
canThrow ? NativeThrowBehavior.MAY : NativeThrowBehavior.NEVER)
..sourceInformation = sourceInformation;
push(foreign);
// TODO(redemption): Global type analysis tracing may have determined that
// the fixed-length property is never checked. If so, we can avoid marking
// the array.
{
js.Template code = js.js.parseForeignJS(r'#.fixed$length = Array');
// We set the instruction as [canThrow] to avoid it being dead code.
// We need a finer grained side effect.
add(new HForeignCode(code, _abstractValueDomain.nullType, [stack.last],
throwBehavior: NativeThrowBehavior.MAY));
}
} else if (isGrowableListConstructorCall) {
push(_buildLiteralList(<HInstruction>[]));
var inferredType = _inferredTypeOfNewList(invocation);
resultType =
_abstractValueDomain.containsAll(inferredType).isPotentiallyTrue
? _abstractValueDomain.growableListType
: inferredType;
stack.last.instructionType = resultType;
} else if (isJSArrayTypedConstructor) {
// TODO(sra): Instead of calling the identity-like factory constructor,
// simply select the single argument.
// Factory constructors take type parameters.
if (closedWorld.rtiNeed
.classNeedsTypeArguments(function.enclosingClass)) {}
List<DartType> typeArguments =
_getConstructorTypeArguments(function, invocation.arguments);
// TODO(johnniwinther): Remove this when type arguments are passed to
// constructors like calling a generic method.
_addTypeArguments(
arguments,
_getClassTypeArguments(function.enclosingClass, invocation.arguments),
sourceInformation);
_pushStaticInvocation(function, arguments, typeMask, typeArguments,
sourceInformation: sourceInformation);
} else {
// Factory constructors take type parameters.
List<DartType> typeArguments =
_getConstructorTypeArguments(function, invocation.arguments);
// TODO(johnniwinther): Remove this when type arguments are passed to
// constructors like calling a generic method.
_addTypeArguments(
arguments,
_getClassTypeArguments(function.enclosingClass, invocation.arguments),
sourceInformation);
instanceType = localsHandler.substInContext(instanceType);
_addImplicitInstantiation(instanceType);
_pushStaticInvocation(function, arguments, typeMask, typeArguments,
sourceInformation: sourceInformation, instanceType: instanceType);
}
HInstruction newInstance = stack.last;
if (isFixedList) {
// If we inlined a constructor the call-site-specific type from type
// inference (e.g. a container type) will not be on the node. Store the
// more specialized type on the allocation.
newInstance.instructionType = resultType;
graph.allocatedFixedLists.add(newInstance);
}
if (_rtiNeed.classNeedsTypeArguments(_commonElements.listClass) &&
(isFixedListConstructorCall ||
isGrowableListConstructorCall ||
isJSArrayTypedConstructor)) {
InterfaceType type = _elementMap.createInterfaceType(
invocation.target.enclosingClass, invocation.arguments.types);
stack
.add(_setListRuntimeTypeInfoIfNeeded(pop(), type, sourceInformation));
}
}
/// Replace calls to `extractTypeArguments` with equivalent code. Returns
/// `true` if `extractTypeArguments` is handled.
bool _handleExtractTypeArguments(
ir.StaticInvocation invocation, SourceInformation sourceInformation) {
// Expand calls as follows:
//
// r = extractTypeArguments<Map>(e, f)
// -->
// interceptor = getInterceptor(e);
// T1 = getRuntimeTypeArgumentIntercepted(interceptor, e, 'Map', 0);
// T2 = getRuntimeTypeArgumentIntercepted(interceptor, e, 'Map', 1);
// r = f<T1, T2>();
//
// TODO(sra): Should we add a check before the variable extraction? We could
// add a type check (which would permit `null`), or add an is-check with an
// explicit throw.
if (invocation.arguments.positional.length != 2) return false;
if (invocation.arguments.named.isNotEmpty) return false;
var types = invocation.arguments.types;
if (types.length != 1) return false;
// The type should be a single type name.
ir.DartType type = types.first;
DartType typeValue =
localsHandler.substInContext(_elementMap.getDartType(type));
if (typeValue is! InterfaceType) return false;
InterfaceType interfaceType = typeValue;
if (!interfaceType.treatAsRaw) return false;
ClassEntity cls = interfaceType.element;
InterfaceType thisType = _elementEnvironment.getThisType(cls);
List<HInstruction> arguments =
_visitPositionalArguments(invocation.arguments);
HInstruction object = arguments[0];
HInstruction closure = arguments[1];
HInstruction interceptor = _interceptorFor(object, sourceInformation);
List<HInstruction> inputs = <HInstruction>[closure];
List<DartType> typeArguments = <DartType>[];
if (options.experimentNewRti) {
HInstruction instanceType =
HInstanceEnvironment(object, _abstractValueDomain.dynamicType);
add(instanceType);
TypeEnvironmentStructure envStructure =
FullTypeEnvironmentStructure(classType: thisType);
thisType.typeArguments.forEach((_typeVariable) {
TypeVariableType variable = _typeVariable;
typeArguments.add(variable);
TypeRecipe recipe = TypeExpressionRecipe(variable);
HInstruction typeEval = new HTypeEval(instanceType, envStructure,
recipe, _abstractValueDomain.dynamicType);
add(typeEval);
inputs.add(typeEval);
});
} else {
thisType.typeArguments.forEach((_typeVariable) {
TypeVariableType variable = _typeVariable;
typeArguments.add(variable);
HInstruction readType = new HTypeInfoReadVariable.intercepted(
variable, interceptor, object, _abstractValueDomain.dynamicType);
add(readType);
inputs.add(readType);
});
}
// TODO(sra): In compliance mode, insert a check that [closure] is a
// function of N type arguments.
Selector selector =
new Selector.callClosure(0, const <String>[], typeArguments.length);
StaticType receiverStaticType =
_getStaticType(invocation.arguments.positional[1]);
AbstractValue receiverType = _abstractValueDomain
.createFromStaticType(receiverStaticType.type,
classRelation: receiverStaticType.relation, nullable: true)
.abstractValue;
push(new HInvokeClosure(selector, receiverType, inputs,
_abstractValueDomain.dynamicType, typeArguments));
return true;
}
void _handleInvokeStaticForeign(
ir.StaticInvocation invocation, MemberEntity member) {
String name = member.name;
if (name == 'JS') {
_handleForeignJs(invocation);
} else if (name == 'DART_CLOSURE_TO_JS') {
_handleForeignDartClosureToJs(invocation, 'DART_CLOSURE_TO_JS');
} else if (name == 'RAW_DART_FUNCTION_REF') {
_handleForeignRawFunctionRef(invocation, 'RAW_DART_FUNCTION_REF');
} else if (name == 'JS_SET_STATIC_STATE') {
_handleForeignJsSetStaticState(invocation);
} else if (name == 'JS_GET_STATIC_STATE') {
_handleForeignJsGetStaticState(invocation);
} else if (name == 'JS_GET_NAME') {
_handleForeignJsGetName(invocation);
} else if (name == 'JS_EMBEDDED_GLOBAL') {
_handleForeignJsEmbeddedGlobal(invocation);
} else if (name == 'JS_BUILTIN') {
_handleForeignJsBuiltin(invocation);
} else if (name == 'JS_GET_FLAG') {
_handleForeignJsGetFlag(invocation);
} else if (name == 'JS_EFFECT') {
stack.add(graph.addConstantNull(closedWorld));
} else if (name == 'JS_INTERCEPTOR_CONSTANT') {
_handleJsInterceptorConstant(invocation);
} else if (name == 'getInterceptor') {
_handleForeignGetInterceptor(invocation);
} else if (name == 'getJSArrayInteropRti') {
_handleForeignGetJSArrayInteropRti(invocation);
} else if (name == 'JS_STRING_CONCAT') {
_handleJsStringConcat(invocation);
} else if (name == '_createInvocationMirror') {
_handleCreateInvocationMirror(invocation);
} else {
reporter.internalError(
_elementMap.getSpannable(targetElement, invocation),
"Unknown foreign: ${name}");
}
}
void _handleCreateInvocationMirror(ir.StaticInvocation invocation) {
ir.StringLiteral nameLiteral = invocation.arguments.positional[0];
String name = nameLiteral.value;
ir.ListLiteral typeArgumentsLiteral = invocation.arguments.positional[1];
List<DartType> typeArguments =
typeArgumentsLiteral.expressions.map((ir.Expression expression) {
ir.TypeLiteral typeLiteral = expression;
return _elementMap.getDartType(typeLiteral.type);
}).toList();
ir.ListLiteral positionalArgumentsLiteral =
invocation.arguments.positional[2];
ir.Expression namedArgumentsLiteral = invocation.arguments.positional[3];
Map<String, ir.Expression> namedArguments = {};
ir.IntLiteral kindLiteral = invocation.arguments.positional[4];
Name memberName = new Name(name, _currentFrame.member.library);
Selector selector;
switch (kindLiteral.value) {
case invocationMirrorGetterKind:
selector = new Selector.getter(memberName);
break;
case invocationMirrorSetterKind:
selector = new Selector.setter(memberName);
break;
case invocationMirrorMethodKind:
if (memberName == Names.INDEX_NAME) {
selector = new Selector.index();
} else if (memberName == Names.INDEX_SET_NAME) {
selector = new Selector.indexSet();
} else {
if (namedArgumentsLiteral is ir.MapLiteral) {
namedArgumentsLiteral.entries.forEach((ir.MapEntry entry) {
ir.StringLiteral key = entry.key;
namedArguments[key.value] = entry.value;
});
} else if (namedArgumentsLiteral is ir.ConstantExpression &&
namedArgumentsLiteral.constant is ir.MapConstant) {
ir.MapConstant constant = namedArgumentsLiteral.constant;
for (ir.ConstantMapEntry entry in constant.entries) {
ir.StringConstant key = entry.key;
namedArguments[key.value] =
new ir.ConstantExpression(entry.value);
}
} else {
reporter.internalError(
computeSourceSpanFromTreeNode(invocation),
"Unexpected named arguments value in createInvocationMirrror: "
"${namedArgumentsLiteral}.");
}
CallStructure callStructure = new CallStructure(
positionalArgumentsLiteral.expressions.length,
namedArguments.keys.toList(),
typeArguments.length);
if (Selector.isOperatorName(name)) {
selector =
new Selector(SelectorKind.OPERATOR, memberName, callStructure);
} else {
selector = new Selector.call(memberName, callStructure);
}
}
break;
}
HConstant nameConstant = graph.addConstant(
constant_system.createSymbol(closedWorld.commonElements, name),
closedWorld);
List<HInstruction> arguments = <HInstruction>[];
for (ir.Expression argument in positionalArgumentsLiteral.expressions) {
argument.accept(this);
arguments.add(pop());
}
if (namedArguments.isNotEmpty) {
Map<String, HInstruction> namedValues = <String, HInstruction>{};
namedArguments.forEach((String name, ir.Expression value) {
value.accept(this);
namedValues[name] = pop();
});
for (String name in selector.callStructure.getOrderedNamedArguments()) {
arguments.add(namedValues[name]);
}
}
SourceInformation sourceInformation =
_sourceInformationBuilder.buildCall(invocation, invocation);
_addTypeArguments(arguments, typeArguments, sourceInformation);
HInstruction argumentsInstruction = _buildLiteralList(arguments);
add(argumentsInstruction);
List<HInstruction> argumentNames = <HInstruction>[];
for (String argumentName
in selector.callStructure.getOrderedNamedArguments()) {
ConstantValue argumentNameConstant =
constant_system.createString(argumentName);
argumentNames.add(graph.addConstant(argumentNameConstant, closedWorld));
}
HInstruction argumentNamesInstruction = _buildLiteralList(argumentNames);
add(argumentNamesInstruction);
HInstruction typeArgumentCount =
graph.addConstantInt(typeArguments.length, closedWorld);
js.Name internalName = _namer.invocationName(selector);
ConstantValue kindConstant =
constant_system.createIntFromInt(selector.invocationMirrorKind);
_pushStaticInvocation(
_commonElements.createUnmangledInvocationMirror,
[
nameConstant,
graph.addConstantStringFromName(internalName, closedWorld),
graph.addConstant(kindConstant, closedWorld),
argumentsInstruction,
argumentNamesInstruction,
typeArgumentCount,
],
_abstractValueDomain.dynamicType,
const <DartType>[],
sourceInformation: sourceInformation);
}
bool _unexpectedForeignArguments(ir.StaticInvocation invocation,
{int minPositional, int maxPositional, int typeArgumentCount = 0}) {
String pluralizeArguments(int count, [String adjective = '']) {
if (count == 0) return 'no ${adjective}arguments';
if (count == 1) return 'one ${adjective}argument';
if (count == 2) return 'two ${adjective}arguments';
return '$count ${adjective}arguments';
}
String name() => invocation.target.name.name;
ir.Arguments arguments = invocation.arguments;
bool bad = false;
if (arguments.types.length != typeArgumentCount) {
String expected = pluralizeArguments(typeArgumentCount, 'type ');
String actual = pluralizeArguments(arguments.types.length, 'type ');
reporter.reportErrorMessage(
_elementMap.getSpannable(targetElement, invocation),
MessageKind.GENERIC,
{'text': "Error: '${name()}' takes $expected, not $actual."});
bad = true;
}
if (arguments.positional.length < minPositional) {
String phrase = pluralizeArguments(minPositional);
if (maxPositional != minPositional) phrase = 'at least $phrase';
reporter.reportErrorMessage(
_elementMap.getSpannable(targetElement, invocation),
MessageKind.GENERIC,
{'text': "Error: Too few arguments. '${name()}' takes $phrase."});
bad = true;
}
if (maxPositional != null && arguments.positional.length > maxPositional) {
String phrase = pluralizeArguments(maxPositional);
if (maxPositional != minPositional) phrase = 'at most $phrase';
reporter.reportErrorMessage(
_elementMap.getSpannable(targetElement, invocation),
MessageKind.GENERIC,
{'text': "Error: Too many arguments. '${name()}' takes $phrase."});
bad = true;
}
if (arguments.named.isNotEmpty) {
reporter.reportErrorMessage(
_elementMap.getSpannable(targetElement, invocation),
MessageKind.GENERIC,
{'text': "Error: '${name()}' does not take named arguments."});
bad = true;
}
return bad;
}
/// Returns the value of the string argument. The argument must evaluate to a
/// constant. If there is an error, the error is reported and `null` is
/// returned.
String _foreignConstantStringArgument(
ir.StaticInvocation invocation, int position, String methodName,
[String adjective = '']) {
ir.Expression argument = invocation.arguments.positional[position];
argument.accept(this);
HInstruction instruction = pop();
if (!instruction.isConstantString()) {
reporter.reportErrorMessage(
_elementMap.getSpannable(targetElement, argument),
MessageKind.GENERIC, {
'text': "Error: Expected String constant as ${adjective}argument "
"to '$methodName'."
});
return null;
}
HConstant hConstant = instruction;
StringConstantValue stringConstant = hConstant.constant;
return stringConstant.stringValue;
}
void _handleForeignDartClosureToJs(
ir.StaticInvocation invocation, String name) {
// TODO(sra): Do we need to wrap the closure in something that saves the
// current isolate?
_handleForeignRawFunctionRef(invocation, name);
}
void _handleForeignRawFunctionRef(
ir.StaticInvocation invocation, String name) {
if (_unexpectedForeignArguments(invocation,
minPositional: 1, maxPositional: 1)) {
// Result expected on stack.
stack.add(graph.addConstantNull(closedWorld));
return;
}
ir.Expression closure = invocation.arguments.positional.single;
String problem = 'requires a static method or top-level method';
bool handleTarget(ir.Procedure procedure) {
ir.FunctionNode function = procedure.function;
if (function != null &&
function.requiredParameterCount ==
function.positionalParameters.length &&
function.namedParameters.isEmpty) {
push(new HForeignCode(
js.js.expressionTemplateYielding(_emitter
.staticFunctionAccess(_elementMap.getMethod(procedure))),
_abstractValueDomain.dynamicType,
<HInstruction>[],
nativeBehavior: NativeBehavior.PURE,
foreignFunction: _elementMap.getMethod(procedure)));
return true;
}
problem = 'does not handle a closure with optional parameters';
return false;
}
if (closure is ir.StaticGet) {
ir.Member staticTarget = closure.target;
if (staticTarget is ir.Procedure) {
if (staticTarget.kind == ir.ProcedureKind.Method) {
if (handleTarget(staticTarget)) {
return;
}
}
}
} else if (closure is ir.ConstantExpression &&
closure.constant is ir.TearOffConstant) {
ir.TearOffConstant tearOff = closure.constant;
if (handleTarget(tearOff.procedure)) {
return;
}
}
reporter.reportErrorMessage(
_elementMap.getSpannable(targetElement, invocation),
MessageKind.GENERIC,
{'text': "'$name' $problem."});
stack.add(graph.addConstantNull(closedWorld)); // Result expected on stack.
return;
}
void _handleForeignJsSetStaticState(ir.StaticInvocation invocation) {
if (_unexpectedForeignArguments(invocation,
minPositional: 1, maxPositional: 1)) {
// Result expected on stack.
stack.add(graph.addConstantNull(closedWorld));
return;
}
List<HInstruction> inputs = _visitPositionalArguments(invocation.arguments);
String isolateName = _namer.staticStateHolder;
SideEffects sideEffects = new SideEffects.empty();
sideEffects.setAllSideEffects();
push(new HForeignCode(js.js.parseForeignJS("$isolateName = #"),
_abstractValueDomain.dynamicType, inputs,
nativeBehavior: NativeBehavior.CHANGES_OTHER, effects: sideEffects));
}
void _handleForeignJsGetStaticState(ir.StaticInvocation invocation) {
if (_unexpectedForeignArguments(invocation,
minPositional: 0, maxPositional: 0)) {
// Result expected on stack.
stack.add(graph.addConstantNull(closedWorld));
return;
}
push(new HForeignCode(js.js.parseForeignJS(_namer.staticStateHolder),
_abstractValueDomain.dynamicType, <HInstruction>[],
nativeBehavior: NativeBehavior.DEPENDS_OTHER));
}
void _handleForeignJsGetName(ir.StaticInvocation invocation) {
if (_unexpectedForeignArguments(invocation,
minPositional: 1, maxPositional: 1)) {
// Result expected on stack.
stack.add(graph.addConstantNull(closedWorld));
return;
}
ir.Node argument = invocation.arguments.positional.first;
argument.accept(this);
HInstruction instruction = pop();
if (instruction is HConstant) {
js.Name name = _getNameForJsGetName(instruction.constant, _namer);
stack.add(graph.addConstantStringFromName(name, closedWorld));
return;
}
reporter.reportErrorMessage(
_elementMap.getSpannable(targetElement, argument),
MessageKind.GENERIC,
{'text': 'Error: Expected a JsGetName enum value.'});
// Result expected on stack.
stack.add(graph.addConstantNull(closedWorld));
}
int _extractEnumIndexFromConstantValue(
ConstantValue constant, ClassEntity classElement) {
if (constant is ConstructedConstantValue) {
if (constant.type.element == classElement) {
assert(constant.fields.length == 1 || constant.fields.length == 2);
ConstantValue indexConstant = constant.fields.values.first;
if (indexConstant is IntConstantValue) {
return indexConstant.intValue.toInt();
}
}
}
return null;
}
/// Returns the [js.Name] for the `JsGetName` [constant] value.
js.Name _getNameForJsGetName(ConstantValue constant, ModularNamer namer) {
int index = _extractEnumIndexFromConstantValue(
constant, _commonElements.jsGetNameEnum);
if (index == null) return null;
return namer.getNameForJsGetName(
CURRENT_ELEMENT_SPANNABLE, JsGetName.values[index]);
}
void _handleForeignJsEmbeddedGlobal(ir.StaticInvocation invocation) {
if (_unexpectedForeignArguments(invocation,
minPositional: 2, maxPositional: 2)) {
// Result expected on stack.
stack.add(graph.addConstantNull(closedWorld));
return;
}
String globalName = _foreignConstantStringArgument(
invocation, 1, 'JS_EMBEDDED_GLOBAL', 'second ');
js.Template expr = js.js.expressionTemplateYielding(
_emitter.generateEmbeddedGlobalAccess(globalName));
NativeBehavior nativeBehavior =
_elementMap.getNativeBehaviorForJsEmbeddedGlobalCall(invocation);
assert(
nativeBehavior != null,
failedAt(_elementMap.getSpannable(targetElement, invocation),
"No NativeBehavior for $invocation"));
AbstractValue ssaType =
_typeInferenceMap.typeFromNativeBehavior(nativeBehavior, closedWorld);
push(new HForeignCode(expr, ssaType, const <HInstruction>[],
nativeBehavior: nativeBehavior));
}
void _handleForeignJsBuiltin(ir.StaticInvocation invocation) {
if (_unexpectedForeignArguments(invocation, minPositional: 2)) {
// Result expected on stack.
stack.add(graph.addConstantNull(closedWorld));
return;
}
List<ir.Expression> arguments = invocation.arguments.positional;
ir.Expression nameArgument = arguments[1];
nameArgument.accept(this);
HInstruction instruction = pop();
js.Template template;
if (instruction is HConstant) {
template = _getJsBuiltinTemplate(instruction.constant, _emitter);
}
if (template == null) {
reporter.reportErrorMessage(
_elementMap.getSpannable(targetElement, nameArgument),
MessageKind.GENERIC,
{'text': 'Error: Expected a JsBuiltin enum value.'});
// Result expected on stack.
stack.add(graph.addConstantNull(closedWorld));
return;
}
List<HInstruction> inputs = <HInstruction>[];
for (ir.Expression argument in arguments.skip(2)) {
argument.accept(this);
inputs.add(pop());
}
NativeBehavior nativeBehavior =
_elementMap.getNativeBehaviorForJsBuiltinCall(invocation);
assert(
nativeBehavior != null,
failedAt(_elementMap.getSpannable(targetElement, invocation),
"No NativeBehavior for $invocation"));
AbstractValue ssaType =
_typeInferenceMap.typeFromNativeBehavior(nativeBehavior, closedWorld);
push(new HForeignCode(template, ssaType, inputs,
nativeBehavior: nativeBehavior));
}
/// Returns the [js.Template] for the `JsBuiltin` [constant] value.
js.Template _getJsBuiltinTemplate(
ConstantValue constant, ModularEmitter emitter) {
int index = _extractEnumIndexFromConstantValue(
constant, _commonElements.jsBuiltinEnum);
if (index == null) return null;
return _templateForBuiltin(JsBuiltin.values[index]);
}
/// Returns the JS template for the given [builtin].
js.Template _templateForBuiltin(JsBuiltin builtin) {
switch (builtin) {
case JsBuiltin.dartObjectConstructor:
ClassEntity objectClass = closedWorld.commonElements.objectClass;
return js.js
.expressionTemplateYielding(_emitter.typeAccess(objectClass));
case JsBuiltin.dartClosureConstructor:
ClassEntity closureClass = closedWorld.commonElements.closureClass;
// TODO(sra): Should add a dependency on the constructor used as a
// token.
registry
// ignore:deprecated_member_use_from_same_package
.registerInstantiatedClass(closureClass);
return js.js
.expressionTemplateYielding(_emitter.typeAccess(closureClass));
case JsBuiltin.isCheckPropertyToJsConstructorName:
int isPrefixLength = _namer.fixedNames.operatorIsPrefix.length;
return js.js.expressionTemplateFor('#.substring($isPrefixLength)');
case JsBuiltin.isFunctionType:
return _rtiEncoder.templateForIsFunctionType;
case JsBuiltin.isFutureOrType:
return _rtiEncoder.templateForIsFutureOrType;
case JsBuiltin.isVoidType:
return _rtiEncoder.templateForIsVoidType;
case JsBuiltin.isDynamicType:
return _rtiEncoder.templateForIsDynamicType;
case JsBuiltin.isJsInteropTypeArgument:
return _rtiEncoder.templateForIsJsInteropTypeArgument;
case JsBuiltin.rawRtiToJsConstructorName:
return js.js.expressionTemplateFor("#.name");
case JsBuiltin.rawRuntimeType:
return js.js.expressionTemplateFor("#.constructor");
case JsBuiltin.isSubtype:
// TODO(floitsch): move this closer to where is-check properties are
// built.
String isPrefix = _namer.fixedNames.operatorIsPrefix;
return js.js.expressionTemplateFor("('$isPrefix' + #) in #.prototype");
case JsBuiltin.isGivenTypeRti:
return js.js.expressionTemplateFor('#.name === #');
case JsBuiltin.getMetadata:
String metadataAccess =
_emitter.generateEmbeddedGlobalAccessString(METADATA);
return js.js.expressionTemplateFor("$metadataAccess[#]");
case JsBuiltin.getType:
String typesAccess = _emitter.generateEmbeddedGlobalAccessString(TYPES);
return js.js.expressionTemplateFor("$typesAccess[#]");
default:
reporter.internalError(
NO_LOCATION_SPANNABLE, "Unhandled Builtin: $builtin");
return null;
}
}
void _handleForeignJsGetFlag(ir.StaticInvocation invocation) {
if (_unexpectedForeignArguments(invocation,
minPositional: 1, maxPositional: 1)) {
stack.add(
// Result expected on stack.
graph.addConstantBool(false, closedWorld));
return;
}
String name = _foreignConstantStringArgument(invocation, 0, 'JS_GET_FLAG');
bool value = _getFlagValue(name);
if (value == null) {
reporter.reportErrorMessage(
_elementMap.getSpannable(targetElement, invocation),
MessageKind.GENERIC,
{'text': 'Error: Unknown internal flag "$name".'});
} else {
stack.add(graph.addConstantBool(value, closedWorld));
}
}
void _handleJsInterceptorConstant(ir.StaticInvocation invocation) {
// Single argument must be a TypeConstant which is converted into a
// InterceptorConstant.
if (_unexpectedForeignArguments(invocation,
minPositional: 1, maxPositional: 1)) {
// Result expected on stack.
stack.add(graph.addConstantNull(closedWorld));
return;
}
ir.Expression argument = invocation.arguments.positional.single;
argument.accept(this);
HInstruction argumentInstruction = pop();
if (argumentInstruction is HConstant) {
ConstantValue argumentConstant = argumentInstruction.constant;
if (argumentConstant is TypeConstantValue &&
argumentConstant.representedType is InterfaceType) {
InterfaceType type = argumentConstant.representedType;
// TODO(sra): Check that type is a subclass of [Interceptor].
ConstantValue constant = new InterceptorConstantValue(type.element);
HInstruction instruction = graph.addConstant(constant, closedWorld);
stack.add(instruction);
return;
}
}
reporter.reportErrorMessage(
_elementMap.getSpannable(targetElement, invocation),
MessageKind.WRONG_ARGUMENT_FOR_JS_INTERCEPTOR_CONSTANT);
stack.add(graph.addConstantNull(closedWorld));
}
void _handleForeignGetInterceptor(ir.StaticInvocation invocation) {
// Single argument is the intercepted object.
if (_unexpectedForeignArguments(invocation,
minPositional: 1, maxPositional: 1)) {
// Result expected on stack.
stack.add(graph.addConstantNull(closedWorld));
return;
}
ir.Expression argument = invocation.arguments.positional.single;
argument.accept(this);
HInstruction argumentInstruction = pop();
SourceInformation sourceInformation =
_sourceInformationBuilder.buildCall(invocation, invocation);
HInstruction instruction =
_interceptorFor(argumentInstruction, sourceInformation);
stack.add(instruction);
}
void _handleForeignGetJSArrayInteropRti(ir.StaticInvocation invocation) {
if (_unexpectedForeignArguments(invocation,
minPositional: 0, maxPositional: 0) ||
!options.experimentNewRti) {
// Result expected on stack.
stack.add(graph.addConstantNull(closedWorld));
return;
}
// TODO(sra): Introduce 'any' type.
InterfaceType interopType =
InterfaceType(_commonElements.jsArrayClass, [DynamicType()]);
SourceInformation sourceInformation =
_sourceInformationBuilder.buildCall(invocation, invocation);
HInstruction rti = HLoadType(interopType, _abstractValueDomain.dynamicType)
..sourceInformation = sourceInformation;
push(rti);
}
void _handleForeignJs(ir.StaticInvocation invocation) {
if (_unexpectedForeignArguments(invocation,
minPositional: 2, maxPositional: null, typeArgumentCount: 1)) {
// Result expected on stack.
stack.add(graph.addConstantNull(closedWorld));
return;
}
NativeBehavior nativeBehavior =
_elementMap.getNativeBehaviorForJsCall(invocation);
assert(
nativeBehavior != null,
failedAt(_elementMap.getSpannable(targetElement, invocation),
"No NativeBehavior for $invocation"));
List<HInstruction> inputs = <HInstruction>[];
for (ir.Expression argument in invocation.arguments.positional.skip(2)) {
argument.accept(this);
inputs.add(pop());
}
if (nativeBehavior.codeTemplate.positionalArgumentCount != inputs.length) {
reporter.reportErrorMessage(
_elementMap.getSpannable(targetElement, invocation),
MessageKind.GENERIC, {
'text': 'Mismatch between number of placeholders'
' and number of arguments.'
});
// Result expected on stack.
stack.add(graph.addConstantNull(closedWorld));
return;
}
if (HasCapturedPlaceholders.check(nativeBehavior.codeTemplate.ast)) {
reporter.reportErrorMessage(
_elementMap.getSpannable(targetElement, invocation),
MessageKind.JS_PLACEHOLDER_CAPTURE);
}
AbstractValue ssaType =
_typeInferenceMap.typeFromNativeBehavior(nativeBehavior, closedWorld);
SourceInformation sourceInformation = null;
HInstruction code = new HForeignCode(
nativeBehavior.codeTemplate, ssaType, inputs,
isStatement: !nativeBehavior.codeTemplate.isExpression,
effects: nativeBehavior.sideEffects,
nativeBehavior: nativeBehavior)
..sourceInformation = sourceInformation;
push(code);
DartType type = _getDartTypeIfValid(invocation.arguments.types.single);
AbstractValue trustedMask = _typeBuilder.trustTypeMask(type);
if (trustedMask != null) {
// We only allow the type argument to narrow `dynamic`, which probably
// comes from an unspecified return type in the NativeBehavior.
if (_abstractValueDomain
.containsAll(code.instructionType)
.isPotentiallyTrue) {
// Overwrite the type with the narrower type.
code.instructionType = trustedMask;
} else if (_abstractValueDomain
.contains(trustedMask, code.instructionType)
.isPotentiallyTrue) {
// It is acceptable for the type parameter to be broader than the
// specified type.
} else {
reporter.reportErrorMessage(
_elementMap.getSpannable(targetElement, invocation),
MessageKind.GENERIC, {
'text': 'Type argument too narrow for specified behavior type '
'(${trustedMask} does not allow '
'all values in ${code.instructionType})'
});
}
}
}
void _handleJsStringConcat(ir.StaticInvocation invocation) {
if (_unexpectedForeignArguments(invocation,
minPositional: 2, maxPositional: 2)) {
// Result expected on stack.
stack.add(graph.addConstantNull(closedWorld));
return;
}
List<HInstruction> inputs = _visitPositionalArguments(invocation.arguments);
push(new HStringConcat(
inputs[0], inputs[1], _abstractValueDomain.stringType));
}
void _pushStaticInvocation(MemberEntity target, List<HInstruction> arguments,
AbstractValue typeMask, List<DartType> typeArguments,
{SourceInformation sourceInformation, InterfaceType instanceType}) {
// TODO(redemption): Pass current node if needed.
if (_tryInlineMethod(
target, null, null, arguments, typeArguments, null, sourceInformation,
instanceType: instanceType)) {
return;
}
var instruction;
if (closedWorld.nativeData.isJsInteropMember(target)) {
instruction = _invokeJsInteropFunction(target, arguments);
} else {
instruction = new HInvokeStatic(
target, arguments, typeMask, typeArguments,
targetCanThrow: !_inferredData.getCannotThrow(target))
..sourceInformation = sourceInformation;
if (_currentImplicitInstantiations.isNotEmpty) {
instruction.instantiatedTypes =
new List<InterfaceType>.from(_currentImplicitInstantiations);
}
instruction.sideEffects = _inferredData.getSideEffectsOfElement(target);
}
push(instruction);
}
void _pushDynamicInvocation(
ir.Node node,
StaticType staticReceiverType,
AbstractValue receiverType,
Selector selector,
List<HInstruction> arguments,
List<DartType> typeArguments,
SourceInformation sourceInformation) {
AbstractValue typeBound = _abstractValueDomain
.createFromStaticType(staticReceiverType.type,
classRelation: staticReceiverType.relation, nullable: true)
.abstractValue;
receiverType = receiverType == null
? typeBound
: _abstractValueDomain.intersection(receiverType, typeBound);
// We prefer to not inline certain operations on indexables,
// because the constant folder will handle them better and turn
// them into simpler instructions that allow further
// optimizations.
bool isOptimizableOperationOnIndexable(
Selector selector, MemberEntity element) {
bool isLength = selector.isGetter && selector.name == "length";
if (isLength || selector.isIndex) {
return closedWorld.classHierarchy.isSubtypeOf(
element.enclosingClass, _commonElements.jsIndexableClass);
} else if (selector.isIndexSet) {
return closedWorld.classHierarchy.isSubtypeOf(
element.enclosingClass, _commonElements.jsMutableIndexableClass);
} else {
return false;
}
}
bool isOptimizableOperation(Selector selector, MemberEntity element) {
ClassEntity cls = element.enclosingClass;
if (isOptimizableOperationOnIndexable(selector, element)) return true;
if (!_interceptorData.interceptedClasses.contains(cls)) return false;
if (selector.isOperator) return true;
if (selector.isSetter) return true;
if (selector.isIndex) return true;
if (selector.isIndexSet) return true;
if (element == _commonElements.jsArrayAdd ||
element == _commonElements.jsArrayRemoveLast ||
_commonElements.isJsStringSplit(element)) {
return true;
}
return false;
}
MemberEntity element =
closedWorld.locateSingleMember(selector, receiverType);
if (element != null &&
!element.isField &&
!(element.isGetter && selector.isCall) &&
!(element.isFunction && selector.isGetter) &&
!isOptimizableOperation(selector, element)) {
if (_tryInlineMethod(element, selector, receiverType, arguments,
typeArguments, node, sourceInformation)) {
return;
}
}
HInstruction receiver = arguments.first;
List<HInstruction> inputs = <HInstruction>[];
selector ??= _elementMap.getSelector(node);
bool isIntercepted =
closedWorld.interceptorData.isInterceptedSelector(selector);
if (isIntercepted) {
HInterceptor interceptor = _interceptorFor(receiver, sourceInformation);
inputs.add(interceptor);
}
inputs.addAll(arguments);
AbstractValue resultType =
_typeInferenceMap.resultTypeOfSelector(selector, receiverType);
if (selector.isGetter) {
push(new HInvokeDynamicGetter(selector, receiverType, element, inputs,
isIntercepted, resultType, sourceInformation));
} else if (selector.isSetter) {
push(new HInvokeDynamicSetter(selector, receiverType, element, inputs,
isIntercepted, resultType, sourceInformation));
} else if (selector.isClosureCall) {
assert(!isIntercepted);
push(new HInvokeClosure(
selector, receiverType, inputs, resultType, typeArguments)
..sourceInformation = sourceInformation);
} else {
push(new HInvokeDynamicMethod(selector, receiverType, inputs, resultType,
typeArguments, sourceInformation,
isIntercepted: isIntercepted));
}
}
HForeignCode _invokeJsInteropFunction(
FunctionEntity element, List<HInstruction> arguments) {
assert(closedWorld.nativeData.isJsInteropMember(element));
registry.registerNativeMethod(element);
if (element is ConstructorEntity &&
element.isFactoryConstructor &&
_nativeData.isAnonymousJsInteropClass(element.enclosingClass)) {
// Factory constructor that is syntactic sugar for creating a JavaScript
// object literal.
ConstructorEntity constructor = element;
int i = 0;
int positions = 0;
var filteredArguments = <HInstruction>[];
var parameterNameMap = new Map<String, js.Expression>();
// Note: we don't use `constructor.parameterStructure` here because
// we don't elide parameters to js-interop external static targets
// (including factory constructors.)
// TODO(johnniwinther): can we elide those parameters? This should be
// consistent with what we do with instance methods.
ir.Procedure node = _elementMap.getMemberDefinition(constructor).node;
List<ir.VariableDeclaration> namedParameters =
node.function.namedParameters.toList();
namedParameters.sort(nativeOrdering);
for (ir.VariableDeclaration variable in namedParameters) {
String parameterName = variable.name;
// TODO(jacobr): consider throwing if parameter names do not match
// names of properties in the class.
HInstruction argument = arguments[i];
if (argument != null) {
filteredArguments.add(argument);
var jsName = _nativeData.computeUnescapedJSInteropName(parameterName);
parameterNameMap[jsName] = new js.InterpolatedExpression(positions++);
}
i++;
}
var codeTemplate =
new js.Template(null, js.objectLiteral(parameterNameMap));
var nativeBehavior = new NativeBehavior()..codeTemplate = codeTemplate;
if (options.trustJSInteropTypeAnnotations) {
InterfaceType thisType =
_elementEnvironment.getThisType(constructor.enclosingClass);
nativeBehavior.typesReturned.add(thisType);
}
// TODO(efortuna): Source information.
return new HForeignCode(
codeTemplate, _abstractValueDomain.dynamicType, filteredArguments,
nativeBehavior: nativeBehavior);
}
var target = new HForeignCode(
js.js
.parseForeignJS("${_nativeData.getFixedBackendMethodPath(element)}."
"${_nativeData.getFixedBackendName(element)}"),
_abstractValueDomain.dynamicType,
<HInstruction>[]);
add(target);
// Strip off trailing arguments that were not specified.
// we could assert that the trailing arguments are all null.
// TODO(jacobr): rewrite named arguments to an object literal matching
// the factory constructor case.
arguments = arguments.where((arg) => arg != null).toList();
var inputs = <HInstruction>[target]..addAll(arguments);
var nativeBehavior = new NativeBehavior()..sideEffects.setAllSideEffects();
DartType type = element is ConstructorEntity
? _elementEnvironment.getThisType(element.enclosingClass)
: _elementEnvironment.getFunctionType(element).returnType;
// Native behavior effects here are similar to native/behavior.dart.
// The return type is dynamic if we don't trust js-interop type
// declarations.
nativeBehavior.typesReturned.add(
options.trustJSInteropTypeAnnotations ? type : const DynamicType());
// The allocation effects include the declared type if it is native (which
// includes js interop types).
if (type is InterfaceType && _nativeData.isNativeClass(type.element)) {
nativeBehavior.typesInstantiated.add(type);
}
// It also includes any other JS interop type if we don't trust the
// annotation or if is declared too broad.
if (!options.trustJSInteropTypeAnnotations ||
type == _commonElements.objectType ||
type is DynamicType) {
nativeBehavior.typesInstantiated.add(_elementEnvironment
.getThisType(_commonElements.jsJavaScriptObjectClass));
}
String template;
if (element.isGetter) {
template = '#';
} else if (element.isSetter) {
template = '# = #';
} else {
var args = new List.filled(arguments.length, '#').join(',');
template = element.isConstructor ? "new #($args)" : "#($args)";
}
js.Template codeTemplate = js.js.parseForeignJS(template);
nativeBehavior.codeTemplate = codeTemplate;
// TODO(efortuna): Add source information.
return new HForeignCode(
codeTemplate, _abstractValueDomain.dynamicType, inputs,
nativeBehavior: nativeBehavior);
}
@override
void visitFunctionNode(ir.FunctionNode node) {
SourceInformation sourceInformation =
_sourceInformationBuilder.buildCreate(node);
ClosureRepresentationInfo closureInfo =
_closureDataLookup.getClosureInfo(node.parent);
ClassEntity closureClassEntity = closureInfo.closureClassEntity;
List<HInstruction> capturedVariables = <HInstruction>[];
_elementEnvironment.forEachInstanceField(closureClassEntity,
(_, FieldEntity field) {
if (_fieldAnalysis.getFieldData(field).isElided) return;
capturedVariables
.add(localsHandler.readLocal(closureInfo.getLocalForField(field)));
});
AbstractValue type =
_abstractValueDomain.createNonNullExact(closureClassEntity);
// TODO(efortuna): Add source information here.
push(new HCreate(
closureClassEntity, capturedVariables, type, sourceInformation,
callMethod: closureInfo.callMethod));
}
@override
void visitFunctionDeclaration(ir.FunctionDeclaration declaration) {
assert(_isReachable);
declaration.function.accept(this);
Local local = _localsMap.getLocalVariable(declaration.variable);
localsHandler.updateLocal(local, pop());
}
@override
void visitFunctionExpression(ir.FunctionExpression funcExpression) {
funcExpression.function.accept(this);
}
@override
void visitInstantiation(ir.Instantiation node) {
var arguments = <HInstruction>[];
node.expression.accept(this);
arguments.add(pop());
StaticType expressionType = _getStaticType(node.expression);
bool typeArgumentsNeeded = _rtiNeed.instantiationNeedsTypeArguments(
expressionType.type, node.typeArguments.length);
List<DartType> typeArguments = node.typeArguments
.map((type) => typeArgumentsNeeded
? _elementMap.getDartType(type)
: _commonElements.dynamicType)
.toList();
registry.registerGenericInstantiation(
new GenericInstantiation(expressionType.type, typeArguments));
// TODO(johnniwinther): Can we avoid creating the instantiation object?
for (DartType type in typeArguments) {
HInstruction instruction =
_typeBuilder.analyzeTypeArgument(type, sourceElement);
arguments.add(instruction);
}
int typeArgumentCount = node.typeArguments.length;
bool targetCanThrow = false; // TODO(sra): Is this true?
FunctionEntity target =
_commonElements.getInstantiateFunction(typeArgumentCount);
if (target == null) {
reporter.internalError(
_elementMap.getSpannable(targetElement, node),
'Generic function instantiation not implemented for '
'${typeArgumentCount} type arguments');
stack.add(graph.addConstantNull(closedWorld));
return;
}
HInstruction instruction = new HInvokeStatic(
target, arguments, _abstractValueDomain.functionType, <DartType>[],
targetCanThrow: targetCanThrow);
// TODO(sra): ..sourceInformation = sourceInformation
instruction.sideEffects
..clearAllDependencies()
..clearAllSideEffects();
push(instruction);
}
@override
void visitMethodInvocation(ir.MethodInvocation node) {
node.receiver.accept(this);
HInstruction receiver = pop();
Selector selector = _elementMap.getSelector(node);
List<DartType> typeArguments = <DartType>[];
selector =
_fillDynamicTypeArguments(selector, node.arguments, typeArguments);
_pushDynamicInvocation(
node,
_getStaticType(node.receiver),
_typeInferenceMap.receiverTypeOfInvocation(node, _abstractValueDomain),
selector,
<HInstruction>[receiver]..addAll(_visitArgumentsForDynamicTarget(
selector, node.arguments, typeArguments)),
typeArguments,
_sourceInformationBuilder.buildCall(node.receiver, node));
}
HInterceptor _interceptorFor(
HInstruction intercepted, SourceInformation sourceInformation) {
HInterceptor interceptor =
new HInterceptor(intercepted, _abstractValueDomain.nonNullType)
..sourceInformation = sourceInformation;
add(interceptor);
return interceptor;
}
static ir.Class _containingClass(ir.TreeNode node) {
while (node != null) {
if (node is ir.Class) return node;
node = node.parent;
}
return null;
}
void _generateSuperNoSuchMethod(
ir.Expression invocation,
String publicName,
List<HInstruction> arguments,
List<DartType> typeArguments,
SourceInformation sourceInformation) {
Selector selector = _elementMap.getSelector(invocation);
ClassEntity containingClass =
_elementMap.getClass(_containingClass(invocation));
FunctionEntity noSuchMethod =
_elementMap.getSuperNoSuchMethod(containingClass);
ConstantValue nameConstant = constant_system.createString(publicName);
js.Name internalName = _namer.invocationName(selector);
var argumentsInstruction = _buildLiteralList(arguments);
add(argumentsInstruction);
var argumentNames = new List<HInstruction>();
for (String argumentName in selector.namedArguments) {
ConstantValue argumentNameConstant =
constant_system.createString(argumentName);
argumentNames.add(graph.addConstant(argumentNameConstant, closedWorld));
}
var argumentNamesInstruction = _buildLiteralList(argumentNames);
add(argumentNamesInstruction);
ConstantValue kindConstant =
constant_system.createIntFromInt(selector.invocationMirrorKind);
_pushStaticInvocation(
_commonElements.createInvocationMirror,
[
graph.addConstant(nameConstant, closedWorld),
graph.addConstantStringFromName(internalName, closedWorld),
graph.addConstant(kindConstant, closedWorld),
argumentsInstruction,
argumentNamesInstruction,
graph.addConstantInt(typeArguments.length, closedWorld),
],
_abstractValueDomain.dynamicType,
typeArguments,
sourceInformation: sourceInformation);
_buildInvokeSuper(Selectors.noSuchMethod_, containingClass, noSuchMethod,
<HInstruction>[pop()], typeArguments, sourceInformation);
}
HInstruction _buildInvokeSuper(
Selector selector,
ClassEntity containingClass,
MemberEntity target,
List<HInstruction> arguments,
List<DartType> typeArguments,
SourceInformation sourceInformation) {
HInstruction receiver =
localsHandler.readThis(sourceInformation: sourceInformation);
List<HInstruction> inputs = <HInstruction>[];
bool isIntercepted =
closedWorld.interceptorData.isInterceptedSelector(selector);
if (isIntercepted) {
inputs.add(_interceptorFor(receiver, sourceInformation));
}
inputs.add(receiver);
inputs.addAll(arguments);
AbstractValue typeMask;
if (target is FunctionEntity) {
typeMask = _typeInferenceMap.getReturnTypeOf(target);
} else {
typeMask = _abstractValueDomain.dynamicType;
}
HInstruction instruction = new HInvokeSuper(
target,
containingClass,
selector,
inputs,
isIntercepted,
typeMask,
typeArguments,
sourceInformation,
isSetter: selector.isSetter || selector.isIndexSet);
instruction.sideEffects =
_inferredData.getSideEffectsOfSelector(selector, null);
push(instruction);
return instruction;
}
@override
void visitSuperPropertyGet(ir.SuperPropertyGet node) {
SourceInformation sourceInformation =
_sourceInformationBuilder.buildGet(node);
MemberEntity member =
_elementMap.getSuperMember(_currentFrame.member, node.name);
if (member == null) {
_generateSuperNoSuchMethod(node, _elementMap.getSelector(node).name,
const <HInstruction>[], const <DartType>[], sourceInformation);
return;
}
if (member.isField) {
FieldAnalysisData fieldData = _fieldAnalysis.getFieldData(member);
if (fieldData.isEffectivelyConstant) {
ConstantValue value = fieldData.constantValue;
stack.add(graph.addConstant(value, closedWorld,
sourceInformation: sourceInformation));
return;
}
}
_buildInvokeSuper(
_elementMap.getSelector(node),
_elementMap.getClass(_containingClass(node)),
member,
const <HInstruction>[],
const <DartType>[],
sourceInformation);
}
@override
void visitSuperMethodInvocation(ir.SuperMethodInvocation node) {
SourceInformation sourceInformation =
_sourceInformationBuilder.buildCall(node, node);
MemberEntity member =
_elementMap.getSuperMember(_currentFrame.member, node.name);
if (member == null) {
Selector selector = _elementMap.getSelector(node);
List<DartType> typeArguments = <DartType>[];
selector =
_fillDynamicTypeArguments(selector, node.arguments, typeArguments);
List<HInstruction> arguments = _visitArgumentsForDynamicTarget(
selector, node.arguments, typeArguments);
_generateSuperNoSuchMethod(
node, selector.name, arguments, typeArguments, sourceInformation);
return;
}
List<DartType> typeArguments =
_getStaticTypeArguments(member, node.arguments);
MemberDefinition targetDefinition = _elementMap.getMemberDefinition(member);
ir.Procedure target = targetDefinition.node;
FunctionEntity function = member;
List<HInstruction> arguments = _visitArgumentsForStaticTarget(
target.function,
function.parameterStructure,
node.arguments,
typeArguments,
sourceInformation);
_buildInvokeSuper(
_elementMap.getSelector(node),
_elementMap.getClass(_containingClass(node)),
member,
arguments,
typeArguments,
sourceInformation);
}
void _assertIsType(HInstruction subtypeInstruction, DartType supertype,
String prefix, String infix, String suffix) {
HInstruction supertypeInstruction = _typeBuilder.analyzeTypeArgument(
localsHandler.substInContext(supertype), sourceElement);
HInstruction prefixInstruction =
graph.addConstantString(prefix, closedWorld);
HInstruction infixInstruction = graph.addConstantString(infix, closedWorld);
HInstruction suffixInstruction =
graph.addConstantString(suffix, closedWorld);
FunctionEntity element = _commonElements.assertIsSubtype;
var inputs = <HInstruction>[
subtypeInstruction,
supertypeInstruction,
prefixInstruction,
infixInstruction,
suffixInstruction
];
HInstruction assertIsSubtype = new HInvokeStatic(element, inputs,
subtypeInstruction.instructionType, const <DartType>[]);
add(assertIsSubtype);
}
void _checkTypeBound(
HInstruction typeInstruction, DartType bound, String variableName) {
HInstruction boundInstruction = _typeBuilder.analyzeTypeArgumentNewRti(
localsHandler.substInContext(bound), sourceElement);
HInstruction variableNameInstruction =
graph.addConstantString(variableName, closedWorld);
FunctionEntity element = _commonElements.checkTypeBound;
var inputs = <HInstruction>[
typeInstruction,
boundInstruction,
variableNameInstruction
];
HInstruction checkBound = new HInvokeStatic(
element, inputs, typeInstruction.instructionType, const <DartType>[]);
add(checkBound);
}
@override
void visitConstructorInvocation(ir.ConstructorInvocation node) {
SourceInformation sourceInformation =
_sourceInformationBuilder.buildNew(node);
ir.Constructor target = node.target;
if (node.isConst) {
ConstantValue constant = _elementMap.getConstantValue(node);
stack.add(graph.addConstant(constant, closedWorld,
sourceInformation: sourceInformation));
return;
}
ConstructorEntity constructor = _elementMap.getConstructor(target);
ClassEntity cls = constructor.enclosingClass;
AbstractValue typeMask = _abstractValueDomain.createNonNullExact(cls);
InterfaceType instanceType = _elementMap.createInterfaceType(
target.enclosingClass, node.arguments.types);
instanceType = localsHandler.substInContext(instanceType);
List<HInstruction> arguments = <HInstruction>[];
if (constructor.isGenerativeConstructor &&
_nativeData.isNativeOrExtendsNative(constructor.enclosingClass) &&
!_nativeData.isJsInteropMember(constructor)) {
// Native class generative constructors take a pre-constructed object.
arguments.add(graph.addConstantNull(closedWorld));
}
List<DartType> typeArguments =
_getConstructorTypeArguments(constructor, node.arguments);
arguments.addAll(closedWorld.nativeData.isJsInteropMember(constructor)
? _visitArgumentsForNativeStaticTarget(target.function, node.arguments)
: _visitArgumentsForStaticTarget(
target.function,
constructor.parameterStructure,
node.arguments,
typeArguments,
sourceInformation));
if (_commonElements.isSymbolConstructor(constructor)) {
constructor = _commonElements.symbolValidatedConstructor;
}
// TODO(johnniwinther): Remove this when type arguments are passed to
// constructors like calling a generic method.
_addTypeArguments(arguments, _getClassTypeArguments(cls, node.arguments),
sourceInformation);
_addImplicitInstantiation(instanceType);
_pushStaticInvocation(constructor, arguments, typeMask, typeArguments,
sourceInformation: sourceInformation, instanceType: instanceType);
_removeImplicitInstantiation(instanceType);
}
@override
void visitIsExpression(ir.IsExpression node) {
node.operand.accept(this);
HInstruction expression = pop();
_pushIsTest(node.type, expression, _sourceInformationBuilder.buildIs(node));
}
void _pushIsTest(ir.DartType type, HInstruction expression,
SourceInformation sourceInformation) {
// Note: The call to "unalias" this type like in the original SSA builder is
// unnecessary in kernel because Kernel has no notion of typedef.
// TODO(efortuna): Add test for this.
if (type is ir.InvalidType) {
// TODO(sra): Make InvalidType carry a message.
_generateTypeError('invalid type', sourceInformation);
pop();
stack.add(graph.addConstantBool(true, closedWorld));
return;
}
DartType typeValue =
localsHandler.substInContext(_elementMap.getDartType(type));
if (typeValue.treatAsDynamic) {
stack.add(graph.addConstantBool(true, closedWorld));
return;
}
if (options.experimentNewRti) {
HInstruction rti =
_typeBuilder.analyzeTypeArgumentNewRti(typeValue, sourceElement);
AbstractValueWithPrecision checkedType =
_abstractValueDomain.createFromStaticType(typeValue, nullable: false);
push(HIsTest(typeValue, checkedType, expression, rti,
_abstractValueDomain.boolType));
return;
}
if (typeValue is FunctionType) {
HInstruction representation =
_typeBuilder.analyzeTypeArgument(typeValue, sourceElement);
List<HInstruction> inputs = <HInstruction>[
expression,
representation,
];
_pushStaticInvocation(_commonElements.functionTypeTest, inputs,
_abstractValueDomain.boolType, const <DartType>[],
sourceInformation: sourceInformation);
HInstruction call = pop();
push(new HIs.compound(typeValue, expression, call,
_abstractValueDomain.boolType, sourceInformation));
return;
}
if (typeValue is FutureOrType) {
HInstruction representation =
_typeBuilder.analyzeTypeArgument(typeValue, sourceElement);
List<HInstruction> inputs = <HInstruction>[
expression,
representation,
];
_pushStaticInvocation(_commonElements.futureOrTest, inputs,
_abstractValueDomain.boolType, const <DartType>[],
sourceInformation: sourceInformation);
HInstruction call = pop();
push(new HIs.compound(typeValue, expression, call,
_abstractValueDomain.boolType, sourceInformation));
return;
}
if (typeValue is TypeVariableType) {
HInstruction runtimeType =
_typeBuilder.addTypeVariableReference(typeValue, sourceElement);
_pushStaticInvocation(
_commonElements.checkSubtypeOfRuntimeType,
<HInstruction>[expression, runtimeType],
_abstractValueDomain.boolType,
const <DartType>[],
sourceInformation: sourceInformation);
push(new HIs.variable(typeValue, expression, pop(),
_abstractValueDomain.boolType, sourceInformation));
return;
}
if (typeValue is InterfaceType && !_canIgnoreTypeArguments(typeValue)) {
HInstruction representations = _typeBuilder
.buildTypeArgumentRepresentations(typeValue, sourceElement);
add(representations);
ClassEntity element = typeValue.element;
js.Name operator = _namer.operatorIs(element);
HInstruction isFieldName =
graph.addConstantStringFromName(operator, closedWorld);
HInstruction asFieldName =
closedWorld.classHierarchy.hasAnyStrictSubtype(element) ||
closedWorld.nativeData.isJsInteropClass(element)
? graph.addConstantStringFromName(
_namer.substitutionName(element), closedWorld)
: graph.addConstantNull(closedWorld);
List<HInstruction> inputs = <HInstruction>[
expression,
isFieldName,
representations,
asFieldName
];
_pushStaticInvocation(_commonElements.checkSubtype, inputs,
_abstractValueDomain.boolType, const <DartType>[],
sourceInformation: sourceInformation);
push(new HIs.compound(typeValue, expression, pop(),
_abstractValueDomain.boolType, sourceInformation));
return;
}
if (_hasDirectCheckFor(typeValue)) {
push(new HIs.direct(typeValue, expression, _abstractValueDomain.boolType,
sourceInformation));
return;
}
// The interceptor is not always needed. It is removed by optimization
// when the receiver type or tested type permit.
push(new HIs.raw(
typeValue,
expression,
_interceptorFor(expression, sourceInformation),
_abstractValueDomain.boolType,
sourceInformation));
return;
}
/// Returns `true` if the checking of [type] is performed directly on the
/// object and not on an interceptor.
bool _hasDirectCheckFor(DartType type) {
if (!type.isInterfaceType) return false;
InterfaceType interfaceType = type;
ClassEntity element = interfaceType.element;
return element == _commonElements.stringClass ||
element == _commonElements.boolClass ||
element == _commonElements.numClass ||
element == _commonElements.intClass ||
element == _commonElements.doubleClass ||
element == _commonElements.jsArrayClass ||
element == _commonElements.jsMutableArrayClass ||
element == _commonElements.jsExtendableArrayClass ||
element == _commonElements.jsFixedArrayClass ||
element == _commonElements.jsUnmodifiableArrayClass;
}
/// Whether an is-check for [type] can be done ignoring type-arguments.
/// This will be true if [type] is raw, or all its type-arguments match the
/// type-parameter bounds.
bool _canIgnoreTypeArguments(InterfaceType type) {
InterfaceType thisType = _elementEnvironment.getThisType(type.element);
List<DartType> bounds = thisType.typeArguments;
for (int i = 0; i < bounds.length; i++) {
DartType arg = type.typeArguments[i];
if (arg.treatAsDynamic) continue;
TypeVariableType typeVariable = bounds[i];
DartType bound =
_elementEnvironment.getTypeVariableBound(typeVariable.element);
if (bound != arg) return false;
}
return true;
}
@override
void visitThrow(ir.Throw node) {
_visitThrowExpression(node.expression);
if (_isReachable) {
SourceInformation sourceInformation =
_sourceInformationBuilder.buildThrow(node);
_handleInTryStatement();
push(
new HThrowExpression(_abstractValueDomain, pop(), sourceInformation));
_isReachable = false;
}
}
void _visitThrowExpression(ir.Expression expression) {
bool old = _inExpressionOfThrow;
try {
_inExpressionOfThrow = true;
expression.accept(this);
} finally {
_inExpressionOfThrow = old;
}
}
@override
void visitYieldStatement(ir.YieldStatement node) {
node.expression.accept(this);
add(new HYield(_abstractValueDomain, pop(), node.isYieldStar,
_sourceInformationBuilder.buildYield(node)));
}
@override
void visitAwaitExpression(ir.AwaitExpression node) {
node.operand.accept(this);
HInstruction awaited = pop();
// TODO(herhut): Improve this type.
push(new HAwait(awaited, _abstractValueDomain.dynamicType)
..sourceInformation = _sourceInformationBuilder.buildAwait(node));
}
@override
void visitRethrow(ir.Rethrow node) {
HInstruction exception = _rethrowableException;
if (exception == null) {
exception = graph.addConstantNull(closedWorld);
reporter.internalError(_elementMap.getSpannable(targetElement, node),
'rethrowableException should not be null.');
}
_handleInTryStatement();
SourceInformation sourceInformation =
_sourceInformationBuilder.buildThrow(node);
_closeAndGotoExit(new HThrow(
_abstractValueDomain, exception, sourceInformation,
isRethrow: true));
// ir.Rethrow is an expression so we need to push a value - a constant with
// no type.
stack.add(graph.addConstantUnreachable(closedWorld));
}
@override
void visitThisExpression(ir.ThisExpression node) {
stack.add(localsHandler.readThis(
sourceInformation: _sourceInformationBuilder.buildGet(node)));
}
@override
void visitNot(ir.Not node) {
node.operand.accept(this);
push(new HNot(popBoolified(), _abstractValueDomain.boolType)
..sourceInformation = _sourceInformationBuilder.buildUnary(node));
}
@override
void visitStringConcatenation(ir.StringConcatenation stringConcat) {
KernelStringBuilder stringBuilder = new KernelStringBuilder(this);
stringConcat.accept(stringBuilder);
stack.add(stringBuilder.result);
}
@override
void visitTryCatch(ir.TryCatch node) {
TryCatchFinallyBuilder tryBuilder = new TryCatchFinallyBuilder(
this, _sourceInformationBuilder.buildTry(node));
node.body.accept(this);
tryBuilder
..closeTryBody()
..buildCatch(node)
..cleanUp();
}
/// `try { ... } catch { ... } finally { ... }` statements are a little funny
/// because a try can have one or both of {catch|finally}. The way this is
/// encoded in kernel AST are two separate classes with no common superclass
/// aside from Statement. If a statement has both `catch` and `finally`
/// clauses then it is encoded in kernel as so that the TryCatch is the body
/// statement of the TryFinally. To produce more efficient code rather than
/// nested try statements, the visitors avoid one potential level of
/// recursion.
@override
void visitTryFinally(ir.TryFinally node) {
TryCatchFinallyBuilder tryBuilder = new TryCatchFinallyBuilder(
this, _sourceInformationBuilder.buildTry(node));
// We do these shenanigans to produce better looking code that doesn't
// have nested try statements.
if (node.body is ir.TryCatch) {
ir.TryCatch tryCatch = node.body;
tryCatch.body.accept(this);
tryBuilder
..closeTryBody()
..buildCatch(tryCatch);
} else {
node.body.accept(this);
tryBuilder.closeTryBody();
}
tryBuilder
..buildFinallyBlock(() {
node.finalizer.accept(this);
})
..cleanUp();
}
/// Try to inline [element] within the correct context of the builder. The
/// insertion point is the state of the builder.
bool _tryInlineMethod(
FunctionEntity function,
Selector selector,
AbstractValue mask,
List<HInstruction> providedArguments,
List<DartType> typeArguments,
ir.Node currentNode,
SourceInformation sourceInformation,
{InterfaceType instanceType}) {
if (function.isExternal) {
// Don't inline external methods; these should just fail at runtime.
return false;
}
if (_nativeData.isJsInteropMember(function) &&
!(function is ConstructorEntity && function.isFactoryConstructor)) {
// We only inline factory JavaScript interop constructors.
return false;
}
bool insideLoop = loopDepth > 0 || graph.calledInLoop;
// Bail out early if the inlining decision is in the cache and we can't
// inline (no need to check the hard constraints).
if (_inlineCache.markedAsNoInline(function)) return false;
bool cachedCanBeInlined =
_inlineCache.canInline(function, insideLoop: insideLoop);
if (cachedCanBeInlined == false) return false;
bool meetsHardConstraints() {
if (options.disableInlining) return false;
assert(
selector != null ||
function.isStatic ||
function.isTopLevel ||
function.isConstructor ||
function is ConstructorBodyEntity,
failedAt(function, "Missing selector for inlining of $function."));
if (selector != null) {
if (!selector.applies(function)) return false;
if (mask != null &&
_abstractValueDomain
.isTargetingMember(mask, function, selector.memberName)
.isDefinitelyFalse) {
return false;
}
}
if (_nativeData.isJsInteropMember(function)) return false;
// Don't inline operator== methods if the parameter can be null.
if (function.name == '==') {
if (function.enclosingClass != _commonElements.objectClass &&
providedArguments[1]
.isNull(_abstractValueDomain)
.isPotentiallyTrue) {
return false;
}
}
// Generative constructors of native classes should not be called directly
// and have an extra argument that causes problems with inlining.
if (function is ConstructorEntity &&
function.isGenerativeConstructor &&
_nativeData.isNativeOrExtendsNative(function.enclosingClass)) {
return false;
}
// A generative constructor body is not seen by global analysis,
// so we should not query for its type.
if (function is! ConstructorBodyEntity) {
if (globalInferenceResults.resultOfMember(function).throwsAlways) {
// TODO(johnniwinther): It seems wrong to set `isReachable` to `false`
// since we are _not_ going to inline [function]. This has
// implications in switch cases where we might need to insert a
// `break` that was skipped due to `isReachable` being `false`.
_isReachable = false;
return false;
}
}
return true;
}
bool doesNotContainCode(InlineData inlineData) {
// A function with size 1 does not contain any code.
return inlineData.canBeInlined(maxInliningNodes: 1);
}
bool reductiveHeuristic(InlineData inlineData) {
// The call is on a path which is executed rarely, so inline only if it
// does not make the program larger.
if (_isCalledOnce(function)) {
return inlineData.canBeInlined();
}
if (inlineData.canBeInlinedReductive(
argumentCount: providedArguments.length)) {
return true;
}
return doesNotContainCode(inlineData);
}
bool heuristicSayGoodToGo() {
// Don't inline recursively,
if (_inliningStack.any((entry) => entry.function == function)) {
return false;
}
// Don't inline across deferred import to prevent leaking code. The only
// exception is an empty function (which does not contain code).
bool hasOnlyNonDeferredImportPaths = closedWorld.outputUnitData
.hasOnlyNonDeferredImportPaths(_initialTargetElement, function);
InlineData inlineData =
_inlineDataCache.getInlineData(_elementMap, function);
if (!hasOnlyNonDeferredImportPaths) {
return doesNotContainCode(inlineData);
}
// Do not inline code that is rarely executed unless it reduces size.
if (_inExpressionOfThrow || _inLazyInitializerExpression) {
return reductiveHeuristic(inlineData);
}
if (cachedCanBeInlined == true) {
// We may have forced the inlining of some methods. Therefore check
// if we can inline this method regardless of size.
String reason;
assert(
(reason = inlineData.cannotBeInlinedReason(allowLoops: true)) ==
null,
failedAt(function, "Cannot inline $function: $reason"));
return true;
}
int numParameters = function.parameterStructure.totalParameters;
int maxInliningNodes;
if (insideLoop) {
maxInliningNodes = InlineWeeder.INLINING_NODES_INSIDE_LOOP +
InlineWeeder.INLINING_NODES_INSIDE_LOOP_ARG_FACTOR * numParameters;
} else {
maxInliningNodes = InlineWeeder.INLINING_NODES_OUTSIDE_LOOP +
InlineWeeder.INLINING_NODES_OUTSIDE_LOOP_ARG_FACTOR * numParameters;
}
bool markedTryInline = _inlineCache.markedAsTryInline(function);
bool calledOnce = _isCalledOnce(function);
// If a method is called only once, and all the methods in the inlining
// stack are called only once as well, we know we will save on output size
// by inlining this method.
if (markedTryInline || calledOnce) {
maxInliningNodes = null;
}
bool allowLoops = false;
if (markedTryInline) {
allowLoops = true;
}
bool canInline = inlineData.canBeInlined(
maxInliningNodes: maxInliningNodes, allowLoops: allowLoops);
if (markedTryInline) {
if (canInline) {
_inlineCache.markAsInlinable(function, insideLoop: true);
_inlineCache.markAsInlinable(function, insideLoop: false);
} else {
_inlineCache.markAsNonInlinable(function, insideLoop: true);
_inlineCache.markAsNonInlinable(function, insideLoop: false);
}
} else if (calledOnce) {
// TODO(34203): We can't update the decision due to imprecision in the
// calledOnce data, described in Issue 34203.
} else {
if (canInline) {
_inlineCache.markAsInlinable(function, insideLoop: insideLoop);
} else {
if (_isFunctionCalledOnce(function)) {
// TODO(34203): We can't update the decision due to imprecision in
// the calledOnce data, described in Issue 34203.
} else {
_inlineCache.markAsNonInlinable(function, insideLoop: insideLoop);
}
}
}
return canInline;
}
void doInlining() {
if (function.isConstructor) {
registry.registerStaticUse(
new StaticUse.constructorInlining(function, instanceType));
} else {
assert(instanceType == null,
"Unexpected instance type for $function: $instanceType");
registry.registerStaticUse(
new StaticUse.methodInlining(function, typeArguments));
}
// Add an explicit null check on the receiver before doing the
// inlining. We use [element] to get the same name in the
// NoSuchMethodError message as if we had called it.
if (function.isInstanceMember &&
function is! ConstructorBodyEntity &&
(mask == null ||
_abstractValueDomain.isNull(mask).isPotentiallyTrue)) {
add(new HFieldGet(null, providedArguments[0],
_abstractValueDomain.dynamicType, sourceInformation,
isAssignable: false));
}
List<HInstruction> compiledArguments = _completeCallArgumentsList(
function, selector, providedArguments, currentNode);
_enterInlinedMethod(function, compiledArguments, instanceType);
_inlinedFrom(function, sourceInformation, () {
if (!_isReachable) {
_emitReturn(graph.addConstantNull(closedWorld), sourceInformation);
} else {
_doInline(function);
}
});
_leaveInlinedMethod();
}
if (meetsHardConstraints() && heuristicSayGoodToGo()) {
doInlining();
_infoReporter?.reportInlined(
function,
_inliningStack.isEmpty
? targetElement
: _inliningStack.last.function);
return true;
}
return false;
}
/// Returns a complete argument list for a call of [function].
List<HInstruction> _completeCallArgumentsList(
FunctionEntity function,
Selector selector,
List<HInstruction> providedArguments,
ir.Node currentNode) {
assert(providedArguments != null);
bool isInstanceMember = function.isInstanceMember;
// For static calls, [providedArguments] is complete, default arguments
// have been included if necessary, see [makeStaticArgumentList].
if (!isInstanceMember ||
currentNode == null || // In erroneous code, currentNode can be null.
_providedArgumentsKnownToBeComplete(currentNode) ||
function is ConstructorBodyEntity ||
selector.isGetter) {
// For these cases, the provided argument list is known to be complete.
return providedArguments;
} else {
return _completeDynamicCallArgumentsList(
selector, function, providedArguments);
}
}
/// Returns a complete argument list for a dynamic call of [function]. The
/// initial argument list [providedArguments], created by
/// [addDynamicSendArgumentsToList], does not include values for default
/// arguments used in the call. The reason is that the target function (which
/// defines the defaults) is not known.
///
/// However, inlining can only be performed when the target function can be
/// resolved statically. The defaults can therefore be included at this point.
///
/// The [providedArguments] list contains first all positional arguments, then
/// the provided named arguments (the named arguments that are defined in the
/// [selector]) in a specific order (see [addDynamicSendArgumentsToList]).
List<HInstruction> _completeDynamicCallArgumentsList(Selector selector,
FunctionEntity function, List<HInstruction> providedArguments) {
assert(selector.applies(function));
CallStructure callStructure = selector.callStructure;
ParameterStructure parameterStructure = function.parameterStructure;
List<String> selectorArgumentNames =
selector.callStructure.getOrderedNamedArguments();
List<HInstruction> compiledArguments = new List<HInstruction>(
parameterStructure.totalParameters +
parameterStructure.typeParameters +
1); // Plus one for receiver.
int compiledArgumentIndex = 0;
// Copy receiver.
compiledArguments[compiledArgumentIndex++] = providedArguments[0];
/// Offset of positional arguments in [providedArguments].
int positionalArgumentOffset = 1;
/// Offset of named arguments in [providedArguments].
int namedArgumentOffset = callStructure.positionalArgumentCount + 1;
int positionalArgumentIndex = 0;
int namedArgumentIndex = 0;
_elementEnvironment.forEachParameter(function,
(DartType type, String name, ConstantValue defaultValue) {
if (positionalArgumentIndex < parameterStructure.positionalParameters) {
if (positionalArgumentIndex < callStructure.positionalArgumentCount) {
compiledArguments[compiledArgumentIndex++] = providedArguments[
positionalArgumentOffset + positionalArgumentIndex++];
} else {
assert(defaultValue != null,
failedAt(function, 'No constant computed for parameter $name'));
compiledArguments[compiledArgumentIndex++] =
graph.addConstant(defaultValue, closedWorld);
}
} else {
// Example:
// void foo(a, {b, d, c})
// foo(0, d = 1, b = 2)
//
// providedArguments = [0, 2, 1]
// selectorArgumentNames = [b, d]
// parameterStructure.namedParameters = [b, c, d]
//
// For each parameter name in the signature, if the argument name
// matches we use the next provided argument, otherwise we get the
// default.
if (namedArgumentIndex < selectorArgumentNames.length &&
name == selectorArgumentNames[namedArgumentIndex]) {
// The named argument was provided in the function invocation.
compiledArguments[compiledArgumentIndex++] =
providedArguments[namedArgumentOffset + namedArgumentIndex++];
} else {
assert(defaultValue != null,
failedAt(function, 'No constant computed for parameter $name'));
compiledArguments[compiledArgumentIndex++] =
graph.addConstant(defaultValue, closedWorld);
}
}
});
if (_rtiNeed.methodNeedsTypeArguments(function)) {
if (callStructure.typeArgumentCount ==
parameterStructure.typeParameters) {
/// Offset of type arguments in [providedArguments].
int typeArgumentOffset = callStructure.argumentCount + 1;
// Pass explicit type arguments.
for (int typeArgumentIndex = 0;
typeArgumentIndex < callStructure.typeArgumentCount;
typeArgumentIndex++) {
compiledArguments[compiledArgumentIndex++] =
providedArguments[typeArgumentOffset + typeArgumentIndex];
}
} else {
assert(callStructure.typeArgumentCount == 0);
// Pass type variable bounds as type arguments.
for (TypeVariableType typeVariable
in _elementEnvironment.getFunctionTypeVariables(function)) {
compiledArguments[compiledArgumentIndex++] =
_computeTypeArgumentDefaultValue(function, typeVariable);
}
}
}
return compiledArguments;
}
HInstruction _computeTypeArgumentDefaultValue(
FunctionEntity function, TypeVariableType typeVariable) {
DartType bound =
_elementEnvironment.getTypeVariableDefaultType(typeVariable.element);
if (bound.containsTypeVariables) {
// TODO(33422): Support type variables in default
// types. Temporarily using the "any" type (encoded as -2) to
// avoid failing on bounds checks.
return graph.addConstantInt(-2, closedWorld);
} else {
return _typeBuilder.analyzeTypeArgument(bound, function);
}
}
/// This method is invoked before inlining the body of [function] into this
/// [SsaGraphBuilder].
void _enterInlinedMethod(FunctionEntity function,
List<HInstruction> compiledArguments, InterfaceType instanceType) {
KernelInliningState state = new KernelInliningState(
function,
_returnLocal,
_returnType,
stack,
localsHandler,
_inTryStatement,
_isCalledOnce(function));
_inliningStack.add(state);
// Setting up the state of the (AST) builder is performed even when the
// inlined function is in IR, because the irInliner uses the [returnElement]
// of the AST builder.
_setupStateForInlining(function, compiledArguments, instanceType);
}
/// This method sets up the local state of the builder for inlining
/// [function]. The arguments of the function are inserted into the
/// [localsHandler].
///
/// When inlining a function, `return` statements are not emitted as
/// [HReturn] instructions. Instead, the value of a synthetic element is
/// updated in the [localsHandler]. This function creates such an element and
/// stores it in the [_returnLocal] field.
void _setupStateForInlining(FunctionEntity function,
List<HInstruction> compiledArguments, InterfaceType instanceType) {
localsHandler = new LocalsHandler(
this,
function,
function,
instanceType ?? _elementMap.getMemberThisType(function),
_nativeData,
_interceptorData);
localsHandler.scopeInfo = _closureDataLookup.getScopeInfo(function);
CapturedScope scopeData = _closureDataLookup.getCapturedScope(function);
bool forGenerativeConstructorBody = function is ConstructorBodyEntity;
_returnLocal = new SyntheticLocal("result", function, function);
localsHandler.updateLocal(_returnLocal, graph.addConstantNull(closedWorld));
_inTryStatement = false; // TODO(lry): why? Document.
int argumentIndex = 0;
if (function.isInstanceMember) {
localsHandler.updateLocal(localsHandler.scopeInfo.thisLocal,
compiledArguments[argumentIndex++]);
}
bool hasBox = false;
KernelToLocalsMap localsMap =
closedWorld.globalLocalsMap.getLocalsMap(function);
forEachOrderedParameter(_elementMap, function,
(ir.VariableDeclaration variable, {bool isElided}) {
Local local = localsMap.getLocalVariable(variable);
if (isElided) {
localsHandler.updateLocal(local, _defaultValueForParameter(variable));
return;
}
if (forGenerativeConstructorBody && scopeData.isBoxedVariable(local)) {
// The parameter will be a field in the box passed as the last
// parameter. So no need to have it.
hasBox = true;
return;
}
HInstruction argument = compiledArguments[argumentIndex++];
localsHandler.updateLocal(local, argument);
});
if (hasBox) {
HInstruction box = compiledArguments[argumentIndex++];
assert(box is HCreateBox);
// TODO(sra): Make inlining of closures work. We should always call
// enterScope, and pass in the inlined 'this' as well as the 'box'.
localsHandler.enterScope(scopeData, null,
inlinedBox: box,
forGenerativeConstructorBody: forGenerativeConstructorBody);
}
ClassEntity enclosing = function.enclosingClass;
if ((function.isConstructor || function is ConstructorBodyEntity) &&
_rtiNeed.classNeedsTypeArguments(enclosing)) {
InterfaceType thisType = _elementEnvironment.getThisType(enclosing);
thisType.typeArguments.forEach((_typeVariable) {
TypeVariableType typeVariable = _typeVariable;
HInstruction argument = compiledArguments[argumentIndex++];
localsHandler.updateLocal(
localsHandler.getTypeVariableAsLocal(typeVariable), argument);
});
}
if (_rtiNeed.methodNeedsTypeArguments(function)) {
bool inlineTypeParameters =
function.parameterStructure.typeParameters == 0;
for (TypeVariableType typeVariable
in _elementEnvironment.getFunctionTypeVariables(function)) {
HInstruction argument;
if (inlineTypeParameters) {
// Add inlined type parameters.
argument = _computeTypeArgumentDefaultValue(function, typeVariable);
} else {
argument = compiledArguments[argumentIndex++];
}
localsHandler.updateLocal(
localsHandler.getTypeVariableAsLocal(typeVariable), argument);
}
}
assert(
argumentIndex == compiledArguments.length ||
!_rtiNeed.methodNeedsTypeArguments(function) &&
compiledArguments.length - argumentIndex ==
function.parameterStructure.typeParameters,
failedAt(
function,
"Only ${argumentIndex} of ${compiledArguments.length} "
"arguments have been read from: ${compiledArguments} passed to "
"$function."));
_returnType = _elementEnvironment.getFunctionType(function).returnType;
stack = <HInstruction>[];
_insertCoverageCall(function);
}
void _leaveInlinedMethod() {
HInstruction result = localsHandler.readLocal(_returnLocal);
KernelInliningState state = _inliningStack.removeLast();
_restoreState(state);
stack.add(result);
}
void _restoreState(KernelInliningState state) {
localsHandler = state.oldLocalsHandler;
_returnLocal = state.oldReturnLocal;
_inTryStatement = state.inTryStatement;
_returnType = state.oldReturnType;
assert(stack.isEmpty);
stack = state.oldStack;
}
bool _providedArgumentsKnownToBeComplete(ir.Node currentNode) {
/* When inlining the iterator methods generated for a for-in loop, the
* [currentNode] is the [ForIn] tree. The compiler-generated iterator
* invocations are known to have fully specified argument lists, no default
* arguments are used. See invocations of [pushInvokeDynamic] in
* [visitForIn].
*/
// TODO(redemption): Is this valid here?
return currentNode is ir.ForInStatement;
}
void _emitReturn(HInstruction value, SourceInformation sourceInformation) {
if (_inliningStack.isEmpty) {
_closeAndGotoExit(
new HReturn(_abstractValueDomain, value, sourceInformation));
} else {
localsHandler.updateLocal(_returnLocal, value);
}
}
void _doInline(FunctionEntity function) {
_visitInlinedFunction(function);
}
/// Run this builder on the body of the [function] to be inlined.
void _visitInlinedFunction(FunctionEntity function) {
_potentiallyCheckInlinedParameterTypes(function);
MemberDefinition definition = _elementMap.getMemberDefinition(function);
switch (definition.kind) {
case MemberKind.constructor:
_buildConstructor(function, definition.node);
return;
case MemberKind.constructorBody:
ir.Constructor constructor = definition.node;
constructor.function.body.accept(this);
return;
case MemberKind.regular:
ir.Node node = definition.node;
if (node is ir.Constructor) {
node.function.body.accept(this);
return;
} else if (node is ir.Procedure) {
node.function.body.accept(this);
return;
}
break;
case MemberKind.closureCall:
ir.LocalFunction node = definition.node;
node.function.body.accept(this);
return;
default:
break;
}
failedAt(function, "Unexpected inlined function: $definition");
}
/// Generates type tests for the parameters of the inlined function.
void _potentiallyCheckInlinedParameterTypes(FunctionEntity function) {
// TODO(sra): Incorporate properties of call site to help determine which
// type parameters and value parameters need to be checked.
bool trusted = false;
if (function.isStatic ||
function.isTopLevel ||
function.isConstructor ||
function is ConstructorBodyEntity) {
// We inline static methods, top-level methods, constructors and
// constructor bodies only from direct call sites.
trusted = true;
}
if (!trusted) {
_checkTypeVariableBounds(function);
}
KernelToLocalsMap localsMap =
closedWorld.globalLocalsMap.getLocalsMap(function);
forEachOrderedParameter(_elementMap, function,
(ir.VariableDeclaration variable, {bool isElided}) {
Local parameter = localsMap.getLocalVariable(variable);
HInstruction argument = localsHandler.readLocal(parameter);
DartType type = localsMap.getLocalType(_elementMap, parameter);
HInstruction checkedOrTrusted;
if (trusted) {
checkedOrTrusted = _typeBuilder.trustTypeOfParameter(argument, type);
} else {
checkedOrTrusted = _typeBuilder.potentiallyCheckOrTrustTypeOfParameter(
function, argument, type);
}
localsHandler.updateLocal(parameter, checkedOrTrusted);
});
}
bool get _allInlinedFunctionsCalledOnce {
return _inliningStack.isEmpty || _inliningStack.last.allFunctionsCalledOnce;
}
bool _isFunctionCalledOnce(FunctionEntity element) {
// ConstructorBodyElements are not in the type inference graph.
if (element is ConstructorBodyEntity) {
// If there are no subclasses with constructors that have this constructor
// as a superconstructor, it is called once by the generative
// constructor's factory. A simplified version is to check this is a
// constructor body for a leaf class.
ClassEntity class_ = element.enclosingClass;
if (closedWorld.classHierarchy.isDirectlyInstantiated(class_)) {
return !closedWorld.classHierarchy.isIndirectlyInstantiated(class_);
}
return false;
}
return globalInferenceResults.resultOfMember(element).isCalledOnce;
}
bool _isCalledOnce(FunctionEntity element) {
return _allInlinedFunctionsCalledOnce && _isFunctionCalledOnce(element);
}
void _insertCoverageCall(MemberEntity element) {
if (!options.experimentCallInstrumentation) return;
if (element == _commonElements.traceHelper) return;
// TODO(sigmund): create a better uuid for elements.
HConstant idConstant = graph.addConstantInt(element.hashCode, closedWorld);
n(e) => e == null ? '' : e.name;
String name = "${n(element.library)}:${n(element.enclosingClass)}."
"${n(element)}";
HConstant nameConstant = graph.addConstantString(name, closedWorld);
add(new HInvokeStatic(
_commonElements.traceHelper,
<HInstruction>[idConstant, nameConstant],
_abstractValueDomain.dynamicType,
const <DartType>[]));
}
}
/// Data collected to create a constructor.
class ConstructorData {
/// Inlined (super) constructors.
final List<ir.Constructor> constructorChain = <ir.Constructor>[];
/// Initial values for all instance fields.
final Map<FieldEntity, HInstruction> fieldValues =
<FieldEntity, HInstruction>{};
/// Classes for which type variables have been prepared.
final Set<ClassEntity> includedClasses = new Set<ClassEntity>();
}
class KernelInliningState {
final FunctionEntity function;
final Local oldReturnLocal;
final DartType oldReturnType;
final List<HInstruction> oldStack;
final LocalsHandler oldLocalsHandler;
final bool inTryStatement;
final bool allFunctionsCalledOnce;
KernelInliningState(
this.function,
this.oldReturnLocal,
this.oldReturnType,
this.oldStack,
this.oldLocalsHandler,
this.inTryStatement,
this.allFunctionsCalledOnce);
@override
String toString() => 'KernelInliningState($function,'
'allFunctionsCalledOnce=$allFunctionsCalledOnce)';
}
/// Class in charge of building try, catch and/or finally blocks. This handles
/// the instructions that need to be output and the dominator calculation of
/// this sequence of code.
class TryCatchFinallyBuilder {
final KernelSsaGraphBuilder kernelBuilder;
final SourceInformation trySourceInformation;
HBasicBlock enterBlock;
HBasicBlock startTryBlock;
HBasicBlock endTryBlock;
HBasicBlock startCatchBlock;
HBasicBlock endCatchBlock;
HBasicBlock startFinallyBlock;
HBasicBlock endFinallyBlock;
HBasicBlock exitBlock;
HTry tryInstruction;
HLocalValue exception;
/// True if the code surrounding this try statement was also part of a
/// try/catch/finally statement.
bool previouslyInTryStatement;
SubGraph bodyGraph;
SubGraph catchGraph;
SubGraph finallyGraph;
// The original set of locals that were defined before this try block.
// The catch block and the finally block must not reuse the existing locals
// handler. None of the variables that have been defined in the body-block
// will be used, but for loops we will add (unnecessary) phis that will
// reference the body variables. This makes it look as if the variables were
// used in a non-dominated block.
LocalsHandler originalSavedLocals;
TryCatchFinallyBuilder(this.kernelBuilder, this.trySourceInformation) {
tryInstruction = new HTry(kernelBuilder._abstractValueDomain);
originalSavedLocals = new LocalsHandler.from(kernelBuilder.localsHandler);
enterBlock = kernelBuilder.openNewBlock();
kernelBuilder.close(tryInstruction);
previouslyInTryStatement = kernelBuilder._inTryStatement;
kernelBuilder._inTryStatement = true;
startTryBlock = kernelBuilder.graph.addNewBlock();
kernelBuilder.open(startTryBlock);
}
void _addExitTrySuccessor(successor) {
if (successor == null) return;
// Iterate over all blocks created inside this try/catch, and
// attach successor information to blocks that end with
// [HExitTry].
for (int i = startTryBlock.id; i < successor.id; i++) {
HBasicBlock block = kernelBuilder.graph.blocks[i];
var last = block.last;
if (last is HExitTry) {
block.addSuccessor(successor);
}
}
}
void _addOptionalSuccessor(block1, block2) {
if (block2 != null) block1.addSuccessor(block2);
}
/// Helper function to set up basic block successors for try-catch-finally
/// sequences.
void _setBlockSuccessors() {
// Setup all successors. The entry block that contains the [HTry]
// has 1) the body, 2) the catch, 3) the finally, and 4) the exit
// blocks as successors.
enterBlock.addSuccessor(startTryBlock);
_addOptionalSuccessor(enterBlock, startCatchBlock);
_addOptionalSuccessor(enterBlock, startFinallyBlock);
enterBlock.addSuccessor(exitBlock);
// The body has either the catch or the finally block as successor.
if (endTryBlock != null) {
assert(startCatchBlock != null || startFinallyBlock != null);
endTryBlock.addSuccessor(
startCatchBlock != null ? startCatchBlock : startFinallyBlock);
endTryBlock.addSuccessor(exitBlock);
}
// The catch block has either the finally or the exit block as
// successor.
endCatchBlock?.addSuccessor(
startFinallyBlock != null ? startFinallyBlock : exitBlock);
// The finally block has the exit block as successor.
endFinallyBlock?.addSuccessor(exitBlock);
// If a block inside try/catch aborts (eg with a return statement),
// we explicitly mark this block a predecessor of the catch
// block and the finally block.
_addExitTrySuccessor(startCatchBlock);
_addExitTrySuccessor(startFinallyBlock);
}
/// Build the finally{} clause of a try/{catch}/finally statement. Note this
/// does not examine the body of the try clause, only the finally portion.
void buildFinallyBlock(void buildFinalizer()) {
kernelBuilder.localsHandler = new LocalsHandler.from(originalSavedLocals);
startFinallyBlock = kernelBuilder.graph.addNewBlock();
kernelBuilder.open(startFinallyBlock);
buildFinalizer();
if (!kernelBuilder.isAborted()) {
endFinallyBlock =
kernelBuilder.close(new HGoto(kernelBuilder._abstractValueDomain));
}
tryInstruction.finallyBlock = startFinallyBlock;
finallyGraph =
new SubGraph(startFinallyBlock, kernelBuilder.lastOpenedBlock);
}
void closeTryBody() {
// We use a [HExitTry] instead of a [HGoto] for the try block
// because it will have multiple successors: the join block, and
// the catch or finally block.
if (!kernelBuilder.isAborted()) {
endTryBlock =
kernelBuilder.close(new HExitTry(kernelBuilder._abstractValueDomain));
}
bodyGraph = new SubGraph(startTryBlock, kernelBuilder.lastOpenedBlock);
}
void buildCatch(ir.TryCatch tryCatch) {
kernelBuilder.localsHandler = new LocalsHandler.from(originalSavedLocals);
startCatchBlock = kernelBuilder.graph.addNewBlock();
kernelBuilder.open(startCatchBlock);
// Note that the name of this local is irrelevant.
SyntheticLocal local = kernelBuilder.localsHandler.createLocal('exception');
exception =
new HLocalValue(local, kernelBuilder._abstractValueDomain.nonNullType)
..sourceInformation = trySourceInformation;
kernelBuilder.add(exception);
HInstruction oldRethrowableException = kernelBuilder._rethrowableException;
kernelBuilder._rethrowableException = exception;
kernelBuilder._pushStaticInvocation(
kernelBuilder._commonElements.exceptionUnwrapper,
[exception],
kernelBuilder._typeInferenceMap
.getReturnTypeOf(kernelBuilder._commonElements.exceptionUnwrapper),
const <DartType>[],
sourceInformation: trySourceInformation);
HInvokeStatic unwrappedException = kernelBuilder.pop();
tryInstruction.exception = exception;
int catchesIndex = 0;
void pushCondition(ir.Catch catchBlock) {
// `guard` is often `dynamic`, which generates `true`.
kernelBuilder._pushIsTest(catchBlock.guard, unwrappedException,
kernelBuilder._sourceInformationBuilder.buildCatch(catchBlock));
}
void visitThen() {
ir.Catch catchBlock = tryCatch.catches[catchesIndex];
catchesIndex++;
if (catchBlock.exception != null) {
Local exceptionVariable =
kernelBuilder._localsMap.getLocalVariable(catchBlock.exception);
kernelBuilder.localsHandler.updateLocal(
exceptionVariable, unwrappedException,
sourceInformation:
kernelBuilder._sourceInformationBuilder.buildCatch(catchBlock));
}
if (catchBlock.stackTrace != null) {
kernelBuilder._pushStaticInvocation(
kernelBuilder._commonElements.traceFromException,
[exception],
kernelBuilder._typeInferenceMap.getReturnTypeOf(
kernelBuilder._commonElements.traceFromException),
const <DartType>[],
sourceInformation:
kernelBuilder._sourceInformationBuilder.buildCatch(catchBlock));
HInstruction traceInstruction = kernelBuilder.pop();
Local traceVariable =
kernelBuilder._localsMap.getLocalVariable(catchBlock.stackTrace);
kernelBuilder.localsHandler.updateLocal(traceVariable, traceInstruction,
sourceInformation:
kernelBuilder._sourceInformationBuilder.buildCatch(catchBlock));
}
catchBlock.body.accept(kernelBuilder);
}
void visitElse() {
if (catchesIndex >= tryCatch.catches.length) {
kernelBuilder._closeAndGotoExit(new HThrow(
kernelBuilder._abstractValueDomain,
exception,
exception.sourceInformation,
isRethrow: true));
} else {
ir.Catch nextCatch = tryCatch.catches[catchesIndex];
kernelBuilder._handleIf(
visitCondition: () {
pushCondition(nextCatch);
},
visitThen: visitThen,
visitElse: visitElse,
sourceInformation:
kernelBuilder._sourceInformationBuilder.buildCatch(nextCatch));
}
}
ir.Catch firstBlock = tryCatch.catches[catchesIndex];
kernelBuilder._handleIf(
visitCondition: () {
pushCondition(firstBlock);
},
visitThen: visitThen,
visitElse: visitElse,
sourceInformation:
kernelBuilder._sourceInformationBuilder.buildCatch(firstBlock));
if (!kernelBuilder.isAborted()) {
endCatchBlock =
kernelBuilder.close(new HGoto(kernelBuilder._abstractValueDomain));
}
kernelBuilder._rethrowableException = oldRethrowableException;
tryInstruction.catchBlock = startCatchBlock;
catchGraph = new SubGraph(startCatchBlock, kernelBuilder.lastOpenedBlock);
}
void cleanUp() {
exitBlock = kernelBuilder.graph.addNewBlock();
_setBlockSuccessors();
// Use the locals handler not altered by the catch and finally
// blocks.
kernelBuilder.localsHandler = originalSavedLocals;
kernelBuilder.open(exitBlock);
enterBlock.setBlockFlow(
new HTryBlockInformation(
kernelBuilder.wrapStatementGraph(bodyGraph),
exception,
kernelBuilder.wrapStatementGraph(catchGraph),
kernelBuilder.wrapStatementGraph(finallyGraph)),
exitBlock);
kernelBuilder._inTryStatement = previouslyInTryStatement;
}
}
class KernelTypeBuilder extends TypeBuilder {
JsToElementMap _elementMap;
KernelTypeBuilder(KernelSsaGraphBuilder builder, this._elementMap)
: super(builder);
@override
KernelSsaGraphBuilder get builder => super.builder;
@override
ClassTypeVariableAccess computeTypeVariableAccess(MemberEntity member) {
return _elementMap.getClassTypeVariableAccessForMember(member);
}
}
class _ErroneousInitializerVisitor extends ir.Visitor<bool> {
_ErroneousInitializerVisitor();
// TODO(30809): Use const constructor.
static bool check(ir.Initializer initializer) =>
initializer.accept(new _ErroneousInitializerVisitor());
@override
bool defaultInitializer(ir.Node node) => false;
@override
bool visitInvalidInitializer(ir.InvalidInitializer node) => true;
@override
bool visitLocalInitializer(ir.LocalInitializer node) {
return node.variable.initializer?.accept(this) ?? false;
}
// Expressions: Does the expression always throw?
@override
bool defaultExpression(ir.Expression node) => false;
@override
bool visitThrow(ir.Throw node) => true;
// TODO(sra): We might need to match other expressions that always throw but
// in a subexpression.
}
/// Special [JumpHandler] implementation used to handle continue statements
/// targeting switch cases.
class KernelSwitchCaseJumpHandler extends SwitchCaseJumpHandler {
KernelSwitchCaseJumpHandler(KernelSsaGraphBuilder builder, JumpTarget target,
ir.SwitchStatement switchStatement, KernelToLocalsMap localsMap)
: super(builder, target) {
// The switch case indices must match those computed in
// [KernelSsaBuilder.buildSwitchCaseConstants].
// Switch indices are 1-based so we can bypass the synthetic loop when no
// cases match simply by branching on the index (which defaults to null).
// TODO
int switchIndex = 1;
for (ir.SwitchCase switchCase in switchStatement.cases) {
JumpTarget continueTarget =
localsMap.getJumpTargetForSwitchCase(switchCase);
if (continueTarget != null) {
targetIndexMap[continueTarget] = switchIndex;
assert(builder.jumpTargets[continueTarget] == null);
builder.jumpTargets[continueTarget] = this;
}
switchIndex++;
}
}
}
class StaticType {
final DartType type;
final ClassRelation relation;
StaticType(this.type, this.relation);
@override
int get hashCode => type.hashCode * 13 + relation.hashCode * 19;
@override
bool operator ==(other) {
if (identical(this, other)) return true;
return other is StaticType &&
type == other.type &&
relation == other.relation;
}
@override
String toString() => 'StaticType($type,$relation)';
}
class InlineData {
bool isConstructor = false;
bool codeAfterReturn = false;
bool hasLoop = false;
bool hasClosure = false;
bool hasTry = false;
bool hasAsyncAwait = false;
bool hasThrow = false;
bool hasLongString = false;
bool hasExternalConstantConstructorCall = false;
bool hasTypeArguments = false;
bool hasArgumentDefaulting = false;
bool hasCast = false;
bool hasIf = false;
List<int> argumentCounts = [];
int regularNodeCount = 0;
int callCount = 0;
int reductiveNodeCount = 0;
InlineData();
InlineData.internal(
{this.codeAfterReturn,
this.hasLoop,
this.hasClosure,
this.hasTry,
this.hasAsyncAwait,
this.hasThrow,
this.hasLongString,
this.regularNodeCount,
this.callCount});
bool canBeInlined({int maxInliningNodes, bool allowLoops: false}) {
return cannotBeInlinedReason(
maxInliningNodes: maxInliningNodes, allowLoops: allowLoops) ==
null;
}
String cannotBeInlinedReason({int maxInliningNodes, bool allowLoops: false}) {
if (hasLoop && !allowLoops) {
return 'loop';
} else if (hasTry) {
return 'try';
} else if (hasClosure) {
return 'closure';
} else if (codeAfterReturn) {
return 'code after return';
} else if (hasAsyncAwait) {
return 'async/await';
} else if (maxInliningNodes != null &&
regularNodeCount - 1 > maxInliningNodes) {
return 'too many nodes (${regularNodeCount - 1}>$maxInliningNodes)';
}
return null;
}
bool canBeInlinedReductive({int argumentCount}) {
return cannotBeInlinedReductiveReason(argumentCount: argumentCount) == null;
}
String cannotBeInlinedReductiveReason({int argumentCount}) {
if (hasTry) {
return 'try';
} else if (hasClosure) {
return 'closure';
} else if (codeAfterReturn) {
return 'code after return';
} else if (hasAsyncAwait) {
return 'async/await';
} else if (callCount > 1) {
return 'too many calls';
} else if (hasThrow) {
return 'throw';
} else if (hasLongString) {
return 'long string';
} else if (hasExternalConstantConstructorCall) {
return 'external const constructor';
} else if (hasTypeArguments) {
return 'type arguments';
} else if (hasArgumentDefaulting) {
return 'argument defaulting';
} else if (hasCast) {
return 'cast';
} else if (hasIf) {
return 'if';
} else if (isConstructor) {
return 'constructor';
}
for (int count in argumentCounts) {
if (count > argumentCount) {
return 'increasing arguments';
}
}
// Node budget that covers one call and the passed-in arguments.
// The +1 also allows a top-level zero-argument to be inlined if it
// returns a constant.
int maxInliningNodes = argumentCount + 1;
if (reductiveNodeCount > maxInliningNodes) {
return 'too many nodes (${reductiveNodeCount}>$maxInliningNodes)';
}
return null;
}
@override
String toString() {
StringBuffer sb = new StringBuffer();
sb.write('InlineData(');
String comma = '';
if (isConstructor) {
sb.write('isConstructor');
comma = ',';
}
if (codeAfterReturn) {
sb.write(comma);
sb.write('codeAfterReturn');
comma = ',';
}
if (hasLoop) {
sb.write(comma);
sb.write('hasLoop');
comma = ',';
}
if (hasClosure) {
sb.write(comma);
sb.write('hasClosure');
comma = ',';
}
if (hasTry) {
sb.write(comma);
sb.write('hasTry');
comma = ',';
}
if (hasAsyncAwait) {
sb.write(comma);
sb.write('hasAsyncAwait');
comma = ',';
}
if (hasThrow) {
sb.write(comma);
sb.write('hasThrow');
comma = ',';
}
if (hasLongString) {
sb.write(comma);
sb.write('hasLongString');
comma = ',';
}
if (hasExternalConstantConstructorCall) {
sb.write(comma);
sb.write('hasExternalConstantConstructorCall');
comma = ',';
}
if (hasTypeArguments) {
sb.write(comma);
sb.write('hasTypeArguments');
comma = ',';
}
if (hasArgumentDefaulting) {
sb.write(comma);
sb.write('hasArgumentDefaulting');
comma = ',';
}
if (hasCast) {
sb.write(comma);
sb.write('hasCast');
comma = ',';
}
if (hasIf) {
sb.write(comma);
sb.write('hasIf');
comma = ',';
}
if (argumentCounts.isNotEmpty) {
sb.write(comma);
sb.write('argumentCounts={${argumentCounts.join(',')}}');
comma = ',';
}
sb.write(comma);
sb.write('regularNodeCount=$regularNodeCount,');
sb.write('callCount=$callCount,');
sb.write('reductiveNodeCount=$reductiveNodeCount');
sb.write(')');
return sb.toString();
}
}
class InlineDataCache {
final bool enableUserAssertions;
final bool omitImplicitCasts;
InlineDataCache(
{this.enableUserAssertions: false, this.omitImplicitCasts: false});
Map<FunctionEntity, InlineData> _cache = {};
InlineData getInlineData(JsToElementMap elementMap, FunctionEntity function) {
return _cache[function] ??= InlineWeeder.computeInlineData(
elementMap, function,
enableUserAssertions: enableUserAssertions,
omitImplicitCasts: omitImplicitCasts);
}
}
class InlineWeeder extends ir.Visitor {
// Invariant: *INSIDE_LOOP* > *OUTSIDE_LOOP*
static const INLINING_NODES_OUTSIDE_LOOP = 15;
static const INLINING_NODES_OUTSIDE_LOOP_ARG_FACTOR = 3;
static const INLINING_NODES_INSIDE_LOOP = 34;
static const INLINING_NODES_INSIDE_LOOP_ARG_FACTOR = 4;
final bool enableUserAssertions;
final bool omitImplicitCasts;
final InlineData data = new InlineData();
bool seenReturn = false;
/// Whether node-count is collector to determine if a function can be
/// inlined.
bool countRegularNode = true;
/// Whether node-count is collected to determine if inlining a function is
/// very likely to reduce code size.
///
/// For the reductive analysis:
/// We allow the body to be a single function call that does not have any more
/// inputs than the inlinee.
///
/// We allow the body to be the return of an 'eligible' constant. A constant
/// is 'eligible' if it is not large (e.g. a long string).
///
/// We skip 'e as{TypeError} T' when the checks are omitted.
//
// TODO(sra): Consider slightly expansive simple constructors where all we
// gain is a 'new' keyword, e.g. `new X.Foo(a)` vs `X.Foo$(a)`.
//
// TODO(25231): Make larger string constants eligible by sharing references.
bool countReductiveNode = true;
InlineWeeder(
{this.enableUserAssertions: false, this.omitImplicitCasts: false});
static InlineData computeInlineData(
JsToElementMap elementMap, FunctionEntity function,
{bool enableUserAssertions: false, bool omitImplicitCasts: false}) {
InlineWeeder visitor = new InlineWeeder(
enableUserAssertions: enableUserAssertions,
omitImplicitCasts: omitImplicitCasts);
ir.FunctionNode node = getFunctionNode(elementMap, function);
node.accept(visitor);
if (function.isConstructor) {
visitor.data.isConstructor = true;
MemberDefinition definition = elementMap.getMemberDefinition(function);
visitor.skipReductiveNodes(() {
ir.Node node = definition.node;
if (node is ir.Constructor) {
visitor.visitList(node.initializers);
}
});
}
return visitor.data;
}
void skipRegularNodes(void f()) {
bool oldCountRegularNode = countRegularNode;
countRegularNode = false;
f();
countRegularNode = oldCountRegularNode;
}
void skipReductiveNodes(void f()) {
bool oldCountReductiveNode = countReductiveNode;
countReductiveNode = false;
f();
countReductiveNode = oldCountReductiveNode;
}
void registerRegularNode() {
if (countRegularNode) {
data.regularNodeCount++;
if (seenReturn) {
data.codeAfterReturn = true;
}
}
}
void registerReductiveNode() {
if (countReductiveNode) {
data.reductiveNodeCount++;
if (seenReturn) {
data.codeAfterReturn = true;
}
}
}
void unregisterReductiveNode() {
if (countReductiveNode) {
data.reductiveNodeCount--;
}
}
void visit(ir.Node node) => node?.accept(this);
void visitList(List<ir.Node> nodes) {
for (ir.Node node in nodes) {
visit(node);
}
}
@override
defaultNode(ir.Node node) {
registerRegularNode();
registerReductiveNode();
node.visitChildren(this);
}
@override
visitConstantExpression(ir.ConstantExpression node) {
registerRegularNode();
registerReductiveNode();
}
@override
visitReturnStatement(ir.ReturnStatement node) {
registerRegularNode();
node.visitChildren(this);
seenReturn = true;
}
@override
visitThrow(ir.Throw node) {
registerRegularNode();
data.hasThrow = true;
node.visitChildren(this);
}
_handleLoop(ir.Node node) {
// It's actually not difficult to inline a method with a loop, but our
// measurements show that it's currently better to not inline a method that
// contains a loop.
data.hasLoop = true;
node.visitChildren(this);
}
@override
visitForStatement(ir.ForStatement node) {
_handleLoop(node);
}
@override
visitForInStatement(ir.ForInStatement node) {
_handleLoop(node);
}
@override
visitWhileStatement(ir.WhileStatement node) {
_handleLoop(node);
}
@override
visitDoStatement(ir.DoStatement node) {
_handleLoop(node);
}
@override
visitTryCatch(ir.TryCatch node) {
data.hasTry = true;
}
@override
visitTryFinally(ir.TryFinally node) {
data.hasTry = true;
}
@override
visitFunctionExpression(ir.FunctionExpression node) {
registerRegularNode();
data.hasClosure = true;
}
@override
visitFunctionDeclaration(ir.FunctionDeclaration node) {
registerRegularNode();
data.hasClosure = true;
}
@override
visitFunctionNode(ir.FunctionNode node) {
if (node.asyncMarker != ir.AsyncMarker.Sync) {
data.hasAsyncAwait = true;
}
// TODO(sra): Cost of parameter checking?
skipReductiveNodes(() {
visitList(node.typeParameters);
visitList(node.positionalParameters);
visitList(node.namedParameters);
visit(node.returnType);
});
visit(node.body);
}
@override
visitConditionalExpression(ir.ConditionalExpression node) {
// Heuristic: In "parameter ? A : B" there is a high probability that
// parameter is a constant. Assuming the parameter is constant, we can
// compute a count that is bounded by the largest arm rather than the sum of
// both arms.
ir.Expression condition = node.condition;
visit(condition);
int commonPrefixCount = data.regularNodeCount;
visit(node.then);
int thenCount = data.regularNodeCount - commonPrefixCount;
data.regularNodeCount = commonPrefixCount;
visit(node.otherwise);
int elseCount = data.regularNodeCount - commonPrefixCount;
data.regularNodeCount = commonPrefixCount + thenCount + elseCount;
if (condition is ir.VariableGet &&
condition.variable.parent is ir.FunctionNode) {
data.regularNodeCount =
commonPrefixCount + (thenCount > elseCount ? thenCount : elseCount);
}
// This is last so that [tooDifficult] is always updated.
registerRegularNode();
registerReductiveNode();
skipRegularNodes(() => visit(node.staticType));
}
@override
visitAssertInitializer(ir.AssertInitializer node) {
if (!enableUserAssertions) return;
node.visitChildren(this);
}
@override
visitAssertStatement(ir.AssertStatement node) {
if (!enableUserAssertions) return;
defaultNode(node);
}
void registerCall() {
++data.callCount;
}
@override
visitEmptyStatement(ir.EmptyStatement node) {
registerRegularNode();
}
@override
visitExpressionStatement(ir.ExpressionStatement node) {
registerRegularNode();
node.visitChildren(this);
}
@override
visitBlock(ir.Block node) {
registerRegularNode();
node.visitChildren(this);
}
@override
visitStringLiteral(ir.StringLiteral node) {
registerRegularNode();
registerReductiveNode();
// Avoid copying long strings into call site.
if (node.value.length > 14) {
data.hasLongString = true;
}
}
@override
visitPropertyGet(ir.PropertyGet node) {
registerCall();
registerRegularNode();
registerReductiveNode();
skipReductiveNodes(() => visit(node.name));
visit(node.receiver);
}
@override
visitDirectPropertyGet(ir.DirectPropertyGet node) {
registerCall();
registerRegularNode();
registerReductiveNode();
visit(node.receiver);
}
@override
visitPropertySet(ir.PropertySet node) {
registerCall();
registerRegularNode();
registerReductiveNode();
skipReductiveNodes(() => visit(node.name));
visit(node.receiver);
visit(node.value);
}
@override
visitDirectPropertySet(ir.DirectPropertySet node) {
registerCall();
registerRegularNode();
registerReductiveNode();
visit(node.receiver);
visit(node.value);
}
@override
visitVariableGet(ir.VariableGet node) {
registerRegularNode();
registerReductiveNode();
skipReductiveNodes(() => visit(node.promotedType));
}
@override
visitThisExpression(ir.ThisExpression node) {
registerRegularNode();
registerReductiveNode();
}
@override
visitStaticGet(ir.StaticGet node) {
// Assume lazy-init static, loaded via a call: `$.$get$foo()`.
registerCall();
registerRegularNode();
registerReductiveNode();
}
@override
visitConstructorInvocation(ir.ConstructorInvocation node) {
registerRegularNode();
registerReductiveNode();
if (node.isConst) {
// A const constructor call compiles to a constant pool reference.
skipReductiveNodes(() => node.visitChildren(this));
} else {
registerCall();
_processArguments(node.arguments, node.target?.function);
}
}
@override
visitStaticInvocation(ir.StaticInvocation node) {
registerRegularNode();
if (node.isConst) {
data.hasExternalConstantConstructorCall = true;
skipReductiveNodes(() => node.visitChildren(this));
} else {
registerCall();
registerReductiveNode();
_processArguments(node.arguments, node.target?.function);
}
}
@override
visitMethodInvocation(ir.MethodInvocation node) {
registerRegularNode();
registerReductiveNode();
registerCall();
visit(node.receiver);
skipReductiveNodes(() => visit(node.name));
_processArguments(node.arguments, null);
}
_processArguments(ir.Arguments arguments, ir.FunctionNode target) {
registerRegularNode();
if (arguments.types.isNotEmpty) {
data.hasTypeArguments = true;
skipReductiveNodes(() => visitList(arguments.types));
}
int count = arguments.positional.length + arguments.named.length;
data.argumentCounts.add(count);
if (target != null) {
// Disallow defaulted optional arguments since they will be passed
// explicitly.
if (target.positionalParameters.length + target.namedParameters.length >
count) {
data.hasArgumentDefaulting = true;
}
}
visitList(arguments.positional);
for (ir.NamedExpression expression in arguments.named) {
registerRegularNode();
expression.value.accept(this);
}
}
@override
visitAsExpression(ir.AsExpression node) {
registerRegularNode();
visit(node.operand);
skipReductiveNodes(() => visit(node.type));
if (!(node.isTypeError && omitImplicitCasts)) {
data.hasCast = true;
}
}
@override
visitVariableDeclaration(ir.VariableDeclaration node) {
registerRegularNode();
skipReductiveNodes(() {
visitList(node.annotations);
visit(node.type);
});
visit(node.initializer);
// A local variable is an alias for the initializer expression.
if (node.initializer != null) {
unregisterReductiveNode(); // discount one reference to the variable.
}
}
@override
visitIfStatement(ir.IfStatement node) {
registerRegularNode();
node.visitChildren(this);
data.hasIf = true;
}
}