[JVM_IR]: Improve stepping for when.

Additionally, use the line number of the class for default interface
dispatch methods.
This commit is contained in:
Mads Ager
2020-06-30 16:51:39 +02:00
committed by max-kammerer
parent 1009a240f2
commit 7f2efabe6a
17 changed files with 742 additions and 123 deletions
@@ -127,6 +127,7 @@ class ExpressionCodegen(
get() = typeMapper.typeSystem
override var lastLineNumber: Int = -1
var noLineNumberScope: Boolean = false
private val closureReifiedMarkers = hashMapOf<IrClass, ReifiedTypeParametersUsages>()
@@ -151,6 +152,7 @@ class ExpressionCodegen(
private fun getLineNumberForOffset(offset: Int): Int = fileEntry?.getLineNumber(offset)?.plus(1) ?: -1
private fun IrElement.markLineNumber(startOffset: Boolean) {
if (noLineNumberScope) return
val offset = if (startOffset) this.startOffset else endOffset
if (offset < 0) {
return
@@ -167,6 +169,13 @@ class ExpressionCodegen(
fun markLineNumber(element: IrElement) = element.markLineNumber(true)
fun noLineNumberScope(block: () -> Unit) {
val previousState = noLineNumberScope
noLineNumberScope = true
block()
noLineNumberScope = previousState
}
// TODO remove
fun gen(expression: IrExpression, type: Type, irType: IrType, data: BlockInfo): StackValue {
if (expression.attributeOwnerId === context.fakeContinuation) {
@@ -365,7 +374,7 @@ class ExpressionCodegen(
private fun visitStatementContainer(container: IrStatementContainer, data: BlockInfo) =
container.statements.fold(unitValue) { prev, exp ->
prev.discard()
exp.accept(this, data).also { (exp as? IrExpression)?.markEndOfStatementIfNeeded() }
exp.accept(this, data)
}
override fun visitBlockBody(body: IrBlockBody, data: BlockInfo): PromisedValue {
@@ -767,7 +776,18 @@ class ExpressionCodegen(
override fun visitWhen(expression: IrWhen, data: BlockInfo): PromisedValue {
expression.markLineNumber(startOffset = true)
SwitchGenerator(expression, data, this).generate()?.let { return it }
// When a lookup/table switch instruction is not generate, output a nop
// for the line number of the when itself. Otherwise, there will be
// no option of breaking on the line of the `when` if there is no
// subject:
//
// when {
// cond1 -> exp1
// else -> exp2
// }
if (expression.origin == IrStatementOrigin.WHEN) {
mv.nop()
}
val endLabel = Label()
val exhaustive = expression.branches.any { it.condition.isTrueConst() } && !expression.type.isUnit()
assert(exhaustive || expression.type.isUnit()) {
@@ -849,19 +869,6 @@ class ExpressionCodegen(
}
}
private fun IrExpression.markEndOfStatementIfNeeded() {
when (this) {
is IrWhen -> if (this.branches.size > 1) {
this.markLineNumber(false)
}
is IrTry -> this.markLineNumber(false)
is IrContainerExpression -> when (this.origin) {
IrStatementOrigin.WHEN, IrStatementOrigin.IF ->
this.markLineNumber(false)
}
}
}
override fun visitWhileLoop(loop: IrWhileLoop, data: BlockInfo): PromisedValue {
val continueLabel = markNewLabel()
val endLabel = Label()
@@ -348,7 +348,27 @@ class SwitchGenerator(private val expression: IrWhen, private val data: BlockInf
override fun shouldOptimize() = cases.size > 1
override fun genSwitch() {
subject.accept(codegen, data).materialize()
// Do not generate line numbers for the table switching. In particular,
// the subject is extracted from the condition of the first branch which
// will give the wrong stepping behavior for code such as:
//
// when {
// x == 42 -> 1
// x == 32 -> 2
// x == 24 -> 3
// ...
// }
//
// If the subject line number is generated, we will not stop on the line
// of the `when` but instead stop on the `x == 42` line. When x is 24,
// we would stop on the line `x == 42` and then step to the line `x == 24`.
// That is confusing and we prefer to stop on the `when` line and then step
// to the `x == 24` line. This is accomplished by ignoring the line number
// information for the subject as the `when` line number has already been
// emitted.
codegen.noLineNumberScope {
subject.accept(codegen, data).materialize()
}
genIntSwitch(cases)
}
}
@@ -419,13 +439,33 @@ class SwitchGenerator(private val expression: IrWhen, private val data: BlockInf
override fun genSwitch() {
with(codegen) {
if (subject.type.isNullableString()) {
// Do not generate line numbers for the table switching. In particular,
// the subject is extracted from the condition of the first branch which
// will give the wrong stepping behavior for code such as:
//
// when {
// x == "x" -> 1
// x == "y" -> 2
// x == "z" -> 3
// ...
// }
//
// If the subject line number is generated, we will not stop on the line
// of the `when` but instead stop on the `x == "x"` line. When x is "z",
// we would stop on the line `x == "x"` and then step to the line `x == "z"`.
// That is confusing and we prefer to stop on the `when` line and then step
// to the `x == "z"` line. This is accomplished by ignoring the line number
// information for the subject as the `when` line number has already been
// emitted.
noLineNumberScope {
if (subject.type.isNullableString()) {
subject.accept(codegen, data).materialize()
mv.ifnull(cases.find { it.value == null }?.label ?: defaultLabel)
}
// Reevaluating the subject is fine here because it is a read of a temporary.
subject.accept(codegen, data).materialize()
mv.ifnull(cases.find { it.value == null }?.label ?: defaultLabel)
mv.invokevirtual("java/lang/String", "hashCode", "()I", false)
}
// Reevaluating the subject is fine here because it is a read of a temporary.
subject.accept(codegen, data).materialize()
mv.invokevirtual("java/lang/String", "hashCode", "()I", false)
genIntSwitch(hashAndSwitchLabels)
// Multiple strings can be hashed into the same bucket.
@@ -433,7 +473,9 @@ class SwitchGenerator(private val expression: IrWhen, private val data: BlockInf
for ((hash, switchLabel) in hashAndSwitchLabels) {
mv.visitLabel(switchLabel)
for ((string, label) in hashToStringAndExprLabels[hash]!!) {
subject.accept(codegen, data).materialize()
noLineNumberScope {
subject.accept(codegen, data).materialize()
}
mv.aconst(string)
mv.invokevirtual("java/lang/String", "equals", "(Ljava/lang/Object;)Z", false)
mv.ifne(label)
@@ -88,7 +88,8 @@ private class InheritedDefaultMethodsOnClassesLowering(val context: JvmBackendCo
val superMethod = firstSuperMethodFromKotlin(irFunction, interfaceImplementation).owner
val defaultImplFun = context.cachedDeclarations.getDefaultImplsFunction(superMethod)
context.createIrBuilder(irFunction.symbol, UNDEFINED_OFFSET, UNDEFINED_OFFSET).apply {
val classStartOffset = classOverride.parentAsClass.startOffset
context.createIrBuilder(irFunction.symbol, classStartOffset, classStartOffset).apply {
irFunction.body = irBlockBody {
+irReturn(
irCall(defaultImplFun.symbol, irFunction.returnType).apply {