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 }