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.CompileTimeConstant;
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    import static org.jetbrains.kotlin.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            //noinspection UnnecessaryLocalVariable
194            long lookup_time_cost = nlabels;
195    
196            boolean useTableSwitch = nlabels > 0 &&
197                                     table_space_cost + 3 * table_time_cost <=
198                                     lookup_space_cost + 3 * lookup_time_cost;
199    
200            if (!useTableSwitch) {
201                v.lookupswitch(defaultLabel, keys, labels);
202                return;
203            }
204    
205            Label[] sparseLabels = new Label[hi - lo + 1];
206            Arrays.fill(sparseLabels, defaultLabel);
207    
208            for (i = 0; i < keys.length; i++) {
209                sparseLabels[keys[i] - lo] = labels[i];
210            }
211    
212            v.tableswitch(lo, hi, defaultLabel, sparseLabels);
213        }
214    
215        protected void generateEntries() {
216            // resolving entries' entryLabels and generating entries' code
217            Iterator<Label> entryLabelsIterator = entryLabels.iterator();
218            for (JetWhenEntry entry : expression.getEntries()) {
219                v.visitLabel(entryLabelsIterator.next());
220    
221                FrameMap.Mark mark = codegen.myFrameMap.mark();
222                codegen.gen(entry.getExpression(), resultType);
223                mark.dropTo();
224    
225                if (!entry.isElse()) {
226                    v.goTo(endLabel);
227                }
228            }
229        }
230    }