blob: be3be294cdb399e571cced8d28618f725d9870e6 [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: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 '../compiler.dart';
import '../constants/values.dart'
show
ConstantValue,
InterceptorConstantValue,
StringConstantValue,
TypeConstantValue;
import '../dump_info.dart';
import '../elements/entities.dart';
import '../elements/jumps.dart';
import '../elements/names.dart';
import '../elements/types.dart';
import '../io/source_information.dart';
import '../js/js.dart' as js;
import '../js_backend/allocator_analysis.dart' show JAllocatorAnalysis;
import '../js_backend/backend.dart' show JavaScriptBackend;
import '../js_backend/runtime_types.dart' show RuntimeTypesSubstitutions;
import '../js_emitter/js_emitter.dart' show NativeEmitter;
import '../js_model/locals.dart'
show forEachOrderedParameter, GlobalLocalsMap, JumpVisitor;
import '../js_model/elements.dart' show JGeneratorBody;
import '../kernel/element_map.dart';
import '../kernel/kernel_backend_strategy.dart';
import '../native/native.dart' as native;
import '../types/abstract_value_domain.dart';
import '../types/types.dart';
import '../universe/call_structure.dart';
import '../universe/feature.dart';
import '../universe/selector.dart';
import '../universe/side_effects.dart' show SideEffects;
import '../universe/target_checks.dart' show TargetChecks;
import '../universe/use.dart'
show ConstantUse, ConstrainedDynamicUse, StaticUse;
import '../universe/world_builder.dart' show CodegenWorldBuilder;
import '../world.dart';
import 'graph_builder.dart';
import 'jump_handler.dart';
import 'kernel_string_builder.dart';
import 'locals_handler.dart';
import 'loop_handler.dart';
import 'nodes.dart';
import 'ssa.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;
final KernelToTypeInferenceMap typeInferenceMap;
final SourceInformationBuilder<ir.Node> sourceInformationBuilder;
StackFrame(this.parent, this.member, this.asyncMarker, this.localsMap,
this.typeInferenceMap, this.sourceInformationBuilder);
}
class KernelSsaGraphBuilder extends ir.Visitor
with GraphBuilder, SsaBuilderFieldMixin {
final MemberEntity targetElement;
final MemberEntity initialTargetElement;
final JClosedWorld closedWorld;
final CodegenWorldBuilder _worldBuilder;
final CodegenRegistry registry;
final ClosureDataLookup closureDataLookup;
JAllocatorAnalysis _allocatorAnalysis;
/// 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 Compiler compiler;
@override
JavaScriptBackend get backend => compiler.backend;
final SourceInformationStrategy<ir.Node> _sourceInformationStrategy;
final KernelToElementMapForBuilding _elementMap;
final GlobalTypeInferenceResults _globalInferenceResults;
final GlobalLocalsMap _globalLocalsMap;
LoopHandler<ir.Node> loopHandler;
TypeBuilder typeBuilder;
final NativeEmitter nativeEmitter;
// [ir.Let] and [ir.LocalInitializer] bindings.
final Map<ir.VariableDeclaration, HInstruction> letBindings =
<ir.VariableDeclaration, HInstruction>{};
/// 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;
KernelSsaGraphBuilder(
this.initialTargetElement,
InterfaceType instanceType,
this.compiler,
this._elementMap,
this._globalInferenceResults,
this._globalLocalsMap,
this.closedWorld,
this._worldBuilder,
this.registry,
this.closureDataLookup,
this.nativeEmitter,
this._sourceInformationStrategy)
: this.targetElement = _effectiveTargetElementFor(initialTargetElement),
_infoReporter = compiler.dumpInfoTask,
_allocatorAnalysis = closedWorld.allocatorAnalysis {
_enterFrame(targetElement);
this.loopHandler = new KernelLoopHandler(this);
typeBuilder = new KernelTypeBuilder(this, _elementMap, _globalLocalsMap);
graph.element = targetElement;
graph.sourceInformation =
_sourceInformationBuilder.buildVariableDeclaration();
this.localsHandler = new LocalsHandler(this, targetElement, targetElement,
instanceType, nativeData, interceptorData);
}
KernelToLocalsMap get localsMap => _currentFrame.localsMap;
CommonElements get _commonElements => _elementMap.commonElements;
KernelToTypeInferenceMap get _typeInferenceMap =>
_currentFrame.typeInferenceMap;
SourceInformationBuilder get _sourceInformationBuilder =>
_currentFrame.sourceInformationBuilder;
static MemberEntity _effectiveTargetElementFor(MemberEntity member) {
if (member is JGeneratorBody) return member.function;
return member;
}
void _enterFrame(MemberEntity member) {
AsyncMarker asyncMarker = AsyncMarker.SYNC;
ir.FunctionNode function = getFunctionNode(_elementMap, member);
if (function != null) {
asyncMarker = getAsyncMarker(function);
}
_currentFrame = new StackFrame(
_currentFrame,
member,
asyncMarker,
_globalLocalsMap.getLocalsMap(member),
new KernelToTypeInferenceMapImpl(member, _globalInferenceResults),
_currentFrame != null
? _currentFrame.sourceInformationBuilder.forContext(member)
: _sourceInformationStrategy.createBuilderForContext(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) {
if (handleConstantField(targetElement, registry, closedWorld)) {
// No code is generated for `targetElement`: All references inline
// the constant value.
return null;
} else if (targetElement.isStatic || targetElement.isTopLevel) {
backend.constants.registerLazyStatic(targetElement);
}
buildField(target);
} else if (target is ir.FunctionExpression) {
buildFunctionNode(
targetElement, _ensureDefaultArgumentValues(target.function));
} else if (target is ir.FunctionDeclaration) {
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:
failedAt(targetElement, "Unexpected closure field: $targetElement");
break;
case MemberKind.signature:
ir.Node target = definition.node;
ir.FunctionNode originalClosureNode;
if (target is ir.Procedure) {
originalClosureNode = target.function;
} else if (target is ir.FunctionExpression) {
originalClosureNode = target.function;
} else if (target is ir.FunctionDeclaration) {
originalClosureNode = target.function;
} else {
failedAt(
targetElement,
"Unexpected function signature: "
"$targetElement inside a non-closure: $target");
}
buildMethodSignature(originalClosureNode);
break;
case MemberKind.generatorBody:
buildGeneratorBody(
initialTargetElement, _functionNodeOf(definition.node));
break;
}
assert(graph.isValid());
if (backend.tracer.isEnabled) {
MemberEntity member = definition.member;
String name = member.name;
if (member.isInstanceMember ||
member.isConstructor ||
member.isStatic) {
name = "${member.enclosingClass.name}.$name";
if (definition.kind == MemberKind.constructorBody) {
name += " (body)";
}
}
backend.tracer.traceCompilation(name);
backend.tracer.traceGraph('builder', graph);
}
return graph;
});
}
ir.FunctionNode _functionNodeOf(ir.TreeNode node) {
if (node is ir.Member) return node.function;
if (node is ir.FunctionDeclaration) return node.function;
if (node is ir.FunctionExpression) 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;
}
@override
ConstantValue getFieldInitialConstantValue(FieldEntity field) {
assert(field == targetElement);
return _elementMap.getFieldConstantValue(field);
}
void buildField(ir.Field node) {
_inLazyInitializerExpression = node.isStatic;
FieldEntity field = _elementMap.getMember(node);
openFunction(field, checks: TargetChecks.none);
if (node.isInstanceMember && options.enableTypeAssertions) {
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(
parameter, _getDartTypeIfValid(node.type));
add(new HFieldSet(abstractValueDomain, field, thisInstruction, value));
} else {
if (node.initializer != null) {
node.initializer.accept(this);
HInstruction fieldValue = pop();
HInstruction checkInstruction =
typeBuilder.potentiallyCheckOrTrustTypeOfAssignment(
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 'boolifies' it.
///
/// Boolification is checking if the value is '=== true'.
@override
HInstruction popBoolified() {
HInstruction value = pop();
if (typeBuilder.checkOrTrustTypes) {
InterfaceType type = commonElements.boolType;
return typeBuilder.potentiallyCheckOrTrustTypeOfAssignment(value, type,
kind: HTypeConversion.BOOLEAN_CONVERSION_CHECK);
}
HInstruction result = new HBoolify(value, abstractValueDomain.boolType);
add(result);
return result;
}
/// 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 = _elementMap.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;
List<TypeVariableType> typeVariables =
_elementMap.elementEnvironment.getFunctionTypeVariables(member);
if (typeVariables.isEmpty) {
return;
}
bool needsTypeArguments = rtiNeed.methodNeedsTypeArguments(member);
typeVariables.forEach((TypeVariableType typeVariableType) {
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;
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, checks: TargetChecks.none);
}
// [fieldValues] accumulates the field initializer values, which may be
// overwritten by initializer-list initializers.
ConstructorData constructorData = new 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 = _elementMap.elementEnvironment.getThisType(cls);
List<FieldEntity> fields = <FieldEntity>[];
_worldBuilder.forEachInstanceField(cls,
(ClassEntity enclosingClass, FieldEntity member) {
HInstruction value = constructorData.fieldValues[member];
if (value == null) {
assert(
_allocatorAnalysis.isInitializedInAllocator(member) ||
isCustomElement ||
reporter.hasReportedError,
'No initializer value for field ${member}');
} else {
fields.add(member);
DartType type = _elementMap.elementEnvironment.getFieldType(member);
type = localsHandler.substInContext(type);
constructorArguments.add(
typeBuilder.potentiallyCheckOrTrustTypeOfAssignment(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,
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) {
// Read the values of the type arguments and create a
// HTypeInfoExpression to set on the newly created object.
List<HInstruction> typeArguments = <HInstruction>[];
InterfaceType thisType =
_elementMap.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, () {
void handleParameter(ir.VariableDeclaration node) {
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.
body.function.positionalParameters.forEach(handleParameter);
body.function.namedParameters.toList()
..sort(namedOrdering)
..forEach(handleParameter);
// If there are locals that escape (i.e. mutated in closures), we pass the
// box to the constructor.
CapturedScope scopeData =
closureDataLookup.getCapturedScope(inlinedConstructor);
if (scopeData.requiresContextBox) {
bodyCallInputs.add(localsHandler.readLocal(scopeData.context));
}
// Pass type arguments.
ClassEntity inlinedConstructorClass = inlinedConstructor.enclosingClass;
if (closedWorld.rtiNeed
.classNeedsTypeArguments(inlinedConstructorClass)) {
InterfaceType thisType = _elementMap.elementEnvironment
.getThisType(inlinedConstructorClass);
for (DartType typeVariable in thisType.typeArguments) {
DartType result = localsHandler.substInContext(typeVariable);
HInstruction argument =
typeBuilder.analyzeTypeArgument(result, sourceElement);
bodyCallInputs.add(argument);
}
}
ConstructorBodyEntity constructorBody =
_elementMap.getConstructorBody(body);
if (!isCustomElement && // TODO(13836): Fix inlining.
_tryInlineMethod(constructorBody, null, null, bodyCallInputs, 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].
inlinedFrom(MemberEntity inlinedTarget, f()) {
reporter.withCurrentElement(inlinedTarget, () {
_enterFrame(inlinedTarget);
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 =
_elementMap.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);
_worldBuilder.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.");
}
if (node.initializer == null) {
// Unassigned fields of native classes are not initialized to
// prevent overwriting pre-initialized native properties.
if (!nativeData.isNativeOrExtendsNative(cls)) {
if (!_allocatorAnalysis.isInitializedInAllocator(field)) {
constructorData.fieldValues[field] =
graph.addConstantNull(closedWorld);
}
}
} else if (node.initializer is! ir.NullLiteral ||
!nativeData.isNativeClass(cls)) {
// 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.
if (!_allocatorAnalysis.isInitializedInAllocator(field)) {
inlinedFrom(field, () {
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) {
// TODO(sra): Skip fields initialized in allocator.
initializer.value.accept(this);
constructorData.fieldValues[_elementMap.getField(initializer.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;
if (callerClass.mixedInType != null) {
_collectFieldValues(callerClass.mixedInType.classNode, constructorData);
}
// 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, () {
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);
List<HInstruction> typeArguments = <HInstruction>[];
// Add function type variables.
FunctionType functionType =
_elementMap.getFunctionType(originalClosureNode);
functionType.forEachTypeVariable((TypeVariableType typeVariableType) {
if (options.strongMode ||
typeVariableType.element.typeDeclaration is ClassEntity) {
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();
}
/// Builds generative constructor body.
void buildConstructorBody(ir.Constructor constructor) {
openFunction(_elementMap.getConstructorBody(constructor),
functionNode: constructor.function, 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;
}
// TODO(sra): Static methods with no tear-off can be generated with no
// checks.
// TODO(sra): Instance methods can be generated with reduced checks if
// called only from non-dynamic call-sites.
openFunction(function, functionNode: functionNode);
// 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));
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);
// 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) {
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 = _elementMap.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.
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);
backend.outputUnitData.registerColocatedMembers(function, body);
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, 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;
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).isBoxed(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];
DartType type = _getDartTypeIfValid(variable.type);
if (options.strongMode) {
if (targetChecks.checkAllParameters ||
(targetChecks.checkCovariantParameters &&
(variable.isGenericCovariantImpl || variable.isCovariant))) {
newParameter = typeBuilder.potentiallyCheckOrTrustTypeOfParameter(
newParameter, type);
} else {
newParameter = typeBuilder.trustTypeOfParameter(newParameter, type);
}
} else {
newParameter = typeBuilder.potentiallyCheckOrTrustTypeOfParameter(
newParameter, type);
}
localsHandler.directLocals[local] = newParameter;
}
function.positionalParameters.forEach(_handleParameter);
function.namedParameters.toList()..forEach(_handleParameter);
}
void checkTypeVariableBounds(FunctionEntity method) {
if (rtiNeed.methodNeedsTypeArguments(method) &&
options.parameterCheckPolicy.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) {
_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);
if (closedWorld.nativeData.isNativeMember(targetElement)) {
nativeEmitter.nativeMethods.add(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)));
}
for (ir.VariableDeclaration param in functionNode.positionalParameters) {
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>[]);
argument = pop();
}
inputs.add(argument);
}
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();
}
}
void openFunction(MemberEntity member,
{ir.FunctionNode functionNode, TargetChecks checks}) {
// TODO(sra): Pass from all sites.
checks ??= TargetChecks.dynamicChecks;
Map<Local, AbstractValue> parameterMap = <Local, AbstractValue>{};
if (functionNode != null) {
void handleParameter(ir.VariableDeclaration node) {
Local local = localsMap.getLocalVariable(node);
parameterMap[local] =
_typeInferenceMap.getInferredTypeOfParameter(local);
}
functionNode.positionalParameters.forEach(handleParameter);
functionNode.namedParameters.toList()
..sort(namedOrdering)
..forEach(handleParameter);
_returnType = _elementMap.getDartType(functionNode.returnType);
}
HBasicBlock block = graph.addNewBlock();
open(graph.entry);
localsHandler.startFunction(
targetElement,
closureDataLookup.getScopeInfo(targetElement),
closureDataLookup.getCapturedScope(targetElement),
parameterMap,
_sourceInformationBuilder.buildDeclaration(targetElement),
isGenerativeConstructorBody: targetElement is ConstructorBodyEntity);
close(new HGoto(abstractValueDomain)).addSuccessor(block);
open(block);
_addClassTypeVariablesIfNeeded(member);
_addFunctionTypeVariablesIfNeeded(member);
if (functionNode != null) {
_potentiallyAddFunctionParameterTypeChecks(functionNode, checks);
}
_insertTraceCall(member);
_insertCoverageCall(member);
}
void closeFunction() {
if (!isAborted()) closeAndGotoExit(new HGoto(abstractValueDomain));
graph.finalize(abstractValueDomain);
}
@override
void defaultExpression(ir.Expression expression) {
// TODO(het): This is only to get tests working.
_trap('Unhandled ir.${expression.runtimeType} $expression');
}
@override
void defaultStatement(ir.Statement statement) {
_trap('Unhandled ir.${statement.runtimeType} $statement');
pop();
}
void _trap(String message) {
HInstruction nullValue = graph.addConstantNull(closedWorld);
HInstruction errorMessage = graph.addConstantString(message, closedWorld);
HInstruction trap = new HForeignCode(
js.js.parseForeignJS("#.#"),
abstractValueDomain.dynamicType,
<HInstruction>[nullValue, errorMessage]);
trap.sideEffects
..setAllSideEffects()
..setDependsOnSomething();
push(trap);
}
/// Returns the current source element. This is used by the type builder.
///
/// The returned element is a declaration element.
// TODO(efortuna): Update this when we implement inlining.
// TODO(sra): Re-implement type builder using Kernel types and the
// `target` for context.
@override
MemberEntity get sourceElement => _currentFrame.member;
@override
void visitCheckLibraryIsLoaded(ir.CheckLibraryIsLoaded checkLoad) {
ImportEntity import = _elementMap.getImport(checkLoad.import);
String loadId = deferredLoadTask.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>[]);
}
@override
void visitLoadLibrary(ir.LoadLibrary loadLibrary) {
String loadId = deferredLoadTask.getImportDeferName(
_elementMap.getSpannable(targetElement, loadLibrary),
_elementMap.getImport(loadLibrary.import));
// TODO(efortuna): Source information!
push(new HInvokeStatic(
commonElements.loadDeferredLibrary,
[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();
}
}
/// Returns true if the [type] is a valid return type for an asynchronous
/// function.
///
/// Asynchronous functions return a `Future`, and a valid return is thus
/// either dynamic, Object, or Future.
///
/// We do not accept the internal Future implementation class.
bool isValidAsyncReturnType(DartType type) {
// TODO(sigurdm): In an internal library a function could be declared:
//
// _FutureImpl foo async => 1;
//
// This should be valid (because the actual value returned from an async
// function is a `_FutureImpl`), but currently false is returned in this
// case.
return type.isDynamic ||
type == _commonElements.objectType ||
(type is InterfaceType && type.element == _commonElements.futureClass);
}
@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) {
if (options.enableTypeAssertions &&
!isValidAsyncReturnType(_returnType)) {
generateTypeError(
"Async function returned a Future,"
" was declared to return a ${_returnType}.",
sourceInformation);
pop();
return;
}
} else {
value = typeBuilder.potentiallyCheckOrTrustTypeOfAssignment(
value, _returnType);
}
}
handleInTryStatement();
_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,
localsMap.getCapturedLoopScope(closureDataLookup, 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>
/// }
_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);
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,
localsMap.getCapturedLoopScope(closureDataLookup, node),
localsMap.getJumpTargetForForIn(node),
buildInitializer,
buildCondition,
buildUpdate,
buildBody,
_sourceInformationBuilder.buildLoop(node));
}
_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;
void buildInitializer() {
AbstractValue mask = _typeInferenceMap.typeOfIterator(node);
node.iterable.accept(this);
HInstruction receiver = pop();
_pushDynamicInvocation(
node,
mask,
Selectors.iterator,
<HInstruction>[receiver],
const <DartType>[],
_sourceInformationBuilder.buildForInIterator(node));
iterator = pop();
}
HInstruction buildCondition() {
AbstractValue mask = _typeInferenceMap.typeOfIteratorMoveNext(node);
_pushDynamicInvocation(
node,
mask,
Selectors.moveNext,
<HInstruction>[iterator],
const <DartType>[],
_sourceInformationBuilder.buildForInMoveNext(node));
return popBoolified();
}
void buildBody() {
SourceInformation sourceInformation =
_sourceInformationBuilder.buildForInCurrent(node);
AbstractValue mask = _typeInferenceMap.typeOfIteratorCurrent(node);
_pushDynamicInvocation(node, mask, Selectors.current, [iterator],
const <DartType>[], sourceInformation);
Local loopVariableLocal = localsMap.getLocalVariable(node.variable);
HInstruction value = typeBuilder.potentiallyCheckOrTrustTypeOfAssignment(
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,
localsMap.getCapturedLoopScope(closureDataLookup, 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]));
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 mask = _typeInferenceMap.typeOfIteratorMoveNext(node);
_pushDynamicInvocation(
node,
mask,
Selectors.moveNext,
[streamIterator],
const <DartType>[],
_sourceInformationBuilder.buildForInMoveNext(node));
HInstruction future = pop();
push(new HAwait(future, abstractValueDomain.dynamicType));
return popBoolified();
}
void buildBody() {
AbstractValue mask = _typeInferenceMap.typeOfIteratorCurrent(node);
_pushDynamicInvocation(
node,
mask,
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,
localsMap.getCapturedLoopScope(closureDataLookup, node),
localsMap.getJumpTargetForForIn(node),
buildInitializer,
buildCondition,
buildUpdate,
buildBody,
_sourceInformationBuilder.buildLoop(node));
void finalizerFunction() {
_pushDynamicInvocation(node, null, Selectors.cancel, [streamIterator],
const <DartType>[], _sourceInformationBuilder.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,
localsMap.getCapturedLoopScope(closureDataLookup, node),
localsMap.getJumpTargetForWhile(node),
() {},
buildCondition,
() {}, () {
node.body.accept(this);
}, _sourceInformationBuilder.buildLoop(node));
}
@override
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 =
localsMap.getCapturedLoopScope(closureDataLookup, 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(null, '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 ? node : _elementMap.getSpannable(targetElement, node));
branchBuilder.handleIf(visitCondition, visitThen, visitElse,
sourceInformation: sourceInformation);
}
@override
void visitAsExpression(ir.AsExpression node) {
SourceInformation sourceInformation =
_sourceInformationBuilder.buildAs(node);
node.operand.accept(this);
HInstruction expressionInstruction = pop();
if (node.type is ir.InvalidType) {
generateTypeError('invalid type', sourceInformation);
return;
}
DartType type = _elementMap.getDartType(node.type);
if (!node.isTypeError || options.implicitDowncastCheckPolicy.isEmitted) {
HInstruction converted = typeBuilder.buildTypeConversion(
expressionInstruction,
localsHandler.substInContext(type),
node.isTypeError
? HTypeConversion.CHECKED_MODE_CHECK
: HTypeConversion.CAST_TYPE_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;
if (node.message == null) {
node.condition.accept(this);
_pushStaticInvocation(
_commonElements.assertHelper,
<HInstruction>[pop()],
_typeInferenceMap.getReturnTypeOf(_commonElements.assertHelper),
const <DartType>[]);
pop();
return;
}
// if (assertTest(condition)) assertThrow(message);
void buildCondition() {
node.condition.accept(this);
_pushStaticInvocation(
_commonElements.assertTest,
<HInstruction>[pop()],
_typeInferenceMap.getReturnTypeOf(_commonElements.assertTest),
const <DartType>[]);
}
void fail() {
node.message.accept(this);
_pushStaticInvocation(
_commonElements.assertThrow,
<HInstruction>[pop()],
_typeInferenceMap.getReturnTypeOf(_commonElements.assertThrow),
const <DartType>[]);
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>[
constantSystem.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,
localsMap.getCapturedLoopScope(closureDataLookup, 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: native.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.length != 0) {
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;
}
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(
targetElement, node, abstractValueDomain);
if (!abstractValueDomain.containsAll(type)) {
listInstruction.instructionType = type;
}
stack.add(listInstruction);
}
@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.accept(this);
constructorArgs.add(pop());
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))) {
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>[]);
removeImplicitInstantiation(type);
}
@override
void visitMapEntry(ir.MapEntry mapEntry) {
// Visit value before the key because each will push an expression to the
// stack, so when we pop them off, the key is popped first, then the value.
mapEntry.value.accept(this);
mapEntry.key.accept(this);
}
@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.runtimeTypeToString,
<HInstruction>[value],
abstractValueDomain.stringType,
const <DartType>[],
sourceInformation: sourceInformation);
_pushStaticInvocation(
_commonElements.createRuntimeType,
<HInstruction>[pop()],
_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);
ConstantValue value = _elementMap.getFieldConstantValue(field);
if (value != null) {
if (!field.isAssignable) {
var unit = compiler.backend.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 (!compiler.backend.outputUnitData
.hasOnlyNonDeferredImportPaths(targetElement, field)) {
stack.add(graph.addDeferredConstant(
value, unit, sourceInformation, compiler, closedWorld));
} else {
stack.add(graph.addConstant(value, closedWorld,
sourceInformation: sourceInformation));
}
} else {
push(new HStatic(field, _typeInferenceMap.getInferredTypeOf(field),
sourceInformation));
}
} else {
push(new HLazyStatic(field, _typeInferenceMap.getInferredTypeOf(field),
sourceInformation));
}
} else {
MemberEntity 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>[]);
pop();
} else {
add(new HStaticStore(
abstractValueDomain,
_elementMap.getMember(staticTarget),
typeBuilder.potentiallyCheckOrTrustTypeOfAssignment(
value, _getDartTypeIfValid(staticTarget.setterType))));
}
stack.add(value);
}
@override
void visitPropertyGet(ir.PropertyGet node) {
node.receiver.accept(this);
HInstruction receiver = pop();
_pushDynamicInvocation(
node,
_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,
_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,
_typeInferenceMap.receiverTypeOfDirectGet(node),
new Selector.getter(_elementMap.getMember(node.target).memberName),
<HInstruction>[receiver],
const <DartType>[],