[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:
committed by
Space Team
parent
4d9c2d3d14
commit
ec18dce7cb
+25
-13
@@ -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)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+12
-1
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user