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    }