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.org.objectweb.asm.Label;
029 import org.jetbrains.org.objectweb.asm.Type;
030 import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter;
031
032 import java.util.*;
033
034 import static org.jetbrains.jet.lang.resolve.BindingContext.EXPRESSION_TYPE;
035
036 abstract public class SwitchCodegen {
037 protected final JetWhenExpression expression;
038 protected final boolean isStatement;
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 JetWhenExpression expression, boolean isStatement,
053 @NotNull ExpressionCodegen codegen
054 ) {
055 this.expression = expression;
056 this.isStatement = isStatement;
057 this.codegen = codegen;
058 this.bindingContext = codegen.getBindingContext();
059
060 subjectType = codegen.expressionType(expression.getSubjectExpression());
061 resultType = isStatement ? Type.VOID_TYPE : codegen.expressionType(expression);
062 v = codegen.v;
063 }
064
065 /**
066 * Generates bytecode for entire when expression
067 */
068 public void generate() {
069 prepareConfiguration();
070
071 boolean hasElse = expression.getElseExpression() != null;
072
073 // if there is no else-entry and it's statement then default --- endLabel
074 defaultLabel = (hasElse || !isStatement) ? elseLabel : endLabel;
075
076 generateSubject();
077
078 generateSwitchInstructionByTransitionsTable();
079
080 generateEntries();
081
082 // there is no else-entry but this is not statement, so we should return Unit
083 if (!hasElse && !isStatement) {
084 v.visitLabel(elseLabel);
085 codegen.putUnitInstanceOntoStackForNonExhaustiveWhen(expression);
086 }
087
088 codegen.markLineNumber(expression);
089 v.mark(endLabel);
090 }
091
092 /**
093 * Sets up transitionsTable and maybe something else needed in a special case
094 * Behaviour may be changed by overriding processConstant
095 */
096 private void prepareConfiguration() {
097 for (JetWhenEntry entry : expression.getEntries()) {
098 Label entryLabel = new Label();
099
100 for (CompileTimeConstant constant : SwitchCodegenUtil.getConstantsFromEntry(entry, bindingContext)) {
101 if (constant instanceof NullValue) continue;
102 processConstant(constant, entryLabel);
103 }
104
105 if (entry.isElse()) {
106 elseLabel = entryLabel;
107 }
108
109 entryLabels.add(entryLabel);
110 }
111 }
112
113 abstract protected void processConstant(
114 @NotNull CompileTimeConstant constant,
115 @NotNull Label entryLabel
116 );
117
118 protected void putTransitionOnce(int value, @NotNull Label entryLabel) {
119 if (!transitionsTable.containsKey(value)) {
120 transitionsTable.put(value, entryLabel);
121 }
122 }
123
124 /**
125 * Should generate int subject on top of the stack
126 * Default implementation just run codegen for actual subject of expression
127 * May also gen nullability check if needed
128 */
129 protected void generateSubject() {
130 codegen.gen(expression.getSubjectExpression(), subjectType);
131 }
132
133 protected void generateNullCheckIfNeeded() {
134 JetType subjectJetType = bindingContext.get(EXPRESSION_TYPE, expression.getSubjectExpression());
135
136 assert subjectJetType != null : "subject type can't be null (i.e. void)";
137
138 if (subjectJetType.isNullable()) {
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 (CompileTimeConstant 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 long lookup_time_cost = nlabels;
193
194 boolean useTableSwitch = nlabels > 0 &&
195 table_space_cost + 3 * table_time_cost <=
196 lookup_space_cost + 3 * lookup_time_cost;
197
198 if (!useTableSwitch) {
199 v.lookupswitch(defaultLabel, keys, labels);
200 return;
201 }
202
203 Label[] sparseLabels = new Label[hi - lo + 1];
204 Arrays.fill(sparseLabels, defaultLabel);
205
206 for (i = 0; i < keys.length; i++) {
207 sparseLabels[keys[i] - lo] = labels[i];
208 }
209
210 v.tableswitch(lo, hi, defaultLabel, sparseLabels);
211 }
212
213 protected void generateEntries() {
214 // resolving entries' entryLabels and generating entries' code
215 Iterator<Label> entryLabelsIterator = entryLabels.iterator();
216 for (JetWhenEntry entry : expression.getEntries()) {
217 v.visitLabel(entryLabelsIterator.next());
218
219 FrameMap.Mark mark = codegen.myFrameMap.mark();
220 codegen.gen(entry.getExpression(), resultType);
221 mark.dropTo();
222
223 if (!entry.isElse()) {
224 v.goTo(endLabel);
225 }
226 }
227 }
228 }