001 /*
002 * Copyright 2010-2015 JetBrains s.r.o.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017 package org.jetbrains.kotlin.codegen.when;
018
019 import org.jetbrains.annotations.NotNull;
020 import org.jetbrains.annotations.Nullable;
021 import org.jetbrains.kotlin.codegen.ExpressionCodegen;
022 import org.jetbrains.kotlin.codegen.FrameMap;
023 import org.jetbrains.kotlin.psi.KtWhenEntry;
024 import org.jetbrains.kotlin.psi.KtWhenExpression;
025 import org.jetbrains.kotlin.resolve.BindingContext;
026 import org.jetbrains.kotlin.resolve.constants.ConstantValue;
027 import org.jetbrains.kotlin.resolve.constants.NullValue;
028 import org.jetbrains.kotlin.types.KotlinType;
029 import org.jetbrains.kotlin.types.TypeUtils;
030 import org.jetbrains.org.objectweb.asm.Label;
031 import org.jetbrains.org.objectweb.asm.Type;
032 import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter;
033
034 import java.util.*;
035
036 abstract public class SwitchCodegen {
037 protected final KtWhenExpression expression;
038 protected final boolean isStatement;
039 protected final boolean isExhaustive;
040 protected final ExpressionCodegen codegen;
041 protected final BindingContext bindingContext;
042 protected final Type subjectType;
043 protected final Type resultType;
044 protected final InstructionAdapter v;
045
046 protected final NavigableMap<Integer, Label> transitionsTable = new TreeMap<Integer, Label>();
047 protected final List<Label> entryLabels = new ArrayList<Label>();
048 protected Label elseLabel = new Label();
049 protected Label endLabel = new Label();
050 protected Label defaultLabel;
051
052 public SwitchCodegen(
053 @NotNull KtWhenExpression expression, boolean isStatement,
054 boolean isExhaustive, @NotNull ExpressionCodegen codegen,
055 @Nullable Type subjectType
056 ) {
057 this.expression = expression;
058 this.isStatement = isStatement;
059 this.isExhaustive = isExhaustive;
060 this.codegen = codegen;
061 this.bindingContext = codegen.getBindingContext();
062
063 this.subjectType = subjectType != null ? subjectType : codegen.expressionType(expression.getSubjectExpression());
064 resultType = isStatement ? Type.VOID_TYPE : codegen.expressionType(expression);
065 v = codegen.v;
066 }
067
068 /**
069 * Generates bytecode for entire when expression
070 */
071 public void generate() {
072 prepareConfiguration();
073
074 boolean hasElse = expression.getElseExpression() != null;
075
076 // if there is no else-entry and it's statement then default --- endLabel
077 defaultLabel = (hasElse || !isStatement || isExhaustive) ? elseLabel : endLabel;
078
079 generateSubject();
080
081 generateSwitchInstructionByTransitionsTable();
082
083 generateEntries();
084
085 // there is no else-entry but this is not statement, so we should return Unit
086 if (!hasElse && (!isStatement || isExhaustive)) {
087 v.visitLabel(elseLabel);
088 codegen.putUnitInstanceOntoStackForNonExhaustiveWhen(expression, isStatement);
089 }
090
091 codegen.markLineNumber(expression, isStatement);
092 v.mark(endLabel);
093 }
094
095 /**
096 * Sets up transitionsTable and maybe something else needed in a special case
097 * Behaviour may be changed by overriding processConstant
098 */
099 private void prepareConfiguration() {
100 for (KtWhenEntry entry : expression.getEntries()) {
101 Label entryLabel = new Label();
102
103 for (ConstantValue<?> constant : SwitchCodegenUtil.getConstantsFromEntry(entry, bindingContext, codegen.getState().getShouldInlineConstVals())) {
104 if (constant instanceof NullValue) continue;
105 processConstant(constant, entryLabel);
106 }
107
108 if (entry.isElse()) {
109 elseLabel = entryLabel;
110 }
111
112 entryLabels.add(entryLabel);
113 }
114 }
115
116 abstract protected void processConstant(
117 @NotNull ConstantValue<?> constant,
118 @NotNull Label entryLabel
119 );
120
121 protected void putTransitionOnce(int value, @NotNull Label entryLabel) {
122 if (!transitionsTable.containsKey(value)) {
123 transitionsTable.put(value, entryLabel);
124 }
125 }
126
127 /**
128 * Should generate int subject on top of the stack
129 * Default implementation just run codegen for actual subject of expression
130 * May also gen nullability check if needed
131 */
132 protected void generateSubject() {
133 codegen.gen(expression.getSubjectExpression(), subjectType);
134 }
135
136 protected void generateNullCheckIfNeeded() {
137 assert expression.getSubjectExpression() != null : "subject expression can't be null";
138 KotlinType subjectJetType = bindingContext.getType(expression.getSubjectExpression());
139
140 assert subjectJetType != null : "subject type can't be null (i.e. void)";
141
142 if (TypeUtils.isNullableType(subjectJetType)) {
143 int nullEntryIndex = findNullEntryIndex(expression);
144 Label nullLabel = nullEntryIndex == -1 ? defaultLabel : entryLabels.get(nullEntryIndex);
145 Label notNullLabel = new Label();
146
147 v.dup();
148 v.ifnonnull(notNullLabel);
149
150 v.pop();
151
152 v.goTo(nullLabel);
153
154 v.visitLabel(notNullLabel);
155 }
156 }
157
158 private int findNullEntryIndex(@NotNull KtWhenExpression expression) {
159 int entryIndex = 0;
160 for (KtWhenEntry entry : expression.getEntries()) {
161 for (ConstantValue<?> constant : SwitchCodegenUtil.getConstantsFromEntry(entry, bindingContext, codegen.getState().getShouldInlineConstVals())) {
162 if (constant instanceof NullValue) {
163 return entryIndex;
164 }
165 }
166
167 entryIndex++;
168 }
169
170 return -1;
171 }
172
173 private void generateSwitchInstructionByTransitionsTable() {
174 int[] keys = new int[transitionsTable.size()];
175 Label[] labels = new Label[transitionsTable.size()];
176 int i = 0;
177
178 for (Map.Entry<Integer, Label> transition : transitionsTable.entrySet()) {
179 keys[i] = transition.getKey();
180 labels[i] = transition.getValue();
181
182 i++;
183 }
184
185 int nlabels = keys.length;
186 int hi = keys[nlabels - 1];
187 int lo = keys[0];
188
189 /*
190 * Heuristic estimation if it's better to use tableswitch or lookupswitch.
191 * From OpenJDK sources
192 */
193 long table_space_cost = 4 + ((long) hi - lo + 1); // words
194 long table_time_cost = 3; // comparisons
195 long lookup_space_cost = 3 + 2 * (long) nlabels;
196 //noinspection UnnecessaryLocalVariable
197 long lookup_time_cost = nlabels;
198
199 boolean useTableSwitch = nlabels > 0 &&
200 table_space_cost + 3 * table_time_cost <=
201 lookup_space_cost + 3 * lookup_time_cost;
202
203 if (!useTableSwitch) {
204 v.lookupswitch(defaultLabel, keys, labels);
205 return;
206 }
207
208 Label[] sparseLabels = new Label[hi - lo + 1];
209 Arrays.fill(sparseLabels, defaultLabel);
210
211 for (i = 0; i < keys.length; i++) {
212 sparseLabels[keys[i] - lo] = labels[i];
213 }
214
215 v.tableswitch(lo, hi, defaultLabel, sparseLabels);
216 }
217
218 protected void generateEntries() {
219 // resolving entries' entryLabels and generating entries' code
220 Iterator<Label> entryLabelsIterator = entryLabels.iterator();
221 for (KtWhenEntry entry : expression.getEntries()) {
222 v.visitLabel(entryLabelsIterator.next());
223
224 FrameMap.Mark mark = codegen.myFrameMap.mark();
225 codegen.gen(entry.getExpression(), resultType);
226 mark.dropTo();
227
228 if (!entry.isElse()) {
229 v.goTo(endLabel);
230 }
231 }
232 }
233 }