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