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.ConstantValue; 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 (ConstantValue<?> 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 ConstantValue<?> 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 (ConstantValue<?> 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 }