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    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 (CompileTimeConstant 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 CompileTimeConstant 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 (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            //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    }