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 }