[JS IR] Fix boxing/unboxing of inline classes in coroutine scope

- don't box/unbox when value is known to be an inline class
 - add unbox state when coroutine resumed
 - correctly handle suspension in case of inline class
 - add tests
This commit is contained in:
Roman Artemev
2019-11-13 17:30:41 +03:00
committed by romanart
parent 52b24ead91
commit f2093a1763
12 changed files with 284 additions and 19 deletions
@@ -57,6 +57,7 @@ abstract class AbstractSuspendFunctionsLowering<C : CommonBackendContext>(val co
protected abstract fun initializeStateMachine(coroutineConstructors: List<IrConstructor>, coroutineClassThis: IrValueDeclaration)
protected open fun IrBuilderWithScope.generateDelegatedCall(expectedType: IrType, delegatingCall: IrExpression): IrExpression = delegatingCall
private val builtCoroutines = mutableMapOf<IrFunction, BuiltCoroutine>()
private val suspendLambdas = mutableMapOf<IrFunction, IrFunctionReference>()
@@ -228,7 +229,7 @@ abstract class AbstractSuspendFunctionsLowering<C : CommonBackendContext>(val co
val statements = (irFunction.body as IrBlockBody).statements
val lastStatement = statements.last()
assert(lastStatement == delegatingCall || lastStatement is IrReturn) { "Unexpected statement $lastStatement" }
statements[statements.lastIndex] = irReturn(returnValue)
statements[statements.lastIndex] = irReturn(generateDelegatedCall(irFunction.returnType, returnValue))
}
}
@@ -551,7 +552,7 @@ abstract class AbstractSuspendFunctionsLowering<C : CommonBackendContext>(val co
Name.identifier("invoke"),
Visibilities.PROTECTED,
Modality.FINAL,
irFunction.returnType,
context.irBuiltIns.anyNType,
isInline = false,
isExternal = false,
isTailrec = false,
@@ -44,14 +44,7 @@ class AutoboxingTransformer(val context: JsIrBackendContext) : AbstractValueUsag
val actualType = when (this) {
is IrConstructorCall -> symbol.owner.returnType
is IrCall -> {
val function = this.symbol.owner
if (function.let { it is IrSimpleFunction && it.isSuspend }) {
irBuiltIns.anyNType
} else {
function.realOverrideTarget.returnType
}
}
is IrCall -> symbol.owner.realOverrideTarget.returnType
is IrGetField -> this.symbol.owner.type
is IrTypeOperatorCall -> {
@@ -16,13 +16,16 @@ import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.symbols.IrFieldSymbol
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
import org.jetbrains.kotlin.ir.symbols.IrValueParameterSymbol
import org.jetbrains.kotlin.ir.symbols.IrValueSymbol
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.util.explicitParameters
import org.jetbrains.kotlin.ir.util.patchDeclarationParents
import org.jetbrains.kotlin.ir.visitors.*
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.utils.DFS
import org.jetbrains.kotlin.utils.addToStdlib.assertedCast
class JsSuspendFunctionsLowering(ctx: JsIrBackendContext) : AbstractSuspendFunctionsLowering<JsIrBackendContext>(ctx) {
@@ -215,6 +218,20 @@ class JsSuspendFunctionsLowering(ctx: JsIrBackendContext) : AbstractSuspendFunct
}
}
override fun IrBuilderWithScope.generateDelegatedCall(expectedType: IrType, delegatingCall: IrExpression): IrExpression {
val fromType = (delegatingCall as? IrCall)?.symbol?.owner?.returnType ?: delegatingCall.type
if (!needUnboxingOrUnit(fromType, expectedType)) return delegatingCall
val ctx = this@JsSuspendFunctionsLowering.context
return irComposite(resultType = fromType) {
val tmp = createTmpVariable(delegatingCall, irType = fromType)
val coroutineSuspended = irCall(ctx.coroutineSuspendGetter)
val condition = irEqeqeq(irGet(tmp), coroutineSuspended)
+irIfThen(fromType, condition, irReturn(irReinterpretCast(irGet(tmp), expectedType)))
+irGet(tmp)
}
}
override fun IrBlockBodyBuilder.generateCoroutineStart(invokeSuspendFunction: IrFunction, receiver: IrExpression) {
val dispatchReceiverVar = createTmpVariable(receiver, irType = receiver.type)
+irCall(coroutineImplResultSymbolSetter).apply {
@@ -225,8 +242,10 @@ class JsSuspendFunctionsLowering(ctx: JsIrBackendContext) : AbstractSuspendFunct
dispatchReceiver = irGet(dispatchReceiverVar)
putValueArgument(0, irNull())
}
+irReturn(irCall(invokeSuspendFunction.symbol).apply {
val call = irCall(invokeSuspendFunction.symbol).apply {
dispatchReceiver = irGet(dispatchReceiverVar)
})
}
val functionReturnType = scope.scopeOwnerSymbol.assertedCast<IrSimpleFunctionSymbol> { "Expected function symbol" }.owner.returnType
+irReturn(generateDelegatedCall(functionReturnType, call))
}
}
@@ -25,6 +25,7 @@ import org.jetbrains.kotlin.ir.symbols.IrValueParameterSymbol
import org.jetbrains.kotlin.ir.symbols.IrVariableSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols
import org.jetbrains.kotlin.ir.util.getInlinedClass
import org.jetbrains.kotlin.ir.visitors.*
class SuspendState(type: IrType) {
@@ -69,6 +70,7 @@ class StateMachineBuilder(
private val loopMap = mutableMapOf<IrLoop, LoopBounds>()
private val unit = context.irBuiltIns.unitType
private val anyN = context.irBuiltIns.anyNType
private val nothing = context.irBuiltIns.nothingType
private val booleanNotSymbol = context.irBuiltIns.booleanNotSymbol
private val eqeqeqSymbol = context.irBuiltIns.eqeqeqSymbol
@@ -278,16 +280,22 @@ class StateMachineBuilder(
override fun visitBlock(expression: IrBlock) =
if (expression is IrReturnableBlock) processReturnableBlock(expression) else super.visitBlock(expression)
private fun implicitCast(value: IrExpression, toType: IrType) =
JsIrBuilder.buildImplicitCast(value, toType)
private fun implicitCast(value: IrExpression, toType: IrType) = JsIrBuilder.buildImplicitCast(value, toType)
private fun reinterpretCast(value: IrExpression, toType: IrType) = JsIrBuilder.buildReinterpretCast(value, toType)
override fun visitCall(expression: IrCall) {
super.visitCall(expression)
if (expression.isSuspend) {
val result = lastExpression()
val expectedType = expression.symbol.owner.returnType
val isInlineClassExpected = expectedType.getInlinedClass() != null
val continueState = SuspendState(unit)
val dispatch = IrDispatchPoint(continueState)
val unboxState = if (isInlineClassExpected) SuspendState(unit) else null
val dispatch = IrDispatchPoint(unboxState ?: continueState)
if (unboxState != null) currentState.successors += unboxState
currentState.successors += continueState
@@ -298,7 +306,7 @@ class StateMachineBuilder(
}
}
addStatement(JsIrBuilder.buildSetVariable(suspendResult, result, unit))
addStatement(JsIrBuilder.buildSetVariable(suspendResult, reinterpretCast(result, anyN), unit))
val irReturn = JsIrBuilder.buildReturn(function, JsIrBuilder.buildGetValue(suspendResult), nothing)
val check = JsIrBuilder.buildCall(eqeqeqSymbol).apply {
@@ -308,13 +316,35 @@ class StateMachineBuilder(
val suspensionBlock = JsIrBuilder.buildBlock(unit, listOf(irReturn))
addStatement(JsIrBuilder.buildIfElse(unit, check, suspensionBlock))
if (isInlineClassExpected) {
addStatement(JsIrBuilder.buildCall(stateSymbolSetter.symbol, unit).apply {
dispatchReceiver = thisReceiver
putValueArgument(0, IrDispatchPoint(continueState))
})
}
doContinue()
unboxState?.let { buildUnboxingState(it, continueState, expectedType) }
updateState(continueState)
addStatement(implicitCast(JsIrBuilder.buildGetValue(suspendResult), expression.type))
val functionReturnType = expression.symbol.owner.returnType
addStatement(reinterpretCast(JsIrBuilder.buildGetValue(suspendResult), functionReturnType))
}
}
private fun buildUnboxingState(unboxState: SuspendState, continueState: SuspendState, expectedType: IrType) {
unboxState.successors += continueState
updateState(unboxState)
val result = JsIrBuilder.buildGetValue(suspendResult)
val tmp = JsIrBuilder.buildVar(expectedType, function.owner, name = "unboxed", initializer = result)
addStatement(tmp)
addStatement(JsIrBuilder.buildSetVariable(suspendResult, reinterpretCast(JsIrBuilder.buildGetValue(tmp.symbol), anyN), anyN))
doDispatch(continueState)
}
override fun visitBreak(jump: IrBreak) {
val exitState = loopMap[jump.loop]!!.exitState
doDispatch(exitState)
@@ -6,8 +6,6 @@
package org.jetbrains.kotlin.ir.backend.js.lower.coroutines
import org.jetbrains.kotlin.backend.common.ir.isSuspend
import org.jetbrains.kotlin.backend.common.pop
import org.jetbrains.kotlin.backend.common.push
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
@@ -19,6 +17,8 @@ import org.jetbrains.kotlin.ir.symbols.IrFieldSymbol
import org.jetbrains.kotlin.ir.symbols.IrReturnableBlockSymbol
import org.jetbrains.kotlin.ir.symbols.IrValueSymbol
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.isUnit
import org.jetbrains.kotlin.ir.util.getInlinedClass
import org.jetbrains.kotlin.ir.visitors.*
object COROUTINE_ROOT_LOOP : IrStatementOriginImpl("COROUTINE_ROOT_LOOP")
@@ -108,4 +108,9 @@ class LiveLocalsTransformer(
JsIrBuilder.buildComposite(declaration.type)
}
}
}
internal fun needUnboxingOrUnit(fromType: IrType, toType: IrType): Boolean {
return (fromType.getInlinedClass() == null && toType.getInlinedClass() != null) ||
(fromType.isUnit() && !toType.isUnit())
}
@@ -0,0 +1,96 @@
// WITH_RUNTIME
// WITH_COROUTINES
// COMMON_COROUTINES_TEST
import helpers.*
import COROUTINES_PACKAGE.*
import COROUTINES_PACKAGE.intrinsics.*
fun builder(c: suspend () -> Unit) {
c.startCoroutine(EmptyContinuation)
}
@Suppress("UNSUPPORTED_FEATURE")
inline class IC(val s: String)
class Test1() {
suspend fun <T> foo(value: T): T {
return suspendCoroutineUninterceptedOrReturn {
it.resume(value)
COROUTINE_SUSPENDED
}
}
suspend fun qux(ss: IC): IC = IC(ss.s)
suspend fun <T> quz(t: T): T = t
suspend fun bar(): IC {
return foo(qux(quz(IC("OK"))))
}
suspend fun test() = bar().s
}
class Test2 {
suspend fun foo(value: IC): IC {
return suspendCoroutineUninterceptedOrReturn {
it.resume(value)
COROUTINE_SUSPENDED
}
}
suspend fun qux(s: String): IC = IC(s)
suspend fun quz(): String = "OK"
suspend fun bar(): IC {
return foo(qux(quz()))
}
suspend fun test() = bar().s
}
class Test3 {
suspend fun <T> foo(value: T): T {
return suspendCoroutineUninterceptedOrReturn {
it.resume(value)
COROUTINE_SUSPENDED
}
}
suspend fun bar(): IC {
return foo(IC("OK"))
}
suspend fun test() = bar().s
}
fun box(): String {
var result = "FAIL"
builder {
result = Test1().test()
}
if (result != "OK") return "FAIL 1 $result"
result = "FAIL2"
builder {
result = Test2().test()
}
if (result != "OK") return "FAIL 2 $result"
result = "FAIL 3"
builder {
result = Test3().test()
}
return result
}
@@ -0,0 +1,41 @@
// WITH_RUNTIME
// WITH_COROUTINES
// COMMON_COROUTINES_TEST
import helpers.*
import COROUTINES_PACKAGE.*
import COROUTINES_PACKAGE.intrinsics.*
fun builder(c: suspend () -> Unit) {
c.startCoroutine(EmptyContinuation)
}
@Suppress("UNSUPPORTED_FEATURE")
inline class IC(val s: String)
interface I {
suspend fun foo(s: String): IC
}
class D: I {
override suspend fun foo(s: String): IC {
return IC(s)
}
}
class C(val d: I) : I by d
fun box(): String {
var result = "FAIL"
val d = D()
builder {
result = C(d).foo("OK").s
}
if (result != "OK") return "FAIL: $result"
return result
}
@@ -7358,6 +7358,16 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/coroutines/inlineClasses"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
}
@TestMetadata("boxUnboxInsideCoroutine.kt")
public void testBoxUnboxInsideCoroutine_1_2() throws Exception {
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/inlineClasses/boxUnboxInsideCoroutine.kt", "kotlin.coroutines.experimental");
}
@TestMetadata("boxUnboxInsideCoroutine.kt")
public void testBoxUnboxInsideCoroutine_1_3() throws Exception {
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/inlineClasses/boxUnboxInsideCoroutine.kt", "kotlin.coroutines");
}
@TestMetadata("bridgeGenerationCrossinline.kt")
public void testBridgeGenerationCrossinline_1_2() throws Exception {
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/inlineClasses/bridgeGenerationCrossinline.kt", "kotlin.coroutines.experimental");
@@ -7377,6 +7387,16 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
public void testBridgeGenerationNonInline_1_3() throws Exception {
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/inlineClasses/bridgeGenerationNonInline.kt", "kotlin.coroutines");
}
@TestMetadata("interfaceDelegateWithInlineClass.kt")
public void testInterfaceDelegateWithInlineClass_1_2() throws Exception {
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/inlineClasses/interfaceDelegateWithInlineClass.kt", "kotlin.coroutines.experimental");
}
@TestMetadata("interfaceDelegateWithInlineClass.kt")
public void testInterfaceDelegateWithInlineClass_1_3() throws Exception {
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/inlineClasses/interfaceDelegateWithInlineClass.kt", "kotlin.coroutines");
}
}
@TestMetadata("compiler/testData/codegen/box/coroutines/intLikeVarSpilling")
@@ -7358,6 +7358,16 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/coroutines/inlineClasses"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
}
@TestMetadata("boxUnboxInsideCoroutine.kt")
public void testBoxUnboxInsideCoroutine_1_2() throws Exception {
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/inlineClasses/boxUnboxInsideCoroutine.kt", "kotlin.coroutines.experimental");
}
@TestMetadata("boxUnboxInsideCoroutine.kt")
public void testBoxUnboxInsideCoroutine_1_3() throws Exception {
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/inlineClasses/boxUnboxInsideCoroutine.kt", "kotlin.coroutines");
}
@TestMetadata("bridgeGenerationCrossinline.kt")
public void testBridgeGenerationCrossinline_1_2() throws Exception {
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/inlineClasses/bridgeGenerationCrossinline.kt", "kotlin.coroutines.experimental");
@@ -7377,6 +7387,16 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
public void testBridgeGenerationNonInline_1_3() throws Exception {
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/inlineClasses/bridgeGenerationNonInline.kt", "kotlin.coroutines");
}
@TestMetadata("interfaceDelegateWithInlineClass.kt")
public void testInterfaceDelegateWithInlineClass_1_2() throws Exception {
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/inlineClasses/interfaceDelegateWithInlineClass.kt", "kotlin.coroutines.experimental");
}
@TestMetadata("interfaceDelegateWithInlineClass.kt")
public void testInterfaceDelegateWithInlineClass_1_3() throws Exception {
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/inlineClasses/interfaceDelegateWithInlineClass.kt", "kotlin.coroutines");
}
}
@TestMetadata("compiler/testData/codegen/box/coroutines/intLikeVarSpilling")
@@ -6683,6 +6683,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/coroutines/inlineClasses"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM_IR, true);
}
@TestMetadata("boxUnboxInsideCoroutine.kt")
public void testBoxUnboxInsideCoroutine_1_3() throws Exception {
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/inlineClasses/boxUnboxInsideCoroutine.kt", "kotlin.coroutines");
}
@TestMetadata("bridgeGenerationCrossinline.kt")
public void testBridgeGenerationCrossinline_1_3() throws Exception {
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/inlineClasses/bridgeGenerationCrossinline.kt", "kotlin.coroutines");
@@ -6692,6 +6697,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
public void testBridgeGenerationNonInline_1_3() throws Exception {
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/inlineClasses/bridgeGenerationNonInline.kt", "kotlin.coroutines");
}
@TestMetadata("interfaceDelegateWithInlineClass.kt")
public void testInterfaceDelegateWithInlineClass_1_3() throws Exception {
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/inlineClasses/interfaceDelegateWithInlineClass.kt", "kotlin.coroutines");
}
}
@TestMetadata("compiler/testData/codegen/box/coroutines/intLikeVarSpilling")
@@ -5708,6 +5708,11 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/coroutines/inlineClasses"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JS_IR, true);
}
@TestMetadata("boxUnboxInsideCoroutine.kt")
public void testBoxUnboxInsideCoroutine_1_3() throws Exception {
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/inlineClasses/boxUnboxInsideCoroutine.kt", "kotlin.coroutines");
}
@TestMetadata("bridgeGenerationCrossinline.kt")
public void testBridgeGenerationCrossinline_1_3() throws Exception {
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/inlineClasses/bridgeGenerationCrossinline.kt", "kotlin.coroutines");
@@ -5717,6 +5722,11 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
public void testBridgeGenerationNonInline_1_3() throws Exception {
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/inlineClasses/bridgeGenerationNonInline.kt", "kotlin.coroutines");
}
@TestMetadata("interfaceDelegateWithInlineClass.kt")
public void testInterfaceDelegateWithInlineClass_1_3() throws Exception {
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/inlineClasses/interfaceDelegateWithInlineClass.kt", "kotlin.coroutines");
}
}
@TestMetadata("compiler/testData/codegen/box/coroutines/intLikeVarSpilling")
@@ -6343,6 +6343,16 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/coroutines/inlineClasses"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JS, true);
}
@TestMetadata("boxUnboxInsideCoroutine.kt")
public void testBoxUnboxInsideCoroutine_1_2() throws Exception {
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/inlineClasses/boxUnboxInsideCoroutine.kt", "kotlin.coroutines.experimental");
}
@TestMetadata("boxUnboxInsideCoroutine.kt")
public void testBoxUnboxInsideCoroutine_1_3() throws Exception {
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/inlineClasses/boxUnboxInsideCoroutine.kt", "kotlin.coroutines");
}
@TestMetadata("bridgeGenerationCrossinline.kt")
public void testBridgeGenerationCrossinline_1_2() throws Exception {
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/inlineClasses/bridgeGenerationCrossinline.kt", "kotlin.coroutines.experimental");
@@ -6362,6 +6372,16 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
public void testBridgeGenerationNonInline_1_3() throws Exception {
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/inlineClasses/bridgeGenerationNonInline.kt", "kotlin.coroutines");
}
@TestMetadata("interfaceDelegateWithInlineClass.kt")
public void testInterfaceDelegateWithInlineClass_1_2() throws Exception {
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/inlineClasses/interfaceDelegateWithInlineClass.kt", "kotlin.coroutines.experimental");
}
@TestMetadata("interfaceDelegateWithInlineClass.kt")
public void testInterfaceDelegateWithInlineClass_1_3() throws Exception {
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/inlineClasses/interfaceDelegateWithInlineClass.kt", "kotlin.coroutines");
}
}
@TestMetadata("compiler/testData/codegen/box/coroutines/intLikeVarSpilling")