Files
sdk/lib/compiler/implementation/ssa/builder.dart
T
karlklose@google.com aeafe0fdda Add erroneous elements for function types and use them to allow unresolvable constructors to be handled in the backend.
This is the first step towards runtime error handling of unresolvable constructors, library prefixes and constructor names in new-expressions.

Review URL: https://chromiumcodereview.appspot.com//10829379

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@11042 260f80e4-7a28-3924-810f-c04153c831b5
2012-08-21 13:40:07 +00:00

3657 lines
130 KiB
Dart

// 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.
class Interceptors {
Compiler compiler;
Interceptors(Compiler this.compiler);
SourceString mapOperatorToMethodName(Operator op) {
String name = op.source.stringValue;
if (name === '+') return const SourceString('add');
if (name === '-') return const SourceString('sub');
if (name === '*') return const SourceString('mul');
if (name === '/') return const SourceString('div');
if (name === '~/') return const SourceString('tdiv');
if (name === '%') return const SourceString('mod');
if (name === '<<') return const SourceString('shl');
if (name === '>>') return const SourceString('shr');
if (name === '|') return const SourceString('or');
if (name === '&') return const SourceString('and');
if (name === '^') return const SourceString('xor');
if (name === '<') return const SourceString('lt');
if (name === '<=') return const SourceString('le');
if (name === '>') return const SourceString('gt');
if (name === '>=') return const SourceString('ge');
if (name === '==') return const SourceString('eq');
if (name === '!=') return const SourceString('eq');
if (name === '===') return const SourceString('eqq');
if (name === '!==') return const SourceString('eqq');
if (name === '+=') return const SourceString('add');
if (name === '-=') return const SourceString('sub');
if (name === '*=') return const SourceString('mul');
if (name === '/=') return const SourceString('div');
if (name === '~/=') return const SourceString('tdiv');
if (name === '%=') return const SourceString('mod');
if (name === '<<=') return const SourceString('shl');
if (name === '>>=') return const SourceString('shr');
if (name === '|=') return const SourceString('or');
if (name === '&=') return const SourceString('and');
if (name === '^=') return const SourceString('xor');
if (name === '++') return const SourceString('add');
if (name === '--') return const SourceString('sub');
compiler.unimplemented('Unknown operator', node: op);
}
Element getStaticInterceptor(SourceString name, int parameters) {
String mangledName = name.slowToString();
Element element = compiler.findInterceptor(new SourceString(mangledName));
if (element !== null && element.isFunction()) {
// Only pick the function element with the short name if the
// number of parameters it expects matches the number we're
// passing modulo the receiver.
FunctionElement function = element;
if (function.parameterCount(compiler) == parameters + 1) return element;
}
String longMangledName = "$mangledName\$$parameters";
return compiler.findInterceptor(new SourceString(longMangledName));
}
Element getStaticGetInterceptor(SourceString name) {
String mangledName = "get\$${name.slowToString()}";
return compiler.findInterceptor(new SourceString(mangledName));
}
Element getStaticSetInterceptor(SourceString name) {
String mangledName = "set\$${name.slowToString()}";
return compiler.findInterceptor(new SourceString(mangledName));
}
Element getOperatorInterceptor(Operator op) {
SourceString name = mapOperatorToMethodName(op);
return compiler.findHelper(name);
}
Element getBoolifiedVersionOf(Element interceptor) {
if (interceptor === null) return interceptor;
String boolifiedName = "${interceptor.name.slowToString()}B";
return compiler.findHelper(new SourceString(boolifiedName));
}
Element getPrefixOperatorInterceptor(Operator op) {
String name = op.source.stringValue;
if (name === '~') {
return compiler.findHelper(const SourceString('not'));
}
if (name === '-') {
return compiler.findHelper(const SourceString('neg'));
}
compiler.unimplemented('Unknown operator', node: op);
}
Element getIndexInterceptor() {
return compiler.findHelper(const SourceString('index'));
}
Element getIndexAssignmentInterceptor() {
return compiler.findHelper(const SourceString('indexSet'));
}
Element getExceptionUnwrapper() {
return compiler.findHelper(const SourceString('unwrapException'));
}
Element getClosureConverter() {
return compiler.findHelper(const SourceString('convertDartClosureToJS'));
}
Element getTraceFromException() {
return compiler.findHelper(const SourceString('getTraceFromException'));
}
Element getEqualsInterceptor() {
return compiler.findHelper(const SourceString('eq'));
}
Element getTripleEqualsInterceptor() {
return compiler.findHelper(const SourceString('eqq'));
}
Element getMapMaker() {
return compiler.findHelper(const SourceString('makeLiteralMap'));
}
// TODO(karlklose): move these to different class or rename class?
Element getSetRuntimeTypeInfo() {
return compiler.findHelper(const SourceString('setRuntimeTypeInfo'));
}
Element getGetRuntimeTypeInfo() {
return compiler.findHelper(const SourceString('getRuntimeTypeInfo'));
}
}
class SsaBuilderTask extends CompilerTask {
final Interceptors interceptors;
final CodeEmitterTask emitter;
// Loop tracking information.
final Set<FunctionElement> functionsCalledInLoop;
final Map<SourceString, Selector> selectorsCalledInLoop;
final JavaScriptBackend backend;
String get name() => 'SSA builder';
SsaBuilderTask(JavaScriptBackend backend)
: interceptors = new Interceptors(backend.compiler),
emitter = backend.emitter,
functionsCalledInLoop = new Set<FunctionElement>(),
selectorsCalledInLoop = new Map<SourceString, Selector>(),
backend = backend,
super(backend.compiler);
HGraph build(WorkItem work) {
return measure(() {
FunctionElement element = work.element;
HInstruction.idCounter = 0;
SsaBuilder builder = new SsaBuilder(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(work.element);
}
assert(graph.isValid());
bool inLoop = functionsCalledInLoop.contains(element);
if (!inLoop) {
Selector selector = selectorsCalledInLoop[element.name];
inLoop = selector !== null && selector.applies(element, compiler);
}
graph.calledInLoop = inLoop;
// If there is an estimate of the parameter types assume these types when
// compiling.
List<HType> parameterTypes =
backend.optimisticParameterTypesWithRecompilationOnTypeChange(
element);
if (parameterTypes != null) {
FunctionSignature signature = element.computeSignature(compiler);
int i = 0;
signature.forEachParameter((Element param) {
builder.parameters[param].guaranteedType = parameterTypes[i++];
});
}
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.kind == ElementKind.GENERATIVE_CONSTRUCTOR_BODY) {
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, WorkItem work) {
// The body of the constructor will be generated in a separate function.
final ClassElement classElement = work.element.getEnclosingClass();
return builder.buildFactory(classElement, work.element);
}
}
/**
* 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).
*/
Map<Element, HInstruction> directLocals;
Map<Element, Element> redirectionMapping;
SsaBuilder builder;
ClosureClassMap closureData;
LocalsHandler(this.builder)
: directLocals = new Map<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 Map<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(const LiteralDartString("{}"),
const LiteralDartString('Object'),
<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) {
// 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) {
// The scope has captured variables. Create a box.
// TODO(floitsch): Clean up this hack. Should we create a box-object by
// just creating an empty object literal?
HInstruction 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.kind == ElementKind.PARAMETER) {
// Store the captured parameter in the box. Get the current value
// before we put the redirection in place.
HInstruction instruction = readLocal(from);
redirectElement(from, to);
// Now that the redirection is set up, the update to the local will
// write the parameter value into the box.
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);
}
void startFunction(FunctionElement function,
FunctionExpression node) {
Compiler compiler = builder.compiler;
closureData = compiler.closureToClassMapper.computeClosureToClassMapping(
node, builder.elements);
FunctionSignature signature = function.computeSignature(compiler);
signature.forEachParameter((Element element) {
HInstruction parameter = new HParameterValue(element);
builder.add(parameter);
builder.parameters[element] = parameter;
directLocals[element] = parameter;
parameter.guaranteedType =
builder.mapInferredType(typesTask.getGuaranteedTypeOfElement(element));
});
enterScope(node);
// 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:].
HInstruction thisInstruction = new HThis();
builder.add(thisInstruction);
updateLocal(closureData.closureElement, thisInstruction);
} else if (function.isInstanceMember()
|| function.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.
ClassElement cls = function.getEnclosingClass();
Type type = cls.computeType(builder.compiler);
HInstruction thisInstruction = new HThis(new HBoundedType.nonNull(type));
builder.add(thisInstruction);
directLocals[closureData.thisElement] = thisInstruction;
}
}
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) {
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.withElement(redirect, receiver);
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.kind == ElementKind.VARIABLE);
HInstruction box = readLocal(redirect.enclosingElement);
HInstruction lookup = new HFieldGet.withElement(redirect, box);
builder.add(lookup);
return lookup;
} else {
assert(isUsedInTry(element));
HLocalValue local = getLocal(element);
HInstruction variable = new HLocalGet(element, local);
builder.add(variable);
return variable;
}
}
HType cachedTypeOfThis;
HInstruction readThis() {
HInstruction res = readLocal(closureData.thisElement);
if (res.guaranteedType === null) {
if (cachedTypeOfThis === null) {
assert(closureData.isClosure());
Element element = closureData.thisElement;
ClassElement cls = element.enclosingElement.getEnclosingClass();
Type type = cls.computeType(builder.compiler);
cachedTypeOfThis = new HBoundedType.nonNull(type);
}
res.guaranteedType = cachedTypeOfThis;
}
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.kind == ElementKind.VARIABLE);
HInstruction box = readLocal(redirect.enclosingElement);
builder.add(new HFieldSet.withElement(redirect, box, value));
} else {
assert(isUsedInTry(element));
HLocalValue local = getLocal(element);
builder.add(new HLocalSet(element, local, value));
}
}
/**
* This function 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);
}
}
void beginLoopHeader(Node node, HBasicBlock loopEntry) {
// Create a copy because we modify the map while iterating over
// it.
Map<Element, HInstruction> saved =
new Map<Element, HInstruction>.from(directLocals);
// Create phis for all elements in the definitions environment.
saved.forEach((Element element, HInstruction instruction) {
if (isAccessedDirectly(element)) {
// We know 'this' cannot be modified.
if (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);
}
}
void enterLoopUpdates(Loop 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);
}
}
void endLoop(HBasicBlock loopEntry) {
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 Map<Element, HInstruction>();
otherLocals.directLocals.forEach((element, instruction) {
// We know 'this' cannot be modified.
if (element === closureData.thisElement) {
assert(directLocals[element] == instruction);
joinedLocals[element] = instruction;
} else {
HInstruction mine = directLocals[element];
if (mine === null) return;
if (instruction === mine) {
joinedLocals[element] = instruction;
} else {
HInstruction phi =
new HPhi.manyInputs(element, <HInstruction>[mine, instruction]);
joinBlock.addPhi(phi);
joinedLocals[element] = phi;
}
}
});
directLocals = joinedLocals;
}
/**
* 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.
* Returns the new LocalsHandler to use (may not be [this]).
*/
LocalsHandler mergeMultiple(List<LocalsHandler> locals,
HBasicBlock joinBlock) {
assert(locals.length > 0);
if (locals.length == 1) return locals[0];
Map<Element, HInstruction> joinedLocals = new Map<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 local in locals) {
local.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;
}
directLocals = joinedLocals;
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);
}
interface JumpHandler default JumpHandlerImpl {
JumpHandler(SsaBuilder builder, TargetElement 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));
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]) {
// TODO(lrn): Need a compiler object and a location. Since label
// is optional, it may be null so we also need a position.
compiler.internalError('generateBreak should not be called');
}
void generateContinue([LabelElement label]) {
// TODO(lrn): Need a compiler object and a location. Since label
// is optional, it may be null so we also need a position.
compiler.internalError('generateContinue should not be called');
}
void forEachBreak(Function ignored) { }
void forEachContinue(Function ignored) { }
void close() { }
final TargetElement target = null;
List<LabelElement> labels() => const <LabelElement>[];
}
// 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 JumpHandlerImpl implements JumpHandler {
final SsaBuilder builder;
final TargetElement target;
final List<JumpHandlerEntry> jumps;
JumpHandlerImpl(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);
}
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);
}
}
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;
}
}
class SsaBuilder extends ResolvedVisitor implements Visitor {
final SsaBuilderTask builder;
final Interceptors interceptors;
final WorkItem work;
bool methodInterceptionEnabled;
HGraph graph;
LocalsHandler localsHandler;
HInstruction rethrowableException;
Map<Element, HParameterValue> parameters;
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 HFieldGet and HFieldSet.
*/
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.
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;
LibraryElement get currentLibrary() => work.element.getLibrary();
Compiler get compiler() => builder.compiler;
CodeEmitterTask get emitter() => builder.emitter;
SsaBuilder(SsaBuilderTask builder, WorkItem work)
: this.builder = builder,
this.work = work,
interceptors = builder.interceptors,
methodInterceptionEnabled = true,
graph = new HGraph(),
stack = new List<HInstruction>(),
activationVariables = new Map<Element, HLocalValue>(),
jumpTargets = new Map<TargetElement, JumpHandler>(),
parameters = new Map<Element, HParameterValue>(),
super(work.resolutionTree) {
localsHandler = new LocalsHandler(this);
}
void disableMethodInterception() {
assert(methodInterceptionEnabled);
methodInterceptionEnabled = false;
}
void enableMethodInterception() {
assert(!methodInterceptionEnabled);
methodInterceptionEnabled = true;
}
HGraph buildMethod(FunctionElement functionElement) {
FunctionExpression function = functionElement.parseNode(compiler);
openFunction(functionElement, function);
function.body.accept(this);
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.kind === ElementKind.GENERATIVE_CONSTRUCTOR);
if (constructor is SynthesizedConstructorElement) return null;
FunctionExpression node = constructor.parseNode(compiler);
// If we know the body doesn't have any code, we don't generate
// it.
if (node.body.asBlock() !== null) {
NodeList statements = node.body.asBlock().statements;
if (statements.isEmpty()) return null;
}
ClassElement classElement = constructor.getEnclosingClass();
ConstructorBodyElement bodyElement;
for (Link<Element> backendMembers = classElement.backendMembers;
!backendMembers.isEmpty();
backendMembers = backendMembers.tail) {
Element backendMember = backendMembers.head;
if (backendMember.kind == ElementKind.GENERATIVE_CONSTRUCTOR_BODY) {
ConstructorBodyElement body = backendMember;
if (body.constructor == constructor) {
bodyElement = backendMember;
break;
}
}
}
if (bodyElement === null) {
bodyElement = new ConstructorBodyElement(constructor);
TreeElements treeElements =
compiler.resolver.resolveMethodElement(constructor);
compiler.enqueuer.codegen.addToWorkList(bodyElement, treeElements);
classElement.backendMembers =
classElement.backendMembers.prepend(bodyElement);
}
assert(bodyElement.kind === ElementKind.GENERATIVE_CONSTRUCTOR_BODY);
return bodyElement;
}
void inlineSuperOrRedirect(FunctionElement constructor,
Selector selector,
Link<Node> arguments,
List<FunctionElement> constructors,
Map<Element, HInstruction> fieldValues) {
constructors.addLast(constructor);
List<HInstruction> compiledArguments = new List<HInstruction>();
bool succeeded = 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);
}
int index = 0;
FunctionSignature params = constructor.computeSignature(compiler);
params.forEachParameter((Element parameter) {
HInstruction argument = compiledArguments[index++];
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;
elements = compiler.resolver.resolveMethodElement(constructor);
buildInitializers(constructor, constructors, fieldValues);
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.
*/
void buildInitializers(FunctionElement constructor,
List<FunctionElement> constructors,
Map<Element, HInstruction> fieldValues) {
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);
foundSuperOrRedirect = true;
} else {
// A field initializer.
SendSet init = link.head;
Link<Node> arguments = init.arguments;
assert(!arguments.isEmpty() && arguments.tail.isEmpty());
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 != compiler.objectClass) {
assert(superClass !== null);
assert(superClass.resolutionState == ClassElement.STATE_DONE);
Selector selector =
new Selector.call(superClass.name, enclosingClass.getLibrary(), 0);
FunctionElement target = superClass.lookupConstructor(superClass.name);
if (target === null) {
compiler.internalError("no default constructor available");
}
inlineSuperOrRedirect(target,
selector,
const EmptyLink<Node>(),
constructors,
fieldValues);
}
}
}
/**
* 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).
*/
HGraph buildFactory(ClassElement classElement,
FunctionElement functionElement) {
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>();
FunctionSignature params = functionElement.computeSignature(compiler);
params.forEachParameter((Element element) {
if (element.kind == ElementKind.FIELD_PARAMETER) {
// If the [element] is a field-parameter (such as [:this.x:] then
// initialize the field element with its value.
FieldParameterElement fieldParameterElement = element;
HInstruction parameterValue = localsHandler.readLocal(element);
fieldValues[fieldParameterElement.fieldElement] = parameterValue;
}
});
List<FunctionElement> constructors = <FunctionElement>[functionElement];
// Analyze the constructor and all referenced constructors and collect
// initializers and constructor bodies.
buildInitializers(functionElement, constructors, fieldValues);
// Call the JavaScript constructor with the fields as argument.
List<HInstruction> constructorArguments = <HInstruction>[];
classElement.forEachInstanceField(
includeBackendMembers: true,
includeSuperMembers: true,
f: (ClassElement enclosingClass, Element member) {
HInstruction value = fieldValues[member];
if (value === null) {
// The field has no value in the initializer list. Initialize it
// with the declaration-site constant (if any).
Constant fieldValue = compiler.constantHandler.compileVariable(member);
value = graph.addConstant(fieldValue);
}
constructorArguments.add(value);
});
HForeignNew newObject = new HForeignNew(classElement, constructorArguments);
add(newObject);
// Generate calls to the constructor bodies.
for (int index = constructors.length - 1; index >= 0; index--) {
FunctionElement constructor = constructors[index];
ConstructorBodyElement body = getConstructorBody(constructor);
if (body === null) continue;
List bodyCallInputs = <HInstruction>[];
bodyCallInputs.add(newObject);
int arity = body.functionSignature.parameterCount;
body.functionSignature.forEachParameter((parameter) {
bodyCallInputs.add(localsHandler.readLocal(parameter));
});
// TODO(ahe): The constructor name is statically resolved. See
// SsaCodeGenerator.visitInvokeDynamicMethod. Is there a cleaner
// way to do this?
SourceString name = new SourceString(compiler.namer.getName(body));
// 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, arity);
add(new HInvokeDynamicMethod(selector, bodyCallInputs));
}
close(new HReturn(newObject)).addSuccessor(graph.exit);
return closeFunction();
}
void openFunction(FunctionElement functionElement,
FunctionExpression node) {
HBasicBlock block = graph.addNewBlock();
open(graph.entry);
localsHandler.startFunction(functionElement, node);
close(new HGoto()).addSuccessor(block);
open(block);
// 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.
FunctionSignature params = functionElement.computeSignature(compiler);
params.forEachParameter((Element element) {
HInstruction newParameter = potentiallyCheckType(
localsHandler.directLocals[element], element);
localsHandler.directLocals[element] = newParameter;
});
}
HInstruction potentiallyCheckType(HInstruction original,
Element sourceElement) {
if (!compiler.enableTypeAssertions) return original;
return convertType(original, sourceElement,
HTypeConversion.CHECKED_MODE_CHECK);
}
HInstruction convertType(HInstruction original,
Element sourceElement,
int kind) {
Type type = sourceElement.computeType(compiler);
if (type === null) return original;
if (type.element === compiler.dynamicClass) return original;
if (type.element === compiler.objectClass) return original;
// If the original can't be null, type conversion also can't produce null.
bool canBeNull = original.guaranteedType.canBeNull();
HType convertedType =
new HType.fromBoundedType(type, compiler, canBeNull);
// No need to convert if we know the instruction has
// [convertedType] as a bound.
if (original.guaranteedType == convertedType) {
return original;
}
HInstruction instruction =
new HTypeConversion(convertedType, original, kind);
add(instruction);
return instruction;
}
HGraph closeFunction() {
// TODO(kasperl): Make this goto an implicit return.
if (!isAborted()) close(new HGoto()).addSuccessor(graph.exit);
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;
}
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());
}
HBoolify popBoolified() {
HBoolify boolified = new HBoolify(pop());
add(boolified);
return boolified;
}
HInstruction attachPosition(HInstruction target, Node node) {
target.sourcePosition = node.getBeginToken();
return target;
}
void visit(Node node) {
if (node !== null) node.accept(this);
}
visitBlock(Block node) {
for (Link<Node> link = node.statements.nodes;
!link.isEmpty();
link = link.tail) {
visit(link.head);
if (isAborted()) {
// 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) {
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);
HBasicBlock loopEntry = graph.addNewLoopHeaderBlock(
jumpHandler.target,
jumpHandler.labels());
previousBlock.addSuccessor(loopEntry);
open(loopEntry);
localsHandler.beginLoopHeader(node, loopEntry);
return jumpHandler;
}
/**
* Ends the loop:
* - creates a new block and adds it as successor to the [branchBlock].
* - opens the new block (setting as [current]).
* - notifies the locals handler that we're exiting a loop.
*/
void endLoop(HBasicBlock loopEntry,
HBasicBlock branchBlock,
JumpHandler jumpHandler,
LocalsHandler savedLocals) {
HBasicBlock loopExitBlock = addNewBlock();
assert(branchBlock.successors.length == 1);
List<LocalsHandler> breakLocals = <LocalsHandler>[];
jumpHandler.forEachBreak((HBreak breakInstruction, LocalsHandler locals) {
breakInstruction.block.addSuccessor(loopExitBlock);
breakLocals.add(locals);
});
branchBlock.addSuccessor(loopExitBlock);
open(loopExitBlock);
localsHandler.endLoop(loopEntry);
if (!breakLocals.isEmpty()) {
breakLocals.add(savedLocals);
localsHandler = savedLocals.mergeMultiple(breakLocals, 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.
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);
LocalsHandler savedLocals = new LocalsHandler.from(localsHandler);
// The body.
HBasicBlock beginBodyBlock = addNewBlock();
conditionExitBlock.addSuccessor(beginBodyBlock);
open(beginBodyBlock);
localsHandler.enterLoopBody(loop);
hackAroundPossiblyAbortingBody(loop, body);
SubGraph bodyGraph = new SubGraph(beginBodyBlock, current);
HBasicBlock bodyBlock = close(new HGoto());
// 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> continueLocals = <LocalsHandler>[];
jumpHandler.forEachContinue((HContinue instruction, LocalsHandler locals) {
instruction.block.addSuccessor(updateBlock);
continueLocals.add(locals);
});
bodyBlock.addSuccessor(updateBlock);
continueLocals.add(localsHandler);
open(updateBlock);
localsHandler = localsHandler.mergeMultiple(continueLocals, 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);
conditionBlock.postProcessLoopHeader();
SubExpression updateGraph = new SubExpression(updateBlock, updateEndBlock);
endLoop(conditionBlock, conditionExitBlock, jumpHandler, savedLocals);
HLoopBlockInformation info =
new HLoopBlockInformation(
HLoopBlockInformation.loopType(loop),
wrapExpressionGraph(initializerGraph),
wrapExpressionGraph(conditionExpression),
wrapStatementGraph(bodyGraph),
wrapExpressionGraph(updateGraph),
conditionBlock.loopInformation.target,
conditionBlock.loopInformation.labels,
loop);
startBlock.setBlockFlow(info, current);
loopInfo.loopBlockInformation = info;
}
visitFor(For node) {
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);
}
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) {
HInstruction buildCondition() {
visit(node.condition);
return popBoolified();
}
handleLoop(node,
() {},
buildCondition,
() {},
() { visit(node.body); });
}
visitDoWhile(DoWhile node) {
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);
hackAroundPossiblyAbortingBody(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 = close(new HGoto());
HBasicBlock conditionBlock = addNewBlock();
List<LocalsHandler> continueLocals = <LocalsHandler>[];
jumpHandler.forEachContinue((HContinue instruction, LocalsHandler locals) {
instruction.block.addSuccessor(conditionBlock);
continueLocals.add(locals);
});
bodyExitBlock.addSuccessor(conditionBlock);
if (!continueLocals.isEmpty()) {
continueLocals.add(localsHandler);
localsHandler = savedLocals.mergeMultiple(continueLocals, 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));
conditionEndBlock.addSuccessor(loopEntryBlock); // The back-edge.
loopEntryBlock.postProcessLoopHeader();
endLoop(loopEntryBlock, conditionEndBlock, jumpHandler, localsHandler);
jumpHandler.close();
SubExpression conditionExpression =
new SubExpression(conditionBlock, conditionEndBlock);
SubGraph bodyGraph = new SubGraph(bodyEntryBlock, bodyExitBlock);
HLoopBlockInformation loopBlockInfo =
new HLoopBlockInformation(
HLoopBlockInformation.DO_WHILE_LOOP,
null,
wrapExpressionGraph(conditionExpression),
wrapStatementGraph(bodyGraph),
null,
loopEntryBlock.loopInformation.target,
loopEntryBlock.loopInformation.labels,
node);
loopEntryBlock.setBlockFlow(loopBlockInfo, current);
loopInfo.loopBlockInformation = loopBlockInfo;
}
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);
assert(closureClassElement.localScope.isEmpty());
List<HInstruction> capturedVariables = <HInstruction>[];
for (Element member in closureClassElement.backendMembers) {
// The backendMembers also contains the call method(s). We are only
// interested in the fields.
if (member.kind == ElementKind.FIELD) {
Element capturedLocal = nestedClosureData.capturedFieldMapping[member];
assert(capturedLocal != null);
capturedVariables.add(localsHandler.readLocal(capturedLocal));
}
}
push(new HForeignNew(closureClassElement, capturedVariables));
}
visitFunctionDeclaration(FunctionDeclaration node) {
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) {
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) {
assert(node.argumentsNode is Prefix);
visit(node.receiver);
assert(op.token.kind !== PLUS_TOKEN);
HInstruction operand = pop();
HInstruction target =
new HStatic(interceptors.getPrefixOperatorInterceptor(op));
add(target);
HInvokeUnary result;
String value = op.source.stringValue;
switch (value) {
case "-": result = new HNegate(target, operand); break;
case "~": result = new HBitNot(target, operand); break;
default:
compiler.internalError('Unexpected unary operator: $value.', node: op);
break;
}
// See if we can constant-fold right away. This avoids rewrites later on.
if (operand is HConstant) {
HConstant constant = operand;
Constant folded = result.operation.fold(constant.constant);
if (folded !== null) {
stack.add(graph.addConstant(folded));
return;
}
}
pushWithPosition(result, node);
}
void visitBinary(HInstruction left, Operator op, HInstruction right) {
Element element = interceptors.getOperatorInterceptor(op);
assert(element != null);
HInstruction target = new HStatic(element);
add(target);
switch (op.source.stringValue) {
case "+":
case "++":
case "+=":
pushWithPosition(new HAdd(target, left, right), op);
break;
case "-":
case "--":
case "-=":
pushWithPosition(new HSubtract(target, left, right), op);
break;
case "*":
case "*=":
pushWithPosition(new HMultiply(target, left, right), op);
break;
case "/":
case "/=":
pushWithPosition(new HDivide(target, left, right), op);
break;
case "~/":
case "~/=":
pushWithPosition(new HTruncatingDivide(target, left, right), op);
break;
case "%":
case "%=":
pushWithPosition(new HModulo(target, left, right), op);
break;
case "<<":
case "<<=":
pushWithPosition(new HShiftLeft(target, left, right), op);
break;
case ">>":
case ">>=":
pushWithPosition(new HShiftRight(target, left, right), op);
break;
case "|":
case "|=":
pushWithPosition(new HBitOr(target, left, right), op);
break;
case "&":
case "&=":
pushWithPosition(new HBitAnd(target, left, right), op);
break;
case "^":
case "^=":
pushWithPosition(new HBitXor(target, left, right), op);
break;
case "==":
pushWithPosition(new HEquals(target, left, right), op);
break;
case "===":
pushWithPosition(new HIdentity(target, left, right), op);
break;
case "!==":
HIdentity eq = new HIdentity(target, left, right);
add(eq);
pushWithPosition(new HNot(eq), op);
break;
case "<":
pushWithPosition(new HLess(target, left, right), op);
break;
case "<=":
pushWithPosition(new HLessEqual(target, left, right), op);
break;
case ">":
pushWithPosition(new HGreater(target, left, right), op);
break;
case ">=":
pushWithPosition(new HGreaterEqual(target, left, right), op);
break;
case "!=":
HEquals eq = new HEquals(target, left, right);
add(eq);
HBoolify bl = new HBoolify(eq);
add(bl);
pushWithPosition(new HNot(bl), op);
break;
default: compiler.unimplemented("SsaBuilder.visitBinary");
}
}
HInstruction generateInstanceSendReceiver(Send send) {
assert(Elements.isInstanceSend(send, elements));
if (send.receiver == null) {
return localsHandler.readThis();
}
visit(send.receiver);
return pop();
}
void generateInstanceGetterWithCompiledReceiver(Send send,
HInstruction receiver) {
assert(Elements.isInstanceSend(send, elements));
// TODO(kasperl): This is a convoluted way of checking if we're
// generating code for a compound assignment. If we are, we need
// to get the selector from the mapping for the AST selector node.
Selector selector = (send.asSendSet() === null)
? elements.getSelector(send)
: elements.getSelector(send.selector);
assert(selector.isGetter());
SourceString getterName = selector.name;
Element staticInterceptor = null;
if (methodInterceptionEnabled) {
staticInterceptor = interceptors.getStaticGetInterceptor(getterName);
}
if (staticInterceptor != null) {
HStatic target = new HStatic(staticInterceptor);
add(target);
List<HInstruction> inputs = <HInstruction>[target, receiver];
push(new HInvokeInterceptor(selector, getterName, inputs, getter: true));
} else {
push(new HInvokeDynamicGetter(selector, null, receiver));
}
}
void generateGetter(Send send, Element element) {
if (Elements.isStaticOrTopLevelField(element)) {
if (element.kind == ElementKind.FIELD && !element.isAssignable()) {
// A static final. Get its constant value and inline it.
Constant value = compiler.constantHandler.compileVariable(element);
stack.add(graph.addConstant(value));
} else {
push(new HStatic(element));
if (element.kind == ElementKind.GETTER) {
push(new HInvokeStatic(<HInstruction>[pop()]));
}
}
} else if (Elements.isInstanceSend(send, elements)) {
HInstruction receiver = generateInstanceSendReceiver(send);
generateInstanceGetterWithCompiledReceiver(send, receiver);
} else if (Elements.isStaticOrTopLevelFunction(element)) {
push(new HStatic(element));
// TODO(ahe): This should be registered in codegen.
compiler.enqueuer.codegen.registerGetOfStaticFunction(element);
} else {
stack.add(localsHandler.readLocal(element));
}
}
void generateInstanceSetterWithCompiledReceiver(Send send,
HInstruction receiver,
HInstruction value) {
assert(Elements.isInstanceSend(send, elements));
Selector selector = elements.getSelector(send);
assert(selector.isSetter());
SourceString setterName = selector.name;
Element staticInterceptor = null;
if (methodInterceptionEnabled) {
staticInterceptor = interceptors.getStaticSetInterceptor(setterName);
}
if (staticInterceptor != null) {
HStatic target = new HStatic(staticInterceptor);
add(target);
List<HInstruction> inputs = <HInstruction>[target, receiver, value];
add(new HInvokeInterceptor(
selector, setterName, inputs, setter: true));
} else {
add(new HInvokeDynamicSetter(selector, null, receiver, value));
}
stack.add(value);
}
void generateSetter(SendSet send, Element element, HInstruction value) {
if (Elements.isStaticOrTopLevelField(element)) {
if (element.kind == ElementKind.SETTER) {
HStatic target = new HStatic(element);
add(target);
add(new HInvokeStatic(<HInstruction>[target, value]));
} else {
add(new HStaticStore(element, value));
}
stack.add(value);
} else if (element === null || Elements.isInstanceField(element)) {
HInstruction receiver = generateInstanceSendReceiver(send);
generateInstanceSetterWithCompiledReceiver(send, receiver, value);
} 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);
if (checked !== value) {
pop();
stack.add(checked);
}
localsHandler.updateLocal(element, checked);
}
}
void pushInvokeHelper0(Element helper) {
HInstruction reference = new HStatic(helper);
add(reference);
List<HInstruction> inputs = <HInstruction>[reference];
HInstruction result = new HInvokeStatic(inputs);
push(result);
}
void pushInvokeHelper1(Element helper, HInstruction a0) {
HInstruction reference = new HStatic(helper);
add(reference);
List<HInstruction> inputs = <HInstruction>[reference, a0];
HInstruction result = new HInvokeStatic(inputs);
push(result);
}
void pushInvokeHelper2(Element helper, HInstruction a0, HInstruction a1) {
HInstruction reference = new HStatic(helper);
add(reference);
List<HInstruction> inputs = <HInstruction>[reference, a0, a1];
HInstruction result = new HInvokeStatic(inputs);
push(result);
}
visitOperatorSend(node) {
assert(node.selector is Operator);
if (!methodInterceptionEnabled) {
visitDynamicSend(node);
return;
}
Operator op = node.selector;
if (const SourceString("[]") == op.source) {
HStatic target = new HStatic(interceptors.getIndexInterceptor());
add(target);
visit(node.receiver);
HInstruction receiver = pop();
visit(node.argumentsNode);
HInstruction index = pop();
push(new HIndex(target, receiver, index));
} 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) {
visit(node.receiver);
HInstruction expression = pop();
Node argument = node.arguments.head;
TypeAnnotation typeAnnotation = argument.asTypeAnnotation();
bool isNot = false;
// TODO(ngeoffray): Duplicating pattern in resolver. We should
// add a new kind of node.
if (typeAnnotation == null) {
typeAnnotation = argument.asSend().receiver;
isNot = true;
}
Type type = elements.getType(typeAnnotation);
HInstruction typeInfo = null;
if (compiler.codegenWorld.rti.hasTypeArguments(type)) {
pushInvokeHelper1(interceptors.getGetRuntimeTypeInfo(), expression);
typeInfo = pop();
}
if (type.element.kind === ElementKind.TYPE_VARIABLE) {
// TODO(karlklose): We emulate the frog behavior and answer
// true to any is check involving a type variable -- both is T
// and is !T -- until we have a proper implementation of
// reified generics.
stack.add(graph.addConstantBool(true));
} else {
HInstruction instruction;
if (typeInfo !== null) {
instruction = new HIs.withTypeInfoCall(type, expression, typeInfo);
} else {
instruction = new HIs(type, expression);
}
if (isNot) {
add(instruction);
instruction = new HNot(instruction);
}
push(instruction);
}
} else if (const SourceString("as") == op.source) {
visit(node.receiver);
HInstruction expression = pop();
Node argument = node.arguments.head;
TypeAnnotation typeAnnotation = argument.asTypeAnnotation();
Type type = elements.getType(typeAnnotation);
HInstruction converted = convertType(expression, type.element,
HTypeConversion.CAST_TYPE_CHECK);
stack.add(converted);
} else {
visit(node.receiver);
visit(node.argumentsNode);
var right = pop();
var left = pop();
visitBinary(left, op, right);
}
}
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]);
}
}
}
/**
* Returns true if the arguments were compatible with the function signature.
*/
bool addStaticSendArgumentsToList(Selector selector,
Link<Node> arguments,
FunctionElement element,
List<HInstruction> list) {
HInstruction compileArgument(Node argument) {
visit(argument);
return pop();
}
HInstruction compileConstant(Element constantElement) {
Constant constant = compiler.compileVariable(constantElement);
return graph.addConstant(constant);
}
return selector.addArgumentsToList(arguments,
list,
element,
compileArgument,
compileConstant,
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);
var inputs = <HInstruction>[];
SourceString dartMethodName;
bool isNotEquals = false;
if (node.isIndex && !node.arguments.tail.isEmpty()) {
dartMethodName = Elements.constructOperatorName(
const SourceString('operator'),
const SourceString('[]='));
} else if (node.selector.asOperator() != null) {
SourceString name = node.selector.asIdentifier().source;
isNotEquals = name.stringValue === '!=';
dartMethodName = Elements.constructOperatorName(
const SourceString('operator'),
name,
node.argumentsNode is Prefix);
} else {
dartMethodName = node.selector.asIdentifier().source;
}
Element interceptor = null;
if (methodInterceptionEnabled && node.receiver !== null) {
interceptor = interceptors.getStaticInterceptor(dartMethodName,
node.argumentCount());
}
if (interceptor != null) {
HStatic target = new HStatic(interceptor);
add(target);
inputs.add(target);
visit(node.receiver);
inputs.add(pop());
addGenericSendArgumentsToList(node.arguments, inputs);
push(new HInvokeInterceptor(selector, dartMethodName, inputs));
return;
}
if (node.receiver === null) {
inputs.add(localsHandler.readThis());
} else {
visit(node.receiver);
inputs.add(pop());
}
addDynamicSendArgumentsToList(node, inputs);
// The first entry in the inputs list is the receiver.
pushWithPosition(new HInvokeDynamicMethod(selector, inputs), node);
if (isNotEquals) {
HNot not = new HNot(popBoolified());
push(not);
}
}
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);
pushWithPosition(new HInvokeClosure(selector, 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.isEmpty()) {
compiler.cancel('At least two arguments expected',
node: node.argumentsNode);
}
link = link.tail.tail;
List<HInstruction> inputs = <HInstruction>[];
addGenericSendArgumentsToList(link, inputs);
Node type = node.arguments.head;
Node literal = node.arguments.tail.head;
if (literal is !StringNode || literal.dynamic.isInterpolation) {
compiler.cancel('JS code must be a string literal', node: literal);
}
if (type is !LiteralString) {
compiler.cancel(
'The type of a JS expression must be a string literal', node: type);
}
push(new HForeign(
literal.dynamic.dartString, type.dynamic.dartString, inputs));
}
void handleForeignUnintercepted(Send node) {
Link<Node> link = node.arguments;
if (!link.tail.isEmpty()) {
compiler.cancel(
'More than one expression in UNINTERCEPTED()', node: node);
}
Expression expression = link.head;
disableMethodInterception();
visit(expression);
enableMethodInterception();
}
void handleForeignJsHasEquals(Send node) {
List<HInstruction> inputs = <HInstruction>[];
if (!node.arguments.tail.isEmpty()) {
compiler.cancel(
'More than one expression in JS_HAS_EQUALS()', node: node);
}
addGenericSendArgumentsToList(node.arguments, inputs);
String name = compiler.namer.instanceMethodName(
currentLibrary, Elements.OPERATOR_EQUALS, 1);
push(new HForeign(new DartString.literal('!!#.$name'),
const LiteralDartString('bool'),
inputs));
}
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 Leg's current isolate.
String name = compiler.namer.CURRENT_ISOLATE;
push(new HForeign(new DartString.literal(name),
const LiteralDartString('var'),
<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.isolateLibrary.find(
const SourceString('_currentIsolate'));
if (element === null) {
compiler.cancel(
'Isolate library and compiler mismatch', node: node);
}
pushInvokeHelper0(element);
}
}
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.isolateLibrary.find(
const SourceString('_callInIsolate'));
if (element === null) {
compiler.cancel(
'Isolate library and compiler mismatch', node: node);
}
HStatic target = new HStatic(element);
add(target);
List<HInstruction> inputs = <HInstruction>[target];
addGenericSendArgumentsToList(link, inputs);
push(new HInvokeStatic(inputs));
}
}
void handleForeignDartClosureToJs(Send node) {
if (node.arguments.isEmpty() || !node.arguments.tail.isEmpty()) {
compiler.cancel('Exactly one argument required',
node: node.argumentsNode);
}
Node closure = node.arguments.head;
Element element = elements[closure];
if (!Elements.isStaticOrTopLevelFunction(element)) {
compiler.cancel(
'JS_TO_CLOSURE requires a static or top-level method',
node: closure);
}
FunctionElement function = element;
FunctionSignature params = function.computeSignature(compiler);
if (params.optionalParameterCount !== 0) {
compiler.cancel(
'JS_TO_CLOSURE does not handle closure with optional parameters',
node: closure);
}
visit(closure);
List<HInstruction> inputs = <HInstruction>[pop()];
String invocationName = compiler.namer.closureInvocationName(
new Selector.callClosure(params.requiredParameterCount));
push(new HForeign(new DartString.literal('#.$invocationName'),
const LiteralDartString('var'),
inputs));
}
visitForeignSend(Send node) {
Selector selector = elements.getSelector(node);
SourceString name = selector.name;
if (name == const SourceString('JS')) {
handleForeignJs(node);
} else if (name == const SourceString('UNINTERCEPTED')) {
handleForeignUnintercepted(node);
} else if (name == const SourceString('JS_HAS_EQUALS')) {
handleForeignJsHasEquals(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);
} else {
throw "Unknown foreign: ${selector}";
}
}
generateSuperNoSuchMethodSend(Send node) {
ClassElement cls = work.element.getEnclosingClass();
Element element = cls.lookupSuperMember(Compiler.NO_SUCH_METHOD);
HStatic target = new HStatic(element);
add(target);
HInstruction self = localsHandler.readThis();
Identifier identifier = node.selector.asIdentifier();
String name = identifier.source.slowToString();
// TODO(ahe): Add the arguments to this list.
push(new HLiteralList([]));
var inputs = <HInstruction>[
target,
self,
graph.addConstantString(new DartString.literal(name), node),
pop()];
push(new HInvokeSuper(inputs));
}
visitSend(Send node) {
Element element = elements[node];
if (element !== null && element === work.element) {
graph.isRecursiveMethod = true;
}
super.visitSend(node);
}
visitSuperSend(Send node) {
Selector selector = elements.getSelector(node);
Element element = elements[node];
if (element === null) return generateSuperNoSuchMethodSend(node);
HInstruction target = new HStatic(element);
HInstruction context = localsHandler.readThis();
add(target);
var inputs = <HInstruction>[target, context];
if (node.isPropertyAccess) {
push(new HInvokeSuper(inputs));
} else if (element.kind == ElementKind.FUNCTION ||
element.kind == ElementKind.GENERATIVE_CONSTRUCTOR) {
bool succeeded = addStaticSendArgumentsToList(selector, node.arguments,
element, inputs);
if (!succeeded) {
// TODO(ngeoffray): Match the VM behavior and throw an
// exception at runtime.
compiler.cancel('Unimplemented non-matching static call', node);
}
push(new HInvokeSuper(inputs));
} else {
target = new HInvokeSuper(inputs);
add(target);
inputs = <HInstruction>[target];
addDynamicSendArgumentsToList(node, inputs);
push(new HInvokeClosure(selector, inputs));
}
}
visitNewSend(Send node) {
computeType(element) {
Element originalElement = elements[node];
if (originalElement.getEnclosingClass() === compiler.listClass) {
if (node.arguments.isEmpty()) {
return HType.EXTENDABLE_ARRAY;
} else {
return HType.MUTABLE_ARRAY;
}
} else if (element.isGenerativeConstructor()) {
ClassElement cls = element.getEnclosingClass();
return new HBoundedType.exact(cls.type);
} else {
return HType.UNKNOWN;
}
}
Selector selector = elements.getSelector(node);
Element element = elements[node];
if (compiler.enqueuer.resolution.getCachedElements(element) === null) {
compiler.internalError("Unresolved element: $element", node: node);
}
FunctionElement functionElement = element;
element = functionElement.defaultImplementation;
HInstruction target = new HStatic(element);
add(target);
var inputs = <HInstruction>[];
inputs.add(target);
bool succeeded = addStaticSendArgumentsToList(selector, node.arguments,
element, inputs);
if (!succeeded) {
// TODO(ngeoffray): Match the VM behavior and throw an
// exception at runtime.
compiler.cancel('Unimplemented non-matching static call', node: node);
}
HType elementType = computeType(element);
HInstruction newInstance = new HInvokeStatic(inputs, elementType);
pushWithPosition(newInstance, node);
TypeAnnotation annotation = getTypeAnnotationFromSend(node);
Type type = elements.getType(annotation);
generateSetRuntimeTypeInformation(newInstance, type);
}
generateSetRuntimeTypeInformation(HInstruction instance, Type type) {
if (compiler.codegenWorld.rti.hasTypeArguments(type)) {
String typeString = compiler.codegenWorld.rti.asJsString(type);
HInstruction typeInfo = new HForeign(new LiteralDartString(typeString),
new LiteralDartString('Object'),
<HInstruction>[]);
add(typeInfo);
Element typeInfoSetterElement = interceptors.getSetRuntimeTypeInfo();
HInstruction typeInfoSetter = new HStatic(typeInfoSetterElement);
add(typeInfoSetter);
var inputs = <HInstruction>[typeInfoSetter, instance, typeInfo];
add(new HInvokeStatic(inputs));
}
}
visitStaticSend(Send node) {
Selector selector = elements.getSelector(node);
Element element = elements[node];
if (element === compiler.assertMethod && !compiler.enableUserAssertions) {
stack.add(graph.addConstantNull());
return;
}
compiler.ensure(element.kind !== ElementKind.GENERATIVE_CONSTRUCTOR);
HInstruction target = new HStatic(element);
add(target);
var inputs = <HInstruction>[];
inputs.add(target);
if (element.kind == ElementKind.FUNCTION) {
bool succeeded = addStaticSendArgumentsToList(selector, node.arguments,
element, inputs);
if (!succeeded) {
// TODO(ngeoffray): Match the VM behavior and throw an
// exception at runtime.
compiler.cancel('Unimplemented non-matching static call', node: node);
}
HInvokeStatic instruction = new HInvokeStatic(inputs);
HType returnType =
builder.backend.optimisticReturnTypesWithRecompilationOnTypeChange(
work.element, element);
if (returnType != null) instruction.guaranteedType = returnType;
pushWithPosition(instruction, node);
} else {
if (element.kind == ElementKind.GETTER) {
target = new HInvokeStatic(inputs);
add(target);
inputs = <HInstruction>[target];
}
addDynamicSendArgumentsToList(node, inputs);
pushWithPosition(new HInvokeClosure(selector, inputs), node);
}
}
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);
}
// TODO(karlklose): share with resolver.
TypeAnnotation getTypeAnnotationFromSend(Send send) {
if (send.selector is TypeAnnotation) {
return send.selector;
} else if (send.selector is Send) {
Send selector = send.selector;
if (selector.receiver is TypeAnnotation) {
return selector.receiver;
}
} else {
compiler.internalError("malformed send in new expression");
}
}
visitNewExpression(NewExpression node) {
Element element = elements[node.send];
if (Element.isInvalid(element)) {
// TODO(karlklose): generate runtime error or noSuchMethodCall, depending
// on whether element is null or it is an erroneous element with a
// particular error message.
compiler.cancel('Unimplemented unresolved constructor call', node: node);
} else if (node.isConst()) {
// TODO(karlklose): add type representation
ConstantHandler handler = compiler.constantHandler;
Constant constant = handler.compileNodeWithDefinitions(node, elements);
stack.add(graph.addConstant(constant));
} else {
visitNewSend(node.send);
}
}
visitSendSet(SendSet node) {
Operator op = node.assignmentOperator;
if (node.isSuperCall) {
Element element = elements[node];
if (element === null) return generateSuperNoSuchMethodSend(node);
HInstruction target = new HStatic(element);
HInstruction context = localsHandler.readThis();
add(target);
var inputs = <HInstruction>[target, context];
addDynamicSendArgumentsToList(node, inputs);
if (node.assignmentOperator.source.stringValue !== '=') {
compiler.unimplemented('complex super assignment',
node: node.assignmentOperator);
}
push(new HInvokeSuper(inputs, isSetter: true));
} else if (node.isIndex) {
if (!methodInterceptionEnabled) {
assert(op.source.stringValue === '=');
visitDynamicSend(node);
} else {
HStatic target = new HStatic(
interceptors.getIndexAssignmentInterceptor());
add(target);
visit(node.receiver);
HInstruction receiver = pop();
visit(node.argumentsNode);
if (const SourceString("=") == op.source) {
HInstruction value = pop();
HInstruction index = pop();
add(new HIndexAssign(target, receiver, index, value));
stack.add(value);
} else {
HInstruction value;
HInstruction index;
bool isCompoundAssignment = op.source.stringValue.endsWith('=');
// Compound assignments are considered as being prefix.
bool isPrefix = !node.isPostfix;
Element getter = elements[node.selector];
if (isCompoundAssignment) {
value = pop();
index = pop();
} else {
index = pop();
value = graph.addConstantInt(1);
}
HStatic indexMethod = new HStatic(interceptors.getIndexInterceptor());
add(indexMethod);
HInstruction left = new HIndex(indexMethod, receiver, index);
add(left);
Element opElement = elements[op];
visitBinary(left, op, value);
value = pop();
HInstruction assign = new HIndexAssign(
target, receiver, index, value);
add(assign);
if (isPrefix) {
stack.add(value);
} else {
stack.add(left);
}
}
}
} else if (const SourceString("=") == op.source) {
Element element = elements[node];
Link<Node> link = node.arguments;
assert(!link.isEmpty() && link.tail.isEmpty());
visit(link.head);
HInstruction value = pop();
generateSetter(node, element, value);
} else if (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("="));
Element element = elements[node];
bool isCompoundAssignment = !node.arguments.isEmpty();
bool isPrefix = !node.isPostfix; // Compound assignments are prefix.
// [receiver] is only used if the node is an instance send.
HInstruction receiver = null;
if (Elements.isInstanceSend(node, elements)) {
receiver = generateInstanceSendReceiver(node);
generateInstanceGetterWithCompiledReceiver(node, receiver);
} else {
generateGetter(node, elements[node.selector]);
}
HInstruction left = pop();
HInstruction right;
if (isCompoundAssignment) {
visit(node.argumentsNode);
right = pop();
} else {
right = graph.addConstantInt(1);
}
visitBinary(left, op, right);
HInstruction operation = pop();
assert(operation !== null);
if (Elements.isInstanceSend(node, elements)) {
assert(receiver !== null);
generateInstanceSetterWithCompiledReceiver(node, receiver, operation);
} else {
assert(receiver === null);
generateSetter(node, element, operation);
}
if (!isPrefix) {
pop();
stack.add(left);
}
}
}
void visitLiteralInt(LiteralInt node) {
stack.add(graph.addConstantInt(node.value));
}
void visitLiteralDouble(LiteralDouble node) {
stack.add(graph.addConstantDouble(node.value));
}
void visitLiteralBool(LiteralBool node) {
stack.add(graph.addConstantBool(node.value));
}
void visitLiteralString(LiteralString node) {
stack.add(graph.addConstantString(node.dartString, node));
}
void visitStringJuxtaposition(StringJuxtaposition node) {
if (!node.isInterpolation) {
// This is a simple string with no interpolations.
stack.add(graph.addConstantString(node.dartString, node));
return;
}
StringBuilderVisitor stringBuilder = new StringBuilderVisitor(this, node);
stringBuilder.visit(node);
stack.add(stringBuilder.result);
}
void visitLiteralNull(LiteralNull node) {
stack.add(graph.addConstantNull());
}
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();
}
visitReturn(Return node) {
if (node.getBeginToken().stringValue === 'native') {
native.handleSsaNative(this, node.expression);
return;
}
HInstruction value;
if (node.expression === null) {
value = graph.addConstantNull();
} else {
visit(node.expression);
value = pop();
}
close(attachPosition(new HReturn(value), node)).addSuccessor(graph.exit);
}
visitThrow(Throw node) {
if (node.expression === null) {
HInstruction exception = rethrowableException;
if (exception === null) {
exception = graph.addConstantNull();
compiler.reportError(node,
'throw without expression outside catch block');
}
close(new HThrow(exception, isRethrow: true));
} else {
visit(node.expression);
close(new HThrow(pop()));
}
}
visitTypeAnnotation(TypeAnnotation node) {
compiler.internalError('visiting type annotation in SSA builder',
node: node);
}
visitVariableDefinitions(VariableDefinitions node) {
for (Link<Node> link = node.definitions.nodes;
!link.isEmpty();
link = link.tail) {
Node definition = link.head;
if (definition is Identifier) {
HInstruction initialValue = graph.addConstantNull();
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, diagnosticNode: 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());
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) {
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.
*/
JumpHandler createJumpHandler(Statement node) {
TargetElement element = elements[node];
if (element === null || element.statement !== node) {
// No breaks or continues to this node.
return new NullJumpHandler(compiler);
}
return new JumpHandler(this, element);
}
visitForIn(ForIn node) {
// Generate a structure equivalent to:
// Iterator<E> $iter = <iterable>.iterator()
// while ($iter.hasNext()) {
// E <declaredIdentifier> = $iter.next();
// <body>
// }
// The iterator is shared between initializer, condition and body.
HInstruction iterator;
void buildInitializer() {
SourceString iteratorName = const SourceString("iterator");
Element interceptor = interceptors.getStaticInterceptor(iteratorName, 0);
assert(interceptor != null);
visit(node.expression);
pushInvokeHelper1(interceptor, pop());
iterator = pop();
}
HInstruction buildCondition() {
SourceString name = const SourceString('hasNext');
Selector call = new Selector.call(name, work.element.getLibrary(), 0);
push(new HInvokeDynamicMethod(call, <HInstruction>[iterator]));
return popBoolified();
}
void buildBody() {
SourceString name = const SourceString('next');
Selector call = new Selector.call(name, work.element.getLibrary(), 0);
push(new HInvokeDynamicMethod(call, <HInstruction>[iterator]));
Element variable;
if (node.declaredIdentifier.asSend() !== null) {
variable = elements[node.declaredIdentifier];
} else {
assert(node.declaredIdentifier.asVariableDefinitions() !== null);
VariableDefinitions variableDefinitions = node.declaredIdentifier;
variable = elements[variableDefinitions.definitions.nodes.head];
}
localsHandler.updateLocal(variable, pop());
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 || 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();
hackAroundPossiblyAbortingBody(node, () { visit(body); });
SubGraph bodyGraph = new SubGraph(entryBlock, lastOpenedBlock);
HBasicBlock joinBlock = graph.addNewBlock();
List<LocalsHandler> breakLocals = <LocalsHandler>[];
handler.forEachBreak((HBreak breakInstruction, LocalsHandler locals) {
breakInstruction.block.addSuccessor(joinBlock);
breakLocals.add(locals);
});
bool hasBreak = breakLocals.length > 0;
if (!isAborted()) {
goto(current, joinBlock);
breakLocals.add(localsHandler);
}
open(joinBlock);
localsHandler = beforeLocals.mergeMultiple(breakLocals, 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.addLast(pop());
inputs.addLast(pop());
}
HLiteralList keyValuePairs = new HLiteralList(inputs);
add(keyValuePairs);
pushInvokeHelper1(interceptors.getMapMaker(), keyValuePairs);
}
visitLiteralMapEntry(LiteralMapEntry node) {
visit(node.value);
visit(node.key);
}
visitNamedArgument(NamedArgument node) {
visit(node.expression);
}
visitSwitchStatement(SwitchStatement node) {
if (tryBuildConstantSwitch(node)) return;
LocalsHandler savedLocals = new LocalsHandler.from(localsHandler);
HBasicBlock startBlock = openNewBlock();
visit(node.expression);
HInstruction expression = pop();
if (node.cases.isEmpty()) {
return;
}
Link<Node> cases = node.cases.nodes;
JumpHandler jumpHandler = createJumpHandler(node);
buildSwitchCases(cases, expression);
HBasicBlock lastBlock = lastOpenedBlock;
// Create merge block for break targets.
HBasicBlock joinBlock = new HBasicBlock();
List<LocalsHandler> caseLocals = <LocalsHandler>[];
jumpHandler.forEachBreak((HBreak instruction, LocalsHandler locals) {
instruction.block.addSuccessor(joinBlock);
caseLocals.add(locals);
});
if (!isAborted()) {
// 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).
caseLocals.add(localsHandler);
goto(current, joinBlock);
}
if (caseLocals.length != 0) {
graph.addBlock(joinBlock);
open(joinBlock);
if (caseLocals.length == 1) {
localsHandler = caseLocals[0];
} else {
localsHandler = savedLocals.mergeMultiple(caseLocals, joinBlock);
}
} else {
// The joinblock is not used.
joinBlock = null;
}
startBlock.setBlockFlow(
new HLabeledBlockInformation.implicit(
new HSubGraphBlockInformation(new SubGraph(startBlock, lastBlock)),
elements[node]),
joinBlock);
jumpHandler.close();
}
bool tryBuildConstantSwitch(SwitchStatement node) {
Map<CaseMatch, Constant> constants = new Map<CaseMatch, Constant>();
// First check whether all case expressions are compile-time constants.
for (SwitchCase switchCase in node.cases) {
for (Node labelOrCase in switchCase.labelsAndCases) {
if (labelOrCase is CaseMatch) {
CaseMatch match = labelOrCase;
Constant constant =
compiler.constantHandler.tryCompileNodeWithDefinitions(
match.expression, elements);
if (constant === null) return false;
constants[labelOrCase] = constant;
} else {
// We don't handle labels yet.
return false;
}
}
}
// TODO(ngeoffray): Handle switch-instruction in bailout code.
work.allowSpeculativeOptimization = false;
// Then build a switch structure.
HBasicBlock expressionStart = openNewBlock();
visit(node.expression);
HInstruction expression = pop();
if (node.cases.isEmpty()) {
return true;
}
HBasicBlock expressionEnd = current;
HSwitch switchInstruction = new HSwitch(<HInstruction>[expression]);
HBasicBlock expressionBlock = close(switchInstruction);
JumpHandler jumpHandler = createJumpHandler(node);
LocalsHandler savedLocals = localsHandler;
List<List<Constant>> matchExpressions = <List<Constant>>[];
List<HStatementInformation> statements = <HStatementInformation>[];
bool hasDefault = false;
Element getFallThroughErrorElement =
compiler.findHelper(const SourceString("getFallThroughError"));
Iterator<Node> caseIterator = node.cases.iterator();
while (caseIterator.hasNext()) {
SwitchCase switchCase = caseIterator.next();
List<Constant> caseConstants = <Constant>[];
HBasicBlock block = graph.addNewBlock();
for (Node labelOrCase in switchCase.labelsAndCases) {
if (labelOrCase is CaseMatch) {
Constant constant = constants[labelOrCase];
caseConstants.add(constant);
HConstant hConstant = graph.addConstant(constant);
switchInstruction.inputs.add(hConstant);
hConstant.usedBy.add(switchInstruction);
expressionBlock.addSuccessor(block);
}
}
matchExpressions.add(caseConstants);
if (switchCase.isDefaultCase) {
// 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);
visit(switchCase.statements);
if (!isAborted() && caseIterator.hasNext()) {
pushInvokeHelper0(getFallThroughErrorElement);
HInstruction error = pop();
close(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 [caseLocals] that is used
// to create the phis in [joinBlock].
// If we never jump to the join block, [caseLocals] will stay empty, and
// the join block is never added to the graph.
HBasicBlock joinBlock = new HBasicBlock();
List<LocalsHandler> caseLocals = <LocalsHandler>[];
jumpHandler.forEachBreak((HBreak instruction, LocalsHandler locals) {
instruction.block.addSuccessor(joinBlock);
caseLocals.add(locals);
});
if (!isAborted()) {
current.close(new HGoto());
lastOpenedBlock.addSuccessor(joinBlock);
caseLocals.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);
caseLocals.add(savedLocals);
}
assert(caseLocals.length == joinBlock.predecessors.length);
if (caseLocals.length != 0) {
graph.addBlock(joinBlock);
open(joinBlock);
if (caseLocals.length == 1) {
localsHandler = caseLocals[0];
} else {
localsHandler = savedLocals.mergeMultiple(caseLocals, 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();
return true;
}
// Recursively build an if/else structure to match the cases.
void buildSwitchCases(Link<Node> cases, HInstruction expression,
[int encounteredCaseTypes = 0]) {
final int NO_TYPE = 0;
final int INT_TYPE = 1;
final int STRING_TYPE = 2;
final int CONFLICT_TYPE = 3;
int combine(int type1, int type2) => type1 | type2;
SwitchCase node = cases.head;
// Called for the statements on all but the last case block.
// Ensures that a user expecting a fallthrough gets an error.
void visitStatementsAndAbort() {
visit(node.statements);
if (!isAborted()) {
compiler.reportWarning(node, 'Missing break at end of switch case');
Element element =
compiler.findHelper(const SourceString("getFallThroughError"));
pushInvokeHelper0(element);
HInstruction error = pop();
close(new HThrow(error));
}
}
Link<Node> skipLabels(Link<Node> labelsAndCases) {
while (!labelsAndCases.isEmpty() && labelsAndCases.head is Label) {
labelsAndCases = labelsAndCases.tail;
}
return labelsAndCases;
}
Link<Node> labelsAndCases = skipLabels(node.labelsAndCases.nodes);
if (labelsAndCases.isEmpty()) {
// Default case with no expressions.
if (!node.isDefaultCase) {
compiler.internalError("Case with no expression and not default",
node: node);
}
visit(node.statements);
// This must be the final case (otherwise "default" would be invalid),
// so we don't need to check for fallthrough.
return;
}
// Recursively build the test conditions. Leaves the result on the
// expression stack.
void buildTests(Link<Node> remainingCases) {
// Build comparison for one case expression.
void left() {
Element equalsHelper = interceptors.getEqualsInterceptor();
HInstruction target = new HStatic(equalsHelper);
add(target);
CaseMatch match = remainingCases.head;
// TODO(lrn): Move the constant resolution to the resolver, so
// we can report an error before reaching the backend.
Constant constant =
compiler.constantHandler.tryCompileNodeWithDefinitions(
match.expression, elements);
if (constant !== null) {
if (constant.isInt()) {
// Report the first mixed-string/int type error only.
if (encounteredCaseTypes == STRING_TYPE) {
compiler.reportWarning(
match, MessageKind.INVALID_CASE_EXPRESSION_TYPE);
}
encounteredCaseTypes = combine(encounteredCaseTypes, INT_TYPE);
} else if (constant.isString()) {
if (encounteredCaseTypes == INT_TYPE) {
compiler.reportWarning(
match, MessageKind.INVALID_CASE_EXPRESSION_TYPE);
}
encounteredCaseTypes = combine(encounteredCaseTypes, STRING_TYPE);
} else {
compiler.reportWarning(match,
MessageKind.INVALID_CASE_EXPRESSION);
encounteredCaseTypes = CONFLICT_TYPE;
}
stack.add(graph.addConstant(constant));
} else {
// TODO(lrn): Remove this else branch, and make the constant
// evaluation mandatory when we are ready to break existing code using
// non constant-int-or-string expressions.
compiler.reportWarning(match,
'case expressions not compile-time constant int or string.');
visit(match.expression);
encounteredCaseTypes = CONFLICT_TYPE;
}
push(new HEquals(target, pop(), expression));
}
// If this is the last expression, just return it.
Link<Node> tail = skipLabels(remainingCases.tail);
if (tail.isEmpty()) {
left();
return;
}
void right() {
buildTests(tail);
}
SsaBranchBuilder branchBuilder =
new SsaBranchBuilder(this, remainingCases.head);
branchBuilder.handleLogicalAndOr(left, right, isAnd: false);
}
if (node.isDefaultCase) {
// Default case must be last.
assert(cases.tail.isEmpty());
// Perform the tests until one of them match, but then always execute the
// statements.
// TODO(lrn): Stop performing tests when all expressions are compile-time
// constant strings or integers.
handleIf(node, () { buildTests(labelsAndCases); }, (){}, null);
visit(node.statements);
} else {
if (cases.tail.isEmpty()) {
handleIf(node,
() { buildTests(labelsAndCases); },
() { visit(node.statements); },
null);
} else {
handleIf(node,
() { buildTests(labelsAndCases); },
() { visitStatementsAndAbort(); },
() { buildSwitchCases(cases.tail, expression,
encounteredCaseTypes); });
}
}
}
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();
List<HBasicBlock> blocks = <HBasicBlock>[];
blocks.add(close(tryInstruction));
HBasicBlock tryBody = graph.addNewBlock();
enterBlock.addSuccessor(tryBody);
open(tryBody);
visit(node.tryBlock);
if (!isAborted()) blocks.add(close(new HGoto()));
SubGraph bodyGraph = new SubGraph(tryBody, lastOpenedBlock);
SubGraph catchGraph = null;
HParameterValue exception = null;
if (!node.catchBlocks.isEmpty()) {
localsHandler = new LocalsHandler.from(savedLocals);
HBasicBlock block = graph.addNewBlock();
enterBlock.addSuccessor(block);
open(block);
// Note that the name of this element is irrelevant.
Element element = new Element(
const SourceString('exception'), ElementKind.PARAMETER, work.element);
exception = new HParameterValue(element);
add(exception);
HInstruction oldRethrowableException = rethrowableException;
rethrowableException = exception;
pushInvokeHelper1(interceptors.getExceptionUnwrapper(), exception);
HInvokeStatic unwrappedException = pop();
tryInstruction.exception = exception;
Link<Node> link = node.catchBlocks.nodes;
void pushCondition(CatchBlock catchBlock) {
if (catchBlock.onKeyword != null) {
Type type = elements.getType(catchBlock.type);
if (type == null) {
compiler.cancel('On with unresolved type',
node: catchBlock.type);
}
HInstruction condition = new HIs(type, unwrappedException);
push(condition);
}
else {
VariableDefinitions declaration = catchBlock.formals.nodes.head;
HInstruction condition = null;
if (declaration.type == null) {
condition = graph.addConstantBool(true);
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
Type type = elements.getType(declaration.type);
if (type == null) {
compiler.cancel('Catch with unresolved type', node: catchBlock);
}
condition = new HIs(type, unwrappedException, nullOk: true);
push(condition);
}
}
}
void visitThen() {
CatchBlock catchBlock = link.head;
link = link.tail;
localsHandler.updateLocal(elements[catchBlock.exception],
unwrappedException);
Node trace = catchBlock.trace;
if (trace != null) {
pushInvokeHelper1(interceptors.getTraceFromException(), exception);
HInstruction traceInstruction = pop();
localsHandler.updateLocal(elements[trace], traceInstruction);
}
visit(catchBlock);
}
void visitElse() {
if (link.isEmpty()) {
close(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()) blocks.add(close(new HGoto()));
rethrowableException = oldRethrowableException;
tryInstruction.catchBlock = block;
catchGraph = new SubGraph(block, lastOpenedBlock);
}
SubGraph finallyGraph = null;
if (node.finallyBlock != null) {
localsHandler = new LocalsHandler.from(savedLocals);
HBasicBlock finallyBlock = graph.addNewBlock();
enterBlock.addSuccessor(finallyBlock);
open(finallyBlock);
visit(node.finallyBlock);
if (!isAborted()) blocks.add(close(new HGoto()));
tryInstruction.finallyBlock = finallyBlock;
finallyGraph = new SubGraph(finallyBlock, lastOpenedBlock);
}
HBasicBlock exitBlock = graph.addNewBlock();
for (HBasicBlock block in blocks) {
block.addSuccessor(exitBlock);
}
// 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);
}
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');
}
HType mapInferredType(Element element) {
if (element === builder.compiler.boolClass) return HType.BOOLEAN;
if (element === builder.compiler.doubleClass) return HType.DOUBLE;
if (element === builder.compiler.intClass) return HType.INTEGER;
if (element === builder.compiler.listClass) return HType.READABLE_ARRAY;
if (element === builder.compiler.nullClass) return HType.NULL;
if (element === builder.compiler.stringClass) return HType.STRING;
return HType.UNKNOWN;
}
/** HACK HACK HACK */
void hackAroundPossiblyAbortingBody(Node statement, void body()) {
visitCondition() {
stack.add(graph.addConstantBool(true));
}
buildBody() {
// TODO(lrn): Make sure to take continue into account.
body();
}
handleIf(statement, visitCondition, buildBody, null);
}
}
/**
* 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 AbstractVisitor {
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();
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;
}
}
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(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);
HPhi result = new HPhi.manyInputs(null,
<HInstruction>[boolifiedRight, builder.graph.addConstantBool(!isAnd)]);
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);
} else {
handleLogicalAndOr(() => builder.visit(left), visitRight, 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;
}
}