001 /*
002 * Copyright 2010-2014 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.jet.codegen.when;
018
019 import org.jetbrains.annotations.NotNull;
020 import org.jetbrains.jet.codegen.ExpressionCodegen;
021 import org.jetbrains.jet.codegen.FrameMap;
022 import org.jetbrains.jet.lang.psi.JetWhenEntry;
023 import org.jetbrains.jet.lang.psi.JetWhenExpression;
024 import org.jetbrains.jet.lang.resolve.BindingContext;
025 import org.jetbrains.jet.lang.resolve.constants.CompileTimeConstant;
026 import org.jetbrains.jet.lang.resolve.constants.NullValue;
027 import org.jetbrains.jet.lang.types.JetType;
028 import org.jetbrains.jet.lang.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 import static org.jetbrains.jet.lang.resolve.BindingContext.EXPRESSION_TYPE;
036
037 abstract public class SwitchCodegen {
038 protected final JetWhenExpression expression;
039 protected final boolean isStatement;
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 JetWhenExpression expression, boolean isStatement,
054 @NotNull ExpressionCodegen codegen
055 ) {
056 this.expression = expression;
057 this.isStatement = isStatement;
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) ? 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) {
085 v.visitLabel(elseLabel);
086 codegen.putUnitInstanceOntoStackForNonExhaustiveWhen(expression);
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 (JetWhenEntry entry : expression.getEntries()) {
099 Label entryLabel = new Label();
100
101 for (CompileTimeConstant 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 CompileTimeConstant 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 JetType subjectJetType = bindingContext.get(EXPRESSION_TYPE, expression.getSubjectExpression());
136
137 assert subjectJetType != null : "subject type can't be null (i.e. void)";
138
139 if (TypeUtils.isNullableType(subjectJetType)) {
140 int nullEntryIndex = findNullEntryIndex(expression);
141 Label nullLabel = nullEntryIndex == -1 ? defaultLabel : entryLabels.get(nullEntryIndex);
142 Label notNullLabel = new Label();
143
144 v.dup();
145 v.ifnonnull(notNullLabel);
146
147 v.pop();
148
149 v.goTo(nullLabel);
150
151 v.visitLabel(notNullLabel);
152 }
153 }
154
155 private int findNullEntryIndex(@NotNull JetWhenExpression expression) {
156 int entryIndex = 0;
157 for (JetWhenEntry entry : expression.getEntries()) {
158 for (CompileTimeConstant constant : SwitchCodegenUtil.getConstantsFromEntry(entry, bindingContext)) {
159 if (constant instanceof NullValue) {
160 return entryIndex;
161 }
162 }
163
164 entryIndex++;
165 }
166
167 return -1;
168 }
169
170 private void generateSwitchInstructionByTransitionsTable() {
171 int[] keys = new int[transitionsTable.size()];
172 Label[] labels = new Label[transitionsTable.size()];
173 int i = 0;
174
175 for (Map.Entry<Integer, Label> transition : transitionsTable.entrySet()) {
176 keys[i] = transition.getKey();
177 labels[i] = transition.getValue();
178
179 i++;
180 }
181
182 int nlabels = keys.length;
183 int hi = keys[nlabels - 1];
184 int lo = keys[0];
185
186 /*
187 * Heuristic estimation if it's better to use tableswitch or lookupswitch.
188 * From OpenJDK sources
189 */
190 long table_space_cost = 4 + ((long) hi - lo + 1); // words
191 long table_time_cost = 3; // comparisons
192 long lookup_space_cost = 3 + 2 * (long) nlabels;
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 }