/* * Copyright 2010-2015 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jetbrains.kotlin.codegen.when; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.codegen.ExpressionCodegen; import org.jetbrains.kotlin.codegen.FrameMap; import org.jetbrains.kotlin.psi.KtWhenEntry; import org.jetbrains.kotlin.psi.KtWhenExpression; import org.jetbrains.kotlin.resolve.BindingContext; import org.jetbrains.kotlin.resolve.constants.ConstantValue; import org.jetbrains.kotlin.resolve.constants.NullValue; import org.jetbrains.kotlin.types.KotlinType; import org.jetbrains.kotlin.types.TypeUtils; import org.jetbrains.org.objectweb.asm.Label; import org.jetbrains.org.objectweb.asm.Type; import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter; import java.util.*; abstract public class SwitchCodegen { protected final KtWhenExpression expression; protected final boolean isStatement; protected final boolean isExhaustive; protected final ExpressionCodegen codegen; protected final BindingContext bindingContext; protected final Type subjectType; protected final Type resultType; protected final InstructionAdapter v; protected final NavigableMap<Integer, Label> transitionsTable = new TreeMap<>(); protected final List<Label> entryLabels = new ArrayList<>(); protected Label elseLabel = new Label(); protected Label endLabel = new Label(); protected Label defaultLabel; public SwitchCodegen( @NotNull KtWhenExpression expression, boolean isStatement, boolean isExhaustive, @NotNull ExpressionCodegen codegen, @Nullable Type subjectType ) { this.expression = expression; this.isStatement = isStatement; this.isExhaustive = isExhaustive; this.codegen = codegen; this.bindingContext = codegen.getBindingContext(); this.subjectType = subjectType != null ? subjectType : codegen.expressionType(expression.getSubjectExpression()); resultType = isStatement ? Type.VOID_TYPE : codegen.expressionType(expression); v = codegen.v; } /** * Generates bytecode for entire when expression */ public void generate() { prepareConfiguration(); boolean hasElse = expression.getElseExpression() != null; // if there is no else-entry and it's statement then default --- endLabel defaultLabel = (hasElse || !isStatement || isExhaustive) ? elseLabel : endLabel; generateSubject(); generateSwitchInstructionByTransitionsTable(); generateEntries(); // there is no else-entry but this is not statement, so we should return Unit if (!hasElse && (!isStatement || isExhaustive)) { v.visitLabel(elseLabel); codegen.putUnitInstanceOntoStackForNonExhaustiveWhen(expression, isStatement); } codegen.markLineNumber(expression, isStatement); v.mark(endLabel); } /** * Sets up transitionsTable and maybe something else needed in a special case * Behaviour may be changed by overriding processConstant */ private void prepareConfiguration() { for (KtWhenEntry entry : expression.getEntries()) { Label entryLabel = new Label(); for (ConstantValue<?> constant : SwitchCodegenUtil.getConstantsFromEntry(entry, bindingContext, codegen.getState().getShouldInlineConstVals())) { if (constant instanceof NullValue) continue; processConstant(constant, entryLabel); } if (entry.isElse()) { elseLabel = entryLabel; } entryLabels.add(entryLabel); } } abstract protected void processConstant( @NotNull ConstantValue<?> constant, @NotNull Label entryLabel ); protected void putTransitionOnce(int value, @NotNull Label entryLabel) { if (!transitionsTable.containsKey(value)) { transitionsTable.put(value, entryLabel); } } /** * Should generate int subject on top of the stack * Default implementation just run codegen for actual subject of expression * May also gen nullability check if needed */ protected void generateSubject() { codegen.gen(expression.getSubjectExpression(), subjectType); } protected void generateNullCheckIfNeeded() { assert expression.getSubjectExpression() != null : "subject expression can't be null"; KotlinType subjectJetType = bindingContext.getType(expression.getSubjectExpression()); assert subjectJetType != null : "subject type can't be null (i.e. void)"; if (TypeUtils.isNullableType(subjectJetType)) { int nullEntryIndex = findNullEntryIndex(expression); Label nullLabel = nullEntryIndex == -1 ? defaultLabel : entryLabels.get(nullEntryIndex); Label notNullLabel = new Label(); v.dup(); v.ifnonnull(notNullLabel); v.pop(); v.goTo(nullLabel); v.visitLabel(notNullLabel); } } private int findNullEntryIndex(@NotNull KtWhenExpression expression) { int entryIndex = 0; for (KtWhenEntry entry : expression.getEntries()) { for (ConstantValue<?> constant : SwitchCodegenUtil.getConstantsFromEntry(entry, bindingContext, codegen.getState().getShouldInlineConstVals())) { if (constant instanceof NullValue) { return entryIndex; } } entryIndex++; } return -1; } private void generateSwitchInstructionByTransitionsTable() { int[] keys = new int[transitionsTable.size()]; Label[] labels = new Label[transitionsTable.size()]; int i = 0; for (Map.Entry<Integer, Label> transition : transitionsTable.entrySet()) { keys[i] = transition.getKey(); labels[i] = transition.getValue(); i++; } int nlabels = keys.length; int hi = keys[nlabels - 1]; int lo = keys[0]; /* * Heuristic estimation if it's better to use tableswitch or lookupswitch. * From OpenJDK sources */ long table_space_cost = 4 + ((long) hi - lo + 1); // words long table_time_cost = 3; // comparisons long lookup_space_cost = 3 + 2 * (long) nlabels; //noinspection UnnecessaryLocalVariable long lookup_time_cost = nlabels; boolean useTableSwitch = nlabels > 0 && table_space_cost + 3 * table_time_cost <= lookup_space_cost + 3 * lookup_time_cost; if (!useTableSwitch) { v.lookupswitch(defaultLabel, keys, labels); return; } Label[] sparseLabels = new Label[hi - lo + 1]; Arrays.fill(sparseLabels, defaultLabel); for (i = 0; i < keys.length; i++) { sparseLabels[keys[i] - lo] = labels[i]; } v.tableswitch(lo, hi, defaultLabel, sparseLabels); } protected void generateEntries() { // resolving entries' entryLabels and generating entries' code Iterator<Label> entryLabelsIterator = entryLabels.iterator(); for (KtWhenEntry entry : expression.getEntries()) { v.visitLabel(entryLabelsIterator.next()); FrameMap.Mark mark = codegen.myFrameMap.mark(); codegen.gen(entry.getExpression(), resultType); mark.dropTo(); if (!entry.isElse()) { v.goTo(endLabel); } } } }