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 }