[JVM_IR]: Improve stepping for when.
Additionally, use the line number of the class for default interface dispatch methods.
This commit is contained in:
+22
-15
@@ -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()
|
||||
|
||||
+49
-7
@@ -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)
|
||||
|
||||
+2
-1
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user