/**
* Copyright (c) 2012-2016 André Bargull
* Alle Rechte vorbehalten / All Rights Reserved. Use is subject to license terms.
*
* <https://github.com/anba/es6draft>
*/
package com.github.anba.es6draft.compiler;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import com.github.anba.es6draft.ast.BinaryExpression;
import com.github.anba.es6draft.ast.Expression;
import com.github.anba.es6draft.ast.Node;
import com.github.anba.es6draft.ast.NumericLiteral;
import com.github.anba.es6draft.ast.StringLiteral;
import com.github.anba.es6draft.ast.SwitchClause;
import com.github.anba.es6draft.ast.SwitchStatement;
import com.github.anba.es6draft.ast.UnaryExpression;
import com.github.anba.es6draft.ast.scope.BlockScope;
import com.github.anba.es6draft.compiler.Labels.BreakLabel;
import com.github.anba.es6draft.compiler.StatementGenerator.Completion;
import com.github.anba.es6draft.compiler.assembler.Jump;
import com.github.anba.es6draft.compiler.assembler.MethodName;
import com.github.anba.es6draft.compiler.assembler.Type;
import com.github.anba.es6draft.compiler.assembler.Variable;
import com.github.anba.es6draft.runtime.LexicalEnvironment;
import com.github.anba.es6draft.runtime.internal.Bootstrap;
/**
* <h1>13 ECMAScript Language: Statements and Declarations</h1>
* <ul>
* <li>13.12 The switch Statement
* </ul>
*/
final class SwitchStatementGenerator extends DefaultCodeGenerator<StatementGenerator.Completion> {
private static final class Methods {
// class: CharSequence
static final MethodName CharSequence_charAt = MethodName.findInterface(Types.CharSequence,
"charAt", Type.methodType(Type.CHAR_TYPE, Type.INT_TYPE));
static final MethodName CharSequence_length = MethodName.findInterface(Types.CharSequence,
"length", Type.methodType(Type.INT_TYPE));
static final MethodName CharSequence_toString = MethodName.findInterface(
Types.CharSequence, "toString", Type.methodType(Types.String));
// class: Number
static final MethodName Number_doubleValue = MethodName.findVirtual(Types.Number,
"doubleValue", Type.methodType(Type.DOUBLE_TYPE));
// class: String
static final MethodName String_equals = MethodName.findVirtual(Types.String, "equals",
Type.methodType(Type.BOOLEAN_TYPE, Types.Object));
static final MethodName String_hashCode = MethodName.findVirtual(Types.String, "hashCode",
Type.methodType(Type.INT_TYPE));
}
private enum SwitchType {
Int, Char, String, Generic, Default;
private static boolean isIntSwitch(SwitchStatement node) {
for (SwitchClause switchClause : node.getClauses()) {
Expression expr = switchClause.getExpression();
if (expr != null) {
if (expr instanceof NumericLiteral && ((NumericLiteral) expr).isInt()) {
continue;
}
if (expr instanceof UnaryExpression
&& ((UnaryExpression) expr).getOperator() == UnaryExpression.Operator.NEG
&& ((UnaryExpression) expr).getOperand() instanceof NumericLiteral
&& ((NumericLiteral) ((UnaryExpression) expr).getOperand()).isInt()
&& ((NumericLiteral) ((UnaryExpression) expr).getOperand()).intValue() != 0) {
continue;
}
return false;
}
}
return true;
}
private static boolean isCharSwitch(SwitchStatement node) {
for (SwitchClause switchClause : node.getClauses()) {
Expression expr = switchClause.getExpression();
if (expr != null) {
if (expr instanceof StringLiteral
&& ((StringLiteral) expr).getValue().length() == 1) {
continue;
}
return false;
}
}
return true;
}
private static boolean isStringSwitch(SwitchStatement node) {
for (SwitchClause switchClause : node.getClauses()) {
Expression expr = switchClause.getExpression();
if (expr != null && !(expr instanceof StringLiteral)) {
return false;
}
}
return true;
}
static SwitchType of(SwitchStatement node) {
List<SwitchClause> clauses = node.getClauses();
if (clauses.size() == 0 || clauses.size() == 1 && clauses.get(0).isDefaultClause()) {
// empty or only default clause
return Default;
}
if (isIntSwitch(node)) {
return Int;
}
if (isCharSwitch(node)) {
return Char;
}
if (isStringSwitch(node)) {
return String;
}
return Generic;
}
}
public SwitchStatementGenerator(CodeGenerator codegen) {
super(codegen);
}
@Override
protected Completion visit(Node node, CodeVisitor mv) {
throw new IllegalStateException(String.format("node-class: %s", node.getClass()));
}
/**
* 13.12.11 Runtime Semantics: Evaluation
*/
@Override
public Completion visit(SwitchClause node, CodeVisitor mv) {
return codegen.statements(node.getStatements(), mv);
}
/**
* 13.12.11 Runtime Semantics: Evaluation
*/
@Override
public Completion visit(SwitchStatement node, CodeVisitor mv) {
// stack -> switchValue
ValType switchValueType = expression(node.getExpression(), mv);
SwitchType type = SwitchType.of(node);
boolean defaultOrReturn = false;
if (type == SwitchType.Int) {
if (!switchValueType.isNumeric() && switchValueType != ValType.Any) {
defaultOrReturn = true;
}
} else if (type == SwitchType.Char) {
if (switchValueType != ValType.String && switchValueType != ValType.Any) {
defaultOrReturn = true;
}
} else if (type == SwitchType.String) {
if (switchValueType != ValType.String && switchValueType != ValType.Any) {
defaultOrReturn = true;
}
} else if (type == SwitchType.Generic) {
mv.toBoxed(switchValueType);
switchValueType = ValType.Any;
} else {
assert type == SwitchType.Default;
defaultOrReturn = true;
}
final boolean defaultClausePresent = hasDefaultClause(node);
if (defaultOrReturn) {
// never true -> emit default switch or return
mv.pop(switchValueType);
if (defaultClausePresent) {
type = SwitchType.Default;
} else {
return Completion.Normal;
}
}
mv.enterVariableScope();
Variable<LexicalEnvironment<?>> savedEnv = saveEnvironment(node, mv);
Variable<?> switchValue = null;
if (type != SwitchType.Default) {
switchValue = mv.newVariable("switchValue", switchValueType.toClass());
mv.store(switchValue);
}
BlockScope scope = node.getScope();
if (scope.isPresent()) {
newDeclarativeEnvironment(scope, mv);
codegen.blockInit(node, mv);
pushLexicalEnvironment(mv);
}
Jump lblExit = new Jump();
BreakLabel lblBreak = new BreakLabel();
mv.enterScope(node);
mv.enterBreakable(node, lblBreak);
Completion result = CaseBlockEvaluation(node, type, lblExit, switchValue, mv);
mv.exitBreakable(node);
mv.exitScope();
if (!defaultClausePresent) {
mv.mark(lblExit);
}
if (scope.isPresent() && !result.isAbrupt()) {
popLexicalEnvironment(mv);
}
if (lblBreak.isTarget()) {
mv.mark(lblBreak);
restoreEnvironment(savedEnv, mv);
}
mv.exitVariableScope();
return result.normal(lblBreak.isTarget());
}
/**
* 13.12.9 Runtime Semantics: CaseBlockEvaluation
*
* @param node
* the switch statement
* @param type
* the switch statement type
* @param lblExit
* the exit label
* @param switchValue
* the variable which holds the switch value
* @param mv
* the code visitor
* @return the completion value
*/
private Completion CaseBlockEvaluation(SwitchStatement node, SwitchType type, Jump lblExit, Variable<?> switchValue,
CodeVisitor mv) {
List<SwitchClause> clauses = node.getClauses();
Jump lblDefault = null;
Jump[] labels = new Jump[clauses.size()];
for (int i = 0, size = clauses.size(); i < size; ++i) {
labels[i] = new Jump();
if (clauses.get(i).isDefaultClause()) {
assert lblDefault == null;
lblDefault = labels[i];
}
}
if (type == SwitchType.Int) {
emitIntSwitch(clauses, labels, lblDefault, lblExit, switchValue, mv);
} else if (type == SwitchType.Char) {
emitCharSwitch(clauses, labels, lblDefault, lblExit, switchValue, mv);
} else if (type == SwitchType.String) {
emitStringSwitch(clauses, labels, lblDefault, lblExit, switchValue, mv);
} else if (type == SwitchType.Generic) {
emitGenericSwitch(clauses, labels, lblDefault, lblExit, switchValue, mv);
} else {
assert type == SwitchType.Default;
assert switchValue == null;
// Directly jump to default clause; since switch clauses before default clause are not
// emitted, jump instruction can be elided as well, so we directly fall into the default
// clause.
}
Completion result = Completion.Normal, lastResult = Completion.Normal;
if (type == SwitchType.Default) {
Iterator<SwitchClause> iter = clauses.iterator();
// skip leading clauses until default clause found
while (iter.hasNext()) {
SwitchClause switchClause = iter.next();
if (switchClause.isDefaultClause()) {
lastResult = switchClause.accept(this, mv);
break;
}
}
// handle clauses following default clause until abrupt completion
while (iter.hasNext() && !lastResult.isAbrupt()) {
lastResult = iter.next().accept(this, mv);
}
result = lastResult;
} else {
int index = 0;
for (SwitchClause switchClause : clauses) {
Jump caseLabel = labels[index++];
if (caseLabel != null) {
mv.mark(caseLabel);
} else if (lastResult.isAbrupt()) {
// Ignore unreachable targets
continue;
}
Completion innerResult = switchClause.accept(this, mv);
if (innerResult.isAbrupt()) {
// not fall-thru
result = result.isAbrupt() ? result.select(innerResult) : innerResult;
}
lastResult = innerResult;
}
}
return result.normal(lblDefault == null || !lastResult.isAbrupt());
}
private static boolean hasDefaultClause(SwitchStatement node) {
for (SwitchClause switchClause : node.getClauses()) {
if (switchClause.isDefaultClause()) {
return true;
}
}
return false;
}
private void invokeDynamicOperator(BinaryExpression.Operator operator, CodeVisitor mv) {
// stack: [lval, rval, cx?] -> [result]
mv.invokedynamic(Bootstrap.getName(operator), Bootstrap.getMethodDescriptor(operator),
Bootstrap.getBootstrap(operator));
}
/**
* <h3>Generic-switch</h3>
*
* <pre>
* switch (v) {
* case key1: ...
* case key2: ...
* }
*
* var $v = v;
* if (strictEquals($v, key1)) goto L1
* if (strictEquals($v, key2)) goto L2
* goTo (default | break)
* L1: ...
* L2: ...
* </pre>
*
* @param clauses
* the switch clauses
* @param labels
* the labels for each switch clause
* @param defaultClause
* the label for the default clause
* @param lblExit
* the exit label
* @param switchValue
* the variable which holds the switch value
* @param mv
* the code visitor
*/
private void emitGenericSwitch(List<SwitchClause> clauses, Jump[] labels, Jump defaultClause, Jump lblExit,
Variable<?> switchValue, CodeVisitor mv) {
assert switchValue.getType().equals(Types.Object);
Jump switchDefault = defaultClause != null ? defaultClause : lblExit;
int index = 0;
for (SwitchClause switchClause : clauses) {
Jump caseLabel = labels[index++];
Expression expr = switchClause.getExpression();
if (expr != null) {
mv.load(switchValue);
// 13.11.10 Runtime Semantics: CaseSelectorEvaluation
expressionBoxed(expr, mv);
invokeDynamicOperator(BinaryExpression.Operator.SHEQ, mv);
mv.ifne(caseLabel);
}
}
mv.goTo(switchDefault);
}
/**
* <h3>String-switch</h3>
*
* <pre>
* switch (v) {
* case "key1": ...
* case "key2": ...
* }
*
* var $v = v;
* if (typeof $v == 'string') {
* lookupswitch(hashCode($v)) {
* hashCode("key1"): goto L1
* hashCode("key2"): goto L2
* }
* L1: if (equals($v, "key1")) ...
* L2: if (equals($v, "key2")) ...
* }
* </pre>
*
* @param clauses
* the switch clauses
* @param labels
* the labels for each switch clause
* @param defaultClause
* the label for the default clause
* @param lblExit
* the exit label
* @param switchValue
* the variable which holds the switch value
* @param mv
* the code visitor
*/
private void emitStringSwitch(List<SwitchClause> clauses, Jump[] labels, Jump defaultClause, Jump lblExit,
Variable<?> switchValue, CodeVisitor mv) {
Jump switchDefault = defaultClause != null ? defaultClause : lblExit;
mv.enterVariableScope();
Variable<String> switchValueString = mv.newVariable("switchValueString", String.class);
if (switchValue.getType().equals(Types.CharSequence)) {
mv.load(switchValue);
mv.invoke(Methods.CharSequence_toString);
mv.dup();
mv.store(switchValueString);
mv.invoke(Methods.String_hashCode);
} else {
assert switchValue.getType().equals(Types.Object);
// test for string: type is java.lang.CharSequence
mv.load(switchValue);
mv.instanceOf(Types.CharSequence);
mv.ifeq(switchDefault);
mv.load(switchValue);
mv.checkcast(Types.CharSequence);
mv.invoke(Methods.CharSequence_toString);
mv.dup();
mv.store(switchValueString);
mv.invoke(Methods.String_hashCode);
}
long[] entries = stringSwitchEntries(clauses, defaultClause != null);
int distinctValues = distinctValues(entries);
Jump[] switchLabels = new Jump[distinctValues];
int[] switchKeys = new int[distinctValues];
for (int i = 0, j = 0, lastValue = 0, length = entries.length; i < length; ++i) {
int value = Value(entries[i]);
if (i == 0 || value != lastValue) {
switchLabels[j] = new Jump();
switchKeys[j] = value;
j += 1;
}
lastValue = value;
}
// emit lookupswitch
mv.lookupswitch(switchDefault, switchKeys, switchLabels);
// add String.equals() calls
for (int i = 0, j = 0, lastValue = 0, length = entries.length; i < length; ++i) {
int value = Value(entries[i]);
int index = Index(entries[i]);
if (i == 0 || value != lastValue) {
if (i != 0) {
mv.goTo(switchDefault);
}
mv.mark(switchLabels[j++]);
}
String string = ((StringLiteral) clauses.get(index).getExpression()).getValue();
mv.load(switchValueString);
mv.aconst(string);
mv.invoke(Methods.String_equals);
mv.ifne(labels[index]);
lastValue = value;
}
mv.goTo(switchDefault);
mv.exitVariableScope();
}
/**
* <h3>char-switch</h3>
*
* <pre>
* switch (v) {
* case "a": ...
* case "b": ...
* }
*
* var $v = v;
* if (typeof $v == 'string' {@literal &&} length($v) == 1) {
* tableswitch|lookupswitch(charCodeAt($v, 0)) {
* charCodeAt("a", 0): goto L1
* charCodeAt("b", 0): goto L2
* }
* L1: ...
* L2: ...
* }
* </pre>
*
* @param clauses
* the switch clauses
* @param labels
* the labels for each switch clause
* @param defaultClause
* the label for the default clause
* @param lblExit
* the exit label
* @param switchValue
* the variable which holds the switch value
* @param mv
* the code visitor
*/
private void emitCharSwitch(List<SwitchClause> clauses, Jump[] labels, Jump defaultClause, Jump lblExit,
Variable<?> switchValue, CodeVisitor mv) {
Jump switchDefault = defaultClause != null ? defaultClause : lblExit;
if (switchValue.getType().equals(Types.CharSequence)) {
// test for char: value is character (string with only one character)
mv.load(switchValue);
mv.invoke(Methods.CharSequence_length);
mv.iconst(1);
mv.ificmpne(switchDefault);
mv.load(switchValue);
mv.iconst(0);
mv.invoke(Methods.CharSequence_charAt);
// mv.cast(Type.CHAR_TYPE, Type.INT_TYPE);
} else {
assert switchValue.getType().equals(Types.Object);
// test for char: type is java.lang.CharSequence
mv.load(switchValue);
mv.instanceOf(Types.CharSequence);
mv.ifeq(switchDefault);
// test for char: value is character (string with only one character)
mv.enterVariableScope();
Variable<CharSequence> switchValueChar = mv.newVariable("switchValueChar",
CharSequence.class);
mv.load(switchValue);
mv.checkcast(Types.CharSequence);
mv.dup();
mv.store(switchValueChar);
mv.invoke(Methods.CharSequence_length);
mv.iconst(1);
mv.ificmpne(switchDefault);
mv.load(switchValueChar);
mv.iconst(0);
mv.invoke(Methods.CharSequence_charAt);
// mv.cast(Type.CHAR_TYPE, Type.INT_TYPE);
mv.exitVariableScope();
}
// emit tableswitch or lookupswitch
long[] entries = charSwitchEntries(clauses, defaultClause != null);
switchInstruction(switchDefault, labels, entries, mv);
}
/**
* <h3>int-switch</h3>
*
* <pre>
* switch (v) {
* case 0: ...
* case 1: ...
* }
*
* var $v = v;
* if (typeof $v == 'number' {@literal &&} isInt($v)) {
* tableswitch|lookupswitch(int($v)) {
* int(0): goto L1
* int(1): goto L2
* }
* L1: ...
* L2: ...
* }
* </pre>
*
* @param clauses
* the switch clauses
* @param labels
* the labels for each switch clause
* @param defaultClause
* the label for the default clause
* @param lblExit
* the exit label
* @param switchValue
* the variable which holds the switch value
* @param mv
* the code visitor
*/
private void emitIntSwitch(List<SwitchClause> clauses, Jump[] labels, Jump defaultClause, Jump lblExit,
Variable<?> switchValue, CodeVisitor mv) {
Jump switchDefault = defaultClause != null ? defaultClause : lblExit;
if (switchValue.getType().equals(Type.INT_TYPE)) {
mv.load(switchValue);
} else if (switchValue.getType().equals(Type.LONG_TYPE)) {
// test for int: value is integer
mv.load(switchValue);
mv.dup2();
mv.l2i();
mv.i2l();
mv.lcmp();
mv.ifne(switchDefault);
mv.load(switchValue);
mv.l2i();
} else if (switchValue.getType().equals(Type.DOUBLE_TYPE)) {
// test for int: value is integer
mv.load(switchValue);
mv.dup2();
mv.d2i();
mv.i2d();
mv.dcmpl();
mv.ifne(switchDefault);
mv.load(switchValue);
mv.d2i();
} else {
assert switchValue.getType().equals(Types.Object);
// test for int: type is java.lang.Number
mv.load(switchValue);
mv.instanceOf(Types.Number);
mv.ifeq(switchDefault);
// test for int: value is integer
mv.enterVariableScope();
Variable<Double> switchValueNum = mv.newVariable("switchValueNum", double.class);
mv.load(switchValue);
mv.checkcast(Types.Number);
mv.invoke(Methods.Number_doubleValue);
mv.dup2();
mv.dup2();
mv.store(switchValueNum);
mv.d2i();
mv.i2d();
mv.dcmpl();
mv.ifne(switchDefault);
mv.load(switchValueNum);
mv.d2i();
mv.exitVariableScope();
}
// emit tableswitch or lookupswitch
long[] entries = intSwitchEntries(clauses, defaultClause != null);
switchInstruction(switchDefault, labels, entries, mv);
}
/**
* Shared implementation for int- and char-switches.
*
* @param switchDefault
* the switch default instruction label
* @param labels
* the switch labels
* @param entries
* the switch entries, value-index pairs
* @param mv
* the code visitor
*/
private static void switchInstruction(Jump switchDefault, Jump[] labels, long[] entries, CodeVisitor mv) {
int entriesLength = entries.length;
int distinctValues = distinctValues(entries);
int minValue = Value(entries[0]);
int maxValue = Value(entries[entriesLength - 1]);
int range = maxValue - minValue + 1;
float density = (float) distinctValues / range;
if (range > 0 && (range <= 5 || density >= 0.5f)) {
// System.out.printf("tableswitch [%d: %d - %d]\n", entriesLength, minValue, maxValue);
Jump[] switchLabels = new Jump[range];
Arrays.fill(switchLabels, switchDefault);
for (int i = 0, lastValue = 0; i < entriesLength; ++i) {
int value = Value(entries[i]);
int index = Index(entries[i]);
if (i == 0 || value != lastValue) {
switchLabels[value - minValue] = labels[index];
} else {
// Duplicate case value
labels[index] = null;
}
lastValue = value;
}
mv.tableswitch(minValue, maxValue, switchDefault, switchLabels);
} else {
// System.out.printf("lookupswitch [%d: %d - %d]\n", entriesLength, minValue, maxValue);
Jump[] switchLabels = new Jump[distinctValues];
int[] switchKeys = new int[distinctValues];
for (int i = 0, j = 0, lastValue = 0; i < entriesLength; ++i) {
int value = Value(entries[i]);
int index = Index(entries[i]);
if (i == 0 || value != lastValue) {
switchLabels[j] = labels[index];
switchKeys[j] = value;
j += 1;
} else {
// Duplicate case value
labels[index] = null;
}
lastValue = value;
}
mv.lookupswitch(switchDefault, switchKeys, switchLabels);
}
}
private static int distinctValues(long[] entries) {
int distinctValues = 0;
for (int i = 0, lastValue = 0, length = entries.length; i < length; ++i) {
int value = Value(entries[i]);
if (i == 0 || value != lastValue) {
distinctValues += 1;
}
lastValue = value;
}
return distinctValues;
}
private static long[] stringSwitchEntries(List<SwitchClause> clauses, boolean hasDefault) {
long[] entries = new long[clauses.size() - (hasDefault ? 1 : 0)];
for (int i = 0, j = 0, size = clauses.size(); i < size; ++i) {
Expression expr = clauses.get(i).getExpression();
if (expr != null) {
entries[j++] = Entry(((StringLiteral) expr).getValue().hashCode(), i);
}
}
// sort values in ascending order
Arrays.sort(entries);
return entries;
}
private static long[] intSwitchEntries(List<SwitchClause> clauses, boolean hasDefault) {
long[] entries = new long[clauses.size() - (hasDefault ? 1 : 0)];
for (int i = 0, j = 0, size = clauses.size(); i < size; ++i) {
Expression expr = clauses.get(i).getExpression();
if (expr != null) {
int value;
if (expr instanceof NumericLiteral) {
value = ((NumericLiteral) expr).intValue();
} else {
value = -((NumericLiteral) ((UnaryExpression) expr).getOperand()).intValue();
}
entries[j++] = Entry(value, i);
}
}
// sort values in ascending order
Arrays.sort(entries);
return entries;
}
private static long[] charSwitchEntries(List<SwitchClause> clauses, boolean hasDefault) {
long[] entries = new long[clauses.size() - (hasDefault ? 1 : 0)];
for (int i = 0, j = 0, size = clauses.size(); i < size; ++i) {
Expression expr = clauses.get(i).getExpression();
if (expr != null) {
entries[j++] = Entry(((StringLiteral) expr).getValue().charAt(0), i);
}
}
// sort values in ascending order
Arrays.sort(entries);
return entries;
}
private static long Entry(int value, int index) {
return ((long) value) << 32 | index;
}
private static int Index(long entry) {
return (int) entry;
}
private static int Value(long entry) {
return (int) (entry >> 32);
}
}