Refactoring of switches codegen parts

This commit is contained in:
Denis Zharkov
2014-07-15 13:40:32 +04:00
committed by Evgeny Gerashchenko
parent dbd1ab8c67
commit b2aa249817
4 changed files with 366 additions and 149 deletions
@@ -37,6 +37,7 @@ import org.jetbrains.jet.codegen.inline.NameGenerator;
import org.jetbrains.jet.codegen.intrinsics.IntrinsicMethod;
import org.jetbrains.jet.codegen.state.GenerationState;
import org.jetbrains.jet.codegen.state.JetTypeMapper;
import org.jetbrains.jet.codegen.when.SwitchCodegenUtil;
import org.jetbrains.jet.lang.descriptors.*;
import org.jetbrains.jet.lang.diagnostics.DiagnosticUtils;
import org.jetbrains.jet.lang.evaluate.EvaluatePackage;
@@ -48,7 +49,6 @@ import org.jetbrains.jet.lang.resolve.DescriptorUtils;
import org.jetbrains.jet.lang.resolve.calls.model.*;
import org.jetbrains.jet.lang.resolve.calls.util.CallMaker;
import org.jetbrains.jet.lang.resolve.constants.CompileTimeConstant;
import org.jetbrains.jet.lang.resolve.constants.IntegerValueConstant;
import org.jetbrains.jet.lang.resolve.constants.IntegerValueTypeConstant;
import org.jetbrains.jet.lang.resolve.java.AsmTypeConstants;
import org.jetbrains.jet.lang.resolve.java.JvmAbi;
@@ -97,7 +97,7 @@ public class ExpressionCodegen extends JetVisitor<StackValue, StackValue> implem
private final BindingContext bindingContext;
public final InstructionAdapter v;
final FrameMap myFrameMap;
public final FrameMap myFrameMap;
private final MethodContext context;
private final Type returnType;
@@ -1520,7 +1520,7 @@ public class ExpressionCodegen extends JetVisitor<StackValue, StackValue> implem
});
}
private void markLineNumber(@NotNull JetElement statement) {
public void markLineNumber(@NotNull JetElement statement) {
Document document = statement.getContainingFile().getViewProvider().getDocument();
if (document != null) {
int lineNumber = document.getLineNumber(statement.getTextRange().getStartOffset()); // 0-based
@@ -3725,8 +3725,8 @@ The "returned" value of try expression with no finally is either the last expres
Type resultType = isStatement ? Type.VOID_TYPE : expressionType(expression);
if (canSwitchBeUsedIn(expression, subjectType)) {
return generateSwitch(expression, subjectType, resultType, isStatement);
if (SwitchCodegenUtil.canSwitchBeUsedIn(expression, subjectType, bindingContext)) {
return generateSwitch(expression, resultType, isStatement);
}
int subjectLocal = expr != null ? myFrameMap.enterTemp(subjectType) : -1;
@@ -3782,8 +3782,10 @@ The "returned" value of try expression with no finally is either the last expres
return StackValue.onStack(resultType);
}
private void putUnitInstanceOntoStackForNonExhaustiveWhen(@NotNull JetWhenExpression expression) {
if (Boolean.TRUE.equals(bindingContext.get(EXHAUSTIVE_WHEN, expression))) {
public void putUnitInstanceOntoStackForNonExhaustiveWhen(
@NotNull JetWhenExpression expression
) {
if (Boolean.TRUE.equals(bindingContext.get(BindingContext.EXHAUSTIVE_WHEN, expression))) {
// when() is supposed to be exhaustive
throwNewException("kotlin/NoWhenBranchMatchedException");
}
@@ -3816,156 +3818,17 @@ The "returned" value of try expression with no finally is either the last expres
private StackValue generateSwitch(
@NotNull JetWhenExpression expression,
@NotNull Type subjectType,
@NotNull Type resultType,
boolean isStatement
) {
Map<Integer, Label> transitions = Maps.newTreeMap();
SwitchCodegenUtil.buildAppropriateSwitchCodegen(
expression, isStatement, this
Label[] entryLabels = new Label[expression.getEntries().size()];
int entryLabelsCounter = 0;
Label elseLabel = new Label();
Label endLabel = new Label();
boolean hasElse = expression.getElseExpression() != null;
for (JetWhenEntry entry : expression.getEntries()) {
Label entryLabel = new Label();
for (JetWhenCondition condition : entry.getConditions()) {
assert condition instanceof JetWhenConditionWithExpression : "Condition should be instance of JetWhenConditionWithExpression";
JetExpression conditionExpression = ((JetWhenConditionWithExpression) condition).getExpression();
assert conditionExpression != null : "Condition expression in when should not be bull";
CompileTimeConstant constant = getCompileTimeConstant(conditionExpression, bindingContext);
assert doesConstantFitForSwitch(constant) : "Condition should be a constant in when for generating switch-instruction";
int value = (constant.getValue() instanceof Number)
? ((Number) constant.getValue()).intValue()
: ((Character) constant.getValue()).charValue();
if (!transitions.containsKey(value)) {
transitions.put(value, entryLabel);
}
}
if (entry.isElse()) {
elseLabel = entryLabel;
}
entryLabels[entryLabelsCounter++] = entryLabel;
}
gen(expression.getSubjectExpression(), subjectType);
generateSwitchInstructionByTransitionsTable(
transitions,
//if there is no else-entry and it's statement then default --- endLabel
(hasElse || !isStatement) ? elseLabel : endLabel
);
//resolving entries' labels
int i = 0;
for (JetWhenEntry entry : expression.getEntries()) {
v.visitLabel(entryLabels[i++]);
FrameMap.Mark mark = myFrameMap.mark();
gen(entry.getExpression(), resultType);
mark.dropTo();
if (!entry.isElse()) {
v.goTo(endLabel);
}
}
//there is no else-entry but this is not statement, so we should return Unit
if (!hasElse && !isStatement) {
v.visitLabel(elseLabel);
putUnitInstanceOntoStackForNonExhaustiveWhen(expression);
}
markLineNumber(expression);
v.mark(endLabel);
).generate();
return StackValue.onStack(resultType);
}
private void generateSwitchInstructionByTransitionsTable(@NotNull Map<Integer, Label> transitions, @NotNull Label defaultLabel) {
int[] keys = new int[transitions.size()];
Label[] labels = new Label[transitions.size()];
int i = 0;
for (Map.Entry<Integer, Label> transition : transitions.entrySet()) {
keys[i] = transition.getKey();
labels[i] = transition.getValue();
i++;
}
int nlabels = keys.length;
int hi = keys[nlabels - 1];
int lo = keys[0];
/*
* Heuristic estimation if it's better to use tableswitch or lookupswitch.
* From OpenJDK sources
*/
long table_space_cost = 4 + ((long) hi - lo + 1); // words
long table_time_cost = 3; // comparisons
long lookup_space_cost = 3 + 2 * (long) nlabels;
long lookup_time_cost = nlabels;
boolean useTableSwitch = nlabels > 0 &&
table_space_cost + 3 * table_time_cost <=
lookup_space_cost + 3 * lookup_time_cost;
if (!useTableSwitch) {
v.lookupswitch(defaultLabel, keys, labels);
return;
}
Label[] sparseLabels = new Label[hi - lo + 1];
Arrays.fill(sparseLabels, defaultLabel);
for (i = 0; i < keys.length; i++) {
sparseLabels[keys[i] - lo] = labels[i];
}
v.tableswitch(lo, hi, defaultLabel, sparseLabels);
}
private static boolean doesConstantFitForSwitch(@Nullable CompileTimeConstant constant) {
return (constant instanceof IntegerValueConstant);
}
private boolean canSwitchBeUsedIn(@NotNull JetWhenExpression expression, @NotNull Type subjectType) {
int typeSort = subjectType.getSort();
if (typeSort != Type.INT && typeSort != Type.CHAR && typeSort != Type.SHORT && typeSort != Type.BYTE) {
return false;
}
for (JetWhenEntry entry : expression.getEntries()) {
for (JetWhenCondition condition : entry.getConditions()) {
if (!(condition instanceof JetWhenConditionWithExpression)) {
return false;
}
//ensure that expression is constant
JetExpression patternExpression = ((JetWhenConditionWithExpression) condition).getExpression();
assert patternExpression != null : "expression in when should not be null";
CompileTimeConstant constant = getCompileTimeConstant(patternExpression, bindingContext);
if (!doesConstantFitForSwitch(constant)) {
return false;
}
}
}
return true;
}
private boolean isIntRangeExpr(JetExpression rangeExpression) {
if (rangeExpression instanceof JetBinaryExpression) {
JetBinaryExpression binaryExpression = (JetBinaryExpression) rangeExpression;
@@ -0,0 +1,45 @@
/*
* Copyright 2010-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.jet.codegen.when;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jet.codegen.ExpressionCodegen;
import org.jetbrains.jet.lang.psi.JetWhenExpression;
import org.jetbrains.jet.lang.resolve.constants.CompileTimeConstant;
import org.jetbrains.org.objectweb.asm.Label;
public class IntegralConstantsSwitchCodegen extends SwitchCodegen {
public IntegralConstantsSwitchCodegen(
@NotNull JetWhenExpression expression,
boolean isStatement,
@NotNull ExpressionCodegen codegen
) {
super(expression, isStatement, codegen);
}
@Override
protected void processConstant(
@NotNull CompileTimeConstant constant, @NotNull Label entryLabel
) {
assert constant.getValue() != null : "constant value should not be null";
int value = (constant.getValue() instanceof Number)
? ((Number) constant.getValue()).intValue()
: ((Character) constant.getValue()).charValue();
putTransitionOnce(value, entryLabel);
}
}
@@ -0,0 +1,184 @@
/*
* Copyright 2010-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.jet.codegen.when;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jet.codegen.ExpressionCodegen;
import org.jetbrains.jet.codegen.FrameMap;
import org.jetbrains.jet.lang.psi.*;
import org.jetbrains.jet.lang.resolve.BindingContext;
import org.jetbrains.jet.lang.resolve.constants.CompileTimeConstant;
import org.jetbrains.org.objectweb.asm.Label;
import org.jetbrains.org.objectweb.asm.Type;
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter;
import java.util.*;
abstract public class SwitchCodegen {
protected final JetWhenExpression expression;
protected final boolean isStatement;
protected final ExpressionCodegen codegen;
protected final BindingContext bindingContext;
protected final Type subjectType;
protected final Type resultType;
protected final InstructionAdapter v;
protected final NavigableMap<Integer, Label> transitionsTable = new TreeMap<Integer, Label>();
protected final Collection<Label> entryLabels = new ArrayList<Label>();
protected Label elseLabel = new Label();
protected Label endLabel = new Label();
public SwitchCodegen(
@NotNull JetWhenExpression expression, boolean isStatement,
@NotNull ExpressionCodegen codegen
) {
this.expression = expression;
this.isStatement = isStatement;
this.codegen = codegen;
this.bindingContext = codegen.getBindingContext();
subjectType = codegen.expressionType(expression.getSubjectExpression());
resultType = codegen.expressionType(expression);
v = codegen.v;
}
/**
* Generates bytecode for entire when expression
*/
public void generate() {
prepareConfiguration();
boolean hasElse = expression.getElseExpression() != null;
generateSubject();
generateSwitchInstructionByTransitionsTable(
transitionsTable,
// if there is no else-entry and it's statement then default --- endLabel
(hasElse || !isStatement) ? elseLabel : endLabel
);
// resolving entries' entryLabels and generating entries' code
Iterator<Label> entryLabelsIterator = entryLabels.iterator();
for (JetWhenEntry entry : expression.getEntries()) {
v.visitLabel(entryLabelsIterator.next());
FrameMap.Mark mark = codegen.myFrameMap.mark();
codegen.gen(entry.getExpression(), resultType);
mark.dropTo();
if (!entry.isElse()) {
v.goTo(endLabel);
}
}
// there is no else-entry but this is not statement, so we should return Unit
if (!hasElse && !isStatement) {
v.visitLabel(elseLabel);
codegen.putUnitInstanceOntoStackForNonExhaustiveWhen(expression);
}
codegen.markLineNumber(expression);
v.mark(endLabel);
}
/**
* Sets up transitionsTable and maybe something else needed in a special case
* Behaviour may be changed by overriding processConstant
*/
private void prepareConfiguration() {
for (JetWhenEntry entry : expression.getEntries()) {
Label entryLabel = new Label();
for (CompileTimeConstant constant : SwitchCodegenUtil.getConstantsFromEntry(entry, bindingContext)) {
processConstant(constant, entryLabel);
}
if (entry.isElse()) {
elseLabel = entryLabel;
}
entryLabels.add(entryLabel);
}
}
abstract protected void processConstant(
@NotNull CompileTimeConstant constant,
@NotNull Label entryLabel
);
protected void putTransitionOnce(int value, @NotNull Label entryLabel) {
if (!transitionsTable.containsKey(value)) {
transitionsTable.put(value, entryLabel);
}
}
/**
* Should generate int subject on top of the stack
* Default implementation just run codegen for actual subject of expression
* May also gen nullability check if needed
*/
protected void generateSubject() {
codegen.gen(expression.getSubjectExpression(), subjectType);
}
private void generateSwitchInstructionByTransitionsTable(
@NotNull Map<Integer, Label> transitions, @NotNull Label defaultLabel
) {
int[] keys = new int[transitions.size()];
Label[] labels = new Label[transitions.size()];
int i = 0;
for (Map.Entry<Integer, Label> transition : transitions.entrySet()) {
keys[i] = transition.getKey();
labels[i] = transition.getValue();
i++;
}
int nlabels = keys.length;
int hi = keys[nlabels - 1];
int lo = keys[0];
/*
* Heuristic estimation if it's better to use tableswitch or lookupswitch.
* From OpenJDK sources
*/
long table_space_cost = 4 + ((long) hi - lo + 1); // words
long table_time_cost = 3; // comparisons
long lookup_space_cost = 3 + 2 * (long) nlabels;
long lookup_time_cost = nlabels;
boolean useTableSwitch = nlabels > 0 &&
table_space_cost + 3 * table_time_cost <=
lookup_space_cost + 3 * lookup_time_cost;
if (!useTableSwitch) {
v.lookupswitch(defaultLabel, keys, labels);
return;
}
Label[] sparseLabels = new Label[hi - lo + 1];
Arrays.fill(sparseLabels, defaultLabel);
for (i = 0; i < keys.length; i++) {
sparseLabels[keys[i] - lo] = labels[i];
}
v.tableswitch(lo, hi, defaultLabel, sparseLabels);
}
}
@@ -0,0 +1,125 @@
/*
* Copyright 2010-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.jet.codegen.when;
import kotlin.Function1;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.codegen.ExpressionCodegen;
import org.jetbrains.jet.lang.psi.*;
import org.jetbrains.jet.lang.resolve.BindingContext;
import org.jetbrains.jet.lang.resolve.constants.CompileTimeConstant;
import org.jetbrains.jet.lang.resolve.constants.IntegerValueConstant;
import org.jetbrains.org.objectweb.asm.Type;
import java.util.ArrayList;
import java.util.List;
public class SwitchCodegenUtil {
public static boolean canSwitchBeUsedIn(
@NotNull JetWhenExpression expression,
@NotNull Type subjectType,
@NotNull BindingContext bindingContext
) {
int typeSort = subjectType.getSort();
if (typeSort != Type.INT && typeSort != Type.CHAR && typeSort != Type.SHORT && typeSort != Type.BYTE) {
return false;
}
return checkAllItemsAreConstantsSatisfying(expression, bindingContext, new Function1<CompileTimeConstant, Boolean>() {
@Override
public Boolean invoke(
@NotNull CompileTimeConstant constant
) {
return constant instanceof IntegerValueConstant;
}
});
}
public static boolean checkAllItemsAreConstantsSatisfying(
@NotNull JetWhenExpression expression,
@NotNull BindingContext bindingContext,
Function1<CompileTimeConstant, Boolean> predicate
) {
for (JetWhenEntry entry : expression.getEntries()) {
for (JetWhenCondition condition : entry.getConditions()) {
if (!(condition instanceof JetWhenConditionWithExpression)) {
return false;
}
// ensure that expression is constant
JetExpression patternExpression = ((JetWhenConditionWithExpression) condition).getExpression();
assert patternExpression != null : "expression in when should not be null";
CompileTimeConstant constant = ExpressionCodegen.getCompileTimeConstant(patternExpression, bindingContext);
if (constant == null || !predicate.invoke(constant)) {
return false;
}
}
}
return true;
}
@NotNull
public static Iterable<CompileTimeConstant> getAllConstants(
@NotNull JetWhenExpression expression,
@NotNull BindingContext bindingContext
) {
List<CompileTimeConstant> result = new ArrayList<CompileTimeConstant>();
for (JetWhenEntry entry : expression.getEntries()) {
addConstantsFromEntry(result, entry, bindingContext);
}
return result;
}
private static void addConstantsFromEntry(
@NotNull List<CompileTimeConstant> result,
@NotNull JetWhenEntry entry,
@NotNull BindingContext bindingContext
) {
for (JetWhenCondition condition : entry.getConditions()) {
JetExpression patternExpression = ((JetWhenConditionWithExpression) condition).getExpression();
assert patternExpression != null : "expression in when should not be null";
result.add(ExpressionCodegen.getCompileTimeConstant(patternExpression, bindingContext));
}
}
@NotNull
public static Iterable<CompileTimeConstant> getConstantsFromEntry(
@NotNull JetWhenEntry entry,
@NotNull BindingContext bindingContext
) {
List<CompileTimeConstant> result = new ArrayList<CompileTimeConstant>();
addConstantsFromEntry(result, entry, bindingContext);
return result;
}
@NotNull
public static SwitchCodegen buildAppropriateSwitchCodegen(
@NotNull JetWhenExpression expression,
boolean isStatement,
@NotNull ExpressionCodegen codegen
) {
return new IntegralConstantsSwitchCodegen(expression, isStatement, codegen);
}
}