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