[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
+16
-7
@@ -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
|
||||
|
||||
+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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user