[JS IR] Keep box and unbox intrinsics in call args after decomposing

^KT-59717 Fixed
This commit is contained in:
Alexander Korepanov
2023-06-28 17:20:39 +02:00
committed by Space Team
parent 148d8c9246
commit 4695dca056
7 changed files with 112 additions and 10 deletions
@@ -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
@@ -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)
@@ -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 {
@@ -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 {
@@ -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 {
+42
View File
@@ -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"
}
+5 -5
View File
@@ -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)