[JS IR] Optimize away upcasts
#KT-50212 Fixed
This commit is contained in:
+45
-2
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.backend.common.BodyLoweringPass
|
||||
import org.jetbrains.kotlin.backend.common.compilationException
|
||||
import org.jetbrains.kotlin.backend.common.ir.isPure
|
||||
import org.jetbrains.kotlin.ir.IrStatement
|
||||
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
||||
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
|
||||
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrArithBuilder
|
||||
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
|
||||
@@ -18,6 +19,7 @@ import org.jetbrains.kotlin.ir.declarations.IrDeclarationBase
|
||||
import org.jetbrains.kotlin.ir.declarations.IrDeclarationParent
|
||||
import org.jetbrains.kotlin.ir.expressions.*
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrCompositeImpl
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrTypeOperatorCallImpl
|
||||
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
|
||||
import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
|
||||
@@ -211,12 +213,53 @@ class TypeOperatorLowering(val context: JsIrBackendContext) : BodyLoweringPass {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The general logic for generating a runtime type check is as follows:
|
||||
* ```
|
||||
* ┌─────────────────────────────┬───────────────────────────────┐
|
||||
* │ to non-null │ to nullable │
|
||||
* ┌─────────────╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
* │ ┃ instanceof check │
|
||||
* │from non-null┃ OR │
|
||||
* │ ┃ advanced check │
|
||||
* ├─────────────╋─────────────────────────────┬───────────────────────────────┤
|
||||
* │ ┃ instanceof check │ null check + instanceof check │
|
||||
* │from nullable┃ OR │ OR │
|
||||
* │ ┃ null check + advanced check │ null check + advanced check │
|
||||
* └─────────────┻─────────────────────────────┴───────────────────────────────┘
|
||||
* ```
|
||||
* Note: advanced check is performed when casting to primitive types, array types, function types, or interfaces.
|
||||
*
|
||||
* If we statically know that this is an upcast, we try to generate no checks.
|
||||
* In this case, the following logic is applied:
|
||||
* ```
|
||||
* ┌─────────────┬─────────────┐
|
||||
* │ to non-null │ to nullable │
|
||||
* ┌─────────────╋━━━━━━━━━━━━━┻━━━━━━━━━━━━━┫
|
||||
* │from non-null┃ true │
|
||||
* ├─────────────╋─────────────┬─────────────┤
|
||||
* │from nullable┃ null check │ true │
|
||||
* └─────────────┻─────────────┴─────────────┘
|
||||
* ```
|
||||
*/
|
||||
private fun generateTypeCheck(argument: () -> IrExpression, toType: IrType): IrExpression {
|
||||
val toNotNullable = toType.makeNotNull()
|
||||
val argumentInstance = argument()
|
||||
val instanceCheck = generateTypeCheckNonNull(argumentInstance, toNotNullable)
|
||||
val isFromNullable = argumentInstance.type.isNullable()
|
||||
val fromType = argumentInstance.type
|
||||
val fromNotNullable = fromType.makeNotNull()
|
||||
|
||||
val isFromNullable = fromType.isNullable()
|
||||
val isToNullable = toType.isNullable()
|
||||
|
||||
if (fromNotNullable !is IrDynamicType && fromNotNullable.isSubtypeOf(toNotNullable, context.typeSystem)) {
|
||||
// This is an upcast
|
||||
return when {
|
||||
isFromNullable && !isToNullable -> calculator.run { not(nullCheck(argument())) }
|
||||
else -> IrConstImpl.constTrue(UNDEFINED_OFFSET, UNDEFINED_OFFSET, context.irBuiltIns.booleanType)
|
||||
}
|
||||
}
|
||||
|
||||
val instanceCheck = generateTypeCheckNonNull(argumentInstance, toNotNullable)
|
||||
val isNativeCheck = !advancedCheckRequired(toNotNullable)
|
||||
|
||||
return when {
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
// MODULE: lib
|
||||
// FILE: t.kt
|
||||
|
||||
fun bar(a: String, b: String): String
|
||||
fun bar(a: String, b: String): Any
|
||||
|
||||
fun foo(): String {
|
||||
fun foo(): Any {
|
||||
return bar("O", "K")
|
||||
}
|
||||
|
||||
@@ -17,4 +17,4 @@ fun box(): String {
|
||||
val r = foo()
|
||||
if (r is String) return r
|
||||
return "OK"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2281,6 +2281,12 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
|
||||
runTest("js/js.translator/testData/box/expression/cast/primitiveToClass.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("redundantCast.kt")
|
||||
public void testRedundantCast() throws Exception {
|
||||
runTest("js/js.translator/testData/box/expression/cast/redundantCast.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("reifiedToNotNull.kt")
|
||||
public void testReifiedToNotNull() throws Exception {
|
||||
|
||||
+6
@@ -2671,6 +2671,12 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest {
|
||||
runTest("js/js.translator/testData/box/expression/cast/primitiveToClass.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("redundantCast.kt")
|
||||
public void testRedundantCast() throws Exception {
|
||||
runTest("js/js.translator/testData/box/expression/cast/redundantCast.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("reifiedToNotNull.kt")
|
||||
public void testReifiedToNotNull() throws Exception {
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
// EXPECTED_REACHABLE_NODES: 1306
|
||||
|
||||
// Test that upcasts are optimized away
|
||||
|
||||
open class Base
|
||||
|
||||
class Derived: Base()
|
||||
|
||||
var counter = 0
|
||||
var _derived = Derived()
|
||||
|
||||
@JsExport
|
||||
fun getDerived(): Derived {
|
||||
counter += 1
|
||||
return _derived
|
||||
}
|
||||
|
||||
@JsExport
|
||||
fun getDerivedNullable(): Derived? {
|
||||
counter += 1
|
||||
return _derived
|
||||
}
|
||||
|
||||
@JsExport
|
||||
fun getDerivedNull(): Derived? {
|
||||
counter += 1
|
||||
return null
|
||||
}
|
||||
|
||||
// CHECK_CONTAINS_NO_CALLS: upcast1 except=getDerived IGNORED_BACKENDS=JS
|
||||
// CHECK_TERNARY_OPERATOR_COUNT: function=upcast1 count=0 IGNORED_BACKENDS=JS
|
||||
// CHECK_IF_COUNT: function=upcast1 count=0 IGNORED_BACKENDS=JS
|
||||
// CHECK_BINOP_COUNT: function=upcast1 count=0 symbol=instanceof IGNORED_BACKENDS=JS
|
||||
fun upcast1() = getDerived() as Base
|
||||
|
||||
// CHECK_CONTAINS_NO_CALLS: upcast2 except=getDerived IGNORED_BACKENDS=JS
|
||||
// CHECK_TERNARY_OPERATOR_COUNT: function=upcast2 count=0 IGNORED_BACKENDS=JS
|
||||
// CHECK_IF_COUNT: function=upcast2 count=0 IGNORED_BACKENDS=JS
|
||||
// CHECK_BINOP_COUNT: function=upcast2 count=0 symbol=instanceof IGNORED_BACKENDS=JS
|
||||
fun upcast2() = getDerived() as Base?
|
||||
|
||||
// CHECK_BINOP_COUNT: function=upcast3 count=0 symbol=instanceof IGNORED_BACKENDS=JS
|
||||
fun upcast3() = getDerivedNullable() as Base
|
||||
|
||||
// CHECK_CONTAINS_NO_CALLS: upcast4 except=getDerivedNullable IGNORED_BACKENDS=JS
|
||||
// CHECK_TERNARY_OPERATOR_COUNT: function=upcast4 count=0 IGNORED_BACKENDS=JS
|
||||
// CHECK_IF_COUNT: function=upcast4 count=0 IGNORED_BACKENDS=JS
|
||||
// CHECK_BINOP_COUNT: function=upcast1 count=0 symbol=instanceof IGNORED_BACKENDS=JS
|
||||
fun upcast4() = getDerivedNullable() as Base?
|
||||
|
||||
// CHECK_BINOP_COUNT: function=upcast5 count=0 symbol=instanceof IGNORED_BACKENDS=JS
|
||||
fun upcast5() = getDerivedNull() as Base
|
||||
|
||||
// CHECK_CONTAINS_NO_CALLS: upcast6 except=getDerivedNull IGNORED_BACKENDS=JS
|
||||
// CHECK_TERNARY_OPERATOR_COUNT: function=upcast6 count=0 IGNORED_BACKENDS=JS
|
||||
// CHECK_IF_COUNT: function=upcast6 count=0 IGNORED_BACKENDS=JS
|
||||
// CHECK_BINOP_COUNT: function=upcast6 count=0 symbol=instanceof IGNORED_BACKENDS=JS
|
||||
fun upcast6() = getDerivedNull() as Base?
|
||||
|
||||
// CHECK_CONTAINS_NO_CALLS: safeCast1 except=getDerived IGNORED_BACKENDS=JS
|
||||
// CHECK_TERNARY_OPERATOR_COUNT: function=safeCast1 count=0 IGNORED_BACKENDS=JS
|
||||
// CHECK_IF_COUNT: function=safeCast1 count=0 IGNORED_BACKENDS=JS
|
||||
// CHECK_BINOP_COUNT: function=safeCast1 count=0 symbol=instanceof IGNORED_BACKENDS=JS
|
||||
fun safeCast1() = getDerived() as? Base
|
||||
|
||||
// CHECK_CONTAINS_NO_CALLS: safeCast2 except=getDerived IGNORED_BACKENDS=JS
|
||||
// CHECK_TERNARY_OPERATOR_COUNT: function=safeCast1 count=0 IGNORED_BACKENDS=JS
|
||||
// CHECK_IF_COUNT: function=safeCast1 count=0 IGNORED_BACKENDS=JS
|
||||
// CHECK_BINOP_COUNT: function=safeCast2 count=0 symbol=instanceof IGNORED_BACKENDS=JS
|
||||
fun safeCast2() = getDerived() as? Base?
|
||||
|
||||
// CHECK_BINOP_COUNT: function=safeCast3 count=0 symbol=instanceof IGNORED_BACKENDS=JS
|
||||
fun safeCast3() = getDerivedNullable() as? Base
|
||||
|
||||
// CHECK_CONTAINS_NO_CALLS: safeCast4 except=getDerivedNullable IGNORED_BACKENDS=JS
|
||||
// CHECK_TERNARY_OPERATOR_COUNT: function=safeCast4 count=0 IGNORED_BACKENDS=JS
|
||||
// CHECK_IF_COUNT: function=safeCast4 count=0 IGNORED_BACKENDS=JS
|
||||
// CHECK_BINOP_COUNT: function=safeCast4 count=0 symbol=instanceof IGNORED_BACKENDS=JS
|
||||
fun safeCast4() = getDerivedNullable() as? Base?
|
||||
|
||||
// CHECK_BINOP_COUNT: function=safeCast5 count=0 symbol=instanceof IGNORED_BACKENDS=JS
|
||||
fun safeCast5() = getDerivedNull() as? Base
|
||||
|
||||
// CHECK_CONTAINS_NO_CALLS: safeCast6 except=getDerivedNull IGNORED_BACKENDS=JS
|
||||
// CHECK_TERNARY_OPERATOR_COUNT: function=safeCast6 count=0 IGNORED_BACKENDS=JS
|
||||
// CHECK_IF_COUNT: function=safeCast6 count=0 IGNORED_BACKENDS=JS
|
||||
// CHECK_BINOP_COUNT: function=safeCast6 count=0 symbol=instanceof IGNORED_BACKENDS=JS
|
||||
fun safeCast6() = getDerivedNull() as? Base?
|
||||
|
||||
// CHECK_CONTAINS_NO_CALLS: upcast1 except=getDerived IGNORED_BACKENDS=JS
|
||||
// CHECK_TERNARY_OPERATOR_COUNT: function=upcast1 count=0 IGNORED_BACKENDS=JS
|
||||
// CHECK_IF_COUNT: function=upcast1 count=0 IGNORED_BACKENDS=JS
|
||||
// CHECK_BINOP_COUNT: function=instanceCheck1 count=0 symbol=instanceof IGNORED_BACKENDS=JS
|
||||
fun instanceCheck1() = getDerived() is Base
|
||||
|
||||
// CHECK_CONTAINS_NO_CALLS: upcast2 except=getDerived IGNORED_BACKENDS=JS
|
||||
// CHECK_TERNARY_OPERATOR_COUNT: function=upcast2 count=0 IGNORED_BACKENDS=JS
|
||||
// CHECK_IF_COUNT: function=upcast2 count=0 IGNORED_BACKENDS=JS
|
||||
// CHECK_BINOP_COUNT: function=instanceCheck2 count=0 symbol=instanceof IGNORED_BACKENDS=JS
|
||||
fun instanceCheck2() = getDerived() is Base?
|
||||
|
||||
// CHECK_BINOP_COUNT: function=instanceCheck3 count=0 symbol=instanceof IGNORED_BACKENDS=JS
|
||||
fun instanceCheck3() = getDerivedNullable() is Base
|
||||
|
||||
// CHECK_CONTAINS_NO_CALLS: upcast4 except=getDerivedNullable IGNORED_BACKENDS=JS
|
||||
// CHECK_TERNARY_OPERATOR_COUNT: function=upcast4 count=0 IGNORED_BACKENDS=JS
|
||||
// CHECK_IF_COUNT: function=upcast4 count=0 IGNORED_BACKENDS=JS
|
||||
// CHECK_BINOP_COUNT: function=instanceCheck4 count=0 symbol=instanceof IGNORED_BACKENDS=JS
|
||||
fun instanceCheck4() = getDerivedNullable() is Base?
|
||||
|
||||
// CHECK_BINOP_COUNT: function=instanceCheck5 count=0 symbol=instanceof IGNORED_BACKENDS=JS
|
||||
fun instanceCheck5() = getDerivedNull() is Base
|
||||
|
||||
// CHECK_CONTAINS_NO_CALLS: upcast6 except=getDerivedNull IGNORED_BACKENDS=JS
|
||||
// CHECK_TERNARY_OPERATOR_COUNT: function=upcast6 count=0 IGNORED_BACKENDS=JS
|
||||
// CHECK_IF_COUNT: function=upcast6 count=0 IGNORED_BACKENDS=JS
|
||||
// CHECK_BINOP_COUNT: function=instanceCheck6 count=0 symbol=instanceof IGNORED_BACKENDS=JS
|
||||
fun instanceCheck6() = getDerivedNull() is Base?
|
||||
|
||||
fun box(): String {
|
||||
assertSame(_derived, upcast1(), "upcast1()")
|
||||
assertSame(_derived, upcast2(), "upcast2()")
|
||||
assertSame(_derived, upcast3(), "upcast3()")
|
||||
assertSame(_derived, upcast4(), "upcast4()")
|
||||
failsClassCast("upcast5()") { upcast5() }
|
||||
assertSame(null, upcast6(), "upcast6()")
|
||||
assertEquals(6, counter)
|
||||
|
||||
counter = 0
|
||||
|
||||
assertSame(_derived, safeCast1(), "safeCast1()")
|
||||
assertSame(_derived, safeCast2(), "safeCast2()")
|
||||
assertSame(_derived, safeCast3(), "safeCast3()")
|
||||
assertSame(_derived, safeCast4(), "safeCast4()")
|
||||
assertSame(null, safeCast5(), "safeCast5()")
|
||||
assertSame(null, safeCast6(), "safeCast6()")
|
||||
assertEquals(6, counter)
|
||||
|
||||
counter = 0
|
||||
|
||||
assertTrue(instanceCheck1(), "instanceCheck1()")
|
||||
assertTrue(instanceCheck2(), "instanceCheck2()")
|
||||
assertTrue(instanceCheck3(), "instanceCheck3()")
|
||||
assertTrue(instanceCheck4(), "instanceCheck4()")
|
||||
assertFalse(instanceCheck5(), "instanceCheck5()")
|
||||
assertTrue(instanceCheck6(), "instanceCheck6()")
|
||||
assertEquals(6, counter)
|
||||
|
||||
return "OK"
|
||||
}
|
||||
+2
-1
@@ -3,7 +3,8 @@ package foo
|
||||
|
||||
// CHECK_NOT_CALLED: isTypeOfOrNull
|
||||
// CHECK_NULLS_COUNT: function=box count=10 TARGET_BACKENDS=JS
|
||||
// CHECK_NULLS_COUNT: function=box count=6 IGNORED_BACKENDS=JS
|
||||
// CHECK_NULLS_COUNT: function=box count=0 IGNORED_BACKENDS=JS
|
||||
// CHECK_CONTAINS_NO_CALLS: box except=assertEquals;A IGNORED_BACKENDS=JS
|
||||
|
||||
inline
|
||||
fun <reified T> Any?.isTypeOfOrNull() = this is T?
|
||||
|
||||
+5
-4
@@ -1,6 +1,5 @@
|
||||
// EXPECTED_REACHABLE_NODES: 1285
|
||||
// CHECK_CALLED_IN_SCOPE: function=isType scope=box TARGET_BACKENDS=JS
|
||||
// CHECK_CALLED_IN_SCOPE: function=isObject scope=box IGNORED_BACKENDS=JS
|
||||
package foo
|
||||
|
||||
class A : Any()
|
||||
@@ -16,9 +15,11 @@ fun box(): String {
|
||||
|
||||
if (arrayOf(1, 2, 3).asAny() !is Any) return "fail3"
|
||||
|
||||
if (createNakedObject() is Any) return "fail4"
|
||||
if (testUtils.isLegacyBackend()) {
|
||||
if (createNakedObject() is Any) return "fail4"
|
||||
}
|
||||
|
||||
if (({ }).asAny() !is Any) return "fail5"
|
||||
if (({ }).asAny() !is Any) return "fail5"
|
||||
|
||||
if ((23).asAny() !is Any) return "fail6"
|
||||
|
||||
@@ -31,4 +32,4 @@ fun box(): String {
|
||||
return "OK"
|
||||
}
|
||||
|
||||
fun createNakedObject(): Any? = js("Object.create(null)")
|
||||
fun createNakedObject(): Any? = js("Object.create(null)")
|
||||
|
||||
Reference in New Issue
Block a user