[JS IR] Improve debug info for suspend functions

- Map generated explicit Unit returns to the closing brace of
  the original body
- Name the continuation parameter as `$completion` to match the JVM BE,
  and generate debug info for it (so that it appears in the 'names'
  array in sourcemaps)
- Don't generate debug info for coroutine instantiation ceremony
  (so that the user doesn't need to step in many times to get where they
  want)

#KT-46276
This commit is contained in:
Sergej Jaskiewicz
2022-10-25 15:37:10 +02:00
committed by Space Team
parent 4d9c2d3d14
commit ec18dce7cb
6 changed files with 56 additions and 29 deletions
@@ -27,6 +27,7 @@ import org.jetbrains.kotlin.ir.types.typeWith
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.name.Name
/**
* Replaces suspend functions with regular non-suspend functions with additional
@@ -78,7 +79,13 @@ private fun transformSuspendFunction(context: CommonBackendContext, function: Ir
newBody.statements.lastOrNull() !is IrReturn
) {
// Adding explicit return of Unit.
newBody.statements += context.createIrBuilder(newFunctionWithContinuation.symbol).irReturnUnit()
// Set both offsets of the IrReturn to body.endOffset - 1 so that a breakpoint set at the closing brace of a lambda expression
// could be hit.
newBody.statements += context.createIrBuilder(
newFunctionWithContinuation.symbol,
startOffset = newBody.endOffset - 1,
endOffset = newBody.endOffset - 1
).irReturnUnit()
}
newFunctionWithContinuation.body = newBody
@@ -123,11 +130,13 @@ private fun IrSimpleFunction.createSuspendFunctionStub(context: CommonBackendCon
val remapper = ValueRemapper(mapping)
function.valueParameters.forEach { it.defaultValue = it.defaultValue?.transform(remapper, null) }
function.addValueParameter(
"\$cont",
continuationType(context).substitute(substitutionMap),
IrDeclarationOrigin.CONTINUATION
)
function.addValueParameter {
startOffset = function.startOffset
endOffset = function.endOffset
origin = IrDeclarationOrigin.CONTINUATION
name = Name.identifier("\$completion")
type = continuationType(context).substitute(substitutionMap)
}
}
}
@@ -136,4 +145,4 @@ private fun IrFunction.continuationType(context: CommonBackendContext): IrType {
}
fun loweredSuspendFunctionReturnType(function: IrFunction, irBuiltIns: IrBuiltIns): IrType =
if (function.returnType.isNullable()) irBuiltIns.anyNType else irBuiltIns.anyType
if (function.returnType.isNullable()) irBuiltIns.anyNType else irBuiltIns.anyType
@@ -11,6 +11,7 @@ import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.backend.js.lower.CallableReferenceLowering
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.builders.declarations.buildClass
@@ -93,7 +94,7 @@ abstract class AbstractSuspendFunctionsLowering<C : CommonBackendContext>(val co
})
// It is important to optimize the case where there is only one suspend call and it is the last statement
// because we don't need to build a fat coroutine class in that case.
// This happens a lot in practise because of suspend functions with default arguments.
// This happens a lot in practice because of suspend functions with default arguments.
// TODO: use TailRecursionCallsCollector.
val lastCall = when (val lastStatement = (body as IrBlockBody).statements.lastOrNull()) {
is IrCall -> lastStatement
@@ -181,7 +182,7 @@ abstract class AbstractSuspendFunctionsLowering<C : CommonBackendContext>(val co
// It is not a lambda - replace original function with a call to constructor of the built coroutine.
with(function) {
val irBuilder = context.createIrBuilder(symbol, startOffset, endOffset)
val irBuilder = context.createIrBuilder(symbol, UNDEFINED_OFFSET, UNDEFINED_OFFSET)
val functionBody = body as IrBlockBody
functionBody.statements.clear()
functionBody.statements.addAll(irBuilder.irBlockBody {
@@ -221,8 +222,6 @@ abstract class AbstractSuspendFunctionsLowering<C : CommonBackendContext>(val co
private fun buildNewCoroutineClass(function: IrSimpleFunction): IrClass =
context.irFactory.buildClass {
startOffset = function.startOffset
endOffset = function.endOffset
origin = DECLARATION_ORIGIN_COROUTINE_IMPL
name = nameForCoroutineClass(function)
visibility = function.visibility
@@ -243,8 +242,6 @@ abstract class AbstractSuspendFunctionsLowering<C : CommonBackendContext>(val co
}
return context.irFactory.buildConstructor {
startOffset = function.startOffset
endOffset = function.endOffset
origin = DECLARATION_ORIGIN_COROUTINE_IMPL
name = coroutineBaseClassConstructor.name
visibility = function.visibility
@@ -261,6 +258,8 @@ abstract class AbstractSuspendFunctionsLowering<C : CommonBackendContext>(val co
valueParameters += continuationParameter.copyTo(
this, DECLARATION_ORIGIN_COROUTINE_IMPL,
index = valueParameters.size,
startOffset = function.startOffset,
endOffset = function.endOffset,
type = continuationType,
defaultValue = null
)
@@ -329,8 +328,8 @@ abstract class AbstractSuspendFunctionsLowering<C : CommonBackendContext>(val co
// return i
private fun buildCreateMethod(superCreateFunction: IrSimpleFunction?, constructor: IrConstructor): IrSimpleFunction =
context.irFactory.buildFun {
startOffset = function.startOffset
endOffset = function.endOffset
startOffset = UNDEFINED_OFFSET
endOffset = UNDEFINED_OFFSET
origin = DECLARATION_ORIGIN_COROUTINE_IMPL
name = Name.identifier("create")
visibility = DescriptorVisibilities.PROTECTED
@@ -396,7 +395,7 @@ abstract class AbstractSuspendFunctionsLowering<C : CommonBackendContext>(val co
}
private fun transformInvokeMethod(createFunction: IrSimpleFunction, stateMachineFunction: IrSimpleFunction) {
val irBuilder = context.createIrBuilder(function.symbol, startOffset, endOffset)
val irBuilder = context.createIrBuilder(function.symbol, UNDEFINED_OFFSET, UNDEFINED_OFFSET)
val thisReceiver = function.dispatchReceiverParameter
?: compilationException(
"Expected dispatch receiver for invoke",
@@ -404,7 +403,7 @@ abstract class AbstractSuspendFunctionsLowering<C : CommonBackendContext>(val co
)
val functionBody = function.body as IrBlockBody
functionBody.statements.clear()
functionBody.statements.addAll(irBuilder.irBlockBody(startOffset, endOffset) {
functionBody.statements.addAll(irBuilder.irBlockBody(UNDEFINED_OFFSET, UNDEFINED_OFFSET) {
generateCoroutineStart(stateMachineFunction, irCall(createFunction).apply {
dispatchReceiver = irGet(thisReceiver)
var index = 0
@@ -486,11 +485,24 @@ abstract class AbstractSuspendFunctionsLowering<C : CommonBackendContext>(val co
if (delegatingCall.isReturnIfSuspendedCall())
delegatingCall.getValueArgument(0)!!
else delegatingCall
context.createIrBuilder(irFunction.symbol).run {
val statements = (irFunction.body as IrBlockBody).statements
val body = irFunction.body as IrBlockBody
// Set both offsets to body.endOffset - 1 so that a breakpoint set at the closing brace of a lambda expression
// could be hit.
context.createIrBuilder(irFunction.symbol, startOffset = body.endOffset - 1, endOffset = body.endOffset - 1).run {
val statements = body.statements
val lastStatement = statements.last()
assert(lastStatement == delegatingCall || lastStatement is IrReturn) { "Unexpected statement $lastStatement" }
statements[statements.lastIndex] = irReturn(generateDelegatedCall(irFunction.returnType, returnValue))
// Instead of returning right away, we save the value to a temporary variable and after that return that variable.
// This is done solely to improve the debugging experience. Otherwise, a breakpoint set to the closing brace of the function
// cannot be hit.
val tempVar = scope.createTemporaryVariable(
generateDelegatedCall(irFunction.returnType, returnValue),
irType = context.irBuiltIns.anyType,
)
statements[statements.lastIndex] = tempVar
statements.add(irReturn(irGet(tempVar)))
}
}
@@ -96,7 +96,18 @@ class StateMachineBuilder(
fun finalizeStateMachine() {
globalCatch = buildGlobalCatch()
if (currentBlock.statements.lastOrNull() !is IrReturn) {
addStatement(JsIrBuilder.buildReturn(function, unitValue, nothing))
// Set both offsets to rootLoop.endOffset - 1 so that a breakpoint set at the closing brace of a lambda expression
// could be hit.
// NOTE: rootLoop's offsets are the same as in the original function.
addStatement(
IrReturnImpl(
startOffset = rootLoop.endOffset - 1,
endOffset = rootLoop.endOffset - 1,
nothing,
function,
unitValue
)
)
}
if (!hasExceptions) entryState.successors += rootExceptionTrap
}
@@ -19,13 +19,8 @@ suspend fun box() {
// test.kt:11 box
// EXPECTATIONS JS_IR
// test.kt:7 box
// test.kt:7 <init>
// test.kt:7 <init>
// test.kt:7 box
// test.kt:7 box
// test.kt:7 box
// test.kt:8 doResume
// test.kt:4 foo
// test.kt:4 foo
// test.kt:9 box$lambda
// test.kt:11 doResume
+1 -1
View File
@@ -15,4 +15,4 @@ suspend fun bar(): Unit {
}
// LINES(JS): 39 4 4 4 7 5 5 45 45 5 93 45 5 5 6 4 4 4 9 15 9 9 9 * 9 15 10 10 11 11 11 11 11 * 11 12 12 13 13 13 13 13 13 13 14 14 * 9 15 9 9 9 9
// LINES(JS_IR): 4 4 * 19 * 19 19 * 5 * 45 * 50 50 93 93 3 45 3 45 45 6 6 19 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 * 10 10 * 11 * 11 12 12 * 13 * 13 13 14 14
// LINES(JS_IR): 4 4 * 19 * 19 19 * 5 * 45 * 50 50 93 93 3 45 3 45 45 6 6 19 19 7 7 9 9 * 9 * 10 10 * 11 * 11 12 12 * 13 * 13 13 14 14 15 15
@@ -9,4 +9,4 @@ suspend fun delay() {
}
// LINES(JS): 1 6 1 1 * 1 6 2 2 2 2 2 * 3 3 4 4 4 4 4 5 5 * 1 6 1 1 1 1 8 9
// LINES(JS_IR): 1 1 1 1 1 1 1 1 1 1 8 8 * 1 1 1 1 1 * 2 * 3 3 3 * 5 5
// LINES(JS_IR): 1 1 * 8 8 9 9 * 1 * 2 * 3 3 3 * 5 5 6 6