[JS IR] Optimize equals call for inline class instances.

Remove implicit boxing of inline class instances in equals call.
This commit is contained in:
Alexander Korepanov
2021-11-29 19:53:53 +03:00
committed by Space
parent b26a81435f
commit 2ae10f4378
3 changed files with 465 additions and 4 deletions
@@ -22,6 +22,7 @@ import org.jetbrains.kotlin.name.Name
class EqualityAndComparisonCallsTransformer(context: JsIrBackendContext) : CallsTransformer {
private val intrinsics = context.intrinsics
private val irBuiltIns = context.irBuiltIns
private val icUtils = context.inlineClassesUtils
private val symbolToTransformer: SymbolToTransformer = mutableMapOf()
@@ -77,12 +78,10 @@ class EqualityAndComparisonCallsTransformer(context: JsIrBackendContext) : Calls
val lhs = call.getValueArgument(0)!!
val rhs = call.getValueArgument(1)!!
val lhsJsType = lhs.type.getPrimitiveType()
val rhsJsType = rhs.type.getPrimitiveType()
val equalsMethod = lhs.type.findEqualsMethod()
val isLhsPrimitive = lhsJsType != PrimitiveType.OTHER
return when {
lhs.type is IrDynamicType ->
@@ -93,12 +92,16 @@ class EqualityAndComparisonCallsTransformer(context: JsIrBackendContext) : Calls
irCall(call, intrinsics.jsEqeq)
// For non-float primitives of the same type use JS `==`
isLhsPrimitive && lhsJsType == rhsJsType && lhsJsType != PrimitiveType.FLOATING_POINT_NUMBER ->
lhsJsType == rhsJsType && lhsJsType.canBeUsedWithJsEq() ->
chooseEqualityOperatorForPrimitiveTypes(call)
!isLhsPrimitive && !lhs.type.isNullable() && equalsMethod != null ->
!lhsJsType.isBuiltin() && !lhs.type.isNullable() && equalsMethod != null ->
irCall(call, equalsMethod.symbol, argumentsAsReceivers = true)
// For inline class instances we can try to unbox them for the equality comparison
lhs.isBoxIntrinsic() && rhs.isBoxIntrinsic() ->
optimizeInlineClassEquality(call, lhs, rhs)
else ->
irCall(call, intrinsics.jsEquals)
}
@@ -176,4 +179,46 @@ class EqualityAndComparisonCallsTransformer(context: JsIrBackendContext) : Calls
private fun IrFunction.isMethodOfPotentiallyPrimitiveJSType() =
isMethodOfPrimitiveJSType() || isFakeOverriddenFromAny()
private fun PrimitiveType.isBuiltin() =
this != PrimitiveType.OTHER
private fun PrimitiveType.canBeUsedWithJsEq() =
isBuiltin() && this != PrimitiveType.FLOATING_POINT_NUMBER
private fun IrType.isDefaultEqualsMethod() =
findEqualsMethod()?.origin === IrDeclarationOrigin.GENERATED_INLINE_CLASS_MEMBER
private fun IrExpression.isBoxIntrinsic() =
this is IrCall && symbol == icUtils.boxIntrinsic
private fun IrExpression.unboxParamWithInlinedClass(): Pair<IrExpression, IrClass?> {
val unboxed = (this as IrFunctionAccessExpression).getValueArgument(0)
?: error("Boxed expression is expected")
return Pair(unboxed, icUtils.getInlinedClass(unboxed.type))
}
private fun optimizeInlineClassEquality(call: IrFunctionAccessExpression, lhs: IrExpression, rhs: IrExpression): IrExpression {
val (lhsUnboxed, lhsClassType) = lhs.unboxParamWithInlinedClass()
val (rhsUnboxed, rhsClassType) = rhs.unboxParamWithInlinedClass()
if (lhsClassType !== null && lhsClassType === rhsClassType && lhsUnboxed.type.isDefaultEqualsMethod()) {
call.putValueArgument(0, lhsUnboxed)
call.putValueArgument(1, rhsUnboxed)
if (lhsUnboxed.type.getLowestUnderlyingType().getPrimitiveType().canBeUsedWithJsEq()) {
return chooseEqualityOperatorForPrimitiveTypes(call)
}
}
return irCall(call, intrinsics.jsEquals)
}
private fun IrType.getLowestUnderlyingType(): IrType {
if (isDefaultEqualsMethod()) {
val underlyingType = icUtils.getInlinedClass(this)?.inlineClassRepresentation?.underlyingType
if (underlyingType !== null) {
return underlyingType.getLowestUnderlyingType()
}
}
return this
}
}