[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 =
|
override fun isSideEffectFree(call: IrCall): Boolean =
|
||||||
call.symbol in intrinsics.primitiveToLiteralConstructor.values ||
|
call.symbol in intrinsics.primitiveToLiteralConstructor.values ||
|
||||||
call.symbol == intrinsics.arrayLiteral ||
|
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 devMode = configuration[JSConfigurationKeys.DEVELOPER_MODE] ?: false
|
||||||
val errorPolicy = configuration[JSConfigurationKeys.ERROR_TOLERANCE_POLICY] ?: ErrorTolerancePolicy.DEFAULT
|
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.JsCommonBackendContext
|
||||||
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
|
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
|
||||||
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
|
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.builders.declarations.buildFun
|
||||||
import org.jetbrains.kotlin.ir.declarations.*
|
import org.jetbrains.kotlin.ir.declarations.*
|
||||||
import org.jetbrains.kotlin.ir.expressions.*
|
import org.jetbrains.kotlin.ir.expressions.*
|
||||||
@@ -94,7 +95,7 @@ abstract class AbstractBlockDecomposerLowering(private val context: JsCommonBack
|
|||||||
}
|
}
|
||||||
|
|
||||||
class BlockDecomposerTransformer(
|
class BlockDecomposerTransformer(
|
||||||
private val context: CommonBackendContext,
|
private val context: JsCommonBackendContext,
|
||||||
private val unreachableExpression: () -> IrExpression
|
private val unreachableExpression: () -> IrExpression
|
||||||
) : IrElementTransformerVoid() {
|
) : IrElementTransformerVoid() {
|
||||||
private lateinit var function: IrDeclarationParent
|
private lateinit var function: IrDeclarationParent
|
||||||
@@ -111,6 +112,9 @@ class BlockDecomposerTransformer(
|
|||||||
|
|
||||||
private val booleanNotSymbol = context.irBuiltIns.booleanNotSymbol
|
private val booleanNotSymbol = context.irBuiltIns.booleanNotSymbol
|
||||||
|
|
||||||
|
private val boxIntrinsic = context.inlineClassesUtils.boxIntrinsic
|
||||||
|
private val unboxIntrinsic = context.inlineClassesUtils.unboxIntrinsic
|
||||||
|
|
||||||
override fun visitScript(declaration: IrScript): IrStatement {
|
override fun visitScript(declaration: IrScript): IrStatement {
|
||||||
function = declaration
|
function = declaration
|
||||||
|
|
||||||
@@ -537,9 +541,9 @@ class BlockDecomposerTransformer(
|
|||||||
value.isPure(anyVariable = false, context = context) -> value
|
value.isPure(anyVariable = false, context = context) -> value
|
||||||
else -> {
|
else -> {
|
||||||
// TODO: do not wrap if value is pure (const, variable, etc)
|
// TODO: do not wrap if value is pure (const, variable, etc)
|
||||||
val irVar = makeTempVar(value.type, value)
|
val (newArg, tempVar) = mapArgument(value)
|
||||||
newStatements += irVar
|
newStatements += tempVar
|
||||||
JsIrBuilder.buildGetValue(irVar.symbol)
|
newArg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -548,6 +552,42 @@ class BlockDecomposerTransformer(
|
|||||||
return arguments
|
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
|
// TODO: remove this when vararg is lowered
|
||||||
override fun visitVararg(expression: IrVararg): IrExpression {
|
override fun visitVararg(expression: IrVararg): IrExpression {
|
||||||
expression.transformChildrenVoid(expressionTransformer)
|
expression.transformChildrenVoid(expressionTransformer)
|
||||||
|
|||||||
+6
@@ -246,6 +246,12 @@ public class FirJsBoxTestGenerated extends AbstractFirJsBoxTest {
|
|||||||
runTest("js/js.translator/testData/box/char/charIsCheck.kt");
|
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
|
@Test
|
||||||
@TestMetadata("charRanges.kt")
|
@TestMetadata("charRanges.kt")
|
||||||
public void testCharRanges() throws Exception {
|
public void testCharRanges() throws Exception {
|
||||||
|
|||||||
+6
@@ -246,6 +246,12 @@ public class IrBoxJsES6TestGenerated extends AbstractIrBoxJsES6Test {
|
|||||||
runTest("js/js.translator/testData/box/char/charIsCheck.kt");
|
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
|
@Test
|
||||||
@TestMetadata("charRanges.kt")
|
@TestMetadata("charRanges.kt")
|
||||||
public void testCharRanges() throws Exception {
|
public void testCharRanges() throws Exception {
|
||||||
|
|||||||
+6
@@ -246,6 +246,12 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest {
|
|||||||
runTest("js/js.translator/testData/box/char/charIsCheck.kt");
|
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
|
@Test
|
||||||
@TestMetadata("charRanges.kt")
|
@TestMetadata("charRanges.kt")
|
||||||
public void testCharRanges() throws Exception {
|
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()
|
caseJsEq()
|
||||||
|
|
||||||
// CHECK_CALLED_IN_SCOPE: scope=testUnderlyingInline$caseEquals function=equals
|
// CHECK_NOT_CALLED_IN_SCOPE: scope=testUnderlyingInline$caseEquals function=equals
|
||||||
// CHECK_NEW_COUNT: function=testUnderlyingInline$caseEquals count=4
|
// CHECK_NEW_COUNT: function=testUnderlyingInline$caseEquals count=2
|
||||||
fun caseEquals() {
|
fun caseEquals() {
|
||||||
assertTrue(c1_1 == ClassUnderlayingInline(x1!!))
|
assertTrue(c1_1 == ClassUnderlayingInline(x1!!))
|
||||||
assertTrue(ClassUnderlayingInline(x1!!) == c1_2)
|
assertTrue(ClassUnderlayingInline(x1!!) == c1_2)
|
||||||
@@ -256,8 +256,8 @@ fun testNullableUnderlyingInlineClass() {
|
|||||||
}
|
}
|
||||||
caseJsEq()
|
caseJsEq()
|
||||||
|
|
||||||
// CHECK_CALLED_IN_SCOPE: scope=testNullableUnderlyingInlineClass$caseEquals function=equals
|
// CHECK_NOT_CALLED_IN_SCOPE: scope=testNullableUnderlyingInlineClass$caseEquals function=equals
|
||||||
// CHECK_NEW_COUNT: function=testNullableUnderlyingInlineClass$caseEquals count=3
|
// CHECK_NEW_COUNT: function=testNullableUnderlyingInlineClass$caseEquals count=1
|
||||||
fun caseEquals() {
|
fun caseEquals() {
|
||||||
assertTrue(ClassNullableUnderlayingInline(i1_1) == ClassNullableUnderlayingInline(i1_2!!))
|
assertTrue(ClassNullableUnderlayingInline(i1_1) == ClassNullableUnderlayingInline(i1_2!!))
|
||||||
}
|
}
|
||||||
@@ -265,7 +265,7 @@ fun testNullableUnderlyingInlineClass() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CHECK_CALLED_IN_SCOPE: scope=testInlineClassWithInterface function=equals
|
// CHECK_CALLED_IN_SCOPE: scope=testInlineClassWithInterface function=equals
|
||||||
// CHECK_NEW_COUNT: function=testInlineClassWithInterface count=14
|
// CHECK_NEW_COUNT: function=testInlineClassWithInterface count=10
|
||||||
fun testInlineClassWithInterface() {
|
fun testInlineClassWithInterface() {
|
||||||
val xi_1_1: InterfaceForInlineClass = ClassIntWithInterface(1)
|
val xi_1_1: InterfaceForInlineClass = ClassIntWithInterface(1)
|
||||||
val xi_1_2: InterfaceForInlineClass = ClassIntWithInterface(1)
|
val xi_1_2: InterfaceForInlineClass = ClassIntWithInterface(1)
|
||||||
|
|||||||
Reference in New Issue
Block a user