Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright 2010-2020 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.FileLoweringPass
import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext
import org.jetbrains.kotlin.backend.common.ir.copyParameterDeclarationsFrom
import org.jetbrains.kotlin.backend.common.ir.passTypeArgumentsFrom
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.common.lower.irBlockBody
import org.jetbrains.kotlin.backend.common.lower.loops.forLoopsPhase
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
import org.jetbrains.kotlin.backend.common.pop
import org.jetbrains.kotlin.backend.common.push
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.*
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.config.ApiVersion
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.builders.declarations.addConstructor
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.symbols.IrValueSymbol
import org.jetbrains.kotlin.ir.transformStatement
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.classOrNull
import org.jetbrains.kotlin.ir.types.isNullable
import org.jetbrains.kotlin.ir.types.makeNotNull
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.JVM_INLINE_ANNOTATION_FQ_NAME
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
val jvmInlineClassPhase = makeIrFilePhase(
::JvmInlineClassLowering,
name = "Inline Classes",
description = "Lower inline classes",
// forLoopsPhase may produce UInt and ULong which are inline classes.
// Standard library replacements are done on the unmangled names for UInt and ULong classes.
// Collection stubs may require mangling by inline class rules.
prerequisite = setOf(forLoopsPhase, jvmStandardLibraryBuiltInsPhase, collectionStubMethodLowering)
)
/**
* Adds new constructors, box, and unbox functions to inline classes as well as replacement
* functions and bridges to avoid clashes between overloaded function. Changes calls with
* known types to call the replacement functions.
*
* We do not unfold inline class types here. Instead, the type mapper will lower inline class
* types to the types of their underlying field.
*/
private class JvmInlineClassLowering(private val context: JvmBackendContext) : FileLoweringPass, IrElementTransformerVoidWithContext() {
private val valueMap = mutableMapOf()
private fun addBindingsFor(original: IrFunction, replacement: IrFunction) {
for ((param, newParam) in original.explicitParameters.zip(replacement.explicitParameters)) {
valueMap[param.symbol] = newParam
}
}
override fun lower(irFile: IrFile) = irFile.transformChildrenVoid()
override fun visitClassNew(declaration: IrClass): IrStatement {
// The arguments to the primary constructor are in scope in the initializers of IrFields.
declaration.primaryConstructor?.let {
context.inlineClassReplacements.getReplacementFunction(it)?.let { replacement -> addBindingsFor(it, replacement) }
}
declaration.transformDeclarationsFlat { memberDeclaration ->
if (memberDeclaration is IrFunction) {
transformFunctionFlat(memberDeclaration)
} else {
memberDeclaration.accept(this, null)
null
}
}
if (declaration.isInline) {
val irConstructor = declaration.primaryConstructor!!
// The field getter is used by reflection and cannot be removed here unless it is internal.
declaration.declarations.removeIf {
it == irConstructor || (it is IrFunction && it.isInlineClassFieldGetter && !it.visibility.isPublicAPI)
}
buildPrimaryInlineClassConstructor(declaration, irConstructor)
buildBoxFunction(declaration)
buildUnboxFunction(declaration)
buildSpecializedEqualsMethod(declaration)
addJvmInlineAnnotation(declaration)
}
return declaration
}
private fun addJvmInlineAnnotation(declaration: IrClass) {
if (declaration.hasAnnotation(JVM_INLINE_ANNOTATION_FQ_NAME)) return
val constructor = context.ir.symbols.jvmInlineAnnotation.constructors.first()
declaration.annotations = declaration.annotations + IrConstructorCallImpl.fromSymbolOwner(
constructor.owner.returnType,
constructor
)
}
private fun transformFunctionFlat(function: IrFunction): List? {
if (function.isPrimaryInlineClassConstructor)
return null
val replacement = context.inlineClassReplacements.getReplacementFunction(function)
if (replacement == null) {
function.transformChildrenVoid()
return null
}
addBindingsFor(function, replacement)
return when (function) {
is IrSimpleFunction -> transformSimpleFunctionFlat(function, replacement)
is IrConstructor -> transformConstructorFlat(function, replacement)
else -> throw IllegalStateException()
}
}
private fun transformSimpleFunctionFlat(function: IrSimpleFunction, replacement: IrSimpleFunction): List {
replacement.valueParameters.forEach { it.transformChildrenVoid() }
allScopes.push(createScope(function))
replacement.body = function.body?.transform(this, null)?.patchDeclarationParents(replacement)
allScopes.pop()
replacement.copyAttributes(function)
// Don't create a wrapper for functions which are only used in an unboxed context
if (function.overriddenSymbols.isEmpty() || replacement.dispatchReceiverParameter != null)
return listOf(replacement)
val bridgeFunction = createBridgeDeclaration(
function,
when {
// If the original function has signature which need mangling we still need to replace it with a mangled version.
(!function.isFakeOverride || function.findInterfaceImplementation(context.state.jvmDefaultMode) != null) &&
function.signatureRequiresMangling() ->
replacement.name
// Since we remove the corresponding property symbol from the bridge we need to resolve getter/setter
// names at this point.
replacement.isGetter ->
Name.identifier(JvmAbi.getterName(replacement.correspondingPropertySymbol!!.owner.name.asString()))
replacement.isSetter ->
Name.identifier(JvmAbi.setterName(replacement.correspondingPropertySymbol!!.owner.name.asString()))
else ->
function.name
}
)
// Update the overridden symbols to point to their inline class replacements
bridgeFunction.overriddenSymbols = replacement.overriddenSymbols
// Replace the function body with a wrapper
if (!bridgeFunction.isFakeOverride || !bridgeFunction.parentAsClass.isInline) {
createBridgeBody(bridgeFunction, replacement)
} else {
// Fake overrides redirect from the replacement to the original function, which is in turn replaced during interfacePhase.
createBridgeBody(replacement, bridgeFunction)
}
return listOf(replacement, bridgeFunction)
}
private fun IrSimpleFunction.signatureRequiresMangling() =
fullValueParameterList.any { it.type.requiresMangling } ||
context.state.functionsWithInlineClassReturnTypesMangled && returnType.requiresMangling
// We may need to add a bridge method for inline class methods with static replacements. Ideally, we'd do this in BridgeLowering,
// but unfortunately this is a special case in the old backend. The bridge method is not marked as such and does not follow the normal
// visibility rules for bridge methods.
private fun createBridgeDeclaration(source: IrSimpleFunction, mangledName: Name) =
context.irFactory.buildFun {
updateFrom(source)
name = mangledName
returnType = source.returnType
}.apply {
copyParameterDeclarationsFrom(source)
annotations += source.annotations
parent = source.parent
// We need to ensure that this bridge has the same attribute owner as its static inline class replacement, since this
// is used in [CoroutineCodegen.isStaticInlineClassReplacementDelegatingCall] to identify the bridge and avoid generating
// a continuation class.
copyAttributes(source)
}
private fun createBridgeBody(source: IrSimpleFunction, target: IrSimpleFunction) {
source.body = context.createIrBuilder(source.symbol, source.startOffset, source.endOffset).run {
irExprBody(irCall(target).apply {
passTypeArgumentsFrom(source)
for ((parameter, newParameter) in source.explicitParameters.zip(target.explicitParameters)) {
putArgument(newParameter, irGet(parameter))
}
})
}
}
// Secondary constructors for boxed types get translated to static functions returning
// unboxed arguments. We remove the original constructor.
private fun transformConstructorFlat(constructor: IrConstructor, replacement: IrSimpleFunction): List {
replacement.valueParameters.forEach { it.transformChildrenVoid() }
replacement.body = context.createIrBuilder(replacement.symbol, replacement.startOffset, replacement.endOffset).irBlockBody(
replacement
) {
val thisVar = irTemporary(irType = replacement.returnType, nameHint = "\$this")
valueMap[constructor.constructedClass.thisReceiver!!.symbol] = thisVar
constructor.body?.statements?.forEach { statement ->
+statement
.transformStatement(object : IrElementTransformerVoid() {
// Don't recurse under nested class declarations
override fun visitClass(declaration: IrClass): IrStatement {
return declaration
}
// Capture the result of a delegating constructor call in a temporary variable "thisVar".
//
// Within the constructor we replace references to "this" with references to "thisVar".
// This is safe, since the delegating constructor call precedes all references to "this".
override fun visitDelegatingConstructorCall(expression: IrDelegatingConstructorCall): IrExpression {
expression.transformChildrenVoid()
return irSet(thisVar.symbol, expression)
}
// A constructor body has type unit and may contain explicit return statements.
// These early returns may have side-effects however, so we still have to evaluate
// the return expression. Afterwards we return "thisVar".
// For example, the following is a valid inline class declaration.
//
// inline class Foo(val x: String) {
// constructor(y: Int) : this(y.toString()) {
// if (y == 0) return throw java.lang.IllegalArgumentException()
// if (y == 1) return
// return Unit
// }
// }
override fun visitReturn(expression: IrReturn): IrExpression {
expression.transformChildrenVoid()
if (expression.returnTargetSymbol != constructor.symbol)
return expression
return irReturn(irBlock(expression.startOffset, expression.endOffset) {
+expression.value
+irGet(thisVar)
})
}
})
.transformStatement(this@JvmInlineClassLowering)
.patchDeclarationParents(replacement)
}
+irReturn(irGet(thisVar))
}
return listOf(replacement)
}
private fun typedArgumentList(function: IrFunction, expression: IrMemberAccessExpression<*>) =
listOfNotNull(
function.dispatchReceiverParameter?.let { it to expression.dispatchReceiver },
function.extensionReceiverParameter?.let { it to expression.extensionReceiver }
) + function.valueParameters.map { it to expression.getValueArgument(it.index) }
private fun IrMemberAccessExpression<*>.buildReplacement(
originalFunction: IrFunction,
original: IrMemberAccessExpression<*>,
replacement: IrSimpleFunction
) {
copyTypeArgumentsFrom(original)
val valueParameterMap = originalFunction.explicitParameters.zip(replacement.explicitParameters).toMap()
for ((parameter, argument) in typedArgumentList(originalFunction, original)) {
if (argument == null) continue
val newParameter = valueParameterMap.getValue(parameter)
putArgument(replacement, newParameter, argument.transform(this@JvmInlineClassLowering, null))
}
}
override fun visitFunctionReference(expression: IrFunctionReference): IrExpression {
if (expression.origin == InlineClassAbi.UNMANGLED_FUNCTION_REFERENCE)
return super.visitFunctionReference(expression)
val function = expression.symbol.owner
val replacement = context.inlineClassReplacements.getReplacementFunction(function)
?: return super.visitFunctionReference(expression)
return IrFunctionReferenceImpl(
expression.startOffset, expression.endOffset, expression.type,
replacement.symbol, replacement.typeParameters.size,
replacement.valueParameters.size, expression.reflectionTarget, expression.origin
).apply {
buildReplacement(function, expression, replacement)
}.copyAttributes(expression)
}
override fun visitFunctionAccess(expression: IrFunctionAccessExpression): IrExpression {
val function = expression.symbol.owner
val replacement = context.inlineClassReplacements.getReplacementFunction(function)
?: return super.visitFunctionAccess(expression)
return IrCallImpl(
expression.startOffset, expression.endOffset, function.returnType.substitute(expression.typeSubstitutionMap),
replacement.symbol, replacement.typeParameters.size, replacement.valueParameters.size,
expression.origin, (expression as? IrCall)?.superQualifierSymbol
).apply {
buildReplacement(function, expression, replacement)
}
}
private fun coerceInlineClasses(argument: IrExpression, from: IrType, to: IrType) =
IrCallImpl.fromSymbolOwner(UNDEFINED_OFFSET, UNDEFINED_OFFSET, to, context.ir.symbols.unsafeCoerceIntrinsic).apply {
putTypeArgument(0, from)
putTypeArgument(1, to)
putValueArgument(0, argument)
}
private fun IrExpression.coerceToUnboxed() =
coerceInlineClasses(this, this.type, this.type.unboxInlineClass())
// Precondition: left has an inline class type, but may not be unboxed
private fun IrBuilderWithScope.specializeEqualsCall(left: IrExpression, right: IrExpression): IrExpression? {
// There's already special handling for null-comparisons in the Equals intrinsic.
if (left.isNullConst() || right.isNullConst())
return null
// We don't specialize calls when both arguments are boxed.
val leftIsUnboxed = left.type.unboxInlineClass() != left.type
val rightIsUnboxed = right.type.unboxInlineClass() != right.type
if (!leftIsUnboxed && !rightIsUnboxed)
return null
// Precondition: left is an unboxed inline class type
fun equals(left: IrExpression, right: IrExpression): IrExpression {
// Unsigned types use primitive comparisons
if (left.type.isUnsigned() && right.type.isUnsigned() && rightIsUnboxed)
return irEquals(left.coerceToUnboxed(), right.coerceToUnboxed())
val klass = left.type.classOrNull!!.owner
val equalsMethod = if (rightIsUnboxed) {
this@JvmInlineClassLowering.context.inlineClassReplacements.getSpecializedEqualsMethod(klass, context.irBuiltIns)
} else {
val equals = klass.functions.single { it.name.asString() == "equals" && it.overriddenSymbols.isNotEmpty() }
this@JvmInlineClassLowering.context.inlineClassReplacements.getReplacementFunction(equals)!!
}
return irCall(equalsMethod).apply {
putValueArgument(0, left)
putValueArgument(1, right)
}
}
val leftNullCheck = left.type.isNullable()
val rightNullCheck = rightIsUnboxed && right.type.isNullable() // equals-impl has a nullable second argument
return if (leftNullCheck || rightNullCheck) {
irBlock {
val leftVal = if (left is IrGetValue) left.symbol.owner else irTemporary(left)
val rightVal = if (right is IrGetValue) right.symbol.owner else irTemporary(right)
val equalsCall = equals(
if (leftNullCheck) irImplicitCast(irGet(leftVal), left.type.makeNotNull()) else irGet(leftVal),
if (rightNullCheck) irImplicitCast(irGet(rightVal), right.type.makeNotNull()) else irGet(rightVal)
)
val equalsRight = if (rightNullCheck) {
irIfNull(context.irBuiltIns.booleanType, irGet(rightVal), irFalse(), equalsCall)
} else {
equalsCall
}
if (leftNullCheck) {
+irIfNull(context.irBuiltIns.booleanType, irGet(leftVal), irEqualsNull(irGet(rightVal)), equalsRight)
} else {
+equalsRight
}
}
} else {
equals(left, right)
}
}
override fun visitCall(expression: IrCall): IrExpression =
when {
// Getting the underlying field of an inline class merely changes the IR type,
// since the underlying representations are the same.
expression.symbol.owner.isInlineClassFieldGetter -> when (val ctor = findInitBlockOrConstructorImpl()) {
InitBlockOrConstructorImpl.InitBlock -> super.visitCall(expression)
is InitBlockOrConstructorImpl.ConstructorImpl -> getConstructorImplArgumentValue(ctor, expression.type)
else -> {
val arg = expression.dispatchReceiver!!.transform(this, null)
coerceInlineClasses(arg, expression.symbol.owner.dispatchReceiverParameter!!.type, expression.type)
}
}
// Specialize calls to equals when the left argument is a value of inline class type.
expression.isSpecializedInlineClassEqEq -> {
expression.transformChildrenVoid()
context.createIrBuilder(currentScope!!.scope.scopeOwnerSymbol, expression.startOffset, expression.endOffset)
.specializeEqualsCall(expression.getValueArgument(0)!!, expression.getValueArgument(1)!!)
?: expression
}
else ->
super.visitCall(expression)
}
private fun getConstructorImplArgumentValue(ctor: InitBlockOrConstructorImpl.ConstructorImpl, expectedType: IrType): IrExpression {
val arg: IrValueParameter = ctor.irElement.valueParameters.single()
return coerceInlineClasses(IrGetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, arg.symbol), arg.type, expectedType)
}
@Suppress("UNCHECKED_CAST")
private fun findInitBlockOrConstructorImpl(): InitBlockOrConstructorImpl? {
for (scope in allScopes.reversed()) {
val irElement = scope.irElement
when {
irElement is IrAnonymousInitializer -> return InitBlockOrConstructorImpl.InitBlock
irElement is IrFunction && irElement.origin == JvmLoweredDeclarationOrigin.STATIC_INLINE_CLASS_CONSTRUCTOR ->
return InitBlockOrConstructorImpl.ConstructorImpl(irElement)
}
}
return null
}
private val IrCall.isSpecializedInlineClassEqEq: Boolean
get() {
// Note that reference equality (x === y) is not allowed on values of inline class type,
// so it is enough to check for eqeq.
if (symbol != context.irBuiltIns.eqeqSymbol)
return false
val leftClass = getValueArgument(0)?.type?.classOrNull?.owner?.takeIf { it.isInline }
?: return false
// Before version 1.4, we cannot rely on the Result.equals-impl0 method
return (leftClass.fqNameWhenAvailable != StandardNames.RESULT_FQ_NAME) ||
context.state.languageVersionSettings.apiVersion >= ApiVersion.KOTLIN_1_4
}
override fun visitGetField(expression: IrGetField): IrExpression {
val field = expression.symbol.owner
val parent = field.parent
if (parent is IrClass && parent.isInline && field.name == parent.inlineClassFieldName) {
val receiver = expression.receiver!!.transform(this, null)
return coerceInlineClasses(receiver, receiver.type, field.type)
}
return super.visitGetField(expression)
}
override fun visitReturn(expression: IrReturn): IrExpression {
expression.returnTargetSymbol.owner.safeAs()?.let { target ->
context.inlineClassReplacements.getReplacementFunction(target)?.let {
return context.createIrBuilder(it.symbol, expression.startOffset, expression.endOffset).irReturn(
expression.value.transform(this, null)
)
}
}
return super.visitReturn(expression)
}
private fun visitStatementContainer(container: IrStatementContainer) {
container.statements.transformFlat { statement ->
if (statement is IrFunction)
transformFunctionFlat(statement)
else
listOf(statement.transformStatement(this))
}
}
override fun visitContainerExpression(expression: IrContainerExpression): IrExpression {
visitStatementContainer(expression)
return expression
}
override fun visitBlockBody(body: IrBlockBody): IrBody {
visitStatementContainer(body)
return body
}
override fun visitGetValue(expression: IrGetValue): IrExpression {
valueMap[expression.symbol]?.let {
return IrGetValueImpl(
expression.startOffset, expression.endOffset,
it.type, it.symbol, expression.origin
)
}
val owner = expression.symbol.owner
if (owner is IrValueParameter && (owner.parent as? IrClass)?.isInline == true && owner.origin == IrDeclarationOrigin.INSTANCE_RECEIVER) {
val ctor = findInitBlockOrConstructorImpl()
if (ctor is InitBlockOrConstructorImpl.ConstructorImpl) {
return getConstructorImplArgumentValue(ctor, expression.type)
}
}
return super.visitGetValue(expression)
}
override fun visitSetValue(expression: IrSetValue): IrExpression {
valueMap[expression.symbol]?.let {
return IrSetValueImpl(
expression.startOffset, expression.endOffset,
it.type, it.symbol,
expression.value.transform(this@JvmInlineClassLowering, null),
expression.origin
)
}
return super.visitSetValue(expression)
}
private fun buildPrimaryInlineClassConstructor(irClass: IrClass, irConstructor: IrConstructor) {
// Add the default primary constructor
irClass.addConstructor {
updateFrom(irConstructor)
visibility = DescriptorVisibilities.PRIVATE
origin = JvmLoweredDeclarationOrigin.SYNTHETIC_INLINE_CLASS_MEMBER
returnType = irConstructor.returnType
}.apply {
// Don't create a default argument stub for the primary constructor
irConstructor.valueParameters.forEach { it.defaultValue = null }
copyParameterDeclarationsFrom(irConstructor)
annotations += irConstructor.annotations
body = context.createIrBuilder(this.symbol).irBlockBody(this) {
+irDelegatingConstructorCall(context.irBuiltIns.anyClass.owner.constructors.single())
+irSetField(
irGet(irClass.thisReceiver!!),
getInlineClassBackingField(irClass),
irGet([email protected][0])
)
}
}
// Add a static bridge method to the primary constructor.
// This is a placeholder for null-checks and default arguments.
val function = context.inlineClassReplacements.getReplacementFunction(irConstructor)!!
val initBlocks = irClass.declarations.filterIsInstance()
function.valueParameters.forEach { it.transformChildrenVoid() }
with(context.createIrBuilder(function.symbol)) {
val argument: IrValueParameter = function.valueParameters[0]
function.body = irBlockBody {
for (initBlock in initBlocks) {
for (stmt in initBlock.body.statements) {
+stmt
}
}
+irReturn(coerceInlineClasses(irGet(argument), argument.type, function.returnType))
}
}
function.accept(this, null)
irClass.declarations.removeAll(initBlocks)
irClass.declarations += function
}
private fun buildBoxFunction(irClass: IrClass) {
val function = context.inlineClassReplacements.getBoxFunction(irClass)
with(context.createIrBuilder(function.symbol)) {
function.body = irExprBody(
irCall(irClass.primaryConstructor!!.symbol).apply {
passTypeArgumentsFrom(function)
putValueArgument(0, irGet(function.valueParameters[0]))
}
)
}
irClass.declarations += function
}
private fun buildUnboxFunction(irClass: IrClass) {
val function = context.inlineClassReplacements.getUnboxFunction(irClass)
val field = getInlineClassBackingField(irClass)
function.body = context.createIrBuilder(function.symbol).irBlockBody {
val thisVal = irGet(function.dispatchReceiverParameter!!)
+irReturn(irGetField(thisVal, field))
}
irClass.declarations += function
}
private fun buildSpecializedEqualsMethod(irClass: IrClass) {
val function = context.inlineClassReplacements.getSpecializedEqualsMethod(irClass, context.irBuiltIns)
val left = function.valueParameters[0]
val right = function.valueParameters[1]
val type = left.type.unboxInlineClass()
function.body = context.createIrBuilder(irClass.symbol).run {
irExprBody(
irEquals(
coerceInlineClasses(irGet(left), left.type, type),
coerceInlineClasses(irGet(right), right.type, type)
)
)
}
irClass.declarations += function
}
}
private sealed class InitBlockOrConstructorImpl {
object InitBlock : InitBlockOrConstructorImpl()
class ConstructorImpl(val irElement: IrFunction) : InitBlockOrConstructorImpl()
}