JVM_IR KT-48435 use Java-like counter loop when possible

This commit is contained in:
Dmitry Petrov
2021-08-30 16:05:34 +03:00
committed by TeamCityServer
parent d4c91c96d3
commit 1c1b9547c1
20 changed files with 295 additions and 26 deletions
@@ -0,0 +1,56 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.optimization
import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode
import org.jetbrains.org.objectweb.asm.tree.JumpInsnNode
import org.jetbrains.org.objectweb.asm.tree.MethodNode
class NegatedJumpsMethodTransformer : MethodTransformer() {
override fun transform(internalClassName: String, methodNode: MethodNode) {
val insnList = methodNode.instructions
// Replace sequence of instructions such as
// IF_ICMPLT L1 = insn
// GOTO L2 = next1
// L1: = next2
// with
// IF_ICMPGE L2 = negatedJumpInsn
// L1: = next2
for (insn in insnList.toArray()) {
if (insn.type != AbstractInsnNode.JUMP_INSN || insn.opcode == Opcodes.GOTO) continue
val next1 = insn.next ?: continue
if (next1.opcode != Opcodes.GOTO) continue
val next2 = next1.next ?: continue
if (next2 != (insn as JumpInsnNode).label) continue
val negatedJumpInsn = JumpInsnNode(negateConditionalJumpOpcode(insn.opcode), (next1 as JumpInsnNode).label)
insnList.insertBefore(insn, negatedJumpInsn)
insnList.remove(insn)
insnList.remove(next1)
}
}
private val negatedConditionalJumpOpcode = IntArray(255).also { a ->
fun negated(opcode1: Int, opcode2: Int) {
a[opcode1] = opcode2
a[opcode2] = opcode1
}
negated(Opcodes.IFNULL, Opcodes.IFNONNULL)
negated(Opcodes.IFEQ, Opcodes.IFNE)
negated(Opcodes.IFLT, Opcodes.IFGE)
negated(Opcodes.IFLE, Opcodes.IFGT)
negated(Opcodes.IF_ICMPEQ, Opcodes.IF_ICMPNE)
negated(Opcodes.IF_ICMPLT, Opcodes.IF_ICMPGE)
negated(Opcodes.IF_ICMPLE, Opcodes.IF_ICMPGT)
negated(Opcodes.IF_ACMPEQ, Opcodes.IF_ACMPNE)
}
private fun negateConditionalJumpOpcode(opcode: Int): Int =
negatedConditionalJumpOpcode[opcode]
}
@@ -59,6 +59,7 @@ class OptimizationMethodVisitor(
DeadCodeEliminationMethodTransformer(),
RedundantGotoMethodTransformer(),
RedundantNopsCleanupMethodTransformer(),
NegatedJumpsMethodTransformer(),
MethodVerifier("AFTER optimizations", generationState)
)
@@ -75,7 +75,7 @@ class RedundantGotoMethodTransformer : MethodTransformer() {
}
// Rewrite branch instructions.
if (!labelsToReplace.isEmpty()) {
if (labelsToReplace.isNotEmpty()) {
insns.filterIsInstance<JumpInsnNode>().forEach { rewriteLabelIfNeeded(it, labelsToReplace) }
}
@@ -33,10 +33,10 @@ class StackPeepholeOptimizationsTransformer : MethodTransformer() {
val insns = methodNode.instructions.toArray()
forInsn@ for (i in 1 until insns.size) {
for (i in 1 until insns.size) {
val insn = insns[i]
val prev = insn.previous
val prevNonNop = insn.findPreviousOrNull { it.opcode != Opcodes.NOP } ?: continue@forInsn
val prevNonNop = insn.findPreviousOrNull { it.opcode != Opcodes.NOP } ?: continue
when (insn.opcode) {
Opcodes.POP -> {
@@ -53,7 +53,7 @@ class StackPeepholeOptimizationsTransformer : MethodTransformer() {
}
Opcodes.SWAP -> {
val prevNonNop2 = prevNonNop.findPreviousOrNull { it.opcode != Opcodes.NOP } ?: continue@forInsn
val prevNonNop2 = prevNonNop.findPreviousOrNull { it.opcode != Opcodes.NOP } ?: continue
if (prevNonNop.isPurePushOfSize1() && prevNonNop2.isPurePushOfSize1()) {
actions.add {
it.remove(insn)
@@ -83,7 +83,7 @@ class StackPeepholeOptimizationsTransformer : MethodTransformer() {
it.remove(prevNonNop)
}
} else if (i > 1) {
val prevNonNop2 = prevNonNop.findPreviousOrNull { it.opcode != Opcodes.NOP } ?: continue@forInsn
val prevNonNop2 = prevNonNop.findPreviousOrNull { it.opcode != Opcodes.NOP } ?: continue
if (prevNonNop.isEliminatedByPop() && prevNonNop2.isEliminatedByPop()) {
actions.add {
it.set(insn, InsnNode(Opcodes.NOP))
@@ -30198,6 +30198,18 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
runTest("compiler/testData/codegen/box/ranges/forInFloatRangeWithCustomIterator.kt");
}
@Test
@TestMetadata("forInIntRangeToConstWithBreak.kt")
public void testForInIntRangeToConstWithBreak() throws Exception {
runTest("compiler/testData/codegen/box/ranges/forInIntRangeToConstWithBreak.kt");
}
@Test
@TestMetadata("forInIntRangeToConstWithContinue.kt")
public void testForInIntRangeToConstWithContinue() throws Exception {
runTest("compiler/testData/codegen/box/ranges/forInIntRangeToConstWithContinue.kt");
}
@Test
@TestMetadata("forInRangeLiteralWithMixedTypeBounds.kt")
public void testForInRangeLiteralWithMixedTypeBounds() throws Exception {
@@ -12,10 +12,10 @@ import org.jetbrains.kotlin.backend.common.lower.irIfThen
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.declarations.IrVariable
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrDoWhileLoopImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrWhileLoopImpl
import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
@@ -59,7 +59,7 @@ interface ForLoopHeader {
abstract class NumericForLoopHeader<T : NumericHeaderInfo>(
val headerInfo: T,
builder: DeclarationIrBuilder,
context: CommonBackendContext
protected val context: CommonBackendContext
) : ForLoopHeader {
override val consumesLoopVariableComponents = false
@@ -338,20 +338,25 @@ class ProgressionLoopHeader(
// (`for (int i = first; i < lastExclusive; ++i) { ... }`).
// Otherwise loop-related optimizations will not kick in, resulting in significant performance degradation.
//
// Use a simple while loop:
// If possible, use a do-while loop:
// do {
// if ( !( inductionVariable < last ) ) break
// val loopVariable = inductionVariable
// <body>
// } while ( { inductionVariable += step; true } )
// This loop form is equivalent to the Java counter loop shown above.
//
// Otherwise, use a simple while loop:
// while (inductionVar < last) {
// val loopVar = inductionVar
// inductionVar += step
// // Loop body
// }
//
val newLoop = IrWhileLoopImpl(oldLoop.startOffset, oldLoop.endOffset, oldLoop.type, oldLoop.origin).apply {
label = oldLoop.label
condition = buildLoopCondition(this@with)
body = newBody
}
LoopReplacement(newLoop, newLoop)
val newLoopCondition = buildLoopCondition(this@with)
buildJavaLikeDoWhileCounterLoop(oldLoop, newLoopCondition, newBody)
?: buildJavaLikeWhileCounterLoop(oldLoop, newLoopCondition, newBody)
} else {
// Use an if-guarded do-while loop (note the difference in loop condition):
//
@@ -372,6 +377,101 @@ class ProgressionLoopHeader(
LoopReplacement(newLoop, irIfThen(loopCondition, newLoop))
}
}
private val booleanNot =
context.irBuiltIns.booleanClass.owner.findDeclaration<IrSimpleFunction> {
it.name == OperatorNameConventions.NOT
} ?: error("No '${OperatorNameConventions.NOT}' in ${context.irBuiltIns.booleanClass.owner.render()}")
private fun buildJavaLikeDoWhileCounterLoop(
oldLoop: IrLoop,
newLoopCondition: IrExpression,
newBody: IrExpression?
): LoopReplacement? {
// Transform loop:
// while (<newLoopCondition>) {
// {
// <loopVarAssignments>
// inductionVariable += step
// }
// <originalLoopBody>
// }
// to:
// do {
// if (!(<newLoopCondition>)) break
// val forLoopVariable = inductionVariable
// <originalLoopBody>
// } while ( { inductionVariable += step; true } )
val bodyBlock = newBody as? IrContainerExpression ?: return null
val forLoopNextBlock = bodyBlock.statements[0] as? IrContainerExpression ?: return null
if (forLoopNextBlock.origin != IrStatementOrigin.FOR_LOOP_NEXT) return null
val loopStep = forLoopNextBlock.statements.last() as? IrSetValue ?: return null
val doWhileLoop = IrDoWhileLoopImpl(oldLoop.startOffset, oldLoop.endOffset, oldLoop.type, oldLoop.origin)
doWhileLoop.label = oldLoop.label
val conditionStartOffset = newLoopCondition.startOffset
val conditionEndOffset = newLoopCondition.endOffset
val negatedCondition =
IrCallImpl.fromSymbolOwner(conditionStartOffset, conditionEndOffset, booleanNot.symbol).apply {
dispatchReceiver = newLoopCondition
}
val negatedConditionCheck =
IrWhenImpl(
conditionStartOffset, conditionEndOffset, context.irBuiltIns.unitType, null,
listOf(
IrBranchImpl(
negatedCondition,
IrBreakImpl(conditionStartOffset, conditionEndOffset, context.irBuiltIns.nothingType, doWhileLoop)
)
)
)
bodyBlock.statements[0] = negatedConditionCheck
val loopVarAssignments =
if (forLoopNextBlock.statements.size == 2)
forLoopNextBlock.statements[0]
else
IrCompositeImpl(
forLoopNextBlock.startOffset, forLoopNextBlock.endOffset, forLoopNextBlock.type, null,
forLoopNextBlock.statements.subList(0, forLoopNextBlock.statements.lastIndex)
)
bodyBlock.statements.add(1, loopVarAssignments)
doWhileLoop.body = bodyBlock
val stepStartOffset = loopStep.startOffset
val stepEndOffset = loopStep.endOffset
val doWhileCondition =
IrCompositeImpl(
stepStartOffset, stepEndOffset, context.irBuiltIns.booleanType, null,
listOf(
loopStep,
IrConstImpl.boolean(stepStartOffset, stepEndOffset, context.irBuiltIns.booleanType, true)
)
)
doWhileLoop.condition = doWhileCondition
return LoopReplacement(doWhileLoop, doWhileLoop)
}
private fun buildJavaLikeWhileCounterLoop(
oldLoop: IrLoop,
newLoopCondition: IrExpression,
newBody: IrExpression?
): LoopReplacement {
// while (inductionVar < last) {
// val loopVar = inductionVar
// inductionVar += step
// // Loop body
// }
val newLoop = IrWhileLoopImpl(oldLoop.startOffset, oldLoop.endOffset, oldLoop.type, oldLoop.origin).apply {
label = oldLoop.label
condition = newLoopCondition
body = newBody
}
return LoopReplacement(newLoop, newLoop)
}
}
private class InitializerCallReplacer(val replacementCall: IrCall) : IrElementTransformerVoid() {
@@ -0,0 +1,16 @@
const val N = 10
fun sumUntil6(): Int {
var sum = 0
for (i in 0..N) {
if (i == 6) break
sum += i
}
return sum
}
fun box(): String {
val test = sumUntil6()
if (test != 15) return "Failed: $test"
return "OK"
}
@@ -0,0 +1,16 @@
const val N = 10
fun sumOdds(): Int {
var sum = 0
for (i in 0..N) {
if (i%2 == 0) continue
sum += i
}
return sum
}
fun box(): String {
val test = sumOdds()
if (test != 25) return "Failed: $test"
return "OK"
}
@@ -9,7 +9,4 @@ abstract class A8 : MutableCollection<Any> {
// 0 INSTANCEOF
/* Only 1 null check should be within the contains method */
// JVM_TEMPLATES:
// 1 IFNULL
// JVM_IR_TEMPLATES:
// 1 IFNONNULL
@@ -9,7 +9,4 @@ abstract class A<T : Any> : MutableCollection<T> {
// 0 INSTANCEOF
/* Only 1 null check should be within the contains method (because T is not nullable) */
// JVM_TEMPLATES:
// 1 IFNULL
// JVM_IR_TEMPLATES:
// 1 IFNONNULL
@@ -8,8 +8,8 @@ class A(val x: String) {
// JVM_TEMPLATES
// Optimization not implemented
// 8 IFNULL
// 0 IFNONNULL
// 4 IFNULL
// 4 IFNONNULL
// 2 ACONST_NULL
// JVM_IR_TEMPLATES
+1 -1
View File
@@ -26,6 +26,6 @@ fun box(): String {
// LOCAL VARIABLES JVM
// test.kt:5 box: i:int=0:int
// LOCAL VARIABLES JVM_IR
// test.kt:5 box:
// test.kt:5 box: i:int=0:int
// LOCAL VARIABLES
// test.kt:14 box:
+1 -1
View File
@@ -53,7 +53,7 @@ fun box() {
// test.kt:24 compute:
// test.kt:25 compute: s2:java.lang.String="NOPE":java.lang.String
// test.kt:26 compute: s2:java.lang.String="NOPE":java.lang.String, j:int=0:int
// test.kt:25 compute: s2:java.lang.String="OK":java.lang.String
// test.kt:25 compute: s2:java.lang.String="OK":java.lang.String, j:int=0:int
// test.kt:28 compute: s2:java.lang.String="OK":java.lang.String
// test.kt:34 box:
// test.kt:35 box: result:java.lang.String="NON_LOCAL_RETURN":java.lang.String
@@ -30042,6 +30042,18 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
runTest("compiler/testData/codegen/box/ranges/forInFloatRangeWithCustomIterator.kt");
}
@Test
@TestMetadata("forInIntRangeToConstWithBreak.kt")
public void testForInIntRangeToConstWithBreak() throws Exception {
runTest("compiler/testData/codegen/box/ranges/forInIntRangeToConstWithBreak.kt");
}
@Test
@TestMetadata("forInIntRangeToConstWithContinue.kt")
public void testForInIntRangeToConstWithContinue() throws Exception {
runTest("compiler/testData/codegen/box/ranges/forInIntRangeToConstWithContinue.kt");
}
@Test
@TestMetadata("forInRangeLiteralWithMixedTypeBounds.kt")
public void testForInRangeLiteralWithMixedTypeBounds() throws Exception {
@@ -30198,6 +30198,18 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
runTest("compiler/testData/codegen/box/ranges/forInFloatRangeWithCustomIterator.kt");
}
@Test
@TestMetadata("forInIntRangeToConstWithBreak.kt")
public void testForInIntRangeToConstWithBreak() throws Exception {
runTest("compiler/testData/codegen/box/ranges/forInIntRangeToConstWithBreak.kt");
}
@Test
@TestMetadata("forInIntRangeToConstWithContinue.kt")
public void testForInIntRangeToConstWithContinue() throws Exception {
runTest("compiler/testData/codegen/box/ranges/forInIntRangeToConstWithContinue.kt");
}
@Test
@TestMetadata("forInRangeLiteralWithMixedTypeBounds.kt")
public void testForInRangeLiteralWithMixedTypeBounds() throws Exception {
@@ -25556,6 +25556,16 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
runTest("compiler/testData/codegen/box/ranges/forInCustomIterable.kt");
}
@TestMetadata("forInIntRangeToConstWithBreak.kt")
public void testForInIntRangeToConstWithBreak() throws Exception {
runTest("compiler/testData/codegen/box/ranges/forInIntRangeToConstWithBreak.kt");
}
@TestMetadata("forInIntRangeToConstWithContinue.kt")
public void testForInIntRangeToConstWithContinue() throws Exception {
runTest("compiler/testData/codegen/box/ranges/forInIntRangeToConstWithContinue.kt");
}
@TestMetadata("forInRangeLiteralWithMixedTypeBounds.kt")
public void testForInRangeLiteralWithMixedTypeBounds() throws Exception {
runTest("compiler/testData/codegen/box/ranges/forInRangeLiteralWithMixedTypeBounds.kt");
@@ -20210,6 +20210,16 @@ public class IrJsCodegenBoxES6TestGenerated extends AbstractIrJsCodegenBoxES6Tes
runTest("compiler/testData/codegen/box/ranges/forInFloatRangeWithCustomIterator.kt");
}
@TestMetadata("forInIntRangeToConstWithBreak.kt")
public void testForInIntRangeToConstWithBreak() throws Exception {
runTest("compiler/testData/codegen/box/ranges/forInIntRangeToConstWithBreak.kt");
}
@TestMetadata("forInIntRangeToConstWithContinue.kt")
public void testForInIntRangeToConstWithContinue() throws Exception {
runTest("compiler/testData/codegen/box/ranges/forInIntRangeToConstWithContinue.kt");
}
@TestMetadata("forInRangeLiteralWithMixedTypeBounds.kt")
public void testForInRangeLiteralWithMixedTypeBounds() throws Exception {
runTest("compiler/testData/codegen/box/ranges/forInRangeLiteralWithMixedTypeBounds.kt");
@@ -19616,6 +19616,16 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
runTest("compiler/testData/codegen/box/ranges/forInFloatRangeWithCustomIterator.kt");
}
@TestMetadata("forInIntRangeToConstWithBreak.kt")
public void testForInIntRangeToConstWithBreak() throws Exception {
runTest("compiler/testData/codegen/box/ranges/forInIntRangeToConstWithBreak.kt");
}
@TestMetadata("forInIntRangeToConstWithContinue.kt")
public void testForInIntRangeToConstWithContinue() throws Exception {
runTest("compiler/testData/codegen/box/ranges/forInIntRangeToConstWithContinue.kt");
}
@TestMetadata("forInRangeLiteralWithMixedTypeBounds.kt")
public void testForInRangeLiteralWithMixedTypeBounds() throws Exception {
runTest("compiler/testData/codegen/box/ranges/forInRangeLiteralWithMixedTypeBounds.kt");
@@ -19666,6 +19666,16 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
runTest("compiler/testData/codegen/box/ranges/forInFloatRangeWithCustomIterator.kt");
}
@TestMetadata("forInIntRangeToConstWithBreak.kt")
public void testForInIntRangeToConstWithBreak() throws Exception {
runTest("compiler/testData/codegen/box/ranges/forInIntRangeToConstWithBreak.kt");
}
@TestMetadata("forInIntRangeToConstWithContinue.kt")
public void testForInIntRangeToConstWithContinue() throws Exception {
runTest("compiler/testData/codegen/box/ranges/forInIntRangeToConstWithContinue.kt");
}
@TestMetadata("forInRangeLiteralWithMixedTypeBounds.kt")
public void testForInRangeLiteralWithMixedTypeBounds() throws Exception {
runTest("compiler/testData/codegen/box/ranges/forInRangeLiteralWithMixedTypeBounds.kt");
@@ -12617,6 +12617,16 @@ public class IrCodegenBoxWasmTestGenerated extends AbstractIrCodegenBoxWasmTest
runTest("compiler/testData/codegen/box/ranges/forInFloatRangeWithCustomIterator.kt");
}
@TestMetadata("forInIntRangeToConstWithBreak.kt")
public void testForInIntRangeToConstWithBreak() throws Exception {
runTest("compiler/testData/codegen/box/ranges/forInIntRangeToConstWithBreak.kt");
}
@TestMetadata("forInIntRangeToConstWithContinue.kt")
public void testForInIntRangeToConstWithContinue() throws Exception {
runTest("compiler/testData/codegen/box/ranges/forInIntRangeToConstWithContinue.kt");
}
@TestMetadata("forInRangeLiteralWithMixedTypeBounds.kt")
public void testForInRangeLiteralWithMixedTypeBounds() throws Exception {
runTest("compiler/testData/codegen/box/ranges/forInRangeLiteralWithMixedTypeBounds.kt");