blob: 9e01079e6abf54f3f7d46bdb8c3750983acb0e94 [file] [log] [blame] [edit]
// 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.
// ignore: implementation_imports
import 'package:_js_interop_checks/src/js_interop.dart'
show getDartJSInteropJSName;
// ignore: implementation_imports
import 'package:front_end/src/api_prototype/static_weak_references.dart'
as ir
show StaticWeakReferences;
import 'package:js_runtime/synced/embedded_names.dart';
import 'package:js_shared/synced/embedded_names.dart'
show JsBuiltin, JsGetName, TYPES;
import 'package:kernel/ast.dart' as ir;
import 'package:kernel/type_environment.dart' as ir;
import '../closure.dart';
import '../common.dart';
import '../common/codegen.dart' show CodegenRegistry;
import '../common/elements.dart';
import '../common/names.dart';
import '../constants/constant_system.dart' as constant_system;
import '../constants/values.dart';
import '../deferred_load/output_unit.dart' show OutputUnit;
import '../dump_info.dart';
import '../elements/entities.dart';
import '../elements/jumps.dart';
import '../elements/names.dart';
import '../elements/types.dart';
import '../inferrer/abstract_value_domain.dart';
import '../inferrer/types.dart';
import '../io/source_information.dart';
import '../ir/util.dart';
import '../js/js.dart' as js;
import '../js_backend/backend.dart' show FunctionInlineCache;
import '../js_backend/field_analysis.dart'
show FieldAnalysisData, JFieldAnalysis;
import '../js_backend/interceptor_data.dart';
import '../js_backend/inferred_data.dart';
import '../js_backend/namer.dart' show ModularNamer;
import '../js_backend/native_data.dart';
import '../js_backend/runtime_types_resolution.dart';
import '../js_emitter/code_emitter_task.dart' show ModularEmitter;
import '../js_model/class_type_variable_access.dart';
import '../js_model/element_map.dart';
import '../js_model/elements.dart' show JGeneratorBody, JParameterStub;
import '../js_model/js_strategy.dart';
import '../js_model/js_world.dart' show JClosedWorld;
import '../js_model/locals.dart' show GlobalLocalsMap, JumpVisitor;
import '../js_model/type_recipe.dart';
import '../js_model/records.dart' show RecordData, JRecordGetter;
import '../kernel/invocation_mirror.dart';
import '../native/behavior.dart';
import '../native/js.dart';
import '../options.dart';
import '../tracer.dart';
import '../universe/call_structure.dart';
import '../universe/feature.dart';
import '../universe/record_shape.dart';
import '../universe/selector.dart';
import '../universe/target_checks.dart' show TargetChecks;
import '../universe/use.dart' show ConstantUse, StaticUse, TypeUse;
import 'branch_builder.dart';
import 'jump_handler.dart';
import 'locals_handler.dart';
import 'loop_handler.dart';
import 'metrics.dart';
import 'nodes.dart';
import 'string_builder.dart';
import 'switch_continue_analysis.dart';
import 'type_builder.dart';
// TODO(johnniwinther): Merge this with [KernelInliningState].
class StackFrame {
final StackFrame? parent;
final MemberEntity member;
final AsyncMarker asyncMarker;
final KernelToLocalsMap localsMap;
// [ir.Let] and [ir.LocalInitializer] bindings.
final Map<ir.VariableDeclaration, HInstruction> letBindings;
final KernelToTypeInferenceMap typeInferenceMap;
final SourceInformationBuilder sourceInformationBuilder;
final ir.StaticTypeContext? staticTypeContext;
StackFrame(
this.parent,
this.member,
this.asyncMarker,
this.localsMap,
this.letBindings,
this.typeInferenceMap,
this.sourceInformationBuilder,
this.staticTypeContext,
);
}
class KernelSsaGraphBuilder extends ir.VisitorDefault<void>
with ir.VisitorVoidMixin {
/// Holds the resulting SSA graph.
final HGraph graph = HGraph();
/// True if the builder is processing nodes inside a try statement. This is
/// important for generating control flow out of a try block like returns or
/// breaks.
bool _inTryStatement = false;
/// Used to track the locals while building the graph.
late LocalsHandler localsHandler;
/// A stack of instructions.
///
/// We build the SSA graph by simulating a stack machine.
List<HInstruction> stack = [];
/// The count of nested loops we are currently building.
///
/// The loop nesting is consulted when inlining a function invocation. The
/// inlining heuristics take this information into account.
int loopDepth = 0;
/// A mapping from jump targets to their handlers.
Map<JumpTarget, JumpHandler> jumpTargets = {};
final CompilerOptions options;
final DiagnosticReporter reporter;
final ModularEmitter _emitter;
final ModularNamer _namer;
final MemberEntity targetElement;
final MemberEntity _initialTargetElement;
final JClosedWorld closedWorld;
final CodegenRegistry registry;
final ClosureData _closureDataLookup;
final RecordData _recordData;
final Tracer _tracer;
/// A stack of [InterfaceType]s that have been seen during inlining of
/// factory constructors. These types are preserved in [HInvokeStatic]s and
/// [HCreate]s inside the inline code and registered during code generation
/// for these nodes.
// TODO(karlklose): consider removing this and keeping the (substituted) types
// of the type variables in an environment (like the [LocalsHandler]).
final List<InterfaceType> _currentImplicitInstantiations = [];
/// Used to report information about inlining (which occurs while building the
/// SSA graph), when dump-info is enabled.
final InfoReporter? _infoReporter;
final SsaMetrics _metrics;
HInstruction? _rethrowableException;
final SourceInformationStrategy _sourceInformationStrategy;
final JsToElementMap _elementMap;
final GlobalTypeInferenceResults globalInferenceResults;
late final LoopHandler _loopHandler;
late final TypeBuilder _typeBuilder;
/// True if we are visiting the expression of a throw statement; we assume
/// this is a slow path.
bool _inExpressionOfThrow = false;
final List<KernelInliningState> _inliningStack = [];
Local? _returnLocal;
DartType? _returnType;
StackFrame? _currentFrame;
final FunctionInlineCache _inlineCache;
final InlineDataCache _inlineDataCache;
KernelSsaGraphBuilder(
this.options,
this.reporter,
this._initialTargetElement,
InterfaceType? instanceType,
this._infoReporter,
this._metrics,
this._elementMap,
this.globalInferenceResults,
this.closedWorld,
this.registry,
this._namer,
this._emitter,
this._tracer,
this._sourceInformationStrategy,
this._inlineCache,
this._inlineDataCache,
) : targetElement = _effectiveTargetElementFor(_initialTargetElement),
_closureDataLookup = closedWorld.closureDataLookup,
_recordData = closedWorld.recordData {
_enterFrame(targetElement, null);
_loopHandler = KernelLoopHandler(this);
_typeBuilder = KernelTypeBuilder(this, _elementMap);
graph.element = targetElement;
graph.sourceInformation = _sourceInformationBuilder
.buildVariableDeclaration();
localsHandler = LocalsHandler(
this,
targetElement,
targetElement,
instanceType,
_nativeData,
_interceptorData,
);
}
KernelToLocalsMap get _localsMap => _currentFrame!.localsMap;
Map<ir.VariableDeclaration, HInstruction> get _letBindings =>
_currentFrame!.letBindings;
JCommonElements get _commonElements => _elementMap.commonElements;
JElementEnvironment get _elementEnvironment => _elementMap.elementEnvironment;
JFieldAnalysis get _fieldAnalysis => closedWorld.fieldAnalysis;
KernelToTypeInferenceMap get _typeInferenceMap =>
_currentFrame!.typeInferenceMap;
SourceInformationBuilder get _sourceInformationBuilder =>
_currentFrame!.sourceInformationBuilder;
AbstractValueDomain get _abstractValueDomain =>
closedWorld.abstractValueDomain;
NativeData get _nativeData => closedWorld.nativeData;
InterceptorData get _interceptorData => closedWorld.interceptorData;
RuntimeTypesNeed get _rtiNeed => closedWorld.rtiNeed;
GlobalLocalsMap get _globalLocalsMap =>
globalInferenceResults.globalLocalsMap;
InferredData get _inferredData => globalInferenceResults.inferredData;
DartTypes get dartTypes => closedWorld.dartTypes;
void push(HInstruction instruction) {
add(instruction);
stack.add(instruction);
}
HInstruction pop() {
return stack.removeLast();
}
/// Pushes a boolean checking [expression] against null.
void pushCheckNull(HInstruction expression) {
push(
HIdentity(
expression,
graph.addConstantNull(closedWorld),
_abstractValueDomain.boolType,
),
);
}
HBasicBlock? _current;
/// The current block to add instructions to. Might be null, if we are
/// visiting dead code, but see [_isReachable].
HBasicBlock? get current => _current;
set current(HBasicBlock? c) {
_isReachable = c != null;
_current = c;
}
/// The most recently opened block. Has the same value as [current] while
/// the block is open, but unlike [current], it isn't cleared when the
/// current block is closed.
late HBasicBlock lastOpenedBlock;
/// Indicates whether the current block is dead (because it has a throw or a
/// return further up). If this is false, then [current] may be null. If the
/// block is dead then it may also be aborted, but for simplicity we only
/// abort on statement boundaries, not in the middle of expressions. See
/// [isAborted].
bool _isReachable = true;
/// Is the current statement or expression nested in a [ir.BlockExpression]?
bool _inBlockExpression = false;
HLocalValue? lastAddedParameter;
Map<Local, HInstruction> parameters = {};
late Set<Local> elidedParameters;
HBasicBlock addNewBlock() {
HBasicBlock block = graph.addNewBlock();
// If adding a new block during building of an expression, it is due to
// conditional expressions or short-circuit logical operators.
return block;
}
void open(HBasicBlock block) {
block.open();
current = block;
lastOpenedBlock = block;
}
HBasicBlock close(HControlFlow end) {
HBasicBlock result = current!;
current!.close(end);
current = null;
return result;
}
HBasicBlock _closeAndGotoExit(HControlFlow end) {
HBasicBlock result = current!;
current!.close(end);
current = null;
result.addSuccessor(graph.exit);
return result;
}
void goto(HBasicBlock from, HBasicBlock to) {
from.close(HGoto());
from.addSuccessor(to);
}
void _prepareEntryBlock() {
HBasicBlock block = graph.addNewBlock();
// Create `graph.entry` as an initially empty block. `graph.entry` is
// treated specially (holding parameters, local variables and constants)
// but cannot receive constants before it has been closed. By closing it
// here, we can use constants in the code that sets up the function.
open(graph.entry);
close(HGoto()).addSuccessor(block);
open(block);
}
bool isAborted() {
return current == null;
}
/// Creates a new block, transitions to it from any current block, and
/// opens the new block.
HBasicBlock openNewBlock() {
HBasicBlock newBlock = addNewBlock();
if (!isAborted()) goto(current!, newBlock);
open(newBlock);
return newBlock;
}
void add(HInstruction instruction) {
current!.add(instruction);
}
HLocalValue addParameter(
Entity? parameter,
AbstractValue type, {
bool isElided = false,
}) {
HLocalValue result = isElided
? HLocalValue(parameter, type)
: HParameterValue(parameter, type);
if (lastAddedParameter == null) {
graph.entry.addBefore(graph.entry.first, result);
} else {
graph.entry.addAfter(lastAddedParameter, result);
}
lastAddedParameter = result;
return result;
}
HSubGraphBlockInformation? wrapStatementGraph(SubGraph? statements) {
if (statements == null) return null;
return HSubGraphBlockInformation(statements);
}
HExpressionInformation? wrapExpressionGraph(SubExpression? expression) {
if (expression == null) return null;
return HSubExpressionBlockInformation(expression);
}
HLiteralList _buildLiteralList(List<HInstruction> inputs) {
return HLiteralList(inputs, _abstractValueDomain.growableListType);
}
/// Called when control flow is about to change, in which case we need to
/// specify special successors if we are already in a try/catch/finally block.
void _handleInTryStatement() {
if (!_inTryStatement) return;
HBasicBlock block = close(HExitTry());
HBasicBlock newBlock = graph.addNewBlock();
block.addSuccessor(newBlock);
open(newBlock);
}
DartType _getStaticType(ir.Expression node) {
// TODO(johnniwinther): Substitute the type by the this type and type
// arguments of the current frame.
ir.DartType type = node.getStaticType(_currentFrame!.staticTypeContext!);
return _elementMap.getDartType(type);
}
DartType _getStaticForInIteratorType(ir.ForInStatement node) {
// TODO(johnniwinther): Substitute the type by the this type and type
// arguments of the current frame.
ir.DartType type = node.getIteratorType(_currentFrame!.staticTypeContext!);
return _elementMap.getDartType(type);
}
DartType _getStaticForInElementType(ir.ForInStatement node) {
// TODO(johnniwinther): Substitute the type by the this type and type
// arguments of the current frame.
ir.DartType type = node.getElementType(_currentFrame!.staticTypeContext!);
return _elementMap.getDartType(type);
}
static MemberEntity _effectiveTargetElementFor(MemberEntity member) {
if (member is JGeneratorBody) return member.function;
return member;
}
void _enterFrame(
MemberEntity member,
SourceInformation? callSourceInformation,
) {
AsyncMarker asyncMarker = AsyncMarker.sync;
final function = getFunctionNode(_elementMap, member);
if (function != null) {
asyncMarker = getAsyncMarker(function);
}
final elementMap = closedWorld.elementMap;
final memberNode = elementMap.getMemberContextNode(member);
_currentFrame = StackFrame(
_currentFrame,
member,
asyncMarker,
_globalLocalsMap.getLocalsMap(member),
{},
KernelToTypeInferenceMapImpl(member, globalInferenceResults),
_currentFrame != null
? _currentFrame!.sourceInformationBuilder.forContext(
member,
callSourceInformation,
)
: _sourceInformationStrategy.createBuilderForContext(member),
memberNode != null
? ir.StaticTypeContext(
memberNode,
elementMap.typeEnvironment,
cache: ir.StaticTypeCacheImpl(),
)
: null,
);
}
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) {
// Skip interop extension type object literal constructors as
// that's handled within `visitStaticInvocation`.
// TODO(54968): We should handle the lowering for object literal
// constructors in the interop transformer somehow instead and
// avoid assuming all such members are object literal constructors
// or otherwise paying the cost to verify by indexing extension
// types.
final isObjectLiteralConstructor =
target.isExtensionTypeMember &&
target.function.namedParameters.isNotEmpty;
if (isObjectLiteralConstructor) return null;
_buildExternalFunctionNode(
targetElement as FunctionEntity,
_ensureDefaultArgumentValues(target.function),
);
} else {
_buildFunctionNode(
targetElement as FunctionEntity,
_ensureDefaultArgumentValues(target.function),
);
}
} else if (target is ir.Field) {
FieldAnalysisData fieldData = closedWorld.fieldAnalysis
.getFieldData(targetElement as FieldEntity);
if (fieldData.initialValue != null) {
registry.registerConstantUse(
ConstantUse.init(fieldData.initialValue!),
);
if (targetElement.isStatic || targetElement.isTopLevel) {
/// No code is created for this field: All references inline the
/// constant value.
return null;
}
} else if (fieldData.isLazy) {
registry.registerStaticUse(
StaticUse.staticInvoke(
closedWorld.commonElements.throwLateFieldADI,
CallStructure.oneArg,
),
);
}
if (targetElement.isInstanceMember) {
if (fieldData.isEffectivelyFinal ||
!closedWorld.annotationsData
.getParameterCheckPolicy(targetElement)
.isEmitted) {
// No need for a checked setter.
return null;
}
}
if (targetElement.isInstanceMember) {
_buildInstanceFieldSetter(target);
} else {
_buildStaticFieldInitializer(target);
}
} else if (target is ir.LocalFunction) {
_buildFunctionNode(
targetElement as FunctionEntity,
_ensureDefaultArgumentValues(target.function),
);
} else {
throw 'No case implemented to handle target: '
'$target for $targetElement';
}
break;
case MemberKind.constructor:
final constructor = definition.node as ir.Constructor;
_ensureDefaultArgumentValues(constructor.function);
_buildConstructor(targetElement as ConstructorEntity, constructor);
break;
case MemberKind.constructorBody:
final constructor = definition.node as ir.Constructor;
_ensureDefaultArgumentValues(constructor.function);
_buildConstructorBody(constructor);
break;
case MemberKind.closureField:
// Closure fields have no setter and therefore never require any code.
return null;
case MemberKind.signature:
ir.Node target = definition.node;
ir.FunctionNode originalClosureNode;
if (target is ir.Procedure) {
originalClosureNode = target.function;
} else if (target is ir.LocalFunction) {
originalClosureNode = target.function;
} else {
failedAt(
targetElement,
"Unexpected function signature: "
"$targetElement inside a non-closure: $target",
);
}
_buildMethodSignature(originalClosureNode);
break;
case MemberKind.parameterStub:
_buildParameterStub(
_initialTargetElement as JParameterStub,
_functionNodeOf(definition.node)!,
);
case MemberKind.generatorBody:
_buildGeneratorBody(
_initialTargetElement as JGeneratorBody,
_functionNodeOf(definition.node)!,
);
break;
case MemberKind.recordGetter:
_buildRecordGetter(
_initialTargetElement as JRecordGetter,
definition as RecordGetterDefinition,
);
break;
}
assert(graph.isValid(), "Invalid graph for $_initialTargetElement.");
if (_tracer.isEnabled) {
MemberEntity member = _initialTargetElement;
String name = member.name ?? '<null>';
if (member.isInstanceMember ||
member is ConstructorEntity ||
member.isStatic) {
name = "${member.enclosingClass!.name}.$name";
if (definition.kind == MemberKind.constructorBody) {
name += " (body)";
}
}
_tracer.traceCompilation(name);
_tracer.traceGraph('builder', graph);
}
return graph;
});
}
ir.FunctionNode? _functionNodeOf(ir.Node node) {
if (node is ir.Member) return node.function;
if (node is ir.LocalFunction) return node.function;
return null;
}
ir.FunctionNode _ensureDefaultArgumentValues(ir.FunctionNode function) {
// Register all [function]'s default argument values.
//
// Default values might be (or contain) functions that are not referenced
// from anywhere else so we need to ensure these are enqueued. Stubs and
// `Function.apply` data are created after the codegen queue is closed, so
// we force these functions into the queue by registering the constants as
// used in advance. See language/cyclic_default_values_test.dart for an
// example.
//
// TODO(sra): We could be more precise if stubs and `Function.apply` data
// were generated by the codegen enqueuer. In practice even in huge programs
// there are only very small number of constants created here that are not
// actually used.
void registerDefaultValue(ir.VariableDeclaration node) {
final constantValue = _elementMap.getConstantValue(
node.initializer,
implicitNull: true,
);
assert(
constantValue != null,
failedAt(
_elementMap.getMethod(function.parent as ir.Procedure),
'No constant computed for $node',
),
);
registry.registerConstantUse(ConstantUse.init(constantValue!));
}
function.positionalParameters
.skip(function.requiredParameterCount)
.forEach(registerDefaultValue);
function.namedParameters.forEach(registerDefaultValue);
return function;
}
void _buildInstanceFieldSetter(ir.Field node) {
assert(!node.isStatic);
FieldEntity field = _elementMap.getMember(node) as FieldEntity;
_openFunction(field, checks: TargetChecks.none);
HInstruction thisInstruction = localsHandler.readThis(
sourceInformation: _sourceInformationBuilder.buildGet(node),
);
// Use dynamic type because the type computed by the inferrer is
// narrowed to the type annotation.
HInstruction parameter = HParameterValue(
field,
_abstractValueDomain.dynamicType,
);
// Add the parameter as the last instruction of the entry block.
// If the method is intercepted, we want the actual receiver
// to be the first parameter.
graph.entry.addBefore(graph.entry.last, parameter);
DartType type = _getDartTypeIfValid(node.type);
HInstruction value = _typeBuilder.potentiallyCheckOrTrustTypeOfParameter(
field,
parameter,
type,
);
// TODO(sra): Pass source information to
// [potentiallyCheckOrTrustTypeOfParameter].
// TODO(sra): The source information should indicate the field and
// possibly its type but not the initializer.
value.sourceInformation ??= _sourceInformationBuilder.buildSet(node);
if (!_fieldAnalysis.getFieldData(field).isElided) {
add(HFieldSet(field, thisInstruction, value));
}
_closeFunction();
}
void _buildStaticFieldInitializer(ir.Field node) {
assert(node.isStatic);
graph.isLazyInitializer = true;
FieldEntity field = _elementMap.getMember(node) as FieldEntity;
_openFunction(field, checks: TargetChecks.none);
if (node.initializer != null) {
node.initializer!.accept(this);
HInstruction fieldValue = pop();
HInstruction checkInstruction = _typeBuilder
.potentiallyCheckOrTrustTypeOfAssignment(
field,
fieldValue,
_getDartTypeIfValid(node.type),
);
stack.add(checkInstruction);
} else {
stack.add(graph.addConstantNull(closedWorld));
}
HInstruction value = pop();
_closeAndGotoExit(
HReturn(value, _sourceInformationBuilder.buildReturn(node)),
);
_closeFunction();
}
DartType _getDartTypeIfValid(ir.DartType type) {
if (type is ir.InvalidType) return dartTypes.dynamicType();
return _elementMap.getDartType(type);
}
/// 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 is! ConstructorEntity && member is! ConstructorBodyEntity) {
return;
}
ClassEntity cls = member.enclosingClass!;
InterfaceType thisType = _elementEnvironment.getThisType(cls);
if (thisType.typeArguments.isEmpty) {
return;
}
bool needsTypeArguments = _rtiNeed.classNeedsTypeArguments(cls);
for (var typeVariable in thisType.typeArguments) {
final typeVariableType = typeVariable as 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;
}
}
/// Extend current method parameters with parameters for the function type
/// variables.
void _addFunctionTypeVariablesIfNeeded(MemberEntity member) {
if (member is! FunctionEntity) return;
FunctionEntity function = member;
List<TypeVariableType> typeVariables = _elementEnvironment
.getFunctionTypeVariables(function);
if (typeVariables.isEmpty) {
return;
}
bool needsTypeArguments = _rtiNeed.methodNeedsTypeArguments(function);
bool elideTypeParameters = function.parameterStructure.typeParameters == 0;
for (TypeVariableType typeVariable in typeVariables) {
HInstruction param;
bool erased = false;
if (elideTypeParameters) {
// Add elided type parameters.
param = _computeTypeArgumentDefaultValue(function, typeVariable);
erased = true;
} else if (needsTypeArguments) {
param = addParameter(
typeVariable.element,
_abstractValueDomain.nonNullType,
);
} else {
// Unused, so bind to bound.
param = _computeTypeArgumentDefaultValue(function, typeVariable);
erased = true;
}
Local local = localsHandler.getTypeVariableAsLocal(typeVariable);
localsHandler.directLocals[local] = param;
if (!erased) {
_functionTypeParameterLocals.add(local);
}
}
}
// Locals for function type parameters that can be forwarded, in argument
// position order.
final List<Local> _functionTypeParameterLocals = [];
/// Builds a generative constructor.
///
/// Generative constructors are built in stages, in effect inlining the
/// initializers and constructor bodies up the inheritance chain.
///
/// 1. Extend method parameters with parameters the class's type parameters.
///
/// 2. Add type checks for value parameters (might need result of (1)).
///
/// 3. Walk inheritance chain to build bindings for type parameters of
/// superclasses and mixed-in classes.
///
/// 4. Collect initializer values. Walk up inheritance chain to collect field
/// initializers from field declarations, initializing parameters and
/// initializer.
///
/// 5. Create reified type information for instance.
///
/// 6. Allocate instance and assign initializers and reified type information
/// to fields by calling JavaScript constructor.
///
/// 7. Walk inheritance chain to call or inline constructor bodies.
///
/// All the bindings are put in the constructor's locals handler. The
/// implication is that a class cannot be extended or mixed-in twice. If we in
/// future support repeated uses of a mixin class, we should do so by cloning
/// the mixin class in the Kernel input.
void _buildConstructor(ConstructorEntity constructor, ir.Constructor node) {
final sourceInformation = _sourceInformationBuilder.buildCreate(node);
ClassEntity cls = constructor.enclosingClass;
if (_inliningStack.isEmpty) {
_openFunction(
constructor,
functionNode: node.function,
parameterStructure: constructor.parameterStructure,
checks: TargetChecks.none,
);
}
// [constructorData.fieldValues] accumulates the field initializer values,
// which may be overwritten by initializer-list initializers.
ConstructorData constructorData = ConstructorData();
_buildInitializers(node, constructorData);
List<HInstruction> constructorArguments = [];
// Doing this instead of fieldValues.forEach because we haven't defined the
// order of the arguments here. We can define that with JElements.
bool isCustomElement =
_nativeData.isNativeOrExtendsNative(cls) &&
!_nativeData.isJsInteropClass(cls);
InterfaceType thisType = _elementEnvironment.getThisType(cls);
List<FieldEntity> fields = [];
_elementEnvironment.forEachInstanceField(cls, (
ClassEntity enclosingClass,
FieldEntity member,
) {
HInstruction? value = constructorData.fieldValues[member];
FieldAnalysisData fieldData = _fieldAnalysis.getFieldData(member);
if (value == null) {
assert(
fieldData.isInitializedInAllocator ||
isCustomElement ||
reporter.hasReportedError,
'No initializer value for field $member',
);
} else {
if (!fieldData.isElided) {
fields.add(member);
DartType type = _elementEnvironment.getFieldType(member);
type = localsHandler.substInContext(type);
constructorArguments.add(
_typeBuilder.potentiallyCheckOrTrustTypeOfAssignment(
member,
value,
type,
),
);
}
}
});
_addImplicitInstantiation(thisType);
List<InterfaceType> instantiatedTypes = List<InterfaceType>.from(
_currentImplicitInstantiations,
);
HInstruction newObject;
if (isCustomElement) {
// Bulk assign to the initialized fields.
newObject = graph.explicitReceiverParameter!;
// Null guard ensures an error if we are being called from an explicit
// 'new' of the constructor instead of via an upgrade. It is optimized out
// if there are field initializers.
newObject = HNullCheck(
newObject,
_abstractValueDomain.excludeNull(newObject.instructionType),
)..sourceInformation = sourceInformation;
add(newObject);
for (int i = 0; i < fields.length; i++) {
final value = constructorArguments[i];
add(HFieldSet(fields[i], newObject, value));
}
} else {
// Create the runtime type information, if needed.
bool needsTypeArguments = closedWorld.rtiNeed.classNeedsTypeArguments(
cls,
);
if (needsTypeArguments) {
InterfaceType thisType = _elementEnvironment.getThisType(cls);
HInstruction typeArgument = _typeBuilder.analyzeTypeArgument(
thisType,
sourceElement,
sourceInformation: sourceInformation,
);
constructorArguments.add(typeArgument);
}
newObject = HCreate(
cls,
constructorArguments,
_abstractValueDomain.createNonNullExact(cls),
sourceInformation,
instantiatedTypes: instantiatedTypes,
hasRtiInput: needsTypeArguments,
);
add(newObject);
}
_removeImplicitInstantiation(thisType);
HInstruction? interceptor;
// Generate calls to the constructor bodies.
for (ir.Constructor body in constructorData.constructorChain.reversed) {
if (_isEmptyStatement(body.function.body)) continue;
List<HInstruction> bodyCallInputs = [];
if (isCustomElement) {
if (interceptor == null) {
ConstantValue constant = InterceptorConstantValue(cls);
interceptor = graph.addConstant(constant, closedWorld);
}
bodyCallInputs.add(interceptor);
}
bodyCallInputs.add(newObject);
// Pass uncaptured arguments first, captured arguments in a box, then type
// arguments.
ConstructorEntity inlinedConstructor = _elementMap.getConstructor(body);
_inlinedFrom(
inlinedConstructor,
_sourceInformationBuilder.buildCall(body, body),
() {
final constructorBody = _elementMap.getConstructorBody(body);
void handleParameter(
ir.VariableDeclaration node, {
required bool isElided,
}) {
if (isElided) return;
Local parameter = _localsMap.getLocalVariable(node);
// If [parameter] is boxed, it will be a field in the box passed as
// the last parameter. So no need to directly pass it.
if (!localsHandler.isBoxed(parameter)) {
bodyCallInputs.add(localsHandler.readLocal(parameter));
}
}
// Provide the parameters to the generative constructor body.
forEachOrderedParameter(
_elementMap,
constructorBody,
handleParameter,
);
// If there are locals that escape (i.e. mutated in closures), we pass the
// box to the constructor.
CapturedScope scopeData = _closureDataLookup.getCapturedScope(
constructorBody,
);
if (scopeData.requiresContextBox) {
bodyCallInputs.add(localsHandler.readLocal(scopeData.contextBox!));
}
// Pass type arguments.
ClassEntity inlinedConstructorClass = constructorBody.enclosingClass!;
if (closedWorld.rtiNeed.classNeedsTypeArguments(
inlinedConstructorClass,
)) {
InterfaceType thisType = _elementEnvironment.getThisType(
inlinedConstructorClass,
);
for (DartType typeVariable in thisType.typeArguments) {
DartType result = localsHandler.substInContext(typeVariable);
HInstruction argument = _typeBuilder.analyzeTypeArgument(
result,
sourceElement,
);
bodyCallInputs.add(argument);
}
}
if (!isCustomElement && // TODO(13836): Fix inlining.
_tryInlineMethod(
constructorBody,
null,
null,
bodyCallInputs,
null,
node,
sourceInformation,
)) {
pop();
} else {
_invokeConstructorBody(
body,
bodyCallInputs,
_sourceInformationBuilder.buildDeclaration(constructor),
);
}
},
);
}
if (_inliningStack.isEmpty) {
_closeAndGotoExit(HReturn(newObject, sourceInformation));
_closeFunction();
} else {
localsHandler.updateLocal(
_returnLocal!,
newObject,
sourceInformation: sourceInformation,
);
}
}
static bool _isEmptyStatement(ir.Statement? body) {
if (body is ir.EmptyStatement) return true;
if (body is ir.Block) return body.statements.every(_isEmptyStatement);
return false;
}
void _invokeConstructorBody(
ir.Constructor constructor,
List<HInstruction> inputs,
SourceInformation? sourceInformation,
) {
MemberEntity constructorBody = _elementMap.getConstructorBody(constructor);
HInvokeConstructorBody invoke = HInvokeConstructorBody(
constructorBody as ConstructorBodyEntity,
inputs,
_abstractValueDomain.nonNullType,
sourceInformation,
);
add(invoke);
}
/// Sets context for generating code that is the result of inlining
/// [inlinedTarget].
void _inlinedFrom(
MemberEntity inlinedTarget,
SourceInformation? callSourceInformation,
void Function() f,
) {
reporter.withCurrentElement(inlinedTarget, () {
_enterFrame(inlinedTarget, callSourceInformation);
f();
_leaveFrame();
});
}
void _ensureTypeVariablesForInitializers(
ConstructorData constructorData,
ClassEntity enclosingClass,
) {
if (!constructorData.includedClasses.add(enclosingClass)) return;
if (_rtiNeed.classNeedsTypeArguments(enclosingClass)) {
// If [enclosingClass] needs RTI, we have to give a value to its type
// parameters. For a super constructor call, the type is the supertype
// of current class. For a redirecting constructor, the type is the
// current type. [LocalsHandler.substInContext] takes care of both.
InterfaceType thisType = _elementEnvironment.getThisType(enclosingClass);
final type = localsHandler.substInContext(thisType) as InterfaceType;
List<DartType> arguments = type.typeArguments;
List<DartType> typeVariables = thisType.typeArguments;
assert(arguments.length == typeVariables.length);
Iterator<DartType> variables = typeVariables.iterator;
for (var argument in type.typeArguments) {
variables.moveNext();
final typeVariable = variables.current as TypeVariableType;
localsHandler.updateLocal(
localsHandler.getTypeVariableAsLocal(typeVariable),
_typeBuilder.analyzeTypeArgument(argument, sourceElement),
);
}
}
}
/// Collects the values for field initializers for the direct fields of
/// [clazz].
void _collectFieldValues(ir.Class clazz, ConstructorData constructorData) {
ClassEntity cls = _elementMap.getClass(clazz);
_elementEnvironment.forEachDirectInstanceField(cls, (FieldEntity field) {
_ensureTypeVariablesForInitializers(
constructorData,
field.enclosingClass!,
);
MemberDefinition definition = _elementMap.getMemberDefinition(field);
ir.Field node;
switch (definition.kind) {
case MemberKind.regular:
node = definition.node as ir.Field;
break;
case MemberKind.constructor:
case MemberKind.constructorBody:
case MemberKind.closureCall:
case MemberKind.closureField:
case MemberKind.signature:
case MemberKind.generatorBody:
case MemberKind.recordGetter:
case MemberKind.parameterStub:
failedAt(field, "Unexpected member definition $definition.");
}
bool ignoreAllocatorAnalysis = false;
if (_nativeData.isNativeOrExtendsNative(cls)) {
// @Native classes have 'fields' which are really getters/setter. Do
// not try to initialize e.g. 'tagName'.
if (_nativeData.isNativeClass(cls)) return;
// Fields that survive this test are fields of custom elements.
ignoreAllocatorAnalysis = true;
}
if (ignoreAllocatorAnalysis ||
!_fieldAnalysis.getFieldData(field).isInitializedInAllocator) {
final initializer = node.initializer;
if (initializer == null) {
constructorData.fieldValues[field] = graph.addConstantNull(
closedWorld,
);
} else {
// Compile the initializer in the context of the field so we know that
// class type parameters are accessed as values.
// TODO(sra): It would be sufficient to know the context was a field
// initializer.
_inlinedFrom(
field,
_sourceInformationBuilder.buildAssignment(initializer),
() {
initializer.accept(this);
constructorData.fieldValues[field] = pop();
},
);
}
}
});
}
static bool _isRedirectingConstructor(ir.Constructor constructor) =>
constructor.initializers.any(
(initializer) => initializer is ir.RedirectingInitializer,
);
/// Collects field initializers all the way up the inheritance chain.
void _buildInitializers(
ir.Constructor constructor,
ConstructorData constructorData,
) {
assert(
_elementMap.getConstructor(constructor) == _localsMap.currentMember,
failedAt(
_localsMap.currentMember,
'Expected ${_localsMap.currentMember} '
'but found ${_elementMap.getConstructor(constructor)}.',
),
);
constructorData.constructorChain.add(constructor);
if (!_isRedirectingConstructor(constructor)) {
// Compute values for field initializers, but only if this is not a
// redirecting constructor, since the target will compute the fields.
_collectFieldValues(constructor.enclosingClass, constructorData);
}
var foundSuperOrRedirectCall = false;
for (var initializer in constructor.initializers) {
if (initializer is ir.FieldInitializer) {
FieldEntity field = _elementMap.getField(initializer.field);
if (!_fieldAnalysis.getFieldData(field).isInitializedInAllocator) {
initializer.value.accept(this);
constructorData.fieldValues[field] = pop();
}
} else if (initializer is ir.SuperInitializer) {
assert(!foundSuperOrRedirectCall);
foundSuperOrRedirectCall = true;
_inlineSuperInitializer(initializer, constructorData, constructor);
} else if (initializer is ir.RedirectingInitializer) {
assert(!foundSuperOrRedirectCall);
foundSuperOrRedirectCall = true;
_inlineRedirectingInitializer(
initializer,
constructorData,
constructor,
);
} else if (initializer is ir.LocalInitializer) {
// LocalInitializer is like a let-expression that is in scope for the
// rest of the initializers.
ir.VariableDeclaration variable = initializer.variable;
assert(variable.isFinal);
variable.initializer!.accept(this);
HInstruction value = pop();
// TODO(sra): Apply inferred type information.
_letBindings[variable] = value;
} else if (initializer is ir.AssertInitializer) {
initializer.statement.accept(this);
} else if (initializer is ir.InvalidInitializer) {
assert(false, 'ir.InvalidInitializer not handled');
} else {
assert(false, 'Unhandled initializer ir.${initializer.runtimeType}');
}
}
if (!foundSuperOrRedirectCall) {
assert(
_elementMap.getClass(constructor.enclosingClass) ==
_elementMap.commonElements.objectClass ||
constructor.isExternal ||
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,
) {
List<HInstruction> builtArguments = [];
var positionalIndex = 0;
for (var parameter in function.positionalParameters) {
if (positionalIndex < arguments.positional.length) {
arguments.positional[positionalIndex++].accept(this);
builtArguments.add(pop());
} else {
builtArguments.add(_defaultValueForParameter(parameter));
}
}
// Evaluate named arguments in given order.
Map<String, HInstruction> namedArguments = _visitNamedArguments(arguments);
// And add them to `builtArguments` in calling-convention order.
function.namedParameters.toList()
..sort(namedOrdering)
..forEach((ir.VariableDeclaration parameter) {
var argument = namedArguments[parameter.name];
argument ??= _defaultValueForParameter(parameter);
builtArguments.add(argument);
});
return builtArguments;
}
/// Inlines the given redirecting [constructor]'s initializers by collecting
/// its field values and building its constructor initializers. We visit super
/// constructors all the way up to the [Object] constructor.
void _inlineRedirectingInitializer(
ir.RedirectingInitializer initializer,
ConstructorData constructorData,
ir.Constructor caller,
) {
ir.Constructor superOrRedirectConstructor = initializer.target;
List<HInstruction> arguments = _normalizeAndBuildArguments(
superOrRedirectConstructor.function,
initializer.arguments,
);
// Redirecting initializer already has [localsHandler] bindings for type
// parameters from the redirecting constructor.
// For redirecting constructors, the fields will be initialized later by the
// effective target, so we don't do it here.
_inlineSuperOrRedirectCommon(
initializer,
superOrRedirectConstructor,
arguments,
constructorData,
caller,
);
}
/// Inlines the given super [constructor]'s initializers by collecting its
/// field values and building its constructor initializers. We visit super
/// constructors all the way up to the [Object] constructor.
void _inlineSuperInitializer(
ir.SuperInitializer initializer,
ConstructorData constructorData,
ir.Constructor caller,
) {
ir.Constructor target = initializer.target;
List<HInstruction> arguments = _normalizeAndBuildArguments(
target.function,
initializer.arguments,
);
ir.Class callerClass = caller.enclosingClass;
ir.Supertype supertype = callerClass.supertype!;
// The class of the super-constructor may not be the supertype class. In
// this case, we must go up the class hierarchy until we reach the class
// containing the super-constructor.
while (supertype.classNode != target.enclosingClass) {
// Fields from unnamed mixin application classes (ie Object&Foo) get
// "collected" with the regular supertype fields, so we must bind type
// parameters from both the supertype and the supertype's mixin classes
// before collecting the field values.
_collectFieldValues(supertype.classNode, constructorData);
supertype = supertype.classNode.supertype!;
}
_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);
final oldScopeMember = localsHandler.scopeMember;
_inlinedFrom(
element,
_sourceInformationBuilder.buildCall(initializer, initializer),
() {
void handleParameter(ir.VariableDeclaration node) {
Local parameter = _localsMap.getLocalVariable(node);
HInstruction argument = arguments[index++];
// Because we are inlining the initializer, we must update
// what was given as parameter. This will be used in case
// there is a parameter check expression in the initializer.
parameters[parameter] = argument;
localsHandler.updateLocal(parameter, argument);
}
constructor.function.positionalParameters.forEach(handleParameter);
constructor.function.namedParameters.toList()
..sort(namedOrdering)
..forEach(handleParameter);
_ensureTypeVariablesForInitializers(
constructorData,
element.enclosingClass,
);
// Set the locals handler state as if we were inlining the constructor.
localsHandler.setupScope(element);
localsHandler.enterScope(
_closureDataLookup.getCapturedScope(element),
_sourceInformationBuilder.buildDeclaration(element),
);
_buildInitializers(constructor, constructorData);
},
);
localsHandler.setupScope(oldScopeMember);
}
/// Constructs a special signature function for a closure.
void _buildMethodSignature(ir.FunctionNode originalClosureNode) {
// The signature function has no corresponding ir.Node, so we just use the
// targetElement to set up the type environment.
_openFunction(targetElement, checks: TargetChecks.none);
FunctionType functionType = _elementMap.getFunctionType(
originalClosureNode,
);
HInstruction rti = _typeBuilder.analyzeTypeArgument(
functionType,
sourceElement,
);
close(
HReturn(rti, _sourceInformationBuilder.buildReturn(originalClosureNode)),
).addSuccessor(graph.exit);
_closeFunction();
}
/// Constructs a parameter stub based on the signature of [stubMember].
void _buildParameterStub(
JParameterStub stubMember,
ir.FunctionNode targetFunctionNode,
) {
final stubTarget = stubMember.target;
final stubParameterStructure = stubMember.parameterStructure;
_openFunction(
stubMember,
functionNode: targetFunctionNode,
parameterStructure: stubParameterStructure,
checks: TargetChecks.none,
);
final sourceInformation = _sourceInformationBuilder.buildStub(
stubTarget,
stubParameterStructure.callStructure,
);
final isIntercepted = closedWorld.interceptorData.isInterceptedMethod(
stubMember,
);
final isNativeOrJsInterop = closedWorld.nativeData.hasFixedBackendName(
stubTarget,
);
List<HInstruction> invokeInputs = [];
if (stubMember.isInstanceMember) {
if (isIntercepted) {
// Intercepted instance members need to include the receiver parameter
// in the set of inputs.
final receiverParameter = graph.explicitReceiverParameter!;
final interceptor = _interceptorFor(
receiverParameter,
sourceInformation,
);
if (!isNativeOrJsInterop) {
// Intercepted stubbed native calls are invoked directly on the
// receiver rather than forwarding to another interceptor.
invokeInputs.add(interceptor);
}
invokeInputs.add(receiverParameter);
} else {
// Non-intercepted instance member calls use `this` as the receiver.
// Use `graph.thisInstruction` which accounts for `this` being different
// in closures.
invokeInputs.add(graph.thisInstruction!);
}
}
final stubNamedParameters = stubParameterStructure.requiredNamedParameters;
final parameterLocals = parameters.keys.toList();
int? indexOfLastOptionalArgumentInParameters =
invokeInputs.length + stubParameterStructure.positionalParameters;
void updateLocalIfNecessary(DartType type, Local local) {
if (!isNativeOrJsInterop) return;
type = type.withoutNullability;
if (type is FunctionType) {
push(
HInvokeStatic(
_commonElements.closureConverter,
[
localsHandler.readLocal(local),
graph.addConstantInt(type.parameterTypes.length, closedWorld),
],
_abstractValueDomain.functionType,
const [],
targetCanThrow: false,
),
);
localsHandler.updateLocal(
local,
pop(),
sourceInformation: sourceInformation,
);
}
}
int count = 0;
_elementEnvironment.forEachParameter(stubTarget, (
DartType type,
String? name,
ConstantValue? value,
) {
if (count < stubParameterStructure.positionalParameters) {
final local = parameterLocals[count];
updateLocalIfNecessary(type, local);
invokeInputs.add(localsHandler.readLocal(local));
} else if (stubNamedParameters.contains(name)) {
// The locals may not match the order of this forEach so find the right
// one linearly.
final local = parameterLocals.firstWhere(
(parameter) => parameter.name == name,
);
updateLocalIfNecessary(type, local);
invokeInputs.add(localsHandler.readLocal(local));
indexOfLastOptionalArgumentInParameters = invokeInputs.length;
} else if (value == null) {
invokeInputs.add(graph.addConstantNull(closedWorld));
} else {
final defaultValue = graph.addConstant(value, closedWorld);
invokeInputs.add(defaultValue);
if (!defaultValue.isConstantNull()) {
indexOfLastOptionalArgumentInParameters = invokeInputs.length;
}
}
count++;
});
final targetTypeArguments = closedWorld.elementEnvironment
.getFunctionTypeVariables(stubTarget);
if (targetTypeArguments.isNotEmpty) {
if (stubParameterStructure.typeParameters == 0) {
// This stub does not include type parameters so use RTI to make the
// appropriate defaults.
for (final typeVariable in targetTypeArguments) {
invokeInputs.add(
_typeBuilder.analyzeTypeArgument(typeVariable, stubMember),
);
}
} else {
// `_functionTypeParameterLocals` might be empty if type arguments are
// elided from the target member.
for (final local in _functionTypeParameterLocals) {
invokeInputs.add(localsHandler.readLocal(local));
}
}
}
// We treat the return type of all these invocations as dynamic.
// Though we can know the return type of the target method, this doesn't
// help us emit better code.
final returnType = _abstractValueDomain.dynamicType;
if (isNativeOrJsInterop) {
// Native calls don't pass trailing null optional parameters.
final nativeInputs = invokeInputs.sublist(
0,
indexOfLastOptionalArgumentInParameters,
);
push(
HInvokeExternal(
stubTarget,
nativeInputs,
returnType,
closedWorld.nativeData.getNativeMethodBehavior(stubTarget),
sourceInformation: sourceInformation,
),
);
_maybeAddInteropNullAssertionForMember(
stubTarget,
nativeInputs.length,
sourceInformation: sourceInformation,
);
} else if (stubTarget.isInstanceMember) {
if (stubTarget.enclosingClass!.isClosure) {
push(
HInvokeClosure(
stubTarget.parameterStructure.callStructure.callSelector,
_abstractValueDomain.dynamicType,
invokeInputs,
returnType,
targetTypeArguments,
),
);
} else if (stubMember.needsSuper) {
push(
HInvokeSuper(
stubTarget,
stubMember.enclosingClass!,
Selector.fromElement(stubTarget),
invokeInputs,
isIntercepted,
returnType,
targetTypeArguments,
sourceInformation,
isSetter: false,
),
);
} else {
push(
HInvokeDynamicMethod(
Selector.fromElement(stubTarget),
_abstractValueDomain.dynamicType,
invokeInputs,
returnType,
targetTypeArguments,
sourceInformation,
isIntercepted: isIntercepted,
)..element = stubTarget,
);
}
} else {
push(
HInvokeStatic(
stubTarget,
invokeInputs,
returnType,
targetTypeArguments,
isIntercepted: isIntercepted,
targetCanThrow: !_inferredData.getCannotThrow(stubTarget),
),
);
}
close(HReturn(pop(), sourceInformation)).addSuccessor(graph.exit);
_closeFunction();
}
/// Builds generative constructor body.
void _buildConstructorBody(ir.Constructor constructor) {
FunctionEntity constructorBody = _elementMap.getConstructorBody(
constructor,
);
_openFunction(
constructorBody,
functionNode: constructor.function,
parameterStructure: constructorBody.parameterStructure,
checks: TargetChecks.none,
);
if (!constructorBody.isExternal) {
constructor.function.body!.accept(this);
}
_closeFunction();
}
void _buildRecordGetter(
JRecordGetter getter,
RecordGetterDefinition definition,
) {
ClassEntity getterClass = getter.enclosingClass!;
int indexInShape = definition.indexInShape;
final representation = _recordData.representationForClass(getterClass)!;
final path = _recordData.pathForAccess(representation.shape, indexInShape);
// TODO(50081): Attribute all synthetic records code to the nearest class
// declared in Dart. Worst case, it can all be attributed to the `Record`
// interface.
SourceInformation? sourceInformation;
// Manually set up entry. This does not work...
//
// _openFunction(getter, checks: TargetChecks.none);
//
// ...since we don't have a scope model for the localsHandler. What we
// should have is a lightweight localsHandler for synthetic methods.
//
// TODO(51310): Split [_openFunction] into parts which can be used for
// synthetic methods.
_prepareEntryBlock();
// Create a 'this' parameter.
//
// Intercepted getters have two parameters (this, receiver) and other
// getters have one (this). Add them at the beginning of the entry block.
final typeOfThis = _abstractValueDomain.createNonNullSubclass(getterClass);
HThis thisInstruction = HThis(null, typeOfThis);
graph.thisInstruction = thisInstruction;
graph.entry.addAtEntry(thisInstruction);
lastAddedParameter = thisInstruction;
if (_interceptorData.isInterceptedMethod(getter)) {
SyntheticLocal parameter = localsHandler.createLocal('receiver');
HParameterValue value = HParameterValue(parameter, typeOfThis);
graph.explicitReceiverParameter = value;
graph.entry.addAfter(thisInstruction, value);
lastAddedParameter = value;
}
HInstruction receiver = thisInstruction;
AbstractValue resultType = _abstractValueDomain.dynamicType;
if (path.index == null) {
HFieldGet fieldGet = HFieldGet(
path.field,
receiver,
resultType,
sourceInformation,
isAssignable: false,
);
push(fieldGet);
} else {
HFieldGet fieldGet = HFieldGet(
path.field,
receiver,
_abstractValueDomain.constListType,
sourceInformation,
isAssignable: false,
);
push(fieldGet);
final list = pop();
push(
HIndex(
list,
graph.addConstantInt(path.index!, closedWorld),
resultType,
),
);
}
HInstruction value = pop();
_closeAndGotoExit(HReturn(value, sourceInformation));
_closeFunction();
}
/// Builds an SSA graph for FunctionNodes, found in FunctionExpressions and
/// Procedures.
void _buildFunctionNode(
FunctionEntity function,
ir.FunctionNode functionNode,
) {
if (functionNode.asyncMarker != ir.AsyncMarker.Sync) {
if (functionNode.asyncMarker == ir.AsyncMarker.SyncStar) {
_buildSyncStarGenerator(function, functionNode);
} else {
_buildGenerator(function, functionNode);
}
return;
}
_openFunction(
function,
functionNode: functionNode,
parameterStructure: function.parameterStructure,
checks: _checksForFunction(function),
);
if (options.experimentUnreachableMethodsThrow) {
var emptyParameters = parameters.values.where(
(parameter) => _abstractValueDomain
.isEmpty(parameter.instructionType)
.isDefinitelyTrue,
);
if (emptyParameters.isNotEmpty) {
_addComment('$emptyParameters inferred as [empty]');
add(
HInvokeStatic(
_commonElements.assertUnreachableMethod,
[],
_abstractValueDomain.dynamicType,
const [],
),
);
_closeFunction();
return;
}
}
functionNode.body!.accept(this);
_closeFunction();
}
/// Adds a JavaScript comment to the output. The comment will be omitted in
/// minified mode. Each line in [text] is preceded with `//` and indented.
/// Use sparingly. In order for the comment to be retained it is modeled as
/// having side effects which will inhibit code motion.
// TODO(sra): Figure out how to keep comment anchored without effects.
void _addComment(String text) {
add(
HForeignCode(
js.js.statementTemplateYielding(js.Comment(text)),
_abstractValueDomain.dynamicType,
[],
isStatement: true,
),
);
}
/// Builds an SSA graph for a sync*/async/async* generator. We generate a
/// entry function which tail-calls a body function. The entry contains
/// per-invocation checks and the body, which is later transformed, contains
/// the reentrant 'state machine' code.
void _buildGenerator(FunctionEntity function, ir.FunctionNode functionNode) {
_openFunction(
function,
functionNode: functionNode,
parameterStructure: function.parameterStructure,
checks: _checksForFunction(function),
);
// Prepare to tail-call the body.
// Is 'buildAsyncBody' the best location for the entry?
var sourceInformation = _sourceInformationBuilder.buildAsyncBody();
// Forward all the parameters to the body.
List<HInstruction> inputs = [];
if (graph.thisInstruction != null) {
inputs.add(graph.thisInstruction!);
}
if (graph.explicitReceiverParameter != null) {
inputs.add(graph.explicitReceiverParameter!);
}
for (Local local in parameters.keys) {
if (!elidedParameters.contains(local)) {
inputs.add(localsHandler.readLocal(local));
}
}
for (Local local in _functionTypeParameterLocals) {
inputs.add(localsHandler.readLocal(local));
}
// Add the type parameter for the generator's element type.
DartType elementType = _elementEnvironment.getAsyncOrSyncStarElementType(
function,
_returnType!,
);
// TODO(sra): [elementType] can contain free type variables that are erased
// due to no rtiNeed. We will get getter code if these type variables are
// substituted with an <any> or <erased> type.
if (elementType.containsFreeTypeVariables) {
// Type must be computed in the entry function, where the type variables
// are in scope, and passed to the body function.
inputs.add(_typeBuilder.analyzeTypeArgument(elementType, function));
} else {
// Types with no type variables can be emitted as part of the generator,
// avoiding an extra argument.
if (_generatedEntryIsEmpty()) {
// If the entry function is empty (e.g. no argument checks) and the type
// can be generated in body, 'inline' the body by generating it in
// place. This works because the subsequent transformation of the code
// is 'correct' for the empty entry function code.
graph.needsAsyncRewrite = true;
graph.asyncElementType = elementType;
functionNode.body!.accept(this);
_closeFunction();
return;
}
}
JGeneratorBody body = _elementMap.getGeneratorBody(function);
push(
HInvokeGeneratorBody(
body,
inputs,
_abstractValueDomain.dynamicType, // TODO: better type.
sourceInformation,
),
);
_closeAndGotoExit(HReturn(pop(), sourceInformation));
_closeFunction();
}
/// Builds an SSA graph for a sync* method. A sync* method is split into an
/// entry function and a body function. The entry function calls the body
/// function and wraps the result in an `_SyncStarIterable<T>`. The body
/// function is a separate entity (GeneratorBodyEntity) that is compiled via
/// SSA and the transformed into a reentrant state-machine.
///
/// Here we generate the entry function which is approximately like this:
///
/// Iterable<T> foo(parameters) {
/// return _makeSyncStarIterable<T>(foo$body(parameters));
/// }
void _buildSyncStarGenerator(
FunctionEntity function,
ir.FunctionNode functionNode,
) {
_openFunction(
function,
functionNode: functionNode,
parameterStructure: function.parameterStructure,
checks: _checksForFunction(function),
);
// Prepare to call the body generator.
// Is 'buildAsyncBody' the best location for the entry?
var sourceInformation = _sourceInformationBuilder.buildAsyncBody();
// Forward all the parameters to the body.
List<HInstruction> inputs = [];
if (graph.thisInstruction != null) {
inputs.add(graph.thisInstruction!);
}
if (graph.explicitReceiverParameter != null) {
inputs.add(graph.explicitReceiverParameter!);
}
for (Local local in parameters.keys) {
if (!elidedParameters.contains(local)) {
inputs.add(localsHandler.readLocal(local));
}
}
for (Local local in _functionTypeParameterLocals) {
inputs.add(localsHandler.readLocal(local));
}
JGeneratorBody body = _elementMap.getGeneratorBody(function);
push(
HInvokeGeneratorBody(
body,
inputs,
_abstractValueDomain.dynamicType, // Untyped JavaScript thunk.
sourceInformation,
),
);
// Call `_makeSyncStarIterable<T>(body)`. This usually gets inlined.
final elementType = _elementEnvironment.getAsyncOrSyncStarElementType(
function,
_returnType!,
);
FunctionEntity method = _commonElements.syncStarIterableFactory;
List<HInstruction> arguments = [pop()];
List<DartType> typeArguments = const [];
if (_rtiNeed.methodNeedsTypeArguments(method)) {
typeArguments = [elementType];
_addTypeArguments(arguments, typeArguments, sourceInformation);
}
_pushStaticInvocation(
method,
arguments,
_typeInferenceMap.getReturnTypeOf(method),
typeArguments,
sourceInformation: sourceInformation,
);
_closeAndGotoExit(HReturn(pop(), sourceInformation));
_closeFunction();
}
/// Builds an SSA graph for a async/async* generator body.
void _buildGeneratorBody(
JGeneratorBody function,
ir.FunctionNode functionNode,
) {
FunctionEntity entry = function.function;
_openFunction(
entry,
functionNode: functionNode,
parameterStructure: function.parameterStructure,
checks: TargetChecks.none,
);
graph.needsAsyncRewrite = true;
if (!function.elementType.containsFreeTypeVariables) {
// We can generate the element type in place
graph.asyncElementType = function.elementType;
}
functionNode.body!.accept(this);
_closeFunction();
}
bool _generatedEntryIsEmpty() {
HBasicBlock block = current!;
// If `block.id` is not 1 then we generated some control flow.
if (block.id != 1) return false;
for (HInstruction? node = block.first; node != null; node = node.next) {
if (node is HGoto) continue;
if (node is HLoadType) continue; // Orphaned if check is redundant.
return false;
}
return true;
}
void _potentiallyAddFunctionParameterTypeChecks(
MemberEntity member,
ir.FunctionNode function,
TargetChecks targetChecks,
) {
// Put the type checks in the first successor of the entry,
// because that is where the type guards will also be inserted.
// This way we ensure that a type guard will dominate the type
// check.
if (targetChecks.checkTypeParameters) {
_checkTypeVariableBounds(targetElement as FunctionEntity);
}
MemberDefinition definition = _elementMap.getMemberDefinition(
targetElement,
);
bool nodeIsConstructorBody = definition.kind == MemberKind.constructorBody;
void handleParameter(ir.VariableDeclaration variable) {
Local local = _localsMap.getLocalVariable(variable);
if (nodeIsConstructorBody &&
_closureDataLookup
.getCapturedScope(targetElement)
.isBoxedVariable(_localsMap, local)) {
// If local is boxed, then `variable` will be a field inside the box
// passed as the last parameter, so no need to update our locals
// handler or check types at this point.
return;
}
if (elidedParameters.contains(local)) {
// Elided parameters are initialized to a default value that is
// statically checked.
return;
}
HInstruction newParameter = localsHandler.readLocal(local);
DartType type = _getDartTypeIfValid(variable.type);
if (targetChecks.checkAllParameters ||
(targetChecks.checkCovariantParameters &&
(variable.isCovariantByClass ||
variable.isCovariantByDeclaration))) {
newParameter = _typeBuilder.potentiallyCheckOrTrustTypeOfParameter(
targetElement,
newParameter,
type,
);
} else {
newParameter = _typeBuilder.trustTypeOfParameter(
targetElement,
newParameter,
type,
);
}
localsHandler.updateLocal(local, newParameter);
}
function.positionalParameters.forEach(handleParameter);
function.namedParameters.toList().forEach(handleParameter);
}
void _checkTypeVariableBounds(FunctionEntity method) {
if (_rtiNeed.methodNeedsTypeArguments(method) &&
closedWorld.annotationsData.getParameterCheckPolicy(method).isEmitted) {
ir.FunctionNode function = getFunctionNode(_elementMap, method)!;
for (ir.TypeParameter typeParameter in function.typeParameters) {
Local local = _localsMap.getLocalTypeVariableEntity(
_elementMap
.getTypeVariableType(
ir.TypeParameterType(typeParameter, ir.Nullability.nonNullable),
)
.element,
);
HInstruction newParameter = localsHandler.directLocals[local]!;
DartType bound = _getDartTypeIfValid(typeParameter.bound);
if (!dartTypes.isTopType(bound)) {
registry.registerTypeUse(TypeUse.typeVariableBoundCheck(bound));
// TODO(sigmund): method name here is not minified, should it be?
_checkTypeBound(newParameter, bound, local.name!, method.name!);
}
}
}
}
/// Builds an SSA graph for FunctionNodes of external methods.
void _buildExternalFunctionNode(
FunctionEntity function,
ir.FunctionNode functionNode,
) {
assert(functionNode.body == null);
if (closedWorld.nativeData.isNativeMember(targetElement)) {
_buildExternalNativeFunctionNode(function, functionNode);
return;
}
if (function.name == '==') {
if (_buildSpecialRuntimeEqualsMethod(function, functionNode)) return;
}
// `external` functions in `dart:_foreign_helper` are queued for compilation
// in a modular or staged compile, so just generate an empty function. The
// actual call sites for these methods are recognized and replaced, so the
// method generated here is never called.
if (_commonElements.isForeignHelper(function)) {
_openFunction(
function,
functionNode: functionNode,
parameterStructure: function.parameterStructure,
checks: _checksForFunction(function),
);
_closeFunction();
return;
}
failedAt(currentElementSpannable, 'Unknown external method $function');
}
bool _buildSpecialRuntimeEqualsMethod(
FunctionEntity function,
ir.FunctionNode functionNode,
) {
assert(function.name == '==');
if (function.enclosingClass == _commonElements.jsNullClass) {
_openFunction(
function,
functionNode: functionNode,
parameterStructure: function.parameterStructure,
checks: _checksForFunction(function),
);
HInstruction instance = graph.addConstantNull(closedWorld);
HInstruction parameter = parameters.values.first;
HInstruction value = HIdentity(
instance,
parameter,
_abstractValueDomain.boolType,
);
add(value);
_closeAndGotoExit(
HReturn(value, _sourceInformationBuilder.buildReturn(functionNode)),
);
_closeFunction();
return true;
}
return false;
}
/// Builds an SSA graph for FunctionNodes of external methods that are
/// 'native' or 'js-interop' methods. This produces a graph for a method with
/// Dart calling conventions that forwards to the actual JavaScript `external`
/// method.
void _buildExternalNativeFunctionNode(
FunctionEntity function,
ir.FunctionNode functionNode,
) {
bool isJsInterop = closedWorld.nativeData.isJsInteropMember(function);
_openFunction(
function,
functionNode: functionNode,
parameterStructure: function.parameterStructure,
checks: _checksForFunction(function),
);
List<HInstruction> inputs = [];
if (targetElement.isInstanceMember) {
inputs.add(
localsHandler.readThis(
sourceInformation: _sourceInformationBuilder.buildGet(functionNode),
),
);
}
void handleParameter(ir.VariableDeclaration param) {
Local local = _localsMap.getLocalVariable(param);
// Convert Dart function to JavaScript function.
HInstruction argument = localsHandler.readLocal(local);
ir.DartType type = param.type;
if (!isJsInterop && type is ir.FunctionType) {
int arity = type.positionalParameters.length;
_pushStaticInvocation(
_commonElements.closureConverter,
[argument, graph.addConstantInt(arity, closedWorld)],
_abstractValueDomain.dynamicType,
const <DartType>[],
sourceInformation: null,
);
argument = pop();
}
inputs.add(argument);
}
for (
int position = 0;
position < function.parameterStructure.positionalParameters;
position++
) {
handleParameter(functionNode.positionalParameters[position]);
}
if (functionNode.namedParameters.isNotEmpty) {
List<ir.VariableDeclaration> namedParameters = functionNode
.namedParameters
// Filter elided parameters.
.where(
(p) => function.parameterStructure.namedParameters.contains(p.name),
)
.toList();
// Sort by file offset to visit parameters in declaration order.
namedParameters.sort(nativeOrdering);
namedParameters.forEach(handleParameter);
}
NativeBehavior nativeBehavior = _nativeData.getNativeMethodBehavior(
function,
);
AbstractValue returnType = _typeInferenceMap.typeFromNativeBehavior(
nativeBehavior,
closedWorld,
);
push(
HInvokeExternal(
targetElement as FunctionEntity,
inputs,
returnType,
nativeBehavior,
sourceInformation: null,
),
);
HInstruction value;
final sourceInformation = _sourceInformationBuilder.buildCall(
functionNode,
functionNode,
);
if (options.nativeNullAssertions && nodeIsInWebLibrary(functionNode)) {
value = pop();
DartType type = _getDartTypeIfValid(functionNode.returnType);
if (dartTypes.isNonNullable(type)) {
push(
HNullCheck(
value,
_abstractValueDomain.excludeNull(returnType),
sticky: true,
)..sourceInformation = sourceInformation,
);
value = pop();
}
} else if (_nativeData.isJsInteropMember(targetElement)) {
if (targetElement.isInstanceMember) {
_maybeAddInteropNullAssertionForMember(
targetElement as FunctionEntity,
inputs.length,
sourceInformation: sourceInformation,
);
} else {
_maybeAddInteropNullAssertionForStatic(
_getDartTypeIfValid(functionNode.returnType),
sourceInformation: sourceInformation,
);
}
value = pop();
} else {
value = pop();
}
if (targetElement.isSetter) {
_closeAndGotoExit(HGoto());
} else {
_emitReturn(value, _sourceInformationBuilder.buildReturn(functionNode));
}
_closeFunction();
}
void _addImplicitInstantiation(DartType? type) {
if (type is InterfaceType) {
_currentImplicitInstantiations.add(type);
} else {
assert(type == null);
}
}
void _removeImplicitInstantiation(DartType? type) {
if (type != null) {
_currentImplicitInstantiations.removeLast();
}
}
TargetChecks _checksForFunction(FunctionEntity function) {
if (!function.isInstanceMember) {
// Static methods with no tear-off can be generated with no checks.
final access = closedWorld.getMemberAccess(function);
if (access != null && access.reads.isEmpty) {
return TargetChecks.none;
}
}
// TODO(sra): Instance methods can be generated with reduced checks if
// called only from non-dynamic call-sites.
return TargetChecks.dynamicChecks;
}
void _openFunction(
MemberEntity member, {
ir.FunctionNode? functionNode,
ParameterStructure? parameterStructure,
required TargetChecks checks,
}) {
Map<Local, AbstractValue> parameterMap = {};
List<ir.VariableDeclaration> elidedParameters = [];
Set<Local> elidedParameterSet = {};
if (functionNode != null) {
void handleParameter(
ir.VariableDeclaration node, {
required bool isOptional,
required bool isElided,
}) {
Local local = _localsMap.getLocalVariable(node);
if (isElided) {
elidedParameters.add(node);
elidedParameterSet.add(local);
}
parameterMap[local] = _typeInferenceMap.getInferredTypeOfParameter(
local,
member,
);
}
forEachOrderedParameterByFunctionNode(
functionNode,
parameterStructure!,
handleParameter,
);
_returnType = _elementMap.getDartType(functionNode.returnType);
}
_prepareEntryBlock();
localsHandler.startFunction(
targetElement,
parameterMap,
elidedParameterSet,
_sourceInformationBuilder.buildDeclaration(targetElement),
isGenerativeConstructorBody: targetElement is ConstructorBodyEntity,
);
ir.Member? memberContextNode = _elementMap.getMemberContextNode(member);
if (memberContextNode != null) {
for (ir.VariableDeclaration node in elidedParameters) {
Local local = _localsMap.getLocalVariable(node);
localsHandler.updateLocal(local, _defaultValueForParameter(node));
}
}
_addClassTypeVariablesIfNeeded(member);
_addFunctionTypeVariablesIfNeeded(member);
// If [member] is `operator==` we explicitly add a null check at the
// beginning of the method. This is to avoid having call sites do the null
// check. The null check is added before the argument type checks since in
// strong mode, the parameter type might be non-nullable.
if (member is FunctionEntity && member.name == '==') {
if (functionNode == null) {
throw StateError("'==' should have functionNode");
}
if (!_commonElements.operatorEqHandlesNullArgument(member)) {
_handleIf(
visitCondition: () {
final parameter = parameters.values.first;
push(
HIdentity(
parameter,
graph.addConstantNull(closedWorld),
_abstractValueDomain.boolType,
),
);
},
visitThen: () {
_closeAndGotoExit(
HReturn(
graph.addConstantBool(false, closedWorld),
_sourceInformationBuilder.buildReturn(functionNode),
),
);
},
visitElse: null,
sourceInformation: _sourceInformationBuilder.buildIf(functionNode),
);
}
}
if (functionNode != null) {
_potentiallyAddFunctionParameterTypeChecks(member, functionNode, checks);
}
_insertCoverageCall(member);
}
void _closeFunction() {
if (!isAborted()) _closeAndGotoExit(HGoto());
graph.finalize();
}
@override
void defaultNode(ir.Node node) {
throw UnsupportedError('Unhandled node $node (${node.runtimeType})');
}
/// Returns the current source element. This is used by the type builder.
// TODO(efortuna): Update this when we implement inlining.
// TODO(sra): Re-implement type builder using Kernel types and the
// `target` for context.
MemberEntity get sourceElement => _currentFrame!.member;
@override
void visitCheckLibraryIsLoaded(ir.CheckLibraryIsLoaded node) {
ImportEntity import = _elementMap.getImport(node.import);
String loadId = closedWorld.outputUnitData.getImportDeferName(
_elementMap.getSpannable(targetElement, node),
import,
);
HInstruction prefixConstant = graph.addConstantString(loadId, closedWorld);
_pushStaticInvocation(
_commonElements.checkDeferredIsLoaded,
[prefixConstant],
_typeInferenceMap.getReturnTypeOf(_commonElements.checkDeferredIsLoaded),
const <DartType>[],
sourceInformation: null,
);
}
@override
void visitLoadLibrary(ir.LoadLibrary node) {
String loadId = closedWorld.outputUnitData.getImportDeferName(
_elementMap.getSpannable(targetElement, node),
_elementMap.getImport(node.import),
);
final priority = closedWorld.annotationsData.getLoadLibraryPriority(node);
final sourceInformation = _sourceInformationBuilder.buildCall(node, node);
push(
HInvokeStatic(
_commonElements.loadDeferredLibrary,
[
graph.addConstantString(loadId, closedWorld),
graph.addConstantString(priority, closedWorld),
],
_abstractValueDomain.nonNullType,
const <DartType>[],
targetCanThrow: false,
)..sourceInformation = sourceInformation,
);
}
@override
void visitBlock(ir.Block node) {
assert(!isAborted());
// [block] can be unreachable at the beginning of a block if an
// ir.BlockExpression that is a subexpression of an expression that contains
// a throwing prior subexpression, e.g. `[throw e, {...[]}]`.
if (!_isReachable) return;
localsHandler.enterScope(
_closureDataLookup.getCapturedBlockScope(node),
_sourceInformationBuilder.buildBlock(node),
);
for (ir.Statement statement in node.statements) {
statement.accept(this);
if (!_isReachable) {
// The block has been aborted by a return or a throw.
if (stack.isNotEmpty) {
reporter.internalError(
noLocationSpannable,
'Non-empty instruction stack.',
);
}
return;
}
}
assert(!current!.isClosed);
if (stack.isNotEmpty) {
reporter.internalError(
noLocationSpannable,
'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;
// Handle a `throw` expression in statement-position, with control flow to
// the exit. (In expression position the throw does not create control-flow
// out of CFG region for the expression).
if (expression is ir.Throw &&
_inliningStack.isEmpty &&
!_inBlockExpression) {
_visitThrowExpression(expression.expression);
_handleInTryStatement();
final sourceInformation = _sourceInformationBuilder.buildThrow(
node.expression,
);
_closeAndGotoExit(
HThrow(
pop(),
sourceInformation,
withoutHelperFrame: closedWorld.annotationsData
.throwWithoutHelperFrame(node),
),
);
} else {
expression.accept(this);
pop();
}
}
@override
void visitConstantExpression(ir.ConstantExpression node) {
ConstantValue value = _elementMap.getConstantValue(node)!;
final sourceInformation = _sourceInformationBuilder.buildGet(node);
if (!closedWorld.outputUnitData.hasOnlyNonDeferredImportPathsToConstant(
targetElement,
value,
)) {
OutputUnit outputUnit = closedWorld.outputUnitData.outputUnitForConstant(
value,
);
final deferredConstant = DeferredGlobalConstantValue(value, outputUnit);
registry.registerConstantUse(ConstantUse.deferred(deferredConstant));
stack.add(
graph.addDeferredConstant(
deferredConstant,
sourceInformation,
closedWorld,
),
);
} else {
stack.add(
graph.addConstant(
value,
closedWorld,
sourceInformation: sourceInformation,
),
);
}
}
@override
void visitReturnStatement(ir.ReturnStatement node) {
final sourceInformation = _sourceInformationBuilder.buildReturn(node);
HInstruction? value;
if (node.expression != null) {
node.expression!.accept(this);
value = pop();
if (_currentFrame!.asyncMarker == AsyncMarker.async) {
// TODO(johnniwinther): Is this special-casing of async still needed
// or should we use the general check below?
/*if (options.enableTypeAssertions &&
!isValidAsyncReturnType(_returnType)) {
generateTypeError(
"Async function returned a Future,"
" was declared to return a ${_returnType}.",
sourceInformation);
pop();
return;
}*/
} else {
value = _typeBuilder.potentiallyCheckOrTrustTypeOfAssignment(
_currentFrame!.member,
value,
_returnType!,
);
}
}
_handleInTryStatement();
if (_inliningStack.isEmpty && targetElement.isSetter) {
if (node.parent is ir.FunctionNode) {
// An arrow function definition of a setter has a ReturnStatement as a
// body, e.g. "set foo(x) => this._x = x;". There is no way to access
// the returned value, so don't emit a return.
return;
}
}
// TODO(43456): Better unreachable code removal. `_isReachable` removes
// more code, but also `return`s that pattern-match against more compact
// arrow functions. The `return`s also help the JavaScript VM.
// TODO(b/276976255): Using `_isReachable` causes a test failure.
if (!isAborted()) {
_emitReturn(value, sourceInformation);
}
}
@override
void visitForStatement(ir.ForStatement node) {
assert(_isReachable);
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 pop();
}
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);
}
final jumpTarget = _localsMap.getJumpTargetForFor(node);
_loopHandler.handleLoop(
node,
_closureDataLookup.getCapturedLoopScope(node),
jumpTarget,
buildInitializer,
buildCondition,
buildUpdate,
buildBody,
_sourceInformationBuilder.buildLoop(node),
);
}
@override
void visitForInStatement(ir.ForInStatement node) {
if (node.isAsync) {
_buildAsyncForIn(node);
} else if (_typeInferenceMap.isJsIndexableIterator(
node,
_abstractValueDomain,
)) {
// If the expression being iterated over is a JS indexable type, we can
// generate an optimized version of for-in that uses indexing.
_buildForInIndexable(node);
} else {
_buildForInIterator(node);
}
}
/// Builds the graph for a for-in node with an indexable expression.
///
/// In this case we build:
///
/// int end = a.length;
/// for (int i = 0;
/// i < a.length;
/// checkConcurrentModificationError(a.length == end, a), ++i) {
/// <declaredIdentifier> = a[i];
/// <body>
/// }
void _buildForInIndexable(ir.ForInStatement node) {
SyntheticLocal indexVariable = localsHandler.createLocal('_i');
// These variables are shared by initializer, condition, body and update.
late final HInstruction array; // Set in buildInitializer.
late final bool isFixed; // Set in buildInitializer.
HInstruction? originalLength; // Set for growable lists.
HInstruction buildGetLength(SourceInformation? sourceInformation) {
HGetLength result = HGetLength(
array,
_abstractValueDomain.positiveIntType,
isAssignable: !isFixed,
)..sourceInformation = sourceInformation;
add(result);
return result;
}
void buildConcurrentModificationErrorCheck() {
if (originalLength == null) return;
// The static call checkConcurrentModificationError() is expanded in
// codegen to:
//
// array.length == _end || throwConcurrentModificationError(array)
//
final sourceInformation = _sourceInformationBuilder.buildForInMoveNext(
node,
);
HInstruction length = buildGetLength(sourceInformation);
push(
HIdentity(length, originalLength!, _abstractValueDomain.boolType)
..sourceInformation = sourceInformation,
);
_pushStaticInvocation(
_commonElements.checkConcurrentModificationError,
[pop(), array],
_typeInferenceMap.getReturnTypeOf(
_commonElements.checkConcurrentModificationError,
),
const <DartType>[],
sourceInformation: sourceInformation,
);
pop();
}
void buildInitializer() {
final sourceInformation = _sourceInformationBuilder.buildForInIterator(
node,
);
node.iterable.accept(this);
array = pop();
isFixed = _abstractValueDomain
.isFixedLengthJsIndexable(array.instructionType)
.isDefinitelyTrue;
localsHandler.updateLocal(
indexVariable,
graph.addConstantInt(0, closedWorld),
sourceInformation: sourceInformation,
);
originalLength = buildGetLength(sourceInformation);
}
HInstruction buildCondition() {
final sourceInformation = _sourceInformationBuilder.buildForInMoveNext(
node,
);
HInstruction index = localsHandler.readLocal(
indexVariable,
sourceInformation: sourceInformation,
);
HInstruction length = buildGetLength(sourceInformation);
HInstruction compare = HLess(index, length, _abstractValueDomain.boolType)
..sourceInformation = sourceInformation;
add(compare);
return compare;
}
void buildBody() {
// If we had mechanically inlined ArrayIterator.moveNext(), it would have
// inserted the ConcurrentModificationError check as part of the
// condition. It is not necessary on the first iteration since there is
// no code between calls to `get iterator` and `moveNext`, so the test is
// moved to the loop update.
// Find a type for the element. Use the element type of the indexer of the
// array, as this is stronger than the Iterator's `get current` type, for
// example, `get current` includes null.
// TODO(sra): The element type of a container type mask might be better.
AbstractValue type = _typeInferenceMap.inferredIndexType(node);
final sourceInformation = _sourceInformationBuilder.buildForInCurrent(
node,
);
HInstruction index = localsHandler.readLocal(
indexVariable,
sourceInformation: sourceInformation,
);
// No bound check is necessary on indexer as it is immediately guarded by
// the condition.
HInstruction value = HIndex(array, index, type)
..sourceInformation = sourceInformation;
final staticType = _abstractValueDomain
.createFromStaticType(_getStaticForInElementType(node))
.abstractValue;
value.instructionType = _abstractValueDomain.intersection(
value.instructionType,
staticType,
);
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.
final sourceInformation = _sourceInformationBuilder.buildForInSet(node);
HInstruction index = localsHandler.readLocal(
indexVariable,
sourceInformation: sourceInformation,
);
HInstruction one = graph.addConstantInt(1, closedWorld);
HInstruction addInstruction = HAdd(
index,
one,
_abstractValueDomain.positiveIntType,
)..sourceInformation = sourceInformation;
add(addInstruction);
localsHandler.updateLocal(
indexVariable,
addInstruction,
sourceInformation: sourceInformation,
);
}
_loopHandler.handleLoop(
node,
_closureDataLookup.getCapturedLoopScope(node),
_localsMap.getJumpTargetForForIn(node),
buildInitializer,
buildCondition,
buildUpdate,
buildBody,
_sourceInformationBuilder.buildLoop(node),
);
}
void _buildForInIterator(ir.ForInStatement node) {
// Generate a structure equivalent to:
// Iterator<E> $iter = <iterable>.iterator;
// while ($iter.moveNext()) {
// <variable> = $iter.current;
// <body>
// }
// The iterator is shared between initializer, condition and body.
late final HInstruction iterator;
final iteratorType = _getStaticForInIteratorType(node);
void buildInitializer() {
final receiverType = _typeInferenceMap.typeOfIterator(node);
node.iterable.accept(this);
HInstruction receiver = pop();
_pushDynamicInvocation(
node,
_getStaticType(node.iterable),
receiverType,
Selectors.iterator,
[receiver],
const <DartType>[],
_sourceInformationBuilder.buildForInIterator(node),
);
iterator = pop();
}
HInstruction buildCondition() {
final receiverType = _typeInferenceMap.typeOfIteratorMoveNext(node);
_pushDynamicInvocation(
node,
iteratorType,
receiverType,
Selectors.moveNext,
[iterator],
const <DartType>[],
_sourceInformationBuilder.buildForInMoveNext(node),
);
return pop();
}
void buildBody() {
final sourceInformation = _sourceInformationBuilder.buildForInCurrent(
node,
);
final receiverType = _typeInferenceMap.typeOfIteratorCurrent(node);
_pushDynamicInvocation(
node,
iteratorType,
receiverType,
Selectors.current,
[iterator],
const <DartType>[],
sourceInformation,
);
Local loopVariableLocal = _localsMap.getLocalVariable(node.variable);
HInstruction value = _typeBuilder.potentiallyCheckOrTrustTypeOfAssignment(
_currentFrame!.member,
pop(),
_getDartTypeIfValid(node.variable.type),
);
localsHandler.updateLocal(
loopVariableLocal,
value,
sourceInformation: sourceInformation,
);
// Hint to name loop value after name of loop variable.
if (loopVariableLocal is! SyntheticLocal) {
value.sourceElement ??= loopVariableLocal;
}
node.body.accept(this);
}
_loopHandler.handleLoop(
node,
_closureDataLookup.getCapturedLoopScope(node),
_localsMap.getJumpTargetForForIn(node),
buildInitializer,
buildCondition,
() {},
buildBody,
_sourceInformationBuilder.buildLoop(node),
);
}
void _buildAsyncForIn(ir.ForInStatement node) {
// The async-for is implemented with a StreamIterator.
HInstruction streamIterator;
node.iterable.accept(this);
List<HInstruction> arguments = [pop()];
ClassEntity cls = _commonElements.streamIterator;
DartType typeArg = _elementMap.getDartType(node.variable.type);
final instanceType =
localsHandler.substInContext(dartTypes.interfaceType(cls, [typeArg]))
as InterfaceType;
_addImplicitInstantiation(instanceType);
final 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() {
final receiverType = _typeInferenceMap.typeOfIteratorMoveNext(node);
_pushDynamicInvocation(
node,
instanceType,
receiverType,
Selectors.moveNext,
[streamIterator],
const <DartType>[],
_sourceInformationBuilder.buildForInMoveNext(node),
);
HInstruction future = pop();
push(HAwait(future, _abstractValueDomain.dynamicType));
return pop();
}
void buildBody() {
final receiverType = _typeInferenceMap.typeOfIteratorCurrent(node);
_pushDynamicInvocation(
node,
instanceType,
receiverType,
Selectors.current,
[streamIterator],
const <DartType>[],
_sourceInformationBuilder.buildForInIterator(node),
);
localsHandler.updateLocal(
_localsMap.getLocalVariable(node.variable),
pop(),
);
node.body.accept(this);
}
void buildUpdate() {}
// Creates a synthetic try/finally block in case anything async goes amiss.
TryCatchFinallyBuilder tryBuilder = TryCatchFinallyBuilder(
this,
_sourceInformationBuilder.buildLoop(node),
);
// Build fake try body:
_loopHandler.handleLoop(
node,
_closureDataLookup.getCapturedLoopScope(node),
_localsMap.getJumpTargetForForIn(node),
buildInitializer,
buildCondition,
buildUpdate,
buildBody,
_sourceInformationBuilder.buildLoop(node),
);
void finalizerFunction() {
_pushDynamicInvocation(
node,
instanceType,
null,
Selectors.cancel,
[streamIterator],
const <DartType>[],
_sourceInformationBuilder
// ignore:deprecated_member_use_from_same_package
.buildGeneric(node),
);
add(HAwait(pop(), _abstractValueDomain.dynamicType));
}
tryBuilder
..closeTryBody()
..buildFinallyBlock(finalizerFunction)
..cleanUp();
}
HInstruction _callSetRuntimeTypeInfo(
HInstruction typeInfo,
HInstruction newObject,
SourceInformation? sourceInformation,
) {
// Set the runtime type information on the object.
FunctionEntity typeInfoSetterFn = _commonElements.setArrayType;
// TODO(efortuna): Insert source information in this static invocation.
_pushStaticInvocation(
typeInfoSetterFn,
[newObject, typeInfo],
_abstractValueDomain.dynamicType,
const <DartType>[],
sourceInformation: sourceInformation,
);
// The new object will now be referenced through the
// `setArrayType` call. We therefore set the type of that
// instruction to be of the object's type.
assert(
stack.last is HInvokeStatic || stack.last == newObject,
failedAt(
currentElementSpannable,
"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 pop();
}
_loopHandler.handleLoop(
node,
_closureDataLookup.getCapturedLoopScope(node),
_localsMap.getJumpTargetForWhile(node),
() {},
buildCondition,
() {},
() {
node.body.accept(this);
},
_sourceInformationBuilder.buildLoop(node),
);
}
@override
void visitDoStatement(ir.DoStatement node) {
final sourceInformation = _sourceInformationBuilder.buildLoop(node);
// TODO(efortuna): I think this can be rewritten using
// LoopHandler.handleLoop with some tricks about when the "update" happens.
LocalsHandler savedLocals = LocalsHandler.from(localsHandler);
CapturedLoopScope loopClosureInfo = _closureDataLookup.getCapturedLoopScope(
node,
);
localsHandler.startLoop(loopClosureInfo, sourceInformation);
final target = _localsMap.getJumpTargetForDo(node);
JumpHandler jumpHandler = _loopHandler.beginLoopHeader(node, target);
final currentBlock = current!;
final loopInfo = currentBlock.loopInformation!;
HBasicBlock loopEntryBlock = currentBlock;
HBasicBlock bodyEntryBlock = currentBlock;
bool hasContinues = target != null && target.isContinueTarget;
if (hasContinues) {
// Add extra block to hang labels on.
// It doesn't currently work if they are on the same block as the
// HLoopInfo. The handling of HLabeledBlockInformation will visit a
// SubGraph that starts at the same block again, so the HLoopInfo is
// either handled twice, or it's handled after the labeled block info,
// both of which generate the wrong code.
// Using a separate block is just a simple workaround.
bodyEntryBlock = openNewBlock();
}
localsHandler.enterLoopBody(loopClosureInfo, sourceInformation);
node.body.accept(this);
// If there are no continues we could avoid the creation of the condition
// block. This could also lead to a block having multiple entries and exits.
HBasicBlock bodyExitBlock;
bool isAbortingBody = false;
if (current != null) {
bodyExitBlock = close(HGoto());
} 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.isNotEmpty) {
if (!isAbortingBody) continueHandlers.add(localsHandler);
localsHandler = savedLocals.mergeMultiple(
continueHandlers,
conditionBlock,
);
SubGraph bodyGraph = SubGraph(bodyEntryBlock, bodyExitBlock);
List<LabelDefinition> labels = jumpHandler.labels;
HSubGraphBlockInformation bodyInfo = HSubGraphBlockInformation(
bodyGraph,
);
HLabeledBlockInformation info;
if (labels.isNotEmpty) {
info = HLabeledBlockInformation(bodyInfo, labels, isContinue: true);
} else {
info = HLabeledBlockInformation.implicit(
bodyInfo,
target,
isContinue: true,
);
}
bodyEntryBlock.setBlockFlow(info, conditionBlock);
}
open(conditionBlock);
node.condition.accept(this);
assert(!isAborted());
HInstruction conditionInstruction = pop();
HBasicBlock conditionEndBlock = close(HLoopBranch(conditionInstruction));
HBasicBlock avoidCriticalEdge = addNewBlock();
conditionEndBlock.addSuccessor(avoidCriticalEdge);
open(avoidCriticalEdge);
close(HGoto());
avoidCriticalEdge.addSuccessor(loopEntryBlock); // The back-edge.
conditionExpression = SubExpression(conditionBlock, conditionEndBlock);
// Avoid a critical edge from the condition to the loop-exit body.
HBasicBlock conditionExitBlock = addNewBlock();
open(conditionExitBlock);
close(HGoto());
conditionEndBlock.addSuccessor(conditionExitBlock);
_loopHandler.endLoop(
loopEntryBlock,
conditionExitBlock,
jumpHandler,
localsHandler,
);
loopEntryBlock.postProcessLoopHeader();
SubGraph bodyGraph = SubGraph(loopEntryBlock, bodyExitBlock);
final newLoopInfo = loopEntryBlock.loopInformation!;
HLoopBlockInformation loopBlockInfo = HLoopBlockInformation(
LoopBlockInformationKind.doWhileLoop,
null,
wrapExpressionGraph(conditionExpression),
wrapStatementGraph(bodyGraph),
null,
newLoopInfo.target,
newLoopInfo.labels,
sourceInformation,
);
loopEntryBlock.setBlockFlow(loopBlockInfo, current);
loopInfo.loopBlockInformation = loopBlockInfo;
} else {
// Since the loop has no back edge, we remove the loop information on the
// header.
loopEntryBlock.loopInformation = null;
if (jumpHandler.hasAnyBreak()) {
// Null branchBlock because the body of the do-while loop always aborts,
// so we never get to the condition.
_loopHandler.endLoop(loopEntryBlock, null, jumpHandler, localsHandler);
// Since the body of the loop has a break, we attach a synthesized label
// to the body.
SubGraph bodyGraph = SubGraph(bodyEntryBlock, bodyExitBlock);
JumpTarget target = _localsMap.getJumpTargetForDo(node)!;
final label = target.addLabel('loop', isBreakTarget: true);
HLabeledBlockInformation info = HLabeledBlockInformation(
HSubGraphBlockInformation(bodyGraph),
<LabelDefinition>[label],
);
loopEntryBlock.setBlockFlow(info, current);
jumpHandler.forEachBreak((HBreak breakInstruction, _) {
HBasicBlock block = breakInstruction.block!;
block.addAtExit(HBreak.toLabel(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,
required void Function() visitCondition,
required void Function() visitThen,
void Function()? visitElse,
SourceInformation? sourceInformation,
}) {
SsaBranchBuilder branchBuilder = SsaBranchBuilder(
this,
node == null ? null : _elementMap.getSpannable(targetElement, node),
);
branchBuilder.handleIf(
visitCondition,
visitThen,
visitElse,
sourceInformation: sourceInformation,
);
}
@override
void visitAsExpression(ir.AsExpression node) {
// Recognize these special cases, where expression e has static type `T?`:
//
// e as T
// (e as dynamic) as T
//
// These patterns can only fail if `e` results in a `null` value. The
// second pattern occurs when `e as dynamic` is used get an implicit
// downcast in order to make use of the different policies for explicit and
// implicit downcasts.
//
// The pattern match is syntactic which ensures the type bindings are
// consistent, i.e. from the same instance of a type variable scope.
ir.Expression operand = _skipCastsToDynamic(node.operand);
operand.accept(this);
bool isNullRemovalPattern = false;
final operandType = _getStaticType(operand);
DartType type = _elementMap.getDartType(node.type);
if (!node.isCovarianceCheck) {
if (_elementMap.types.isSubtype(operandType, type)) {
// Skip unneeded casts.
return;
}
if (_elementMap.types.isSubtype(
operandType,
_elementMap.types.nullableType(type),
)) {
isNullRemovalPattern = true;
}
}
final sourceInformation = _sourceInformationBuilder.buildAs(node);
HInstruction expressionInstruction = pop();
if (node.type is ir.InvalidType) {
_generateTypeError('invalid type', sourceInformation);
return;
}
CheckPolicy policy;
final currentFrame = _currentFrame!;
if (node.isTypeError) {
policy = closedWorld.annotationsData.getImplicitDowncastCheckPolicy(
currentFrame.member,
);
} else {
policy = closedWorld.annotationsData.getExplicitCastCheckPolicy(
currentFrame.member,
);
}
if (!policy.isEmitted) {
stack.add(expressionInstruction);
return;
}
void generateCheck() {
HInstruction converted = _typeBuilder.buildAsCheck(
expressionInstruction,
localsHandler.substInContext(type),
isTypeError: node.isTypeError,
sourceInformation: sourceInformation,
);
if (converted != expressionInstruction) {
add(converted);
}
stack.add(converted);
}
if (isNullRemovalPattern) {
// Generate a conditional to test only `null` values:
//
// temp = e;
// temp == null ? temp as T : temp
SsaBranchBuilder(this).handleConditional(
() {
push(
HIdentity(
expressionInstruction,
graph.addConstantNull(closedWorld),
_abstractValueDomain.boolType,
),
);
},
generateCheck,
() {
stack.add(expressionInstruction);
},
);
} else {
generateCheck();
}
}
static ir.Expression _skipCastsToDynamic(ir.Expression node) {
if (node is ir.AsExpression && node.type is ir.DynamicType) {
return _skipCastsToDynamic(node.operand);
}
return node;
}
@override
void visitNullCheck(ir.NullCheck node) {
node.operand.accept(this);
HInstruction expression = pop();
final sourceInformation = _sourceInformationBuilder.buildUnary(node);
push(
HNullCheck(
expression,
_abstractValueDomain.excludeNull(expression.instructionType),
)..sourceInformation = sourceInformation,
);
}
void _generateError(
FunctionEntity function,
String message,
AbstractValue typeMask,
SourceInformation? sourceInformation,
) {
HInstruction errorMessage = graph.addConstantString(message, closedWorld);
_pushStaticInvocation(
function,
[errorMessage],
typeMask,
const <DartType>[],
sourceInformation: sourceInformation,
);
}
void _generateTypeError(
String message,
SourceInformation? sourceInformation,
) {
_generateError(
_commonElements.throwTypeError,
message,
_typeInferenceMap.getReturnTypeOf(_commonElements.throwTypeError),
sourceInformation,
);
}
void _generateUnsupportedError(
String message,
SourceInformation? sourceInformation,
) {
_generateError(
_commonElements.throwUnsupportedError,
message,
_typeInferenceMap.getReturnTypeOf(_commonElements.throwUnsupportedError),
sourceInformation,
);
}
@override
void visitAssertStatement(ir.AssertStatement node) {
if (!options.enableUserAssertions) return;
var sourceInformation = _sourceInformationBuilder.buildAssert(node);
if (node.message == null) {
node.condition.accept(this);
_pushStaticInvocation(
_commonElements.assertHelper,
[pop()],
_typeInferenceMap.getReturnTypeOf(_commonElements.assertHelper),
const <DartType>[],
sourceInformation: sourceInformation,
);
pop();
return;
}
// if (assertTest(condition)) assertThrow(message);
void buildCondition() {
node.condition.accept(this);
_pushStaticInvocation(
_commonElements.assertTest,
[pop()],
_typeInferenceMap.getReturnTypeOf(_commonElements.assertTest),
const <DartType>[],
sourceInformation: sourceInformation,
);
}
void fail() {
node.message!.accept(this);
_pushStaticInvocation(
_commonElements.assertThrow,
[pop()],
_typeInferenceMap.getReturnTypeOf(_commonElements.assertThrow),
const <DartType>[],
sourceInformation: sourceInformation,
);
pop();
}
_handleIf(visitCondition: buildCondition, visitThen: fail);
}
/// Creates a [JumpHandler] for a statement. The node must be a jump
/// target. If there are no breaks or continues targeting the statement,
/// a special "null handler" is returned.
///
/// [isLoopJump] is true when the jump handler is for a loop. This is used
/// to distinguish the synthesized loop created for a switch statement with
/// continue statements from simple switch statements.
JumpHandler createJumpHandler(
ir.TreeNode node,
JumpTarget? target, {
bool isLoopJump = false,
}) {
if (target == null) {
// No breaks or continues to this node.
return NullJumpHandler(reporter);
}
if (isLoopJump && node is ir.SwitchStatement) {
return KernelSwitchCaseJumpHandler(this, target, node, _localsMap);
}
return JumpHandler(this, target);
}
@override
void visitBreakStatement(ir.BreakStatement node) {
assert(!isAborted());
_handleInTryStatement();
JumpTarget target = _localsMap.getJumpTargetForBreak(node);
JumpHandler handler = jumpTargets[target]!;
final 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;
}
final jumpTarget = _localsMap.getJumpTargetForLabel(node);
if (jumpTarget == null) {
// The label is not needed.
body.accept(this);
return;
}
JumpHandler handler = createJumpHandler(node, jumpTarget);
LocalsHandler beforeLocals = LocalsHandler.from(localsHandler);
HBasicBlock newBlock = openNewBlock();
body.accept(this);
SubGraph bodyGraph = SubGraph(newBlock, lastOpenedBlock);
// Create join block only if reached, otherwise it won't have a dominator.
late final HBasicBlock joinBlock = graph.addNewBlock();
List<LocalsHandler> breakHandlers = [];
handler.forEachBreak((HBreak breakInstruction, LocalsHandler locals) {
breakInstruction.block!.addSuccessor(joinBlock);
breakHandlers.add(locals);
});
if (!isAborted()) {
goto(current!, joinBlock);
breakHandlers.add(localsHandler);
}
if (breakHandlers.isNotEmpty) {
open(joinBlock);
localsHandler = beforeLocals.mergeMultiple(breakHandlers, joinBlock);
// There was at least one reachable break, so the label is needed.
newBlock.setBlockFlow(
HLabeledBlockInformation(
HSubGraphBlockInformation(bodyGraph),
handler.labels,
),
joinBlock,
);
}
handler.close();
}
/// Loop through the cases in a switch and create a mapping of case
/// expressions to constants.
Map<ir.Expression, ConstantValue> _buildSwitchCaseConstants(
ir.SwitchStatement switchStatement,
) {
Map<ir.Expression, ConstantValue> constants = {};
for (ir.SwitchCase switchCase in switchStatement.cases) {
for (ir.Expression caseExpression in switchCase.expressions) {
final constant = _elementMap.getConstantValue(caseExpression)!;
constants[caseExpression] = constant;
}
}
return constants;
}
@override
void visitContinueSwitchStatement(ir.ContinueSwitchStatement node) {
_handleInTryStatement();
JumpTarget target = _localsMap.getJumpTargetForContinueSwitch(node);
final handler = jumpTargets[target]!;
assert(target.labels.isNotEmpty);
handler.generateContinue(
_sourceInformationBuilder.buildGoto(node),
target.labels.first,
);
}
@override
void visitSwitchStatement(ir.SwitchStatement node) {
final sourceInformation = _sourceInformationBuilder.buildSwitch(node);
// The switch case indices must match those computed in
// [KernelSwitchCaseJumpHandler].
bool hasContinue = false;
Map<ir.SwitchCase, int> caseIndex = {};
int switchIndex = 1;
bool hasDefault = false;
for (ir.SwitchCase switchCase in node.cases) {
if (_isDefaultCase(switchCase)) {
hasDefault = true;
}
if (SwitchContinueAnalysis.containsContinue(switchCase.body)) {
hasContinue = true;
}
caseIndex[switchCase] = switchIndex;
switchIndex++;
}
JumpHandler jumpHandler = createJumpHandler(
node,
_localsMap.getJumpTargetForSwitch(node),
);
if (!hasContinue) {
// If the switch statement has no switch cases targeted by continue
// statements we encode the switch statement directly.
_buildSimpleSwitchStatement(node, jumpHandler, sourceInformation);
} else {
_buildComplexSwitchStatement(
node,
jumpHandler,
caseIndex,
hasDefault,
sourceInformation,
);
}
}
/// Helper for building switch statements.
static bool _isDefaultCase(ir.SwitchCase? switchCase) =>
switchCase == null || switchCase.isDefault;
/// Helper for building switch statements.
HInstruction _buildExpression(ir.SwitchStatement switchStatement) {
switchStatement.expression.accept(this);
return pop();
}
/// Helper method for creating the list of constants that make up the
/// switch case branches.
List<ConstantValue> _getSwitchConstants(
ir.SwitchStatement parentSwitch,
ir.SwitchCase? switchCase,
) {
Map<ir.Expression, ConstantValue> constantsLookup =
_buildSwitchCaseConstants(parentSwitch);
List<ConstantValue> constantList = [];
if (switchCase != null) {
for (var expression in switchCase.expressions) {
constantList.add(constantsLookup[expression]!);
}
}
return constantList;
}
/// Builds a simple switch statement which does not handle uses of continue
/// statements to labeled switch cases.
void _buildSimpleSwitchStatement(
ir.SwitchStatement switchStatement,
JumpHandler jumpHandler,
SourceInformation? sourceInformation,
) {
void buildSwitchCase(ir.SwitchCase? switchCase) {
switchCase!.body.accept(this);
}
_handleSwitch(
switchStatement,
jumpHandler,
_buildExpression,
switchStatement.cases,
_getSwitchConstants,
_isDefaultCase,
buildSwitchCase,
sourceInformation,
);
jumpHandler.close();
}
/// Builds a switch statement that can handle arbitrary uses of continue
/// statements to labeled switch cases.
void _buildComplexSwitchStatement(
ir.SwitchStatement switchStatement,
JumpHandler jumpHandler,
Map<ir.SwitchCase, int> caseIndex,
bool hasDefault,
SourceInformation? sourceInformation,
) {
// If the switch statement has switch cases targeted by continue
// statements we create the following encoding:
//
// switch (e) {
// l_1: case e0: s_1; break;
// l_2: case e1: s_2; continue l_i;
// ...
// l_n: default: s_n; continue l_j;
// }
//
// is encoded as
//
// var target;
// switch (e) {
// case e1: target = 1; break;
// case e2: target = 2; break;
// ...
// default: target = n; break;
// }
// l: while (true) {
// switch (target) {
// case 1: s_1; break l;
// case 2: s_2; target = i; continue l;
// ...
// case n: s_n; target = j; continue l;
// }
// }
//
// This is because JS does not have this same "continue label" semantics so
// we encode it in the form of a state machine.
// TODO(https://dartbug.com/51777): Consider alternative with single switch
// statement.
JumpTarget switchTarget = _localsMap.getJumpTargetForSwitch(
switchStatement,
)!;
localsHandler.updateLocal(switchTarget, graph.addConstantNull(closedWorld));
var switchCases = List<ir.SwitchCase?>.from(switchStatement.cases);
if (!hasDefault) {
// Use null as the marker for a synthetic default clause.
// The synthetic default is added because otherwise there would be no
// good place to give a default value to the local.
switchCases = List<ir.SwitchCase?>.from(switchCases);
switchCases.add(null);
}
void buildSwitchCase(ir.SwitchCase? switchCase) {
SourceInformation? caseSourceInformation = sourceInformation;
if (switchCase != null) {
caseSourceInformation = _sourceInformationBuilder.buildGoto(switchCase);
// Generate 'target = i; break;' for switch case i.
int index = caseIndex[switchCase]!;
HInstruction value = graph.addConstantInt(index, closedWorld);
localsHandler.updateLocal(
switchTarget,
value,
sourceInformation: caseSourceInformation,
);
} else {
// Generate synthetic default case 'target = null; break;'.
HInstruction nullValue = graph.addConstantNull(closedWorld);
localsHandler.updateLocal(
switchTarget,
nullValue,
sourceInformation: caseSourceInformation,
);
}
jumpTargets[switchTarget]!.generateBreak(caseSourceInformation);
}
_handleSwitch(
switchStatement,
jumpHandler,
_buildExpression,
switchCases,
_getSwitchConstants,
_isDefaultCase,
buildSwitchCase,
sourceInformation,
);
jumpHandler.close();
HInstruction buildCondition() => graph.addConstantBool(true, closedWorld);
void buildSwitch() {
HInstruction buildExpression(ir.SwitchStatement notUsed) {
return localsHandler.readLocal(switchTarget);
}
List<ConstantValue> getConstants(
ir.SwitchStatement parentSwitch,
ir.SwitchCase? switchCase,
) {
return [constant_system.createIntFromInt(caseIndex[switchCase]!)];
}
void buildSwitchCase(ir.SwitchCase? switchCase) {
switchCase!.body.accept(this);
if (!isAborted()) {
// Ensure that we break the loop if the case falls through. (This
// is only possible for the last case.)
jumpTargets[switchTarget]!.generateBreak(sourceInformation);
}
}
// Pass a [NullJumpHandler] because the target for the contained break
// is not the generated switch statement but instead the loop generated
// in the call to [handleLoop] below.
_handleSwitch(
switchStatement, // nor is buildExpression.
NullJumpHandler(reporter),
buildExpression,
switchStatement.cases,
getConstants,
(_) => false, // No case is default.
buildSwitchCase,
sourceInformation,
);
}
void buildLoop() {
_loopHandler.handleLoop(
switchStatement,
_closureDataLookup.getCapturedLoopScope(switchStatement),
switchTarget,
() {},
buildCondition,
() {},
buildSwitch,
_sourceInformationBuilder.buildLoop(switchStatement),
);
}
if (hasDefault) {
buildLoop();
} else {
// If the switch statement has no default case, surround the loop with
// a test of the target. So:
// `if (target) while (true) ...` If there's no default case, target is
// null, so we don't drop into the while loop.
void buildCondition() {
js.Template code = js.js.parseForeignJS('#');
push(
HForeignCode(code, _abstractValueDomain.boolType, [
localsHandler.readLocal(switchTarget),
], nativeBehavior: NativeBehavior.pure),
);
}
_handleIf(
node: switchStatement,
visitCondition: buildCondition,
visitThen: buildLoop,
visitElse: () => {},
sourceInformation: sourceInformation,
);
}
}
/// Creates a switch statement.
///
/// [jumpHandler] is the [JumpHandler] for the created switch statement.
/// [buildSwitchCase] creates the statements for the switch case.
void _handleSwitch(
ir.SwitchStatement switchStatement,
JumpHandler jumpHandler,
HInstruction Function(ir.SwitchStatement statement) buildExpression,
List<ir.SwitchCase?> switchCases,
List<ConstantValue> Function(
ir.SwitchStatement parentSwitch,
ir.SwitchCase? switchCase,
)
getConstants,
bool Function(ir.SwitchCase? switchCase) isDefaultCase,
void Function(ir.SwitchCase? switchCase) buildSwitchCase,
SourceInformation? sourceInformation,
) {
HBasicBlock expressionStart = openNewBlock();
HInstruction expression = buildExpression(switchStatement);
if (switchCases.isEmpty) {
return;
}
HSwitch switchInstruction = HSwitch(expression);
HBasicBlock expressionEnd = close(switchInstruction);
LocalsHandler savedLocals = localsHandler;
List<HStatementInformation> statements = [];
bool hasDefault = false;
for (ir.SwitchCase? switchCase in switchCases) {
HBasicBlock block = graph.addNewBlock();
for (ConstantValue constant in getConstants(
switchStatement,
switchCase,
)) {
HConstant hConstant = graph.addConstant(constant, closedWorld);
switchInstruction.inputs.add(hConstant);
hConstant.usedBy.add(switchInstruction);
expressionEnd.addSuccessor(block);
}
if (isDefaultCase(switchCase)) {
// An HSwitch has n inputs and n+1 successors, the last being the
// default case.
expressionEnd.addSuccessor(block);
hasDefault = true;
}
open(block);
localsHandler = LocalsHandler.from(savedLocals);
buildSwitchCase(switchCase);
if (!isAborted() &&
// TODO(johnniwinther): Reinsert this if `isReachable` is no longer
// set to `false` when `_tryInlineMethod` sees an always throwing
// method.
//switchCase == switchCases.last &&
!isDefaultCase(switchCase)) {
// If there is no default, we will add one later to avoid
// the critical edge. So we generate a break statement to make
// sure the last case does not fall through to the default case.
jumpHandler.generateBreak(sourceInformation);
}
statements.add(
HSubGraphBlockInformation(SubGraph(block, lastOpenedBlock)),
);
}
// Add a join-block if necessary.
// We create [joinBlock] early, and then go through the cases that might
// want to jump to it. In each case, if we add [joinBlock] as a successor
// of another block, we also add an element to [caseHandlers] that is used
// to create the phis in [joinBlock].
// If we never jump to the join block, [caseHandlers] will stay empty, and
// the join block is never added to the graph.
HBasicBlock? joinBlock = HBasicBlock();
List<LocalsHandler> caseHandlers = [];
jumpHandler.forEachBreak((HBreak instruction, LocalsHandler locals) {
instruction.block!.addSuccessor(joinBlock!);
caseHandlers.add(locals);
});
jumpHandler.forEachContinue((HContinue instruction, LocalsHandler locals) {
assert(
false,
failedAt(
_elementMap.getSpannable(targetElement, switchStatement),
'Continue cannot target a switch.',
),
);
});
if (!isAborted()) {
current!.close(HGoto());
lastOpenedBlock.addSuccessor(joinBlock);
caseHandlers.add(localsHandler);
}
if (!hasDefault) {
// Always create a default case, to avoid a critical edge in the
// graph.
HBasicBlock defaultCase = addNewBlock();
expressionEnd.addSuccessor(defaultCase);
open(defaultCase);
close(HGoto());
defaultCase.addSuccessor(joinBlock);
caseHandlers.add(savedLocals);
statements.add(
HSubGraphBlockInformation(SubGraph(defaultCase, defaultCase)),
);
}
assert(caseHandlers.length == joinBlock.predecessors.length);
if (caseHandlers.isNotEmpty) {
graph.addBlock(joinBlock);
open(joinBlock);
if (caseHandlers.length == 1) {
localsHandler = caseHandlers[0];
} else {
localsHandler = savedLocals.mergeMultiple(caseHandlers, joinBlock);
}
} else {
// The joinBlock is not used.
joinBlock = null;
}
HSubExpressionBlockInformation expressionInfo =
HSubExpressionBlockInformation(
SubExpression(expressionStart, expressionEnd),
);
expressionStart.setBlockFlow(
HSwitchBlockInformation(
expressionInfo,
statements,
jumpHandler.target,
jumpHandler.labels,
sourceInformation,
),
joinBlock,
);
jumpHandler.close();
}
@override
void visitConditionalExpression(ir.ConditionalExpression node) {
SsaBranchBuilder brancher = SsaBranchBuilder(this);
brancher.handleConditional(
() => node.condition.accept(this),
() => node.then.accept(this),
() => node.otherwise.accept(this),
);
}
@override
void visitLogicalExpression(ir.LogicalExpression node) {
SsaBranchBuilder brancher = SsaBranchBuilder(this);
_handleLogicalExpression(
node.left,
() => node.right.accept(this),
brancher,
node.operatorEnum,
_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 Function() visitRight,
SsaBranchBuilder brancher,
ir.LogicalExpressionOperator operatorEnum,
SourceInformation? sourceInformation,
) {
if (left is ir.LogicalExpression && left.operatorEnum == operatorEnum) {
ir.Expression innerLeft = left.left;
ir.Expression middle = left.right;
_handleLogicalExpression(
innerLeft,
() => _handleLogicalExpression(
middle,
visitRight,
brancher,
operatorEnum,
_sourceInformationBuilder.buildBinary(middle),
),
brancher,
operatorEnum,
sourceInformation,
);
} else {
brancher.handleLogicalBinary(
() => left.accept(this),
visitRight,
sourceInformation,
isAnd: operatorEnum == ir.LogicalExpressionOperator.AND,
);
}
}
@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,
) {
// [type] could be `List<T>`, so ensure it is `JSArray<T>`.
InterfaceType arrayType = dartTypes.interfaceType(
_commonElements.jsArrayClass,
type.typeArguments,
);
if (!_rtiNeed.classNeedsTypeArguments(type.element) ||
_equivalentToMissingRti(arrayType)) {
return object;
}
HInstruction rti = _typeBuilder.analyzeTypeArgument(
arrayType,
sourceElement,
);
// TODO(15489): Register at codegen.
registry.registerInstantiation(type);
return _callSetRuntimeTypeInfo(rti, object, sourceInformation);
}
@override
void visitListLiteral(ir.ListLiteral node) {
HInstruction listInstruction;
if (node.isConst) {
listInstruction = graph.addConstant(
_elementMap.getConstantValue(node)!,
closedWorld,
);
} else {
List<HInstruction> elements = [];
for (ir.Expression element in node.expressions) {
element.accept(this);
elements.add(pop());
}
listInstruction = _buildLiteralList(elements);
add(listInstruction);
final sourceInformation = _sourceInformationBuilder.buildListLiteral(
node,
);
final type =
localsHandler.substInContext(
_commonElements.listType(
_elementMap.getDartType(node.typeArgument),
),
)
as InterfaceType;
listInstruction = _setListRuntimeTypeInfoIfNeeded(
listInstruction,
type,
sourceInformation,
);
}
AbstractValue type = _typeInferenceMap.typeOfListLiteral(
node,
_abstractValueDomain,
);
if (_abstractValueDomain.containsAll(type).isDefinitelyFalse) {
listInstruction.instructionType = type;
}
stack.add(listInstruction);
}
@override
void visitSetLiteral(ir.SetLiteral node) {
if (node.isConst) {
stack.add(
graph.addConstant(_elementMap.getConstantValue(node)!, closedWorld),
);
return;
}
// The set literal constructors take the elements as a List.
List<HInstruction> elements = [];
for (ir.Expression element in node.expressions) {
element.accept(this);
elements.add(pop());
}
// The constructor is a procedure because it's a factory.
FunctionEntity constructor;
List<HInstruction> inputs = [];
if (elements.isEmpty) {
constructor = _commonElements.setLiteralConstructorEmpty;
} else {
constructor = _commonElements.setLiteralConstructor;
HLiteralList argList = _buildLiteralList(elements);
add(argList);
inputs.add(argList);
}
assert(
constructor is ConstructorEntity && constructor.isFactoryConstructor,
);
final type =
localsHandler.substInContext(
_commonElements.setType(
_elementMap.getDartType(node.typeArgument),
),
)
as InterfaceType;
final cls = constructor.enclosingClass!;
if (_rtiNeed.classNeedsTypeArguments(cls)) {
List<HInstruction> typeInputs = [];
for (var argument in type.typeArguments) {
typeInputs.add(
_typeBuilder.analyzeTypeArgument(argument, sourceElement),
);
}
inputs.addAll(typeInputs);
}
// If runtime type information is needed and the set literal has no type
// parameter, 'constructor' is a static function that forwards the call to
// the factory constructor without a type parameter.
assert(
constructor.isFunction ||
(constructor is ConstructorEntity &&
constructor.isFactoryConstructor),
);
// The instruction type will always be a subtype of the setLiteralClass, but
// type inference might discover a more specific type or find nothing (in
// dart2js unit tests).
AbstractValue setType = _abstractValueDomain.createNonNullSubtype(
_commonElements.setLiteralClass,
);
AbstractValue returnTypeMask = _typeInferenceMap.getReturnTypeOf(
constructor,
);
AbstractValue instructionType = _abstractValueDomain.intersection(
setType,
returnTypeMask,
);
_addImplicitInstantiation(type);
_pushStaticInvocation(
constructor,
inputs,
instructionType,
const <DartType>[],
sourceInformation: _sourceInformationBuilder.buildNew(node),
);
_removeImplicitInstantiation(type);
}
@override
void visitMapLiteral(ir.MapLiteral node) {
if (node.isConst) {
stack.add(
graph.addConstant(_elementMap.getConstantValue(node)!, closedWorld),
);
return;
}
// The map literal constructors take the key-value pairs as a List
List<HInstruction> constructorArgs = [];
for (ir.MapLiteralEntry mapEntry in node.entries) {
mapEntry.key.accept(this);
constructorArgs.add(pop());
mapEntry.value.accept(this);
constructorArgs.add(pop());
}
// The constructor is a procedure because it's a factory.
FunctionEntity constructor;
List<HInstruction> inputs = [];
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,
);
final type =
localsHandler.substInContext(
_commonElements.mapType(
_elementMap.getDartType(node.keyType),
_elementMap.getDartType(node.valueType),
),
)
as InterfaceType;
final cls = constructor.enclosingClass!;
if (_rtiNeed.classNeedsTypeArguments(cls)) {
List<HInstruction> typeInputs = [];
for (var argument in type.typeArguments) {
typeInputs.add(
_typeBuilder.analyzeTypeArgument(argument, sourceElement),
);
}
inputs.addAll(typeInputs);
}
// If runtime type information is needed and the map literal has no type
// parameters, 'constructor' is a static function that forwards the call to
// the factory constructor without type parameters.
assert(
constructor.isFunction ||
(constructor is ConstructorEntity &&
constructor.isFactoryConstructor),
);
// The instruction type will always be a subtype of the mapLiteralClass, but
// type inference might discover a more specific type, or find nothing (in
// dart2js unit tests).
AbstractValue mapType = _abstractValueDomain.createNonNullSubtype(
_commonElements.mapLiteralClass,
);
AbstractValue returnTypeMask = _typeInferenceMap.getReturnTypeOf(
constructor,
);
AbstractValue instructionType = _abstractValueDomain.intersection(
mapType,
returnTypeMask,
);
_addImplicitInstantiation(type);
_pushStaticInvocation(
constructor,
inputs,
instructionType,
const <DartType>[],
sourceInformation: _sourceInformationBuilder.buildNew(node),
);
_removeImplicitInstantiation(type);
}
@override
void visitMapLiteralEntry(ir.MapLiteralEntry node) {
failedAt(
currentElementSpannable,
'ir.MapEntry should be handled in visitMapLiteral',
);
}
@override
void visitRecordLiteral(ir.RecordLiteral node) {
SourceInformation? sourceInformation = _sourceInformationBuilder
.buildCreate(node);
assert(!node.isConst);
List<HInstruction> inputs = [];
for (ir.Expression expression in node.positional) {
expression.accept(this);
inputs.add(pop());
}
for (ir.NamedExpression namedExpression in node.named) {
namedExpression.value.accept(this);
inputs.add(pop());
}
// TODO(50701): Choose class depending in inferred type of record fields
// which might be better than the static type.
RecordType dartType =
_elementMap.getDartType(node.recordType) as RecordType;
if (dartType.containsTypeVariables) {
dartType = localsHandler.substInContext(dartType) as RecordType;
}
final recordRepresentation = _recordData.representationForStaticType(
dartType,
);
ClassEntity recordClass = recordRepresentation.cls;
if (recordRepresentation.usesList) {
// TODO(50081): Can we use `.constListType`?
push(HLiteralList(inputs, _abstractValueDomain.fixedListType));
inputs = [pop()];
}
AbstractValue type =
_typeInferenceMap.typeOfRecordLiteral(node, _abstractValueDomain) ??
_abstractValueDomain.createFromStaticType(dartType).abstractValue;
final allocation = HCreate(recordClass, inputs, type, sourceInformation);
// TODO(50701): With traced record types there might be a better type.
push(allocation);
}
@override
void visitRecordIndexGet(ir.RecordIndexGet node) {
final shape = recordShapeOfRecordType(node.receiverType);
return _handleRecordFieldGet(node, node.receiver, shape, node.index);
}
@override
void visitRecordNameGet(ir.RecordNameGet node) {
final shape = recordShapeOfRecordType(node.receiverType);
int index = shape.indexOfFieldName(node.name);
return _handleRecordFieldGet(node, node.receiver, shape, index);
}
void _handleRecordFieldGet(
ir.Expression node,
ir.TreeNode receiverNode,
RecordShape shape,
int indexInShape,
) {
receiverNode.accept(this);
HInstruction receiver = pop();
SourceInformation? sourceInformation = _sourceInformationBuilder.buildGet(
node,
);
if (_recordData.representationForShape(shape) != null) {
final recordType =
_typeInferenceMap.receiverTypeOfGet(node) ??
_abstractValueDomain
.createFromStaticType(_getStaticType(node))
.abstractValue;
final fieldType = _abstractValueDomain.getGetterTypeInRecord(
recordType,
shape.getterNameOfIndex(indexInShape),
);
final path = _recordData.pathForAccess(shape, indexInShape);
if (path.index == null) {
HFieldGet fieldGet = HFieldGet(
path.field,
receiver,
fieldType,
sourceInformation,
isAssignable: false,
);
push(fieldGet);
} else {
HFieldGet fieldGet = HFieldGet(
path.field,
receiver,
_abstractValueDomain.constListType,
sourceInformation,
isAssignable: false,
);
push(fieldGet);
final list = pop();
push(
HIndex(
list,
graph.addConstantInt(indexInShape, closedWorld),
fieldType,
),
);
}
} else {
// There are no records with this shape, so the path here must be
// infeasible.
push(
HInvokeStatic(
_commonElements.assertUnreachableMethod,
[],
_abstractValueDomain.emptyType,
const [],
)..sourceInformation = sourceInformation,
);
// TODO(50081): Should we make subsequent code unreachable?
}
}
@override
void visitTypeLiteral(ir.TypeLiteral node) {
final sourceInformation = _sourceInformationBuilder.buildGet(node);
ir.DartType type = node.type;
DartType dartType = _elementMap.getDartType(type);
if (!dartType.containsTypeVariables) {
final constant = _elementMap.getConstantValue(node)!;
stack.add(
graph.addConstant(
constant,
closedWorld,
sourceInformation: sourceInformation,
),
);
return;
}
// 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 = localsHandler.substInContext(dartType);
HInstruction value = _typeBuilder.analyzeTypeArgument(
dartType,
sourceElement,
sourceInformation: sourceInformation,
);
_pushStaticInvocation(
_commonElements.createRuntimeType,
[value],
_typeInferenceMap.getReturnTypeOf(_commonElements.createRuntimeType),
const <DartType>[],
sourceInformation: sourceInformation,
);
}
@override
void visitStaticGet(ir.StaticGet node) {
ir.Member staticTarget = node.target;
final sourceInformation = _sourceInformationBuilder.buildGet(node);
if (staticTarget is ir.Procedure &&
staticTarget.kind == ir.ProcedureKind.Getter) {
final getter = _elementMap.getMember(staticTarget) as FunctionEntity;
// Invoke the getter
_pushStaticInvocation(
getter,
const [],
_typeInferenceMap.getReturnTypeOf(getter),
const <DartType>[],
sourceInformation: sourceInformation,
);
} else if (staticTarget is ir.Field) {
FieldEntity field = _elementMap.getField(staticTarget);
FieldAnalysisData fieldData = _fieldAnalysis.getFieldData(field);
if (fieldData.isEager) {
push(
HStatic(
field,
_typeInferenceMap.getInferredTypeOf(field),
sourceInformation,
),
);
} else if (fieldData.isEffectivelyConstant) {
OutputUnit outputUnit = closedWorld.outputUnitData.outputUnitForMember(
field,
);
final initialValue = fieldData.initialValue!;
// TODO(sigmund): this is not equivalent to what the old FE does: if
// there is no prefix the old FE wouldn't treat this in any special
// way. Also, if the prefix points to a constant in the main output
// unit, the old FE would still generate a deferred wrapper here.
if (!closedWorld.outputUnitData.hasOnlyNonDeferredImportPaths(
targetElement,
field,
)) {
final deferredConstant = DeferredGlobalConstantValue(
initialValue,
outputUnit,
);
registry.registerConstantUse(ConstantUse.deferred(deferredConstant));
stack.add(
graph.addDeferredConstant(
deferredConstant,
sourceInformation,
closedWorld,
),
);
} else {
stack.add(
graph.addConstant(
initialValue,
closedWorld,
sourceInformation: sourceInformation,
),
);
}
} else {
assert(
fieldData.isLazy,
"Unexpected field data for $field: $fieldData",
);
push(
HLazyStatic(
field,
_typeInferenceMap.getInferredTypeOf(field),
sourceInformation,
),
);
}
} else {
// TODO(johnniwinther): This is a constant tear off, so we should have
// created a constant value instead. Remove this case when we use CFE
// constants.
final member = _elementMap.getMember(staticTarget);
push(
HStatic(
member,
_typeInferenceMap.getInferredTypeOf(member),
sourceInformation,
),
);
}
}
@override
void visitStaticTearOff(ir.StaticTearOff node) {
// TODO(johnniwinther): This is a constant tear off, so we should have
// created a constant value instead. Remove this case when we use CFE
// constants.
ir.Member staticTarget = node.target;
final sourceInformation = _sourceInformationBuilder.buildGet(node);
final member = _elementMap.getMember(staticTarget);
push(
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) {
final setter = _elementMap.getMember(staticTarget) as FunctionEntity;
// Invoke the setter
_pushStaticInvocation(
setter,
[value],
_typeInferenceMap.getReturnTypeOf(setter),
const <DartType>[],
sourceInformation: _sourceInformationBuilder.buildSet(node),
);
pop();
} else {
final target = _elementMap.getMember(staticTarget) as FieldEntity;
if (!_fieldAnalysis.getFieldData(target).isElided) {
add(
HStaticStore(
target,
_typeBuilder.potentiallyCheckOrTrustTypeOfAssignment(
target,
value,
_getDartTypeIfValid(staticTarget.setterType),
),
),
);
}
}
stack.add(value);
}
void _handlePropertyGet(
ir.Expression node,
ir.Expression receiver,
ir.Name name,
) {
receiver.accept(this);
HInstruction receiverInstruction = pop();
_pushDynamicInvocation(
node,
_getStaticType(receiver),
_typeInferenceMap.receiverTypeOfGet(node),
Selector.getter(_elementMap.getName(name)),
[receiverInstruction],
const <DartType>[],
_sourceInformationBuilder.buildGet(node),
);
}
@override
void visitInstanceGet(ir.InstanceGet node) {
_handlePropertyGet(node, node.receiver, node.name);
}
@override
void visitInstanceTearOff(ir.InstanceTearOff node) {
_handlePropertyGet(node, node.receiver, node.name);
}
@override
void visitDynamicGet(ir.DynamicGet node) {
_handlePropertyGet(node, node.receiver, node.name);
}
@override
void visitFunctionTearOff(ir.FunctionTearOff node) {
_handlePropertyGet(node, node.receiver, ir.Name.callName);
}
@override
void visitVariableGet(ir.VariableGet node) {
ir.VariableDeclaration variable = node.variable;
final letBinding = _letBindings[variable];
if (letBinding != null) {
stack.add(letBinding);
return;
}
final local = _localsMap.getLocalVariable(node.variable);
final readLocal = localsHandler.readLocal(
local,
sourceInformation: _sourceInformationBuilder.buildGet(node),
);
if (node.promotedType != null) {
// Use the prompted type by inserting a HTypeKnown refinement node.
//
// The front end does not tell us _why_ the type was promoted, so the
// HTypeKnown is pinned at the variable reference rather than at the
// control flow reponsible for the promotion. Pinning at the variable
// reference impairs code motion.
//
// Many of the promoted types are also refined by the optimizer (in
// SsaTypeConversionInserter, where controlling phis are handled). This
// causes a similar HTypeKnown to be inserted, but pinned at a location
// responsible the promotion. The optimizer-inserted HTypeKnown nodes
// dominate the nodes inserted here, allowing the nodes inserted here to
// be removed as redundant, sometimes lifting the code motion
// restriction. There is not an exact match between the front-end
// inference, so some of the promotions remain.
final trusted = _typeBuilder.trustPromotedType(
readLocal,
_getDartTypeIfValid(node.promotedType!),
);
if (trusted != readLocal) {
push(trusted);
return;
}
}
stack.add(readLocal);
}
void _handlePropertySet(
ir.Expression node,
ir.Expression receiver,
ir.Name name,
ir.Expression value,
) {
receiver.accept(this);
HInstruction receiverInstruction = pop();
value.accept(this);
HInstruction valueInstruction = pop();
_pushDynamicInvocation(
node,
_getStaticType(receiver),
_typeInferenceMap.receiverTypeOfSet(node, _abstractValueDomain),
Selector.setter(_elementMap.getName(name)),
[receiverInstruction, valueInstruction],
const <DartType>[],
_sourceInformationBuilder.buildAssignment(node),
);
pop();
stack.add(valueInstruction);
}
@override
void visitInstanceSet(ir.InstanceSet node) {
_handlePropertySet(node, node.receiver, node.name, node.value);
}
@override
void visitDynamicSet(ir.DynamicSet node) {
_handlePropertySet(node, node.receiver, node.name, node.value);
}
@override
void visitSuperPropertySet(ir.SuperPropertySet node) {
final sourceInformation = _sourceInformationBuilder.buildAssignment(node);
node.value.accept(this);
HInstruction value = pop();
final target = getEffectiveSuperTarget(node.interfaceTarget);
MemberEntity member = _elementMap.getMember(target);
_buildInvokeSuper(
_elementMap.getSelector(node),
_elementMap.getClass(_containingClass(node)),
member,
[value],
const <DartType>[],
sourceInformation,
);
pop();
stack.add(value);
}
@override
void visitVariableSet(ir.VariableSet node) {
node.value.accept(this);
HInstruction value = pop();
_visitLocalSetter(
node.variable,
value,
_sourceInformationBuilder.buildAssignment(node),
);
}
@override
void visitVariableDeclaration(ir.VariableDeclaration node) {
Local local = _localsMap.getLocalVariable(node);
if (node.initializer == null) {
HInstruction initialValue = graph.addConstantNull(closedWorld);
localsHandler.updateLocal(local, initialValue);
} else if (node.isConst) {
final constant = _elementMap.getConstantValue(node.initializer);
assert(constant != null, failedAt(currentElementSpannable));
HInstruction initialValue = graph.addConstant(constant!, closedWorld);
localsHandler.updateLocal(local, initialValue);
} else {
node.initializer!.accept(this);
HInstruction initialValue = pop();
_visitLocalSetter(
node,
initialValue,
_sourceInformationBuilder.buildAssignment(node),
);
// Ignore value
pop();
}
}
void _visitLocalSetter(
ir.VariableDeclaration variable,
HInstruction value,
SourceInformation? sourceInformation,
) {
Local local = _localsMap.getLocalVariable(variable);
// Give the value a name if it doesn't have one already.
value.sourceElement ??= local;
stack.add(value);
localsHandler.updateLocal(
local,
_typeBuilder.potentiallyCheckOrTrustTypeOfAssignment(
_currentFrame!.member,
value,
_getDartTypeIfValid(variable.type),
),
sourceInformation: sourceInformation,
);
}
@override
void visitLet(ir.Let node) {
ir.VariableDeclaration variable = node.variable;
variable.initializer!.accept(this);
HInstruction initializedValue = pop();
// TODO(sra): Apply inferred type information.
_letBindings[variable] = initializedValue;
// TODO(43456): Use `!_isReachable` for better dead code removal.
if (isAborted()) {
stack.add(graph.addConstantUnreachable(closedWorld));
} else {
node.body.accept(this);
}
}
@override
void visitBlockExpression(ir.BlockExpression node) {
node.body.accept(this);
// Body can be partially generated due to an exception exit and be missing
// bindings referenced in the value.
if (!_isReachable) {
stack.add(graph.addConstantUnreachable(closedWorld));
} else {
final previous = _inBlockExpression;
try {
_inBlockExpression = true;
node.value.accept(this);
} finally {
_inBlockExpression = previous;
}
}
}
/// Generate instructions to evaluate the positional arguments in source
/// order.
List<HInstruction> _visitPositionalArguments(ir.Arguments arguments) {
List<HInstruction> result = [];
for (ir.Expression argument in arguments.positional) {
argument.accept(this);
result.add(pop());
}
return result;
}
/// Generate instructions to evaluate the named arguments in source order.
/// Returns a fresh map from parameter name to evaluated argument.
Map<String, HInstruction> _visitNamedArguments(ir.Arguments arguments) {
Map<String, HInstruction> values = {};
for (ir.NamedExpression argument in arguments.named) {
argument.value.accept(this);
values[argument.name] = pop();
}
return values;
}
/// Builds the list of instructions for the expressions in the arguments to a
/// dynamic target (member function). Dynamic targets use stubs to add
/// defaulted arguments, so (unlike static targets) we do not add the default
/// values.
List<HInstruction> _visitArgumentsForDynamicTarget(
Selector selector,
ir.Arguments arguments,
List<DartType> typeArguments, [
SourceInformation? sourceInformation,
]) {
List<HInstruction> values = _visitPositionalArguments(arguments);
if (arguments.named.isNotEmpty) {
Map<String, HInstruction> namedValues = _visitNamedArguments(arguments);
for (String name in selector.callStructure.getOrderedNamedArguments()) {
values.add(namedValues[name]!);
}
}
_addTypeArguments(values, typeArguments, sourceInformation);
return values;
}
/// Build the argument list for JS-interop invocations, which have slightly
/// different semantics than dart because of JS's null vs undefined and lack
/// of named arguments. A `null` argument indicates an optional argument which
/// was not provided.
List<HInstruction?> _visitArgumentsForNativeStaticTarget(
ir.FunctionNode target,
ir.Arguments arguments,
) {
// Visit arguments in source order, then re-order and fill in defaults.
List<HInstruction?> values = List.from(
_visitPositionalArguments(arguments),
);
if (target.namedParameters.isNotEmpty) {
// Only anonymous factory or extension type literal constructors involving
// JS interop are allowed to have named parameters. Otherwise, throw an
// error.
final member = target.parent as ir.Member;
final function = _elementMap.getMember(member) as FunctionEntity;
bool isAnonymousFactory =
function is ConstructorEntity &&
function.isFactoryConstructor &&
_nativeData.isAnonymousJsInteropClass(function.enclosingClass);
// JS interop checks assert that the only extension type interop member
// that has named parameters is an object literal constructor.
// TODO(54968): We should handle the lowering for object literal
// constructors in the interop transformer somehow instead and avoid
// assuming all such members are object literal constructors or
// otherwise paying the cost to verify by indexing extension types.
bool isObjectLiteralConstructor = member.isExtensionTypeMember;
if (isAnonymousFactory || isObjectLiteralConstructor) {
// TODO(sra): Have a "CompiledArguments" structure to just update with
// what values we have rather than creating a map and de-populating it.
Map<String, HInstruction> namedValues = _visitNamedArguments(arguments);
// Visit named arguments in parameter-position order, selecting provided
// or default value.
var namedParameters = target.namedParameters.toList();
assert(
namedValues.keys.every(
(k) => namedParameters.any((p) => p.name == k),
),
);
namedParameters.sort(nativeOrdering);
for (ir.VariableDeclaration parameter in namedParameters) {
final value = namedValues[parameter.name];
values.add(value);
}
}
}
return values;
}
/// Fills [typeArguments] with the type arguments needed for [selector] and
/// returns the selector corresponding to the passed type arguments.
///
/// If [isImplicitCall] is `true`, the target of the invocation can be either
/// the target of the [selector] or of the corresponding `.call` selector. In
/// this case we need to check both selectors to see if we need to pass type
/// arguments. This occurs for field/getter invocations.
Selector _fillDynamicTypeArguments(
Selector selector,
ir.Arguments arguments,
List<DartType> typeArguments, {
bool isImplicitCall = false,
}) {
if (selector.typeArgumentCount > 0) {
if (_rtiNeed.selectorNeedsTypeArguments(selector) ||
(isImplicitCall &&
_rtiNeed.selectorNeedsTypeArguments(selector.toCallSelector()))) {
typeArguments.addAll(arguments.types.map(_elementMap.getDartType));
} else {
return selector.toNonGeneric();
}
}
return selector;
}
List<DartType> _getConstructorTypeArguments(
ConstructorEntity constructor,
ir.Arguments arguments,
) {
// TODO(johnniwinther): Pass type arguments to constructors like calling
// a generic method.
return const <DartType>[];
}
// TODO(johnniwinther): Remove this when type arguments are passed to
// constructors like calling a generic method.
List<DartType> _getClassTypeArguments(
ClassEntity cls,
ir.Arguments arguments,
) {
if (_rtiNeed.classNeedsTypeArguments(cls)) {
return arguments.types.map(_elementMap.getDartType).toList();
}
return const <DartType>[];
}
List<DartType> _getStaticTypeArguments(
FunctionEntity function,
ir.Arguments arguments,
) {
if (_rtiNeed.methodNeedsTypeArguments(function)) {
return arguments.types.map(_elementMap.getDartType).toList();
}
return const <DartType>[];
}
/// Build argument list in canonical order for a static [target], including
/// filling in the default argument value.
List<HInstruction> _visitArgumentsForStaticTarget(
ir.FunctionNode target,
ParameterStructure parameterStructure,
ir.Arguments arguments,
List<DartType> typeArguments,
SourceInformation? sourceInformation,
) {
// Visit arguments in source order, then re-order and fill in defaults.
List<HInstruction> values = _visitPositionalArguments(arguments);
while (values.length < parameterStructure.positionalParameters) {
ir.VariableDeclaration parameter =
target.positionalParameters[values.length];
values.add(_defaultValueForParameter(parameter));
}
if (parameterStructure.namedParameters.isNotEmpty) {
Map<String, HInstruction> namedValues = _visitNamedArguments(arguments);
// Visit named arguments in parameter-position order, selecting provided
// or default value.
// TODO(sra): Ensure the stored order is canonical so we don't have to
// sort. The old builder uses CallStructure.makeArgumentList which depends
// on the old element model.
List<ir.VariableDeclaration> namedParameters =
target.namedParameters
// Filter elided parameters.
.where((p) => parameterStructure.namedParameters.contains(p.name))
.toList()
..sort(namedOrdering);
for (ir.VariableDeclaration parameter in namedParameters) {
final value = namedValues[parameter.name];
if (value == null) {
values.add(_defaultValueForParameter(parameter));
} else {
values.add(value);
namedValues.remove(parameter.name);
}
}
assert(namedValues.isEmpty);
} else {
assert(arguments.named.isEmpty);
}
_addTypeArguments(values, typeArguments, sourceInformation);
return values;
}
void _addTypeArguments(
List<HInstruction?> values,
List<DartType> typeArguments,
SourceInformation? sourceInformation,
) {
if (typeArguments.isEmpty) return;
for (DartType type in typeArguments) {
values.add(
_typeBuilder.analyzeTypeArgument(
type,
sourceElement,
sourceInformation: sourceInformation,
),
);
}
}
HInstruction _defaultValueForParameter(ir.VariableDeclaration parameter) {
final constant = _elementMap.getConstantValue(
parameter.initializer,
implicitNull: true,
);
assert(constant != null, failedAt(currentElementSpannable));
return graph.addConstant(constant!, closedWorld);
}
@override
void visitStaticInvocation(ir.StaticInvocation node) {
if (ir.StaticWeakReferences.isWeakReference(node)) {
final weakTarget = ir.StaticWeakReferences.getWeakReferenceTarget(node);
if (_elementMap.containsMethod(weakTarget)) {
final argument = ir.StaticWeakReferences.getWeakReferenceArgument(node);
argument.accept(this);
return;
}
stack.add(graph.addConstantNull(closedWorld));
return;
}
ir.Procedure target = node.target;
final sourceInformation = _sourceInformationBuilder.buildCall(node, node);
final function = _elementMap.getMember(target) as FunctionEntity;
if (_commonElements.isForeignHelper(function)) {
_handleInvokeStaticForeign(node, function, sourceInformation);
return;
}
if (_commonElements.isExtractTypeArguments(function) &&
_handleExtractTypeArguments(node, sourceInformation)) {
return;
}
AbstractValue typeMask = _typeInferenceMap.getReturnTypeOf(function);
List<DartType> typeArguments = _getStaticTypeArguments(
function,
node.arguments,
);
// Recognize e.g. `bool.fromEnvironment('x')`
// TODO(sra): Can we delete this code now that the CFE does constant folding
// for us during loading?
if (function.isExternal &&
function is ConstructorEntity &&
function.isFromEnvironmentConstructor) {
if (node.isConst) {
// Just like all const constructors (see visitConstructorInvocation).
stack.add(
graph.addConstant(
_elementMap.getConstantValue(node)!,
closedWorld,
sourceInformation: sourceInformation,
),
);
} else {
_generateUnsupportedError(
'${function.enclosingClass.name}.${function.name} '
'can only be used as a const constructor',
sourceInformation,
);
}
return;
}
if (closedWorld.nativeData.isJsInteropMember(function)) {
final arguments = _visitArgumentsForNativeStaticTarget(
target.function,
node.arguments,
);
if (function is ConstructorEntity && function.isFactoryConstructor) {
_handleInvokeNativeFactoryConstructor(
node,
function,
typeMask,
arguments,
sourceInformation,
);
return;
}
// Static methods currently ignore the type parameters.
_pushStaticNativeInvocation(
function,
arguments,
typeMask,
typeArguments,
sourceInformation: sourceInformation,
);
} else {
final arguments = _visitArgumentsForStaticTarget(
target.function,
function.parameterStructure,
node.arguments,
typeArguments,
sourceInformation,
);
if (function is ConstructorEntity && function.isFactoryConstructor) {
_handleInvokeFactoryConstructor(
node,
function,
typeMask,
arguments,
sourceInformation,
);
return;
}
// Static methods currently ignore the type parameters.
_pushStaticInvocation(
function,
arguments,
typeMask,
typeArguments,
sourceInformation: sourceInformation,
);
}
}
void _handleInvokeFactoryConstructor(
ir.StaticInvocation invocation,
ConstructorEntity function,
AbstractValue typeMask,
List<HInstruction> arguments,
SourceInformation? sourceInformation,
) {
// Recognize `JSArray<E>.typed(allocation)`.
if (function == _commonElements.jsArrayTypedConstructor) {
if (invocation.arguments.named.isEmpty) {
if (invocation.arguments.positional.length == 1) {
assert(arguments.length == 1);
_handleInvokeJSArrayTypedConstructor(
invocation,
function,
typeMask,
arguments,
sourceInformation,
);
return;
}
}
}
InterfaceType instanceType = _elementMap.createInterfaceType(
invocation.target.enclosingClass!,
invocation.arguments.types,
);
// Factory constructors take type parameters.
List<DartType> typeArguments = _getConstructorTypeArguments(
function,
invocation.arguments,
);
// This could be a List factory constructor that returned a fresh list and
// we have a call-site-specific type from type inference.
var allocatedListType = globalInferenceResults.typeOfNewList(invocation);
AbstractValue resultType = allocatedListType ?? typeMask;
// TODO(johnniwinther): Remove this when type arguments are passed to
// constructors like calling a generic method.
_addTypeArguments(
arguments,
_getClassTypeArguments(function.enclosingClass, invocation.arguments),
sourceInformation,
);
instanceType = localsHandler.substInContext(instanceType) as InterfaceType;
_addImplicitInstantiation(instanceType);
_pushStaticInvocation(
function,
arguments,
resultType,
typeArguments,
sourceInformation: sourceInformation,
instanceType: instanceType,
);
if (allocatedListType != null) {
HInstruction newInstance = stack.last.nonCheck();
if (newInstance is HInvokeStatic) {
newInstance.setAllocation(true);
}
// Is the constructor call one from which we can extract the length
// argument?
bool isFixedList = false;
if (_abstractValueDomain.isGrowableArray(resultType).isDefinitelyFalse) {
// These constructors all take a length as the first argument.
if (_commonElements.isNamedListConstructor('filled', function) ||
_commonElements.isNamedListConstructor('generate', function) ||
_commonElements.isNamedJSArrayConstructor('fixed', function) ||
_commonElements.isNamedJSArrayConstructor(
'allocateFixed',
function,
)) {
isFixedList = true;
}
}
if (_abstractValueDomain.isTypedArray(resultType).isDefinitelyTrue) {
// The unnamed constructors of typed arrays take a length as the first
// argument.
if (function.name == '') isFixedList = true;
// TODO(sra): Can this misfire?
}
if (isFixedList) {
if (newInstance is HInvokeStatic || newInstance is HForeignCode) {
graph.allocatedFixedLists.add(newInstance);
}
}
}
}
void _handleInvokeNativeFactoryConstructor(
ir.StaticInvocation invocation,
ConstructorEntity function,
AbstractValue typeMask,
List<HInstruction?> arguments,
SourceInformation? sourceInformation,
) {
InterfaceType instanceType = _elementMap.createInterfaceType(
invocation.target.enclosingClass!,
invocation.arguments.types,
);
// Factory constructors take type parameters.
List<DartType> typeArguments = _getConstructorTypeArguments(
function,
invocation.arguments,
);
// TODO(johnniwinther): Remove this when type arguments are passed to
// constructors like calling a generic method.
_addTypeArguments(
arguments,
_getClassTypeArguments(function.enclosingClass, invocation.arguments),
sourceInformation,
);
instanceType = localsHandler.substInContext(instanceType) as InterfaceType;
_addImplicitInstantiation(instanceType);
_pushStaticNativeInvocation(
function,
arguments,
typeMask,
typeArguments,
sourceInformation: sourceInformation,
);
}
/// Handle the `JSArray<E>.typed` constructor, which returns its argument,
/// which must be a JSArray, with the JSArray type Rti information added on a
/// property.
void _handleInvokeJSArrayTypedConstructor(
ir.StaticInvocation invocation,
ConstructorEntity function,
AbstractValue typeMask,
List<HInstruction> arguments,
SourceInformation? sourceInformation,
) {
// TODO(sra): We rely here on inlining the identity-like factory
// constructor. Instead simply select the single argument and add the type.
// Factory constructors take type parameters.
List<DartType> typeArguments = _getConstructorTypeArguments(
function,
invocation.arguments,
);
// TODO(johnniwinther): Remove this when type arguments are passed to
// constructors like calling a generic method.
_addTypeArguments(
arguments,
_getClassTypeArguments(function.enclosingClass, invocation.arguments),
sourceInformation,
);
_pushStaticInvocation(
function,
arguments,
typeMask,
typeArguments,
sourceInformation: sourceInformation,
);
InterfaceType type = _elementMap.createInterfaceType(
invocation.target.enclosingClass!,
invocation.arguments.types,
);
stack.add(_setListRuntimeTypeInfoIfNeeded(pop(), type, sourceInformation));
}
/// Replace calls to `extractTypeArguments` with equivalent code. Returns
/// `true` if `extractTypeArguments` is handled.
bool _handleExtractTypeArguments(
ir.StaticInvocation invocation,
SourceInformation? sourceInformation,
) {
// Expand calls as follows:
//
// r = extractTypeArguments<Map>(e, f)
// -->
// environment = HInstanceEnvironment(e);
// T1 = HTypeEval( environment, 'Map.K');
// T2 = HTypeEval( environment, 'Map.V');
// r = f<T1, T2>();
//
// TODO(sra): Should we add a check before the variable extraction? We could
// add a type check (which would permit `null`), or add an is-check with an
// explicit throw.
if (invocation.arguments.positional.length != 2) return false;
if (invocation.arguments.named.isNotEmpty) return false;
var types = invocation.arguments.types;
if (types.length != 1) return false;
// The type should be a single type name.
ir.DartType type = types.first;
DartType typeValue = localsHandler.substInContext(
_elementMap.getDartType(type),
);
if (typeValue is! InterfaceType) return false;
InterfaceType interfaceType = typeValue;
if (!dartTypes.treatAsRawType(interfaceType)) return false;
ClassEntity cls = interfaceType.element;
InterfaceType thisType = _elementEnvironment.getThisType(cls);
List<HInstruction> arguments = _visitPositionalArguments(
invocation.arguments,
);
HInstruction object = arguments[0];
HInstruction closure = arguments[1];
List<HInstruction> inputs = [closure];
List<DartType> typeArguments = [];
closedWorld.registerExtractTypeArguments(cls);
HInstruction instanceType = HInstanceEnvironment(
object,
_abstractValueDomain.dynamicType,
);
add(instanceType);
TypeEnvironmentStructure envStructure = FullTypeEnvironmentStructure(
classType: thisType,
);
for (var variable in thisType.typeArguments) {
assert(variable is TypeVariableType);
typeArguments.add(variable);
TypeRecipe recipe = TypeExpressionRecipe(variable);
HInstruction typeEval = HTypeEval(
instanceType,
envStructure,
recipe,
_abstractValueDomain.dynamicType,
);
add(typeEval);
inputs.add(typeEval);
}
// TODO(sra): In compliance mode, insert a check that [closure] is a
// function of N type arguments.
Selector selector = Selector.callClosure(
0,
const <String>[],
typeArguments.length,
);
final receiverStaticType = _getStaticType(
invocation.arguments.positional[1],
);
AbstractValue receiverType = _abstractValueDomain
.createFromStaticType(receiverStaticType)
.abstractValue;
push(
HInvokeClosure(
selector,
receiverType,
inputs,
_abstractValueDomain.dynamicType,
typeArguments,
),
);
return true;
}
void _handleInvokeStaticForeign(
ir.StaticInvocation invocation,
MemberEntity member,
SourceInformation? sourceInformation,
) {
final name = member.name;
if (name == 'JS') {
_handleForeignJs(invocation, sourceInformation);
} else if (name == 'DART_CLOSURE_TO_JS') {
_handleForeignDartClosureToJs(
invocation,
'DART_CLOSURE_TO_JS',
sourceInformation,
);
} else if (name == 'RAW_DART_FUNCTION_REF') {
_handleForeignRawFunctionRef(
invocation,
'RAW_DART_FUNCTION_REF',
sourceInformation,
);
} else if (name == 'JS_GET_NAME') {
_handleForeignJsGetName(invocation, sourceInformation);
} else if (name == 'JS_EMBEDDED_GLOBAL') {
_handleForeignJsEmbeddedGlobal(invocation, sourceInformation);
} else if (name == 'JS_BUILTIN') {
_handleForeignJsBuiltin(invocation, sourceInformation);
} else if (name == 'JS_TRUE') {
_handleForeignJsBool(true, invocation, sourceInformation);
} else if (name == 'JS_FALSE') {
_handleForeignJsBool(false, invocation, sourceInformation);
} else if (name == 'JS_EFFECT') {
stack.add(
graph.addConstantNull(closedWorld)
..sourceInformation = sourceInformation,
);
} else if (name == 'JS_INTERCEPTOR_CONSTANT') {
_handleJsInterceptorConstant(invocation, sourceInformation);
} else if (name == 'getInterceptor') {
_handleForeignGetInterceptor(invocation, sourceInformation);
} else if (name == 'getJSArrayInteropRti') {
_handleForeignGetJSArrayInteropRti(invocation, sourceInformation);
} else if (name == 'JS_RAW_EXCEPTION') {
_handleJsRawException(invocation, sourceInformation);
} else if (name == 'JS_STRING_CONCAT') {
_handleJsStringConcat(invocation, sourceInformation);
} else if (name == '_createInvocationMirror') {
_handleCreateInvocationMirror(invocation, sourceInformation);
} else if (name == 'TYPE_REF') {
_handleForeignTypeRef(invocation, sourceInformation);
} else if (name == 'createJsSentinel') {
_handleForeignCreateJsSentinel(invocation, sourceInformation);
} else if (name == 'isJsSentinel') {
_handleForeignIsJsSentinel(invocation, sourceInformation);
} else if (name == '_lateReadCheck') {
_handleLateReadCheck(invocation, sourceInformation);
} else if (name == '_lateWriteOnceCheck') {
_handleLateWriteOnceCheck(invocation, sourceInformation);
} else if (name == '_lateInitializeOnceCheck') {
_handleLateInitializeOnceCheck(invocation, sourceInformation);
} else if (name == 'HCharCodeAt') {
_handleCharCodeAt(invocation, sourceInformation);
} else if (name == 'HArrayFlagsGet') {
_handleArrayFlagsGet(invocation, sourceInformation);
} else if (name == 'HArrayFlagsSet') {
_handleArrayFlagsSet(invocation, sourceInformation);
} else if (name == 'HArrayFlagsCheck') {
_handleArrayFlagsCheck(invocation, sourceInformation);
} else {
reporter.internalError(
_elementMap.getSpannable(targetElement, invocation),
"Unknown foreign: $name",
);
}
}
String _readStringLiteral(ir.Expression node) {
if (node is ir.StringLiteral) {
return node.value;
}
if (node is ir.ConstantExpression) {
final constant = node.constant;
if (constant is ir.StringConstant) {
return constant.value;
}
}
return reporter.internalError(
_elementMap.getSpannable(targetElement, node),
"Unexpected string literal: "
"${node is ir.ConstantExpression ? node.constant : node}",
);
}
int _readIntLiteral(ir.Expression node) {
if (node is ir.IntLiteral) {
return node.value;
}
if (node is ir.ConstantExpression) {
final constant = node.constant;
if (constant is ir.IntConstant) {
return constant.value;
} else if (constant is ir.DoubleConstant) {
assert(
constant.value.floor() == constant.value,
"Unexpected int literal value ${constant.value}.",
);
return constant.value.toInt();
}
}
return reporter.internalError(
_elementMap.getSpannable(targetElement, node),
"Unexpected int literal: "
"${node is ir.ConstantExpression ? node.constant : node}",
);
}
void _handleCreateInvocationMirror(
ir.StaticInvocation invocation,
SourceInformation? sourceInformation,
) {
String name = _readStringLiteral(invocation.arguments.positional[0]);
final typeArgumentsLiteral =
invocation.arguments.positional[1] as ir.ListLiteral;
List<DartType> typeArguments = typeArgumentsLiteral.expressions.map((
ir.Expression expression,
) {
final typeLiteral = expression as ir.TypeLiteral;
return _elementMap.getDartType(typeLiteral.type);
}).toList();
final positionalArgumentsLiteral =
invocation.arguments.positional[2] as ir.ListLiteral;
ir.Expression namedArgumentsLiteral = invocation.arguments.positional[3];
Map<String, ir.Expression> namedArguments = {};
int kind = _readIntLiteral(invocation.arguments.positional[4]);
Name memberName = Name(name, _currentFrame!.member.library.canonicalUri);
Selector selector;
switch (InvocationMirrorKind.values[kind]) {
case InvocationMirrorKind.getter:
selector = Selector.getter(memberName);
break;
case InvocationMirrorKind.setter:
selector = Selector.setter(memberName);
break;
case InvocationMirrorKind.method:
if (memberName == Names.indexName) {
selector = Selector.index();
} else if (memberName == Names.indexSetName) {
selector = Selector.indexSet();
} else {
if (namedArgumentsLiteral is ir.MapLiteral) {
for (var entry in namedArgumentsLiteral.entries) {
String key = _readStringLiteral(entry.key);
namedArguments[key] = entry.value;
}
} else if (namedArgumentsLiteral is ir.ConstantExpression &&
namedArgumentsLiteral.constant is ir.MapConstant) {
final constant = namedArgumentsLiteral.constant as ir.MapConstant;
for (ir.ConstantMapEntry entry in constant.entries) {
final key = entry.key as ir.StringConstant;
namedArguments[key.value] = ir.ConstantExpression(entry.value);
}
} else {
reporter.internalError(
computeSourceSpanFromTreeNode(invocation),
"Unexpected named arguments value in createInvocationMirror: "
"$namedArgumentsLiteral.",
);
}
CallStructure callStructure = CallStructure(
positionalArgumentsLiteral.expressions.length,
namedArguments.keys.toList(),
typeArguments.length,
);
if (Selector.isOperatorName(name)) {
selector = Selector(
SelectorKind.operator,
memberName,
callStructure,
);
} else {
selector = Selector.call(memberName, callStructure);
}
}
break;
}
HConstant nameConstant = graph.addConstant(
constant_system.createSymbol(closedWorld.commonElements, name),
closedWorld,
)..sourceInformation = sourceInformation;
List<HInstruction> arguments = [];
for (ir.Expression argument in positionalArgumentsLiteral.expressions) {
argument.accept(this);
arguments.add(pop());
}
if (namedArguments.isNotEmpty) {
Map<String, HInstruction> namedValues = {};
namedArguments.forEach((String name, ir.Expression value) {
value.accept(this);
namedValues[name] = pop();
});
for (String name in selector.callStructure.getOrderedNamedArguments()) {
arguments.add(namedValues[name]!);
}
}
_addTypeArguments(arguments, typeArguments, sourceInformation);
HInstruction argumentsInstruction = _buildLiteralList(arguments)
..sourceInformation = sourceInformation;
add(argumentsInstruction);
List<HInstruction> argumentNames = <HInstruction>[];
for (String argumentName
in selector.callStructure.getOrderedNamedArguments()) {
ConstantValue argumentNameConstant = constant_system.createString(
argumentName,
);
argumentNames.add(
graph.addConstant(argumentNameConstant, closedWorld)
..sourceInformation = sourceInformation,
);
}
HInstruction argumentNamesInstruction = _buildLiteralList(argumentNames)
..sourceInformation = sourceInformation;
add(argumentNamesInstruction);
HInstruction typeArgumentCount = graph.addConstantInt(
typeArguments.length,
closedWorld,
)..sourceInformation = sourceInformation;
js.Name internalName = _namer.invocationName(selector);
ConstantValue kindConstant = constant_system.createIntFromInt(
selector.invocationMirrorKind.index,
);
_pushStaticInvocation(
_commonElements.createUnmangledInvocationMirror,
[
nameConstant,
graph.addConstantStringFromName(internalName, closedWorld)
..sourceInformation = sourceInformation,
graph.addConstant(kindConstant, closedWorld)
..sourceInformation = sourceInformation,
argumentsInstruction,
argumentNamesInstruction,
typeArgumentCount,
],
_abstractValueDomain.dynamicType,
const <DartType>[],
sourceInformation: sourceInformation,
);
}
bool _unexpectedForeignArguments(
ir.StaticInvocation invocation, {
required int minPositional,
int? maxPositional,
int typeArgumentCount = 0,
}) {
String pluralizeArguments(int count, [String adjective = '']) {
if (count == 0) return 'no ${adjective}arguments';
if (count == 1) return 'one ${adjective}argument';
if (count == 2) return 'two ${adjective}arguments';
return '$count ${adjective}arguments';
}
String name() => invocation.target.name.text;
ir.Arguments arguments = invocation.arguments;
bool bad = false;
if (arguments.types.length != typeArgumentCount) {
String expected = pluralizeArguments(typeArgumentCount, 'type ');
String actual = pluralizeArguments(arguments.types.length, 'type ');
reporter.reportErrorMessage(
_elementMap.getSpannable(targetElement, invocation),
MessageKind.generic,
{'text': "Error: '${name()}' takes $expected, not $actual."},
);
bad = true;
}
if (arguments.positional.length < minPositional) {
String phrase = pluralizeArguments(minPositional);
if (maxPositional != minPositional) phrase = 'at least $phrase';
reporter.reportErrorMessage(
_elementMap.getSpannable(targetElement, invocation),
MessageKind.generic,
{'text': "Error: Too few arguments. '${name()}' takes $phrase."},
);
bad = true;
}
if (maxPositional != null && arguments.positional.length > maxPositional) {
String phrase = pluralizeArguments(maxPositional);
if (maxPositional != minPositional) phrase = 'at most $phrase';
reporter.reportErrorMessage(
_elementMap.getSpannable(targetElement, invocation),
MessageKind.generic,
{'text': "Error: Too many arguments. '${name()}' takes $phrase."},
);
bad = true;
}
if (arguments.named.isNotEmpty) {
reporter.reportErrorMessage(
_elementMap.getSpannable(targetElement, invocation),
MessageKind.generic,
{'text': "Error: '${name()}' does not take named arguments."},
);
bad = true;
}
return bad;
}
/// Returns the value of the string argument. The argument must evaluate to a
/// constant. If there is an error, the error is reported and `null` is
/// returned.
String? _foreignConstantStringArgument(
ir.StaticInvocation invocation,
int position,
String methodName, [
String adjective = '',
]) {
ir.Expression argument = invocation.arguments.positional[position];
argument.accept(this);
HInstruction instruction = pop();
if (instruction is HConstant) {
ConstantValue constant = instruction.constant;
if (constant is StringConstantValue) return constant.stringValue;
}
reporter.reportErrorMessage(
_elementMap.getSpannable(targetElement, argument),
MessageKind.generic,
{
'text':
"Error: Expected String constant as ${adjective}argument "
"to '$methodName'.",
},
);
return null;
}
void _handleForeignDartClosureToJs(
ir.StaticInvocation invocation,
String name,
SourceInformation? sourceInformation,
) {
// TODO(sra): Do we need to wrap the closure in something that saves the
// current isolate?
_handleForeignRawFunctionRef(invocation, name, sourceInformation);
}
void _handleForeignRawFunctionRef(
ir.StaticInvocation invocation,
String name,
SourceInformation? sourceInformation,
) {
if (_unexpectedForeignArguments(
invocation,
minPositional: 1,
maxPositional: 1,
)) {
// Result expected on stack.
stack.add(
graph.addConstantNull(closedWorld)
..sourceInformation = sourceInformation,
);
return;
}
ir.Expression closure = invocation.arguments.positional.single;
String problem = 'requires a static method or top-level method';
bool handleTarget(ir.Procedure procedure) {
final function = procedure.function;
if (function.requiredParameterCount ==
function.positionalParameters.length &&
function.namedParameters.isEmpty) {
push(
HFunctionReference(
_elementMap.getMethod(procedure),
_abstractValueDomain.dynamicType,
)..sourceInformation = sourceInformation,
);
return true;
}
problem = 'does not handle a closure with optional parameters';
return false;
}
if (closure is ir.StaticGet) {
ir.Member staticTarget = closure.target;
if (staticTarget is ir.Procedure) {
if (staticTarget.kind == ir.ProcedureKind.Method) {
if (handleTarget(staticTarget)) {
return;
}
}
}
} else if (closure is ir.ConstantExpression) {
final tearOff = closure.constant;
if (tearOff is ir.StaticTearOffConstant) {
ir.Procedure member = tearOff.target;
if (handleTarget(member)) {
return;
}
}
}
reporter.reportErrorMessage(
_elementMap.getSpannable(targetElement, invocation),
MessageKind.generic,
{'text': "'$name' $problem."},
);
// Result expected on stack.
stack.add(
graph.addConstantNull(closedWorld)..sourceInformation = sourceInformation,
);
return;
}
void _handleJsRawException(
ir.StaticInvocation invocation,
SourceInformation? sourceInformation,
) {
if (_unexpectedForeignArguments(
invocation,
minPositional: 0,
maxPositional: 0,
)) {
// Result expected on stack.
stack.add(
graph.addConstantNull(closedWorld)
..sourceInformation = sourceInformation,
);
return;
}
if (_rethrowableException != null) {
stack.add(_rethrowableException!);
return;
}
reporter.reportErrorMessage(
_elementMap.getSpannable(targetElement, invocation),
MessageKind.generic,
{'text': "Error: JS_RAW_EXCEPTION() must be in a 'catch' block."},
);
// Result expected on stack.
stack.add(
graph.addConstantNull(closedWorld)..sourceInformation = sourceInformation,
);
}
void _handleForeignJsGetName(
ir.StaticInvocation invocation,
SourceInformation? sourceInformation,
) {
if (_unexpectedForeignArguments(
invocation,
minPositional: 1,
maxPositional: 1,
)) {
// Result expected on stack.
stack.add(
graph.addConstantNull(closedWorld)
..sourceInformation = sourceInformation,
);
return;
}
ir.Node argument = invocation.arguments.positional.first;
argument.accept(this);
HInstruction instruction = pop();
if (instruction is HConstant) {
final name = _getNameForJsGetName(instruction.constant, _namer)!;
stack.add(
graph.addConstantStringFromName(name, closedWorld)
..sourceInformation = sourceInformation,
);
return;
}
reporter.reportErrorMessage(
_elementMap.getSpannable(targetElement, argument),
MessageKind.generic,
{'text': 'Error: Expected a JsGetName enum value.'},
);
// Result expected on stack.
stack.add(
graph.addConstantNull(closedWorld)..sourceInformation = sourceInformation,
);
}
int? _extractEnumIndexFromConstantValue(
ConstantValue constant,
ClassEntity classElement,
) {
if (constant is ConstructedConstantValue &&
constant.type.element == classElement) {
assert(constant.fields.isNotEmpty);
for (var field in constant.fields.keys) {
if (field.memberName.text == "index") {
final indexConstant = constant.fields[field];
if (indexConstant is IntConstantValue) {
return indexConstant.intValue.toInt();
}
break;
}
}
}
return null;
}
/// Returns the [js.Name] for the `JsGetName` [constant] value.
js.Name? _getNameForJsGetName(ConstantValue constant, ModularNamer namer) {
final index = _extractEnumIndexFromConstantValue(
constant,
_commonElements.jsGetNameEnum,
);
if (index == null) return null;
return namer.getNameForJsGetName(
currentElementSpannable,
JsGetName.values[index],
);
}
void _handleForeignJsEmbeddedGlobal(
ir.StaticInvocation invocation,
SourceInformation? sourceInformation,
) {
if (_unexpectedForeignArguments(
invocation,
minPositional: 2,
maxPositional: 2,
)) {
// Result expected on stack.
stack.add(
graph.addConstantNull(closedWorld)
..sourceInformation = sourceInformation,
);
return;
}
final globalName = _foreignConstantStringArgument(
invocation,
1,
'JS_EMBEDDED_GLOBAL',
'second ',
)!;
NativeBehavior nativeBehavior = _elementMap
.getNativeBehaviorForJsEmbeddedGlobalCall(invocation);
AbstractValue ssaType = _typeInferenceMap.typeFromNativeBehavior(
nativeBehavior,
closedWorld,
);
push(
HEmbeddedGlobalGet(globalName, nativeBehavior, ssaType)
..sourceInformation = sourceInformation,
);
}
void _handleForeignJsBuiltin(
ir.StaticInvocation invocation,
SourceInformation? sourceInformation,
) {
if (_unexpectedForeignArguments(invocation, minPositional: 2)) {
// Result expected on stack.
stack.add(
graph.addConstantNull(closedWorld)
..sourceInformation = sourceInformation,
);
return;
}
List<ir.Expression> arguments = invocation.arguments.positional;
ir.Expression nameArgument = arguments[1];
nameArgument.accept(this);
HInstruction instruction = pop();
js.Template? template;
if (instruction is HConstant) {
template = _getJsBuiltinTemplate(instruction.constant, _emitter);
}
if (template == null) {
reporter.reportErrorMessage(
_elementMap.getSpannable(targetElement, nameArgument),
MessageKind.generic,
{'text': 'Error: Expected a JsBuiltin enum value.'},
);
// Result expected on stack.
stack.add(
graph.addConstantNull(closedWorld)
..sourceInformation = sourceInformation,
);
return;
}
List<HInstruction> inputs = [];
for (ir.Expression argument in arguments.skip(2)) {
argument.accept(this);
inputs.add(pop());
}
NativeBehavior nativeBehavior = _elementMap
.getNativeBehaviorForJsBuiltinCall(invocation);
AbstractValue ssaType = _typeInferenceMap.typeFromNativeBehavior(
nativeBehavior,
closedWorld,
);
push(
HForeignCode(template, ssaType, inputs, nativeBehavior: nativeBehavior)
..sourceInformation = sourceInformation,
);
}
/// Returns the [js.Template] for the `JsBuiltin` [constant] value.
js.Template? _getJsBuiltinTemplate(
ConstantValue constant,
ModularEmitter emitter,
) {
final index = _extractEnumIndexFromConstantValue(
constant,
_commonElements.jsBuiltinEnum,
);
if (index == null) return null;
return _templateForBuiltin(JsBuiltin.values[index]);
}
/// Returns the JS template for the given [builtin].
js.Template? _templateForBuiltin(JsBuiltin builtin) {
switch (builtin) {
case JsBuiltin.dartObjectConstructor:
ClassEntity objectClass = closedWorld.commonElements.objectClass;
return js.js.expressionTemplateYielding(
_emitter.constructorAccess(objectClass),
);
case JsBuiltin.dartClosureConstructor:
ClassEntity closureClass = closedWorld.commonElements.closureClass;
// TODO(sra): Should add a dependency on the constructor used as a
// token.
registry
// ignore:deprecated_member_use_from_same_package
.registerInstantiatedClass(closureClass);
return js.js.expressionTemplateYielding(
_emitter.constructorAccess(closureClass),
);
case JsBuiltin.getMetadata:
String metadataAccess = _emitter.generateEmbeddedGlobalAccessString(
METADATA,
);
return js.js.expressionTemplateFor("$metadataAccess[#]");
case JsBuiltin.getType:
String typesAccess = _emitter.generateEmbeddedGlobalAccessString(TYPES);
return js.js.expressionTemplateFor("$typesAccess[#]");
case JsBuiltin.isJsInteropTypeArgument:
reporter.internalError(
noLocationSpannable,
"Unhandled Builtin: $builtin",
);
}
}
void _handleForeignJsBool(
bool value,
ir.StaticInvocation invocation,
SourceInformation? sourceInformation,
) {
_unexpectedForeignArguments(invocation, minPositional: 0, maxPositional: 0);
stack.add(
graph.addConstantBool(value, closedWorld)
..sourceInformation = sourceInformation,
);
}
void _handleJsInterceptorConstant(
ir.StaticInvocation invocation,
SourceInformation? sourceInformation,
) {
// Single argument must be a TypeConstant which is converted into a
// InterceptorConstant.
if (_unexpectedForeignArguments(
invocation,
minPositional: 1,
maxPositional: 1,
)) {
// Result expected on stack.
stack.add(
graph.addConstantNull(closedWorld)
..sourceInformation = sourceInformation,
);
return;
}
ir.Expression argument = invocation.arguments.positional.single;
argument.accept(this);
HInstruction argumentInstruction = pop();
if (argumentInstruction is HConstant) {
ConstantValue argumentConstant = argumentInstruction.constant;
if (argumentConstant is TypeConstantValue) {
final interfaceType =
argumentConstant.representedType.withoutNullability;
if (interfaceType is InterfaceType) {
// TODO(sra): Check that type is a subclass of [Interceptor].
ConstantValue constant = InterceptorConstantValue(
interfaceType.element,
);
HInstruction instruction = graph.addConstant(constant, closedWorld)
..sourceInformation = sourceInformation;
stack.add(instruction);
return;
}
}
}
reporter.reportErrorMessage(
_elementMap.getSpannable(targetElement, invocation),
MessageKind.wrongArgumentForJSInterceptorConstant,
);
stack.add(
graph.addConstantNull(closedWorld)..sourceInformation = sourceInformation,
);
}
void _handleForeignGetInterceptor(
ir.StaticInvocation invocation,
SourceInformation? sourceInformation,
) {
// Single argument is the intercepted object.
if (_unexpectedForeignArguments(
invocation,
minPositional: 1,
maxPositional: 1,
)) {
// Result expected on stack.
stack.add(
graph.addConstantNull(closedWorld)
..sourceInformation = sourceInformation,
);
return;
}
ir.Expression argument = invocation.arguments.positional.single;
argument.accept(this);
HInstruction argumentInstruction = pop();
HInstruction instruction = _interceptorFor(
argumentInstruction,
sourceInformation,
)..sourceInformation = sourceInformation;
stack.add(instruction);
}
void _handleForeignGetJSArrayInteropRti(
ir.StaticInvocation invocation,
SourceInformation? sourceInformation,
) {
if (_unexpectedForeignArguments(
invocation,
minPositional: 0,
maxPositional: 0,
)) {
// Result expected on stack.
stack.add(
graph.addConstantNull(closedWorld)
..sourceInformation = sourceInformation,
);
return;
}
// TODO(sra): This should be JSArray<any>, created via
// _elementEnvironment.getJsInteropType(_elementEnvironment.jsArrayClass);
InterfaceType interopType = dartTypes.interfaceType(
_commonElements.jsArrayClass,
[dartTypes.dynamicType()],
);
HInstruction rti = HLoadType.type(
interopType,
_abstractValueDomain.dynamicType,
)..sourceInformation = sourceInformation;
push(rti);
}
bool _equivalentToMissingRti(InterfaceType type) {
assert(type.element == _commonElements.jsArrayClass);
return dartTypes.isTopType(type.typeArguments.single);
}
void _handleForeignJs(
ir.StaticInvocation invocation,
SourceInformation? sourceInformation,
) {
if (_unexpectedForeignArguments(
invocation,
minPositional: 2,
maxPositional: null,
typeArgumentCount: 1,
)) {
// Result expected on stack.
stack.add(
graph.addConstantNull(closedWorld)
..sourceInformation = sourceInformation,
);
return;
}
NativeBehavior nativeBehavior = _elementMap.getNativeBehaviorForJsCall(
invocation,
);
List<HInstruction> inputs = [];
for (ir.Expression argument in invocation.arguments.positional.skip(2)) {
argument.accept(this);
inputs.add(pop());
}
final codeTemplate = nativeBehavior.codeTemplate!;
if (codeTemplate.positionalArgumentCount != inputs.length) {
reporter.reportErrorMessage(
_elementMap.getSpannable(targetElement, invocation),
MessageKind.generic,
{
'text':
'Mismatch between number of placeholders'
' and number of arguments.',
},
);
// Result expected on stack.
stack.add(
graph.addConstantNull(closedWorld)
..sourceInformation = sourceInformation,
);
return;
}
if (HasCapturedPlaceholders.check(codeTemplate.ast)) {
reporter.reportErrorMessage(
_elementMap.getSpannable(targetElement, invocation),
MessageKind.jsPlaceholderCapture,
);
}
AbstractValue ssaType = _typeInferenceMap.typeFromNativeBehavior(
nativeBehavior,
closedWorld,
);
HInstruction code = HForeignCode(
codeTemplate,
ssaType,
inputs,
isStatement: !codeTemplate.isExpression,
effects: nativeBehavior.sideEffects,
nativeBehavior: nativeBehavior,
)..sourceInformation = sourceInformation;
push(code);
DartType type = _getDartTypeIfValid(invocation.arguments.types.single);
final trustedMask = _typeBuilder.trustTypeMask(type);
if (trustedMask != null) {
// We only allow the type argument to narrow `dynamic`, which probably
// comes from an unspecified return type in the NativeBehavior.
if (_abstractValueDomain
.containsAll(code.instructionType)
.isPotentiallyTrue) {
// Overwrite the type with the narrower type.
code.instructionType = trustedMask;
}
// It is acceptable for the type parameter to be broader than the
// specified type.
}
_maybeAddNullCheckOnJS(invocation);
}
/// If [invocation] is a `JS()` invocation in a web library and the static
/// type is non-nullable, add a check to make sure it isn't null.
void _maybeAddNullCheckOnJS(ir.StaticInvocation invocation) {
if (options.nativeNullAssertions &&
nodeIsInWebLibrary(invocation) &&
closedWorld.dartTypes.isNonNullable(_getStaticType(invocation))) {
HInstruction code = pop();
push(
HNullCheck(
code,
_abstractValueDomain.excludeNull(code.instructionType),
sticky: true,
),
);
}
}
void _maybeAddInteropNullAssertionForMember(
FunctionEntity member,
int argumentCount, {
SourceInformation? sourceInformation,
}) {
if (options.interopNullAssertions) {
final functionType = _elementEnvironment.getFunctionType(member);
if (dartTypes.isNonNullable(functionType.returnType)) {
final name = PublicName(
_nativeData.computeUnescapedJSInteropName(member.name!),
);
_addInteropNullAssertionForSelector(
Selector.call(name, CallStructure.unnamed(argumentCount)),
sourceInformation: sourceInformation,
);
}
}
}
void _maybeAddInteropNullAssertionForSelector(
Selector selector, {
SourceInformation? sourceInformation,
}) {
if (options.interopNullAssertions &&
_nativeData.interopNullChecks[selector] ==
InteropNullCheckKind.callerCheck) {
_addInteropNullAssertion(sourceInformation: sourceInformation);
}
}
void _addInteropNullAssertionForSelector(
Selector selector, {
SourceInformation? sourceInformation,
}) {
if (_nativeData.interopNullChecks[selector] ==
InteropNullCheckKind.callerCheck) {
_addInteropNullAssertion(sourceInformation: sourceInformation);
}
}
void _maybeAddInteropNullAssertionForStatic(
DartType returnType, {
SourceInformation? sourceInformation,
}) {
if (options.interopNullAssertions &&
closedWorld.dartTypes.isNonNullable(returnType)) {
_addInteropNullAssertion(sourceInformation: sourceInformation);
}
}
void _addInteropNullAssertion({SourceInformation? sourceInformation}) {
final value = pop();
_pushStaticInvocation(
_commonElements.interopNullAssertion,
[value],
value.instructionType,
const <DartType>[],
sourceInformation: sourceInformation,
);
}
void _handleJsStringConcat(
ir.StaticInvocation invocation,
SourceInformation? sourceInformation,
) {
if (_unexpectedForeignArguments(
invocation,
minPositional: 2,
maxPositional: 2,
)) {
// Result expected on stack.
stack.add(
graph.addConstantNull(closedWorld)
..sourceInformation = sourceInformation,
);
return;
}
List<HInstruction> inputs = _visitPositionalArguments(invocation.arguments);
push(
HStringConcat(inputs[0], inputs[1], _abstractValueDomain.stringType)
..sourceInformation = sourceInformation,
);
}
void _handleCharCodeAt(
ir.StaticInvocation invocation,
SourceInformation? sourceInformation,
) {
if (_unexpectedForeignArguments(
invocation,
minPositional: 2,
maxPositional: 2,
)) {
// Result expected on stack.
stack.add(
graph.addConstantNull(closedWorld)
..sourceInformation = sourceInformation,
);
return;
}
List<HInstruction> inputs = _visitPositionalArguments(invocation.arguments);
push(
HCharCodeAt(inputs[0], inputs[1], _abstractValueDomain.uint31Type)
..sourceInformation = sourceInformation,
);
}
void _handleArrayFlagsGet(
ir.StaticInvocation invocation,
SourceInformation? sourceInformation,
) {
if (_unexpectedForeignArguments(
invocation,
minPositional: 1,
maxPositional: 1,
)) {
// Result expected on stack.
stack.add(graph.addConstantNull(closedWorld));
return;
}
List<HInstruction> inputs = _visitPositionalArguments(invocation.arguments);
final array = inputs.single;
push(
HArrayFlagsGet(array, _abstractValueDomain.uint31Type)
..sourceInformation = sourceInformation,
);
}
void _handleArrayFlagsSet(
ir.StaticInvocation invocation,
SourceInformation? sourceInformation,
) {
if (_unexpectedForeignArguments(
invocation,
minPositional: 2,
maxPositional: 2,
typeArgumentCount: 1,
)) {
// Result expected on stack.
stack.add(graph.addConstantNull(closedWorld));
return;
}
List<HInstruction> inputs = _visitPositionalArguments(invocation.arguments);
final array = inputs[0];
final flags = inputs[1];
// TODO(sra): Use the flags to improve in the AbstractValue, which may
// contain powerset domain bits outside of the conventional type
// system. Perhaps do this in types_propagation.
DartType type = _getDartTypeIfValid(invocation.arguments.types.single);
AbstractValue? instructionType = _typeBuilder.trustTypeMask(type);
// TODO(sra): Better type
instructionType ??= _abstractValueDomain.dynamicType;
push(
HArrayFlagsSet(array, flags, instructionType)
..sourceInformation = sourceInformation,
);
}
void _handleArrayFlagsCheck(
ir.StaticInvocation invocation,
SourceInformation? sourceInformation,
) {
if (_unexpectedForeignArguments(
invocation,
minPositional: 4,
maxPositional: 5,
typeArgumentCount: 1,
)) {
// Result expected on stack.
stack.add(graph.addConstantNull(closedWorld));
return;
}
List<HInstruction> inputs = _visitPositionalArguments(invocation.arguments);
final array = inputs[0];
final arrayFlags = inputs[1];
final checkFlags = inputs[2];
final operation = inputs[3];
final verb = inputs.length > 4 ? inputs[4] : null;
// TODO(sra): Use the flags to improve in the AbstractValue, which may
// contain powerset domain bits outside of the conventional type
// system. Perhaps do this in types_propagation.
DartType type = _getDartTypeIfValid(invocation.arguments.types.single);
AbstractValue? instructionType = _typeBuilder.trustTypeMask(type);
// TODO(sra): Better type
instructionType ??= _abstractValueDomain.dynamicType;
push(
HArrayFlagsCheck(
array,
arrayFlags,
checkFlags,
operation,
verb,
instructionType,
)..sourceInformation = sourceInformation,
);
}
void _handleForeignTypeRef(
ir.StaticInvocation invocation,
SourceInformation? sourceInformation,
) {
if (_unexpectedForeignArguments(
invocation,
minPositional: 0,
maxPositional: 0,
typeArgumentCount: 1,
)) {
stack.add(
// Result expected on stack.
graph.addConstantNull(closedWorld)
..sourceInformation = sourceInformation,
);
return;
}
DartType type = _elementMap.getDartType(invocation.arguments.types.single);
push(
HLoadType.type(type, _abstractValueDomain.dynamicType)
..sourceInformation = sourceInformation,
);
}
void _handleForeignCreateJsSentinel(
ir.StaticInvocation invocation,
SourceInformation? sourceInformation,
) {
if (_unexpectedForeignArguments(
invocation,
minPositional: 0,
maxPositional: 0,
typeArgumentCount: 1,
)) {
stack.add(
// Result expected on stack.
graph.addConstantNull(closedWorld)
..sourceInformation = sourceInformation,
);
return;
}
stack.add(
graph.addConstantLateSentinel(
closedWorld,
sourceInformation: sourceInformation,
),
);
}
void _handleForeignIsJsSentinel(
ir.StaticInvocation invocation,
SourceInformation? sourceInformation,
) {
if (_unexpectedForeignArguments(
invocation,
minPositional: 1,
maxPositional: 1,
)) {
stack.add(
// Result expected on stack.
graph.addConstantNull(closedWorld)
..sourceInformation = sourceInformation,
);
return;
}
HInstruction checkedExpression = _visitPositionalArguments(
invocation.arguments,
).single;
push(
HIsLateSentinel(checkedExpression, _abstractValueDomain.boolType)
..sourceInformation = sourceInformation,
);
}
// TODO(fishythefish): Support specialization of late sentinels based on type.
void _handleLateReadCheck(
ir.StaticInvocation invocation,
SourceInformation? sourceInformation,
) {
if (_unexpectedForeignArguments(
invocation,
minPositional: 2,
maxPositional: 2,
typeArgumentCount: 1,
)) {
stack.add(
// Result expected on stack.
graph.addConstantNull(closedWorld)
..sourceInformation = sourceInformation,
);
return;
}
List<HInstruction> arguments = _visitPositionalArguments(
invocation.arguments,
);
HInstruction value = arguments[0];
final name = options.omitLateNames ? null : arguments[1];
CheckPolicy policy = closedWorld.annotationsData
.getLateVariableCheckPolicyAt(invocation);
push(
HLateReadCheck(
value,
name,
policy.isTrusted,
_abstractValueDomain.excludeLateSentinel(value.instructionType),
)..sourceInformation = sourceInformation,
);
}
void _handleLateWriteOnceCheck(
ir.StaticInvocation invocation,
SourceInformation? sourceInformation,
) {
if (_unexpectedForeignArguments(
invocation,
minPositional: 2,
maxPositional: 2,
)) {
stack.add(
// Result expected on stack.
graph.addConstantNull(closedWorld)
..sourceInformation = sourceInformation,
);
return;
}
List<HInstruction> arguments = _visitPositionalArguments(
invocation.arguments,
);
HInstruction value = arguments[0];
final name = options.omitLateNames ? null : arguments[1];
CheckPolicy policy = closedWorld.annotationsData
.getLateVariableCheckPolicyAt(invocation);
push(
HLateWriteOnceCheck(
value,
name,
policy.isTrusted,
_abstractValueDomain.dynamicType,
)..sourceInformation = sourceInformation,
);
}
void _handleLateInitializeOnceCheck(
ir.StaticInvocation invocation,
SourceInformation? sourceInformation,
) {
if (_unexpectedForeignArguments(
invocation,
minPositional: 2,
maxPositional: 2,
)) {
stack.add(
// Result expected on stack.
graph.addConstantNull(closedWorld)
..sourceInformation = sourceInformation,
);
return;
}
List<HInstruction> arguments = _visitPositionalArguments(
invocation.arguments,
);
HInstruction value = arguments[0];
final name = options.omitLateNames ? null : arguments[1];
CheckPolicy policy = closedWorld.annotationsData
.getLateVariableCheckPolicyAt(invocation);
push(
HLateInitializeOnceCheck(
value,
name,
policy.isTrusted,
_abstractValueDomain.dynamicType,
)..sourceInformation = sourceInformation,
);
}
void _pushStaticInvocation(
FunctionEntity target,
List<HInstruction> arguments,
AbstractValue typeMask,
List<DartType> typeArguments, {
SourceInformation? sourceInformation,
InterfaceType? instanceType,
}) {
// TODO(redemption): Pass current node if needed.
if (_tryInlineMethod(
target,
null,
null,
arguments,
typeArguments,
null,
sourceInformation,
instanceType: instanceType,
)) {
return;
}
if (closedWorld.nativeData.isJsInteropMember(target)) {
_pushStaticNativeInvocation(
target,
arguments,
typeMask,
typeArguments,
sourceInformation: sourceInformation,
);
return;
}
HInvokeStatic instruction = HInvokeStatic(
target,
arguments,
typeMask,
typeArguments,
targetCanThrow: !_inferredData.getCannotThrow(target),
)..sourceInformation = sourceInformation;
if (_currentImplicitInstantiations.isNotEmpty) {
instruction.instantiatedTypes = List<InterfaceType>.from(
_currentImplicitInstantiations,
);
}
instruction.sideEffects.setTo(
_inferredData.getSideEffectsOfElement(target),
);
instruction.allowCSE = closedWorld.annotationsData.allowCSE(target);
instruction.allowDCE = closedWorld.annotationsData.allowDCE(target);
push(instruction);
}
void _pushStaticNativeInvocation(
FunctionEntity target,
List<HInstruction?> arguments,
AbstractValue typeMask,
List<DartType> typeArguments, {
SourceInformation? sourceInformation,
}) {
_invokeJsInteropFunction(
target,
arguments,
sourceInformation: sourceInformation,
);
}
void _pushDynamicInvocation(
ir.Node node,
DartType staticReceiverType,
AbstractValue? receiverType,
Selector selector,
List<HInstruction> arguments,
List<DartType> typeArguments,
SourceInformation? sourceInformation,
) {
AbstractValue typeBound = _abstractValueDomain
.createFromStaticType(staticReceiverType)
.abstractValue;
receiverType = receiverType == null
? typeBound
: _abstractValueDomain.intersection(receiverType, typeBound);
// We prefer to not inline certain operations on indexables,
// because the constant folder will handle them better and turn
// them into simpler instructions that allow further
// optimizations.
bool isOptimizableOperationOnIndexable(
Selector selector,
MemberEntity element,
) {
bool isLength = selector.isGetter && selector.name == "length";
if (isLength || selector.isIndex) {
return closedWorld.classHierarchy.isSubtypeOf(
element.enclosingClass!,
_commonElements.jsIndexableClass,
);
} else if (selector.isIndexSet) {
return closedWorld.classHierarchy.isSubtypeOf(
element.enclosingClass!,
_commonElements.jsMutableIndexableClass,
);
} else {
return false;
}
}
bool isOptimizableOperation(Selector selector, MemberEntity element) {
final cls = element.enclosingClass;
if (isOptimizableOperationOnIndexable(selector, element)) return true;
if (!_interceptorData.interceptedClasses.contains(cls)) return false;
if (selector.isOperator) return true;
if (selector.isSetter) return true;
if (selector.isIndex) return true;
if (selector.isIndexSet) return true;
if (element == _commonElements.jsArrayAdd ||
element == _commonElements.jsArrayRemoveLast ||
_commonElements.isJsStringSplit(element)) {
return true;
}
return false;
}
final element = closedWorld.locateSingleMember(selector, receiverType);
if (element != null &&
element is FunctionEntity &&
!(element.isGetter && selector.isCall) &&
!(element.isFunction && selector.isGetter) &&
!isOptimizableOperation(selector, element)) {
if (_tryInlineMethod(
element,
selector,
receiverType,
arguments,
typeArguments,
node,
sourceInformation,
)) {
return;
}
}
HInstruction receiver = arguments.first;
List<HInstruction> inputs = [];
bool isIntercepted = closedWorld.interceptorData.isInterceptedSelector(
selector,
);
if (isIntercepted) {
HInterceptor interceptor = _interceptorFor(receiver, sourceInformation);
inputs.add(interceptor);
}
inputs.addAll(arguments);
AbstractValue resultType = _typeInferenceMap.resultTypeOfSelector(
selector,
receiverType,
);
HInvokeDynamic invoke;
if (selector.isGetter) {
_metrics.countGettersTotal.add();
invoke = HInvokeDynamicGetter(
selector,
receiverType,
element,
inputs,
isIntercepted,
resultType,
sourceInformation,
);
} else if (selector.isSetter) {
_metrics.countSettersTotal.add();
invoke = HInvokeDynamicSetter(
selector,
receiverType,
element,
inputs,
isIntercepted,
resultType,
sourceInformation,
);
} else if (selector.isMaybeClosureCall) {
assert(!isIntercepted);
invoke = HInvokeClosure(
selector,
receiverType,
inputs,
resultType,
typeArguments,
)..sourceInformation = sourceInformation;
} else {
invoke = HInvokeDynamicMethod(
selector,
receiverType,
inputs,
resultType,
typeArguments,
sourceInformation,
isIntercepted: isIntercepted,
);
}
invoke.instructionContext = _currentFrame!.member;
if (node is ir.InstanceInvocation) {
invoke.isInvariant = node.isInvariant;
invoke.isBoundsSafe = node.isBoundsSafe;
if (node.receiver is ir.ThisExpression) {
// If the receiver of an instance invocation is `this` then the call is
// invariant with respect to the class type variables.
invoke.isInvariant = true;
}
} else if (node is ir.InstanceSet) {
if (node.receiver is ir.ThisExpression) {
// If the receiver of an instance invocation is `this` then the call is
// invariant with respect to the class type variables.
invoke.isInvariant = true;
}
}
if (element != null) {
invoke.allowCSE = closedWorld.annotationsData.allowCSE(element);
invoke.allowDCE = closedWorld.annotationsData.allowDCE(element);
}
if (node is ir.InstanceInvocation ||
node is ir.FunctionInvocation ||
node is ir.InstanceGet) {
final staticType = _abstractValueDomain
.createFromStaticType(_getStaticType(node as ir.Expression))
.abstractValue;
invoke.staticType = staticType;
invoke.instructionType = invoke.computeInstructionType(
resultType,
_abstractValueDomain,
);
}
push(invoke);
if (element != null &&
_abstractValueDomain.isNull(resultType).isDefinitelyFalse) {
_maybeAddInteropNullAssertionForSelector(
selector,
sourceInformation: sourceInformation,
);
}
}
void _invokeJsInteropFunction(
FunctionEntity element,
List<HInstruction?> arguments, {
SourceInformation? sourceInformation,
}) {
assert(closedWorld.nativeData.isJsInteropMember(element));
bool isAnonymousFactory =
element is ConstructorEntity &&
element.isFactoryConstructor &&
_nativeData.isAnonymousJsInteropClass(element.enclosingClass);
ir.Node node = _elementMap.getMemberDefinition(element).node;
// JS interop checks assert that the only inline class interop member that
// has named parameters is an object literal constructor. We could do a more
// robust check by visiting all inline classes and recording descriptors,
// but that's expensive.
bool isObjectLiteralConstructor =
node is ir.Procedure &&
node.isExtensionTypeMember &&
node.function.namedParameters.isNotEmpty;
if (isAnonymousFactory || isObjectLiteralConstructor) {
// Constructor that is syntactic sugar for creating a JavaScript object
// literal.
int i = 0;
int positions = 0;
List<HInstruction> filteredArguments = [];
Map<String, js.Expression> parameterNameMap = {};
// Note: we don't use `constructor.parameterStructure` here because
// we don't elide parameters to js-interop external static targets
// (including factory constructors.)
// TODO(johnniwinther): can we elide those parameters? This should be
// consistent with what we do with instance methods.
final procedure = node as ir.Procedure;
List<ir.VariableDeclaration> namedParameters = procedure
.function
.namedParameters
.toList();
namedParameters.sort(nativeOrdering);
for (ir.VariableDeclaration variable in namedParameters) {
String parameterName = variable.name!;
// TODO(jacobr): consider throwing if parameter names do not match
// names of properties in the class.
final argument = arguments[i];
if (argument != null) {
filteredArguments.add(argument);
var customName = getDartJSInteropJSName(variable);
var jsName = (customName.isNotEmpty && isObjectLiteralConstructor)
? customName
: _nativeData.computeUnescapedJSInteropName(parameterName);
parameterNameMap[jsName] = js.InterpolatedExpression(positions++);
}
i++;
}
var codeTemplate = js.Template(null, js.objectLiteral(parameterNameMap));
var nativeBehavior = NativeBehavior()..codeTemplate = codeTemplate;
registry.registerNativeMethod(element);
// TODO(efortuna): Source information.
push(
HForeignCode(
codeTemplate,
_abstractValueDomain.dynamicType,
filteredArguments,
nativeBehavior: nativeBehavior,
),
);
return;
}
// Strip off trailing arguments that were not specified.
// we could assert that the trailing arguments are all null.
// TODO(jacobr): rewrite named arguments to an object literal matching
// the factory constructor case.
List<HInstruction> inputs = arguments.whereType<HInstruction>().toList();
var nativeBehavior = NativeBehavior()..sideEffects.setAllSideEffects();
DartType type = element is ConstructorEntity
? _elementEnvironment.getThisType(element.enclosingClass)
: _elementEnvironment.getFunctionType(element).returnType;
// Native behavior effects here are similar to native/behavior.dart.
// The return type is dynamic because we don't trust js-interop type
// declarations.
nativeBehavior.typesReturned.add(dartTypes.dynamicType());
// The allocation effects include the declared type if it is native (which
// includes js interop types).
final nonNullableType = type.withoutNullability;
if (nonNullableType is InterfaceType &&
_nativeData.isNativeClass(nonNullableType.element)) {
nativeBehavior.typesInstantiated.add(nonNullableType);
}
// It also includes any other JS interop type. Technically, a JS interop API
// could return anything, so the sound thing to do would be to assume that
// anything that may come from JS as instantiated. In order to prevent the
// resulting code bloat (e.g. from `dart:html`), we unsoundly assume that
// only JS interop types are returned.
nativeBehavior.typesInstantiated.add(
_elementEnvironment.getThisType(
_commonElements.jsLegacyJavaScriptObjectClass,
),
);
AbstractValue instructionType = _typeInferenceMap.typeFromNativeBehavior(
nativeBehavior,
closedWorld,
);
push(
HInvokeExternal(
element,
inputs,
instructionType,
nativeBehavior,
sourceInformation: sourceInformation,
),
);
_maybeAddInteropNullAssertionForStatic(
type,
sourceInformation: sourceInformation,
);
}
@override
void visitFunctionNode(ir.FunctionNode node) {
final sourceInformation = _sourceInformationBuilder.buildCreate(node);
ClosureRepresentationInfo closureInfo = _closureDataLookup.getClosureInfo(
node.parent as ir.LocalFunction,
);
ClassEntity closureClassEntity = closureInfo.closureClassEntity!;
List<HInstruction> capturedVariables = [];
_elementEnvironment.forEachInstanceField(closureClassEntity, (
_,
FieldEntity field,
) {
if (_fieldAnalysis.getFieldData(field).isElided) return;
capturedVariables.add(
localsHandler.readLocal(
closureInfo.getLocalForField(_localsMap, field),
),
);
});
AbstractValue type = _abstractValueDomain.createNonNullExact(
closureClassEntity,
);
// TODO(efortuna): Add source information here.
push(
HCreate(
closureClassEntity,
capturedVariables,
type,
sourceInformation,
callMethod: closureInfo.callMethod,
),
);
}
@override
void visitFunctionDeclaration(ir.FunctionDeclaration node) {
assert(_isReachable);
node.function.accept(this);
Local local = _localsMap.getLocalVariable(node.variable);
localsHandler.updateLocal(local, pop());
}
@override
void visitFunctionExpression(ir.FunctionExpression node) {
node.function.accept(this);
}
@override
void visitInstantiation(ir.Instantiation node) {
List<HInstruction> arguments = [];
node.expression.accept(this);
arguments.add(pop());
// A generic function instantiation is created by calling a helper function
// which takes the arguments.
int typeArgumentCount = node.typeArguments.length;
FunctionEntity target = _commonElements.getInstantiateFunction(
typeArgumentCount,
);
final expressionType = _getStaticType(node.expression);
final functionType = expressionType.withoutNullability as FunctionType;
bool typeArgumentsNeeded = _rtiNeed.methodNeedsTypeArguments(target);
List<DartType> typeArguments = node.typeArguments
.map(
(type) => typeArgumentsNeeded
? _elementMap.getDartType(type)
: _commonElements.dynamicType,
)
.toList();
registry.registerGenericInstantiation(
GenericInstantiation(functionType, typeArguments),
);
// TODO(sra): Add instantiations to SourceInformationBuilder.
SourceInformation? sourceInformation;
// TODO(47484): Allow callee to have different calling convention for type
// arguments.
if (typeArgumentsNeeded) {
_addTypeArguments(arguments, typeArguments, sourceInformation);
}
bool targetCanThrow = false; // TODO(sra): Is this true?
// TODO(sra): Use [_pushStaticInvocation] to allow inlining. We don't now
// because inference can't tell that the call has no side-effects.
HInstruction instruction = HInvokeStatic(
target,
arguments,
_abstractValueDomain.functionType,
<DartType>[],
targetCanThrow: targetCanThrow,
);
instruction.sourceInformation = sourceInformation;
instruction.sideEffects
..clearAllDependencies()
..clearAllSideEffects();
push(instruction);
}
void _handleMethodInvocation(
ir.Expression node,
ir.Expression receiver,
ir.Arguments arguments, {
bool isImplicitCall = false,
}) {
receiver.accept(this);
HInstruction receiverInstruction = pop();
Selector selector = _elementMap.getSelector(node);
List<DartType> typeArguments = [];
selector = _fillDynamicTypeArguments(
selector,
arguments,
typeArguments,
isImplicitCall: isImplicitCall,
);
_pushDynamicInvocation(
node,
_getStaticType(receiver),
_typeInferenceMap.receiverTypeOfInvocation(node, _abstractValueDomain),
selector,
[
receiverInstruction,
..._visitArgumentsForDynamicTarget(selector, arguments, typeArguments),
],
typeArguments,
_sourceInformationBuilder.buildCall(receiver, node),
);
}
@override
void visitInstanceInvocation(ir.InstanceInvocation node) {
_handleMethodInvocation(node, node.receiver, node.arguments);
}
@override
void visitInstanceGetterInvocation(ir.InstanceGetterInvocation node) {
_handleMethodInvocation(
node,
node.receiver,
node.arguments,
isImplicitCall: true,
);
}
@override
void visitDynamicInvocation(ir.DynamicInvocation node) {
_handleMethodInvocation(node, node.receiver, node.arguments);
}
@override
void visitFunctionInvocation(ir.FunctionInvocation node) {
_handleMethodInvocation(node, node.receiver, node.arguments);
}
@override
void visitLocalFunctionInvocation(ir.LocalFunctionInvocation node) {
Local local = _localsMap.getLocalVariable(node.variable);
stack.add(
localsHandler.readLocal(
local,
sourceInformation: _sourceInformationBuilder.buildGet(node),
),
);
HInstruction receiverInstruction = pop();
Selector selector = _elementMap.getSelector(node);
List<DartType> typeArguments = [];
selector = _fillDynamicTypeArguments(
selector,
node.arguments,
typeArguments,
);
_pushDynamicInvocation(
node,
_elementMap.getDartType(node.variable.type),
_typeInferenceMap.receiverTypeOfInvocation(node, _abstractValueDomain),
selector,
[
receiverInstruction,
..._visitArgumentsForDynamicTarget(
selector,
node.arguments,
typeArguments,
),
],
typeArguments,
_sourceInformationBuilder.buildCall(node, node),
);
}
void _handleEquals(
ir.Expression node,
ir.Expression left,
HInstruction leftInstruction,
HInstruction rightInstruction,
) {
_pushDynamicInvocation(
node,
_getStaticType(left),
_typeInferenceMap.receiverTypeOfInvocation(node, _abstractValueDomain),
Selectors.equals,
[leftInstruction, rightInstruction],
const <DartType>[],
_sourceInformationBuilder.buildCall(left, node),
);
}
@override
void visitEqualsNull(ir.EqualsNull node) {
node.expression.accept(this);
HInstruction receiverInstruction = pop();
_handleEquals(
node,
node.expression,
receiverInstruction,
graph.addConstantNull(closedWorld),
);
}
@override
void visitEqualsCall(ir.EqualsCall node) {
node.left.accept(this);
HInstruction leftInstruction = pop();
node.right.accept(this);
HInstruction rightInstruction = pop();
return _handleEquals(node, node.left, leftInstruction, rightInstruction);
}
HInterceptor _interceptorFor(
HInstruction intercepted,
SourceInformation? sourceInformation,
) {
HInterceptor interceptor = HInterceptor(
intercepted,
_abstractValueDomain.nonNullType,
)..sourceInformation = sourceInformation;
add(interceptor);
return interceptor;
}
static ir.Class _containingClass(ir.TreeNode? node) {
while (node != null) {
if (node is ir.Class) return node;
node = node.parent;
}
throw ArgumentError.value(node, 'node', 'No containing class found.');
}
HInstruction _buildInvokeSuper(
Selector selector,
ClassEntity containingClass,
MemberEntity target,
List<HInstruction> arguments,
List<DartType> typeArguments,
SourceInformation? sourceInformation,
) {
HInstruction receiver = localsHandler.readThis(
sourceInformation: sourceInformation,
);
List<HInstruction> inputs = [];
bool isIntercepted = closedWorld.interceptorData.isInterceptedSelector(
selector,
);
if (isIntercepted) {
inputs.add(_interceptorFor(receiver, sourceInformation));
}
inputs.add(receiver);
inputs.addAll(arguments);
late final AbstractValue typeMask;
if (selector.isGetter && target.isGetter ||
!selector.isGetter && target is FunctionEntity) {
typeMask = _typeInferenceMap.getReturnTypeOf(target as FunctionEntity);
} else {
typeMask = _abstractValueDomain.dynamicType;
}
HInstruction instruction = HInvokeSuper(
target,
containingClass,
selector,
inputs,
isIntercepted,
typeMask,
typeArguments,
sourceInformation,
isSetter: selector.isSetter || selector.isIndexSet,
);
// TODO(natebiggs): Pass typeMask below and make non-nullable.
instruction.sideEffects.setTo(
_inferredData.getSideEffectsOfSelector(selector, null),
);
push(instruction);
return instruction;
}
@override
void visitSuperPropertyGet(ir.SuperPropertyGet node) {
final sourceInformation = _sourceInformationBuilder.buildGet(node);
final target = getEffectiveSuperTarget(node.interfaceTarget);
MemberEntity member = _elementMap.getMember(target);
if (member is FieldEntity) {
FieldAnalysisData fieldData = _fieldAnalysis.getFieldData(member);
if (fieldData.isEffectivelyConstant) {
final value = fieldData.constantValue!;
stack.add(
graph.addConstant(
value,
closedWorld,
sourceInformation: sourceInformation,
),
);
return;
}
}
_buildInvokeSuper(
_elementMap.getSelector(node),
_elementMap.getClass(_containingClass(node)),
member,
const <HInstruction>[],
const <DartType>[],
sourceInformation,
);
}
@override
void visitSuperMethodInvocation(ir.SuperMethodInvocation node) {
final sourceInformation = _sourceInformationBuilder.buildCall(node, node);
final superTarget = getEffectiveSuperTarget(node.interfaceTarget);
MemberEntity member = _elementMap.getMember(superTarget);
List<DartType> typeArguments = _getStaticTypeArguments(
member as FunctionEntity,
node.arguments,
);
MemberDefinition targetDefinition = _elementMap.getMemberDefinition(member);
final target = targetDefinition.node as ir.Procedure;
List<HInstruction> arguments = _visitArgumentsForStaticTarget(
target.function,
member.parameterStructure,
node.arguments,
typeArguments,
sourceInformation,
);
_buildInvokeSuper(
_elementMap.getSelector(node),
_elementMap.getClass(_containingClass(node)),
member,
arguments,
typeArguments,
sourceInformation,
);
}
void _checkTypeBound(
HInstruction typeInstruction,
DartType bound,
String variableName,
String methodName,
) {
HInstruction boundInstruction = _typeBuilder.analyzeTypeArgument(
localsHandler.substInContext(bound),
sourceElement,
);
HInstruction variableNameInstruction = graph.addConstantString(
variableName,
closedWorld,
);
HInstruction methodNameInstruction = graph.addConstantString(
methodName,
closedWorld,
);
FunctionEntity element = _commonElements.checkTypeBound;
List<HInstruction> inputs = [
typeInstruction,
boundInstruction,
variableNameInstruction,
methodNameInstruction,
];
HInstruction checkBound = HInvokeStatic(
element,
inputs,
typeInstruction.instructionType,
const <DartType>[],
);
add(checkBound);
}
@override
void visitConstructorInvocation(ir.ConstructorInvocation node) {
final sourceInformation = _sourceInformationBuilder.buildNew(node);
ir.Constructor target = node.target;
if (node.isConst) {
ConstantValue constant = _elementMap.getConstantValue(node)!;
stack.add(
graph.addConstant(
constant,
closedWorld,
sourceInformation: sourceInformation,
),
);
return;
}
ConstructorEntity constructor = _elementMap.getConstructor(target);
ClassEntity cls = constructor.enclosingClass;
AbstractValue typeMask = _abstractValueDomain.createNonNullExact(cls);
InterfaceType instanceType = _elementMap.createInterfaceType(
target.enclosingClass,
node.arguments.types,
);
instanceType = localsHandler.substInContext(instanceType) as InterfaceType;
if (_nativeData.isJsInteropMember(constructor)) {
List<HInstruction?> arguments = [];
List<DartType> typeArguments = _getConstructorTypeArguments(
constructor,
node.arguments,
);
arguments.addAll(
_visitArgumentsForNativeStaticTarget(target.function, node.arguments),
);
// TODO(johnniwinther): Remove this when type arguments are passed to
// constructors like calling a generic method.
_addTypeArguments(
arguments,
_getClassTypeArguments(cls, node.arguments),
sourceInformation,
);
_addImplicitInstantiation(instanceType);
_pushStaticNativeInvocation(
constructor,
arguments,
typeMask,
typeArguments,
sourceInformation: sourceInformation,
);
_removeImplicitInstantiation(instanceType);
} else {
List<HInstruction> arguments = [];
if (constructor.isGenerativeConstructor &&
_nativeData.isNativeOrExtendsNative(constructor.enclosingClass)) {
// Native class generative constructors take a pre-constructed object.
arguments.add(graph.addConstantNull(closedWorld));
}
List<DartType> typeArguments = _getConstructorTypeArguments(
constructor,
node.arguments,
);
arguments.addAll(
_visitArgumentsForStaticTarget(
target.function,
constructor.parameterStructure,
node.arguments,
typeArguments,
sourceInformation,
),
);
// TODO(johnniwinther): Remove this when type arguments are passed to
// constructors like calling a generic method.
_addTypeArguments(
arguments,
_getClassTypeArguments(cls, node.arguments),
sourceInformation,
);
_addImplicitInstantiation(instanceType);
_pushStaticInvocation(
constructor,
arguments,
typeMask,
typeArguments,
sourceInformation: sourceInformation,
instanceType: instanceType,
);
_removeImplicitInstantiation(instanceType);
}
}
@override
void visitIsExpression(ir.IsExpression node) {
node.operand.accept(this);
HInstruction expression = pop();
_pushIsTest(
_widenCheckedTypeForOperand(node.type, node.operand),
expression,
_sourceInformationBuilder.buildIs(node),
);
}
/// Returns a type that is equivalent to `checkedType`, but possibly simpler.
/// For example, if operand has static type `Iterable<E>` and checkedType is
/// `List<E>`, returns `List<dynamic>` since that is an easier test that does
/// not reference any type variables.
ir.DartType _widenCheckedTypeForOperand(
ir.DartType checkedType,
ir.Expression operand,
) {
if (checkedType is ir.InterfaceType) {
if (checkedType.typeArguments.isEmpty) return checkedType;
final operandType = operand.getStaticType(
_currentFrame!.staticTypeContext!,
);
final sufficiency = closedWorld.elementMap.typeEnvironment
.computeTypeShapeCheckSufficiency(
expressionStaticType: operandType,
checkTargetType: checkedType,
);
// If `true` the caller only needs to check nullabillity and the actual
// concrete class, no need to check [testedAgainstType] arguments.
if (sufficiency == ir.TypeShapeCheckSufficiency.interfaceShape) {
return closedWorld.elementMap.coreTypes.rawType(
checkedType.classNode,
operandType.nullability == ir.Nullability.nonNullable
? ir.Nullability.nonNullable
: checkedType.nullability,
);
}
}
return checkedType;
}
void _pushIsTest(
ir.DartType type,
HInstruction expression,
SourceInformation? sourceInformation,
) {
if (type is ir.InvalidType) {
// TODO(sra): Make InvalidType carry a message.
_generateTypeError('invalid type', sourceInformation);
pop();
stack.add(graph.addConstantBool(true, closedWorld));
return;
}
DartType typeValue = localsHandler.substInContext(
_elementMap.getDartType(type),
);
HInstruction rti = _typeBuilder.analyzeTypeArgument(
typeValue,
sourceElement,
);
AbstractValueWithPrecision checkedType = _abstractValueDomain
.createFromStaticType(typeValue);
push(
HIsTest(
typeValue,
checkedType,
rti,
expression,
_abstractValueDomain.boolType,
)..sourceInformation = sourceInformation,
);
}
@override
void visitThrow(ir.Throw node) {
_visitThrowExpression(node.expression);
if (_isReachable) {
final sourceInformation = _sourceInformationBuilder.buildThrow(node);
_handleInTryStatement();
push(
HThrowExpression(
pop(),
_abstractValueDomain.emptyType,
sourceInformation,
withoutHelperFrame: closedWorld.annotationsData
.throwWithoutHelperFrame(node),
),
);
_isReachable = false;
}
}
void _visitThrowExpression(ir.Expression expression) {
bool old = _inExpressionOfThrow;
try {
_inExpressionOfThrow = true;
expression.accept(this);
} finally {
_inExpressionOfThrow = old;
}
}
@override
void visitYieldStatement(ir.YieldStatement node) {
node.expression.accept(this);
add(
HYield(
pop(),
node.isYieldStar,
_abstractValueDomain.emptyType,
_sourceInformationBuilder.buildYield(node),
),
);
}
@override
void visitAwaitExpression(ir.AwaitExpression node) {
node.operand.accept(this);
HInstruction awaited = pop();
// TODO(herhut): Improve this type.
push(
HAwait(awaited, _abstractValueDomain.dynamicType)
..sourceInformation = _sourceInformationBuilder.buildAwait(node),
);
}
@override
void visitRethrow(ir.Rethrow node) {
HInstruction? exception = _rethrowableException;
if (exception == null) {
exception = graph.addConstantNull(closedWorld);
reporter.internalError(
_elementMap.getSpannable(targetElement, node),
'rethrowableException should not be null.',
);
}
_handleInTryStatement();
final sourceInformation = _sourceInformationBuilder.buildThrow(node);
_closeAndGotoExit(HThrow(exception, sourceInformation, isRethrow: true));
// ir.Rethrow is an expression so we need to push a value - a constant with
// no type.
stack.add(graph.addConstantUnreachable(closedWorld));
}
@override
void visitThisExpression(ir.ThisExpression node) {
stack.add(
localsHandler.readThis(
sourceInformation: _sourceInformationBuilder.buildGet(node),
),
);
}
@override
void visitNot(ir.Not node) {
node.operand.accept(this);
push(
HNot(pop(), _abstractValueDomain.boolType)
..sourceInformation = _sourceInformationBuilder.buildUnary(node),
);
}
@override
void visitStringConcatenation(ir.StringConcatenation node) {
KernelStringBuilder stringBuilder = KernelStringBuilder(this);
node.accept(stringBuilder);
stack.add(stringBuilder.result!);
}
@override
void visitTryCatch(ir.TryCatch node) {
TryCatchFinallyBuilder tryBuilder = TryCatchFinallyBuilder(
this,
_sourceInformationBuilder.buildTry(node),
);
node.body.accept(this);
tryBuilder
..closeTryBody()
..buildCatch(node)
..cleanUp();
}
/// `try { ... } catch { ... } finally { ... }` statements are a little funny
/// because a try can have one or both of {catch|finally}. The way this is
/// encoded in kernel AST are two separate classes with no common superclass
/// aside from Statement. If a statement has both `catch` and `finally`
/// clauses then it is encoded in kernel as so that the TryCatch is the body
/// statement of the TryFinally. To produce more efficient code rather than
/// nested try statements, the visitors avoid one potential level of
/// recursion.
@override
void visitTryFinally(ir.TryFinally node) {
TryCatchFinallyBuilder tryBuilder = TryCatchFinallyBuilder(
this,
_sourceInformationBuilder.buildTry(node),
);
// We do these shenanigans to produce better looking code that doesn't
// have nested try statements.
final nodeBody = node.body;
if (nodeBody is ir.TryCatch) {
nodeBody.body.accept(this);
tryBuilder
..closeTryBody()
..buildCatch(nodeBody);
} else {
node.body.accept(this);
tryBuilder.closeTryBody();
}
tryBuilder
..buildFinallyBlock(() {
node.finalizer.accept(this);
})
..cleanUp();
}
@override
void visitFileUriExpression(ir.FileUriExpression node) {
node.expression.accept(this);
}
bool _tryInlineMethod(
FunctionEntity function,
Selector? selector,
AbstractValue? mask,
List<HInstruction> providedArguments,
List<DartType>? typeArguments,
ir.Node? currentNode,
SourceInformation? sourceInformation, {
InterfaceType? instanceType,
}) {
final inlined = _doTryInlineMethod(
function,
selector,
mask,
providedArguments,
typeArguments,
currentNode,
sourceInformation,
instanceType: instanceType,
);
if (inlined) {
_metrics.countMethodInlined.add();
} else {
_metrics.countMethodNotInlined.add();
}
return inlined;
}
/// Try to inline [element] within the correct context of the builder. The
/// insertion point is the state of the builder.
bool _doTryInlineMethod(
FunctionEntity function,
Selector? selector,
AbstractValue? mask,
List<HInstruction> providedArguments,
List<DartType>? typeArguments,
ir.Node? currentNode,
SourceInformation? sourceInformation, {
InterfaceType? instanceType,
}) {
if (function.isExternal) {
// Don't inline external methods; these should just fail at runtime.
return false;
}
if (_nativeData.isJsInteropMember(function) &&
!(function is ConstructorEntity && function.isFactoryConstructor)) {
// We only inline factory JavaScript interop constructors.
return false;
}
// Check if inlining is disabled for the current element (includes globally)
// before making decisions on the basis of the callee so that cached callee
// decisions are not a function of the call site's method.
if (closedWorld.annotationsData.hasDisableInlining(_currentFrame!.member)) {
return false;
}
// Don't inline functions marked with 'allow-dce' since we need the call
// instruction to recognize the whole call as unused. We might be able to
// inline simple methods afterwards.
if (closedWorld.annotationsData.allowDCE(function)) {
return false;
}
// Don't inline functions marked with 'allow-cse' since we need the call
// instructions to recognize repeated calls. We might be able to inline
// simple methods afterwards. If this is the only call site, we will never
// find the repeated call, so we should consider inlining here.
if (closedWorld.annotationsData.allowCSE(function) &&
!_isCalledOnce(function)) {
return false;
}
bool insideLoop = loopDepth > 0 || graph.calledInLoop;
// Bail out early if the inlining decision is in the cache and we can't
// inline (no need to check the hard constraints).
if (_inlineCache.markedAsNoInline(function)) return false;
final cachedCanBeInlined = _inlineCache.canInline(
function,
insideLoop: insideLoop,
);
if (cachedCanBeInlined == false) return false;
bool meetsHardConstraints() {
assert(
selector != null ||
function.isStatic ||
function.isTopLevel ||
function is ConstructorEntity ||
function is ConstructorBodyEntity,
failedAt(function, "Missing selector for inlining of $function."),
);
if (selector != null) {
if (!selector.applies(function)) return false;
if (mask != null &&
_abstractValueDomain
.isTargetingMember(mask, function, selector.memberName)
.isDefinitelyFalse) {
return false;
}
}
if (_nativeData.isJsInteropMember(function)) return false;
// Don't inline operator== methods if the parameter can be null.
if (function.name == '==') {
if (providedArguments[1]
.isNull(_abstractValueDomain)
.isPotentiallyTrue) {
return false;
}
}
// Generative constructors of native classes should not be called directly
// and have an extra argument that causes problems with inlining.
if (function is ConstructorEntity &&
function.isGenerativeConstructor &&
_nativeData.isNativeOrExtendsNative(function.enclosingClass)) {
return false;
}
// A generative constructor body is not seen by global analysis,
// so we should not query for its type.
if (function is! ConstructorBodyEntity) {
if (globalInferenceResults.resultOfMember(function).throwsAlways) {
// TODO(johnniwinther): It seems wrong to set `isReachable` to `false`
// since we are _not_ going to inline [function]. This has
// implications in switch cases where we might need to insert a
// `break` that was skipped due to `isReachable` being `false`.
_isReachable = false;
return false;
}
}
// Record getters are synthetic and have no bodies so we cannot inline
// them at this point.
if (function is JRecordGetter) return false;
return true;
}
bool doesNotContainCode(InlineData inlineData) {
// A function with size 1 does not contain any code.
return inlineData.canBeInlined(maxInliningNodes: 1);
}
bool reductiveHeuristic(InlineData inlineData) {
// The call is on a path which is executed rarely, so inline only if it
// does not make the program larger.
if (_isCalledOnce(function)) {
return inlineData.canBeInlined();
}
if (inlineData.canBeInlinedReductive(
argumentCount: providedArguments.length,
)) {
return true;
}
return doesNotContainCode(inlineData);
}
bool heuristicSayGoodToGo() {
// Don't inline recursively,
if (_inliningStack.any((entry) => entry.function == function)) {
return false;
}
// Don't inline across deferred import to prevent leaking code. The only
// exception is an empty function (which does not contain code).
bool hasOnlyNonDeferredImportPaths = closedWorld.outputUnitData
.hasOnlyNonDeferredImportPaths(_initialTargetElement, function);
InlineData inlineData = _inlineDataCache.getInlineData(
_elementMap,
function,
);
if (!hasOnlyNonDeferredImportPaths) {
return doesNotContainCode(inlineData);
}
// Do not inline code that is rarely executed unless it reduces size.
if (_inExpressionOfThrow || graph.isLazyInitializer) {
return reductiveHeuristic(inlineData);
}
if (cachedCanBeInlined == true) {
// We may have forced the inlining of some methods. Therefore check
// if we can inline this method regardless of size.
String? reason;
assert(
(reason = inlineData.cannotBeInlinedReason(allowLoops: true)) == null,
failedAt(function, "Cannot inline $function: $reason"),
);
return true;
}
int numParameters = function.parameterStructure.totalParameters;
int? maxInliningNodes;
if (insideLoop) {
maxInliningNodes =
InlineWeeder.inliningNodesInsideLoop +
InlineWeeder.inliningNodesInsideLoopArgFactor * numParameters;
} else {
maxInliningNodes =
InlineWeeder.inliningNodesOutsideLoop +
InlineWeeder.inliningNodesOutsideLoopArgFactor * numParameters;
}
bool markedTryInline = _inlineCache.markedAsTryInline(function);
bool calledOnce = _isCalledOnce(function);
// If a method is called only once, and all the methods in the inlining
// stack are called only once as well, we know we will save on output size
// by inlining this method.
if (markedTryInline || calledOnce) {
maxInliningNodes = null;
}
bool allowLoops = false;
if (markedTryInline) {
allowLoops = true;
}
bool canInline = inlineData.canBeInlined(
maxInliningNodes: maxInliningNodes,
allowLoops: allowLoops,
);
if (markedTryInline) {
if (canInline) {
_inlineCache.markAsInlinable(function, insideLoop: true);
_inlineCache.markAsInlinable(function, insideLoop: false);
} else {
_inlineCache.markAsNonInlinable(function, insideLoop: true);
_inlineCache.markAsNonInlinable(function, insideLoop: false);
}
} else if (calledOnce) {
// TODO(34203): We can't update the decision due to imprecision in the
// calledOnce data, described in Issue 34203.
} else {
if (canInline) {
_inlineCache.markAsInlinable(function, insideLoop: insideLoop);
} else {
if (_isFunctionCalledOnce(function)) {
// TODO(34203): We can't update the decision due to imprecision in
// the calledOnce data, described in Issue 34203.
} else {
_inlineCache.markAsNonInlinable(function, insideLoop: insideLoop);
}
}
}
return canInline;
}
void doInlining() {
if (function is ConstructorEntity) {
registry.registerStaticUse(
StaticUse.constructorInlining(function, instanceType),
);
} else {
assert(
instanceType == null,
"Unexpected instance type for $function: $instanceType",
);
registry.registerStaticUse(
StaticUse.methodInlining(function, typeArguments),
);
}
// Add an explicit null check on the receiver before doing the inlining.
if (function.isInstanceMember &&
function is! ConstructorBodyEntity &&
(mask == null ||
_abstractValueDomain.isNull(mask).isPotentiallyTrue)) {
HNullCheck guard =
HNullCheck(providedArguments[0], _abstractValueDomain.dynamicType)
..selector = selector
..sourceInformation = sourceInformation;
add(guard);
providedArguments[0] = guard;
}
List<HInstruction> compiledArguments = _completeCallArgumentsList(
function,
selector,
providedArguments,
currentNode,
);
_enterInlinedMethod(function, compiledArguments, instanceType);
_inlinedFrom(function, sourceInformation, () {
if (!_isReachable) {
_emitReturn(
graph.addConstantUnreachable(closedWorld),
sourceInformation,
);
} else {
_doInline(function);
}
});
_leaveInlinedMethod();
}
if (meetsHardConstraints() && heuristicSayGoodToGo()) {
doInlining();
_infoReporter?.reportInlined(
function,
_inliningStack.isEmpty ? targetElement : _inliningStack.last.function,
);
return true;
}
return false;
}
/// Returns a complete argument list for a call of [function].
List<HInstruction> _completeCallArgumentsList(
FunctionEntity function,
Selector? selector,
List<HInstruction> providedArguments,
ir.Node? currentNode,
) {
bool isInstanceMember = function.isInstanceMember;
// For static calls, [providedArguments] is complete, default arguments
// have been included if necessary, see [makeStaticArgumentList].
if (!isInstanceMember ||
currentNode == null || // In erroneous code, currentNode can be null.
_providedArgumentsKnownToBeComplete(currentNode) ||
function is ConstructorBodyEntity ||
selector!.isGetter) {
// For these cases, the provided argument list is known to be complete.
return providedArguments;
} else {
return _completeDynamicCallArgumentsList(
selector,
function,
providedArguments,
);
}
}
/// Returns a complete argument list for a dynamic call of [function]. The
/// initial argument list [providedArguments], created by
/// [addDynamicSendArgumentsToList], does not include values for default
/// arguments used in the call. The reason is that the target function (which
/// defines the defaults) is not known.
///
/// However, inlining can only be performed when the target function can be
/// resolved statically. The defaults can therefore be included at this point.
///
/// The [providedArguments] list contains first all positional arguments, then
/// the provided named arguments (the named arguments that are defined in the
/// [selector]) in a specific order (see [addDynamicSendArgumentsToList]).
List<HInstruction> _completeDynamicCallArgumentsList(
Selector selector,
FunctionEntity function,
List<HInstruction> providedArguments,
) {
assert(selector.applies(function));
CallStructure callStructure = selector.callStructure;
ParameterStructure parameterStructure = function.parameterStructure;
List<String> selectorArgumentNames = selector.callStructure
.getOrderedNamedArguments();
bool methodNeedsTypeArguments = _rtiNeed.methodNeedsTypeArguments(function);
List<HInstruction> compiledArguments = [];
// Copy receiver.
compiledArguments.add(providedArguments[0]);
/// Offset of positional arguments in [providedArguments].
int positionalArgumentOffset = 1;
/// Offset of named arguments in [providedArguments].
int namedArgumentOffset = callStructure.positionalArgumentCount + 1;
int positionalArgumentIndex = 0;
int namedArgumentIndex = 0;
_elementEnvironment.forEachParameter(function, (
DartType type,
String? name,
ConstantValue? defaultValue,
) {
if (positionalArgumentIndex < parameterStructure.positionalParameters) {
if (positionalArgumentIndex < callStructure.positionalArgumentCount) {
compiledArguments.add(
providedArguments[positionalArgumentOffset +
positionalArgumentIndex++],
);
} else {
assert(
defaultValue != null,
failedAt(function, 'No constant computed for parameter $name'),
);
compiledArguments.add(graph.addConstant(defaultValue!, closedWorld));
}
} else {
// Example:
// void foo(a, {b, d, c})
// foo(0, d = 1, b = 2)
//
// providedArguments = [0, 2, 1]
// selectorArgumentNames = [b, d]
// parameterStructure.namedParameters = [b, c, d]
//
// For each parameter name in the signature, if the argument name
// matches we use the next provided argument, otherwise we get the
// default.
if (namedArgumentIndex < selectorArgumentNames.length &&
name == selectorArgumentNames[namedArgumentIndex]) {
// The named argument was provided in the function invocation.
compiledArguments.add(
providedArguments[namedArgumentOffset + namedArgumentIndex++],
);
} else {
assert(
defaultValue != null,
failedAt(function, 'No constant computed for parameter $name'),
);
compiledArguments.add(graph.addConstant(defaultValue!, closedWorld));
}
}
});
if (methodNeedsTypeArguments) {
if (callStructure.typeArgumentCount ==
parameterStructure.typeParameters) {
/// Offset of type arguments in [providedArguments].
int typeArgumentOffset = callStructure.argumentCount + 1;
// Pass explicit type arguments.
for (
int typeArgumentIndex = 0;
typeArgumentIndex < callStructure.typeArgumentCount;
typeArgumentIndex++
) {
compiledArguments.add(
providedArguments[typeArgumentOffset + typeArgumentIndex],
);
}
} else {
assert(callStructure.typeArgumentCount == 0);
// Pass type variable bounds as type arguments.
for (TypeVariableType typeVariable
in _elementEnvironment.getFunctionTypeVariables(function)) {
compiledArguments.add(
_computeTypeArgumentDefaultValue(function, typeVariable),
);
}
}
}
return compiledArguments;
}
HInstruction _computeTypeArgumentDefaultValue(
FunctionEntity function,
TypeVariableType typeVariable,
) {
DartType bound = _elementEnvironment.getTypeVariableDefaultType(
typeVariable.element,
);
return _typeBuilder.analyzeTypeArgument(bound, function);
}
/// This method is invoked before inlining the body of [function] into this
/// [SsaGraphBuilder].
void _enterInlinedMethod(
FunctionEntity function,
List<HInstruction> compiledArguments,
InterfaceType? instanceType,
) {
KernelInliningState state = KernelInliningState(
function,
_returnLocal,
_returnType,
stack,
localsHandler,
_inTryStatement,
_isCalledOnce(function),
);
_inliningStack.add(state);
// Setting up the state of the (AST) builder is performed even when the
// inlined function is in IR, because the irInliner uses the [returnElement]
// of the AST builder.
_setupStateForInlining(function, compiledArguments, instanceType);
}
/// This method sets up the local state of the builder for inlining
/// [function]. The arguments of the function are inserted into the
/// [localsHandler].
///
/// When inlining a function, `return` statements are not emitted as
/// [HReturn] instructions. Instead, the value of a synthetic element is
/// updated in the [localsHandler]. This function creates such an element and
/// stores it in the [_returnLocal] field.
void _setupStateForInlining(
FunctionEntity function,
List<HInstruction> compiledArguments,
InterfaceType? instanceType,
) {
localsHandler = LocalsHandler(
this,
function,
function,
instanceType ?? _elementMap.getMemberThisType(function),
_nativeData,
_interceptorData,
);
localsHandler.setupScope(function);
CapturedScope scopeData = _closureDataLookup.getCapturedScope(function);
bool forGenerativeConstructorBody = function is ConstructorBodyEntity;
_returnLocal = SyntheticLocal("result", function, function);
localsHandler.updateLocal(
_returnLocal!,
graph.addConstantNull(closedWorld),
);
_inTryStatement = false; // TODO(lry): why? Document.
int argumentIndex = 0;
if (function.isInstanceMember) {
localsHandler.updateLocal(
localsHandler.thisLocal!,
compiledArguments[argumentIndex++],
);
}
KernelToLocalsMap localsMap = _globalLocalsMap.getLocalsMap(function);
forEachOrderedParameter(_elementMap, function, (
ir.VariableDeclaration variable, {
required bool isElided,
}) {
Local local = localsMap.getLocalVariable(variable);
if (isElided) {
localsHandler.updateLocal(local, _defaultValueForParameter(variable));
return;
}
if (forGenerativeConstructorBody &&
scopeData.isBoxedVariable(_localsMap, local)) {
// The parameter will be a field in the box passed as the last
// parameter. So no need to have it.
return;
}
HInstruction argument = compiledArguments[argumentIndex++];
localsHandler.updateLocal(local, argument);
});
bool hasTypeParameters = function.parameterStructure.typeParameters > 0;
bool needsTypeArguments = _rtiNeed.methodNeedsTypeArguments(function);
for (TypeVariableType typeVariable
in _elementEnvironment.getFunctionTypeVariables(function)) {
HInstruction argument;
if (hasTypeParameters && needsTypeArguments) {
argument = compiledArguments[argumentIndex++];
} else {
argument = _computeTypeArgumentDefaultValue(function, typeVariable);
}
localsHandler.updateLocal(
localsHandler.getTypeVariableAsLocal(typeVariable),
argument,
);
}
if (forGenerativeConstructorBody && scopeData.requiresContextBox) {
HInstruction box = compiledArguments[argumentIndex++];
assert(box is HCreateBox);
// TODO(sra): Make inlining of closures work. We should always call
// enterScope, and pass in the inlined 'this' as well as the 'box'.
localsHandler.enterScope(
scopeData,
null,
inlinedBox: box,
forGenerativeConstructorBody: forGenerativeConstructorBody,
);
}
final enclosing = function.enclosingClass;
if ((function is ConstructorEntity || function is ConstructorBodyEntity) &&
_rtiNeed.classNeedsTypeArguments(enclosing!)) {
InterfaceType thisType = _elementEnvironment.getThisType(enclosing);
for (var (typeVariable as TypeVariableType) in thisType.typeArguments) {
HInstruction argument = compiledArguments[argumentIndex++];
localsHandler.updateLocal(
localsHandler.getTypeVariableAsLocal(typeVariable),
argument,
);
}
}
assert(
argumentIndex == compiledArguments.length ||
!_rtiNeed.methodNeedsTypeArguments(function) &&
compiledArguments.length - argumentIndex ==
function.parameterStructure.typeParameters,
failedAt(
function,
"Only $argumentIndex of ${compiledArguments.length} "
"arguments have been read from: $compiledArguments passed to "
"$function.",
),
);
_returnType = _elementEnvironment.getFunctionType(function).returnType;
stack = <HInstruction>[];
_insertCoverageCall(function);
}
void _leaveInlinedMethod() {
HInstruction result = localsHandler.readLocal(_returnLocal!);
KernelInliningState state = _inliningStack.removeLast();
_restoreState(state);
stack.add(result);
}
void _restoreState(KernelInliningState state) {
localsHandler = state.oldLocalsHandler;
_returnLocal = state.oldReturnLocal;
_inTryStatement = state.inTryStatement;
_returnType = state.oldReturnType;
assert(stack.isEmpty);
stack = state.oldStack;
}
bool _providedArgumentsKnownToBeComplete(ir.Node currentNode) {
/* When inlining the iterator methods generated for a for-in loop, the
* [currentNode] is the [ForIn] tree. The compiler-generated iterator
* invocations are known to have fully specified argument lists, no default
* arguments are used. See invocations of [pushInvokeDynamic] in
* [visitForIn].
*/
// TODO(redemption): Is this valid here?
return currentNode is ir.ForInStatement;
}
void _emitReturn(HInstruction? value, SourceInformation? sourceInformation) {
if (_inliningStack.isEmpty) {
_closeAndGotoExit(HReturn(value, sourceInformation));
} else {
value ??= graph.addConstantNull(closedWorld);
localsHandler.updateLocal(_returnLocal!, value);
}
}
void _doInline(FunctionEntity function) {
_visitInlinedFunction(function);
}
/// Run this builder on the body of the [function] to be inlined.
void _visitInlinedFunction(FunctionEntity function) {
_potentiallyCheckInlinedParameterTypes(function);
MemberDefinition definition = _elementMap.getMemberDefinition(function);
switch (definition.kind) {
case MemberKind.constructor:
_buildConstructor(
function as ConstructorEntity,
definition.node as ir.Constructor,
);
return;
case MemberKind.constructorBody:
final constructor = definition.node as ir.Constructor;
constructor.function.body!.accept(this);
return;
case MemberKind.regular:
ir.Node node = definition.node;
if (node is ir.Constructor) {
node.function.body!.accept(this);
return;
} else if (node is ir.Procedure) {
node.function.body!.accept(this);
return;
}
break;
case MemberKind.closureCall:
final node = definition.node as ir.LocalFunction;
node.function.body!.accept(this);
return;
case MemberKind.closureField:
case MemberKind.generatorBody:
case MemberKind.recordGetter:
case MemberKind.signature:
case MemberKind.parameterStub:
}
failedAt(function, "Unexpected inlined function: $definition");
}
/// Generates type tests for the parameters of the inlined function.
void _potentiallyCheckInlinedParameterTypes(FunctionEntity function) {
// TODO(sra): Incorporate properties of call site to help determine which
// type parameters and value parameters need to be checked.
bool trusted = false;
if (function.isStatic ||
function.isTopLevel ||
function is ConstructorEntity ||
function is ConstructorBodyEntity) {
// We inline static methods, top-level methods, constructors and
// constructor bodies only from direct call sites.
trusted = true;
}
if (!trusted) {
_checkTypeVariableBounds(function);
}
KernelToLocalsMap localsMap = _globalLocalsMap.getLocalsMap(function);
forEachOrderedParameter(_elementMap, function, (
ir.VariableDeclaration variable, {
required bool isElided,
}) {
Local parameter = localsMap.getLocalVariable(variable);
HInstruction argument = localsHandler.readLocal(parameter);
DartType type = localsMap.getLocalType(_elementMap, parameter);
HInstruction checkedOrTrusted;
if (trusted) {
checkedOrTrusted = _typeBuilder.trustTypeOfParameter(
function,
argument,
type,
);
} else {
checkedOrTrusted = _typeBuilder.potentiallyCheckOrTrustTypeOfParameter(
function,
argument,
type,
);
}
localsHandler.updateLocal(parameter, checkedOrTrusted);
});
}
bool get _allInlinedFunctionsCalledOnce {
return _inliningStack.isEmpty || _inliningStack.last.allFunctionsCalledOnce;
}
bool _isFunctionCalledOnce(FunctionEntity element) {
// ConstructorBodyElements are not in the type inference graph.
if (element is ConstructorBodyEntity) {
// If there are no subclasses with constructors that have this constructor
// as a superconstructor, it is called once by the generative
// constructor's factory. A simplified version is to check this is a
// constructor body for a leaf class.
ClassEntity class_ = element.enclosingClass!;
if (closedWorld.classHierarchy.isDirectlyInstantiated(class_)) {
return !closedWorld.classHierarchy.isIndirectlyInstantiated(class_);
}
return false;
}
return globalInferenceResults.resultOfMember(element).isCalledOnce;
}
bool _isCalledOnce(FunctionEntity element) {
return _allInlinedFunctionsCalledOnce && _isFunctionCalledOnce(element);
}
void _insertCoverageCall(MemberEntity element) {
if (!options.experimentCallInstrumentation) return;
if (element == _commonElements.traceHelper) return;
// TODO(sigmund): create a better uuid for elements.
HConstant idConstant = graph.addConstantInt(element.hashCode, closedWorld);
String? n(Entity? e) => e == null ? '' : e.name;
String name =
"${n(element.library)}:${n(element.enclosingClass)}."
"${n(element)}";
HConstant nameConstant = graph.addConstantString(name, closedWorld);
add(
HInvokeStatic(
_commonElements.traceHelper,
[idConstant, nameConstant],
_abstractValueDomain.dynamicType,
const <DartType>[],
),
);
}
}
/// Data collected to create a constructor.
class ConstructorData {
/// Inlined (super) constructors.
final List<ir.Constructor> constructorChain = [];
/// Initial values for all instance fields.
final Map<FieldEntity, HInstruction> fieldValues = {};
/// Classes for which type variables have been prepared.
final Set<ClassEntity> includedClasses = {};
}
class KernelInliningState {
final FunctionEntity function;
final Local? oldReturnLocal;
final DartType? oldReturnType;
final List<HInstruction> oldStack;
final LocalsHandler oldLocalsHandler;
final bool inTryStatement;
final bool allFunctionsCalledOnce;
KernelInliningState(
this.function,
this.oldReturnLocal,
this.oldReturnType,
this.oldStack,
this.oldLocalsHandler,
this.inTryStatement,
this.allFunctionsCalledOnce,
);
@override
String toString() =>
'KernelInliningState($function,'
'allFunctionsCalledOnce=$allFunctionsCalledOnce)';
}
/// Class in charge of building try, catch and/or finally blocks. This handles
/// the instructions that need to be output and the dominator calculation of
/// this sequence of code.
class TryCatchFinallyBuilder {
final KernelSsaGraphBuilder kernelBuilder;
final SourceInformation? trySourceInformation;
late final HBasicBlock enterBlock;
late final HBasicBlock startTryBlock;
HBasicBlock? endTryBlock;
HBasicBlock? startCatchBlock;
HBasicBlock? endCatchBlock;
HBasicBlock? startFinallyBlock;
HBasicBlock? endFinallyBlock;
HBasicBlock? exitBlock;
late final HTry tryInstruction;
HLocalValue? exception;
/// True if the code surrounding this try statement was also part of a
/// try/catch/finally statement.
late final bool previouslyInTryStatement;
SubGraph? bodyGraph;
SubGraph? catchGraph;
SubGraph? finallyGraph;
// The original set of locals that were defined before this try block.
// The catch block and the finally block must not reuse the existing locals
// handler. None of the variables that have been defined in the body-block
// will be used, but for loops we will add (unnecessary) phis that will
// reference the body variables. This makes it look as if the variables were
// used in a non-dominated block.
LocalsHandler? originalSavedLocals;
TryCatchFinallyBuilder(this.kernelBuilder, this.trySourceInformation) {
tryInstruction = HTry();
originalSavedLocals = LocalsHandler.from(kernelBuilder.localsHandler);
enterBlock = kernelBuilder.openNewBlock();
kernelBuilder.close(tryInstruction);
previouslyInTryStatement = kernelBuilder._inTryStatement;
kernelBuilder._inTryStatement = true;
startTryBlock = kernelBuilder.graph.addNewBlock();
kernelBuilder.open(startTryBlock);
}
void _addExitTrySuccessor(HBasicBlock? successor) {
if (successor == null) return;
// Iterate over all blocks created inside this try/catch, and
// attach successor information to blocks that end with
// [HExitTry].
for (int i = startTryBlock.id; i < successor.id; i++) {
HBasicBlock block = kernelBuilder.graph.blocks[i];
var last = block.last;
if (last is HExitTry) {
block.addSuccessor(successor);
}
}
}
void _addOptionalSuccessor(HBasicBlock block1, HBasicBlock? block2) {
if (block2 != null) block1.addSuccessor(block2);
}
/// Helper function to set up basic block successors for try-catch-finally
/// sequences.
void _setBlockSuccessors() {
// Setup all successors. The entry block that contains the [HTry]
// has 1) the body, 2) the catch, 3) the finally, and 4) the exit
// blocks as successors.
final exit = exitBlock!;
enterBlock.addSuccessor(startTryBlock);
_addOptionalSuccessor(enterBlock, startCatchBlock);
_addOptionalSuccessor(enterBlock, startFinallyBlock);
enterBlock.addSuccessor(exit);
// The body has either the catch or the finally block as successor.
final endTry = endTryBlock;
if (endTry != null) {
assert(startCatchBlock != null || startFinallyBlock != null);
endTry.addSuccessor(startCatchBlock ?? startFinallyBlock!);
endTry.addSuccessor(exit);
}
// The catch block has either the finally or the exit block as
// successor.
endCatchBlock?.addSuccessor(startFinallyBlock ?? exit);
// The finally block has the exit block as successor.
endFinallyBlock?.addSuccessor(exit);
// If a block inside try/catch aborts (eg with a return statement),
// we explicitly mark this block a predecessor of the catch
// block and the finally block.
_addExitTrySuccessor(startCatchBlock);
_addExitTrySuccessor(startFinallyBlock);
}
/// Build the finally{} clause of a try/{catch}/finally statement. Note this
/// does not examine the body of the try clause, only the finally portion.
void buildFinallyBlock(void Function() buildFinalizer) {
kernelBuilder.localsHandler = LocalsHandler.from(originalSavedLocals!);
startFinallyBlock = kernelBuilder.graph.addNewBlock();
kernelBuilder.open(startFinallyBlock!);
buildFinalizer();
if (!kernelBuilder.isAborted()) {
endFinallyBlock = kernelBuilder.close(HGoto());
}
tryInstruction.finallyBlock = startFinallyBlock;
finallyGraph = SubGraph(startFinallyBlock!, kernelBuilder.lastOpenedBlock);
}
void closeTryBody() {
// We use a [HExitTry] instead of a [HGoto] for the try block
// because it will have multiple successors: the join block, and
// the catch or finally block.
if (!kernelBuilder.isAborted()) {
endTryBlock = kernelBuilder.close(HExitTry());
}
bodyGraph = SubGraph(startTryBlock, kernelBuilder.lastOpenedBlock);
}
void buildCatch(ir.TryCatch tryCatch) {
kernelBuilder.localsHandler = LocalsHandler.from(originalSavedLocals!);
startCatchBlock = kernelBuilder.graph.addNewBlock();
kernelBuilder.open(startCatchBlock!);
// Note that the name of this local is irrelevant.
SyntheticLocal local = kernelBuilder.localsHandler.createLocal('exception');
exception = HLocalValue(
local,
kernelBuilder._abstractValueDomain.nonNullType,
)..sourceInformation = trySourceInformation;
kernelBuilder.add(exception!);
final oldRethrowableException = kernelBuilder._rethrowableException;
kernelBuilder._rethrowableException = exception;
AbstractValue unwrappedType = kernelBuilder._typeInferenceMap
.getReturnTypeOf(kernelBuilder._commonElements.exceptionUnwrapper);
// Global type analysis does not currently understand that strong mode
// `Object` is not nullable, so is imprecise in the return type of the
// unwrapper, which leads to unnecessary checks for 'on Object'.
unwrappedType = kernelBuilder._abstractValueDomain.excludeNull(
unwrappedType,
);
kernelBuilder._pushStaticInvocation(
kernelBuilder._commonElements.exceptionUnwrapper,
[exception!],
unwrappedType,
const <DartType>[],
sourceInformation: trySourceInformation,
);
final unwrappedException = kernelBuilder.pop() as HInvokeStatic;
unwrappedException.sideEffects
..clearAllDependencies()
..clearAllSideEffects();
unwrappedException.targetCanThrow = false;
tryInstruction.exception = exception;
int catchesIndex = 0;
void pushCondition(ir.Catch catchBlock) {
kernelBuilder._pushIsTest(
catchBlock.guard,
unwrappedException,
kernelBuilder._sourceInformationBuilder.buildCatch(catchBlock),
);
}
void visitThen() {
ir.Catch catchBlock = tryCatch.catches[catchesIndex];
catchesIndex++;
if (catchBlock.exception != null) {
Local exceptionVariable = kernelBuilder._localsMap.getLocalVariable(
catchBlock.exception!,
);
kernelBuilder.localsHandler.updateLocal(
exceptionVariable,
unwrappedException,
sourceInformation: kernelBuilder._sourceInformationBuilder.buildCatch(
catchBlock,
),
);
}
if (catchBlock.stackTrace != null) {
kernelBuilder._pushStaticInvocation(
kernelBuilder._commonElements.traceFromException,
[exception!],
kernelBuilder._typeInferenceMap.getReturnTypeOf(
kernelBuilder._commonElements.traceFromException,
),
const <DartType>[],
sourceInformation: kernelBuilder._sourceInformationBuilder.buildCatch(
catchBlock,
),
);
HInstruction traceInstruction = kernelBuilder.pop();
Local traceVariable = kernelBuilder._localsMap.getLocalVariable(
catchBlock.stackTrace!,
);
kernelBuilder.localsHandler.updateLocal(
traceVariable,
traceInstruction,
sourceInformation: kernelBuilder._sourceInformationBuilder.buildCatch(
catchBlock,
),
);
}
catchBlock.body.accept(kernelBuilder);
}
void visitElse() {
if (catchesIndex >= tryCatch.catches.length) {
kernelBuilder._closeAndGotoExit(
HThrow(exception!, exception!.sourceInformation, isRethrow: true),
);
} else {
ir.Catch nextCatch = tryCatch.catches[catchesIndex];
kernelBuilder._handleIf(
visitCondition: () {
pushCondition(nextCatch);
},
visitThen: visitThen,
visitElse: visitElse,
sourceInformation: kernelBuilder._sourceInformationBuilder.buildCatch(
nextCatch,
),
);
}
}
ir.Catch firstBlock = tryCatch.catches[catchesIndex];
kernelBuilder._handleIf(
visitCondition: () {
pushCondition(firstBlock);
},
visitThen: visitThen,
visitElse: visitElse,
sourceInformation: kernelBuilder._sourceInformationBuilder.buildCatch(
firstBlock,
),
);
if (!kernelBuilder.isAborted()) {
endCatchBlock = kernelBuilder.close(HGoto());
}
kernelBuilder._rethrowableException = oldRethrowableException;
tryInstruction.catchBlock = startCatchBlock;
catchGraph = SubGraph(startCatchBlock!, kernelBuilder.lastOpenedBlock);
}
void cleanUp() {
exitBlock = kernelBuilder.graph.addNewBlock();
_setBlockSuccessors();
// Use the locals handler not altered by the catch and finally
// blocks.
kernelBuilder.localsHandler = originalSavedLocals!;
kernelBuilder.open(exitBlock!);
enterBlock.setBlockFlow(
HTryBlockInformation(
kernelBuilder.wrapStatementGraph(bodyGraph),
exception,
kernelBuilder.wrapStatementGraph(catchGraph),
kernelBuilder.wrapStatementGraph(finallyGraph),
),
exitBlock,
);
kernelBuilder._inTryStatement = previouslyInTryStatement;
}
}
class KernelTypeBuilder extends TypeBuilder {
final JsToElementMap _elementMap;
KernelTypeBuilder(super.builder, this._elementMap);
@override
ClassTypeVariableAccess computeTypeVariableAccess(MemberEntity member) {
return _elementMap.getClassTypeVariableAccessForMember(member);
}
}
class _ErroneousInitializerVisitor extends ir.VisitorDefault<bool>
with ir.VisitorDefaultValueMixin<bool> {
_ErroneousInitializerVisitor();
// TODO(30809): Use const constructor.
static bool check(ir.Initializer initializer) =>
initializer.accept(_ErroneousInitializerVisitor());
@override
bool defaultInitializer(ir.Node node) => false;
@override
bool visitInvalidInitializer(ir.InvalidInitializer node) => true;
@override
bool visitLocalInitializer(ir.LocalInitializer node) {
return node.variable.initializer?.accept(this) ?? false;
}
// Expressions: Does the expression always throw?
@override
bool get defaultValue => false;
@override
bool visitThrow(ir.Throw node) => true;
// TODO(sra): We might need to match other expressions that always throw but
// in a subexpression.
}
/// Special [JumpHandler] implementation used to handle continue statements
/// targeting switch cases.
class KernelSwitchCaseJumpHandler extends SwitchCaseJumpHandler {
KernelSwitchCaseJumpHandler(
KernelSsaGraphBuilder builder,
JumpTarget target,
ir.SwitchStatement switchStatement,
KernelToLocalsMap localsMap,
) : super(builder, target) {
// The switch case indices must match those computed in
// [KernelSsaBuilder.buildSwitchCaseConstants].
// Switch indices are 1-based so we can bypass the synthetic loop when no
// cases match simply by branching on the index (which defaults to null).
// TODO
int switchIndex = 1;
for (ir.SwitchCase switchCase in switchStatement.cases) {
final continueTarget = localsMap.getJumpTargetForSwitchCase(switchCase);
if (continueTarget != null) {
targetIndexMap[continueTarget] = switchIndex;
assert(builder.jumpTargets[continueTarget] == null);
builder.jumpTargets[continueTarget] = this;
}
switchIndex++;
}
}
}
class InlineData {
bool isConstructor = false;
bool codeAfterReturn = false;
bool hasLoop = false;
bool hasClosure = false;
bool hasTry = false;
bool hasAsyncAwait = false;
bool hasThrow = false;
bool hasLongString = false;
bool hasExternalConstantConstructorCall = false;
bool hasTypeArguments = false;
bool hasArgumentDefaulting = false;
bool hasCast = false;
bool hasIf = false;
bool hasLabel = false; // TODO(51652): Remove when inlining works with labels.
List<int> argumentCounts = [];
int regularNodeCount = 0;
int callCount = 0;
int reductiveNodeCount = 0;
InlineData();
bool canBeInlined({int? maxInliningNodes, bool allowLoops = false}) {
return cannotBeInlinedReason(
maxInliningNodes: maxInliningNodes,
allowLoops: allowLoops,
) ==
null;
}
String? cannotBeInlinedReason({
int? maxInliningNodes,
bool allowLoops = false,
}) {
if (hasLoop && !allowLoops) {
return 'loop';
} else if (hasTry) {
return 'try';
} else if (hasClosure) {
return 'closure';
} else if (codeAfterReturn) {
return 'code after return';
} else if (hasAsyncAwait) {
return 'async/await';
} else if (maxInliningNodes != null &&
regularNodeCount - 1 > maxInliningNodes) {
return 'too many nodes (${regularNodeCount - 1}>$maxInliningNodes)';
}
if (hasLabel) return 'has label';
return null;
}
bool canBeInlinedReductive({required int argumentCount}) {
return cannotBeInlinedReductiveReason(argumentCount: argumentCount) == null;
}
String? cannotBeInlinedReductiveReason({required int argumentCount}) {
if (hasTry) {
return 'try';
} else if (hasClosure) {
return 'closure';
} else if (codeAfterReturn) {
return 'code after return';
} else if (hasAsyncAwait) {
return 'async/await';
} else if (callCount > 1) {
return 'too many calls';
} else if (hasThrow) {
return 'throw';
} else if (hasLongString) {
return 'long string';
} else if (hasExternalConstantConstructorCall) {
return 'external const constructor';
} else if (hasTypeArguments) {
return 'type arguments';
} else if (hasArgumentDefaulting) {
return 'argument defaulting';
} else if (hasCast) {
return 'cast';
} else if (hasIf) {
return 'if';
} else if (isConstructor) {
return 'constructor';
}
if (hasLabel) return 'has label';
for (int count in argumentCounts) {
if (count > argumentCount) {
return 'increasing arguments';
}
}
// Node budget that covers one call and the passed-in arguments.
// The +1 also allows a top-level zero-argument to be inlined if it
// returns a constant.
int maxInliningNodes = argumentCount + 1;
if (reductiveNodeCount > maxInliningNodes) {
return 'too many nodes ($reductiveNodeCount>$maxInliningNodes)';
}
return null;
}
@override
String toString() {
StringBuffer sb = StringBuffer();
sb.write('InlineData(');
String comma = '';
if (isConstructor) {
sb.write('isConstructor');
comma = ',';
}
if (codeAfterReturn) {
sb.write(comma);
sb.write('codeAfterReturn');
comma = ',';
}
if (hasLoop) {
sb.write(comma);
sb.write('hasLoop');
comma = ',';
}
if (hasClosure) {
sb.write(comma);
sb.write('hasClosure');
comma = ',';
}
if (hasTry) {
sb.write(comma);
sb.write('hasTry');
comma = ',';
}
if (hasAsyncAwait) {
sb.write(comma);
sb.write('hasAsyncAwait');
comma = ',';
}
if (hasThrow) {
sb.write(comma);
sb.write('hasThrow');
comma = ',';
}
if (hasLongString) {
sb.write(comma);
sb.write('hasLongString');
comma = ',';
}
if (hasExternalConstantConstructorCall) {
sb.write(comma);
sb.write('hasExternalConstantConstructorCall');
comma = ',';
}
if (hasTypeArguments) {
sb.write(comma);
sb.write('hasTypeArguments');
comma = ',';
}
if (hasArgumentDefaulting) {
sb.write(comma);
sb.write('hasArgumentDefaulting');
comma = ',';
}
if (hasCast) {
sb.write(comma);
sb.write('hasCast');
comma = ',';
}
if (hasIf) {
sb.write(comma);
sb.write('hasIf');
comma = ',';
}
if (hasLabel) {
sb.write(comma);
sb.write('hasLabel');
comma = ',';
}
if (argumentCounts.isNotEmpty) {
sb.write(comma);
sb.write('argumentCounts={${argumentCounts.join(',')}}');
comma = ',';
}
sb.write(comma);
sb.write('regularNodeCount=$regularNodeCount,');
sb.write('callCount=$callCount,');
sb.write('reductiveNodeCount=$reductiveNodeCount');
sb.write(')');
return sb.toString();
}
}
class InlineDataCache {
final bool enableUserAssertions;
final bool omitImplicitCasts;
InlineDataCache({
this.enableUserAssertions = false,
this.omitImplicitCasts = false,
});
final Map<FunctionEntity, InlineData> _cache = {};
InlineData getInlineData(JsToElementMap elementMap, FunctionEntity function) {
return _cache[function] ??= InlineWeeder.computeInlineData(
elementMap,
function,
enableUserAssertions: enableUserAssertions,
omitImplicitCasts: omitImplicitCasts,
);
}
}
class InlineWeeder extends ir.VisitorDefault<void> with ir.VisitorVoidMixin {
// Invariant: *INSIDE_LOOP* > *OUTSIDE_LOOP*
static const inliningNodesOutsideLoop = 15;
static const inliningNodesOutsideLoopArgFactor = 3;
static const inliningNodesInsideLoop = 34;
static const inliningNodesInsideLoopArgFactor = 4;
final bool enableUserAssertions;
final bool omitImplicitCasts;
final InlineData data = InlineData();
bool seenReturn = false;
/// Whether node-count is collector to determine if a function can be
/// inlined.
bool countRegularNode = true;
/// Whether node-count is collected to determine if inlining a function is
/// very likely to reduce code size.
///
/// For the reductive analysis:
/// We allow the body to be a single function call that does not have any more
/// inputs than the inlinee.
///
/// We allow the body to be the return of an 'eligible' constant. A constant
/// is 'eligible' if it is not large (e.g. a long string).
///
/// We skip 'e as{TypeError} T' when the checks are omitted.
//
// TODO(sra): Consider slightly expansive simple constructors where all we
// gain is a 'new' keyword, e.g. `new X.Foo(a)` vs `X.Foo$(a)`.
//
// TODO(25231): Make larger string constants eligible by sharing references.
bool countReductiveNode = true;
// When handling a generative constructor factory, the super constructor calls
// are 'inlined', so tend to reuse the same parameters. [discountParameters]
// is true to avoid double-counting these parameters.
bool discountParameters = false;
InlineWeeder({
this.enableUserAssertions = false,
this.omitImplicitCasts = false,
});
static InlineData computeInlineData(
JsToElementMap elementMap,
FunctionEntity function, {
bool enableUserAssertions = false,
bool omitImplicitCasts = false,
}) {
InlineWeeder visitor = InlineWeeder(
enableUserAssertions: enableUserAssertions,
omitImplicitCasts: omitImplicitCasts,
);
final node = getFunctionNode(elementMap, function)!;
if (function is ConstructorEntity) {
visitor.data.isConstructor = true;
MemberDefinition definition = elementMap.getMemberDefinition(function);
ir.Node node = definition.node;
if (node is ir.Constructor) {
visitor.skipReductiveNodes(() {
visitor.handleGenerativeConstructorFactory(node);
});
return visitor.data;
}
}
node.accept(visitor);
return visitor.data;
}
void skipRegularNodes(void Function() f) {
bool oldCountRegularNode = countRegularNode;
countRegularNode = false;
f();
countRegularNode = oldCountRegularNode;
}
void skipReductiveNodes(void Function() f) {
bool oldCountReductiveNode = countReductiveNode;
countReductiveNode = false;
f();
countReductiveNode = oldCountReductiveNode;
}
void registerRegularNode([int count = 1]) {
if (countRegularNode) {
data.regularNodeCount += count;
if (seenReturn) {
data.codeAfterReturn = true;
}
}
}
void registerReductiveNode() {
if (countReductiveNode) {
data.reductiveNodeCount++;
if (seenReturn) {
data.codeAfterReturn = true;
}
}
}
void unregisterReductiveNode() {
if (countReductiveNode) {
data.reductiveNodeCount--;
}
}
void visit(ir.Node? node) => node?.accept(this);
void visitList(List<ir.Node> nodes) {
for (ir.Node node in nodes) {
visit(node);
}
}
@override
void defaultNode(ir.Node node) {
registerRegularNode();
registerReductiveNode();
node.visitChildren(this);
}
@override
void visitConstantExpression(ir.ConstantExpression node) {
registerRegularNode();
registerReductiveNode();
ir.Constant constant = node.constant;
// Avoid copying long strings into call site.
if (constant is ir.StringConstant && isLongString(constant.value)) {
data.hasLongString = true;
}
}
@override
void visitReturnStatement(ir.ReturnStatement node) {
registerRegularNode();
node.visitChildren(this);
seenReturn = true;
}
@override
void visitThrow(ir.Throw node) {
registerRegularNode();
data.hasThrow = true;
node.visitChildren(this);
}
void _handleLoop(ir.Node node) {
// It's actually not difficult to inline a method with a loop, but our
// measurements show that it's currently better to not inline a method that
// contains a loop.
data.hasLoop = true;
node.visitChildren(this);
}
@override
void visitForStatement(ir.ForStatement node) {
_handleLoop(node);
}
@override
void visitForInStatement(ir.ForInStatement node) {
_handleLoop(node);
}
@override
void visitWhileStatement(ir.WhileStatement node) {
_handleLoop(node);
}
@override
void visitDoStatement(ir.DoStatement node) {
_handleLoop(node);
}
@override
void visitTryCatch(ir.TryCatch node) {
data.hasTry = true;
}
@override
void visitTryFinally(ir.TryFinally node) {
data.hasTry = true;
}
@override
void visitFunctionExpression(ir.FunctionExpression node) {
registerRegularNode();
data.hasClosure = true;
}
@override
void visitFunctionDeclaration(ir.FunctionDeclaration node) {
registerRegularNode();
data.hasClosure = true;
}
@override
void visitFunctionNode(ir.FunctionNode node) {
if (node.asyncMarker != ir.AsyncMarker.Sync) {
data.hasAsyncAwait = true;
}
// TODO(sra): Cost of parameter checking?
skipReductiveNodes(() {
visitList(node.typeParameters);
visitList(node.positionalParameters);
visitList(node.namedParameters);
visit(node.returnType);
});
visit(node.body);
}
@override
void visitConditionalExpression(ir.ConditionalExpression node) {
// Heuristic: In "parameter ? A : B" there is a high probability that
// parameter is a constant. Assuming the parameter is constant, we can
// compute a count that is bounded by the largest arm rather than the sum of
// both arms.
ir.Expression condition = node.condition;
visit(condition);
int commonPrefixCount = data.regularNodeCount;
visit(node.then);
int thenCount = data.regularNodeCount - commonPrefixCount;
data.regularNodeCount = commonPrefixCount;
visit(node.otherwise);
int elseCount = data.regularNodeCount - commonPrefixCount;
data.regularNodeCount = commonPrefixCount + thenCount + elseCount;
if (condition is ir.VariableGet &&
condition.variable.parent is ir.FunctionNode) {
data.regularNodeCount =
commonPrefixCount + (thenCount > elseCount ? thenCount : elseCount);
}
// This is last so that [tooDifficult] is always updated.
registerRegularNode();
registerReductiveNode();
skipRegularNodes(() => visit(node.staticType));
}
@override
void visitAssertInitializer(ir.AssertInitializer node) {
if (!enableUserAssertions) return;
node.visitChildren(this);
}
@override
void visitAssertStatement(ir.AssertStatement node) {
if (!enableUserAssertions) return;
defaultNode(node);
}
void registerCall() {
++data.callCount;
}
@override
void visitEmptyStatement(ir.EmptyStatement node) {
registerRegularNode();
}
@override
void visitExpressionStatement(ir.ExpressionStatement node) {
registerRegularNode();
node.visitChildren(this);
}
@override
void visitLabeledStatement(ir.LabeledStatement node) {
registerRegularNode();
data.hasLabel = true;
node.visitChildren(this);
}
@override
void visitSwitchStatement(ir.SwitchStatement node) {
registerRegularNode();
registerReductiveNode();
// Don't visit 'SwitchStatement.expressionType'.
node.expression.accept(this);
visitList(node.cases);
}
@override
void visitBlock(ir.Block node) {
registerRegularNode();
node.visitChildren(this);
}
/// Returns `true` if [value] is considered a long string for which copying
/// should be avoided.
bool isLongString(String value) => value.length > 14;
@override
void visitStringLiteral(ir.StringLiteral node) {
registerRegularNode();
registerReductiveNode();
// Avoid copying long strings into call site.
if (isLongString(node.value)) {
data.hasLongString = true;
}
}
@override
void visitInstanceGet(ir.InstanceGet node) {
registerCall();
registerRegularNode();
registerReductiveNode();
skipReductiveNodes(() => visit(node.name));
visit(node.receiver);
}
@override
void visitInstanceTearOff(ir.InstanceTearOff node) {
registerCall();
registerRegularNode();
registerReductiveNode();
skipReductiveNodes(() => visit(node.name));
visit(node.receiver);
}
@override
void visitDynamicGet(ir.DynamicGet node) {
registerCall();
registerRegularNode();
registerReductiveNode();
skipReductiveNodes(() => visit(node.name));
visit(node.receiver);
}
@override
void visitInstanceSet(ir.InstanceSet node) {
registerCall();
registerRegularNode();
registerReductiveNode();
skipReductiveNodes(() => visit(node.name));
visit(node.receiver);
visit(node.value);
}
@override
void visitDynamicSet(ir.DynamicSet node) {
registerCall();
registerRegularNode();
registerReductiveNode();
skipReductiveNodes(() => visit(node.name));
visit(node.receiver);
visit(node.value);
}
@override
void visitVariableGet(ir.VariableGet node) {
if (discountParameters && node.variable.parent is ir.FunctionNode) return;
registerRegularNode();
registerReductiveNode();
skipReductiveNodes(() => visit(node.promotedType));
}
@override
void visitThisExpression(ir.ThisExpression node) {
registerRegularNode();
registerReductiveNode();
}
@override
void visitStaticGet(ir.StaticGet node) {
// Assume lazy-init static, loaded via a call: `$.$get$foo()`.
registerCall();
registerRegularNode();
registerReductiveNode();
}
@override
void visitConstructorInvocation(ir.ConstructorInvocation node) {
registerRegularNode();
registerReductiveNode();
if (node.isConst) {
// A const constructor call compiles to a constant pool reference.
skipReductiveNodes(() => node.visitChildren(this));
} else {
registerCall();
_processArguments(node.arguments, node.target.function);
}
}
@override
void visitStaticInvocation(ir.StaticInvocation node) {
registerRegularNode();
if (node.isConst) {
data.hasExternalConstantConstructorCall = true;
skipReductiveNodes(() => node.visitChildren(this));
} else {
registerCall();
registerReductiveNode();
_processArguments(node.arguments, node.target.function);
}
}
@override
void visitInstanceInvocation(ir.InstanceInvocation node) {
registerRegularNode();
registerReductiveNode();
registerCall();
visit(node.receiver);
skipReductiveNodes(() => visit(node.name));
_processArguments(node.arguments, null);
}
@override
void visitInstanceGetterInvocation(ir.InstanceGetterInvocation node) {
registerRegularNode();
registerReductiveNode();
registerCall();
visit(node.receiver);
skipReductiveNodes(() => visit(node.name));
_processArguments(node.arguments, null);
}
@override
void visitDynamicInvocation(ir.DynamicInvocation node) {
registerRegularNode();
registerReductiveNode();
registerCall();
visit(node.receiver);
skipReductiveNodes(() => visit(node.name));
_processArguments(node.arguments, null);
}
@override
void visitFunctionInvocation(ir.FunctionInvocation node) {
registerRegularNode();
registerReductiveNode();
registerCall();
visit(node.receiver);
skipReductiveNodes(() => visit(node.name));
_processArguments(node.arguments, null);
}
@override
void visitLocalFunctionInvocation(ir.LocalFunctionInvocation node) {
registerRegularNode();
registerReductiveNode();
registerCall();
_processArguments(node.arguments, null);
// Account for the implicit access to the local variable:
registerRegularNode();
registerReductiveNode();
}
@override
void visitEqualsNull(ir.EqualsNull node) {
registerRegularNode();
registerReductiveNode();
visit(node.expression);
}
@override
void visitEqualsCall(ir.EqualsCall node) {
registerRegularNode();
registerReductiveNode();
registerCall();
visit(node.left);
visit(node.right);
}
void _processArguments(ir.Arguments arguments, ir.FunctionNode? target) {
registerRegularNode();
if (arguments.types.isNotEmpty) {
data.hasTypeArguments = true;
skipReductiveNodes(() => visitList(arguments.types));
}
int count = arguments.positional.length + arguments.named.length;
data.argumentCounts.add(count);
if (target != null) {
// Disallow defaulted optional arguments since they will be passed
// explicitly.
if (target.positionalParameters.length + target.namedParameters.length >
count) {
data.hasArgumentDefaulting = true;
}
}
visitList(arguments.positional);
for (ir.NamedExpression expression in arguments.named) {
registerRegularNode();
expression.value.accept(this);
}
}
@override
void visitAsExpression(ir.AsExpression node) {
registerRegularNode();
visit(node.operand);
skipReductiveNodes(() => visit(node.type));
if (!(node.isTypeError && omitImplicitCasts)) {
data.hasCast = true;
}
}
@override
void visitVariableDeclaration(ir.VariableDeclaration node) {
registerRegularNode();
skipReductiveNodes(() {
visitList(node.annotations);
visit(node.type);
});
visit(node.initializer);
// A local variable is an alias for the initializer expression.
if (node.initializer != null) {
unregisterReductiveNode(); // discount one reference to the variable.
}
}
@override
void visitIfStatement(ir.IfStatement node) {
registerRegularNode();
node.visitChildren(this);
data.hasIf = true;
}
void handleGenerativeConstructorFactory(ir.Constructor node) {
// Generative constructors are compiled to a factory constructor which
// contains inlined all the initializations up the inheritance chain and
// then call each of the constructor bodies down the inheritance chain.
ir.Constructor? constructor = node;
Set<ir.Field> initializedFields = {};
bool hasCallToSomeConstructorBody = false;
inheritance_loop:
while (constructor != null) {
ir.Constructor? superConstructor;
for (var initializer in constructor.initializers) {
if (initializer is ir.RedirectingInitializer) {
// Discount the size of the arguments by references that are
// pass-through.
// TODO(sra): Need to add size of defaulted arguments.
var discountParametersOld = discountParameters;
discountParameters = true;
initializer.arguments.accept(this);
discountParameters = discountParametersOld;
constructor = initializer.target;
continue inheritance_loop;
} else if (initializer is ir.SuperInitializer) {
superConstructor = initializer.target;
// Discount the size of the arguments by references that are
// pass-through.
// TODO(sra): Need to add size of defaulted arguments.
var discountParametersOld = discountParameters;
discountParameters = true;
initializer.arguments.accept(this);
discountParameters = discountParametersOld;
} else if (initializer is ir.FieldInitializer) {
initializedFields.add(initializer.field);
initializer.value.accept(this);
} else if (initializer is ir.AssertInitializer) {
if (enableUserAssertions) {
initializer.accept(this);
}
} else {
initializer.accept(this);
}
}
_handleFields(constructor!.enclosingClass, initializedFields);
// There will be a call to the constructor's body, which might be empty
// and inlined away.
var function = constructor.function;
var body = function.body!;
if (!isEmptyBody(body)) {
// All of the parameters are passed to the body.
int parameterCount =
function.positionalParameters.length +
function.namedParameters.length +
function.typeParameters.length;
hasCallToSomeConstructorBody = true;
registerCall();
// A body call looks like "t.Body$(arguments);", i.e. an expression
// statement with an instance member call, but the receiver is not
// counted in the arguments. I'm guessing about 6 nodes for this.
registerRegularNode(
6 + parameterCount * inliningNodesOutsideLoopArgFactor,
);
// We can't inline a generative constructor factory when one of the
// bodies rewrites the environment to put locals or parameters into a
// box. The box is created in the generative constructor factory since
// the box may be shared between closures in the initializer list and
// closures in the constructor body.
var bodyVisitor = InlineWeederBodyClosure();
body.accept(bodyVisitor);
if (bodyVisitor.tooDifficult) {
data.hasClosure = true;
}
}
if (superConstructor != null) {
// 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.
ir.Supertype supertype = constructor.enclosingClass.supertype!;
while (supertype.classNode != superConstructor.enclosingClass) {
_handleFields(supertype.classNode, initializedFields);
supertype = supertype.classNode.supertype!;
}
}
constructor = superConstructor;
}
// In addition to the initializer expressions and body calls, there is an
// allocator call.
if (hasCallToSomeConstructorBody) {
// A temporary is required so we have
//
// t=new ...;
// ...;
// use(t);
//
// I'm guessing it takes about 4 nodes to introduce the temporary and
// assign it.
registerRegularNode(4); // A temporary is required.
}
// The initial field values are passed to the allocator.
registerRegularNode(
initializedFields.length * inliningNodesOutsideLoopArgFactor,
);
}
void _handleFields(ir.Class cls, Set<ir.Field> initializedFields) {
for (ir.Field field in cls.fields) {
if (!field.isInstanceMember) continue;
final initializer = field.initializer;
if (initializer == null ||
initializer is ir.ConstantExpression &&
initializer.constant is ir.PrimitiveConstant ||
initializer is ir.BasicLiteral) {
// Simple field initializers happen in the allocator, so do not
// contribute to the size of the generative constructor factory.
// TODO(sra): Use FieldInfo which tells us if the field is elided or
// initialized in the allocator.
continue;
}
if (!initializedFields.add(field)) continue;
initializer.accept(this);
}
// If [cls] is a mixin application, include fields from mixed in class.
if (cls.mixedInType != null) {
_handleFields(cls.mixedInType!.classNode, initializedFields);
}
}
bool isEmptyBody(ir.Statement body) {
if (body is ir.EmptyStatement) return true;
if (body is ir.Block) return body.statements.every(isEmptyBody);
if (body is ir.AssertStatement && !enableUserAssertions) return true;
return false;
}
}
/// Visitor to detect environment-rewriting that prevents inlining
/// (e.g. closures).
class InlineWeederBodyClosure extends ir.VisitorDefault<void>
with ir.VisitorVoidMixin {
bool tooDifficult = false;
InlineWeederBodyClosure();
@override
void defaultNode(ir.Node node) {
if (tooDifficult) return;
node.visitChildren(this);
}
@override
void visitFunctionExpression(ir.FunctionExpression node) {
tooDifficult = true;
}
@override
void visitFunctionDeclaration(ir.FunctionDeclaration node) {
tooDifficult = true;
}
@override
void visitFunctionNode(ir.FunctionNode node) {
assert(false);
if (node.asyncMarker != ir.AsyncMarker.Sync) {
tooDifficult = true;
return;
}
node.visitChildren(this);
}
}