Convert SwitchCodegen to Kotlin, prettify and optimize codegen
This commit is contained in:
@@ -1,236 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
protected final SwitchCodegenProvider switchCodegenProvider;
|
|
||||||
|
|
||||||
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.switchCodegenProvider = new SwitchCodegenProvider(codegen);
|
|
||||||
|
|
||||||
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 : switchCodegenProvider.getConstantsFromEntry(entry)) {
|
|
||||||
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 : switchCodegenProvider.getConstantsFromEntry(entry)) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.codegen.`when`
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.codegen.ExpressionCodegen
|
||||||
|
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.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 class SwitchCodegen(
|
||||||
|
@JvmField
|
||||||
|
protected val expression: KtWhenExpression,
|
||||||
|
protected val isStatement: Boolean,
|
||||||
|
private val isExhaustive: Boolean,
|
||||||
|
@JvmField
|
||||||
|
protected val codegen: ExpressionCodegen,
|
||||||
|
subjectType: Type?
|
||||||
|
) {
|
||||||
|
protected val bindingContext: BindingContext = codegen.bindingContext
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
protected val subjectType = subjectType ?: codegen.expressionType(expression.subjectExpression)
|
||||||
|
|
||||||
|
protected val resultType: Type = if (isStatement) Type.VOID_TYPE else codegen.expressionType(expression)
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
protected val v: InstructionAdapter = codegen.v
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
protected val transitionsTable: NavigableMap<Int, Label> = TreeMap()
|
||||||
|
|
||||||
|
private val entryLabels: MutableList<Label> = ArrayList()
|
||||||
|
private var elseLabel = Label()
|
||||||
|
private var endLabel = Label()
|
||||||
|
protected lateinit var defaultLabel: Label
|
||||||
|
|
||||||
|
private val switchCodegenProvider = SwitchCodegenProvider(codegen)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates bytecode for entire when expression
|
||||||
|
*/
|
||||||
|
open fun generate() {
|
||||||
|
prepareConfiguration()
|
||||||
|
|
||||||
|
val hasElse = expression.elseExpression != null
|
||||||
|
|
||||||
|
// if there is no else-entry and it's statement then default --- endLabel
|
||||||
|
defaultLabel = if (hasElse || !isStatement || isExhaustive) elseLabel else 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 fun prepareConfiguration() {
|
||||||
|
for (entry in expression.entries) {
|
||||||
|
val entryLabel = Label()
|
||||||
|
|
||||||
|
for (constant in switchCodegenProvider.getConstantsFromEntry(entry)) {
|
||||||
|
if (constant is NullValue || constant == null) continue
|
||||||
|
processConstant(constant, entryLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.isElse) {
|
||||||
|
elseLabel = entryLabel
|
||||||
|
}
|
||||||
|
|
||||||
|
entryLabels.add(entryLabel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract fun processConstant(
|
||||||
|
constant: ConstantValue<*>,
|
||||||
|
entryLabel: Label
|
||||||
|
)
|
||||||
|
|
||||||
|
protected fun putTransitionOnce(value: Int, entryLabel: Label) {
|
||||||
|
if (!transitionsTable.containsKey(value)) {
|
||||||
|
transitionsTable[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 open fun generateSubject() {
|
||||||
|
codegen.gen(expression.subjectExpression, subjectType)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun generateNullCheckIfNeeded() {
|
||||||
|
assert(expression.subjectExpression != null) { "subject expression can't be null" }
|
||||||
|
val subjectJetType = bindingContext.getType(expression.subjectExpression!!) ?: error("subject type can't be null (i.e. void)")
|
||||||
|
|
||||||
|
if (TypeUtils.isNullableType(subjectJetType)) {
|
||||||
|
val nullEntryIndex = findNullEntryIndex(expression)
|
||||||
|
val nullLabel = if (nullEntryIndex == -1) defaultLabel else entryLabels[nullEntryIndex]
|
||||||
|
val notNullLabel = Label()
|
||||||
|
|
||||||
|
with(v) {
|
||||||
|
dup()
|
||||||
|
ifnonnull(notNullLabel)
|
||||||
|
pop()
|
||||||
|
goTo(nullLabel)
|
||||||
|
visitLabel(notNullLabel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findNullEntryIndex(expression: KtWhenExpression) =
|
||||||
|
expression.entries.withIndex().firstOrNull { (_, entry) ->
|
||||||
|
switchCodegenProvider.getConstantsFromEntry(entry).any { it is NullValue }
|
||||||
|
}?.index ?: -1
|
||||||
|
|
||||||
|
private fun generateSwitchInstructionByTransitionsTable() {
|
||||||
|
val keys = transitionsTable.keys.toIntArray()
|
||||||
|
|
||||||
|
val labelsNumber = keys.size
|
||||||
|
val maxValue = keys.last()
|
||||||
|
val minValue = keys.first()
|
||||||
|
val rangeLength = maxValue.toLong() - minValue.toLong() + 1L
|
||||||
|
|
||||||
|
// In modern JVM implementations it shouldn't matter very much for runtime performance
|
||||||
|
// whether to choose lookupswitch or tableswitch.
|
||||||
|
// The only metric that really matters is bytecode size and here we can estimate:
|
||||||
|
// - lookupswitch: ~ 2 * labelsNumber
|
||||||
|
// - tableswitch: ~ rangeLength
|
||||||
|
if (rangeLength > 2L * labelsNumber || rangeLength > Int.MAX_VALUE) {
|
||||||
|
val labels = transitionsTable.values.toTypedArray()
|
||||||
|
v.lookupswitch(defaultLabel, keys, labels)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val sparseLabels = Array(rangeLength.toInt()) { index ->
|
||||||
|
transitionsTable[index + minValue] ?: defaultLabel
|
||||||
|
}
|
||||||
|
|
||||||
|
v.tableswitch(minValue, maxValue, defaultLabel, *sparseLabels)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun generateEntries() {
|
||||||
|
// resolving entries' entryLabels and generating entries' code
|
||||||
|
val entryLabelsIterator = entryLabels.iterator()
|
||||||
|
for (entry in expression.entries) {
|
||||||
|
v.visitLabel(entryLabelsIterator.next())
|
||||||
|
|
||||||
|
val mark = codegen.myFrameMap.mark()
|
||||||
|
codegen.gen(entry.expression, resultType)
|
||||||
|
mark.dropTo()
|
||||||
|
|
||||||
|
if (!entry.isElse) {
|
||||||
|
v.goTo(endLabel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-2
@@ -9,6 +9,6 @@ fun box(): String {
|
|||||||
return if (b) "OK" else "FAIL"
|
return if (b) "OK" else "FAIL"
|
||||||
}
|
}
|
||||||
|
|
||||||
// 0 TABLESWITCH
|
// 1 TABLESWITCH
|
||||||
// 1 LOOKUPSWITCH
|
// 0 LOOKUPSWITCH
|
||||||
// 1 ATHROW
|
// 1 ATHROW
|
||||||
@@ -7,6 +7,6 @@ fun box(): String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 0 TABLESWITCH
|
// 1 TABLESWITCH
|
||||||
// 1 LOOKUPSWITCH
|
// 0 LOOKUPSWITCH
|
||||||
// 1 ATHROW
|
// 1 ATHROW
|
||||||
|
|||||||
+1
-2
@@ -12,5 +12,4 @@ fun foo2(x: Int): Boolean {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 0 TABLESWITCH
|
// 2 TABLESWITCH
|
||||||
// 2 LOOKUPSWITCH
|
|
||||||
|
|||||||
+1
-1
@@ -18,4 +18,4 @@ fun box() : String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1 LOOKUPSWITCH
|
// 1 TABLESWITCH
|
||||||
|
|||||||
@@ -24,4 +24,4 @@ fun box(): String {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1 LOOKUPSWITCH
|
// 1 TABLESWITCH
|
||||||
|
|||||||
@@ -24,4 +24,4 @@ fun box(): String {
|
|||||||
return "O".doTheThing() + Level.K.doTheThing()
|
return "O".doTheThing() + Level.K.doTheThing()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1 LOOKUPSWITCH
|
// 1 TABLESWITCH
|
||||||
|
|||||||
Reference in New Issue
Block a user