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    }