[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
@@ -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())
}