Implement constant switch as JS switch.

This introduces a block-structure for constant switches.
Non-constant switches still use the old if-else-if implementation.

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

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@8331 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
lrn@google.com 2012-06-06 13:14:59 +00:00
parent 931f116dfe
commit 745cb6085f
5 changed files with 261 additions and 12 deletions

View file

@ -2913,6 +2913,8 @@ class SsaBuilder extends ResolvedVisitor implements Visitor {
}
visitSwitchStatement(SwitchStatement node) {
if (tryBuildConstantSwitch(node)) return;
LocalsHandler savedLocals = new LocalsHandler.from(localsHandler);
HBasicBlock startBlock = openNewBlock();
visit(node.expression);
@ -2920,8 +2922,8 @@ class SsaBuilder extends ResolvedVisitor implements Visitor {
if (node.cases.isEmpty()) {
return;
}
Link<Node> cases = node.cases.nodes;
Link<Node> cases = node.cases.nodes;
JumpHandler jumpHandler = createJumpHandler(node);
buildSwitchCases(cases, expression);
@ -2962,10 +2964,141 @@ class SsaBuilder extends ResolvedVisitor implements Visitor {
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()) {
push(new HStatic(getFallThroughErrorElement));
HInstruction error = new HInvokeStatic(
Selector.INVOCATION_0, <HInstruction>[pop()]);
add(error);
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.
buildSwitchCases(Link<Node> cases, HInstruction expression,
[int encounteredCaseTypes = 0]) {
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;

View file

@ -633,6 +633,45 @@ class SsaCodeGenerator implements HVisitor, HBlockInformationVisitor {
return true;
}
bool visitSwitchInfo(HSwitchBlockInformation info) {
bool isExpression = isJSExpression(info.expression);
if (!isExpression) {
generateStatements(info.expression);
}
addIndentation();
for (LabelElement label in info.labels) {
if (label.isTarget) {
writeLabel(label);
buffer.add(":");
}
}
addIndented("switch (");
if (isExpression) {
generateExpression(info.expression);
} else {
use(info.expression.conditionExpression,
JSPrecedence.EXPRESSION_PRECEDENCE);
}
buffer.add(") {\n");
indent++;
for (int i = 0; i < info.matchExpressions.length; i++) {
for (Constant constant in info.matchExpressions[i]) {
addIndented("case ");
generateConstant(constant);
buffer.add(":\n");
}
if (i == info.matchExpressions.length - 1 && info.hasDefault) {
addIndented("default:\n");
}
indent++;
generateStatements(info.statements[i]);
indent--;
}
indent--;
addIndented("}\n");
return true;
}
bool visitSequenceInfo(HStatementSequenceInformation info) {
return false;
}
@ -1565,28 +1604,33 @@ class SsaCodeGenerator implements HVisitor, HBlockInformationVisitor {
endExpression(JSPrecedence.MEMBER_PRECEDENCE);
}
visitConstant(HConstant node) {
assert(isGenerateAtUseSite(node));
void generateConstant(Constant constant) {
// 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);
String name = handler.getNameForConstant(constant);
if (name === null) {
assert(!node.constant.isObject());
if (node.constant.isNum()
assert(!constant.isObject());
if (constant.isNum()
&& expectedPrecedence == JSPrecedence.MEMBER_PRECEDENCE) {
buffer.add('(');
handler.writeConstant(buffer, node.constant);
handler.writeConstant(buffer, constant);
buffer.add(')');
} else {
handler.writeConstant(buffer, node.constant);
handler.writeConstant(buffer, constant);
}
} else {
buffer.add(compiler.namer.CURRENT_ISOLATE);
buffer.add(".");
buffer.add(name);
}
}
visitConstant(HConstant node) {
assert(isGenerateAtUseSite(node));
generateConstant(node.constant);
}
visitLoopBranch(HLoopBranch node) {
@ -1794,6 +1838,10 @@ class SsaCodeGenerator implements HVisitor, HBlockInformationVisitor {
buffer.add(text);
}
void visitSwitch(HSwitch node) {
// Switches are handled using [visitSwitchInfo].
}
void visitStatic(HStatic node) {
world.registerStaticUse(node.element);
buffer.add(compiler.namer.isolateAccess(node.element));

View file

@ -52,6 +52,7 @@ interface HVisitor<R> {
R visitStatic(HStatic node);
R visitStaticStore(HStaticStore node);
R visitSubtract(HSubtract node);
R visitSwitch(HSwitch node);
R visitThis(HThis node);
R visitThrow(HThrow node);
R visitTruncatingDivide(HTruncatingDivide node);
@ -300,6 +301,7 @@ class HBaseVisitor extends HGraphVisitor implements HVisitor {
visitShiftRight(HShiftRight node) => visitBinaryBitOp(node);
visitShiftLeft(HShiftLeft node) => visitBinaryBitOp(node);
visitSubtract(HSubtract node) => visitBinaryArithmetic(node);
visitSwitch(HSwitch node) => visitControlFlow(node);
visitStatic(HStatic node) => visitInstruction(node);
visitStaticStore(HStaticStore node) => visitInstruction(node);
visitThis(HThis node) => visitParameterValue(node);
@ -333,7 +335,7 @@ class SubExpression extends SubGraph {
/** Find the condition expression if this sub-expression is a condition. */
HInstruction get conditionExpression() {
HInstruction last = end.last;
if (last is HConditionalBranch) return last.inputs[0];
if (last is HConditionalBranch || last is HSwitch) return last.inputs[0];
return null;
}
}
@ -1478,6 +1480,26 @@ class HSubtract extends HBinaryArithmetic {
bool dataEquals(HInstruction other) => true;
}
/**
* An [HSwitch] instruction has one input for the incoming
* value, and one input per constant that it can switch on.
* Its block has one successor per constant, and one for the default.
* If the switch didn't have a default case, the last successor is
* the join block.
*/
class HSwitch extends HControlFlow {
HSwitch(List<HInstruction> inputs) : super(inputs);
HConstant constant(int index) => inputs[index + 1];
HInstruction get expression() => inputs[0];
HBasicBlock get defaultTarget() => block.successors.last();
accept(HVisitor visitor) => visitor.visitSwitch(this);
String toString() => "HSwitch cases = $inputs";
}
class HTruncatingDivide extends HBinaryArithmetic {
HTruncatingDivide(HStatic target, HInstruction left, HInstruction right)
: super(target, left, right);
@ -2308,6 +2330,7 @@ interface HStatementInformationVisitor {
bool visitLoopInfo(HLoopBlockInformation info);
bool visitIfInfo(HIfBlockInformation info);
bool visitTryInfo(HTryBlockInformation info);
bool visitSwitchInfo(HSwitchBlockInformation info);
bool visitSequenceInfo(HStatementSequenceInformation info);
// Pseudo-structure embedding a dominator-based traversal into
// the block-structure traversal. This will eventually go away.
@ -2503,3 +2526,33 @@ class HTryBlockInformation implements HStatementInformation {
bool accept(HStatementInformationVisitor visitor) =>
visitor.visitTryInfo(this);
}
class HSwitchBlockInformation implements HStatementInformation {
final HExpressionInformation expression;
final List<List<Constant>> matchExpressions;
final List<HStatementInformation> statements;
// If the switch has a default, it's the last statement block, which
// may or may not have other expresions.
final bool hasDefault;
final TargetElement target;
final List<LabelElement> labels;
HSwitchBlockInformation(this.expression,
this.matchExpressions,
this.statements,
this.hasDefault,
this.target,
this.labels);
HBasicBlock get start() => expression.start;
HBasicBlock get end() {
// We don't create a switch block if there are no cases.
assert(!statements.isEmpty());
return statements.last().end;
}
bool accept(HStatementInformationVisitor visitor) =>
visitor.visitSwitchInfo(this);
}

View file

@ -383,6 +383,22 @@ class HInstructionStringifier implements HVisitor<String> {
String visitSubtract(HSubtract node) => visitInvokeStatic(node);
String visitSwitch(HSwitch node) {
StringBuffer buf = new StringBuffer();
buf.add("Switch: (");
buf.add(temporaryId(node.inputs[0]));
buf.add(") ");
for (int i = 1; i < node.inputs.length; i++) {
buf.add(temporaryId(node.inputs[i]));
buf.add(": B");
buf.add(node.block.successors[i - 1].id);
buf.add(", ");
}
buf.add("default: B");
buf.add(node.block.successors.last().id);
return buf.toString();
}
String visitThis(HThis node) => "this";
String visitThrow(HThrow node) => "Throw ${temporaryId(node.inputs[0])}";

View file

@ -63,7 +63,6 @@ generic_test: Fail # cannot resolve type T
get_set_syntax_test/none: Fail # Parsing of pseudo keywords.
getters_setters_test: Fail # Internal Error: SsaBuilder.visitIdentifier on non-this
implicit_scope_test: Fail # duplicate definition of a="bar"
label_test: Fail # switch cases are not implemented
library_prefixes_test: Fail # other is not a type
local_function_test: Fail # TypeError: Object #<Closure> has no method '$call$2' (bad exception mapping).
many_generic_instanceof_test: Fail # cannot resolve type T