[JS IR] Keep box and unbox intrinsics in call args after decomposing
^KT-59717 Fixed
This commit is contained in:
committed by
Space Team
parent
148d8c9246
commit
4695dca056
@@ -97,7 +97,9 @@ class JsIrBackendContext(
|
||||
override fun isSideEffectFree(call: IrCall): Boolean =
|
||||
call.symbol in intrinsics.primitiveToLiteralConstructor.values ||
|
||||
call.symbol == intrinsics.arrayLiteral ||
|
||||
call.symbol == intrinsics.arrayConcat
|
||||
call.symbol == intrinsics.arrayConcat ||
|
||||
call.symbol == intrinsics.jsBoxIntrinsic ||
|
||||
call.symbol == intrinsics.jsUnboxIntrinsic
|
||||
|
||||
val devMode = configuration[JSConfigurationKeys.DEVELOPER_MODE] ?: false
|
||||
val errorPolicy = configuration[JSConfigurationKeys.ERROR_TOLERANCE_POLICY] ?: ErrorTolerancePolicy.DEFAULT
|
||||
|
||||
+44
-4
@@ -16,6 +16,7 @@ import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
||||
import org.jetbrains.kotlin.ir.backend.js.JsCommonBackendContext
|
||||
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
|
||||
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.typeArguments
|
||||
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.expressions.*
|
||||
@@ -94,7 +95,7 @@ abstract class AbstractBlockDecomposerLowering(private val context: JsCommonBack
|
||||
}
|
||||
|
||||
class BlockDecomposerTransformer(
|
||||
private val context: CommonBackendContext,
|
||||
private val context: JsCommonBackendContext,
|
||||
private val unreachableExpression: () -> IrExpression
|
||||
) : IrElementTransformerVoid() {
|
||||
private lateinit var function: IrDeclarationParent
|
||||
@@ -111,6 +112,9 @@ class BlockDecomposerTransformer(
|
||||
|
||||
private val booleanNotSymbol = context.irBuiltIns.booleanNotSymbol
|
||||
|
||||
private val boxIntrinsic = context.inlineClassesUtils.boxIntrinsic
|
||||
private val unboxIntrinsic = context.inlineClassesUtils.unboxIntrinsic
|
||||
|
||||
override fun visitScript(declaration: IrScript): IrStatement {
|
||||
function = declaration
|
||||
|
||||
@@ -537,9 +541,9 @@ class BlockDecomposerTransformer(
|
||||
value.isPure(anyVariable = false, context = context) -> value
|
||||
else -> {
|
||||
// TODO: do not wrap if value is pure (const, variable, etc)
|
||||
val irVar = makeTempVar(value.type, value)
|
||||
newStatements += irVar
|
||||
JsIrBuilder.buildGetValue(irVar.symbol)
|
||||
val (newArg, tempVar) = mapArgument(value)
|
||||
newStatements += tempVar
|
||||
newArg
|
||||
}
|
||||
}
|
||||
|
||||
@@ -548,6 +552,42 @@ class BlockDecomposerTransformer(
|
||||
return arguments
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the passing argument and store it in a temporary variable.
|
||||
* However, the box and unbox intrinsics should be preserved in the call.
|
||||
* They can be used later for optimizations, for example, in [EqualityAndComparisonCallsTransformer].
|
||||
* Example:
|
||||
* foo(boxIntrinsic(<expr>))
|
||||
* should be transformed to:
|
||||
* var tmp = <expr>
|
||||
* foo(boxIntrinsic(tmp))
|
||||
*/
|
||||
private fun mapArgument(arg: IrExpression): Pair<IrExpression, IrVariable> {
|
||||
var saveToTmp = arg
|
||||
var rootIntrinsicCall: IrCall? = null
|
||||
var lastIntrinsicCall: IrCall? = null
|
||||
while (saveToTmp is IrCall && (saveToTmp.symbol == boxIntrinsic || saveToTmp.symbol == unboxIntrinsic)) {
|
||||
if (lastIntrinsicCall == null) {
|
||||
lastIntrinsicCall = JsIrBuilder.buildCall(saveToTmp.symbol, saveToTmp.type, saveToTmp.typeArguments.filterNotNull())
|
||||
rootIntrinsicCall = lastIntrinsicCall
|
||||
} else {
|
||||
val nextCall = JsIrBuilder.buildCall(saveToTmp.symbol)
|
||||
lastIntrinsicCall.putValueArgument(0, nextCall)
|
||||
lastIntrinsicCall = nextCall
|
||||
}
|
||||
saveToTmp = saveToTmp.getValueArgument(0) ?: error("expect passing 1 argument to boxing intrinsic")
|
||||
}
|
||||
|
||||
val irTempVar = makeTempVar(saveToTmp.type, saveToTmp)
|
||||
val irGetTempVar = JsIrBuilder.buildGetValue(irTempVar.symbol)
|
||||
val newArg = lastIntrinsicCall?.let {
|
||||
it.putValueArgument(0, irGetTempVar)
|
||||
rootIntrinsicCall
|
||||
} ?: irGetTempVar
|
||||
|
||||
return newArg to irTempVar
|
||||
}
|
||||
|
||||
// TODO: remove this when vararg is lowered
|
||||
override fun visitVararg(expression: IrVararg): IrExpression {
|
||||
expression.transformChildrenVoid(expressionTransformer)
|
||||
|
||||
+6
@@ -246,6 +246,12 @@ public class FirJsBoxTestGenerated extends AbstractFirJsBoxTest {
|
||||
runTest("js/js.translator/testData/box/char/charIsCheck.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("charNoBoxing.kt")
|
||||
public void testCharNoBoxing() throws Exception {
|
||||
runTest("js/js.translator/testData/box/char/charNoBoxing.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("charRanges.kt")
|
||||
public void testCharRanges() throws Exception {
|
||||
|
||||
+6
@@ -246,6 +246,12 @@ public class IrBoxJsES6TestGenerated extends AbstractIrBoxJsES6Test {
|
||||
runTest("js/js.translator/testData/box/char/charIsCheck.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("charNoBoxing.kt")
|
||||
public void testCharNoBoxing() throws Exception {
|
||||
runTest("js/js.translator/testData/box/char/charNoBoxing.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("charRanges.kt")
|
||||
public void testCharRanges() throws Exception {
|
||||
|
||||
+6
@@ -246,6 +246,12 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest {
|
||||
runTest("js/js.translator/testData/box/char/charIsCheck.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("charNoBoxing.kt")
|
||||
public void testCharNoBoxing() throws Exception {
|
||||
runTest("js/js.translator/testData/box/char/charNoBoxing.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("charRanges.kt")
|
||||
public void testCharRanges() throws Exception {
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
fun getC(c: Char) = c
|
||||
inline fun getCInline(c: Char) = c
|
||||
|
||||
// CHECK_NEW_COUNT: function=testNonInline1 count=0
|
||||
fun testNonInline1(a: Char, b: Char) : Boolean {
|
||||
return getC(a) == getC(b)
|
||||
}
|
||||
|
||||
// CHECK_NEW_COUNT: function=testNonInline2 count=0
|
||||
fun testNonInline2(a: Char, b: Char) : Boolean {
|
||||
val a1 = getC(a)
|
||||
val b1 = getC(b)
|
||||
return a1 == b1
|
||||
}
|
||||
|
||||
// CHECK_NEW_COUNT: function=testInline1 count=0
|
||||
fun testInline1(a: Char, b: Char) : Boolean {
|
||||
return getCInline(a) == getCInline(b)
|
||||
}
|
||||
|
||||
// CHECK_NEW_COUNT: function=testInline2 count=0
|
||||
fun testInline2(a: Char, b: Char) : Boolean {
|
||||
val a1 = getCInline(a)
|
||||
val b1 = getCInline(b)
|
||||
return a1 == b1
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
if (!testNonInline1('a', 'a')) {
|
||||
return "Fail testNonInline1"
|
||||
}
|
||||
if (!testNonInline2('b', 'b')) {
|
||||
return "Fail testNonInline2"
|
||||
}
|
||||
if (!testInline1('c', 'c')) {
|
||||
return "Fail testInline1"
|
||||
}
|
||||
if (!testInline2('d', 'd')) {
|
||||
return "Fail testInline2"
|
||||
}
|
||||
return "OK"
|
||||
}
|
||||
@@ -141,8 +141,8 @@ fun testUnderlyingInline() {
|
||||
}
|
||||
caseJsEq()
|
||||
|
||||
// CHECK_CALLED_IN_SCOPE: scope=testUnderlyingInline$caseEquals function=equals
|
||||
// CHECK_NEW_COUNT: function=testUnderlyingInline$caseEquals count=4
|
||||
// CHECK_NOT_CALLED_IN_SCOPE: scope=testUnderlyingInline$caseEquals function=equals
|
||||
// CHECK_NEW_COUNT: function=testUnderlyingInline$caseEquals count=2
|
||||
fun caseEquals() {
|
||||
assertTrue(c1_1 == ClassUnderlayingInline(x1!!))
|
||||
assertTrue(ClassUnderlayingInline(x1!!) == c1_2)
|
||||
@@ -256,8 +256,8 @@ fun testNullableUnderlyingInlineClass() {
|
||||
}
|
||||
caseJsEq()
|
||||
|
||||
// CHECK_CALLED_IN_SCOPE: scope=testNullableUnderlyingInlineClass$caseEquals function=equals
|
||||
// CHECK_NEW_COUNT: function=testNullableUnderlyingInlineClass$caseEquals count=3
|
||||
// CHECK_NOT_CALLED_IN_SCOPE: scope=testNullableUnderlyingInlineClass$caseEquals function=equals
|
||||
// CHECK_NEW_COUNT: function=testNullableUnderlyingInlineClass$caseEquals count=1
|
||||
fun caseEquals() {
|
||||
assertTrue(ClassNullableUnderlayingInline(i1_1) == ClassNullableUnderlayingInline(i1_2!!))
|
||||
}
|
||||
@@ -265,7 +265,7 @@ fun testNullableUnderlyingInlineClass() {
|
||||
}
|
||||
|
||||
// CHECK_CALLED_IN_SCOPE: scope=testInlineClassWithInterface function=equals
|
||||
// CHECK_NEW_COUNT: function=testInlineClassWithInterface count=14
|
||||
// CHECK_NEW_COUNT: function=testInlineClassWithInterface count=10
|
||||
fun testInlineClassWithInterface() {
|
||||
val xi_1_1: InterfaceForInlineClass = ClassIntWithInterface(1)
|
||||
val xi_1_2: InterfaceForInlineClass = ClassIntWithInterface(1)
|
||||
|
||||
Reference in New Issue
Block a user