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.KtWhenEntry;
023    import org.jetbrains.kotlin.psi.KtWhenExpression;
024    import org.jetbrains.kotlin.resolve.BindingContext;
025    import org.jetbrains.kotlin.resolve.constants.ConstantValue;
026    import org.jetbrains.kotlin.resolve.constants.NullValue;
027    import org.jetbrains.kotlin.types.KotlinType;
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 KtWhenExpression expression;
037        protected final boolean isStatement;
038        protected final boolean isExhaustive;
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 KtWhenExpression expression, boolean isStatement,
053                boolean isExhaustive, @NotNull ExpressionCodegen codegen
054        ) {
055            this.expression = expression;
056            this.isStatement = isStatement;
057            this.isExhaustive = isExhaustive;
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 || isExhaustive) ? 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 || isExhaustive)) {
085                v.visitLabel(elseLabel);
086                codegen.putUnitInstanceOntoStackForNonExhaustiveWhen(expression, isStatement);
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 (KtWhenEntry entry : expression.getEntries()) {
099                Label entryLabel = new Label();
100    
101                for (ConstantValue<?> 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 ConstantValue<?> 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            assert expression.getSubjectExpression() != null : "subject expression can't be null";
136            KotlinType subjectJetType = bindingContext.getType(expression.getSubjectExpression());
137    
138            assert subjectJetType != null : "subject type can't be null (i.e. void)";
139    
140            if (TypeUtils.isNullableType(subjectJetType)) {
141                int nullEntryIndex = findNullEntryIndex(expression);
142                Label nullLabel = nullEntryIndex == -1 ? defaultLabel : entryLabels.get(nullEntryIndex);
143                Label notNullLabel = new Label();
144    
145                v.dup();
146                v.ifnonnull(notNullLabel);
147    
148                v.pop();
149    
150                v.goTo(nullLabel);
151    
152                v.visitLabel(notNullLabel);
153            }
154        }
155    
156        private int findNullEntryIndex(@NotNull KtWhenExpression expression) {
157            int entryIndex = 0;
158            for (KtWhenEntry entry : expression.getEntries()) {
159                for (ConstantValue<?> constant : SwitchCodegenUtil.getConstantsFromEntry(entry, bindingContext)) {
160                    if (constant instanceof NullValue) {
161                        return entryIndex;
162                    }
163                }
164    
165                entryIndex++;
166            }
167    
168            return -1;
169        }
170    
171        private void generateSwitchInstructionByTransitionsTable() {
172            int[] keys = new int[transitionsTable.size()];
173            Label[] labels = new Label[transitionsTable.size()];
174            int i = 0;
175    
176            for (Map.Entry<Integer, Label> transition : transitionsTable.entrySet()) {
177                keys[i] = transition.getKey();
178                labels[i] = transition.getValue();
179    
180                i++;
181            }
182    
183            int nlabels = keys.length;
184            int hi = keys[nlabels - 1];
185            int lo = keys[0];
186    
187            /*
188             * Heuristic estimation if it's better to use tableswitch or lookupswitch.
189             * From OpenJDK sources
190             */
191            long table_space_cost = 4 + ((long) hi - lo + 1); // words
192            long table_time_cost = 3; // comparisons
193            long lookup_space_cost = 3 + 2 * (long) nlabels;
194            //noinspection UnnecessaryLocalVariable
195            long lookup_time_cost = nlabels;
196    
197            boolean useTableSwitch = nlabels > 0 &&
198                                     table_space_cost + 3 * table_time_cost <=
199                                     lookup_space_cost + 3 * lookup_time_cost;
200    
201            if (!useTableSwitch) {
202                v.lookupswitch(defaultLabel, keys, labels);
203                return;
204            }
205    
206            Label[] sparseLabels = new Label[hi - lo + 1];
207            Arrays.fill(sparseLabels, defaultLabel);
208    
209            for (i = 0; i < keys.length; i++) {
210                sparseLabels[keys[i] - lo] = labels[i];
211            }
212    
213            v.tableswitch(lo, hi, defaultLabel, sparseLabels);
214        }
215    
216        protected void generateEntries() {
217            // resolving entries' entryLabels and generating entries' code
218            Iterator<Label> entryLabelsIterator = entryLabels.iterator();
219            for (KtWhenEntry entry : expression.getEntries()) {
220                v.visitLabel(entryLabelsIterator.next());
221    
222                FrameMap.Mark mark = codegen.myFrameMap.mark();
223                codegen.gen(entry.getExpression(), resultType);
224                mark.dropTo();
225    
226                if (!entry.isElse()) {
227                    v.goTo(endLabel);
228                }
229            }
230        }
231    }