[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.util.*
|
||||||
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
||||||
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
|
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
|
||||||
|
import org.jetbrains.kotlin.name.Name
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replaces suspend functions with regular non-suspend functions with additional
|
* 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
|
newBody.statements.lastOrNull() !is IrReturn
|
||||||
) {
|
) {
|
||||||
// Adding explicit return of Unit.
|
// 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
|
newFunctionWithContinuation.body = newBody
|
||||||
@@ -123,11 +130,13 @@ private fun IrSimpleFunction.createSuspendFunctionStub(context: CommonBackendCon
|
|||||||
val remapper = ValueRemapper(mapping)
|
val remapper = ValueRemapper(mapping)
|
||||||
function.valueParameters.forEach { it.defaultValue = it.defaultValue?.transform(remapper, null) }
|
function.valueParameters.forEach { it.defaultValue = it.defaultValue?.transform(remapper, null) }
|
||||||
|
|
||||||
function.addValueParameter(
|
function.addValueParameter {
|
||||||
"\$cont",
|
startOffset = function.startOffset
|
||||||
continuationType(context).substitute(substitutionMap),
|
endOffset = function.endOffset
|
||||||
IrDeclarationOrigin.CONTINUATION
|
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 =
|
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.DescriptorVisibilities
|
||||||
import org.jetbrains.kotlin.descriptors.Modality
|
import org.jetbrains.kotlin.descriptors.Modality
|
||||||
import org.jetbrains.kotlin.ir.IrElement
|
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.backend.js.lower.CallableReferenceLowering
|
||||||
import org.jetbrains.kotlin.ir.builders.*
|
import org.jetbrains.kotlin.ir.builders.*
|
||||||
import org.jetbrains.kotlin.ir.builders.declarations.buildClass
|
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
|
// 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.
|
// 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.
|
// TODO: use TailRecursionCallsCollector.
|
||||||
val lastCall = when (val lastStatement = (body as IrBlockBody).statements.lastOrNull()) {
|
val lastCall = when (val lastStatement = (body as IrBlockBody).statements.lastOrNull()) {
|
||||||
is IrCall -> lastStatement
|
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.
|
// It is not a lambda - replace original function with a call to constructor of the built coroutine.
|
||||||
|
|
||||||
with(function) {
|
with(function) {
|
||||||
val irBuilder = context.createIrBuilder(symbol, startOffset, endOffset)
|
val irBuilder = context.createIrBuilder(symbol, UNDEFINED_OFFSET, UNDEFINED_OFFSET)
|
||||||
val functionBody = body as IrBlockBody
|
val functionBody = body as IrBlockBody
|
||||||
functionBody.statements.clear()
|
functionBody.statements.clear()
|
||||||
functionBody.statements.addAll(irBuilder.irBlockBody {
|
functionBody.statements.addAll(irBuilder.irBlockBody {
|
||||||
@@ -221,8 +222,6 @@ abstract class AbstractSuspendFunctionsLowering<C : CommonBackendContext>(val co
|
|||||||
|
|
||||||
private fun buildNewCoroutineClass(function: IrSimpleFunction): IrClass =
|
private fun buildNewCoroutineClass(function: IrSimpleFunction): IrClass =
|
||||||
context.irFactory.buildClass {
|
context.irFactory.buildClass {
|
||||||
startOffset = function.startOffset
|
|
||||||
endOffset = function.endOffset
|
|
||||||
origin = DECLARATION_ORIGIN_COROUTINE_IMPL
|
origin = DECLARATION_ORIGIN_COROUTINE_IMPL
|
||||||
name = nameForCoroutineClass(function)
|
name = nameForCoroutineClass(function)
|
||||||
visibility = function.visibility
|
visibility = function.visibility
|
||||||
@@ -243,8 +242,6 @@ abstract class AbstractSuspendFunctionsLowering<C : CommonBackendContext>(val co
|
|||||||
}
|
}
|
||||||
|
|
||||||
return context.irFactory.buildConstructor {
|
return context.irFactory.buildConstructor {
|
||||||
startOffset = function.startOffset
|
|
||||||
endOffset = function.endOffset
|
|
||||||
origin = DECLARATION_ORIGIN_COROUTINE_IMPL
|
origin = DECLARATION_ORIGIN_COROUTINE_IMPL
|
||||||
name = coroutineBaseClassConstructor.name
|
name = coroutineBaseClassConstructor.name
|
||||||
visibility = function.visibility
|
visibility = function.visibility
|
||||||
@@ -261,6 +258,8 @@ abstract class AbstractSuspendFunctionsLowering<C : CommonBackendContext>(val co
|
|||||||
valueParameters += continuationParameter.copyTo(
|
valueParameters += continuationParameter.copyTo(
|
||||||
this, DECLARATION_ORIGIN_COROUTINE_IMPL,
|
this, DECLARATION_ORIGIN_COROUTINE_IMPL,
|
||||||
index = valueParameters.size,
|
index = valueParameters.size,
|
||||||
|
startOffset = function.startOffset,
|
||||||
|
endOffset = function.endOffset,
|
||||||
type = continuationType,
|
type = continuationType,
|
||||||
defaultValue = null
|
defaultValue = null
|
||||||
)
|
)
|
||||||
@@ -329,8 +328,8 @@ abstract class AbstractSuspendFunctionsLowering<C : CommonBackendContext>(val co
|
|||||||
// return i
|
// return i
|
||||||
private fun buildCreateMethod(superCreateFunction: IrSimpleFunction?, constructor: IrConstructor): IrSimpleFunction =
|
private fun buildCreateMethod(superCreateFunction: IrSimpleFunction?, constructor: IrConstructor): IrSimpleFunction =
|
||||||
context.irFactory.buildFun {
|
context.irFactory.buildFun {
|
||||||
startOffset = function.startOffset
|
startOffset = UNDEFINED_OFFSET
|
||||||
endOffset = function.endOffset
|
endOffset = UNDEFINED_OFFSET
|
||||||
origin = DECLARATION_ORIGIN_COROUTINE_IMPL
|
origin = DECLARATION_ORIGIN_COROUTINE_IMPL
|
||||||
name = Name.identifier("create")
|
name = Name.identifier("create")
|
||||||
visibility = DescriptorVisibilities.PROTECTED
|
visibility = DescriptorVisibilities.PROTECTED
|
||||||
@@ -396,7 +395,7 @@ abstract class AbstractSuspendFunctionsLowering<C : CommonBackendContext>(val co
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun transformInvokeMethod(createFunction: IrSimpleFunction, stateMachineFunction: IrSimpleFunction) {
|
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
|
val thisReceiver = function.dispatchReceiverParameter
|
||||||
?: compilationException(
|
?: compilationException(
|
||||||
"Expected dispatch receiver for invoke",
|
"Expected dispatch receiver for invoke",
|
||||||
@@ -404,7 +403,7 @@ abstract class AbstractSuspendFunctionsLowering<C : CommonBackendContext>(val co
|
|||||||
)
|
)
|
||||||
val functionBody = function.body as IrBlockBody
|
val functionBody = function.body as IrBlockBody
|
||||||
functionBody.statements.clear()
|
functionBody.statements.clear()
|
||||||
functionBody.statements.addAll(irBuilder.irBlockBody(startOffset, endOffset) {
|
functionBody.statements.addAll(irBuilder.irBlockBody(UNDEFINED_OFFSET, UNDEFINED_OFFSET) {
|
||||||
generateCoroutineStart(stateMachineFunction, irCall(createFunction).apply {
|
generateCoroutineStart(stateMachineFunction, irCall(createFunction).apply {
|
||||||
dispatchReceiver = irGet(thisReceiver)
|
dispatchReceiver = irGet(thisReceiver)
|
||||||
var index = 0
|
var index = 0
|
||||||
@@ -486,11 +485,24 @@ abstract class AbstractSuspendFunctionsLowering<C : CommonBackendContext>(val co
|
|||||||
if (delegatingCall.isReturnIfSuspendedCall())
|
if (delegatingCall.isReturnIfSuspendedCall())
|
||||||
delegatingCall.getValueArgument(0)!!
|
delegatingCall.getValueArgument(0)!!
|
||||||
else delegatingCall
|
else delegatingCall
|
||||||
context.createIrBuilder(irFunction.symbol).run {
|
val body = irFunction.body as IrBlockBody
|
||||||
val statements = (irFunction.body as IrBlockBody).statements
|
|
||||||
|
// 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()
|
val lastStatement = statements.last()
|
||||||
assert(lastStatement == delegatingCall || lastStatement is IrReturn) { "Unexpected statement $lastStatement" }
|
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() {
|
fun finalizeStateMachine() {
|
||||||
globalCatch = buildGlobalCatch()
|
globalCatch = buildGlobalCatch()
|
||||||
if (currentBlock.statements.lastOrNull() !is IrReturn) {
|
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
|
if (!hasExceptions) entryState.successors += rootExceptionTrap
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,13 +19,8 @@ suspend fun box() {
|
|||||||
// test.kt:11 box
|
// test.kt:11 box
|
||||||
|
|
||||||
// EXPECTATIONS JS_IR
|
// 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:8 doResume
|
||||||
// test.kt:4 foo
|
// test.kt:4 foo
|
||||||
// test.kt:4 foo
|
// test.kt:4 foo
|
||||||
// test.kt:9 box$lambda
|
// 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): 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): 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