blob: 1722f9f5e3dbb3ff767266cf70089dc75f6d873a [file] [log] [blame]
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
part of ssa;
/**
* A special element for the extra parameter taken by intercepted
* methods. We need to override [Element.computeType] because our
* optimizers may look at its declared type.
*/
class InterceptedElement extends ElementX {
final DartType type;
InterceptedElement(this.type, SourceString name, Element enclosing)
: super(name, ElementKind.PARAMETER, enclosing);
DartType computeType(Compiler compiler) => type;
}
class SsaBuilderTask extends CompilerTask {
final CodeEmitterTask emitter;
final JavaScriptBackend backend;
String get name => 'SSA builder';
SsaBuilderTask(JavaScriptBackend backend)
: emitter = backend.emitter,
backend = backend,
super(backend.compiler);
HGraph build(CodegenWorkItem work) {
return measure(() {
Element element = work.element.implementation;
HInstruction.idCounter = 0;
ConstantSystem constantSystem = compiler.backend.constantSystem;
SsaBuilder builder = new SsaBuilder(constantSystem, this, work);
HGraph graph;
ElementKind kind = element.kind;
if (kind == ElementKind.GENERATIVE_CONSTRUCTOR) {
graph = compileConstructor(builder, work);
} else if (kind == ElementKind.GENERATIVE_CONSTRUCTOR_BODY ||
kind == ElementKind.FUNCTION ||
kind == ElementKind.GETTER ||
kind == ElementKind.SETTER) {
graph = builder.buildMethod(element);
} else if (kind == ElementKind.FIELD) {
graph = builder.buildLazyInitializer(element);
} else {
compiler.internalErrorOnElement(element,
'unexpected element kind $kind');
}
assert(graph.isValid());
if (!identical(kind, ElementKind.FIELD)) {
FunctionElement function = element;
graph.calledInLoop = compiler.world.isCalledInLoop(function);
OptionalParameterTypes defaultValueTypes = null;
FunctionSignature signature = function.computeSignature(compiler);
if (signature.optionalParameterCount > 0) {
defaultValueTypes =
new OptionalParameterTypes(signature.optionalParameterCount);
int index = 0;
signature.forEachOptionalParameter((Element parameter) {
Constant defaultValue = builder.compileVariable(parameter);
HType type = HGraph.mapConstantTypeToSsaType(defaultValue);
defaultValueTypes.update(index, parameter.name, type);
index++;
});
} else {
// TODO(ahe): I have disabled type optimizations for
// optional arguments as the types are stored in the wrong
// order.
HTypeList parameterTypes =
backend.optimisticParameterTypes(element.declaration,
defaultValueTypes);
if (!parameterTypes.allUnknown) {
int i = 0;
signature.forEachParameter((Element param) {
builder.parameters[param].instructionType = parameterTypes[i++];
});
}
backend.registerParameterTypesOptimization(
element.declaration, parameterTypes, defaultValueTypes);
}
}
if (compiler.tracer.enabled) {
String name;
if (element.isMember()) {
String className = element.getEnclosingClass().name.slowToString();
String memberName = element.name.slowToString();
name = "$className.$memberName";
if (element.isGenerativeConstructorBody()) {
name = "$name (body)";
}
} else {
name = "${element.name.slowToString()}";
}
compiler.tracer.traceCompilation(name, work.compilationContext);
compiler.tracer.traceGraph('builder', graph);
}
return graph;
});
}
HGraph compileConstructor(SsaBuilder builder, CodegenWorkItem work) {
// The body of the constructor will be generated in a separate function.
final ClassElement classElement = work.element.getEnclosingClass();
return builder.buildFactory(classElement.implementation,
work.element.implementation);
}
}
/**
* Keeps track of locals (including parameters and phis) when building. The
* 'this' reference is treated as parameter and hence handled by this class,
* too.
*/
class LocalsHandler {
/**
* The values of locals that can be directly accessed (without redirections
* to boxes or closure-fields).
*
* [directLocals] is iterated, so it is a [LinkedHashMap] to make the
* iteration order a function only of insertions and not a function of
* e.g. Element hash codes. I'd prefer to use a SortedMap but some elements
* don't have source locations for [Elements.compareByPosition].
*/
LinkedHashMap<Element, HInstruction> directLocals;
Map<Element, Element> redirectionMapping;
SsaBuilder builder;
ClosureClassMap closureData;
LocalsHandler(this.builder)
: directLocals = new LinkedHashMap<Element, HInstruction>(),
redirectionMapping = new Map<Element, Element>();
get typesTask => builder.compiler.typesTask;
/**
* Creates a new [LocalsHandler] based on [other]. We only need to
* copy the [directLocals], since the other fields can be shared
* throughout the AST visit.
*/
LocalsHandler.from(LocalsHandler other)
: directLocals =
new LinkedHashMap<Element, HInstruction>.from(other.directLocals),
redirectionMapping = other.redirectionMapping,
builder = other.builder,
closureData = other.closureData;
/**
* Redirects accesses from element [from] to element [to]. The [to] element
* must be a boxed variable or a variable that is stored in a closure-field.
*/
void redirectElement(Element from, Element to) {
assert(redirectionMapping[from] == null);
redirectionMapping[from] = to;
assert(isStoredInClosureField(from) || isBoxed(from));
}
HInstruction createBox() {
// TODO(floitsch): Clean up this hack. Should we create a box-object by
// just creating an empty object literal?
HInstruction box = new HForeign(new js.ObjectInitializer([]),
HType.UNKNOWN,
<HInstruction>[]);
builder.add(box);
return box;
}
/**
* If the scope (function or loop) [node] has captured variables then this
* method creates a box and sets up the redirections.
*/
void enterScope(Node node, Element element) {
// See if any variable in the top-scope of the function is captured. If yes
// we need to create a box-object.
ClosureScope scopeData = closureData.capturingScopes[node];
if (scopeData == null) return;
HInstruction box;
// The scope has captured variables.
if (element != null && element.isGenerativeConstructorBody()) {
// The box is passed as a parameter to a generative
// constructor body.
box = builder.addParameter(scopeData.boxElement);
} else {
box = createBox();
}
// Add the box to the known locals.
directLocals[scopeData.boxElement] = box;
// Make sure that accesses to the boxed locals go into the box. We also
// need to make sure that parameters are copied into the box if necessary.
scopeData.capturedVariableMapping.forEach((Element from, Element to) {
// The [from] can only be a parameter for function-scopes and not
// loop scopes.
if (from.isParameter() && !element.isGenerativeConstructorBody()) {
// Now that the redirection is set up, the update to the local will
// write the parameter value into the box.
// Store the captured parameter in the box. Get the current value
// before we put the redirection in place.
// We don't need to update the local for a generative
// constructor body, because it receives a box that already
// contains the updates as the last parameter.
HInstruction instruction = readLocal(from);
redirectElement(from, to);
updateLocal(from, instruction);
} else {
redirectElement(from, to);
}
});
}
/**
* Replaces the current box with a new box and copies over the given list
* of elements from the old box into the new box.
*/
void updateCaptureBox(Element boxElement, List<Element> toBeCopiedElements) {
// Create a new box and copy over the values from the old box into the
// new one.
HInstruction oldBox = readLocal(boxElement);
HInstruction newBox = createBox();
for (Element boxedVariable in toBeCopiedElements) {
// [readLocal] uses the [boxElement] to find its box. By replacing it
// behind its back we can still get to the old values.
updateLocal(boxElement, oldBox);
HInstruction oldValue = readLocal(boxedVariable);
updateLocal(boxElement, newBox);
updateLocal(boxedVariable, oldValue);
}
updateLocal(boxElement, newBox);
}
/**
* Documentation wanted -- johnniwinther
*
* Invariant: [function] must be an implementation element.
*/
void startFunction(Element element, Expression node) {
assert(invariant(node, element.isImplementation));
Compiler compiler = builder.compiler;
closureData = compiler.closureToClassMapper.computeClosureToClassMapping(
element, node, builder.elements);
if (element is FunctionElement) {
FunctionElement functionElement = element;
FunctionSignature params = functionElement.computeSignature(compiler);
params.orderedForEachParameter((Element parameterElement) {
if (element.isGenerativeConstructorBody()) {
ClosureScope scopeData = closureData.capturingScopes[node];
if (scopeData != null
&& scopeData.capturedVariableMapping.containsKey(
parameterElement)) {
// The parameter will be a field in the box passed as the
// last parameter. So no need to have it.
return;
}
}
HInstruction parameter = builder.addParameter(parameterElement);
builder.parameters[parameterElement] = parameter;
directLocals[parameterElement] = parameter;
parameter.instructionType =
new HType.inferredTypeForElement(parameterElement, compiler);
});
}
enterScope(node, element);
// If the freeVariableMapping is not empty, then this function was a
// nested closure that captures variables. Redirect the captured
// variables to fields in the closure.
closureData.freeVariableMapping.forEach((Element from, Element to) {
redirectElement(from, to);
});
if (closureData.isClosure()) {
// Inside closure redirect references to itself to [:this:].
HThis thisInstruction = new HThis(closureData.thisElement,
HType.NON_NULL);
builder.graph.thisInstruction = thisInstruction;
builder.graph.entry.addAtEntry(thisInstruction);
updateLocal(closureData.closureElement, thisInstruction);
} else if (element.isInstanceMember()
|| element.isGenerativeConstructor()) {
// Once closures have been mapped to classes their instance members might
// not have any thisElement if the closure was created inside a static
// context.
HThis thisInstruction = new HThis(
closureData.thisElement, builder.getTypeOfThis());
builder.graph.thisInstruction = thisInstruction;
builder.graph.entry.addAtEntry(thisInstruction);
directLocals[closureData.thisElement] = thisInstruction;
}
// If this method is an intercepted method, add the extra
// parameter to it, that is the actual receiver for intercepted
// classes, or the same as [:this:] for non-intercepted classes.
ClassElement cls = element.getEnclosingClass();
JavaScriptBackend backend = compiler.backend;
if (backend.isInterceptedMethod(element)) {
bool isInterceptorClass = backend.isInterceptorClass(cls.declaration);
SourceString name = isInterceptorClass
? const SourceString('receiver')
: const SourceString('_');
Element parameter = new InterceptedElement(
cls.computeType(compiler), name, element);
HParameterValue value = new HParameterValue(parameter);
builder.graph.explicitReceiverParameter = value;
builder.graph.entry.addAfter(
directLocals[closureData.thisElement], value);
if (isInterceptorClass) {
// Only use the extra parameter in intercepted classes.
directLocals[closureData.thisElement] = value;
}
value.instructionType = builder.getTypeOfThis();
}
}
bool hasValueForDirectLocal(Element element) {
assert(element != null);
assert(isAccessedDirectly(element));
return directLocals[element] != null;
}
/**
* Returns true if the local can be accessed directly. Boxed variables or
* captured variables that are stored in the closure-field return [false].
*/
bool isAccessedDirectly(Element element) {
assert(element != null);
return redirectionMapping[element] == null
&& !closureData.usedVariablesInTry.contains(element);
}
bool isStoredInClosureField(Element element) {
assert(element != null);
if (isAccessedDirectly(element)) return false;
Element redirectTarget = redirectionMapping[element];
if (redirectTarget == null) return false;
if (redirectTarget.isMember()) {
assert(redirectTarget is ClosureFieldElement);
return true;
}
return false;
}
bool isBoxed(Element element) {
if (isAccessedDirectly(element)) return false;
if (isStoredInClosureField(element)) return false;
return redirectionMapping[element] != null;
}
bool isUsedInTry(Element element) {
return closureData.usedVariablesInTry.contains(element);
}
/**
* Returns an [HInstruction] for the given element. If the element is
* boxed or stored in a closure then the method generates code to retrieve
* the value.
*/
HInstruction readLocal(Element element) {
if (isAccessedDirectly(element)) {
if (directLocals[element] == null) {
if (element.isTypeVariable()) {
builder.compiler.internalError(
"Runtime type information not available for $element",
element: builder.compiler.currentElement);
} else {
builder.compiler.internalError(
"Cannot find value $element",
element: element);
}
}
return directLocals[element];
} else if (isStoredInClosureField(element)) {
Element redirect = redirectionMapping[element];
HInstruction receiver = readLocal(closureData.closureElement);
HInstruction fieldGet = new HFieldGet(redirect, receiver);
fieldGet.instructionType = builder.getTypeOfCapturedVariable(element);
builder.add(fieldGet);
return fieldGet;
} else if (isBoxed(element)) {
Element redirect = redirectionMapping[element];
// In the function that declares the captured variable the box is
// accessed as direct local. Inside the nested closure the box is
// accessed through a closure-field.
// Calling [readLocal] makes sure we generate the correct code to get
// the box.
assert(redirect.enclosingElement.isVariable());
HInstruction box = readLocal(redirect.enclosingElement);
HInstruction lookup = new HFieldGet(redirect, box);
lookup.instructionType = builder.getTypeOfCapturedVariable(element);
builder.add(lookup);
return lookup;
} else {
assert(isUsedInTry(element));
HLocalValue local = getLocal(element);
HInstruction variable = new HLocalGet(element, local);
builder.add(variable);
return variable;
}
}
HInstruction readThis() {
HInstruction res = readLocal(closureData.thisElement);
if (res.instructionType == null) {
res.instructionType = builder.getTypeOfThis();
}
return res;
}
HLocalValue getLocal(Element element) {
// If the element is a parameter, we already have a
// HParameterValue for it. We cannot create another one because
// it could then have another name than the real parameter. And
// the other one would not know it is just a copy of the real
// parameter.
if (element.isParameter()) return builder.parameters[element];
return builder.activationVariables.putIfAbsent(element, () {
HLocalValue local = new HLocalValue(element);
builder.graph.entry.addAtExit(local);
return local;
});
}
/**
* Sets the [element] to [value]. If the element is boxed or stored in a
* closure then the method generates code to set the value.
*/
void updateLocal(Element element, HInstruction value) {
assert(!isStoredInClosureField(element));
if (isAccessedDirectly(element)) {
directLocals[element] = value;
} else if (isBoxed(element)) {
Element redirect = redirectionMapping[element];
// The box itself could be captured, or be local. A local variable that
// is captured will be boxed, but the box itself will be a local.
// Inside the closure the box is stored in a closure-field and cannot
// be accessed directly.
assert(redirect.enclosingElement.isVariable());
HInstruction box = readLocal(redirect.enclosingElement);
builder.add(new HFieldSet(redirect, box, value));
} else {
assert(isUsedInTry(element));
HLocalValue local = getLocal(element);
builder.add(new HLocalSet(element, local, value));
}
}
/**
* This function, startLoop, must be called before visiting any children of
* the loop. In particular it needs to be called before executing the
* initializers.
*
* The [LocalsHandler] will make the boxes and updates at the right moment.
* The builder just needs to call [enterLoopBody] and [enterLoopUpdates] (for
* [For] loops) at the correct places. For phi-handling [beginLoopHeader] and
* [endLoop] must also be called.
*
* The correct place for the box depends on the given loop. In most cases
* the box will be created when entering the loop-body: while, do-while, and
* for-in (assuming the call to [:next:] is inside the body) can always be
* constructed this way.
*
* Things are slightly more complicated for [For] loops. If no declared
* loop variable is boxed then the loop-body approach works here too. If a
* loop-variable is boxed we need to introduce a new box for the
* loop-variable before we enter the initializer so that the initializer
* writes the values into the box. In any case we need to create the box
* before the condition since the condition could box the variable.
* Since the first box is created outside the actual loop we have a second
* location where a box is created: just before the updates. This is
* necessary since updates are considered to be part of the next iteration
* (and can again capture variables).
*
* For example the following Dart code prints 1 3 -- 3 4.
*
* var fs = [];
* for (var i = 0; i < 3; (f() { fs.add(f); print(i); i++; })()) {
* i++;
* }
* print("--");
* for (var i = 0; i < 2; i++) fs[i]();
*
* We solve this by emitting the following code (only for [For] loops):
* <Create box> <== move the first box creation outside the loop.
* <initializer>;
* loop-entry:
* if (!<condition>) goto loop-exit;
* <body>
* <update box> // create a new box and copy the captured loop-variables.
* <updates>
* goto loop-entry;
* loop-exit:
*/
void startLoop(Node node) {
ClosureScope scopeData = closureData.capturingScopes[node];
if (scopeData == null) return;
if (scopeData.hasBoxedLoopVariables()) {
// If there are boxed loop variables then we set up the box and
// redirections already now. This way the initializer can write its
// values into the box.
// For other loops the box will be created when entering the body.
enterScope(node, null);
}
}
/**
* Create phis at the loop entry for local variables (ready for the values
* from the back edge). Populate the phis with the current values.
*/
void beginLoopHeader(HBasicBlock loopEntry) {
// Create a copy because we modify the map while iterating over it.
Map<Element, HInstruction> savedDirectLocals =
new LinkedHashMap<Element, HInstruction>.from(directLocals);
// Create phis for all elements in the definitions environment.
savedDirectLocals.forEach((Element element, HInstruction instruction) {
if (isAccessedDirectly(element)) {
// We know 'this' cannot be modified.
if (!identical(element, closureData.thisElement)) {
HPhi phi = new HPhi.singleInput(element, instruction);
loopEntry.addPhi(phi);
directLocals[element] = phi;
} else {
directLocals[element] = instruction;
}
}
});
}
void enterLoopBody(Node node) {
ClosureScope scopeData = closureData.capturingScopes[node];
if (scopeData == null) return;
// If there are no declared boxed loop variables then we did not create the
// box before the initializer and we have to create the box now.
if (!scopeData.hasBoxedLoopVariables()) {
enterScope(node, null);
}
}
void enterLoopUpdates(Node node) {
// If there are declared boxed loop variables then the updates might have
// access to the box and we must switch to a new box before executing the
// updates.
// In all other cases a new box will be created when entering the body of
// the next iteration.
ClosureScope scopeData = closureData.capturingScopes[node];
if (scopeData == null) return;
if (scopeData.hasBoxedLoopVariables()) {
updateCaptureBox(scopeData.boxElement, scopeData.boxedLoopVariables);
}
}
/**
* Goes through the phis created in beginLoopHeader entry and adds the
* input from the back edge (from the current value of directLocals) to them.
*/
void endLoop(HBasicBlock loopEntry) {
// If the loop has an aborting body, we don't update the loop
// phis.
if (loopEntry.predecessors.length == 1) return;
loopEntry.forEachPhi((HPhi phi) {
Element element = phi.sourceElement;
HInstruction postLoopDefinition = directLocals[element];
phi.addInput(postLoopDefinition);
});
}
/**
* Merge [otherLocals] into this locals handler, creating phi-nodes when
* there is a conflict.
* If a phi node is necessary, it will use this handler's instruction as the
* first input, and the otherLocals instruction as the second.
*/
void mergeWith(LocalsHandler otherLocals, HBasicBlock joinBlock) {
// If an element is in one map but not the other we can safely
// ignore it. It means that a variable was declared in the
// block. Since variable declarations are scoped the declared
// variable cannot be alive outside the block. Note: this is only
// true for nodes where we do joins.
Map<Element, HInstruction> joinedLocals =
new LinkedHashMap<Element, HInstruction>();
otherLocals.directLocals.forEach((element, instruction) {
// We know 'this' cannot be modified.
if (identical(element, closureData.thisElement)) {
assert(directLocals[element] == instruction);
joinedLocals[element] = instruction;
} else {
HInstruction mine = directLocals[element];
if (mine == null) return;
if (identical(instruction, mine)) {
joinedLocals[element] = instruction;
} else {
HInstruction phi =
new HPhi.manyInputs(element, <HInstruction>[mine, instruction]);
joinBlock.addPhi(phi);
joinedLocals[element] = phi;
}
}
});
directLocals = joinedLocals;
}
/**
* When control flow merges, this method can be used to merge several
* localsHandlers into a new one using phis. The new localsHandler is
* returned. Unless it is also in the list, the current localsHandler is not
* used for its values, only for its declared variables. This is a way to
* exclude local values from the result when they are no longer in scope.
*/
LocalsHandler mergeMultiple(List<LocalsHandler> localsHandlers,
HBasicBlock joinBlock) {
assert(localsHandlers.length > 0);
if (localsHandlers.length == 1) return localsHandlers[0];
Map<Element, HInstruction> joinedLocals =
new LinkedHashMap<Element,HInstruction>();
HInstruction thisValue = null;
directLocals.forEach((Element element, HInstruction instruction) {
if (element != closureData.thisElement) {
HPhi phi = new HPhi.noInputs(element);
joinedLocals[element] = phi;
joinBlock.addPhi(phi);
} else {
// We know that "this" never changes, if it's there.
// Save it for later. While merging, there is no phi for "this",
// so we don't have to special case it in the merge loop.
thisValue = instruction;
}
});
for (LocalsHandler handler in localsHandlers) {
handler.directLocals.forEach((Element element, HInstruction instruction) {
HPhi phi = joinedLocals[element];
if (phi != null) {
phi.addInput(instruction);
}
});
}
if (thisValue != null) {
// If there was a "this" for the scope, add it to the new locals.
joinedLocals[closureData.thisElement] = thisValue;
}
// Remove locals that are not in all handlers.
directLocals = new LinkedHashMap<Element, HInstruction>();
joinedLocals.forEach((element, instruction) {
if (instruction is HPhi
&& instruction.inputs.length != localsHandlers.length) {
joinBlock.removePhi(instruction);
} else {
directLocals[element] = instruction;
}
});
return this;
}
}
// Represents a single break/continue instruction.
class JumpHandlerEntry {
final HJump jumpInstruction;
final LocalsHandler locals;
bool isBreak() => jumpInstruction is HBreak;
bool isContinue() => jumpInstruction is HContinue;
JumpHandlerEntry(this.jumpInstruction, this.locals);
}
abstract class JumpHandler {
factory JumpHandler(SsaBuilder builder, TargetElement target) {
return new TargetJumpHandler(builder, target);
}
void generateBreak([LabelElement label]);
void generateContinue([LabelElement label]);
void forEachBreak(void action(HBreak instruction, LocalsHandler locals));
void forEachContinue(void action(HContinue instruction,
LocalsHandler locals));
bool hasAnyContinue();
bool hasAnyBreak();
void close();
final TargetElement target;
List<LabelElement> labels();
}
// Insert break handler used to avoid null checks when a target isn't
// used as the target of a break, and therefore doesn't need a break
// handler associated with it.
class NullJumpHandler implements JumpHandler {
final Compiler compiler;
NullJumpHandler(this.compiler);
void generateBreak([LabelElement label]) {
compiler.internalError('generateBreak should not be called');
}
void generateContinue([LabelElement label]) {
compiler.internalError('generateContinue should not be called');
}
void forEachBreak(Function ignored) { }
void forEachContinue(Function ignored) { }
void close() { }
bool hasAnyContinue() => false;
bool hasAnyBreak() => false;
List<LabelElement> labels() => const <LabelElement>[];
TargetElement get target => null;
}
// Records breaks until a target block is available.
// Breaks are always forward jumps.
// Continues in loops are implemented as breaks of the body.
// Continues in switches is currently not handled.
class TargetJumpHandler implements JumpHandler {
final SsaBuilder builder;
final TargetElement target;
final List<JumpHandlerEntry> jumps;
TargetJumpHandler(SsaBuilder builder, this.target)
: this.builder = builder,
jumps = <JumpHandlerEntry>[] {
assert(builder.jumpTargets[target] == null);
builder.jumpTargets[target] = this;
}
void generateBreak([LabelElement label]) {
HInstruction breakInstruction;
if (label == null) {
breakInstruction = new HBreak(target);
} else {
breakInstruction = new HBreak.toLabel(label);
}
LocalsHandler locals = new LocalsHandler.from(builder.localsHandler);
builder.close(breakInstruction);
jumps.add(new JumpHandlerEntry(breakInstruction, locals));
}
void generateContinue([LabelElement label]) {
HInstruction continueInstruction;
if (label == null) {
continueInstruction = new HContinue(target);
} else {
continueInstruction = new HContinue.toLabel(label);
// Switch case continue statements must be handled by the
// [SwitchCaseJumpHandler].
assert(label.target.statement is! SwitchCase);
}
LocalsHandler locals = new LocalsHandler.from(builder.localsHandler);
builder.close(continueInstruction);
jumps.add(new JumpHandlerEntry(continueInstruction, locals));
}
void forEachBreak(Function action) {
for (JumpHandlerEntry entry in jumps) {
if (entry.isBreak()) action(entry.jumpInstruction, entry.locals);
}
}
void forEachContinue(Function action) {
for (JumpHandlerEntry entry in jumps) {
if (entry.isContinue()) action(entry.jumpInstruction, entry.locals);
}
}
bool hasAnyContinue() {
for (JumpHandlerEntry entry in jumps) {
if (entry.isContinue()) return true;
}
return false;
}
bool hasAnyBreak() {
for (JumpHandlerEntry entry in jumps) {
if (entry.isBreak()) return true;
}
return false;
}
void close() {
// The mapping from TargetElement to JumpHandler is no longer needed.
builder.jumpTargets.remove(target);
}
List<LabelElement> labels() {
List<LabelElement> result = null;
for (LabelElement element in target.labels) {
if (result == null) result = <LabelElement>[];
result.add(element);
}
return (result == null) ? const <LabelElement>[] : result;
}
}
/// Special [JumpHandler] implementation used to handle continue statements
/// targeting switch cases.
class SwitchCaseJumpHandler extends TargetJumpHandler {
/// Map from switch case targets to indices used to encode the flow of the
/// switch case loop.
final Map<TargetElement, int> targetIndexMap = new Map<TargetElement, int>();
SwitchCaseJumpHandler(SsaBuilder builder,
TargetElement target,
SwitchStatement node)
: super(builder, target) {
// The switch case indices must match those computed in
// [SsaBuilder.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).
int switchIndex = 1;
for (SwitchCase switchCase in node.cases) {
for (Node labelOrCase in switchCase.labelsAndCases) {
Node label = labelOrCase.asLabel();
if (label != null) {
LabelElement labelElement = builder.elements[label];
if (labelElement != null && labelElement.isContinueTarget) {
TargetElement continueTarget = labelElement.target;
targetIndexMap[continueTarget] = switchIndex;
assert(builder.jumpTargets[continueTarget] == null);
builder.jumpTargets[continueTarget] = this;
}
}
}
switchIndex++;
}
}
void generateBreak([LabelElement label]) {
if (label == null) {
// Creates a special break instruction for the synthetic loop generated
// for a switch statement with continue statements. See
// [SsaBuilder.buildComplexSwitchStatement] for detail.
HInstruction breakInstruction =
new HBreak(target, breakSwitchContinueLoop: true);
LocalsHandler locals = new LocalsHandler.from(builder.localsHandler);
builder.close(breakInstruction);
jumps.add(new JumpHandlerEntry(breakInstruction, locals));
} else {
super.generateBreak(label);
}
}
bool isContinueToSwitchCase(LabelElement label) {
return label != null && targetIndexMap.containsKey(label.target);
}
void generateContinue([LabelElement label]) {
if (isContinueToSwitchCase(label)) {
// Creates the special instructions 'label = i; continue l;' used in
// switch statements with continue statements. See
// [SsaBuilder.buildComplexSwitchStatement] for detail.
assert(label != null);
HInstruction value = builder.graph.addConstantInt(
targetIndexMap[label.target],
builder.constantSystem);
builder.localsHandler.updateLocal(target, value);
assert(label.target.labels.contains(label));
HInstruction continueInstruction = new HContinue(target);
LocalsHandler locals = new LocalsHandler.from(builder.localsHandler);
builder.close(continueInstruction);
jumps.add(new JumpHandlerEntry(continueInstruction, locals));
} else {
super.generateContinue(label);
}
}
void close() {
// The mapping from TargetElement to JumpHandler is no longer needed.
for (TargetElement target in targetIndexMap.keys) {
builder.jumpTargets.remove(target);
}
super.close();
}
}
class SsaBuilder extends ResolvedVisitor implements Visitor {
final SsaBuilderTask builder;
final JavaScriptBackend backend;
final CodegenWorkItem work;
final ConstantSystem constantSystem;
HGraph graph;
LocalsHandler localsHandler;
HInstruction rethrowableException;
Map<Element, HInstruction> parameters;
final RuntimeTypes rti;
HParameterValue lastAddedParameter;
Map<TargetElement, JumpHandler> jumpTargets;
/**
* Variables stored in the current activation. These variables are
* being updated in try/catch blocks, and should be
* accessed indirectly through [HLocalGet] and [HLocalSet].
*/
Map<Element, HLocalValue> activationVariables;
// We build the Ssa graph by simulating a stack machine.
List<HInstruction> stack;
/**
* The current block to add instructions to. Might be null, if we are
* visiting dead code, but see [isReachable].
*/
HBasicBlock _current;
/**
* The most recently opened block. Has the same value as [_current] while
* the block is open, but unlike [_current], it isn't cleared when the
* current block is closed.
*/
HBasicBlock lastOpenedBlock;
/**
* Indicates whether the current block is dead (because it has a throw or a
* return further up). If this is false, then [_current] may be null. If the
* block is dead then it may also be aborted, but for simplicity we only
* abort on statement boundaries, not in the middle of expressions. See
* isAborted.
*/
bool isReachable = true;
final List<Element> sourceElementStack;
Element get currentElement => sourceElementStack.last.declaration;
Element get currentNonClosureClass {
ClassElement cls = currentElement.getEnclosingClass();
if (cls != null && cls.isClosure()) {
var closureClass = cls;
return closureClass.methodElement.getEnclosingClass();
} else {
return cls;
}
}
Compiler get compiler => builder.compiler;
CodeEmitterTask get emitter => builder.emitter;
SsaBuilder(this.constantSystem, SsaBuilderTask builder, CodegenWorkItem work)
: this.builder = builder,
this.backend = builder.backend,
this.work = work,
graph = new HGraph(),
stack = new List<HInstruction>(),
activationVariables = new Map<Element, HLocalValue>(),
jumpTargets = new Map<TargetElement, JumpHandler>(),
parameters = new Map<Element, HInstruction>(),
sourceElementStack = <Element>[work.element],
inliningStack = <InliningState>[],
rti = builder.backend.rti,
super(work.resolutionTree) {
localsHandler = new LocalsHandler(this);
}
static const MAX_INLINING_DEPTH = 3;
static const MAX_INLINING_NODES = 46;
List<InliningState> inliningStack;
Element returnElement;
DartType returnType;
bool inTryStatement = false;
HBasicBlock get current => _current;
void set current(c) {
isReachable = c != null;
_current = c;
}
/**
* Compiles compile-time constants. Never returns [:null:]. If the
* initial value is not a compile-time constants, it reports an
* internal error.
*/
Constant compileConstant(VariableElement element) {
return compiler.constantHandler.compileConstant(element);
}
Constant compileVariable(VariableElement element) {
return compiler.constantHandler.compileVariable(element);
}
bool isLazilyInitialized(VariableElement element) {
Constant initialValue = compileVariable(element);
return initialValue == null;
}
HType cachedTypeOfThis;
HType getTypeOfThis() {
HType result = cachedTypeOfThis;
if (result == null) {
Element element = localsHandler.closureData.thisElement;
ClassElement cls = element.enclosingElement.getEnclosingClass();
// Use the raw type because we don't have the type context for the
// type parameters.
DartType type = cls.rawType;
if (compiler.world.isUsedAsMixin(cls)) {
// If the enclosing class is used as a mixin, [:this:] can be
// of the class that mixins the enclosing class. These two
// classes do not have a subclass relationship, so, for
// simplicity, we mark the type as an interface type.
result = new HType.nonNullSubtype(type, compiler);
} else {
result = new HType.nonNullSubclass(type, compiler);
}
cachedTypeOfThis = result;
}
return result;
}
Map<Element, HType> cachedTypesOfCapturedVariables =
new Map<Element, HType>();
HType getTypeOfCapturedVariable(Element element) {
return cachedTypesOfCapturedVariables.putIfAbsent(element, () {
return new HType.inferredTypeForElement(element, compiler);
});
}
/**
* Documentation wanted -- johnniwinther
*
* Invariant: [functionElement] must be an implementation element.
*/
HGraph buildMethod(FunctionElement functionElement) {
assert(invariant(functionElement, functionElement.isImplementation));
FunctionExpression function = functionElement.parseNode(compiler);
assert(function != null);
assert(!function.modifiers.isExternal());
assert(elements[function] != null);
openFunction(functionElement, function);
SourceString name = functionElement.name;
// If [functionElement] is `operator==` we explicitely add a null check at
// the beginning of the method. This is to avoid having call sites do the
// null check.
if (name == const SourceString('==')) {
if (!backend.operatorEqHandlesNullArgument(functionElement)) {
handleIf(
function,
() {
HParameterValue parameter = parameters.values.first;
push(new HIdentity(
parameter, graph.addConstantNull(constantSystem)));
},
() {
closeAndGotoExit(new HReturn(
graph.addConstantBool(false, constantSystem)));
},
null);
}
}
function.body.accept(this);
return closeFunction();
}
HGraph buildLazyInitializer(VariableElement variable) {
SendSet node = variable.parseNode(compiler);
openFunction(variable, node);
Link<Node> link = node.arguments;
assert(!link.isEmpty && link.tail.isEmpty);
visit(link.head);
HInstruction value = pop();
value = potentiallyCheckType(value, variable.computeType(compiler));
closeAndGotoExit(new HReturn(value));
return closeFunction();
}
/**
* Returns the constructor body associated with the given constructor or
* creates a new constructor body, if none can be found.
*
* Returns [:null:] if the constructor does not have a body.
*/
ConstructorBodyElement getConstructorBody(FunctionElement constructor) {
assert(constructor.isGenerativeConstructor());
assert(invariant(constructor, constructor.isImplementation));
if (constructor.isSynthesized) return null;
FunctionExpression node = constructor.parseNode(compiler);
// If we know the body doesn't have any code, we don't generate it.
if (!node.hasBody()) return null;
if (node.hasEmptyBody()) return null;
ClassElement classElement = constructor.getEnclosingClass();
ConstructorBodyElement bodyElement;
classElement.forEachBackendMember((Element backendMember) {
if (backendMember.isGenerativeConstructorBody()) {
ConstructorBodyElement body = backendMember;
if (body.constructor == constructor) {
// TODO(kasperl): Find a way of stopping the iteration
// through the backend members.
bodyElement = backendMember;
}
}
});
if (bodyElement == null) {
bodyElement = new ConstructorBodyElementX(constructor);
// [:resolveMethodElement:] require the passed element to be a
// declaration.
TreeElements treeElements =
compiler.enqueuer.resolution.getCachedElements(
constructor.declaration);
classElement.addBackendMember(bodyElement);
if (constructor.isPatch) {
// Create origin body element for patched constructors.
bodyElement.origin = new ConstructorBodyElementX(constructor.origin);
bodyElement.origin.patch = bodyElement;
classElement.origin.addBackendMember(bodyElement.origin);
}
compiler.enqueuer.codegen.addToWorkList(bodyElement.declaration,
treeElements);
}
assert(bodyElement.isGenerativeConstructorBody());
return bodyElement;
}
HParameterValue addParameter(Element element) {
HParameterValue result = new HParameterValue(element);
if (lastAddedParameter == null) {
graph.entry.addBefore(graph.entry.first, result);
} else {
graph.entry.addAfter(lastAddedParameter, result);
}
lastAddedParameter = result;
return result;
}
/**
* Documentation wanted -- johnniwinther
*
* Invariant: [function] must be an implementation element.
*/
InliningState enterInlinedMethod(PartialFunctionElement function,
Selector selector,
Link<Node> argumentsNodes,
List<HInstruction> providedArguments,
Node currentNode) {
assert(invariant(function, function.isImplementation));
List<HInstruction> compiledArguments;
bool isInstanceMember = function.isInstanceMember();
if (isInstanceMember) {
assert(providedArguments != null);
compiledArguments = new List<HInstruction>();
compiledArguments.add(providedArguments[0]);
// [providedArguments] contains the arguments given in our
// internal order (see [addDynamicSendArgumentsToList]). So we
// call [Selector.addArgumentsToList] only for getting the
// default values of the optional parameters.
bool succeeded = selector.addArgumentsToList(
argumentsNodes,
compiledArguments,
function,
(node) => null,
handleConstantForOptionalParameter,
compiler);
int argumentIndex = 1; // Skip receiver.
// [compiledArguments] now only contains the default values of
// the optional parameters that were not provided by
// [argumentsNodes]. So we iterate over [providedArguments] to fill
// in all the arguments.
for (int i = 1; i < compiledArguments.length; i++) {
if (compiledArguments[i] == null) {
compiledArguments[i] = providedArguments[argumentIndex++];
}
}
// The caller of [enterInlinedMethod] has ensured the selector
// matches the element.
assert(succeeded);
} else {
assert(providedArguments != null);
compiledArguments = providedArguments;
}
// Create the inlining state after evaluating the arguments, that
// may have an impact on the state of the current method.
InliningState state = new InliningState(
function, returnElement, returnType, elements, stack, localsHandler);
LocalsHandler newLocalsHandler = new LocalsHandler.from(localsHandler);
newLocalsHandler.closureData =
compiler.closureToClassMapper.computeClosureToClassMapping(
function, function.parseNode(compiler), elements);
int argumentIndex = 0;
if (isInstanceMember) {
newLocalsHandler.updateLocal(newLocalsHandler.closureData.thisElement,
compiledArguments[argumentIndex++]);
}
if (function.isConstructor()) {
ClassElement enclosing = function.getEnclosingClass();
if (backend.needsRti(enclosing)) {
assert(currentNode is NewExpression);
InterfaceType type = elements.getType(currentNode);
Link<DartType> typeVariable = enclosing.typeVariables;
type.typeArguments.forEach((DartType argument) {
HInstruction instruction =
analyzeTypeArgument(argument, currentNode);
newLocalsHandler.updateLocal(typeVariable.head.element, instruction);
typeVariable = typeVariable.tail;
});
while (!typeVariable.isEmpty) {
newLocalsHandler.updateLocal(typeVariable.head.element,
graph.addConstantNull(constantSystem));
typeVariable = typeVariable.tail;
}
}
}
// Check the type of the arguments. This must be done after setting up the
// type variables in the [localsHandler] because the checked types may
// contain type variables.
FunctionSignature signature = function.computeSignature(compiler);
signature.orderedForEachParameter((Element parameter) {
HInstruction argument = compiledArguments[argumentIndex++];
newLocalsHandler.updateLocal(parameter, argument);
potentiallyCheckType(argument, parameter.computeType(compiler));
});
// TODO(kasperl): Bad smell. We shouldn't be constructing elements here.
returnElement = new ElementX(const SourceString("result"),
ElementKind.VARIABLE,
function);
newLocalsHandler.updateLocal(returnElement,
graph.addConstantNull(constantSystem));
elements = compiler.enqueuer.resolution.getCachedElements(function);
assert(elements != null);
returnType = signature.returnType;
stack = <HInstruction>[];
inliningStack.add(state);
localsHandler = newLocalsHandler;
return state;
}
void leaveInlinedMethod(InliningState state) {
InliningState poppedState = inliningStack.removeLast();
assert(state == poppedState);
elements = state.oldElements;
stack.add(localsHandler.readLocal(returnElement));
returnElement = state.oldReturnElement;
returnType = state.oldReturnType;
assert(stack.length == 1);
state.oldStack.add(stack[0]);
stack = state.oldStack;
localsHandler = state.oldLocalsHandler;
}
/**
* Try to inline [element] within the currect context of the
* builder. The insertion point is the state of the builder.
*/
bool tryInlineMethod(Element element,
Selector selector,
Link<Node> argumentsNodes,
List<HInstruction> providedArguments,
Node currentNode) {
// We cannot inline a method from a deferred library into a method
// which isn't deferred.
// TODO(ahe): But we should still inline into the same
// connected-component of the deferred library.
if (compiler.deferredLoadTask.isDeferred(element)) return false;
if (compiler.disableInlining) return false;
// Ensure that [element] is an implementation element.
element = element.implementation;
// TODO(floitsch): we should be able to inline inside lazy initializers.
if (!currentElement.isFunction()) return false;
// TODO(floitsch): find a cleaner way to know if the element is a function
// containing nodes.
// [PartialFunctionElement]s are [FunctionElement]s that have [Node]s.
if (element is !PartialFunctionElement) return false;
// TODO(ngeoffray): try to inline generative constructors. They
// don't have any body, which make it more difficult.
if (element.isGenerativeConstructor()) return false;
if (inliningStack.length > MAX_INLINING_DEPTH) return false;
// Don't inline recursive calls. We use the same elements for the inlined
// functions and would thus clobber our local variables.
// Use [:element.declaration:] since [work.element] is always a declaration.
if (currentElement == element.declaration) return false;
for (int i = 0; i < inliningStack.length; i++) {
if (inliningStack[i].function == element) return false;
}
PartialFunctionElement function = element;
bool canBeInlined = backend.canBeInlined[function];
if (canBeInlined == false) return false;
assert(selector != null || Elements.isStaticOrTopLevel(element));
if (selector != null && !selector.applies(function, compiler)) return false;
// Don't inline operator== methods if the parameter can be null.
if (element.name == const SourceString('==')) {
if (element.getEnclosingClass() != compiler.objectClass
&& providedArguments[1].canBeNull()) {
return false;
}
}
FunctionExpression functionExpression = function.parseNode(compiler);
TreeElements newElements =
compiler.enqueuer.resolution.getCachedElements(function);
if (newElements == null) {
compiler.internalError("Element not resolved: $function");
}
if (canBeInlined == null) {
canBeInlined = InlineWeeder.canBeInlined(functionExpression, newElements);
backend.canBeInlined[function] = canBeInlined;
if (!canBeInlined) return false;
}
// We cannot inline methods with type variables in the signature in checked
// mode, because we currently do not have access to the type variables
// through the locals.
// TODO(karlklose): remove this and enable inlining of these methods.
if (compiler.enableTypeAssertions &&
element.computeType(compiler).containsTypeVariables) {
return false;
}
assert(canBeInlined);
InliningState state = enterInlinedMethod(
function, selector, argumentsNodes, providedArguments, currentNode);
// Add an explicit null check on the receiver. We use [element]
// to get the same name in the NoSuchMethodError message as if we had
// called it.
if (element.isInstanceMember()
&& (selector.mask == null || selector.mask.isNullable)) {
addWithPosition(
new HFieldGet(element, providedArguments[0]), currentNode);
}
inlinedFrom(element, () {
functionExpression.body.accept(this);
});
leaveInlinedMethod(state);
return true;
}
inlinedFrom(Element element, f()) {
assert(element is FunctionElement || element is VariableElement);
return compiler.withCurrentElement(element, () {
sourceElementStack.add(element);
var result = f();
sourceElementStack.removeLast();
return result;
});
}
/**
* Documentation wanted -- johnniwinther
*
* Invariant: [constructor] and [constructors] must all be implementation
* elements.
*/
void inlineSuperOrRedirect(FunctionElement constructor,
Selector selector,
Link<Node> arguments,
List<FunctionElement> constructors,
Map<Element, HInstruction> fieldValues,
FunctionElement inlinedFromElement,
Node callNode) {
constructor = constructor.implementation;
compiler.withCurrentElement(constructor, () {
constructors.add(constructor);
List<HInstruction> compiledArguments = new List<HInstruction>();
bool succeeded =
inlinedFrom(inlinedFromElement,
() => addStaticSendArgumentsToList(selector,
arguments,
constructor,
compiledArguments));
if (!succeeded) {
// Non-matching super and redirects are compile-time errors and thus
// checked by the resolver.
compiler.internalError(
"Parameters and arguments didn't match for super/redirect call",
element: constructor);
}
ClassElement superclass = constructor.getEnclosingClass();
if (backend.needsRti(superclass)) {
// If [superclass] needs RTI, we have to give a value to its
// type parameters. Those values are in the [supertype]
// declaration of [subclass].
ClassElement subclass = inlinedFromElement.getEnclosingClass();
InterfaceType supertype = subclass.supertype;
Link<DartType> typeVariables = superclass.typeVariables;
supertype.typeArguments.forEach((DartType argument) {
localsHandler.updateLocal(typeVariables.head.element,
analyzeTypeArgument(argument, callNode));
typeVariables = typeVariables.tail;
});
// If the supertype is a raw type, we need to set to null the
// type variables.
assert(typeVariables.isEmpty
|| superclass.typeVariables == typeVariables);
while (!typeVariables.isEmpty) {
localsHandler.updateLocal(typeVariables.head.element,
graph.addConstantNull(constantSystem));
typeVariables = typeVariables.tail;
}
}
inlinedFrom(constructor, () {
buildFieldInitializers(constructor.enclosingElement.implementation,
fieldValues);
});
int index = 0;
FunctionSignature params = constructor.computeSignature(compiler);
params.orderedForEachParameter((Element parameter) {
HInstruction argument = compiledArguments[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);
// Don't forget to update the field, if the parameter is of the
// form [:this.x:].
if (parameter.kind == ElementKind.FIELD_PARAMETER) {
FieldParameterElement fieldParameterElement = parameter;
fieldValues[fieldParameterElement.fieldElement] = argument;
}
});
// Build the initializers in the context of the new constructor.
TreeElements oldElements = elements;
if (constructor.isForwardingConstructor) {
constructor = constructor.targetConstructor;
}
elements =
compiler.enqueuer.resolution.getCachedElements(constructor);
ClosureClassMap oldClosureData = localsHandler.closureData;
Node node = constructor.parseNode(compiler);
ClosureClassMap newClosureData =
compiler.closureToClassMapper.computeClosureToClassMapping(
constructor, node, elements);
// The [:this:] element now refers to the one in the new closure
// data, that is the [:this:] of the super constructor. We
// update the element to refer to the current [:this:].
localsHandler.updateLocal(newClosureData.thisElement,
localsHandler.readThis());
localsHandler.closureData = newClosureData;
params.orderedForEachParameter((Element parameterElement) {
if (elements.isParameterChecked(parameterElement)) {
addParameterCheckInstruction(parameterElement);
}
});
localsHandler.enterScope(node, constructor);
buildInitializers(constructor, constructors, fieldValues);
localsHandler.closureData = oldClosureData;
elements = oldElements;
});
}
/**
* Run through the initializers and inline all field initializers. Recursively
* inlines super initializers.
*
* The constructors of the inlined initializers is added to [constructors]
* with sub constructors having a lower index than super constructors.
*
* Invariant: The [constructor] and elements in [constructors] must all be
* implementation elements.
*/
void buildInitializers(FunctionElement constructor,
List<FunctionElement> constructors,
Map<Element, HInstruction> fieldValues) {
assert(invariant(constructor, constructor.isImplementation));
FunctionExpression functionNode = constructor.parseNode(compiler);
bool foundSuperOrRedirect = false;
if (functionNode.initializers != null) {
Link<Node> initializers = functionNode.initializers.nodes;
for (Link<Node> link = initializers; !link.isEmpty; link = link.tail) {
assert(link.head is Send);
if (link.head is !SendSet) {
// A super initializer or constructor redirection.
Send call = link.head;
assert(Initializers.isSuperConstructorCall(call) ||
Initializers.isConstructorRedirect(call));
FunctionElement target = elements[call];
Selector selector = elements.getSelector(call);
Link<Node> arguments = call.arguments;
inlineSuperOrRedirect(target,
selector,
arguments,
constructors,
fieldValues,
constructor,
call);
foundSuperOrRedirect = true;
} else {
// A field initializer.
SendSet init = link.head;
Link<Node> arguments = init.arguments;
assert(!arguments.isEmpty && arguments.tail.isEmpty);
inlinedFrom(constructor, () {
visit(arguments.head);
});
fieldValues[elements[init]] = pop();
}
}
}
if (!foundSuperOrRedirect) {
// No super initializer found. Try to find the default constructor if
// the class is not Object.
ClassElement enclosingClass = constructor.getEnclosingClass();
ClassElement superClass = enclosingClass.superclass;
if (!enclosingClass.isObject(compiler)) {
assert(superClass != null);
assert(superClass.resolutionState == STATE_DONE);
Selector selector =
new Selector.callDefaultConstructor(enclosingClass.getLibrary());
// TODO(johnniwinther): Should we find injected constructors as well?
FunctionElement target = superClass.lookupConstructor(selector);
if (target == null) {
compiler.internalError("no default constructor available");
}
inlineSuperOrRedirect(target,
selector,
const Link<Node>(),
constructors,
fieldValues,
constructor,
functionNode);
}
}
}
/**
* Run through the fields of [cls] and add their potential
* initializers.
*
* Invariant: [classElement] must be an implementation element.
*/
void buildFieldInitializers(ClassElement classElement,
Map<Element, HInstruction> fieldValues) {
assert(invariant(classElement, classElement.isImplementation));
classElement.forEachInstanceField(
(ClassElement enclosingClass, Element member) {
compiler.withCurrentElement(member, () {
TreeElements definitions = compiler.analyzeElement(member);
Node node = member.parseNode(compiler);
SendSet assignment = node.asSendSet();
HInstruction value;
if (assignment == null) {
value = graph.addConstantNull(constantSystem);
} else {
Node right = assignment.arguments.head;
TreeElements savedElements = elements;
elements = definitions;
// In case the field initializer uses closures, run the
// closure to class mapper.
compiler.closureToClassMapper.computeClosureToClassMapping(
member, node, elements);
inlinedFrom(member, () => right.accept(this));
elements = savedElements;
value = pop();
}
fieldValues[member] = value;
});
},
includeBackendMembers: true,
includeSuperMembers: false);
}
/**
* Build the factory function corresponding to the constructor
* [functionElement]:
* - Initialize fields with the values of the field initializers of the
* current constructor and super constructors or constructors redirected
* to, starting from the current constructor.
* - Call the the constructor bodies, starting from the constructor(s) in the
* super class(es).
*
* Invariant: Both [classElement] and [functionElement] must be
* implementation elements.
*/
HGraph buildFactory(ClassElement classElement,
FunctionElement functionElement) {
assert(invariant(classElement, classElement.isImplementation));
assert(invariant(functionElement, functionElement.isImplementation));
FunctionExpression function = functionElement.parseNode(compiler);
// Note that constructors (like any other static function) do not need
// to deal with optional arguments. It is the callers job to provide all
// arguments as if they were positional.
// The initializer list could contain closures.
openFunction(functionElement, function);
Map<Element, HInstruction> fieldValues = new Map<Element, HInstruction>();
// Compile the possible initialization code for local fields and
// super fields.
buildFieldInitializers(classElement, fieldValues);
// Compile field-parameters such as [:this.x:].
FunctionSignature params = functionElement.computeSignature(compiler);
params.orderedForEachParameter((Element element) {
if (element.kind == ElementKind.FIELD_PARAMETER) {
// If the [element] is a field-parameter then
// initialize the field element with its value.
FieldParameterElement fieldParameterElement = element;
HInstruction parameterValue = localsHandler.readLocal(element);
fieldValues[fieldParameterElement.fieldElement] = parameterValue;
}
});
// Analyze the constructor and all referenced constructors and collect
// initializers and constructor bodies.
List<FunctionElement> constructors = <FunctionElement>[functionElement];
buildInitializers(functionElement, constructors, fieldValues);
// Call the JavaScript constructor with the fields as argument.
List<HInstruction> constructorArguments = <HInstruction>[];
classElement.forEachInstanceField(
(ClassElement enclosingClass, Element member) {
constructorArguments.add(potentiallyCheckType(
fieldValues[member], member.computeType(compiler)));
},
includeBackendMembers: true,
includeSuperMembers: true);
InterfaceType type = classElement.computeType(compiler);
HType ssaType = new HType.nonNullExact(type, compiler);
HForeignNew newObject = new HForeignNew(classElement,
ssaType,
constructorArguments);
add(newObject);
// Create the runtime type information, if needed.
if (backend.needsRti(classElement)) {
List<HInstruction> rtiInputs = <HInstruction>[];
classElement.typeVariables.forEach((TypeVariableType typeVariable) {
rtiInputs.add(localsHandler.readLocal(typeVariable.element));
});
callSetRuntimeTypeInfo(classElement, rtiInputs, newObject);
}
// Generate calls to the constructor bodies.
for (int index = constructors.length - 1; index >= 0; index--) {
FunctionElement constructor = constructors[index];
assert(invariant(functionElement, constructor.isImplementation));
ConstructorBodyElement body = getConstructorBody(constructor);
if (body == null) continue;
List bodyCallInputs = <HInstruction>[];
bodyCallInputs.add(newObject);
TreeElements elements =
compiler.enqueuer.resolution.getCachedElements(constructor);
Node node = constructor.parseNode(compiler);
ClosureClassMap parameterClosureData =
compiler.closureToClassMapper.getMappingForNestedFunction(node);
FunctionSignature functionSignature = body.computeSignature(compiler);
// Provide the parameters to the generative constructor body.
functionSignature.orderedForEachParameter((parameter) {
// 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 parameter checks to the generative constructor
// body.
functionSignature.orderedForEachParameter((parameter) {
// If [parameter] is checked, we pass the already computed
// boolean to the constructor body.
if (elements.isParameterChecked(parameter)) {
Element fieldCheck =
parameterClosureData.parametersWithSentinel[parameter];
bodyCallInputs.add(localsHandler.readLocal(fieldCheck));
}
});
ClassElement currentClass = constructor.getEnclosingClass();
if (backend.needsRti(currentClass)) {
// If [currentClass] needs RTI, we add the type variables as
// parameters of the generative constructor body.
currentClass.typeVariables.forEach((DartType argument) {
bodyCallInputs.add(localsHandler.readLocal(argument.element));
});
}
// If there are locals that escape (ie mutated in closures), we
// pass the box to the constructor.
ClosureScope scopeData = parameterClosureData.capturingScopes[node];
if (scopeData != null) {
bodyCallInputs.add(localsHandler.readLocal(scopeData.boxElement));
}
// TODO(ahe): The constructor name is statically resolved. See
// SsaCodeGenerator.visitInvokeDynamicMethod. Is there a cleaner
// way to do this?
SourceString name =
new SourceString(backend.namer.getName(body.declaration));
// TODO(kasperl): This seems fishy. We shouldn't be inventing all
// these selectors. Maybe the resolver can do more of the work
// for us here?
LibraryElement library = body.getLibrary();
Selector selector = new Selector.call(
name, library, bodyCallInputs.length - 1);
HInvokeDynamic invoke =
new HInvokeDynamicMethod(selector, bodyCallInputs);
invoke.element = body;
add(invoke);
}
closeAndGotoExit(new HReturn(newObject));
return closeFunction();
}
void addParameterCheckInstruction(Element element) {
HInstruction check;
Element checkResultElement =
localsHandler.closureData.parametersWithSentinel[element];
if (currentElement.isGenerativeConstructorBody()) {
// A generative constructor body receives extra parameters that
// indicate if a parameter was passed to the factory.
check = addParameter(checkResultElement);
} else {
// This is the code we emit for a parameter that is being checked
// on whether it was given at value at the call site:
//
// foo([a = 42]) {
// if (?a) print('parameter passed $a');
// }
//
// foo([a = 42]) {
// var t1 = identical(a, sentinel);
// if (t1) a = 42;
// if (!t1) print('parameter passed ' + a);
// }
// Fetch the original default value of [element];
Constant constant = compileVariable(element);
HConstant defaultValue = constant == null
? graph.addConstantNull(constantSystem)
: graph.addConstant(constant);
// Emit the equality check with the sentinel.
HConstant sentinel = graph.addConstant(SentinelConstant.SENTINEL);
HInstruction operand = parameters[element];
check = new HIdentity(sentinel, operand);
add(check);
// If the check succeeds, we must update the parameter with the
// default value.
handleIf(element.parseNode(compiler),
() => stack.add(check),
() => localsHandler.updateLocal(element, defaultValue),
null);
// Create the instruction that parameter checks will use.
check = new HNot(check);
add(check);
}
localsHandler.updateLocal(checkResultElement, check);
}
/**
* Documentation wanted -- johnniwinther
*
* Invariant: [functionElement] must be the implementation element.
*/
void openFunction(Element element, Expression node) {
assert(invariant(element, element.isImplementation));
HBasicBlock block = graph.addNewBlock();
open(graph.entry);
localsHandler.startFunction(element, node);
close(new HGoto()).addSuccessor(block);
open(block);
// Add the type parameters of the class as parameters of this method. This
// must be done before adding the normal parameters, because their types
// may contain references to type variables.
var enclosing = element.enclosingElement;
if ((element.isConstructor() || element.isGenerativeConstructorBody())
&& backend.needsRti(enclosing)) {
enclosing.typeVariables.forEach((TypeVariableType typeVariable) {
HParameterValue param = addParameter(typeVariable.element);
localsHandler.directLocals[typeVariable.element] = param;
});
}
if (element is FunctionElement) {
FunctionElement functionElement = element;
FunctionSignature signature = functionElement.computeSignature(compiler);
signature.orderedForEachParameter((Element parameterElement) {
if (elements.isParameterChecked(parameterElement)) {
addParameterCheckInstruction(parameterElement);
}
});
// 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.
signature.orderedForEachParameter((Element parameterElement) {
if (element.isGenerativeConstructorBody()) {
ClosureScope scopeData =
localsHandler.closureData.capturingScopes[node];
if (scopeData != null
&& scopeData.capturedVariableMapping.containsKey(
parameterElement)) {
// The parameter will be a field in the box passed as the
// last parameter. So no need to have it.
return;
}
}
HInstruction newParameter = potentiallyCheckType(
localsHandler.directLocals[parameterElement],
parameterElement.computeType(compiler));
localsHandler.directLocals[parameterElement] = newParameter;
});
returnType = signature.returnType;
} else {
// Otherwise it is a lazy initializer which does not have parameters.
assert(element is VariableElement);
}
}
HInstruction buildTypeConversion(Compiler compiler, HInstruction original,
DartType type, int kind) {
if (type == null) return original;
if (type.kind == TypeKind.INTERFACE && !type.isMalformed && !type.isRaw) {
HType subtype = new HType.subtype(type, compiler);
if (type.isRaw) {
return new HTypeConversion(type, kind, subtype, original);
}
HInstruction representations = buildTypeArgumentRepresentations(type);
add(representations);
return new HTypeConversion.withTypeRepresentation(type, kind, subtype,
original, representations);
} else if (type.kind == TypeKind.TYPE_VARIABLE) {
HType subtype = original.instructionType;
HInstruction typeVariable = addTypeVariableReference(type);
return new HTypeConversion.withTypeRepresentation(type, kind, subtype,
original, typeVariable);
} else {
return original.convertType(compiler, type, kind);
}
}
HInstruction potentiallyCheckType(HInstruction original, DartType type,
{ int kind: HTypeConversion.CHECKED_MODE_CHECK }) {
if (!compiler.enableTypeAssertions) return original;
HInstruction other =
buildTypeConversion(compiler, original, type, kind);
if (other != original) add(other);
return other;
}
HGraph closeFunction() {
// TODO(kasperl): Make this goto an implicit return.
if (!isAborted()) closeAndGotoExit(new HGoto());
graph.finalize();
return graph;
}
HBasicBlock addNewBlock() {
HBasicBlock block = graph.addNewBlock();
// If adding a new block during building of an expression, it is due to
// conditional expressions or short-circuit logical operators.
return block;
}
void open(HBasicBlock block) {
block.open();
current = block;
lastOpenedBlock = block;
}
HBasicBlock close(HControlFlow end) {
HBasicBlock result = current;
current.close(end);
current = null;
return result;
}
HBasicBlock closeAndGotoExit(HControlFlow end) {
HBasicBlock result = current;
current.close(end);
current = null;
result.addSuccessor(graph.exit);
return result;
}
void goto(HBasicBlock from, HBasicBlock to) {
from.close(new HGoto());
from.addSuccessor(to);
}
bool isAborted() {
return _current == null;
}
/**
* Creates a new block, transitions to it from any current block, and
* opens the new block.
*/
HBasicBlock openNewBlock() {
HBasicBlock newBlock = addNewBlock();
if (!isAborted()) goto(current, newBlock);
open(newBlock);
return newBlock;
}
void add(HInstruction instruction) {
current.add(instruction);
}
void addWithPosition(HInstruction instruction, Node node) {
add(attachPosition(instruction, node));
}
void push(HInstruction instruction) {
add(instruction);
stack.add(instruction);
}
void pushWithPosition(HInstruction instruction, Node node) {
push(attachPosition(instruction, node));
}
HInstruction pop() {
return stack.removeLast();
}
void dup() {
stack.add(stack.last);
}
HInstruction popBoolified() {
HInstruction value = pop();
if (compiler.enableTypeAssertions) {
return potentiallyCheckType(
value,
compiler.boolClass.computeType(compiler),
kind: HTypeConversion.BOOLEAN_CONVERSION_CHECK);
}
HInstruction result = new HBoolify(value);
add(result);
return result;
}
HInstruction attachPosition(HInstruction target, Node node) {
target.sourcePosition = sourceFileLocationForBeginToken(node);
return target;
}
SourceFileLocation sourceFileLocationForBeginToken(Node node) =>
sourceFileLocationForToken(node, node.getBeginToken());
SourceFileLocation sourceFileLocationForEndToken(Node node) =>
sourceFileLocationForToken(node, node.getEndToken());
SourceFileLocation sourceFileLocationForToken(Node node, Token token) {
Element element = sourceElementStack.last;
// TODO(johnniwinther): remove the 'element.patch' hack.
if (element is FunctionElement) {
FunctionElement functionElement = element;
if (functionElement.patch != null) element = functionElement.patch;
}
Script script = element.getCompilationUnit().script;
SourceFile sourceFile = script.file;
SourceFileLocation location = new SourceFileLocation(sourceFile, token);
if (!location.isValid()) {
throw MessageKind.INVALID_SOURCE_FILE_LOCATION.message(
{'offset': token.charOffset,
'fileName': sourceFile.filename,
'length': sourceFile.text.length});
}
return location;
}
void visit(Node node) {
if (node != null) node.accept(this);
}
visitBlock(Block node) {
assert(!isAborted());
if (!isReachable) return; // This can only happen when inlining.
for (Link<Node> link = node.statements.nodes;
!link.isEmpty;
link = link.tail) {
visit(link.head);
if (!isReachable) {
// The block has been aborted by a return or a throw.
if (!stack.isEmpty) compiler.cancel('non-empty instruction stack');
return;
}
}
assert(!current.isClosed());
if (!stack.isEmpty) compiler.cancel('non-empty instruction stack');
}
visitClassNode(ClassNode node) {
compiler.internalError('visitClassNode should not be called', node: node);
}
visitExpressionStatement(ExpressionStatement node) {
assert(isReachable);
Throw throwExpression = node.expression.asThrow();
if (throwExpression != null && inliningStack.isEmpty) {
visit(throwExpression.expression);
handleInTryStatement();
closeAndGotoExit(new HThrow(pop()));
} else {
visit(node.expression);
pop();
}
}
/**
* Creates a new loop-header block. The previous [current] block
* is closed with an [HGoto] and replaced by the newly created block.
* Also notifies the locals handler that we're entering a loop.
*/
JumpHandler beginLoopHeader(Node node) {
assert(!isAborted());
HBasicBlock previousBlock = close(new HGoto());
JumpHandler jumpHandler = createJumpHandler(node, isLoopJump: true);
HBasicBlock loopEntry = graph.addNewLoopHeaderBlock(
jumpHandler.target,
jumpHandler.labels());
previousBlock.addSuccessor(loopEntry);
open(loopEntry);
localsHandler.beginLoopHeader(loopEntry);
return jumpHandler;
}
/**
* Ends the loop:
* - creates a new block and adds it as successor to the [branchBlock] and
* any blocks that end in break.
* - opens the new block (setting as [current]).
* - notifies the locals handler that we're exiting a loop.
* [savedLocals] are the locals from the end of the loop condition.
* [branchBlock] is the exit (branching) block of the condition. For the
* while and for loops this is at the top of the loop. For do-while it is
* the end of the body. It is null for degenerate do-while loops that have
* no back edge because they abort (throw/return/break in the body and have
* no continues).
*/
void endLoop(HBasicBlock loopEntry,
HBasicBlock branchBlock,
JumpHandler jumpHandler,
LocalsHandler savedLocals) {
HBasicBlock loopExitBlock = addNewBlock();
List<LocalsHandler> breakHandlers = <LocalsHandler>[];
// Collect data for the successors and the phis at each break.
jumpHandler.forEachBreak((HBreak breakInstruction, LocalsHandler locals) {
breakInstruction.block.addSuccessor(loopExitBlock);
breakHandlers.add(locals);
});
// The exit block is a successor of the loop condition if it is reached.
// We don't add the successor in the case of a while/for loop that aborts
// because the caller of endLoop will be wiring up a special empty else
// block instead.
if (branchBlock != null) {
branchBlock.addSuccessor(loopExitBlock);
}
// Update the phis at the loop entry with the current values of locals.
localsHandler.endLoop(loopEntry);
// Start generating code for the exit block.
open(loopExitBlock);
// Create a new localsHandler for the loopExitBlock with the correct phis.
if (!breakHandlers.isEmpty) {
if (branchBlock != null) {
// Add the values of the locals at the end of the condition block to
// the phis. These are the values that flow to the exit if the
// condition fails.
breakHandlers.add(savedLocals);
}
localsHandler = savedLocals.mergeMultiple(breakHandlers, loopExitBlock);
} else {
localsHandler = savedLocals;
}
}
HSubGraphBlockInformation wrapStatementGraph(SubGraph statements) {
if (statements == null) return null;
return new HSubGraphBlockInformation(statements);
}
HSubExpressionBlockInformation wrapExpressionGraph(SubExpression expression) {
if (expression == null) return null;
return new HSubExpressionBlockInformation(expression);
}
// For while loops, initializer and update are null.
// The condition function must return a boolean result.
// None of the functions must leave anything on the stack.
void handleLoop(Node loop,
void initialize(),
HInstruction condition(),
void update(),
void body()) {
// Generate:
// <initializer>
// loop-entry:
// if (!<condition>) goto loop-exit;
// <body>
// <updates>
// goto loop-entry;
// loop-exit:
localsHandler.startLoop(loop);
// The initializer.
SubExpression initializerGraph = null;
HBasicBlock startBlock;
if (initialize != null) {
HBasicBlock initializerBlock = openNewBlock();
startBlock = initializerBlock;
initialize();
assert(!isAborted());
initializerGraph =
new SubExpression(initializerBlock, current);
}
JumpHandler jumpHandler = beginLoopHeader(loop);
HLoopInformation loopInfo = current.loopInformation;
HBasicBlock conditionBlock = current;
if (startBlock == null) startBlock = conditionBlock;
HInstruction conditionInstruction = condition();
HBasicBlock conditionExitBlock =
close(new HLoopBranch(conditionInstruction));
SubExpression conditionExpression =
new SubExpression(conditionBlock, conditionExitBlock);
// Save the values of the local variables at the end of the condition
// block. These are the values that will flow to the loop exit if the
// condition fails.
LocalsHandler savedLocals = new LocalsHandler.from(localsHandler);
// The body.
HBasicBlock beginBodyBlock = addNewBlock();
conditionExitBlock.addSuccessor(beginBodyBlock);
open(beginBodyBlock);
localsHandler.enterLoopBody(loop);
body();
SubGraph bodyGraph = new SubGraph(beginBodyBlock, lastOpenedBlock);
HBasicBlock bodyBlock = current;
if (current != null) close(new HGoto());
SubExpression updateGraph;
bool loopIsDegenerate = !jumpHandler.hasAnyContinue() && bodyBlock == null;
if (!loopIsDegenerate) {
// Update.
// We create an update block, even when we are in a while loop. There the
// update block is the jump-target for continue statements. We could avoid
// the creation if there is no continue, but for now we always create it.
HBasicBlock updateBlock = addNewBlock();
List<LocalsHandler> continueHandlers = <LocalsHandler>[];
jumpHandler.forEachContinue((HContinue instruction,
LocalsHandler locals) {
instruction.block.addSuccessor(updateBlock);
continueHandlers.add(locals);
});
if (bodyBlock != null) {
continueHandlers.add(localsHandler);
bodyBlock.addSuccessor(updateBlock);
}
open(updateBlock);
localsHandler =
continueHandlers[0].mergeMultiple(continueHandlers, updateBlock);
HLabeledBlockInformation labelInfo;
List<LabelElement> labels = jumpHandler.labels();
TargetElement target = elements[loop];
if (!labels.isEmpty) {
beginBodyBlock.setBlockFlow(
new HLabeledBlockInformation(
new HSubGraphBlockInformation(bodyGraph),
jumpHandler.labels(),
isContinue: true),
updateBlock);
} else if (target != null && target.isContinueTarget) {
beginBodyBlock.setBlockFlow(
new HLabeledBlockInformation.implicit(
new HSubGraphBlockInformation(bodyGraph),
target,
isContinue: true),
updateBlock);
}
localsHandler.enterLoopUpdates(loop);
update();
HBasicBlock updateEndBlock = close(new HGoto());
// The back-edge completing the cycle.
updateEndBlock.addSuccessor(conditionBlock);
updateGraph = new SubExpression(updateBlock, updateEndBlock);
endLoop(conditionBlock, conditionExitBlock, jumpHandler, savedLocals);
conditionBlock.postProcessLoopHeader();
HLoopBlockInformation info =
new HLoopBlockInformation(
HLoopBlockInformation.loopType(loop),
wrapExpressionGraph(initializerGraph),
wrapExpressionGraph(conditionExpression),
wrapStatementGraph(bodyGraph),
wrapExpressionGraph(updateGraph),
conditionBlock.loopInformation.target,
conditionBlock.loopInformation.labels,
sourceFileLocationForBeginToken(loop),
sourceFileLocationForEndToken(loop));
startBlock.setBlockFlow(info, current);
loopInfo.loopBlockInformation = info;
} else {
// The body of the for/while loop always aborts, so there is no back edge.
// We turn the code into:
// if (condition) {
// body;
// } else {
// // We always create an empty else block to avoid critical edges.
// }
//
// If there is any break in the body, we attach a synthetic
// label to the if.
HBasicBlock elseBlock = addNewBlock();
open(elseBlock);
close(new HGoto());
// Pass the elseBlock as the branchBlock, because that's the block we go
// to just before leaving the 'loop'.
endLoop(conditionBlock, elseBlock, jumpHandler, savedLocals);
SubGraph elseGraph = new SubGraph(elseBlock, elseBlock);
// Remove the loop information attached to the header.
conditionBlock.loopInformation = null;
// Remove the [HLoopBranch] instruction and replace it with
// [HIf].
HInstruction condition = conditionExitBlock.last.inputs[0];
conditionExitBlock.addAtExit(new HIf(condition));
conditionExitBlock.addSuccessor(elseBlock);
conditionExitBlock.remove(conditionExitBlock.last);
HIfBlockInformation info =
new HIfBlockInformation(
wrapExpressionGraph(conditionExpression),
wrapStatementGraph(bodyGraph),
wrapStatementGraph(elseGraph));
conditionExitBlock.setBlockFlow(info, current);
HIf ifBlock = conditionExitBlock.last;
ifBlock.blockInformation = conditionExitBlock.blockFlow;
// If the body has any break, attach a synthesized label to the
// if block.
if (jumpHandler.hasAnyBreak()) {
TargetElement target = elements[loop];
LabelElement label = target.addLabel(null, 'loop');
label.setBreakTarget();
SubGraph labelGraph = new SubGraph(conditionBlock, current);
HLabeledBlockInformation labelInfo = new HLabeledBlockInformation(
new HSubGraphBlockInformation(labelGraph),
<LabelElement>[label]);
conditionBlock.setBlockFlow(labelInfo, current);
jumpHandler.forEachBreak((HBreak breakInstruction, _) {
HBasicBlock block = breakInstruction.block;
block.addAtExit(new HBreak.toLabel(label));
block.remove(breakInstruction);
});
}
}
jumpHandler.close();
}
visitFor(For node) {
assert(isReachable);
assert(node.body != null);
void buildInitializer() {
if (node.initializer == null) return;
Node initializer = node.initializer;
if (initializer != null) {
visit(initializer);
if (initializer.asExpression() != null) {
pop();
}
}
}
HInstruction buildCondition() {
if (node.condition == null) {
return graph.addConstantBool(true, constantSystem);
}
visit(node.condition);
return popBoolified();
}
void buildUpdate() {
for (Expression expression in node.update) {
visit(expression);
assert(!isAborted());
// The result of the update instruction isn't used, and can just
// be dropped.
HInstruction updateInstruction = pop();
}
}
void buildBody() {
visit(node.body);
}
handleLoop(node, buildInitializer, buildCondition, buildUpdate, buildBody);
}
visitWhile(While node) {
assert(isReachable);
HInstruction buildCondition() {
visit(node.condition);
return popBoolified();
}
handleLoop(node,
() {},
buildCondition,
() {},
() { visit(node.body); });
}
visitDoWhile(DoWhile node) {
assert(isReachable);
LocalsHandler savedLocals = new LocalsHandler.from(localsHandler);
localsHandler.startLoop(node);
JumpHandler jumpHandler = beginLoopHeader(node);
HLoopInformation loopInfo = current.loopInformation;
HBasicBlock loopEntryBlock = current;
HBasicBlock bodyEntryBlock = current;
TargetElement target = elements[node];
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(node);
visit(node.body);
// If there are no continues we could avoid the creation of the condition
// block. This could also lead to a block having multiple entries and exits.
HBasicBlock bodyExitBlock;
bool isAbortingBody = false;
if (current != null) {
bodyExitBlock = close(new HGoto());
} else {
isAbortingBody = true;
bodyExitBlock = lastOpenedBlock;
}
SubExpression conditionExpression;
bool loopIsDegenerate = isAbortingBody && !hasContinues;
if (!loopIsDegenerate) {
HBasicBlock conditionBlock = addNewBlock();
List<LocalsHandler> continueHandlers = <LocalsHandler>[];
jumpHandler.forEachContinue((HContinue instruction,
LocalsHandler locals) {
instruction.block.addSuccessor(conditionBlock);
continueHandlers.add(locals);
});
if (!isAbortingBody) {
bodyExitBlock.addSuccessor(conditionBlock);
}
if (!continueHandlers.isEmpty) {
if (!isAbortingBody) continueHandlers.add(localsHandler);
localsHandler =
savedLocals.mergeMultiple(continueHandlers, conditionBlock);
SubGraph bodyGraph = new SubGraph(bodyEntryBlock, bodyExitBlock);
List<LabelElement> labels = jumpHandler.labels();
HSubGraphBlockInformation bodyInfo =
new HSubGraphBlockInformation(bodyGraph);
HLabeledBlockInformation info;
if (!labels.isEmpty) {
info = new HLabeledBlockInformation(bodyInfo, labels,
isContinue: true);
} else {
info = new HLabeledBlockInformation.implicit(bodyInfo, target,
isContinue: true);
}
bodyEntryBlock.setBlockFlow(info, conditionBlock);
}
open(conditionBlock);
visit(node.condition);
assert(!isAborted());
HInstruction conditionInstruction = popBoolified();
HBasicBlock conditionEndBlock = close(
new HLoopBranch(conditionInstruction, HLoopBranch.DO_WHILE_LOOP));
HBasicBlock avoidCriticalEdge = addNewBlock();
conditionEndBlock.addSuccessor(avoidCriticalEdge);
open(avoidCriticalEdge);
close(new HGoto());
avoidCriticalEdge.addSuccessor(loopEntryBlock); // The back-edge.
conditionExpression =
new SubExpression(conditionBlock, conditionEndBlock);
endLoop(loopEntryBlock, conditionEndBlock, jumpHandler, localsHandler);
loopEntryBlock.postProcessLoopHeader();
SubGraph bodyGraph = new SubGraph(loopEntryBlock, bodyExitBlock);
HLoopBlockInformation loopBlockInfo =
new HLoopBlockInformation(
HLoopBlockInformation.DO_WHILE_LOOP,
null,
wrapExpressionGraph(conditionExpression),
wrapStatementGraph(bodyGraph),
null,
loopEntryBlock.loopInformation.target,
loopEntryBlock.loopInformation.labels,
sourceFileLocationForBeginToken(node),
sourceFileLocationForEndToken(node));
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.
endLoop(loopEntryBlock, null, jumpHandler, localsHandler);
// Since the body of the loop has a break, we attach a synthesized label
// to the body.
SubGraph bodyGraph = new SubGraph(bodyEntryBlock, bodyExitBlock);
TargetElement target = elements[node];
LabelElement label = target.addLabel(null, 'loop');
label.setBreakTarget();
HLabeledBlockInformation info = new HLabeledBlockInformation(
new HSubGraphBlockInformation(bodyGraph), <LabelElement>[label]);
loopEntryBlock.setBlockFlow(info, current);
jumpHandler.forEachBreak((HBreak breakInstruction, _) {
HBasicBlock block = breakInstruction.block;
block.addAtExit(new HBreak.toLabel(label));
block.remove(breakInstruction);
});
}
}
jumpHandler.close();
}
visitFunctionExpression(FunctionExpression node) {
ClosureClassMap nestedClosureData =
compiler.closureToClassMapper.getMappingForNestedFunction(node);
assert(nestedClosureData != null);
assert(nestedClosureData.closureClassElement != null);
ClassElement closureClassElement =
nestedClosureData.closureClassElement;
FunctionElement callElement = nestedClosureData.callElement;
// TODO(ahe): This should be registered in codegen, not here.
compiler.enqueuer.codegen.addToWorkList(callElement, elements);
// TODO(ahe): This should be registered in codegen, not here.
compiler.enqueuer.codegen.registerInstantiatedClass(
closureClassElement, work.resolutionTree);
assert(!closureClassElement.hasLocalScopeMembers);
List<HInstruction> capturedVariables = <HInstruction>[];
closureClassElement.forEachBackendMember((Element member) {
// The backendMembers also contains the call method(s). We are only
// interested in the fields.
if (member.isField()) {
Element capturedLocal = nestedClosureData.capturedFieldMapping[member];
assert(capturedLocal != null);
capturedVariables.add(localsHandler.readLocal(capturedLocal));
}
});
HType type = new HType.nonNullExact(
compiler.functionClass.computeType(compiler),
compiler);
push(new HForeignNew(closureClassElement, type, capturedVariables));
}
visitFunctionDeclaration(FunctionDeclaration node) {
assert(isReachable);
visit(node.function);
localsHandler.updateLocal(elements[node], pop());
}
visitIdentifier(Identifier node) {
if (node.isThis()) {
stack.add(localsHandler.readThis());
} else {
compiler.internalError("SsaBuilder.visitIdentifier on non-this",
node: node);
}
}
visitIf(If node) {
assert(isReachable);
handleIf(node,
() => visit(node.condition),
() => visit(node.thenPart),
node.elsePart != null ? () => visit(node.elsePart) : null);
}
void handleIf(Node diagnosticNode,
void visitCondition(), void visitThen(), void visitElse()) {
SsaBranchBuilder branchBuilder = new SsaBranchBuilder(this, diagnosticNode);
branchBuilder.handleIf(visitCondition, visitThen, visitElse);
}
void visitLogicalAndOr(Send node, Operator op) {
SsaBranchBuilder branchBuilder = new SsaBranchBuilder(this, node);
branchBuilder.handleLogicalAndOrWithLeftNode(
node.receiver,
() { visit(node.argumentsNode); },
isAnd: (const SourceString("&&") == op.source));
}
void visitLogicalNot(Send node) {
assert(node.argumentsNode is Prefix);
visit(node.receiver);
HNot not = new HNot(popBoolified());
pushWithPosition(not, node);
}
void visitUnary(Send node, Operator op) {
if (node.isParameterCheck) {
Element element = elements[node.receiver];
Node function = element.enclosingElement.parseNode(compiler);
ClosureClassMap parameterClosureData =
compiler.closureToClassMapper.getMappingForNestedFunction(function);
Element fieldCheck =
parameterClosureData.parametersWithSentinel[element];
stack.add(localsHandler.readLocal(fieldCheck));
return;
}
assert(node.argumentsNode is Prefix);
visit(node.receiver);
assert(!identical(op.token.kind, PLUS_TOKEN));
HInstruction operand = pop();
// See if we can constant-fold right away. This avoids rewrites later on.
if (operand is HConstant) {
UnaryOperation operation = constantSystem.lookupUnary(op.source);
HConstant constant = operand;
Constant folded = operation.fold(constant.constant);
if (folded != null) {
stack.add(graph.addConstant(folded));
return;
}
}
pushInvokeDynamic(node, elements.getSelector(node), [operand]);
}
void visitBinary(HInstruction left,
Operator op,
HInstruction right,
Selector selector,
Send send) {
switch (op.source.stringValue) {
case "===":
pushWithPosition(new HIdentity(left, right), op);
return;
case "!==":
HIdentity eq = new HIdentity(left, right);
add(eq);
pushWithPosition(new HNot(eq), op);
return;
}
pushInvokeDynamic(send, selector, [left, right], location: op);
if (op.source.stringValue == '!=') {
pushWithPosition(new HNot(popBoolified()), op);
}
}
HInstruction generateInstanceSendReceiver(Send send) {
assert(Elements.isInstanceSend(send, elements));
if (send.receiver == null) {
return localsHandler.readThis();
}
visit(send.receiver);
return pop();
}
String getTargetName(ErroneousElement error, [String prefix]) {
String result = error.name.slowToString();
if (?prefix) {
result = '$prefix $result';
}
return result;
}
/**
* Returns a set of interceptor classes that contain the given
* [selector].
*/
void generateInstanceGetterWithCompiledReceiver(Send send,
Selector selector,
HInstruction receiver) {
assert(Elements.isInstanceSend(send, elements));
assert(selector.isGetter());
pushInvokeDynamic(send, selector, [receiver]);
}
void generateGetter(Send send, Element element) {
if (Elements.isStaticOrTopLevelField(element)) {
Constant value;
if (element.isField() && !element.isAssignable()) {
// A static final or const. Get its constant value and inline it if
// the value can be compiled eagerly.
value = compileVariable(element);
}
if (value != null) {
stack.add(graph.addConstant(value));
} else if (element.isField() && isLazilyInitialized(element)) {
HInstruction instruction = new HLazyStatic(element);
instruction.instructionType =
new HType.inferredTypeForElement(element, compiler);
push(instruction);
} else {
if (element.isGetter()) {
pushInvokeStatic(send, element, <HInstruction>[]);
} else {
// TODO(5346): Try to avoid the need for calling [declaration] before
// creating an [HStatic].
HInstruction instruction = new HStatic(element.declaration);
instruction.instructionType =
new HType.inferredTypeForElement(element, compiler);
push(instruction);
}
}
} else if (Elements.isInstanceSend(send, elements)) {
HInstruction receiver = generateInstanceSendReceiver(send);
generateInstanceGetterWithCompiledReceiver(
send, elements.getSelector(send), receiver);
} else if (Elements.isStaticOrTopLevelFunction(element)) {
// TODO(5346): Try to avoid the need for calling [declaration] before
// creating an [HStatic].
push(new HStatic(element.declaration));
// TODO(ahe): This should be registered in codegen.
compiler.enqueuer.codegen.registerGetOfStaticFunction(element);
} else if (Elements.isErroneousElement(element)) {
// An erroneous element indicates an unresolved static getter.
generateThrowNoSuchMethod(send,
getTargetName(element, 'get'),
argumentNodes: const Link<Node>());
} else {
stack.add(localsHandler.readLocal(element));
}
}
void generateInstanceSetterWithCompiledReceiver(Send send,
HInstruction receiver,
HInstruction value,
{Selector selector,
Node location}) {
assert(send == null || Elements.isInstanceSend(send, elements));
if (selector == null) {
assert(send != null);
selector = elements.getSelector(send);
}
if (location == null) {
assert(send != null);
location = send;
}
assert(selector.isSetter());
pushInvokeDynamic(location, selector, [receiver, value]);
pop();
stack.add(value);
}
void generateNonInstanceSetter(SendSet send,
Element element,
HInstruction value,
{Node location}) {
assert(send == null || !Elements.isInstanceSend(send, elements));
if (location == null) {
assert(send != null);
location = send;
}
if (Elements.isStaticOrTopLevelField(element)) {
if (element.isSetter()) {
pushInvokeStatic(location, element, <HInstruction>[value]);
pop();
} else {
value =
potentiallyCheckType(value, element.computeType(compiler));
addWithPosition(new HStaticStore(element, value), location);
}
stack.add(value);
} else if (Elements.isErroneousElement(element)) {
// An erroneous element indicates an unresolved static setter.
generateThrowNoSuchMethod(
location,
getTargetName(element, 'set'),
argumentNodes: (send == null ? const Link<Node>() : send.arguments));
} else {
stack.add(value);
// If the value does not already have a name, give it here.
if (value.sourceElement == null) {
value.sourceElement = element;
}
HInstruction checked =
potentiallyCheckType(value, element.computeType(compiler));
if (!identical(checked, value)) {
pop();
stack.add(checked);
}
localsHandler.updateLocal(element, checked);
}
}
HInstruction invokeInterceptor(Set<ClassElement> intercepted,
HInstruction receiver) {
HInterceptor interceptor = new HInterceptor(intercepted, receiver);
add(interceptor);
return interceptor;
}
HForeign createForeign(String code,
HType type,
List<HInstruction> inputs) {
return new HForeign(js.js.parseForeignJS(code), type, inputs);
}
HInstruction getRuntimeTypeInfo(HInstruction target) {
pushInvokeStatic(null, backend.getGetRuntimeTypeInfo(), [target]);
return pop();
}
// TODO(karlklose): change construction of the representations to be GVN'able
// (dartbug.com/7182).
HInstruction buildTypeArgumentRepresentations(DartType type) {
// Compute the representation of the type arguments, including access
// to the runtime type information for type variables as instructions.
if (type.kind == TypeKind.TYPE_VARIABLE) {
return new HLiteralList(<HInstruction>[addTypeVariableReference(type)]);
} else {
assert(type.element.isClass());
InterfaceType interface = type;
List<HInstruction> inputs = <HInstruction>[];
bool first = true;
List<String> templates = <String>[];
for (DartType argument in interface.typeArguments) {
templates.add(rti.getTypeRepresentation(argument, (variable) {
HInstruction runtimeType = addTypeVariableReference(variable);
inputs.add(runtimeType);
}));
}
String template = '[${templates.join(', ')}]';
HInstruction representation =
createForeign(template, HType.READABLE_ARRAY, inputs);
return representation;
}
}
visitOperatorSend(node) {
Operator op = node.selector;
if (const SourceString("[]") == op.source) {
visitDynamicSend(node);
} else if (const SourceString("&&") == op.source ||
const SourceString("||") == op.source) {
visitLogicalAndOr(node, op);
} else if (const SourceString("!") == op.source) {
visitLogicalNot(node);
} else if (node.argumentsNode is Prefix) {
visitUnary(node, op);
} else if (const SourceString("is") == op.source) {
visitIsSend(node);
} else if (const SourceString("as") == op.source) {
visit(node.receiver);
HInstruction expression = pop();
Node argument = node.arguments.head;
TypeAnnotation typeAnnotation = argument.asTypeAnnotation();
DartType type = elements.getType(typeAnnotation);
HInstruction converted = expression.convertType(
compiler, type, HTypeConversion.CAST_TYPE_CHECK);
if (converted != expression) add(converted);
stack.add(converted);
} else {
visit(node.receiver);
visit(node.argumentsNode);
var right = pop();
var left = pop();
visitBinary(left, op, right, elements.getSelector(node), node);
}
}
void visitIsSend(Send node) {
visit(node.receiver);
HInstruction expression = pop();
bool isNot = node.isIsNotCheck;
DartType type = elements.getType(node.typeAnnotationFromIsCheck);
if (type.isMalformed) {
String reasons = Types.fetchReasonsFromMalformedType(type);
if (compiler.enableTypeAssertions) {
generateMalformedSubtypeError(node, expression, type, reasons);
} else {
generateRuntimeError(node, '$type is malformed: $reasons');
}
} else {
HInstruction instruction = buildIsNode(node, type, expression);
if (isNot) {
add(instruction);
instruction = new HNot(instruction);
}
push(instruction);
}
}
HInstruction buildIsNode(Node node, DartType type, HInstruction expression) {
if (type.kind == TypeKind.TYPE_VARIABLE) {
HInstruction runtimeType = addTypeVariableReference(type);
Element helper = backend.getCheckSubtypeOfRuntimeType();
List<HInstruction> inputs = <HInstruction>[expression, runtimeType];
pushInvokeStatic(null, helper, inputs, HType.BOOLEAN);
HInstruction call = pop();
return new HIs(type, <HInstruction>[expression, call],
HIs.VARIABLE_CHECK);
} else if (RuntimeTypes.hasTypeArguments(type)) {
Element element = type.element;
Element helper = backend.getCheckSubtype();
HInstruction representations =
buildTypeArgumentRepresentations(type);
add(representations);
String operator =
backend.namer.operatorIs(backend.getImplementationClass(element));
HInstruction isFieldName = addConstantString(node, operator);
// TODO(karlklose): use [:null:] for [asField] if [element] does not
// have a subclass.
HInstruction asFieldName =
addConstantString(node, backend.namer.substitutionName(element));
List<HInstruction> inputs = <HInstruction>[expression,
isFieldName,
representations,
asFieldName];
pushInvokeStatic(node, helper, inputs, HType.BOOLEAN);
HInstruction call = pop();
return
new HIs(type, <HInstruction>[expression, call], HIs.COMPOUND_CHECK);
} else {
return new HIs(type, <HInstruction>[expression], HIs.RAW_CHECK);
}
}
void addDynamicSendArgumentsToList(Send node, List<HInstruction> list) {
Selector selector = elements.getSelector(node);
if (selector.namedArgumentCount == 0) {
addGenericSendArgumentsToList(node.arguments, list);
} else {
// Visit positional arguments and add them to the list.
Link<Node> arguments = node.arguments;
int positionalArgumentCount = selector.positionalArgumentCount;
for (int i = 0;
i < positionalArgumentCount;
arguments = arguments.tail, i++) {
visit(arguments.head);
list.add(pop());
}
// Visit named arguments and add them into a temporary map.
Map<SourceString, HInstruction> instructions =
new Map<SourceString, HInstruction>();
List<SourceString> namedArguments = selector.namedArguments;
int nameIndex = 0;
for (; !arguments.isEmpty; arguments = arguments.tail) {
visit(arguments.head);
instructions[namedArguments[nameIndex++]] = pop();
}
// Iterate through the named arguments to add them to the list
// of instructions, in an order that can be shared with
// selectors with the same named arguments.
List<SourceString> orderedNames = selector.getOrderedNamedArguments();
for (SourceString name in orderedNames) {
list.add(instructions[name]);
}
}
}
HInstruction handleConstantForOptionalParameter(Element parameter) {
Constant constant;
Element element = parameter.enclosingElement;
TreeElements calleeElements =
compiler.enqueuer.resolution.getCachedElements(element);
if (calleeElements.isParameterChecked(parameter)) {
constant = SentinelConstant.SENTINEL;
} else {
constant = compileConstant(parameter);
}
return graph.addConstant(constant);
}
/**
* Returns true if the arguments were compatible with the function signature.
*
* Invariant: [element] must be an implementation element.
*/
bool addStaticSendArgumentsToList(Selector selector,
Link<Node> arguments,
FunctionElement element,
List<HInstruction> list) {
assert(invariant(element, element.isImplementation));
HInstruction compileArgument(Node argument) {
visit(argument);
return pop();
}
if (element.isForwardingConstructor) {
element = element.targetConstructor;
}
return selector.addArgumentsToList(arguments,
list,
element,
compileArgument,
handleConstantForOptionalParameter,
compiler);
}
void addGenericSendArgumentsToList(Link<Node> link, List<HInstruction> list) {
for (; !link.isEmpty; link = link.tail) {
visit(link.head);
list.add(pop());
}
}
visitDynamicSend(Send node) {
Selector selector = elements.getSelector(node);
List<HInstruction> inputs = <HInstruction>[];
HInstruction receiver = generateInstanceSendReceiver(node);
inputs.add(receiver);
addDynamicSendArgumentsToList(node, inputs);
pushInvokeDynamic(node, selector, inputs);
if (selector.isSetter() || selector.isIndexSet()) {
pop();
stack.add(inputs.last);
}
}
visitClosureSend(Send node) {
Selector selector = elements.getSelector(node);
assert(node.receiver == null);
Element element = elements[node];
HInstruction closureTarget;
if (element == null) {
visit(node.selector);
closureTarget = pop();
} else {
assert(Elements.isLocal(element));
closureTarget = localsHandler.readLocal(element);
}
var inputs = <HInstruction>[];
inputs.add(closureTarget);
addDynamicSendArgumentsToList(node, inputs);
Selector closureSelector = new Selector.callClosureFrom(selector);
pushWithPosition(new HInvokeClosure(closureSelector, inputs), node);
}
void handleForeignJs(Send node) {
Link<Node> link = node.arguments;
// If the invoke is on foreign code, don't visit the first
// argument, which is the type, and the second argument,
// which is the foreign code.
if (link.isEmpty || link.tail.isEmpty) {
compiler.cancel('At least two arguments expected',
node: node.argumentsNode);
}
native.NativeBehavior nativeBehavior =
compiler.enqueuer.resolution.nativeEnqueuer.getNativeBehaviorOf(node);
List<HInstruction> inputs = <HInstruction>[];
addGenericSendArgumentsToList(link.tail.tail, inputs);
HType ssaType = new HType.fromNativeBehavior(nativeBehavior, compiler);
push(new HForeign(nativeBehavior.codeAst, ssaType, inputs,
effects: nativeBehavior.sideEffects));
return;
}
void handleForeignJsCurrentIsolate(Send node) {
if (!node.arguments.isEmpty) {
compiler.cancel(
'Too many arguments to JS_CURRENT_ISOLATE', node: node);
}
if (!compiler.hasIsolateSupport()) {
// If the isolate library is not used, we just generate code
// to fetch the current isolate.
String name = backend.namer.CURRENT_ISOLATE;
push(new HForeign(new js.LiteralString(name),
HType.UNKNOWN,
<HInstruction>[]));
} else {
// Call a helper method from the isolate library. The isolate
// library uses its own isolate structure, that encapsulates
// Leg's isolate.
Element element = compiler.isolateHelperLibrary.find(
const SourceString('_currentIsolate'));
if (element == null) {
compiler.cancel(
'Isolate library and compiler mismatch', node: node);
}
pushInvokeStatic(null, element, [], HType.UNKNOWN);
}
}
void handleForeignJsCallInIsolate(Send node) {
Link<Node> link = node.arguments;
if (!compiler.hasIsolateSupport()) {
// If the isolate library is not used, we just invoke the
// closure.
visit(link.tail.head);
Selector selector = new Selector.callClosure(0);
push(new HInvokeClosure(selector, <HInstruction>[pop()]));
} else {
// Call a helper method from the isolate library.
Element element = compiler.isolateHelperLibrary.find(
const SourceString('_callInIsolate'));
if (element == null) {
compiler.cancel(
'Isolate library and compiler mismatch', node: node);
}
List<HInstruction> inputs = <HInstruction>[];
addGenericSendArgumentsToList(link, inputs);
pushInvokeStatic(node, element, inputs, HType.UNKNOWN);
}
}
FunctionSignature handleForeignRawFunctionRef(Send node, String name) {
if (node.arguments.isEmpty || !node.arguments.tail.isEmpty) {
compiler.cancel('"$name" requires exactly one argument',
node: node.argumentsNode);
}
Node closure = node.arguments.head;
Element element = elements[closure];
if (!Elements.isStaticOrTopLevelFunction(element)) {
compiler.cancel(
'"$name" requires a static or top-level method',
node: closure);
}
FunctionElement function = element;
// TODO(johnniwinther): Try to eliminate the need to distinguish declaration
// and implementation signatures. Currently it is need because the
// signatures have different elements for parameters.
FunctionElement implementation = function.implementation;
FunctionSignature params = implementation.computeSignature(compiler);
if (params.optionalParameterCount != 0) {
compiler.cancel(
'"$name" does not handle closure with optional parameters',
node: closure);
}
visit(closure);
return params;
}
void handleForeignDartClosureToJs(Send node, String name) {
FunctionSignature params = handleForeignRawFunctionRef(node, name);
List<HInstruction> inputs = <HInstruction>[pop()];
String invocationName = backend.namer.invocationName(
new Selector.callClosure(params.requiredParameterCount));
push(new HForeign(js.js('#.$invocationName'),
HType.UNKNOWN,
inputs));
}
void handleForeignSetCurrentIsolate(Send node) {
if (node.arguments.isEmpty || !node.arguments.tail.isEmpty) {
compiler.cancel('Exactly one argument required',
node: node.argumentsNode);
}
visit(node.arguments.head);
String isolateName = backend.namer.CURRENT_ISOLATE;
SideEffects sideEffects = new SideEffects.empty();
sideEffects.setAllSideEffects();
push(new HForeign(js.js("$isolateName = #"),
HType.UNKNOWN,
<HInstruction>[pop()],
effects: sideEffects));
}
void handleForeignCreateIsolate(Send node) {
if (!node.arguments.isEmpty) {
compiler.cancel('Too many arguments',
node: node.argumentsNode);
}
String constructorName = backend.namer.isolateName;
push(new HForeign(js.js("new $constructorName()"),
HType.UNKNOWN,
<HInstruction>[]));
}
void handleForeignDartObjectJsConstructorFunction(Send node) {
if (!node.arguments.isEmpty) {
compiler.cancel('Too many arguments', node: node.argumentsNode);
}
String jsClassReference = backend.namer.isolateAccess(compiler.objectClass);
push(new HForeign(new js.LiteralString(jsClassReference),
HType.UNKNOWN,
<HInstruction>[]));
}
visitForeignSend(Send node) {
Selector selector = elements.getSelector(node);
SourceString name = selector.name;
if (name == const SourceString('JS')) {
handleForeignJs(node);
} else if (name == const SourceString('JS_CURRENT_ISOLATE')) {
handleForeignJsCurrentIsolate(node);
} else if (name == const SourceString('JS_CALL_IN_ISOLATE')) {
handleForeignJsCallInIsolate(node);
} else if (name == const SourceString('DART_CLOSURE_TO_JS')) {
handleForeignDartClosureToJs(node, 'DART_CLOSURE_TO_JS');
} else if (name == const SourceString('RAW_DART_FUNCTION_REF')) {
handleForeignRawFunctionRef(node, 'RAW_DART_FUNCTION_REF');
} else if (name == const SourceString('JS_SET_CURRENT_ISOLATE')) {
handleForeignSetCurrentIsolate(node);
} else if (name == const SourceString('JS_CREATE_ISOLATE')) {
handleForeignCreateIsolate(node);
} else if (name == const SourceString('JS_OPERATOR_IS_PREFIX')) {
stack.add(addConstantString(node, backend.namer.operatorIsPrefix()));
} else if (name == const SourceString('JS_OPERATOR_AS_PREFIX')) {
stack.add(addConstantString(node, backend.namer.operatorAsPrefix()));
} else if (name == const SourceString('JS_DART_OBJECT_CONSTRUCTOR')) {
handleForeignDartObjectJsConstructorFunction(node);
} else {
throw "Unknown foreign: ${selector}";
}
}
generateSuperNoSuchMethodSend(Send node,
Selector selector,
List<HInstruction> arguments) {
SourceString name = selector.name;
ClassElement cls = currentElement.getEnclosingClass();
Element element = cls.lookupSuperMember(Compiler.NO_SUCH_METHOD);
if (element.enclosingElement.declaration != compiler.objectClass) {
// Register the call as dynamic if [noSuchMethod] on the super
// class is _not_ the default implementation from [Object], in
// case the [noSuchMethod] implementation calls
// [JSInvocationMirror._invokeOn].
compiler.enqueuer.codegen.registerSelectorUse(selector);
}
Constant nameConstant = constantSystem.createString(
new DartString.literal(name.slowToString()), node);
String internalName = backend.namer.invocationName(selector);
Constant internalNameConstant =
constantSystem.createString(new DartString.literal(internalName), node);
Element createInvocationMirror = backend.getCreateInvocationMirror();
var argumentsInstruction = new HLiteralList(arguments);
add(argumentsInstruction);
var argumentNames = new List<HInstruction>();
for (SourceString argumentName in selector.namedArguments) {
Constant argumentNameConstant =
constantSystem.createString(new DartString.literal(
argumentName.slowToString()), node);
argumentNames.add(graph.addConstant(argumentNameConstant));
}
var argumentNamesInstruction = new HLiteralList(argumentNames);
add(argumentNamesInstruction);
Constant kindConstant =
constantSystem.createInt(selector.invocationMirrorKind);
pushInvokeStatic(null,
createInvocationMirror,
[graph.addConstant(nameConstant),
graph.addConstant(internalNameConstant),
graph.addConstant(kindConstant),
argumentsInstruction,
argumentNamesInstruction],
HType.UNKNOWN);
var inputs = <HInstruction>[pop()];
push(buildInvokeSuper(compiler.noSuchMethodSelector, element, inputs));
}
visitSend(Send node) {
Element element = elements[node];
if (element != null && identical(element, currentElement)) {
graph.isRecursiveMethod = true;
}
super.visitSend(node);
}
visitSuperSend(Send node) {
Selector selector = elements.getSelector(node);
Element element = elements[node];
if (Elements.isUnresolved(element)) {
List<HInstruction> arguments = <HInstruction>[];
if (!node.isPropertyAccess) {
addGenericSendArgumentsToList(node.arguments, arguments);
}
return generateSuperNoSuchMethodSend(node, selector, arguments);
}
List<HInstruction> inputs = <HInstruction>[];
if (node.isPropertyAccess) {
push(buildInvokeSuper(selector, element, inputs));
} else if (element.isFunction() || element.isGenerativeConstructor()) {
// TODO(5347): Try to avoid the need for calling [implementation] before
// calling [addStaticSendArgumentsToList].
FunctionElement function = element.implementation;
bool succeeded = addStaticSendArgumentsToList(selector, node.arguments,
function, inputs);
if (!succeeded) {
generateWrongArgumentCountError(node, element, node.arguments);
} else {
push(buildInvokeSuper(selector, element, inputs));
}
} else {
HInstruction target = buildInvokeSuper(selector, element, inputs);
add(target);
inputs = <HInstruction>[target];
addDynamicSendArgumentsToList(node, inputs);
Selector closureSelector = new Selector.callClosureFrom(selector);
push(new HInvokeClosure(closureSelector, inputs));
}
}
/**
* Generate code to extract the type arguments from the object, substitute
* them as an instance of the type we are testing against (if necessary), and
* extract the type argument by the index of the variable in the list of type
* variables for that class.
*/
HInstruction readTypeVariable(ClassElement cls,
TypeVariableElement variable) {
assert(currentElement.isInstanceMember());
int index = RuntimeTypes.getTypeVariableIndex(variable);
String substitutionNameString = backend.namer.substitutionName(cls);
HInstruction substitutionName = graph.addConstantString(
new LiteralDartString(substitutionNameString), null, constantSystem);
HInstruction target = localsHandler.readThis();
HInstruction substitution = createForeign('#[#]', HType.UNKNOWN,
<HInstruction>[target, substitutionName]);
add(substitution);
pushInvokeStatic(null,
backend.getGetRuntimeTypeArgument(),
[target,
substitution,
graph.addConstantInt(index, constantSystem)],
HType.UNKNOWN);
return pop();
}
/**
* Helper to create an instruction that gets the value of a type variable.
*/
HInstruction addTypeVariableReference(TypeVariableType type) {
Element member = currentElement;
bool isClosure = member.enclosingElement.isClosure();
if (isClosure) {
ClosureClassElement closureClass = member.enclosingElement;
member = closureClass.methodElement;
member = member.getOutermostEnclosingMemberOrTopLevel();
}
if (isClosure && member.isFactoryConstructor()) {
// The type variable is used from a closure in a factory constructor. The
// value of the type argument is stored as a local on the closure itself.
return localsHandler.readLocal(type.element);
} else if (member.isConstructor() ||
member.isGenerativeConstructorBody() ||
member.isField()) {
// The type variable is stored in a parameter of the method.
return localsHandler.readLocal(type.element);
} else if (member.isInstanceMember()) {
// The type variable is stored on the object.
return readTypeVariable(member.getEnclosingClass(),
type.element);
} else {
// TODO(ngeoffray): Match the VM behavior and throw an
// exception at runtime.
compiler.cancel('Unimplemented unresolved type variable',
element: type.element);
}
}
/**
* Documentation wanted -- johnniwinther
*
* Invariant: [argument] must not be malformed in checked mode.
*/
HInstruction analyzeTypeArgument(DartType argument, Node currentNode) {
assert(invariant(currentNode,
!compiler.enableTypeAssertions || !argument.isMalformed,
message: '$argument is malformed in checked mode'));
if (argument == compiler.types.dynamicType || argument.isMalformed) {
// Represent [dynamic] as [null].
return graph.addConstantNull(constantSystem);
}
List<HInstruction> inputs = <HInstruction>[];
String template = rti.getTypeRepresentation(argument, (variable) {
inputs.add(addTypeVariableReference(variable));
});
HInstruction result = createForeign(template, HType.STRING, inputs);
add(result);
return result;
}
void handleListConstructor(InterfaceType type,
Node currentNode,
HInstruction newObject) {
if (!backend.needsRti(type.element)) return;
if (!type.isRaw) {
List<HInstruction> inputs = <HInstruction>[];
type.typeArguments.forEach((DartType argument) {
inputs.add(analyzeTypeArgument(argument, currentNode));
});
callSetRuntimeTypeInfo(type.element, inputs, newObject);
}
}
void callSetRuntimeTypeInfo(ClassElement element,
List<HInstruction> rtiInputs,
HInstruction newObject) {
if (!backend.needsRti(element) || element.typeVariables.isEmpty) {
return;
}
HInstruction typeInfo = new HLiteralList(rtiInputs);
add(typeInfo);
// Set the runtime type information on the object.
Element typeInfoSetterElement = backend.getSetRuntimeTypeInfo();
pushInvokeStatic(
null,
typeInfoSetterElement,
<HInstruction>[newObject, typeInfo],
HType.UNKNOWN);
pop();
}
/**
* Documentation wanted -- johnniwinther
*
* Invariant: [type] must not be malformed in checked mode.
*/
handleNewSend(NewExpression node, InterfaceType type) {
Send send = node.send;
assert(invariant(send,
!compiler.enableTypeAssertions || !type.isMalformed,
message: '$type is malformed in checked mode'));
bool isListConstructor = false;
computeType(element) {
Element originalElement = elements[send];
if (Elements.isFixedListConstructorCall(
originalElement, send, compiler)) {
isListConstructor = true;
return HType.FIXED_ARRAY;
} else if (Elements.isGrowableListConstructorCall(
originalElement, send, compiler)) {
isListConstructor = true;
return HType.EXTENDABLE_ARRAY;
} else if (element.isGenerativeConstructor()) {
ClassElement cls = element.getEnclosingClass();
return new HType.nonNullExact(cls.thisType, compiler);
} else {
return new HType.inferredTypeForElement(originalElement, compiler);
}
}
Element constructor = elements[send];
Selector selector = elements.getSelector(send);
if (constructor.isForwardingConstructor) {
compiler.unimplemented('forwarded constructor in named mixin application',
element: constructor.getEnclosingClass());
}
if (compiler.enqueuer.resolution.getCachedElements(constructor) == null) {
compiler.internalError("Unresolved element: $constructor", node: send);
}
FunctionElement functionElement = constructor;
constructor = functionElement.redirectionTarget;
final bool isSymbolConstructor =
functionElement == compiler.symbolConstructor;
if (isSymbolConstructor) {
constructor = compiler.symbolValidatedConstructor;
assert(invariant(send, constructor != null,
message: 'Constructor Symbol.validated is missing'));
selector = compiler.symbolValidatedConstructorSelector;
assert(invariant(send, selector != null,
message: 'Constructor Symbol.validated is missing'));
}
var inputs = <HInstruction>[];
// TODO(5347): Try to avoid the need for calling [implementation] before
// calling [addStaticSendArgumentsToList].
bool succeeded = addStaticSendArgumentsToList(selector, send.arguments,
constructor.implementation,
inputs);
if (!succeeded) {
generateWrongArgumentCountError(send, constructor, send.arguments);
return;
}
ClassElement cls = constructor.getEnclosingClass();
if (cls.isAbstract(compiler) && constructor.isGenerativeConstructor()) {
generateAbstractClassInstantiationError(send, cls.name.slowToString());
return;
}
if (backend.needsRti(cls)) {
Link<DartType> typeVariable = cls.typeVariables;
type.typeArguments.forEach((DartType argument) {
inputs.add(analyzeTypeArgument(argument, send));
typeVariable = typeVariable.tail;
});
// Also add null to non-provided type variables to call the
// constructor with the right number of arguments.
while (!typeVariable.isEmpty) {
inputs.add(graph.addConstantNull(constantSystem));
typeVariable = typeVariable.tail;
}
}
if (constructor.isFactoryConstructor() && !type.typeArguments.isEmpty) {
compiler.enqueuer.codegen.registerFactoryWithTypeArguments(elements);
}
HType elementType = computeType(constructor);
pushInvokeStatic(node, constructor, inputs, elementType);
HInstruction newInstance = stack.last;
// The List constructor forwards to a Dart static method that does
// not know about the type argument. Therefore we special case
// this constructor to have the setRuntimeTypeInfo called where
// the 'new' is done.
if (isListConstructor && backend.needsRti(compiler.listClass)) {
handleListConstructor(type, send, newInstance);
}
}
visitStaticSend(Send node) {
Selector selector = elements.getSelector(node);
Element element = elements[node];
if (element.isForeign(compiler)) {
visitForeignSend(node);
return;
}
if (element.isErroneous()) {
generateThrowNoSuchMethod(node,
getTargetName(element),
argumentNodes: node.arguments);
return;
}
if (identical(element, compiler.assertMethod)
&& !compiler.enableUserAssertions) {
stack.add(graph.addConstantNull(constantSystem));
return;
}
compiler.ensure(!element.isGenerativeConstructor());
if (element.isFunction()) {
var inputs = <HInstruction>[];
// TODO(5347): Try to avoid the need for calling [implementation] before
// calling [addStaticSendArgumentsToList].
bool succeeded = addStaticSendArgumentsToList(selector, node.arguments,
element.implementation,
inputs);
if (!succeeded) {
generateWrongArgumentCountError(node, element, node.arguments);
return;
}
if (element == compiler.identicalFunction) {
pushWithPosition(new HIdentity(inputs[0], inputs[1]), node);
return;
}
pushInvokeStatic(node, element, inputs);
} else {
generateGetter(node, element);
List<HInstruction> inputs = <HInstruction>[pop()];
addDynamicSendArgumentsToList(node, inputs);
Selector closureSelector = new Selector.callClosureFrom(selector);
pushWithPosition(new HInvokeClosure(closureSelector, inputs), node);
}
}
HConstant addConstantString(Node node, String string) {
DartString dartString = new DartString.literal(string);
Constant constant = constantSystem.createString(dartString, node);
return graph.addConstant(constant);
}
visitTypeReferenceSend(Send node) {
Element element = elements[node];
if (element.isClass() || element.isTypedef()) {
// TODO(karlklose): add type representation
ConstantHandler handler = compiler.constantHandler;
Constant constant = handler.compileNodeWithDefinitions(node, elements);
stack.add(graph.addConstant(constant));
} else if (element.isTypeVariable()) {
HInstruction value =
addTypeVariableReference(element.computeType(compiler));
pushInvokeStatic(node,
backend.getRuntimeTypeToString(),
[value],
HType.STRING);
pushInvokeStatic(node,
backend.getCreateRuntimeType(),
[pop(), value]);
} else {
internalError('unexpected element kind $element', node: node);
}
if (node.isCall) {
// This send is of the form 'e(...)', where e is resolved to a type
// reference. We create a regular closure call on the result of the type
// reference instead of creating a NoSuchMethodError to avoid pulling it
// in if it is not used (e.g., in a try/catch).
HInstruction target = pop();
Selector selector = elements.getSelector(node);
List<HInstruction> inputs = <HInstruction>[target];
addDynamicSendArgumentsToList(node, inputs);
Selector closureSelector = new Selector.callClosureFrom(selector);
push(new HInvokeClosure(closureSelector, inputs));
}
}
visitGetterSend(Send node) {
generateGetter(node, elements[node]);
}
// TODO(antonm): migrate rest of SsaBuilder to internalError.
internalError(String reason, {Node node}) {
compiler.internalError(reason, node: node);
}
void generateError(Node node, String message, Element helper) {
HInstruction errorMessage = addConstantString(node, message);
pushInvokeStatic(node, helper, [errorMessage]);
}
void generateRuntimeError(Node node, String message) {
generateError(node, message, backend.getThrowRuntimeError());
}
void generateAbstractClassInstantiationError(Node node, String message) {
generateError(node,
message,
backend.getThrowAbstractClassInstantiationError());
}
void generateThrowNoSuchMethod(Node diagnosticNode,
String methodName,
{Link<Node> argumentNodes,
List<HInstruction> argumentValues,
List<String> existingArguments}) {
Element helper = backend.getThrowNoSuchMethod();
Constant receiverConstant =
constantSystem.createString(new DartString.empty(), diagnosticNode);
HInstruction receiver = graph.addConstant(receiverConstant);
DartString dartString = new DartString.literal(methodName);
Constant nameConstant =
constantSystem.createString(dartString, diagnosticNode);
HInstruction name = graph.addConstant(nameConstant);
if (argumentValues == null) {
argumentValues = <HInstruction>[];
argumentNodes.forEach((argumentNode) {
visit(argumentNode);
HInstruction value = pop();
argumentValues.add(value);
});
}
HInstruction arguments = new HLiteralList(argumentValues);
add(arguments);
HInstruction existingNamesList;
if (existingArguments != null) {
List<HInstruction> existingNames = <HInstruction>[];
for (String name in existingArguments) {
HInstruction nameConstant =
graph.addConstantString(new DartString.literal(name),
diagnosticNode, constantSystem);
existingNames.add(nameConstant);
}
existingNamesList = new HLiteralList(existingNames);
add(existingNamesList);
} else {
existingNamesList = graph.addConstantNull(constantSystem);
}
pushInvokeStatic(diagnosticNode,
helper,
[receiver, name, arguments, existingNamesList]);
}
/**
* Generate code to throw a [NoSuchMethodError] exception for calling a
* method with a wrong number of arguments or mismatching named optional
* arguments.
*/
void generateWrongArgumentCountError(Node diagnosticNode,
FunctionElement function,
Link<Node> argumentNodes) {
List<String> existingArguments = <String>[];
FunctionSignature signature = function.computeSignature(compiler);
signature.forEachParameter((Element parameter) {
existingArguments.add(parameter.name.slowToString());
});
generateThrowNoSuchMethod(diagnosticNode,
function.name.slowToString(),
argumentNodes: argumentNodes,
existingArguments: existingArguments);
}
void generateMalformedSubtypeError(Node node, HInstruction value,
DartType type, String reasons) {
HInstruction typeString = addConstantString(node, type.toString());
HInstruction reasonsString = addConstantString(node, reasons);
Element helper = backend.getThrowMalformedSubtypeError();
pushInvokeStatic(node, helper, [value, typeString, reasonsString]);
}
visitNewExpression(NewExpression node) {
Element element = elements[node.send];
final bool isSymbolConstructor = element == compiler.symbolConstructor;
if (!Elements.isErroneousElement(element)) {
FunctionElement function = element;
element = function.redirectionTarget;
}
if (Elements.isErroneousElement(element)) {
ErroneousElement error = element;
if (error.messageKind == MessageKind.CANNOT_FIND_CONSTRUCTOR) {
generateThrowNoSuchMethod(node.send,
getTargetName(error, 'constructor'),
argumentNodes: node.send.arguments);
} else {
Message message = error.messageKind.message(error.messageArguments);
generateRuntimeError(node.send, message.toString());
}
} else if (node.isConst()) {
// TODO(karlklose): add type representation
ConstantHandler handler = compiler.constantHandler;
Constant constant = handler.compileNodeWithDefinitions(node, elements);
stack.add(graph.addConstant(constant));
if (isSymbolConstructor) {
ConstructedConstant symbol = constant;
StringConstant stringConstant = symbol.fields.single;
String nameString = stringConstant.toDartString().slowToString();
compiler.enqueuer.codegen.registerConstSymbol(nameString, elements);
}
} else {
DartType type = elements.getType(node);
if (compiler.enableTypeAssertions && type.isMalformed) {
String reasons = Types.fetchReasonsFromMalformedType(type);
// TODO(johnniwinther): Change to resemble type errors from bounds check
// on type arguments.
generateRuntimeError(node, '$type is malformed: $reasons');
} else {
// TODO(karlklose): move this type registration to the codegen.
compiler.codegenWorld.instantiatedTypes.add(type);
handleNewSend(node, type);
}
}
}
void pushInvokeDynamic(Node node,
Selector selector,
List<HInstruction> arguments,
{Node location}) {
if (location == null) location = node;
// 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, Element element) {
bool isLength = selector.isGetter()
&& selector.name == const SourceString("length");
if (isLength || selector.isIndex()) {
DartType classType = element.getEnclosingClass().computeType(compiler);
HType type = new HType.nonNullExact(classType, compiler);
return type.isIndexable(compiler);
} else if (selector.isIndexSet()) {
DartType classType = element.getEnclosingClass().computeType(compiler);
HType type = new HType.nonNullExact(classType, compiler);
return type.isMutableIndexable(compiler);
} else {
return false;
}
}
bool isOptimizableOperation(Send node, Selector selector, Element element) {
ClassElement cls = element.getEnclosingClass();
if (isOptimizableOperationOnIndexable(selector, element)) return true;
if (!backend.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 == backend.jsArrayAdd
|| element == backend.jsArrayRemoveLast
|| element == backend.jsStringSplit) {
return true;
}
return false;
}
Element element = compiler.world.locateSingleElement(selector);
if (element != null
// TODO(ngeoffray): Handle non-send nodes.
&& (node.asSend() != null)
&& !(element.isGetter() && selector.isCall())
&& !(element.isFunction() && selector.isGetter())
&& !isOptimizableOperation(node, selector, element)) {
Send send = node.asSend();
Link<Node> nodes = send.isPropertyAccess ? null : send.arguments;
if (tryInlineMethod(element, selector, nodes, arguments, node)) {
return;
}
}
HInstruction receiver = arguments[0];
Set<ClassElement> interceptedClasses =
backend.getInterceptedClassesOn(selector.name);
List<HInstruction> inputs = <HInstruction>[];
bool isIntercepted = interceptedClasses != null;
if (isIntercepted) {
assert(!interceptedClasses.isEmpty);
inputs.add(invokeInterceptor(interceptedClasses, receiver));
}
inputs.addAll(arguments);
if (selector.isGetter()) {
bool hasGetter = compiler.world.hasAnyUserDefinedGetter(selector);
pushWithPosition(
new HInvokeDynamicGetter(selector, null, inputs, !hasGetter),
location);
} else if (selector.isSetter()) {
bool hasSetter = compiler.world.hasAnyUserDefinedSetter(selector);
pushWithPosition(
new HInvokeDynamicSetter(selector, null, inputs, !hasSetter),
location);
} else {
pushWithPosition(
new HInvokeDynamicMethod(selector, inputs, isIntercepted),
location);
}
}
void pushInvokeStatic(Node location,
Element element,
List<HInstruction> arguments,
[HType type = null]) {
if (tryInlineMethod(element, null, null, arguments, location)) {
return;
}
if (type == null) {
type = new HType.inferredReturnTypeForElement(element, compiler);
if (type.isUnknown()) {
// TODO(ngeoffray): Only do this if knowing the return type is
// useful.
type =
builder.backend.optimisticReturnTypesWithRecompilationOnTypeChange(
currentElement, element);
if (type == null) type = HType.UNKNOWN;
}
}
// TODO(5346): Try to avoid the need for calling [declaration] before
// creating an [HInvokeStatic].
HInstruction instruction =
new HInvokeStatic(element.declaration, arguments, type);
instruction.sideEffects = compiler.world.getSideEffectsOfElement(element);
if (location == null) {
push(instruction);
} else {
pushWithPosition(instruction, location);
}
}
HInstruction buildInvokeSuper(Selector selector,
Element element,
List<HInstruction> arguments) {
HInstruction receiver = localsHandler.readThis();
// TODO(5346): Try to avoid the need for calling [declaration] before
// creating an [HStatic].
List<HInstruction> inputs = <HInstruction>[];
Set<ClassElement> interceptedClasses =
backend.getInterceptedClassesOn(selector.name);
if (interceptedClasses != null) {
inputs.add(invokeInterceptor(interceptedClasses, receiver));
}
inputs.add(receiver);
inputs.addAll(arguments);
HInstruction instruction = new HInvokeSuper(
element,
currentNonClosureClass,
selector,
inputs,
isSetter: selector.isSetter() || selector.isIndexSet());
instruction.instructionType =
new HType.inferredReturnTypeForElement(element, compiler);
instruction.sideEffects = compiler.world.getSideEffectsOfElement(element);
return instruction;
}
void handleComplexOperatorSend(SendSet node,
HInstruction receiver,
Link<Node> arguments) {
HInstruction rhs;
if (node.isPrefix || node.isPostfix) {
rhs = graph.addConstantInt(1, constantSystem);
} else {
visit(arguments.head);
assert(arguments.tail.isEmpty);
rhs = pop();
}
visitBinary(receiver, node.assignmentOperator, rhs,
elements.getOperatorSelectorInComplexSendSet(node), node);
}
visitSendSet(SendSet node) {
Element element = elements[node];
if (!Elements.isUnresolved(element) && element.impliesType()) {
Identifier selector = node.selector;
generateThrowNoSuchMethod(node, selector.source.slowToString(),
argumentNodes: node.arguments);
return;
}
Operator op = node.assignmentOperator;
if (node.isSuperCall) {
HInstruction result;
List<HInstruction> setterInputs = <HInstruction>[];
if (identical(node.assignmentOperator.source.stringValue, '=')) {
addDynamicSendArgumentsToList(node, setterInputs);
result = setterInputs.last;
} else {
Element getter = elements[node.selector];
List<HInstruction> getterInputs = <HInstruction>[];
Link<Node> arguments = node.arguments;
if (node.isIndex) {
// If node is of the from [:super.foo[0] += 2:], the send has
// two arguments: the index and the left hand side. We get
// the index and add it as input of the getter and the
// setter.
visit(arguments.head);
arguments = arguments.tail;
HInstruction index = pop();
getterInputs.add(index);
setterInputs.add(index);
}
HInstruction getterInstruction;
Selector getterSelector =
elements.getGetterSelectorInComplexSendSet(node);
if (Elements.isUnresolved(getter)) {
generateSuperNoSuchMethodSend(
node,
getterSelector,
getterInputs);
getterInstruction = pop();
} else {
getterInstruction = buildInvokeSuper(
getterSelector, getter, getterInputs);
add(getterInstruction);
}
handleComplexOperatorSend(node, getterInstruction, arguments);
setterInputs.add(pop());
if (node.isPostfix) {
result = getterInstruction;
} else {
result = setterInputs.last;
}
}
Selector setterSelector = elements.getSelector(node);
if (Elements.isUnresolved(element)) {
generateSuperNoSuchMethodSend(
node, setterSelector, setterInputs);
pop();
} else {
add(buildInvokeSuper(setterSelector, element, setterInputs));
}
stack.add(result);
} else if (node.isIndex) {
if (const SourceString("=") == op.source) {
visitDynamicSend(node);
} else {
visit(node.receiver);
HInstruction receiver = pop();
Link<Node> arguments = node.arguments;
HInstruction index;
if (node.isIndex) {
visit(arguments.head);
arguments = arguments.tail;
index = pop();
}
pushInvokeDynamic(
node,
elements.getGetterSelectorInComplexSendSet(node),
[receiver, index]);
HInstruction getterInstruction = pop();
handleComplexOperatorSend(node, getterInstruction, arguments);
HInstruction value = pop();
pushInvokeDynamic(
node, elements.getSelector(node), [receiver, index, value]);
pop();
if (node.isPostfix) {
stack.add(getterInstruction);
} else {
stack.add(value);
}
}
} else if (const SourceString("=") == op.source) {
Link<Node> link = node.arguments;
assert(!link.isEmpty && link.tail.isEmpty);
if (Elements.isInstanceSend(node, elements)) {
HInstruction receiver = generateInstanceSendReceiver(node);
visit(link.head);
generateInstanceSetterWithCompiledReceiver(node, receiver, pop());
} else {
visit(link.head);
generateNonInstanceSetter(node, element, pop());
}
} else if (identical(op.source.stringValue, "is")) {
compiler.internalError("is-operator as SendSet", node: op);
} else {
assert(const SourceString("++") == op.source ||
const SourceString("--") == op.source ||
node.assignmentOperator.source.stringValue.endsWith("="));
// [receiver] is only used if the node is an instance send.
HInstruction receiver = null;
if (Elements.isInstanceSend(node, elements)) {
receiver = generateInstanceSendReceiver(node);
generateInstanceGetterWithCompiledReceiver(
node, elements.getGetterSelectorInComplexSendSet(node), receiver);
} else {
generateGetter(node, elements[node.selector]);
}
HInstruction getterInstruction = pop();
handleComplexOperatorSend(node, getterInstruction, node.arguments);
HInstruction value = pop();
assert(value != null);
if (Elements.isInstanceSend(node, elements)) {
assert(receiver != null);
generateInstanceSetterWithCompiledReceiver(node, receiver, value);
} else {
assert(receiver == null);
generateNonInstanceSetter(node, element, value);
}
if (node.isPostfix) {
pop();
stack.add(getterInstruction);
}
}
}
void visitLiteralInt(LiteralInt node) {
stack.add(graph.addConstantInt(node.value, constantSystem));
}
void visitLiteralDouble(LiteralDouble node) {
stack.add(graph.addConstantDouble(node.value, constantSystem));
}
void visitLiteralBool(LiteralBool node) {
stack.add(graph.addConstantBool(node.value, constantSystem));
}
void visitLiteralString(LiteralString node) {
stack.add(graph.addConstantString(node.dartString, node, constantSystem));
}
void visitStringJuxtaposition(StringJuxtaposition node) {
if (!node.isInterpolation) {
// This is a simple string with no interpolations.
stack.add(graph.addConstantString(node.dartString, node, constantSystem));
return;
}
StringBuilderVisitor stringBuilder = new StringBuilderVisitor(this, node);
stringBuilder.visit(node);
stack.add(stringBuilder.result);
}
void visitLiteralNull(LiteralNull node) {
stack.add(graph.addConstantNull(constantSystem));
}
visitNodeList(NodeList node) {
for (Link<Node> link = node.nodes; !link.isEmpty; link = link.tail) {
if (isAborted()) {
compiler.reportWarning(link.head, 'dead code');
} else {
visit(link.head);
}
}
}
void visitParenthesizedExpression(ParenthesizedExpression node) {
visit(node.expression);
}
visitOperator(Operator node) {
// Operators are intercepted in their surrounding Send nodes.
compiler.internalError('visitOperator should not be called', node: node);
}
visitCascade(Cascade node) {
visit(node.expression);
// Remove the result and reveal the duplicated receiver on the stack.
pop();
}
visitCascadeReceiver(CascadeReceiver node) {
visit(node.expression);
dup();
}
void handleInTryStatement() {
if (!inTryStatement) return;
HBasicBlock block = close(new HExitTry());
HBasicBlock newBlock = graph.addNewBlock();
block.addSuccessor(newBlock);
open(newBlock);
}
visitRethrow(Rethrow node) {
HInstruction exception = rethrowableException;
if (exception == null) {
exception = graph.addConstantNull(constantSystem);
compiler.internalError(
'rethrowableException should not be null', node: node);
}
handleInTryStatement();
closeAndGotoExit(new HThrow(exception, isRethrow: true));
}
visitReturn(Return node) {
if (identical(node.getBeginToken().stringValue, 'native')) {
native.handleSsaNative(this, node.expression);
return;
}
assert(invariant(node, !node.isRedirectingFactoryBody));
HInstruction value;
if (node.expression == null) {
value = graph.addConstantNull(constantSystem);
} else {
visit(node.expression);
value = pop();
value = potentiallyCheckType(value, returnType);
}
handleInTryStatement();
if (!inliningStack.isEmpty) {
localsHandler.updateLocal(returnElement, value);
} else {
closeAndGotoExit(attachPosition(new HReturn(value), node));
}
}
visitThrow(Throw node) {
visit(node.expression);
if (isReachable) {
handleInTryStatement();
push(new HThrowExpression(pop()));
isReachable = false;
}
}
visitTypeAnnotation(TypeAnnotation node) {
compiler.internalError('visiting type annotation in SSA builder',
node: node);
}
visitVariableDefinitions(VariableDefinitions node) {
assert(isReachable);
for (Link<Node> link = node.definitions.nodes;
!link.isEmpty;
link = link.tail) {
Node definition = link.head;
if (definition is Identifier) {
HInstruction initialValue = graph.addConstantNull(constantSystem);
localsHandler.updateLocal(elements[definition], initialValue);
} else {
assert(definition is SendSet);
visitSendSet(definition);
pop(); // Discard value.
}
}
}
visitLiteralList(LiteralList node) {
if (node.isConst()) {
ConstantHandler handler = compiler.constantHandler;
Constant constant = handler.compileNodeWithDefinitions(node, elements);
stack.add(graph.addConstant(constant));
return;
}
List<HInstruction> inputs = <HInstruction>[];
for (Link<Node> link = node.elements.nodes;
!link.isEmpty;
link = link.tail) {
visit(link.head);
inputs.add(pop());
}
push(new HLiteralList(inputs));
}
visitConditional(Conditional node) {
SsaBranchBuilder brancher = new SsaBranchBuilder(this, node);
brancher.handleConditional(() => visit(node.condition),
() => visit(node.thenExpression),
() => visit(node.elseExpression));
}
visitStringInterpolation(StringInterpolation node) {
StringBuilderVisitor stringBuilder = new StringBuilderVisitor(this, node);
stringBuilder.visit(node);
stack.add(stringBuilder.result);
}
visitStringInterpolationPart(StringInterpolationPart node) {
// The parts are iterated in visitStringInterpolation.
compiler.internalError('visitStringInterpolation should not be called',
node: node);
}
visitEmptyStatement(EmptyStatement node) {
// Do nothing, empty statement.
}
visitModifiers(Modifiers node) {
compiler.unimplemented('SsaBuilder.visitModifiers', node: node);
}
visitBreakStatement(BreakStatement node) {
assert(!isAborted());
handleInTryStatement();
TargetElement target = elements[node];
assert(target != null);
JumpHandler handler = jumpTargets[target];
assert(handler != null);
if (node.target == null) {
handler.generateBreak();
} else {
LabelElement label = elements[node.target];
handler.generateBreak(label);
}
}
visitContinueStatement(ContinueStatement node) {
handleInTryStatement();
TargetElement target = elements[node];
assert(target != null);
JumpHandler handler = jumpTargets[target];
assert(handler != null);
if (node.target == null) {
handler.generateContinue();
} else {
LabelElement label = elements[node.target];
assert(label != null);
handler.generateContinue(label);
}
}
/**
* 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 synthetized loop created for a switch statement with
* continue statements from simple switch statements.
*/
JumpHandler createJumpHandler(Statement node, {bool isLoopJump}) {
TargetElement element = elements[node];
if (element == null || !identical(element.statement, node)) {
// No breaks or continues to this node.
return new NullJumpHandler(compiler);
}
if (isLoopJump && node is SwitchStatement) {
// Create a special jump handler for loops created for switch statements
// with continue statements.
return new SwitchCaseJumpHandler(this, element, node);
}
return new JumpHandler(this, element);
}
visitForIn(ForIn node) {
// Generate a structure equivalent to:
// Iterator<E> $iter = <iterable>.iterator;
// while ($iter.moveNext()) {
// E <declaredIdentifier> = $iter.current;
// <body>
// }
// The iterator is shared between initializer, condition and body.
HInstruction iterator;
void buildInitializer() {
Selector selector = compiler.iteratorSelector;
visit(node.expression);
HInstruction receiver = pop();
pushInvokeDynamic(node, selector, [receiver]);
iterator = pop();
}
HInstruction buildCondition() {
Selector selector = compiler.moveNextSelector;
pushInvokeDynamic(node, selector, [iterator]);
return popBoolified();
}
void buildBody() {
Selector call = compiler.currentSelector;
pushInvokeDynamic(node, call, [iterator]);
Node identifier = node.declaredIdentifier;
Element variable = elements[identifier];
Selector selector = elements.getSelector(identifier);
HInstruction value = pop();
if (identifier.asSend() != null
&& Elements.isInstanceSend(identifier, elements)) {
HInstruction receiver = generateInstanceSendReceiver(identifier);
assert(receiver != null);
generateInstanceSetterWithCompiledReceiver(
null,
receiver,
value,
selector: selector,
location: identifier);
} else {
generateNonInstanceSetter(null, variable, value, location: identifier);
}
pop(); // Pop the value pushed by the setter call.
visit(node.body);
}
handleLoop(node, buildInitializer, buildCondition, () {}, buildBody);
}
visitLabel(Label node) {
compiler.internalError('SsaBuilder.visitLabel', node: node);
}
visitLabeledStatement(LabeledStatement node) {
Statement body = node.statement;
if (body is Loop || body is SwitchStatement) {
// Loops and switches handle their own labels.
visit(body);
return;
}
// Non-loop statements can only be break targets, not continue targets.
TargetElement targetElement = elements[body];
if (targetElement == null || !identical(targetElement.statement, body)) {
// Labeled statements with no element on the body have no breaks.
// A different target statement only happens if the body is itself
// a break or continue for a different target. In that case, this
// label is also always unused.
visit(body);
return;
}
LocalsHandler beforeLocals = new LocalsHandler.from(localsHandler);
assert(targetElement.isBreakTarget);
JumpHandler handler = new JumpHandler(this, targetElement);
// Introduce a new basic block.
HBasicBlock entryBlock = openNewBlock();
visit(body);
SubGraph bodyGraph = new SubGraph(entryBlock, lastOpenedBlock);
HBasicBlock joinBlock = graph.addNewBlock();
List<LocalsHandler> breakHandlers = <LocalsHandler>[];
handler.forEachBreak((HBreak breakInstruction, LocalsHandler locals) {
breakInstruction.block.addSuccessor(joinBlock);
breakHandlers.add(locals);
});
bool hasBreak = breakHandlers.length > 0;
if (!isAborted()) {
goto(current, joinBlock);
breakHandlers.add(localsHandler);
}
open(joinBlock);
localsHandler = beforeLocals.mergeMultiple(breakHandlers, joinBlock);
if (hasBreak) {
// There was at least one reachable break, so the label is needed.
entryBlock.setBlockFlow(
new HLabeledBlockInformation(new HSubGraphBlockInformation(bodyGraph),
handler.labels()),
joinBlock);
}
handler.close();
}
visitLiteralMap(LiteralMap node) {
if (node.isConst()) {
ConstantHandler handler = compiler.constantHandler;
Constant constant = handler.compileNodeWithDefinitions(node, elements);
stack.add(graph.addConstant(constant));
return;
}
List<HInstruction> inputs = <HInstruction>[];
for (Link<Node> link = node.entries.nodes;
!link.isEmpty;
link = link.tail) {
visit(link.head);
inputs.add(pop());
inputs.add(pop());
}
HLiteralList keyValuePairs = new HLiteralList(inputs);
add(keyValuePairs);
HType mapType = new HType.nonNullSubtype(
backend.mapLiteralClass.computeType(compiler), compiler);
pushInvokeStatic(node, backend.getMapMaker(), [keyValuePairs], mapType);
}
visitLiteralMapEntry(LiteralMapEntry node) {
visit(node.value);
visit(node.key);
}
visitNamedArgument(NamedArgument node) {
visit(node.expression);
}
Map<CaseMatch,Constant> buildSwitchCaseConstants(SwitchStatement node) {
Map<CaseMatch, Constant> constants = new Map<CaseMatch, Constant>();
// First check whether all case expressions are compile-time constants,
// and all have the same type that doesn't override operator==.
// TODO(lrn): Move the constant resolution to the resolver, so
// we can report an error before reaching the backend.
DartType firstConstantType = null;
bool failure = false;
for (SwitchCase switchCase in node.cases) {
for (Node labelOrCase in switchCase.labelsAndCases) {
if (labelOrCase is CaseMatch) {
CaseMatch match = labelOrCase;
Constant constant =
compiler.constantHandler.compileNodeWithDefinitions(
match.expression, elements, isConst: true);
if (firstConstantType == null) {
firstConstantType = constant.computeType(compiler);
if (nonPrimitiveTypeOverridesEquals(constant)) {
compiler.reportError(match.expression,
MessageKind.SWITCH_CASE_VALUE_OVERRIDES_EQUALS.error());
failure = true;
}
} else {
DartType constantType =
constant.computeType(compiler);
if (constantType != firstConstantType) {
compiler.reportError(match.expression,
MessageKind.SWITCH_CASE_TYPES_NOT_EQUAL.error());
failure = true;
}
}
constants[labelOrCase] = constant;
}
}
}
return constants;
}
visitSwitchStatement(SwitchStatement node) {
Map<CaseMatch,Constant> constants = buildSwitchCaseConstants(node);
// The switch case indices must match those computed in
// [SwitchCaseJumpHandler].
bool hasContinue = false;
Map<SwitchCase, int> caseIndex = new Map<SwitchCase, int>();
int switchIndex = 1;
bool hasDefault = false;
for (SwitchCase switchCase in node.cases) {
for (Node labelOrCase in switchCase.labelsAndCases) {
Node label = labelOrCase.asLabel();
if (label != null) {
LabelElement labelElement = elements[label];
if (labelElement != null && labelElement.isContinueTarget) {
hasContinue = true;
}
}
}
if (switchCase.isDefaultCase) {
hasDefault = true;
}
caseIndex[switchCase] = switchIndex;
switchIndex++;
}
if (!hasContinue) {
// If the switch statement has no switch cases targeted by continue
// statements we encode the switch statement directly.
buildSimpleSwitchStatement(node, constants);
} else {
buildComplexSwitchStatement(node, constants, caseIndex, hasDefault);
}
}
/**
* Builds a simple switch statement which does not handle uses of continue
* statements to labeled switch cases.
*/
void buildSimpleSwitchStatement(SwitchStatement node,
Map<CaseMatch, Constant> constants) {
JumpHandler jumpHandler = createJumpHandler(node, isLoopJump: false);
HInstruction buildExpression() {
visit(node.expression);
return pop();
}
Iterable<Constant> getConstants(SwitchCase switchCase) {
List<Constant> constantList = <Constant>[];
for (Node labelOrCase in switchCase.labelsAndCases) {
if (labelOrCase is CaseMatch) {
constantList.add(constants[labelOrCase]);
}
}
return constantList;
}
bool isDefaultCase(SwitchCase switchCase) {
return switchCase.isDefaultCase;
}
void buildSwitchCase(SwitchCase node) {
visit(node.statements);
}
handleSwitch(node,
jumpHandler,
buildExpression,
node.cases,
getConstants,
isDefaultCase,
buildSwitchCase);
jumpHandler.close();
}
/**
* Builds a switch statement that can handle arbitrary uses of continue
* statements to labeled switch cases.
*/
void buildComplexSwitchStatement(SwitchStatement node,
Map<CaseMatch, Constant> constants,
Map<SwitchCase, int> caseIndex,
bool hasDefault) {
// 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;
// }
// }
TargetElement switchTarget = elements[node];
HInstruction initialValue = graph.addConstantNull(constantSystem);
localsHandler.updateLocal(switchTarget, initialValue);
JumpHandler jumpHandler = createJumpHandler(node, isLoopJump: false);
var switchCases = node.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 = node.cases.nodes.toList()..add(null);
}
HInstruction buildExpression() {
visit(node.expression);
return pop();
}
Iterable<Constant> getConstants(SwitchCase switchCase) {
List<Constant> constantList = <Constant>[];
if (switchCase != null) {
for (Node labelOrCase in switchCase.labelsAndCases) {
if (labelOrCase is CaseMatch) {
constantList.add(constants[labelOrCase]);
}
}
}
return constantList;
}
bool isDefaultCase(SwitchCase switchCase) {
return switchCase == null || switchCase.isDefaultCase;
}
void buildSwitchCase(SwitchCase switchCase) {
if (switchCase != null) {
// Generate 'target = i; break;' for switch case i.
int index = caseIndex[switchCase];
HInstruction value = graph.addConstantInt(index, constantSystem);
localsHandler.updateLocal(switchTarget, value);
} else {
// Generate synthetic default case 'target = null; break;'.
HInstruction value = graph.addConstantNull(constantSystem);
localsHandler.updateLocal(switchTarget, value);
}
jumpTargets[switchTarget].generateBreak();
}
handleSwitch(node,
jumpHandler,
buildExpression,
switchCases,
getConstants,
isDefaultCase,
buildSwitchCase);
jumpHandler.close();
HInstruction buildCondition() =>
graph.addConstantBool(true, constantSystem);
void buildSwitch() {
HInstruction buildExpression() {
return localsHandler.readLocal(switchTarget);
}
Iterable<Constant> getConstants(SwitchCase switchCase) {
return <Constant>[constantSystem.createInt(caseIndex[switchCase])];
}
void buildSwitchCase(SwitchCase switchCase) {
visit(switchCase.statements);
if (!isAborted()) {
// Ensure that we break the loop if the case falls through. (This
// is only possible for the last case.)
jumpTargets[switchTarget].generateBreak();
}
}
// 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(node,
new NullJumpHandler(compiler),
buildExpression, node.cases, getConstants,
(_) => false, // No case is default.
buildSwitchCase);
}
void buildLoop() {
handleLoop(node,
() {},
buildCondition,
() {},
buildSwitch);
}
if (hasDefault) {
buildLoop();
} else {
// If the switch statement has no default case, surround the loop with
// a test of the target.
void buildCondition() {
push(createForeign('#', HType.BOOLEAN,
[localsHandler.readLocal(switchTarget)]));
}
handleIf(node, buildCondition, buildLoop, () => {});
}
}
/**
* Creates a switch statement.
*
* [jumpHandler] is the [JumpHandler] for the created switch statement.
* [buildExpression] creates the switch expression.
* [switchCases] must be either an [Iterable] of [SwitchCase] nodes or
* a [Link] or a [NodeList] of [SwitchCase] nodes.
* [getConstants] returns the set of constants for a switch case.
* [isDefaultCase] returns [:true:] if the provided switch case should be
* considered default for the created switch statement.
* [buildSwitchCase] creates the statements for the switch case.
*/
void handleSwitch(Node errorNode,
JumpHandler jumpHandler,
HInstruction buildExpression(),
var switchCases,
Iterable<Constant> getConstants(SwitchCase switchCase),
bool isDefaultCase(SwitchCase switchCase),
void buildSwitchCase(SwitchCase switchCase)) {
Map<CaseMatch, Constant> constants = new Map<CaseMatch, Constant>();
// TODO(ngeoffray): Handle switch-instruction in bailout code.
work.allowSpeculativeOptimization = false;
// Then build a switch structure.
HBasicBlock expressionStart = openNewBlock();
HInstruction expression = buildExpression();
if (switchCases.isEmpty) {
return;
}
HBasicBlock expressionEnd = current;
HSwitch switchInstruction = new HSwitch(<HInstruction>[expression]);
HBasicBlock expressionBlock = close(switchInstruction);
LocalsHandler savedLocals = localsHandler;
List<List<Constant>> matchExpressions = <List<Constant>>[];
List<HStatementInformation> statements = <HStatementInformation>[];
bool hasDefault = false;
Element getFallThroughErrorElement = backend.getFallThroughError();
HasNextIterator<Node> caseIterator =
new HasNextIterator<Node>(switchCases.iterator);
while (caseIterator.hasNext) {
SwitchCase switchCase = caseIterator.next();
List<Constant> caseConstants = <Constant>[];
HBasicBlock block = graph.addNewBlock();
for (Constant constant in getConstants(switchCase)) {
caseConstants.add(constant);
HConstant hConstant = graph.addConstant(constant);
switchInstruction.inputs.add(hConstant);
hConstant.usedBy.add(switchInstruction);
expressionBlock.addSuccessor(block);
}
matchExpressions.add(caseConstants);
if (isDefaultCase(switchCase)) {
// An HSwitch has n inputs and n+1 successors, the last being the
// default case.
expressionBlock.addSuccessor(block);
hasDefault = true;
}
open(block);
localsHandler = new LocalsHandler.from(savedLocals);
buildSwitchCase(switchCase);
if (!isAborted() && caseIterator.hasNext) {
pushInvokeStatic(switchCase, getFallThroughErrorElement, []);
HInstruction error = pop();
closeAndGotoExit(new HThrow(error));
}
statements.add(
new HSubGraphBlockInformation(new SubGraph(block, lastOpenedBlock)));
}
// Add a join-block if necessary.
// We create [joinBlock] early, and then go through the cases that might
// want to jump to it. In each case, if we add [joinBlock] as a successor
// of another block, we also add an element to [caseHandlers] that is used
// to create the phis in [joinBlock].
// If we never jump to the join block, [caseHandlers] will stay empty, and
// the join block is never added to the graph.
HBasicBlock joinBlock = new HBasicBlock();
List<LocalsHandler> caseHandlers = <LocalsHandler>[];
jumpHandler.forEachBreak((HBreak instruction, LocalsHandler locals) {
instruction.block.addSuccessor(joinBlock);
caseHandlers.add(locals);
});
jumpHandler.forEachContinue((HContinue instruction, LocalsHandler locals) {
assert(invariant(errorNode, false,
message: 'Continue cannot target a switch.'));
});
if (!isAborted()) {
current.close(new HGoto());
lastOpenedBlock.addSuccessor(joinBlock);
caseHandlers.add(localsHandler);
}
if (!hasDefault) {
// The current flow is only aborted if the switch has a default that
// aborts (all previous cases must abort, and if there is no default,
// it's possible to miss all the cases).
expressionEnd.addSuccessor(joinBlock);
caseHandlers.add(savedLocals);
}
assert(caseHandlers.length == joinBlock.predecessors.length);
if (caseHandlers.length != 0) {
graph.addBlock(joinBlock);
open(joinBlock);
if (caseHandlers.length == 1) {
localsHandler = caseHandlers[0];
} else {
localsHandler = savedLocals.mergeMultiple(caseHandlers, joinBlock);
}
} else {
// The joinblock is not used.
joinBlock = null;
}
HSubExpressionBlockInformation expressionInfo =
new HSubExpressionBlockInformation(new SubExpression(expressionStart,
expressionEnd));
expressionStart.setBlockFlow(
new HSwitchBlockInformation(expressionInfo,
matchExpressions,
statements,
hasDefault,
jumpHandler.target,
jumpHandler.labels()),
joinBlock);
jumpHandler.close();
}
bool nonPrimitiveTypeOverridesEquals(Constant constant) {
// Function values override equals. Even static ones, since
// they inherit from [Function].
if (constant.isFunction()) return true;
// [Map] and [List] do not override equals.
// If constant is primitive, just return false. We know
// about the equals methods of num/String classes.
if (!constant.isConstructedObject()) return false;
ConstructedConstant constructedConstant = constant;
DartType type = constructedConstant.type;
assert(type != null);
Element element = type.element;
// If the type is not a class, we'll just assume it overrides
// operator==. Typedefs do, since [Function] does.
if (!element.isClass()) return true;
ClassElement classElement = element;
return typeOverridesObjectEquals(classElement);
}
bool typeOverridesObjectEquals(ClassElement classElement) {
Element operatorEq =
lookupOperator(classElement, const SourceString('=='));
if (operatorEq == null) return false;
// If the operator== declaration is in Object, it's not overridden.
return (operatorEq.getEnclosingClass() != compiler.objectClass);
}
Element lookupOperator(ClassElement classElement, SourceString operatorName) {
SourceString dartMethodName =
Elements.constructOperatorName(operatorName, false);
return classElement.lookupMember(dartMethodName);
}
visitSwitchCase(SwitchCase node) {
compiler.internalError('SsaBuilder.visitSwitchCase');
}
visitCaseMatch(CaseMatch node) {
compiler.internalError('SsaBuilder.visitCaseMatch');
}
visitTryStatement(TryStatement node) {
work.allowSpeculativeOptimization = false;
// Save the current locals. 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 savedLocals = new LocalsHandler.from(localsHandler);
HBasicBlock enterBlock = openNewBlock();
HTry tryInstruction = new HTry();
close(tryInstruction);
bool oldInTryStatement = inTryStatement;
inTryStatement = true;
HBasicBlock startTryBlock;
HBasicBlock endTryBlock;
HBasicBlock startCatchBlock;
HBasicBlock endCatchBlock;
HBasicBlock startFinallyBlock;
HBasicBlock endFinallyBlock;
startTryBlock = graph.addNewBlock();
open(startTryBlock);
visit(node.tryBlock);
// 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 (!isAborted()) endTryBlock = close(new HExitTry());
SubGraph bodyGraph = new SubGraph(startTryBlock, lastOpenedBlock);
SubGraph catchGraph = null;
HLocalValue exception = null;
if (!node.catchBlocks.isEmpty) {
localsHandler = new LocalsHandler.from(savedLocals);
startCatchBlock = graph.addNewBlock();
open(startCatchBlock);
// TODO(kasperl): Bad smell. We shouldn't be constructing elements here.
// Note that the name of this element is irrelevant.
Element element = new ElementX(const SourceString('exception'),
ElementKind.PARAMETER,
currentElement);
exception = new HLocalValue(element);
add(exception);
HInstruction oldRethrowableException = rethrowableException;
rethrowableException = exception;
pushInvokeStatic(node, backend.getExceptionUnwrapper(), [exception]);
HInvokeStatic unwrappedException = pop();
tryInstruction.exception = exception;
Link<Node> link = node.catchBlocks.nodes;
void pushCondition(CatchBlock catchBlock) {
if (catchBlock.onKeyword != null) {
DartType type = elements.getType(catchBlock.type);
if (type == null) {
compiler.internalError('On with no type', node: catchBlock.type);
}
if (type.isMalformed) {
// TODO(johnniwinther): Handle malformed types in [HIs] instead.
HInstruction condition =
graph.addConstantBool(true, constantSystem);
stack.add(condition);
} else {
// TODO(karlkose): support type arguments here.
HInstruction condition = new HIs(type,
<HInstruction>[unwrappedException],
HIs.RAW_CHECK);
push(condition);
}
} else {
VariableDefinitions declaration = catchBlock.formals.nodes.head;
HInstruction condition = null;
if (declaration.type == null) {
condition = graph.addConstantBool(true, constantSystem);
stack.add(condition);
} else {
// TODO(aprelev@gmail.com): Once old catch syntax is removed
// "if" condition above and this "else" branch should be deleted as
// type of declared variable won't matter for the catch
// condition.
DartType type = elements.getType(declaration.type);
if (type == null) {
compiler.cancel('Catch with unresolved type', node: catchBlock);
}
// TODO(karlkose): support type arguments here.
condition = new HIs(type, <HInstruction>[unwrappedException],
HIs.RAW_CHECK);
push(condition);
}
}
}
void visitThen() {
CatchBlock catchBlock = link.head;
link = link.tail;
if (compiler.enableTypeAssertions) {
// In checked mode: throw a type error if the on-catch type is
// malformed.
if (catchBlock.onKeyword != null) {
DartType type = elements.getType(catchBlock.type);
if (type != null && type.isMalformed) {
String reasons = Types.fetchReasonsFromMalformedType(type);
generateMalformedSubtypeError(node,
unwrappedException, type, reasons);
pop();
return;
}
}
}
if (catchBlock.exception != null) {
localsHandler.updateLocal(elements[catchBlock.exception],
unwrappedException);
}
Node trace = catchBlock.trace;
if (trace != null) {
pushInvokeStatic(trace, backend.getTraceFromException(), [exception]);
HInstruction traceInstruction = pop();
localsHandler.updateLocal(elements[trace], traceInstruction);
}
visit(catchBlock);
}
void visitElse() {
if (link.isEmpty) {
closeAndGotoExit(new HThrow(exception, isRethrow: true));
} else {
CatchBlock newBlock = link.head;
handleIf(node,
() { pushCondition(newBlock); },
visitThen, visitElse);
}
}
CatchBlock firstBlock = link.head;
handleIf(node, () { pushCondition(firstBlock); }, visitThen, visitElse);
if (!isAborted()) endCatchBlock = close(new HGoto());
rethrowableException = oldRethrowableException;
tryInstruction.catchBlock = startCatchBlock;
catchGraph = new SubGraph(startCatchBlock, lastOpenedBlock);
}
SubGraph finallyGraph = null;
if (node.finallyBlock != null) {
localsHandler = new LocalsHandler.from(savedLocals);
startFinallyBlock = graph.addNewBlock();
open(startFinallyBlock);
visit(node.finallyBlock);
if (!isAborted()) endFinallyBlock = close(new HGoto());
tryInstruction.finallyBlock = startFinallyBlock;
finallyGraph = new SubGraph(startFinallyBlock, lastOpenedBlock);
}
HBasicBlock exitBlock = graph.addNewBlock();
addOptionalSuccessor(b1, b2) { if (b2 != null) b1.addSuccessor(b2); }
addExitTrySuccessor(successor) {
if (successor == null) return;
// Iterate over all blocks created inside this try/catch, and
// attach successor information to blocks that end with
// [HExitTry].
for (int i = startTryBlock.id; i < successor.id; i++) {
HBasicBlock block = graph.blocks[i];
var last = block.last;
if (last is HExitTry) {
block.addSuccessor(successor);
}
}
}
// Setup all successors. The entry block that contains the [HTry]
// has 1) the body, 2) the catch, 3) the finally, and 4) the exit
// blocks as successors.
enterBlock.addSuccessor(startTryBlock);
addOptionalSuccessor(enterBlock, startCatchBlock);
addOptionalSuccessor(enterBlock, startFinallyBlock);
enterBlock.addSuccessor(exitBlock);
// The body has either the catch or the finally block as successor.
if (endTryBlock != null) {
assert(startCatchBlock != null || startFinallyBlock != null);
endTryBlock.addSuccessor(
startCatchBlock != null ? startCatchBlock : startFinallyBlock);
endTryBlock.addSuccessor(exitBlock);
}
// The catch block has either the finally or the exit block as
// successor.
if (endCatchBlock != null) {
endCatchBlock.addSuccessor(
startFinallyBlock != null ? startFinallyBlock : exitBlock);
}
// The finally block has the exit block as successor.
if (endFinallyBlock != null) {
endFinallyBlock.addSuccessor(exitBlock);
}
// If a block inside try/catch aborts (eg with a return statement),
// we explicitely mark this block a predecessor of the catch
// block and the finally block.
addExitTrySuccessor(startCatchBlock);
addExitTrySuccessor(startFinallyBlock);
// Use the locals handler not altered by the catch and finally
// blocks.
localsHandler = savedLocals;
open(exitBlock);
enterBlock.setBlockFlow(
new HTryBlockInformation(
wrapStatementGraph(bodyGraph),
exception,
wrapStatementGraph(catchGraph),
wrapStatementGraph(finallyGraph)),
exitBlock);
inTryStatement = oldInTryStatement;
}
visitScriptTag(ScriptTag node) {
compiler.unimplemented('SsaBuilder.visitScriptTag', node: node);
}
visitCatchBlock(CatchBlock node) {
visit(node.block);
}
visitTypedef(Typedef node) {
compiler.unimplemented('SsaBuilder.visitTypedef', node: node);
}
visitTypeVariable(TypeVariable node) {
compiler.internalError('SsaBuilder.visitTypeVariable');
}
}
/**
* Visitor that handles generation of string literals (LiteralString,
* StringInterpolation), and otherwise delegates to the given visitor for
* non-literal subexpressions.
* TODO(lrn): Consider whether to handle compile time constant int/boolean
* expressions as well.
*/
class StringBuilderVisitor extends Visitor {
final SsaBuilder builder;
final Node diagnosticNode;
/**
* The string value generated so far.
*/
HInstruction result = null;
StringBuilderVisitor(this.builder, this.diagnosticNode);
void visit(Node node) {
node.accept(this);
}
visitNode(Node node) {
builder.compiler.internalError('unexpected node', node: node);
}
void visitExpression(Node node) {
node.accept(builder);
HInstruction expression = builder.pop();
if (!expression.isConstantString()) {
expression = new HStringify(expression, node);
builder.add(expression);
}
result = (result == null) ? expression : concat(result, expression);
}
void visitStringInterpolation(StringInterpolation node) {
node.visitChildren(this);
}
void visitStringInterpolationPart(StringInterpolationPart node) {
visit(node.expression);
visit(node.string);
}
void visitStringJuxtaposition(StringJuxtaposition node) {
node.visitChildren(this);
}
void visitNodeList(NodeList node) {
node.visitChildren(this);
}
HInstruction concat(HInstruction left, HInstruction right) {
HInstruction instruction = new HStringConcat(left, right, diagnosticNode);
builder.add(instruction);
return instruction;
}
}
/**
* This class visits the method that is a candidate for inlining and
* finds whether it is too difficult to inline.
*/
class InlineWeeder extends Visitor {
final TreeElements elements;
bool seenReturn = false;
bool tooDifficult = false;
int nodeCount = 0;
InlineWeeder(this.elements);
static bool canBeInlined(FunctionExpression functionExpression,
TreeElements elements) {
InlineWeeder weeder = new InlineWeeder(elements);
weeder.visit(functionExpression.body);
if (weeder.tooDifficult) return false;
return true;
}
bool registerNode() {
if (nodeCount++ > SsaBuilder.MAX_INLINING_NODES) {
tooDifficult = true;
return false;
} else {
return true;
}
}
void visit(Node node) {
node.accept(this);
}
void visitNode(Node node) {
if (!registerNode()) return;
if (seenReturn) {
tooDifficult = true;
} else {
node.visitChildren(this);
}
}
void visitFunctionExpression(Node node) {
if (!registerNode()) return;
tooDifficult = true;
}
void visitFunctionDeclaration(Node node) {
if (!registerNode()) return;
tooDifficult = true;
}
void visitSend(Send node) {
if (!registerNode()) return;
if (node.isParameterCheck) {
tooDifficult = true;
return;
}
node.visitChildren(this);
}
visitLoop(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.
tooDifficult = true;
}
void visitRethrow(Rethrow node) {
if (!registerNode()) return;
tooDifficult = true;
}
void visitReturn(Return node) {
if (!registerNode()) return;
if (seenReturn
|| identical(node.getBeginToken().stringValue, 'native')
|| node.isRedirectingFactoryBody) {
tooDifficult = true;
return;
}
node.visitChildren(this);
seenReturn = true;
}
void visitTryStatement(Node node) {
if (!registerNode()) return;
tooDifficult = true;
}
void visitThrow(Throw node) {
if (!registerNode()) return;
// For now, we don't want to handle throw after a return even if
// it is in an "if".
if (seenReturn) tooDifficult = true;
}
}
class InliningState {
/**
* Documentation wanted -- johnniwinther
*
* Invariant: [function] must be an implementation element.
*/
final PartialFunctionElement function;
final Element oldReturnElement;
final DartType oldReturnType;
final TreeElements oldElements;
final List<HInstruction> oldStack;
final LocalsHandler oldLocalsHandler;
InliningState(this.function,
this.oldReturnElement,
this.oldReturnType,
this.oldElements,
this.oldStack,
this.oldLocalsHandler) {
assert(function.isImplementation);
}
}
class SsaBranch {
final SsaBranchBuilder branchBuilder;
final HBasicBlock block;
LocalsHandler startLocals;
LocalsHandler exitLocals;
SubGraph graph;
SsaBranch(this.branchBuilder) : block = new HBasicBlock();
}
class SsaBranchBuilder {
final SsaBuilder builder;
final Node diagnosticNode;
SsaBranchBuilder(this.builder, [this.diagnosticNode]);
Compiler get compiler => builder.compiler;
void checkNotAborted() {
if (builder.isAborted()) {
compiler.unimplemented("aborted control flow", node: diagnosticNode);
}
}
void buildCondition(void visitCondition(),
SsaBranch conditionBranch,
SsaBranch thenBranch,
SsaBranch elseBranch) {
startBranch(conditionBranch);
visitCondition();
checkNotAborted();
assert(identical(builder.current, builder.lastOpenedBlock));
HInstruction conditionValue = builder.popBoolified();
HIf branch = new HIf(conditionValue);
HBasicBlock conditionExitBlock = builder.current;
builder.close(branch);
conditionBranch.exitLocals = builder.localsHandler;
conditionExitBlock.addSuccessor(thenBranch.block);
conditionExitBlock.addSuccessor(elseBranch.block);
bool conditionBranchLocalsCanBeReused =
mergeLocals(conditionBranch, thenBranch, mayReuseFromLocals: true);
mergeLocals(conditionBranch, elseBranch,
mayReuseFromLocals: conditionBranchLocalsCanBeReused);
conditionBranch.graph =
new SubExpression(conditionBranch.block, conditionExitBlock);
}
/**
* Returns true if the locals of the [fromBranch] may be reused. A [:true:]
* return value implies that [mayReuseFromLocals] was set to [:true:].
*/
bool mergeLocals(SsaBranch fromBranch, SsaBranch toBranch,
{bool mayReuseFromLocals}) {
LocalsHandler fromLocals = fromBranch.exitLocals;
if (toBranch.startLocals == null) {
if (mayReuseFromLocals) {
toBranch.startLocals = fromLocals;
return false;
} else {
toBranch.startLocals = new LocalsHandler.from(fromLocals);
return true;
}
} else {
toBranch.startLocals.mergeWith(fromLocals, toBranch.block);
return true;
}
}
void startBranch(SsaBranch branch) {
builder.graph.addBlock(branch.block);
builder.localsHandler = branch.startLocals;
builder.open(branch.block);
}
HInstruction buildBranch(SsaBranch branch,
void visitBranch(),
SsaBranch joinBranch,
bool isExpression) {
startBranch(branch);
visitBranch();
branch.graph = new SubGraph(branch.block, builder.lastOpenedBlock);
branch.exitLocals = builder.localsHandler;
if (!builder.isAborted()) {
builder.goto(builder.current, joinBranch.block);
mergeLocals(branch, joinBranch, mayReuseFromLocals: true);
}
if (isExpression) {
checkNotAborted();
return builder.pop();
}
return null;
}
handleIf(void visitCondition(), void visitThen(), void visitElse()) {
if (visitElse == null) {
// Make sure to have an else part to avoid a critical edge. A
// critical edge is an edge that connects a block with multiple
// successors to a block with multiple predecessors. We avoid
// such edges because they prevent inserting copies during code
// generation of phi instructions.
visitElse = () {};
}
_handleDiamondBranch(visitCondition, visitThen, visitElse, false);
}
handleConditional(void visitCondition(), void visitThen(), void visitElse()) {
assert(visitElse != null);
_handleDiamondBranch(visitCondition, visitThen, visitElse, true);
}
void handleLogicalAndOr(void left(), void right(), {bool isAnd}) {
// x && y is transformed into:
// t0 = boolify(x);
// if (t0) {
// t1 = boolify(y);
// }
// result = phi(t1, false);
//
// x || y is transformed into:
// t0 = boolify(x);
// if (not(t0)) {
// t1 = boolify(y);
// }
// result = phi(t1, true);
HInstruction boolifiedLeft;
HInstruction boolifiedRight;
void visitCondition() {
left();
boolifiedLeft = builder.popBoolified();
builder.stack.add(boolifiedLeft);
if (!isAnd) {
builder.push(new HNot(builder.pop()));
}
}
void visitThen() {
right();
boolifiedRight = builder.popBoolified();
}
handleIf(visitCondition, visitThen, null);
HConstant notIsAnd =
builder.graph.addConstantBool(!isAnd, builder.constantSystem);
HPhi result = new HPhi.manyInputs(null,
<HInstruction>[boolifiedRight, notIsAnd]);
builder.current.addPhi(result);
builder.stack.add(result);
}
void handleLogicalAndOrWithLeftNode(Node left,
void visitRight(),
{bool isAnd}) {
// This method is similar to [handleLogicalAndOr] but optimizes the case
// where left is a logical "and" or logical "or".
//
// For example (x && y) && z is transformed into x && (y && z):
// t0 = boolify(x);
// if (t0) {
// t1 = boolify(y);
// if (t1) {
// t2 = boolify(z);
// }
// t3 = phi(t2, false);
// }
// result = phi(t3, false);
Send send = left.asSend();
if (send != null &&
(isAnd ? send.isLogicalAnd : send.isLogicalOr)) {
Node newLeft = send.receiver;
Link<Node> link = send.argumentsNode.nodes;
assert(link.tail.isEmpty);
Node middle = link.head;
handleLogicalAndOrWithLeftNode(
newLeft,
() => handleLogicalAndOrWithLeftNode(middle, visitRight,
isAnd: isAnd),
isAnd: isAnd);
} else {
handleLogicalAndOr(() => builder.visit(left), visitRight, isAnd: isAnd);
}
}
void _handleDiamondBranch(void visitCondition(),
void visitThen(),
void visitElse(),
bool isExpression) {
SsaBranch conditionBranch = new SsaBranch(this);
SsaBranch thenBranch = new SsaBranch(this);
SsaBranch elseBranch = new SsaBranch(this);
SsaBranch joinBranch = new SsaBranch(this);
conditionBranch.startLocals = builder.localsHandler;
builder.goto(builder.current, conditionBranch.block);
buildCondition(visitCondition, conditionBranch, thenBranch, elseBranch);
HInstruction thenValue =
buildBranch(thenBranch, visitThen, joinBranch, isExpression);
HInstruction elseValue =
buildBranch(elseBranch, visitElse, joinBranch, isExpression);
if (isExpression) {
assert(thenValue != null && elseValue != null);
HPhi phi =
new HPhi.manyInputs(null, <HInstruction>[thenValue, elseValue]);
joinBranch.block.addPhi(phi);
builder.stack.add(phi);
}
HBasicBlock thenBlock = thenBranch.block;
HBasicBlock elseBlock = elseBranch.block;
HBasicBlock joinBlock;
// If at least one branch did not abort, open the joinBranch.
if (!joinBranch.block.predecessors.isEmpty) {
startBranch(joinBranch);
joinBlock = joinBranch.block;
}
HIfBlockInformation info =
new HIfBlockInformation(
new HSubExpressionBlockInformation(conditionBranch.graph),
new HSubGraphBlockInformation(thenBranch.graph),
new HSubGraphBlockInformation(elseBranch.graph));
HBasicBlock conditionStartBlock = conditionBranch.block;
conditionStartBlock.setBlockFlow(info, joinBlock);
SubGraph conditionGraph = conditionBranch.graph;
HIf branch = conditionGraph.end.last;
assert(branch is HIf);
branch.blockInformation = conditionStartBlock.blockFlow;
}
}