blob: e3412386c8318480078a0f35297013ea52eda4ae [file] [log] [blame]
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
class SsaCodeGeneratorTask extends CompilerTask {
SsaCodeGeneratorTask(Compiler compiler) : super(compiler);
String get name() => 'SSA code generator';
String generateMethod(WorkItem work, HGraph graph) {
return measure(() {
compiler.tracer.traceGraph("codegen", graph);
Map<Element, String> parameterNames = getParameterNames(work);
String parameters = Strings.join(parameterNames.getValues(), ', ');
SsaOptimizedCodeGenerator codegen = new SsaOptimizedCodeGenerator(
compiler, work, parameters, parameterNames);
codegen.visitGraph(graph);
return 'function($parameters) {\n${codegen.buffer}}';
});
}
String generateBailoutMethod(WorkItem work, HGraph graph) {
return measure(() {
compiler.tracer.traceGraph("codegen-bailout", graph);
new SsaBailoutPropagator(compiler).visitGraph(graph);
Map<Element, String> parameterNames = getParameterNames(work);
String parameters = Strings.join(parameterNames.getValues(), ', ');
SsaUnoptimizedCodeGenerator codegen = new SsaUnoptimizedCodeGenerator(
compiler, work, parameters, parameterNames);
codegen.visitGraph(graph);
StringBuffer newParameters = new StringBuffer();
if (!parameterNames.isEmpty()) newParameters.add('$parameters, ');
newParameters.add('state');
for (int i = 0; i < codegen.maxBailoutParameters; i++) {
newParameters.add(', env$i');
}
return 'function($newParameters) {\n${codegen.setup}${codegen.buffer}}';
});
}
Map<Element, String> getParameterNames(WorkItem work) {
Map<Element, String> parameterNames = new LinkedHashMap<Element, String>();
FunctionElement function = work.element;
// The dom/html libraries have inline JS code that reference
// parameter names directly. Long-term such code will be rejected.
// Now, just don't mangle the parameter name.
function.computeParameters(compiler).forEachParameter((Element element) {
parameterNames[element] = function.isNative()
? element.name.slowToString()
: JsNames.getValid('${element.name.slowToString()}');
});
return parameterNames;
}
}
typedef void ElementAction(Element element);
class SsaCodeGenerator implements HVisitor {
final Compiler compiler;
final WorkItem work;
final StringBuffer buffer;
final String parameters;
final Map<Element, String> parameterNames;
final Map<int, String> names;
final Map<String, int> prefixes;
final Set<HInstruction> generateAtUseSite;
final Map<HPhi, String> logicalOperations;
final Map<Element, ElementAction> breakAction;
final Map<Element, ElementAction> continueAction;
Element equalsNullElement;
int indent = 0;
int expectedPrecedence = JSPrecedence.STATEMENT_PRECEDENCE;
HGraph currentGraph;
HBasicBlock currentBlock;
// Records a block-information that is being handled specially.
// Used to break bad recursion.
HLabeledBlockInformation currentBlockInformation;
// The subgraph is used to delimit traversal for some constructions, e.g.,
// if branches.
SubGraph subGraph;
LibraryElement get currentLibrary() => work.element.getLibrary();
bool isGenerateAtUseSite(HInstruction instruction) {
return generateAtUseSite.contains(instruction);
}
SsaCodeGenerator(this.compiler,
this.work,
this.parameters,
this.parameterNames)
: names = new Map<int, String>(),
prefixes = new Map<String, int>(),
buffer = new StringBuffer(),
generateAtUseSite = new Set<HInstruction>(),
logicalOperations = new Map<HPhi, String>(),
breakAction = new Map<Element, ElementAction>(),
continueAction = new Map<Element, ElementAction>() {
for (final name in parameterNames.getValues()) {
prefixes[name] = 0;
}
equalsNullElement =
compiler.builder.interceptors.getEqualsNullInterceptor();
}
abstract visitTypeGuard(HTypeGuard node);
abstract beginGraph(HGraph graph);
abstract endGraph(HGraph graph);
abstract beginLoop(HBasicBlock block);
abstract endLoop(HBasicBlock block);
abstract handleLoopCondition(HLoopBranch node);
abstract startIf(HIf node);
abstract endIf(HIf node);
abstract startThen(HIf node);
abstract endThen(HIf node);
abstract startElse(HIf node);
abstract endElse(HIf node);
void beginExpression(int precedence) {
if (precedence < expectedPrecedence) {
buffer.add('(');
}
}
void endExpression(int precedence) {
if (precedence < expectedPrecedence) {
buffer.add(')');
}
}
void preGenerateMethod(HGraph graph) {
new SsaInstructionMerger(generateAtUseSite).visitGraph(graph);
new SsaConditionMerger(generateAtUseSite,
logicalOperations).visitGraph(graph);
}
visitGraph(HGraph graph) {
preGenerateMethod(graph);
currentGraph = graph;
indent++; // We are already inside a function.
subGraph = new SubGraph(graph.entry, graph.exit);
beginGraph(graph);
visitBasicBlock(graph.entry);
endGraph(graph);
}
void visitSubGraph(SubGraph newSubGraph) {
SubGraph oldSubGraph = subGraph;
subGraph = newSubGraph;
visitBasicBlock(subGraph.start);
subGraph = oldSubGraph;
}
String temporary(HInstruction instruction) {
int id = instruction.id;
String name = names[id];
if (name !== null) return name;
if (instruction is HPhi) {
HPhi phi = instruction;
Element element = phi.element;
if (element != null && element.kind == ElementKind.PARAMETER) {
name = parameterNames[element];
names[id] = name;
return name;
}
String prefix;
if (element !== null && !element.name.isEmpty()) {
prefix = element.name.slowToString();
} else {
prefix = 'v';
}
if (!prefixes.containsKey(prefix)) {
prefixes[prefix] = 0;
return newName(id, prefix);
} else {
return newName(id, '${prefix}_${prefixes[prefix]++}');
}
} else {
String prefix = 't';
if (!prefixes.containsKey(prefix)) prefixes[prefix] = 0;
return newName(id, '${prefix}${prefixes[prefix]++}');
}
}
bool temporaryExists(HInstruction instruction) {
return names.containsKey(instruction.id);
}
String newName(int id, String name) {
String result = JsNames.getValid(name);
names[id] = result;
return result;
}
/**
* Only visits the arguments starting at inputs[HInvoke.ARGUMENTS_OFFSET].
*/
void visitArguments(List<HInstruction> inputs) {
assert(inputs.length >= HInvoke.ARGUMENTS_OFFSET);
buffer.add('(');
for (int i = HInvoke.ARGUMENTS_OFFSET; i < inputs.length; i++) {
if (i != HInvoke.ARGUMENTS_OFFSET) buffer.add(', ');
use(inputs[i], JSPrecedence.ASSIGNMENT_PRECEDENCE);
}
buffer.add(')');
}
void define(HInstruction instruction) {
buffer.add('var ${temporary(instruction)} = ');
visit(instruction, JSPrecedence.ASSIGNMENT_PRECEDENCE);
}
void use(HInstruction argument, int expectedPrecedence) {
if (isGenerateAtUseSite(argument)) {
visit(argument, expectedPrecedence);
} else if (argument is HIntegerCheck) {
HIntegerCheck instruction = argument;
use(instruction.value, expectedPrecedence);
} else if (argument is HBoundsCheck) {
HBoundsCheck instruction = argument;
use(instruction.index, expectedPrecedence);
} else if (argument is HTypeGuard) {
HTypeGuard instruction = argument;
use(instruction.guarded, expectedPrecedence);
} else {
buffer.add(temporary(argument));
}
}
visit(HInstruction node, int expectedPrecedence) {
int oldPrecedence = this.expectedPrecedence;
this.expectedPrecedence = expectedPrecedence;
node.accept(this);
this.expectedPrecedence = oldPrecedence;
}
void continueAsBreak(LabelElement target) {
addIndentation();
buffer.add("break ");
writeContinueLabel(target);
buffer.add(";\n");
}
void implicitContinueAsBreak(TargetElement target) {
addIndentation();
buffer.add("break ");
writeImplicitContinueLabel(target);
buffer.add(";\n");
}
void implicitBreakWithLabel(TargetElement target) {
addIndentation();
buffer.add("break ");
writeImplicitLabel(target);
buffer.add(";\n");
}
void handleLabeledBlock(HLabeledBlockInformation labeledBlockInfo) {
addIndentation();
Link<Element> continueOverrides = const EmptyLink<Element>();
// If [labeledBlockInfo.isContinue], the block is an artificial
// block around the body of a loop with an update block, so that
// continues of the loop can be written as breaks of the body
// block.
if (labeledBlockInfo.isContinue) {
for (LabelElement label in labeledBlockInfo.labels) {
if (label.isContinueTarget) {
writeContinueLabel(label);
buffer.add(':');
continueAction[label] = continueAsBreak;
continueOverrides = continueOverrides.prepend(label);
}
}
// For handling unlabeled continues from the body of a loop.
// TODO(lrn): Consider recording whether the target is in fact
// a target of an unlabeled continue, and not generate this if it isn't.
TargetElement target = labeledBlockInfo.target;
writeImplicitContinueLabel(target);
buffer.add(':');
continueAction[target] = implicitContinueAsBreak;
continueOverrides = continueOverrides.prepend(target);
} else {
for (LabelElement label in labeledBlockInfo.labels) {
if (label.isBreakTarget) {
writeLabel(label);
buffer.add(':');
}
}
TargetElement target = labeledBlockInfo.target;
if (target.isSwitch) {
// This is an extra block around a switch that is generated
// as a nested if/else chain. We add an extra break target
// so that case code can break.
writeImplicitLabel(target);
buffer.add(':');
breakAction[target] = implicitBreakWithLabel;
}
}
buffer.add('{\n');
indent++;
visitSubGraph(labeledBlockInfo.body);
indent--;
addIndentation();
buffer.add('}\n');
if (labeledBlockInfo.joinBlock !== null) {
visitBasicBlock(labeledBlockInfo.joinBlock);
}
if (labeledBlockInfo.isContinue) {
while (!continueOverrides.isEmpty()) {
continueAction.remove(continueOverrides.head);
continueOverrides = continueOverrides.tail;
}
} else {
breakAction.remove(labeledBlockInfo.target);
}
}
void emitLogicalOperation(HPhi node, String operation) {
JSBinaryOperatorPrecedence operatorPrecedence =
JSPrecedence.binary[operation];
beginExpression(operatorPrecedence.precedence);
use(node.inputs[0], operatorPrecedence.left);
buffer.add(" $operation ");
use(node.inputs[1], operatorPrecedence.right);
endExpression(operatorPrecedence.precedence);
}
visitBasicBlock(HBasicBlock node) {
// Abort traversal if we are leaving the currently active sub-graph.
if (!subGraph.contains(node)) return;
// If this node has special behavior attached, handle it.
// If we reach here again while handling the attached information,
// e.g., because we call visitSubGraph on a subgraph starting here,
// don't handle it again.
if (node.hasLabeledBlockInformation() &&
node.labeledBlockInformation !== currentBlockInformation) {
HLabeledBlockInformation oldBlockInformation = currentBlockInformation;
currentBlockInformation = node.labeledBlockInformation;
handleLabeledBlock(currentBlockInformation);
currentBlockInformation = oldBlockInformation;
return;
}
currentBlock = node;
if (node.isLoopHeader()) {
// While loop will be closed by the conditional loop-branch.
// TODO(floitsch): HACK HACK HACK.
beginLoop(node);
}
HInstruction instruction = node.first;
while (instruction != null) {
if (instruction === node.last) {
for (HBasicBlock successor in node.successors) {
int index = successor.predecessors.indexOf(node);
successor.forEachPhi((HPhi phi) {
bool isLogicalOperation = logicalOperations.containsKey(phi);
// In case the phi is being generated by another
// instruction.
if (isLogicalOperation && isGenerateAtUseSite(phi)) return;
addIndentation();
if (!temporaryExists(phi)) buffer.add('var ');
buffer.add('${temporary(phi)} = ');
if (isLogicalOperation) {
emitLogicalOperation(phi, logicalOperations[phi]);
} else {
use(phi.inputs[index], JSPrecedence.ASSIGNMENT_PRECEDENCE);
}
buffer.add(';\n');
});
}
}
if (instruction is HGoto || instruction is HExit || instruction is HTry) {
visit(instruction, JSPrecedence.STATEMENT_PRECEDENCE);
return;
} else if (!isGenerateAtUseSite(instruction)) {
if (instruction is !HIf && instruction is !HTypeGuard) {
addIndentation();
}
if (instruction.usedBy.isEmpty()
|| instruction is HTypeGuard
|| instruction is HCheck) {
visit(instruction, JSPrecedence.STATEMENT_PRECEDENCE);
} else {
define(instruction);
}
// Control flow instructions know how to handle ';'.
if (instruction is !HControlFlow && instruction is !HTypeGuard) {
buffer.add(';\n');
}
} else if (instruction is HIf) {
HIf hif = instruction;
// The "if" is implementing part of a logical expression.
// Skip directly forward to to its latest successor, since everything
// in-between must also be generateAtUseSite.
assert(hif.trueBranch.id < hif.falseBranch.id);
visitBasicBlock(hif.falseBranch);
return;
}
instruction = instruction.next;
}
}
visitInvokeBinary(HInvokeBinary node, String op) {
if (node.builtin) {
JSBinaryOperatorPrecedence operatorPrecedences = JSPrecedence.binary[op];
beginExpression(operatorPrecedences.precedence);
use(node.left, operatorPrecedences.left);
buffer.add(' $op ');
use(node.right, operatorPrecedences.right);
endExpression(operatorPrecedences.precedence);
} else {
visitInvokeStatic(node);
}
}
visitInvokeUnary(HInvokeUnary node, String op) {
if (node.builtin) {
beginExpression(JSPrecedence.PREFIX_PRECEDENCE);
buffer.add('$op');
use(node.operand, JSPrecedence.PREFIX_PRECEDENCE);
endExpression(JSPrecedence.PREFIX_PRECEDENCE);
} else {
visitInvokeStatic(node);
}
}
visitEquals(HEquals node) {
if (node.builtin) {
beginExpression(JSPrecedence.EQUALITY_PRECEDENCE);
use(node.left, JSPrecedence.EQUALITY_PRECEDENCE);
buffer.add(' === ');
use(node.right, JSPrecedence.RELATIONAL_PRECEDENCE);
endExpression(JSPrecedence.EQUALITY_PRECEDENCE);
} else if (node.element === equalsNullElement) {
beginExpression(JSPrecedence.CALL_PRECEDENCE);
use(node.target, JSPrecedence.CALL_PRECEDENCE);
buffer.add('(');
use(node.left, JSPrecedence.ASSIGNMENT_PRECEDENCE);
buffer.add(')');
endExpression(JSPrecedence.CALL_PRECEDENCE);
} else {
visitInvokeStatic(node);
}
}
visitAdd(HAdd node) => visitInvokeBinary(node, '+');
visitDivide(HDivide node) => visitInvokeBinary(node, '/');
visitMultiply(HMultiply node) => visitInvokeBinary(node, '*');
visitSubtract(HSubtract node) => visitInvokeBinary(node, '-');
// Truncating divide does not have a JS equivalent.
visitTruncatingDivide(HTruncatingDivide node) => visitInvokeStatic(node);
// Modulo cannot be mapped to the native operator (different semantics).
visitModulo(HModulo node) => visitInvokeStatic(node);
visitBitAnd(HBitAnd node) => visitInvokeBinary(node, '&');
visitBitNot(HBitNot node) => visitInvokeUnary(node, '~');
visitBitOr(HBitOr node) => visitInvokeBinary(node, '|');
visitBitXor(HBitXor node) => visitInvokeBinary(node, '^');
// We need to check if the left operand is negative in order to use
// the native operator.
visitShiftRight(HShiftRight node) => visitInvokeStatic(node);
// Shift left cannot be mapped to the native operator (different semantics).
visitShiftLeft(HShiftLeft node) => visitInvokeStatic(node);
visitNegate(HNegate node) => visitInvokeUnary(node, '-');
visitIdentity(HIdentity node) => visitInvokeBinary(node, '===');
visitLess(HLess node) => visitInvokeBinary(node, '<');
visitLessEqual(HLessEqual node) => visitInvokeBinary(node, '<=');
visitGreater(HGreater node) => visitInvokeBinary(node, '>');
visitGreaterEqual(HGreaterEqual node) => visitInvokeBinary(node, '>=');
visitBoolify(HBoolify node) {
beginExpression(JSPrecedence.EQUALITY_PRECEDENCE);
assert(node.inputs.length == 1);
use(node.inputs[0], JSPrecedence.EQUALITY_PRECEDENCE);
buffer.add(' === true');
endExpression(JSPrecedence.EQUALITY_PRECEDENCE);
}
visitExit(HExit node) {
// Don't do anything.
}
visitGoto(HGoto node) {
assert(currentBlock.successors.length == 1);
List<HBasicBlock> dominated = currentBlock.dominatedBlocks;
// With the exception of the entry-node which dominates its successor
// and the exit node, no block finishing with a 'goto' can have more than
// one dominated block (since it has only one successor).
// If the successor is dominated by another block, then the other block
// is responsible for visiting the successor.
if (dominated.isEmpty()) return;
if (dominated.length > 2) unreachable();
if (dominated.length == 2 && currentBlock !== currentGraph.entry) {
unreachable();
}
assert(dominated[0] == currentBlock.successors[0]);
visitBasicBlock(dominated[0]);
}
// Used to write the name of labels.
void writeLabel(LabelElement label) {
buffer.add('\$${label.labelName}\$${label.target.nestingLevel}');
}
void writeImplicitLabel(TargetElement target) {
buffer.add('\$${target.nestingLevel}');
}
// We sometimes handle continue targets differently from break targets,
// so we have special continue-only labels.
void writeContinueLabel(LabelElement label) {
buffer.add('c\$${label.labelName}\$${label.target.nestingLevel}');
}
void writeImplicitContinueLabel(TargetElement target) {
buffer.add('c\$${target.nestingLevel}');
}
/**
* Checks if [map] contains an [ElementAction] for [element], and
* if so calls that action and returns true.
* Otherwise returns false.
*/
bool tryCallAction(Map<Element, ElementAction> map, Element element) {
ElementAction action = map[element];
if (action === null) return false;
action(element);
return true;
}
visitBreak(HBreak node) {
assert(currentBlock.successors.length == 1);
if (node.label !== null) {
LabelElement label = node.label;
if (!tryCallAction(breakAction, label)) {
addIndentation();
buffer.add("break ");
writeLabel(label);
buffer.add(";\n");
}
} else {
TargetElement target = node.target;
if (!tryCallAction(breakAction, target)) {
addIndentation();
buffer.add("break;\n");
}
}
}
visitContinue(HContinue node) {
assert(currentBlock.successors.length == 1);
if (node.label !== null) {
LabelElement label = node.label;
if (!tryCallAction(continueAction, label)) {
addIndentation();
buffer.add("continue ");
writeLabel(label);
buffer.add(";\n");
}
} else {
TargetElement target = node.target;
if (!tryCallAction(continueAction, target)) {
addIndentation();
buffer.add("continue;\n");
}
}
}
visitTry(HTry node) {
addIndentation();
buffer.add('try {\n');
indent++;
List<HBasicBlock> successors = node.block.successors;
visitBasicBlock(successors[0]);
indent--;
if (node.finallyBlock != successors[1]) {
// Printing the catch part.
addIndentation();
String name = temporary(node.exception);
parameterNames[node.exception.element] = name;
buffer.add('} catch ($name) {\n');
indent++;
visitBasicBlock(successors[1]);
parameterNames.remove(node.exception.element);
indent--;
}
if (node.finallyBlock != null) {
addIndentation();
buffer.add('} finally {\n');
indent++;
visitBasicBlock(node.finallyBlock);
indent--;
}
addIndentation();
buffer.add('}\n');
visitBasicBlock(node.joinBlock);
}
visitIf(HIf node) {
List<HBasicBlock> dominated = node.block.dominatedBlocks;
HIfBlockInformation info = node.blockInformation;
startIf(node);
assert(!isGenerateAtUseSite(node));
startThen(node);
assert(node.thenBlock === dominated[0]);
visitSubGraph(info.thenGraph);
int preVisitedBlocks = 1;
endThen(node);
if (node.hasElse) {
startElse(node);
assert(node.elseBlock === dominated[1]);
visitSubGraph(info.elseGraph);
preVisitedBlocks = 2;
endElse(node);
}
endIf(node);
if (info.joinBlock !== null && info.joinBlock.dominator !== node.block) {
// The join block is dominated by a block in one of the branches.
// The subgraph traversal never reached it, so we visit it here
// instead.
visitBasicBlock(info.joinBlock);
}
// Visit all the dominated blocks that are not part of the then or else
// branches, and is not the join block.
// Depending on how the then/else branches terminate
// (e.g., return/throw/break) there can be any number of these.
int dominatedCount = dominated.length;
for (int i = preVisitedBlocks; i < dominatedCount; i++) {
HBasicBlock dominatedBlock = dominated[i];
assert(dominatedBlock.dominator === node.block);
visitBasicBlock(dominatedBlock);
}
}
visitInvokeDynamicMethod(HInvokeDynamicMethod node) {
beginExpression(JSPrecedence.CALL_PRECEDENCE);
use(node.receiver, JSPrecedence.MEMBER_PRECEDENCE);
buffer.add('.');
// Avoid adding the generative constructor name to the list of
// seen selectors.
if (node.inputs[0] is HForeignNew) {
HForeignNew foreignNew = node.inputs[0];
// Remove 'this' from the number of arguments.
int argumentCount = node.inputs.length - 1;
// TODO(ahe): The constructor name was statically resolved in
// SsaBuilder.buildFactory. Is there a cleaner way to do this?
node.name.printOn(buffer);
visitArguments(node.inputs);
} else {
buffer.add(compiler.namer.instanceMethodInvocationName(
currentLibrary, node.name, node.selector));
visitArguments(node.inputs);
compiler.registerDynamicInvocation(node.name, node.selector);
}
endExpression(JSPrecedence.CALL_PRECEDENCE);
}
visitInvokeDynamicSetter(HInvokeDynamicSetter node) {
beginExpression(JSPrecedence.CALL_PRECEDENCE);
use(node.receiver, JSPrecedence.MEMBER_PRECEDENCE);
buffer.add('.');
buffer.add(compiler.namer.setterName(currentLibrary, node.name));
visitArguments(node.inputs);
compiler.registerDynamicSetter(node.name);
endExpression(JSPrecedence.CALL_PRECEDENCE);
}
visitInvokeDynamicGetter(HInvokeDynamicGetter node) {
beginExpression(JSPrecedence.CALL_PRECEDENCE);
use(node.receiver, JSPrecedence.MEMBER_PRECEDENCE);
buffer.add('.');
buffer.add(compiler.namer.getterName(currentLibrary, node.name));
visitArguments(node.inputs);
compiler.registerDynamicGetter(node.name);
endExpression(JSPrecedence.CALL_PRECEDENCE);
}
visitInvokeClosure(HInvokeClosure node) {
beginExpression(JSPrecedence.CALL_PRECEDENCE);
use(node.receiver, JSPrecedence.MEMBER_PRECEDENCE);
buffer.add('.');
buffer.add(compiler.namer.closureInvocationName(node.selector));
visitArguments(node.inputs);
// TODO(floitsch): we should have a separate list for closure invocations.
compiler.registerDynamicInvocation(Namer.CLOSURE_INVOCATION_NAME,
node.selector);
endExpression(JSPrecedence.CALL_PRECEDENCE);
}
visitInvokeStatic(HInvokeStatic node) {
beginExpression(JSPrecedence.CALL_PRECEDENCE);
use(node.target, JSPrecedence.CALL_PRECEDENCE);
visitArguments(node.inputs);
endExpression(JSPrecedence.CALL_PRECEDENCE);
}
visitInvokeSuper(HInvokeSuper node) {
beginExpression(JSPrecedence.CALL_PRECEDENCE);
Element superMethod = node.element;
Element superClass = superMethod.enclosingElement;
// Remove the element and 'this'.
int argumentCount = node.inputs.length - 2;
String className = compiler.namer.isolatePropertyAccess(superClass);
String methodName;
if (superMethod.kind == ElementKind.FUNCTION ||
superMethod.kind == ElementKind.GENERATIVE_CONSTRUCTOR) {
methodName = compiler.namer.instanceMethodName(
currentLibrary, superMethod.name, argumentCount);
} else {
methodName = compiler.namer.getterName(currentLibrary, superMethod.name);
// We need to register the name to ensure that the emitter
// generates the necessary getter.
// TODO(ahe): This is not optimal for tree-shaking, but we lack
// API to register the precise information. In this case, the
// enclosingElement of superMethod needs the getter, no other
// class (not even its subclasses).
compiler.registerDynamicGetter(superMethod.name);
}
buffer.add('$className.prototype.$methodName.call');
visitArguments(node.inputs);
endExpression(JSPrecedence.CALL_PRECEDENCE);
compiler.registerStaticUse(superMethod);
}
visitFieldGet(HFieldGet node) {
String name = JsNames.getValid(node.element.name.slowToString());
if (node.receiver !== null) {
beginExpression(JSPrecedence.MEMBER_PRECEDENCE);
use(node.receiver, JSPrecedence.MEMBER_PRECEDENCE);
buffer.add('.');
buffer.add(name);
beginExpression(JSPrecedence.MEMBER_PRECEDENCE);
} else {
buffer.add(name);
}
}
visitFieldSet(HFieldSet node) {
if (node.receiver !== null) {
beginExpression(JSPrecedence.ASSIGNMENT_PRECEDENCE);
use(node.receiver, JSPrecedence.MEMBER_PRECEDENCE);
buffer.add('.');
} else {
// TODO(ngeoffray): Remove the 'var' once we don't globally box
// variables used in a try/catch.
buffer.add('var ');
}
String name = JsNames.getValid(node.element.name.slowToString());
buffer.add(name);
buffer.add(' = ');
use(node.value, JSPrecedence.ASSIGNMENT_PRECEDENCE);
if (node.receiver !== null) {
endExpression(JSPrecedence.ASSIGNMENT_PRECEDENCE);
}
}
visitForeign(HForeign node) {
String code = node.code.slowToString();
List<HInstruction> inputs = node.inputs;
List<String> parts = code.split('#');
if (parts.length != inputs.length + 1) {
compiler.internalError(
'Wrong number of arguments for JS', instruction: node);
}
beginExpression(JSPrecedence.EXPRESSION_PRECEDENCE);
buffer.add(parts[0]);
for (int i = 0; i < inputs.length; i++) {
use(inputs[i], JSPrecedence.EXPRESSION_PRECEDENCE);
buffer.add(parts[i + 1]);
}
endExpression(JSPrecedence.EXPRESSION_PRECEDENCE);
}
visitForeignNew(HForeignNew node) {
String jsClassReference = compiler.namer.isolateAccess(node.element);
beginExpression(JSPrecedence.MEMBER_PRECEDENCE);
buffer.add('new $jsClassReference(');
// We can't use 'visitArguments', since our arguments start at input[0].
List<HInstruction> inputs = node.inputs;
for (int i = 0; i < inputs.length; i++) {
if (i != 0) buffer.add(', ');
use(inputs[i], JSPrecedence.ASSIGNMENT_PRECEDENCE);
}
buffer.add(')');
endExpression(JSPrecedence.MEMBER_PRECEDENCE);
}
visitConstant(HConstant node) {
assert(isGenerateAtUseSite(node));
// TODO(floitsch): the compile-time constant handler and the codegen
// need to work together to avoid the parenthesis. See r4928 for an
// implementation that still dealt with precedence.
ConstantHandler handler = compiler.constantHandler;
String name = handler.getNameForConstant(node.constant);
if (name === null) {
assert(!node.constant.isObject());
if (node.constant.isNum()
&& expectedPrecedence == JSPrecedence.MEMBER_PRECEDENCE) {
buffer.add('(');
node.constant.writeJsCode(buffer, handler);
buffer.add(')');
} else {
node.constant.writeJsCode(buffer, handler);
}
} else {
buffer.add(compiler.namer.CURRENT_ISOLATE);
buffer.add(".");
buffer.add(name);
}
}
visitLoopBranch(HLoopBranch node) {
HBasicBlock branchBlock = currentBlock;
handleLoopCondition(node);
List<HBasicBlock> dominated = currentBlock.dominatedBlocks;
// For a do while loop, the body has already been visited.
if (!node.isDoWhile()) {
visitBasicBlock(dominated[0]);
}
endLoop(node.block);
visitBasicBlock(branchBlock.successors[1]);
// With labeled breaks we can have more dominated blocks.
if (dominated.length >= 3) {
for (int i = 2; i < dominated.length; i++) {
visitBasicBlock(dominated[i]);
}
}
}
visitNot(HNot node) {
assert(node.inputs.length == 1);
beginExpression(JSPrecedence.PREFIX_PRECEDENCE);
buffer.add('!');
use(node.inputs[0], JSPrecedence.PREFIX_PRECEDENCE);
endExpression(JSPrecedence.PREFIX_PRECEDENCE);
}
visitParameterValue(HParameterValue node) {
assert(isGenerateAtUseSite(node));
buffer.add(parameterNames[node.element]);
}
visitPhi(HPhi node) {
String operation = logicalOperations[node];
if (operation !== null) {
emitLogicalOperation(node, operation);
} else {
buffer.add('${temporary(node)}');
}
}
visitReturn(HReturn node) {
assert(node.inputs.length == 1);
HInstruction input = node.inputs[0];
if (input.isConstantNull()) {
buffer.add('return;\n');
} else {
buffer.add('return ');
use(node.inputs[0], JSPrecedence.EXPRESSION_PRECEDENCE);
buffer.add(';\n');
}
}
visitThis(HThis node) {
buffer.add('this');
}
visitThrow(HThrow node) {
if (node.isRethrow) {
buffer.add('throw ');
use(node.inputs[0], JSPrecedence.EXPRESSION_PRECEDENCE);
} else {
generateThrowWithHelper('captureStackTrace', node.inputs[0]);
}
buffer.add(';\n');
}
visitBoundsCheck(HBoundsCheck node) {
buffer.add('if (');
use(node.index, JSPrecedence.RELATIONAL_PRECEDENCE);
buffer.add(' < 0 || ');
use(node.index, JSPrecedence.RELATIONAL_PRECEDENCE);
buffer.add(' >= ');
use(node.length, JSPrecedence.SHIFT_PRECEDENCE);
buffer.add(") ");
generateThrowWithHelper('ioore', node.index);
}
visitIntegerCheck(HIntegerCheck node) {
buffer.add('if (');
use(node.value, JSPrecedence.EQUALITY_PRECEDENCE);
buffer.add(' !== (');
use(node.value, JSPrecedence.BITWISE_OR_PRECEDENCE);
buffer.add(" | 0)) ");
generateThrowWithHelper('iae', node.value);
}
void generateThrowWithHelper(String helperName, HInstruction argument) {
Element helper = compiler.findHelper(new SourceString(helperName));
compiler.registerStaticUse(helper);
buffer.add('throw ');
beginExpression(JSPrecedence.EXPRESSION_PRECEDENCE);
beginExpression(JSPrecedence.CALL_PRECEDENCE);
buffer.add(compiler.namer.isolateAccess(helper));
visitArguments([null, argument]);
endExpression(JSPrecedence.CALL_PRECEDENCE);
endExpression(JSPrecedence.EXPRESSION_PRECEDENCE);
}
void addIndentation() {
for (int i = 0; i < indent; i++) {
buffer.add(' ');
}
}
void visitStatic(HStatic node) {
compiler.registerStaticUse(node.element);
buffer.add(compiler.namer.isolateAccess(node.element));
}
void visitStaticStore(HStaticStore node) {
compiler.registerStaticUse(node.element);
beginExpression(JSPrecedence.ASSIGNMENT_PRECEDENCE);
buffer.add(compiler.namer.isolateAccess(node.element));
buffer.add(' = ');
use(node.inputs[0], JSPrecedence.ASSIGNMENT_PRECEDENCE);
endExpression(JSPrecedence.ASSIGNMENT_PRECEDENCE);
}
void visitLiteralList(HLiteralList node) {
if (node.isConst) {
// TODO(floitsch): Remove this when CTC handles arrays.
SourceString name = new SourceString('makeLiteralListConst');
Element helper = compiler.findHelper(name);
compiler.registerStaticUse(helper);
beginExpression(JSPrecedence.CALL_PRECEDENCE);
buffer.add(compiler.namer.isolateAccess(helper));
buffer.add('(');
generateArrayLiteral(node);
buffer.add(')');
endExpression(JSPrecedence.CALL_PRECEDENCE);
} else {
generateArrayLiteral(node);
}
}
void generateArrayLiteral(HLiteralList node) {
buffer.add('[');
int len = node.inputs.length;
for (int i = 0; i < len; i++) {
if (i != 0) buffer.add(', ');
use(node.inputs[i], JSPrecedence.ASSIGNMENT_PRECEDENCE);
}
buffer.add(']');
}
void visitIndex(HIndex node) {
if (node.builtin) {
beginExpression(JSPrecedence.MEMBER_PRECEDENCE);
use(node.inputs[1], JSPrecedence.MEMBER_PRECEDENCE);
buffer.add('[');
use(node.inputs[2], JSPrecedence.EXPRESSION_PRECEDENCE);
buffer.add(']');
endExpression(JSPrecedence.MEMBER_PRECEDENCE);
} else {
visitInvokeStatic(node);
}
}
void visitIndexAssign(HIndexAssign node) {
if (node.builtin) {
beginExpression(JSPrecedence.ASSIGNMENT_PRECEDENCE);
use(node.inputs[1], JSPrecedence.MEMBER_PRECEDENCE);
buffer.add('[');
use(node.inputs[2], JSPrecedence.EXPRESSION_PRECEDENCE);
buffer.add('] = ');
use(node.inputs[3], JSPrecedence.ASSIGNMENT_PRECEDENCE);
endExpression(JSPrecedence.ASSIGNMENT_PRECEDENCE);
} else {
visitInvokeStatic(node);
}
}
void visitInvokeInterceptor(HInvokeInterceptor node) {
if (node.builtinJsName != null) {
beginExpression(JSPrecedence.CALL_PRECEDENCE);
use(node.inputs[1], JSPrecedence.MEMBER_PRECEDENCE);
buffer.add('.');
buffer.add(node.builtinJsName);
if (node.getter) return;
buffer.add('(');
for (int i = 2; i < node.inputs.length; i++) {
if (i != 2) buffer.add(', ');
use(node.inputs[i], JSPrecedence.ASSIGNMENT_PRECEDENCE);
}
buffer.add(")");
endExpression(JSPrecedence.CALL_PRECEDENCE);
} else {
return visitInvokeStatic(node);
}
}
void checkInt(HInstruction input, String cmp) {
beginExpression(JSPrecedence.EQUALITY_PRECEDENCE);
use(input, JSPrecedence.EQUALITY_PRECEDENCE);
buffer.add(' $cmp (');
use(input, JSPrecedence.BITWISE_OR_PRECEDENCE);
buffer.add(' | 0)');
endExpression(JSPrecedence.EQUALITY_PRECEDENCE);
}
void checkNum(HInstruction input, String cmp) {
beginExpression(JSPrecedence.EQUALITY_PRECEDENCE);
buffer.add('typeof ');
use(input, JSPrecedence.PREFIX_PRECEDENCE);
buffer.add(" $cmp 'number'");
endExpression(JSPrecedence.EQUALITY_PRECEDENCE);
}
void checkDouble(HInstruction input, String cmp) {
checkNum(input, cmp);
}
void checkString(HInstruction input, String cmp) {
beginExpression(JSPrecedence.EQUALITY_PRECEDENCE);
buffer.add('typeof ');
use(input, JSPrecedence.PREFIX_PRECEDENCE);
buffer.add(" $cmp 'string'");
endExpression(JSPrecedence.EQUALITY_PRECEDENCE);
}
void checkBool(HInstruction input, String cmp) {
beginExpression(JSPrecedence.EQUALITY_PRECEDENCE);
buffer.add('typeof ');
use(input, JSPrecedence.PREFIX_PRECEDENCE);
buffer.add(" $cmp 'boolean'");
endExpression(JSPrecedence.EQUALITY_PRECEDENCE);
}
void checkObject(HInstruction input, String cmp) {
beginExpression(JSPrecedence.EQUALITY_PRECEDENCE);
buffer.add('typeof ');
use(input, JSPrecedence.PREFIX_PRECEDENCE);
buffer.add(" $cmp 'object'");
endExpression(JSPrecedence.EQUALITY_PRECEDENCE);
}
void checkArray(HInstruction input, String cmp) {
beginExpression(JSPrecedence.EQUALITY_PRECEDENCE);
use(input, JSPrecedence.MEMBER_PRECEDENCE);
buffer.add('.constructor $cmp Array');
endExpression(JSPrecedence.EQUALITY_PRECEDENCE);
}
void checkNull(HInstruction input) {
beginExpression(JSPrecedence.EQUALITY_PRECEDENCE);
use(input, JSPrecedence.EQUALITY_PRECEDENCE);
buffer.add(" === (void 0)");
endExpression(JSPrecedence.EQUALITY_PRECEDENCE);
}
void checkFunction(HInstruction input, Element element) {
beginExpression(JSPrecedence.LOGICAL_OR_PRECEDENCE);
beginExpression(JSPrecedence.EQUALITY_PRECEDENCE);
buffer.add('typeof ');
use(input, JSPrecedence.PREFIX_PRECEDENCE);
buffer.add(" === 'function'");
endExpression(JSPrecedence.EQUALITY_PRECEDENCE);
buffer.add(" || ");
beginExpression(JSPrecedence.LOGICAL_AND_PRECEDENCE);
checkObject(input, '===');
buffer.add(" && ");
checkType(input, element);
endExpression(JSPrecedence.LOGICAL_AND_PRECEDENCE);
endExpression(JSPrecedence.LOGICAL_OR_PRECEDENCE);
}
void checkType(HInstruction input, Element element) {
bool requiresNativeIsCheck =
compiler.emitter.nativeEmitter.requiresNativeIsCheck(element);
if (!requiresNativeIsCheck) buffer.add('!!');
use(input, JSPrecedence.MEMBER_PRECEDENCE);
buffer.add('.');
buffer.add(compiler.namer.operatorIs(element));
if (requiresNativeIsCheck) buffer.add('()');
}
void handleStringSupertypeCheck(HInstruction input, Element element) {
// Make sure List and String don't share supertypes, otherwise we
// would need to check for List too.
assert(element !== compiler.listClass
&& !Elements.isListSupertype(element, compiler));
beginExpression(JSPrecedence.LOGICAL_OR_PRECEDENCE);
checkString(input, '===');
buffer.add(' || ');
beginExpression(JSPrecedence.LOGICAL_AND_PRECEDENCE);
checkObject(input, '===');
buffer.add(' && ');
checkType(input, element);
endExpression(JSPrecedence.LOGICAL_AND_PRECEDENCE);
endExpression(JSPrecedence.LOGICAL_OR_PRECEDENCE);
}
void handleListOrSupertypeCheck(HInstruction input, Element element) {
// Make sure List and String don't share supertypes, otherwise we
// would need to check for String too.
assert(element !== compiler.stringClass
&& !Elements.isStringSupertype(element, compiler));
beginExpression(JSPrecedence.LOGICAL_AND_PRECEDENCE);
checkObject(input, '===');
buffer.add(' && (');
beginExpression(JSPrecedence.LOGICAL_OR_PRECEDENCE);
checkArray(input, '===');
buffer.add(' || ');
checkType(input, element);
buffer.add(')');
endExpression(JSPrecedence.LOGICAL_OR_PRECEDENCE);
endExpression(JSPrecedence.LOGICAL_AND_PRECEDENCE);
}
void visitIs(HIs node) {
Element element = node.typeExpression;
if (element.kind === ElementKind.TYPE_VARIABLE) {
compiler.unimplemented("visitIs for type variables");
}
compiler.registerIsCheck(element);
LibraryElement coreLibrary = compiler.coreLibrary;
ClassElement objectClass = coreLibrary.find(const SourceString('Object'));
HInstruction input = node.expression;
if (node.nullOk) {
beginExpression(JSPrecedence.LOGICAL_OR_PRECEDENCE);
checkNull(input);
buffer.add(' || ');
}
if (element === objectClass || element === compiler.dynamicClass) {
// The constant folder also does this optimization, but we make
// it safe by assuming it may have not run.
buffer.add('true');
} else if (element == compiler.stringClass) {
checkString(input, '===');
} else if (element == compiler.doubleClass) {
checkDouble(input, '===');
} else if (element == compiler.numClass) {
checkNum(input, '===');
} else if (element == compiler.boolClass) {
checkBool(input, '===');
} else if (element == compiler.functionClass) {
checkFunction(input, element);
} else if (element == compiler.intClass) {
beginExpression(JSPrecedence.LOGICAL_AND_PRECEDENCE);
checkNum(input, '===');
buffer.add(' && ');
checkInt(input, '===');
endExpression(JSPrecedence.LOGICAL_AND_PRECEDENCE);
} else if (Elements.isStringSupertype(element, compiler)) {
handleStringSupertypeCheck(input, element);
} else if (element === compiler.listClass
|| Elements.isListSupertype(element, compiler)) {
handleListOrSupertypeCheck(input, element);
} else {
beginExpression(JSPrecedence.LOGICAL_AND_PRECEDENCE);
checkObject(input, '===');
buffer.add(' && ');
checkType(input, element);
endExpression(JSPrecedence.LOGICAL_AND_PRECEDENCE);
}
if (node.nullOk) {
endExpression(JSPrecedence.LOGICAL_OR_PRECEDENCE);
}
}
}
class SsaOptimizedCodeGenerator extends SsaCodeGenerator {
SsaOptimizedCodeGenerator(compiler, work, parameters, parameterNames)
: super(compiler, work, parameters, parameterNames);
void beginGraph(HGraph graph) {}
void endGraph(HGraph graph) {}
void bailout(HTypeGuard guard, String reason) {
HInstruction input = guard.guarded;
Namer namer = compiler.namer;
Element element = work.element;
buffer.add('return ');
if (element.isInstanceMember()) {
// TODO(ngeoffray): This does not work in case we come from a
// super call. We must make bailout names unique.
buffer.add('this.${namer.getBailoutName(element)}');
} else {
buffer.add(namer.isolateBailoutAccess(element));
}
int parametersCount = parameterNames.length;
buffer.add('($parameters');
if (parametersCount != 0) buffer.add(', ');
if (guard.guarded is !HParameterValue) {
buffer.add('${guard.state}');
bool first = true;
// TODO(ngeoffray): if the bailout method takes more arguments,
// fill the remaining arguments with undefined.
// TODO(ngeoffray): try to put a variable at a deterministic
// location, so that multiple bailout calls put the variable at
// the same parameter index.
for (int i = 0; i < guard.inputs.length; i++) {
buffer.add(', ');
use(guard.inputs[i], JSPrecedence.ASSIGNMENT_PRECEDENCE);
}
} else {
assert(guard.guarded is HParameterValue);
buffer.add(' 0');
}
buffer.add(')');
}
void visitTypeGuard(HTypeGuard node) {
addIndentation();
HInstruction input = node.guarded;
assert(!isGenerateAtUseSite(input) || input.isCodeMotionInvariant());
if (node.isInteger()) {
buffer.add('if (');
checkInt(input, '!==');
buffer.add(') ');
bailout(node, 'Not an integer');
} else if (node.isNumber()) {
buffer.add('if (');
checkNum(input, '!==');
buffer.add(') ');
bailout(node, 'Not a number');
} else if (node.isBoolean()) {
buffer.add('if (');
checkBool(input, '!==');
buffer.add(') ');
bailout(node, 'Not a boolean');
} else if (node.isString()) {
buffer.add('if (');
checkString(input, '!==');
buffer.add(') ');
bailout(node, 'Not a string');
} else if (node.isArray()) {
buffer.add('if (');
checkObject(input, '!==');
buffer.add('||');
checkArray(input, '!==');
buffer.add(') ');
bailout(node, 'Not an array');
} else if (node.isStringOrArray()) {
buffer.add('if (');
checkString(input, '!==');
buffer.add(' && (');
checkObject(input, '!==');
buffer.add('||');
checkArray(input, '!==');
buffer.add(')) ');
bailout(node, 'Not a string or array');
} else {
unreachable();
}
buffer.add(';\n');
}
void beginLoop(HBasicBlock block) {
addIndentation();
for (LabelElement label in block.loopInformation.labels) {
writeLabel(label);
buffer.add(":");
}
buffer.add('while (true) {\n');
indent++;
}
void endLoop(HBasicBlock block) {
indent--;
addIndentation();
buffer.add('}\n'); // Close 'while' loop.
}
void handleLoopCondition(HLoopBranch node) {
buffer.add('if (!');
use(node.inputs[0], JSPrecedence.PREFIX_PRECEDENCE);
buffer.add(') break;\n');
}
void startIf(HIf node) {
}
void endIf(HIf node) {
indent--;
addIndentation();
buffer.add('}\n');
}
void startThen(HIf node) {
addIndentation();
buffer.add('if (');
use(node.inputs[0], JSPrecedence.EXPRESSION_PRECEDENCE);
buffer.add(') {\n');
indent++;
}
void endThen(HIf node) {
}
void startElse(HIf node) {
indent--;
addIndentation();
buffer.add('} else {\n');
indent++;
}
void endElse(HIf node) {
}
}
class SsaUnoptimizedCodeGenerator extends SsaCodeGenerator {
final StringBuffer setup;
final List<String> labels;
int labelId = 0;
int maxBailoutParameters = 0;
SsaUnoptimizedCodeGenerator(compiler, work, parameters, parameterNames)
: super(compiler, work, parameters, parameterNames),
setup = new StringBuffer(),
labels = <String>[];
String pushLabel() {
String label = 'L${labelId++}';
labels.addLast(label);
return label;
}
String popLabel() {
return labels.removeLast();
}
String currentLabel() {
return labels.last();
}
void beginGraph(HGraph graph) {
if (!graph.entry.hasGuards()) return;
addIndentation();
buffer.add('switch (state) {\n');
indent++;
addIndentation();
buffer.add('case 0:\n');
indent++;
// The setup phase of a bailout function sets up the environment for
// each bailout target. Each bailout target will populate this
// setup phase. It is put at the beginning of the function.
setup.add(' switch (state) {\n');
}
void endGraph(HGraph graph) {
if (!graph.entry.hasGuards()) return;
indent--; // Close original case.
indent--;
addIndentation();
buffer.add('}\n'); // Close 'switch'.
setup.add(' }\n');
}
// For instructions that reference a guard or a check, we change that
// reference to the instruction they guard against. Therefore, we must
// use that instruction when restoring the environment.
HInstruction unwrap(HInstruction argument) {
if (argument is HIntegerCheck) {
HIntegerCheck instruction = argument;
return unwrap(instruction.value);
} else if (argument is HBoundsCheck) {
HBoundsCheck instruction = argument;
return unwrap(instruction.index);
} else if (argument is HTypeGuard) {
HTypeGuard instruction = argument;
return unwrap(instruction.guarded);
} else {
return argument;
}
}
void visitTypeGuard(HTypeGuard node) {
indent--;
addIndentation();
buffer.add('case ${node.state}:\n');
indent++;
addIndentation();
buffer.add('state = 0;\n');
setup.add(' case ${node.state}:\n');
int i = 0;
for (HInstruction input in node.inputs) {
HInstruction instruction = unwrap(input);
setup.add(' ${temporary(instruction)} = env$i;\n');
i++;
}
if (i > maxBailoutParameters) maxBailoutParameters = i;
setup.add(' break;\n');
}
void startBailoutCase(List<HTypeGuard> bailouts1,
List<HTypeGuard> bailouts2) {
indent--;
handleBailoutCase(bailouts1);
handleBailoutCase(bailouts2);
indent++;
}
void handleBailoutCase(List<HTypeGuard> guards) {
for (int i = 0, len = guards.length; i < len; i++) {
addIndentation();
buffer.add('case ${guards[i].state}:\n');
}
}
void startBailoutSwitch() {
addIndentation();
buffer.add('switch (state) {\n');
indent++;
addIndentation();
buffer.add('case 0:\n');
indent++;
}
void endBailoutSwitch() {
indent--; // Close 'case'.
indent--;
addIndentation();
buffer.add('}\n'); // Close 'switch'.
}
void beginLoop(HBasicBlock block) {
// TODO(ngeoffray): Don't put labels on loops that don't bailout.
String newLabel = pushLabel();
if (block.hasGuards()) {
startBailoutCase(block.guards, const <HTypeGuard>[]);
}
addIndentation();
for (SourceString label in block.loopInformation.labels) {
writeLabel(label);
buffer.add(":");
}
buffer.add('$newLabel: while (true) {\n');
indent++;
if (block.hasGuards()) {
startBailoutSwitch();
}
}
void endLoop(HBasicBlock block) {
popLabel();
HBasicBlock header = block.isLoopHeader() ? block : block.parentLoopHeader;
if (header.hasGuards()) {
endBailoutSwitch();
}
indent--;
addIndentation();
buffer.add('}\n'); // Close 'while'.
}
void handleLoopCondition(HLoopBranch node) {
buffer.add('if (!');
use(node.inputs[0], JSPrecedence.PREFIX_PRECEDENCE);
buffer.add(') break ${currentLabel()};\n');
}
void startIf(HIf node) {
bool hasGuards = node.thenBlock.hasGuards()
|| (node.hasElse && node.elseBlock.hasGuards());
if (hasGuards) {
startBailoutCase(node.thenBlock.guards,
node.hasElse ? node.elseBlock.guards : const <HTypeGuard>[]);
}
}
void endIf(HIf node) {
indent--;
addIndentation();
buffer.add('}\n');
}
void startThen(HIf node) {
addIndentation();
bool hasGuards = node.thenBlock.hasGuards()
|| (node.hasElse && node.elseBlock.hasGuards());
buffer.add('if (');
int precedence = JSPrecedence.EXPRESSION_PRECEDENCE;
if (hasGuards) {
// TODO(ngeoffray): Put the condition initialization in the
// [setup] buffer.
List<HTypeGuard> guards = node.thenBlock.guards;
for (int i = 0, len = guards.length; i < len; i++) {
buffer.add('state == ${guards[i].state} || ');
}
buffer.add('(state == 0 && ');
precedence = JSPrecedence.BITWISE_OR_PRECEDENCE;
}
use(node.inputs[0], precedence);
if (hasGuards) {
buffer.add(')');
}
buffer.add(') {\n');
indent++;
if (node.thenBlock.hasGuards()) {
startBailoutSwitch();
}
}
void endThen(HIf node) {
if (node.thenBlock.hasGuards()) {
endBailoutSwitch();
}
}
void startElse(HIf node) {
indent--;
addIndentation();
buffer.add('} else {\n');
indent++;
if (node.elseBlock.hasGuards()) {
startBailoutSwitch();
}
}
void endElse(HIf node) {
if (node.elseBlock.hasGuards()) {
endBailoutSwitch();
}
}
}