[K/N] Refactor default argument lowering to avoid boxing

Current inline classes API is not compatible between different backends.
So implementing common function required for this optimization
was not possible in Native backend.

So, common default arguments lowering was refactored to make bigger
piece of code replaceable in backends.

^KT-57860
This commit is contained in:
Pavel Kunyavskiy
2023-04-24 12:52:17 +02:00
committed by Space Team
parent 2aea5822b2
commit f55fd481e9
18 changed files with 141 additions and 54 deletions
@@ -26,14 +26,8 @@ abstract class Ir<out T : CommonBackendContext>(val context: T) {
abstract val symbols: Symbols
val defaultParameterDeclarationsCache = mutableMapOf<IrFunction, IrFunction>()
internal val localScopeWithCounterMap = LocalDeclarationsLowering.LocalScopeWithCounterMap()
// If irType is an inline class type, return the underlying type according to the
// unfolding rules of the current backend. Otherwise, returns null.
open fun unfoldInlineClassType(irType: IrType): IrType? = null
open fun shouldGenerateHandlerParameterForDefaultBodyFun() = false
}
@@ -15,12 +15,14 @@ import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
import org.jetbrains.kotlin.ir.expressions.impl.IrErrorExpressionImpl
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.makeNullable
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.utils.*
import org.jetbrains.kotlin.name.Name
abstract class DefaultArgumentFunctionFactory(open val context: CommonBackendContext) {
abstract class DefaultArgumentFunctionFactory(val context: CommonBackendContext) {
protected fun IrFunction.generateDefaultArgumentsFunctionName() =
Name.identifier("${name}\$default")
@@ -40,11 +42,19 @@ abstract class DefaultArgumentFunctionFactory(open val context: CommonBackendCon
contextReceiverParametersCount = original.contextReceiverParametersCount
}
protected fun IrFunction.copyValueParametersFrom(original: IrFunction, wrapWithNullable: Boolean = true) {
/**
* Whether `null` will be used for this type if no argument is passed.
* In that case, the type of the default dispatch function will be made nullable.
*
* By default, always returns `true` this is valid, but suboptimal.
* Better performance can be achieved in a backend-specific way.
*/
protected open fun IrType.hasNullAsUndefinedValue(): Boolean = true
protected fun IrFunction.copyValueParametersFrom(original: IrFunction) {
valueParameters = original.valueParameters.memoryOptimizedMap {
val newType = it.type.remapTypeParameters(original.classIfConstructor, classIfConstructor)
val makeNullable = wrapWithNullable && it.defaultValue != null &&
(context.ir.unfoldInlineClassType(it.type) ?: it.type) !in context.irBuiltIns.primitiveIrTypes
val makeNullable = it.defaultValue != null && it.type.hasNullAsUndefinedValue()
it.copyTo(
this,
type = if (makeNullable) newType.makeNullable() else newType,
@@ -33,12 +33,12 @@ import org.jetbrains.kotlin.utils.memoryOptimizedPlus
// TODO: fix expect/actual default parameters
open class DefaultArgumentStubGenerator(
open val context: CommonBackendContext,
open class DefaultArgumentStubGenerator<TContext : CommonBackendContext>(
val context: TContext,
private val factory: DefaultArgumentFunctionFactory,
private val skipInlineMethods: Boolean = true,
private val skipExternalMethods: Boolean = false,
private val forceSetOverrideSymbols: Boolean = true,
private val factory: DefaultArgumentFunctionFactory = MaskedDefaultArgumentFunctionFactory(context)
private val forceSetOverrideSymbols: Boolean = true
) : DeclarationTransformer {
override val withLocalDeclarations: Boolean get() = true
@@ -255,12 +255,12 @@ open class DefaultArgumentStubGenerator(
private fun log(msg: () -> String) = context.log { "DEFAULT-REPLACER: ${msg()}" }
}
open class DefaultParameterInjector(
open val context: CommonBackendContext,
open class DefaultParameterInjector<TContext : CommonBackendContext>(
protected val context: TContext,
protected val factory: DefaultArgumentFunctionFactory,
protected val skipInline: Boolean = true,
protected val skipExternalMethods: Boolean = false,
protected val forceSetOverrideSymbols: Boolean = true,
protected val factory: DefaultArgumentFunctionFactory = MaskedDefaultArgumentFunctionFactory(context),
) : IrElementTransformerVoid(), BodyLoweringPass {
override fun lower(irBody: IrBody, container: IrDeclaration) {
@@ -499,8 +499,8 @@ class DefaultParameterPatchOverridenSymbolsLowering(
}
}
private class MaskedDefaultArgumentFunctionFactory(context: CommonBackendContext) : DefaultArgumentFunctionFactory(context) {
override fun IrFunction.generateDefaultArgumentStubFrom(original: IrFunction, useConstructorMarker: Boolean) {
open class MaskedDefaultArgumentFunctionFactory(context: CommonBackendContext) : DefaultArgumentFunctionFactory(context) {
final override fun IrFunction.generateDefaultArgumentStubFrom(original: IrFunction, useConstructorMarker: Boolean) {
copyAttributesFrom(original)
copyTypeParametersFrom(original)
copyReturnTypeFrom(original)
@@ -288,10 +288,6 @@ class JsIrBackendContext(
}
}
override fun unfoldInlineClassType(irType: IrType): IrType? {
return inlineClassesUtils.getInlinedClass(irType)?.typeWith()
}
override fun shouldGenerateHandlerParameterForDefaultBodyFun() = true
}
@@ -5,11 +5,13 @@
package org.jetbrains.kotlin.ir.backend.js.lower
import org.jetbrains.kotlin.backend.common.CommonBackendContext
import org.jetbrains.kotlin.backend.common.lower.DefaultArgumentFunctionFactory
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.JsLoweredDeclarationOrigin
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.makeNullable
import org.jetbrains.kotlin.ir.util.copyTypeParametersFrom
import org.jetbrains.kotlin.ir.util.defaultType
@@ -17,13 +19,16 @@ import org.jetbrains.kotlin.ir.util.isTopLevel
import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.name.Name
class JsDefaultArgumentFunctionFactory(override val context: JsIrBackendContext) : DefaultArgumentFunctionFactory(context) {
class JsDefaultArgumentFunctionFactory(context: CommonBackendContext) : DefaultArgumentFunctionFactory(context) {
override fun IrType.hasNullAsUndefinedValue() = false
override fun IrFunction.generateDefaultArgumentStubFrom(original: IrFunction, useConstructorMarker: Boolean) {
copyAttributesFrom(original)
copyTypeParametersFrom(original)
copyReturnTypeFrom(original)
copyReceiversFrom(original)
copyValueParametersFrom(original, wrapWithNullable = false)
copyValueParametersFrom(original)
if (!original.isTopLevel) {
introduceContextParam()
@@ -29,12 +29,12 @@ import org.jetbrains.kotlin.utils.addToStdlib.runIf
import org.jetbrains.kotlin.utils.memoryOptimizedMap
import org.jetbrains.kotlin.utils.memoryOptimizedPlus
class JsDefaultArgumentStubGenerator(override val context: JsIrBackendContext) :
DefaultArgumentStubGenerator(
context,
class JsDefaultArgumentStubGenerator(context: JsIrBackendContext) :
DefaultArgumentStubGenerator<JsIrBackendContext>(
context = context,
factory = JsDefaultArgumentFunctionFactory(context),
skipExternalMethods = true,
forceSetOverrideSymbols = false,
factory = JsDefaultArgumentFunctionFactory(context)
forceSetOverrideSymbols = false
) {
private fun IrBuilderWithScope.createDefaultResolutionExpression(
@@ -22,16 +22,15 @@ import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.util.copyAnnotations
import org.jetbrains.kotlin.ir.util.defaultType
import org.jetbrains.kotlin.ir.util.isTopLevel
import org.jetbrains.kotlin.ir.util.isVararg
class JsDefaultParameterInjector(override val context: JsIrBackendContext) :
DefaultParameterInjector(
class JsDefaultParameterInjector(context: JsIrBackendContext) :
DefaultParameterInjector<JsIrBackendContext>(
context,
factory = JsDefaultArgumentFunctionFactory(context),
skipExternalMethods = true,
forceSetOverrideSymbols = false,
factory = JsDefaultArgumentFunctionFactory(context)
forceSetOverrideSymbols = false
) {
override fun nullConst(startOffset: Int, endOffset: Int, irParameter: IrValueParameter): IrExpression? =
if (irParameter.isVararg && !irParameter.hasDefaultValue()) {
@@ -0,0 +1,16 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.backend.jvm.lower
import org.jetbrains.kotlin.backend.common.CommonBackendContext
import org.jetbrains.kotlin.backend.common.lower.*
import org.jetbrains.kotlin.backend.jvm.InlineClassAbi
import org.jetbrains.kotlin.ir.types.IrType
internal class JvmDefaultArgumentFunctionFactory(context: CommonBackendContext) : MaskedDefaultArgumentFunctionFactory(context) {
override fun IrType.hasNullAsUndefinedValue() =
(InlineClassAbi.unboxType(this) ?: this) !in context.irBuiltIns.primitiveIrTypes
}
@@ -19,7 +19,12 @@ import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.util.isFinalClass
import org.jetbrains.kotlin.ir.util.isTopLevelDeclaration
class JvmDefaultArgumentStubGenerator(override val context: JvmBackendContext) : DefaultArgumentStubGenerator(context, false, false) {
class JvmDefaultArgumentStubGenerator(context: JvmBackendContext) : DefaultArgumentStubGenerator<JvmBackendContext>(
context = context,
factory = JvmDefaultArgumentFunctionFactory(context),
skipInlineMethods = false,
skipExternalMethods = false
) {
override fun defaultArgumentStubVisibility(function: IrFunction) = function.getJvmVisibilityOfDefaultArgumentStub()
override fun useConstructorMarker(function: IrFunction): Boolean =
@@ -16,10 +16,12 @@ import org.jetbrains.kotlin.ir.declarations.IrValueParameter
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.types.IrType
class JvmDefaultParameterInjector(context: JvmBackendContext) :
DefaultParameterInjector(context, skipInline = false, skipExternalMethods = false) {
override val context: JvmBackendContext get() = super.context as JvmBackendContext
class JvmDefaultParameterInjector(context: JvmBackendContext) : DefaultParameterInjector<JvmBackendContext>(
context = context,
factory = JvmDefaultArgumentFunctionFactory(context),
skipInline = false,
skipExternalMethods = false
) {
override fun nullConst(startOffset: Int, endOffset: Int, irParameter: IrValueParameter): IrExpression? =
nullConst(startOffset, endOffset, irParameter.type)
@@ -293,10 +293,6 @@ class JvmBackendContext(
) : Ir<JvmBackendContext>(this) {
override val symbols = JvmSymbols(this@JvmBackendContext, symbolTable)
override fun unfoldInlineClassType(irType: IrType): IrType? {
return InlineClassAbi.unboxType(irType)
}
override fun shouldGenerateHandlerParameterForDefaultBodyFun() = true
}
@@ -369,7 +369,7 @@ private val addMainFunctionCallsLowering = makeCustomWasmModulePhase(
)
private val defaultArgumentStubGeneratorPhase = makeWasmModulePhase(
{ context -> DefaultArgumentStubGenerator(context, skipExternalMethods = true) },
{ context -> DefaultArgumentStubGenerator(context, MaskedDefaultArgumentFunctionFactory(context), skipExternalMethods = true) },
name = "DefaultArgumentStubGenerator",
description = "Generate synthetic stubs for functions with default parameter values"
)
@@ -382,7 +382,7 @@ private val defaultArgumentPatchOverridesPhase = makeWasmModulePhase(
)
private val defaultParameterInjectorPhase = makeWasmModulePhase(
{ context -> DefaultParameterInjector(context, skipExternalMethods = true) },
{ context -> DefaultParameterInjector(context, MaskedDefaultArgumentFunctionFactory(context), skipExternalMethods = true) },
name = "DefaultParameterInjector",
description = "Replace call site with default parameters with corresponding stub function",
prerequisite = setOf(innerClassesLoweringPhase)
@@ -226,9 +226,9 @@ private val volatilePhase = createFileLoweringPhase(
private val defaultParameterExtentPhase = createFileLoweringPhase(
{ context, irFile ->
KonanDefaultArgumentStubGenerator(context).lower(irFile)
NativeDefaultArgumentStubGenerator(context).lower(irFile)
DefaultParameterCleaner(context, replaceDefaultValuesWithStubs = true).lower(irFile)
KonanDefaultParameterInjector(context).lower(irFile)
NativeDefaultParameterInjector(context).lower(irFile)
},
name = "DefaultParameterExtent",
description = "Default parameter extent lowering",
@@ -0,0 +1,19 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.backend.konan.lower
import org.jetbrains.kotlin.backend.common.CommonBackendContext
import org.jetbrains.kotlin.backend.common.lower.*
import org.jetbrains.kotlin.backend.konan.PrimitiveBinaryType
import org.jetbrains.kotlin.backend.konan.computePrimitiveBinaryTypeOrNull
import org.jetbrains.kotlin.ir.types.IrType
internal class NativeDefaultArgumentFunctionFactory(context: CommonBackendContext) : MaskedDefaultArgumentFunctionFactory(context) {
override fun IrType.hasNullAsUndefinedValue(): Boolean {
val binaryType = computePrimitiveBinaryTypeOrNull() ?: return true
return binaryType == PrimitiveBinaryType.POINTER || binaryType == PrimitiveBinaryType.VECTOR128
}
}
@@ -12,9 +12,11 @@ import org.jetbrains.kotlin.ir.declarations.IrValueDeclaration
import org.jetbrains.kotlin.ir.declarations.IrValueParameter
import org.jetbrains.kotlin.ir.expressions.IrExpression
internal class KonanDefaultArgumentStubGenerator(override val context: Context)
: DefaultArgumentStubGenerator(context, skipInlineMethods = false)
{
internal class NativeDefaultArgumentStubGenerator(context: Context) : DefaultArgumentStubGenerator<Context>(
context = context,
factory = NativeDefaultArgumentFunctionFactory(context),
skipInlineMethods = false
) {
override fun IrBlockBodyBuilder.selectArgumentOrDefault(
defaultFlag: IrExpression,
parameter: IrValueParameter,
@@ -15,11 +15,14 @@ import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.util.irCall
internal class KonanDefaultParameterInjector(private val konanContext: KonanBackendContext)
: DefaultParameterInjector(konanContext, skipInline = false) {
internal class NativeDefaultParameterInjector(context: KonanBackendContext) : DefaultParameterInjector<KonanBackendContext>(
context = context,
factory = NativeDefaultArgumentFunctionFactory(context),
skipInline = false
) {
override fun nullConst(startOffset: Int, endOffset: Int, type: IrType): IrExpression {
val symbols = konanContext.ir.symbols
val symbols = context.ir.symbols
val nullConstOfEquivalentType = when (type.computePrimitiveBinaryTypeOrNull()) {
null -> IrConstImpl.constNull(startOffset, endOffset, context.irBuiltIns.nothingNType)
@@ -6487,6 +6487,10 @@ fileCheckTest("filecheck_intrinsics") {
annotatedSource = project.file('filecheck/intrinsics.kt')
}
fileCheckTest("filecheck_default_parameters_dont_box") {
annotatedSource = project.file('filecheck/default_parameters_dont_box.kt')
}
task kt53119_side_effect(type: KonanLocalTest) {
useGoldenData = true
source = "codegen/stringConcatenationTypeNarrowing/kt53119_side_effect.kt"
@@ -0,0 +1,36 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
value class A(val i: Int)
value class B(val a: A)
value class C(val s: String)
fun defaultInt(a: Int = 1, aa: Int = 1) = a
fun defaultA(a: A = A(1), aa: A = A(1)) = a.i
fun defaultB(b: B = B(A(1)), bb: B = B(A(1))) = b.a.i
fun defaultC(c: C = C("1"), cc: C = C("1")) = c.s
// CHECK-LABEL: "kfun:#main(){}"
fun main(){
// CHECK-LABEL: entry
// CHECK-NOT: <Int-box>
// CHECK-NOT: <A-box>
// CHECK-NOT: <B-box>
// CHECK-NOT: <C-box>
defaultInt()
defaultA()
defaultB()
defaultC()
defaultInt(1)
defaultA(A(1))
defaultB(B(A(1)))
defaultC(C("1"))
defaultInt(1, 1)
defaultA(A(1), A(1))
defaultB(B(A(1)), B(A(1)))
defaultC(C("1"), C("1"))
// CHECK-LABEL: epilogue
}