JVM IR: extract separate module backend.jvm.codegen

The main benefit is that now lowerings and codegen are compiled in
parallel, which speeds up the build. Hopefully, this will also improve
incremental compilation in case of implementation changes because the
rest of the compiler (cli & tests) has no "api" dependency on lowerings
and codegen.
This commit is contained in:
Alexander Udalov
2021-09-15 01:48:16 +02:00
parent 616cec8527
commit a95375cd9f
68 changed files with 26 additions and 4 deletions
@@ -0,0 +1,19 @@
plugins {
kotlin("jvm")
id("jps-compatible")
}
dependencies {
api(project(":compiler:ir.tree"))
api(project(":compiler:ir.backend.common"))
api(project(":compiler:backend.jvm"))
implementation(project(":compiler:ir.tree.impl"))
compileOnly(intellijCoreDep()) { includeJars("intellij-core", rootProject = rootProject) }
}
sourceSets {
"main" {
projectDefault()
}
"test" {}
}
@@ -0,0 +1,462 @@
/*
* Copyright 2010-2019 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.backend.jvm.codegen
import org.jetbrains.annotations.NotNull
import org.jetbrains.annotations.Nullable
import org.jetbrains.kotlin.backend.common.ir.ir2string
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
import org.jetbrains.kotlin.backend.jvm.JvmSymbols
import org.jetbrains.kotlin.backend.jvm.ir.*
import org.jetbrains.kotlin.backend.jvm.mapping.mapClass
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.codegen.TypeAnnotationCollector
import org.jetbrains.kotlin.codegen.TypePathInfo
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.config.JvmTarget.JVM_1_6
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.descriptors.annotations.KotlinRetention
import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.symbols.IrEnumEntrySymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.load.java.JvmAnnotationNames
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.types.TypeSystemCommonBackendContext
import org.jetbrains.kotlin.types.model.KotlinTypeMarker
import org.jetbrains.org.objectweb.asm.AnnotationVisitor
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.TypePath
import org.jetbrains.org.objectweb.asm.TypeReference
import java.lang.annotation.RetentionPolicy
abstract class AnnotationCodegen(
private val innerClassConsumer: InnerClassConsumer,
private val context: JvmBackendContext,
private val skipNullabilityAnnotations: Boolean = false
) {
private val typeMapper = context.typeMapper
private val methodSignatureMapper = context.methodSignatureMapper
/**
* @param returnType can be null if not applicable (e.g. [annotated] is a class)
*/
fun genAnnotations(
annotated: IrAnnotationContainer?,
returnType: Type?,
typeForTypeAnnotations: IrType?
) {
if (annotated == null) return
val annotationDescriptorsAlreadyPresent = mutableSetOf<String>()
val annotations = annotated.annotations
for (annotation in annotations) {
val applicableTargets = annotation.applicableTargetSet()
if (annotated is IrSimpleFunction &&
annotated.origin === IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA &&
KotlinTarget.FUNCTION !in applicableTargets &&
KotlinTarget.PROPERTY_GETTER !in applicableTargets &&
KotlinTarget.PROPERTY_SETTER !in applicableTargets
) {
assert(KotlinTarget.EXPRESSION in applicableTargets) {
"Inconsistent target list for lambda annotation: $applicableTargets on $annotated"
}
continue
}
if (annotated is IrClass &&
KotlinTarget.CLASS !in applicableTargets &&
KotlinTarget.ANNOTATION_CLASS !in applicableTargets
) {
if (annotated.visibility == DescriptorVisibilities.LOCAL) {
assert(KotlinTarget.EXPRESSION in applicableTargets) {
"Inconsistent target list for object literal annotation: $applicableTargets on $annotated"
}
continue
}
}
genAnnotation(annotation, null, false)?.let { descriptor ->
annotationDescriptorsAlreadyPresent.add(descriptor)
}
}
if (!skipNullabilityAnnotations && annotated is IrDeclaration && returnType != null && !AsmUtil.isPrimitive(returnType)) {
generateNullabilityAnnotationForCallable(annotated, annotationDescriptorsAlreadyPresent)
}
generateTypeAnnotations(annotated, typeForTypeAnnotations)
}
abstract fun visitAnnotation(descr: String, visible: Boolean): AnnotationVisitor
open fun visitTypeAnnotation(
descr: String,
path: TypePath?,
visible: Boolean,
): AnnotationVisitor {
throw RuntimeException("Not implemented")
}
private fun generateNullabilityAnnotationForCallable(
declaration: IrDeclaration, // There is no superclass that encompasses IrFunction, IrField and nothing else.
annotationDescriptorsAlreadyPresent: MutableSet<String>
) {
if (isInvisibleForNullabilityAnalysis(declaration)) return
if (declaration is IrValueParameter) {
val parent = declaration.parent as IrDeclaration
if (isInvisibleForNullabilityAnalysis(parent)) return
if (isMovedReceiverParameterOfStaticInlineClassReplacement(declaration, parent)) return
}
// No need to annotate annotation methods since they're always non-null
if (declaration is IrSimpleFunction && declaration.correspondingPropertySymbol != null &&
declaration.parentAsClass.isAnnotationClass
) {
return
}
val type = when (declaration) {
is IrFunction -> declaration.returnType
is IrValueDeclaration -> declaration.type
is IrField ->
if (declaration.correspondingPropertySymbol?.owner?.isLateinit == true) {
// Don't generate nullability annotations on lateinit fields
return
} else {
declaration.type
}
else -> return
}
if (isBareTypeParameterWithNullableUpperBound(type)) {
// This is to account for the case of, say
// class Function<R> { fun invoke(): R }
// it would be a shame to put @Nullable on the return type of the function, and force all callers to check for null,
// so we put no annotations
return
}
// A flexible type whose lower bound in not-null and upper bound is nullable, should not be annotated
if (type.isWithFlexibleNullability()) return
val annotationClass = if (type.isNullable()) Nullable::class.java else NotNull::class.java
generateAnnotationIfNotPresent(annotationDescriptorsAlreadyPresent, annotationClass)
}
private fun isMovedReceiverParameterOfStaticInlineClassReplacement(parameter: IrValueParameter, parent: IrDeclaration): Boolean =
parent.origin == JvmLoweredDeclarationOrigin.STATIC_INLINE_CLASS_REPLACEMENT &&
parameter.origin == IrDeclarationOrigin.MOVED_DISPATCH_RECEIVER
private fun generateAnnotationIfNotPresent(annotationDescriptorsAlreadyPresent: MutableSet<String>, annotationClass: Class<*>) {
val descriptor = Type.getType(annotationClass).descriptor
if (!annotationDescriptorsAlreadyPresent.contains(descriptor)) {
visitAnnotation(descriptor, false).visitEnd()
}
}
fun generateAnnotationDefaultValue(value: IrExpression) {
val visitor = visitAnnotation("", false) // Parameters are unimportant
genCompileTimeValue(null, value, visitor)
visitor.visitEnd()
}
private fun genAnnotation(annotation: IrConstructorCall, path: TypePath?, isTypeAnnotation: Boolean): String? {
val annotationClass = annotation.annotationClass
val retentionPolicy = getRetentionPolicy(annotationClass)
if (retentionPolicy == RetentionPolicy.SOURCE) return null
// FlexibleNullability is an internal annotation, used only inside the compiler
if (annotationClass.fqNameWhenAvailable in internalAnnotations) return null
// We do not generate annotations whose classes are optional (annotated with `@OptionalExpectation`) because if an annotation entry
// is resolved to the expected declaration, this means that annotation has no actual class, and thus should not be generated.
// (Otherwise we would've resolved the entry to the actual annotation class.)
if (annotationClass.isOptionalAnnotationClass) return null
innerClassConsumer.addInnerClassInfoFromAnnotation(annotationClass)
val asmTypeDescriptor = typeMapper.mapType(annotation.type).descriptor
val annotationVisitor =
if (!isTypeAnnotation) visitAnnotation(asmTypeDescriptor, retentionPolicy == RetentionPolicy.RUNTIME) else
visitTypeAnnotation(asmTypeDescriptor, path, retentionPolicy == RetentionPolicy.RUNTIME)
genAnnotationArguments(annotation, annotationVisitor)
annotationVisitor.visitEnd()
return asmTypeDescriptor
}
private fun genAnnotationArguments(annotation: IrConstructorCall, annotationVisitor: AnnotationVisitor) {
val annotationClass = annotation.annotationClass
for (param in annotation.symbol.owner.valueParameters) {
val value = annotation.getValueArgument(param.index)
if (value != null)
genCompileTimeValue(getAnnotationArgumentJvmName(annotationClass, param.name), value, annotationVisitor)
else if (param.defaultValue != null)
continue // Default value will be supplied by JVM at runtime.
else
error("No value for annotation parameter $param")
}
}
private fun getAnnotationArgumentJvmName(annotationClass: IrClass?, parameterName: Name): String {
if (annotationClass == null) return parameterName.asString()
val propertyOrGetter = annotationClass.declarations.singleOrNull {
// IrSimpleFunction if lowered, IrProperty with a getter if imported
(it is IrSimpleFunction && it.correspondingPropertySymbol?.owner?.name == parameterName) ||
(it is IrProperty && it.name == parameterName)
} ?: return parameterName.asString()
val getter = propertyOrGetter as? IrSimpleFunction
?: (propertyOrGetter as IrProperty).getter
?: error("No getter for annotation property: ${propertyOrGetter.render()}")
return methodSignatureMapper.mapFunctionName(getter)
}
private fun genCompileTimeValue(
name: String?,
value: IrExpression,
annotationVisitor: AnnotationVisitor
) {
when (value) {
is IrConst<*> -> annotationVisitor.visit(name, value.value)
is IrConstructorCall -> {
val callee = value.symbol.owner
when {
callee.parentAsClass.isAnnotationClass -> {
val annotationClassType = callee.returnType
val internalAnnName = typeMapper.mapType(annotationClassType).descriptor
val visitor = annotationVisitor.visitAnnotation(name, internalAnnName)
annotationClassType.classOrNull?.owner?.let(innerClassConsumer::addInnerClassInfoFromAnnotation)
genAnnotationArguments(value, visitor)
visitor.visitEnd()
}
else -> error("Not supported as annotation! ${ir2string(value)}")
}
}
is IrGetEnumValue -> {
val enumEntry = value.symbol.owner
val enumClass = enumEntry.parentAsClass
innerClassConsumer.addInnerClassInfoFromAnnotation(enumClass)
annotationVisitor.visitEnum(name, typeMapper.mapClass(enumClass).descriptor, enumEntry.name.asString())
}
is IrVararg -> { // array constructor
val visitor = annotationVisitor.visitArray(name)
for (element in value.elements) {
genCompileTimeValue(null, element as IrExpression, visitor)
}
visitor.visitEnd()
}
is IrClassReference -> {
val classType = value.classType
classType.classOrNull?.owner?.let(innerClassConsumer::addInnerClassInfoFromAnnotation)
val mappedType =
if (classType.isInlineClassType()) typeMapper.mapClass(classType.erasedUpperBound)
else typeMapper.mapType(classType)
annotationVisitor.visit(name, mappedType)
}
is IrErrorExpression -> error("Don't know how to compile annotation value ${ir2string(value)}")
else -> error("Unsupported compile-time value ${ir2string(value)}")
}
}
companion object {
fun genAnnotationsOnTypeParametersAndBounds(
context: JvmBackendContext,
typeParameterContainer: IrTypeParametersContainer,
classCodegen: ClassCodegen,
referenceType: Int,
boundType: Int,
visitor: (typeRef: Int, typePath: TypePath?, descriptor: String, visible: Boolean) -> AnnotationVisitor
) {
if (context.state.target != JVM_1_6) {
typeParameterContainer.typeParameters.forEachIndexed { index, typeParameter ->
object : AnnotationCodegen(classCodegen, context, true) {
override fun visitAnnotation(descr: String, visible: Boolean): AnnotationVisitor {
return visitor(
TypeReference.newTypeParameterReference(referenceType, index).value,
null,
descr,
visible
)
}
override fun visitTypeAnnotation(descr: String, path: TypePath?, visible: Boolean): AnnotationVisitor {
throw RuntimeException(
"Error during generation: type annotation shouldn't be presented on type parameter: " +
"${ir2string(typeParameter)} in ${ir2string(typeParameterContainer)}"
)
}
}.genAnnotations(typeParameter, null, null)
if (context.state.configuration.getBoolean(JVMConfigurationKeys.EMIT_JVM_TYPE_ANNOTATIONS)) {
var superInterfaceIndex = 1
typeParameter.superTypes.forEach { superType ->
val isClassOrTypeParameter = !superType.isInterface() && !superType.isAnnotation()
val superIndex = if (isClassOrTypeParameter) 0 else superInterfaceIndex++
object : AnnotationCodegen(classCodegen, context, true) {
override fun visitAnnotation(descr: String, visible: Boolean): AnnotationVisitor {
throw RuntimeException(
"Error during generation: only type annotations should be presented on type parameters bounds: " +
"${ir2string(typeParameter)} in ${ir2string(typeParameter)}"
)
}
override fun visitTypeAnnotation(descr: String, path: TypePath?, visible: Boolean): AnnotationVisitor {
return visitor(
TypeReference.newTypeParameterBoundReference(boundType, index, superIndex).value,
path,
descr,
visible
)
}
}.generateTypeAnnotations(typeParameterContainer, superType)
}
}
}
}
}
private fun isInvisibleForNullabilityAnalysis(declaration: IrDeclaration): Boolean =
when {
declaration.origin.isSynthetic ->
true
declaration.origin == JvmLoweredDeclarationOrigin.INLINE_CLASS_GENERATED_IMPL_METHOD ||
declaration.origin == IrDeclarationOrigin.GENERATED_SAM_IMPLEMENTATION ->
true
else ->
false
}
private val annotationRetentionMap = mapOf(
KotlinRetention.SOURCE to RetentionPolicy.SOURCE,
KotlinRetention.BINARY to RetentionPolicy.CLASS,
KotlinRetention.RUNTIME to RetentionPolicy.RUNTIME
)
internal val internalAnnotations = setOf(
JvmSymbols.FLEXIBLE_NULLABILITY_ANNOTATION_FQ_NAME,
JvmSymbols.FLEXIBLE_MUTABILITY_ANNOTATION_FQ_NAME,
JvmAnnotationNames.ENHANCED_NULLABILITY_ANNOTATION,
JvmSymbols.RAW_TYPE_ANNOTATION_FQ_NAME
)
private fun getRetentionPolicy(irClass: IrClass): RetentionPolicy {
val retention = irClass.getAnnotationRetention()
if (retention != null) {
@Suppress("MapGetWithNotNullAssertionOperator")
return annotationRetentionMap[retention]!!
}
irClass.getAnnotation(FqName(java.lang.annotation.Retention::class.java.name))?.let { retentionAnnotation ->
val value = retentionAnnotation.getValueArgument(0)
if (value is IrDeclarationReference) {
val symbol = value.symbol
if (symbol is IrEnumEntrySymbol) {
val entry = symbol.owner
val enumClassFqName = entry.parentAsClass.fqNameWhenAvailable
if (RetentionPolicy::class.java.name == enumClassFqName?.asString()) {
return RetentionPolicy.valueOf(entry.name.asString())
}
}
}
}
return RetentionPolicy.RUNTIME
}
/* Temporary? */
fun IrConstructorCall.applicableTargetSet() =
annotationClass.applicableTargetSet() ?: KotlinTarget.DEFAULT_TARGET_SET
val IrConstructorCall.annotationClass get() = symbol.owner.parentAsClass
}
internal fun generateTypeAnnotations(
annotated: IrAnnotationContainer,
type: IrType?
) {
if ((annotated as? IrDeclaration)?.origin == JvmLoweredDeclarationOrigin.SYNTHETIC_ACCESSOR ||
type == null || context.state.target === JVM_1_6 ||
!context.state.configuration.getBoolean(JVMConfigurationKeys.EMIT_JVM_TYPE_ANNOTATIONS)
) {
return
}
val infos: Iterable<TypePathInfo<IrConstructorCall>> =
IrTypeAnnotationCollector(context.typeMapper.typeSystem).collectTypeAnnotations(type)
for (info in infos) {
for (annotation in info.annotations) {
genAnnotation(annotation, info.path, true)
}
}
}
private class IrTypeAnnotationCollector(context: TypeSystemCommonBackendContext) : TypeAnnotationCollector<IrConstructorCall>(context) {
override fun KotlinTypeMarker.extractAnnotations(): List<IrConstructorCall> {
require(this is IrType)
return annotations.filter {
// We only generate annotations which have the TYPE_USE Java target.
// Those are type annotations which were compiled with JVM target bytecode version 1.8 or greater
(it.annotationClass.origin != IrDeclarationOrigin.IR_EXTERNAL_DECLARATION_STUB &&
it.annotationClass.origin != IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB) ||
it.annotationClass.isCompiledToJvm8OrHigher
}
}
private val IrClass.isCompiledToJvm8OrHigher: Boolean
get() =
(origin == IrDeclarationOrigin.IR_EXTERNAL_DECLARATION_STUB || origin == IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB) &&
isCompiledToJvm8OrHigher(source)
}
}
interface InnerClassConsumer {
fun addInnerClassInfoFromAnnotation(innerClass: IrClass)
}
private fun isBareTypeParameterWithNullableUpperBound(type: IrType): Boolean {
return type.classifierOrNull?.owner is IrTypeParameter && !type.isMarkedNullable() && type.isNullable()
}
// Copied and modified from AnnotationChecker.kt
private val TARGET_ALLOWED_TARGETS = Name.identifier("allowedTargets")
private fun IrClass.applicableTargetSet(): Set<KotlinTarget>? {
val targetEntry = getAnnotation(StandardNames.FqNames.target) ?: return null
return loadAnnotationTargets(targetEntry)
}
private fun loadAnnotationTargets(targetEntry: IrConstructorCall): Set<KotlinTarget>? {
val valueArgument = targetEntry.getValueArgument(TARGET_ALLOWED_TARGETS)
as? IrVararg ?: return null
return valueArgument.elements.filterIsInstance<IrGetEnumValue>().mapNotNull {
KotlinTarget.valueOrNull(it.symbol.owner.name.asString())
}.toSet()
}
@@ -0,0 +1,614 @@
/*
* 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.codegen
import org.jetbrains.kotlin.backend.common.lower.ANNOTATION_IMPLEMENTATION
import org.jetbrains.kotlin.backend.common.psi.PsiSourceManager
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
import org.jetbrains.kotlin.backend.jvm.MultifileFacadeFileEntry
import org.jetbrains.kotlin.backend.jvm.ir.*
import org.jetbrains.kotlin.backend.jvm.mapping.mapClass
import org.jetbrains.kotlin.backend.jvm.mapping.mapType
import org.jetbrains.kotlin.backend.jvm.metadata.MetadataSerializer
import org.jetbrains.kotlin.codegen.DescriptorAsmUtil
import org.jetbrains.kotlin.codegen.VersionIndependentOpcodes
import org.jetbrains.kotlin.codegen.addRecordComponent
import org.jetbrains.kotlin.codegen.inline.*
import org.jetbrains.kotlin.codegen.writeKotlinMetadata
import org.jetbrains.kotlin.config.JvmAnalysisFlags
import org.jetbrains.kotlin.config.JvmTarget
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.descriptors.DescriptorVisibility
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.builders.declarations.addFunction
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.descriptors.toIrBasedDescriptor
import org.jetbrains.kotlin.ir.expressions.IrBlockBody
import org.jetbrains.kotlin.ir.expressions.IrConst
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.impl.IrBlockBodyImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrSetFieldImpl
import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
import org.jetbrains.kotlin.ir.types.IrSimpleType
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.IrTypeProjection
import org.jetbrains.kotlin.ir.types.classifierOrFail
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.load.java.JvmAnnotationNames
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
import org.jetbrains.kotlin.metadata.jvm.deserialization.BitEncoding
import org.jetbrains.kotlin.name.JvmNames.JVM_RECORD_ANNOTATION_FQ_NAME
import org.jetbrains.kotlin.name.JvmNames.JVM_SYNTHETIC_ANNOTATION_FQ_NAME
import org.jetbrains.kotlin.name.JvmNames.TRANSIENT_ANNOTATION_FQ_NAME
import org.jetbrains.kotlin.name.JvmNames.VOLATILE_ANNOTATION_FQ_NAME
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.jvm.checkers.JvmSimpleNameBacktickChecker
import org.jetbrains.kotlin.resolve.jvm.diagnostics.*
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmClassSignature
import org.jetbrains.kotlin.utils.addToStdlib.cast
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import org.jetbrains.org.objectweb.asm.*
import org.jetbrains.org.objectweb.asm.commons.Method
import java.io.File
class ClassCodegen private constructor(
val irClass: IrClass,
val context: JvmBackendContext,
private val parentFunction: IrFunction?,
) : InnerClassConsumer {
// We need to avoid recursive calls to getOrCreate() from within the constructor to prevent lockups
// in ConcurrentHashMap context.classCodegens.
private val parentClassCodegen by lazy {
(parentFunction?.parentAsClass ?: irClass.parent as? IrClass)?.let { getOrCreate(it, context) }
}
private val withinInline: Boolean by lazy { parentClassCodegen?.withinInline == true || parentFunction?.isInline == true }
private val metadataSerializer: MetadataSerializer by lazy {
context.backendExtension.createSerializer(
context, irClass, type, visitor.serializationBindings, parentClassCodegen?.metadataSerializer
)
}
private val state get() = context.state
private val typeMapper get() = context.typeMapper
val type: Type = typeMapper.mapClass(irClass)
val reifiedTypeParametersUsages = ReifiedTypeParametersUsages()
private val jvmSignatureClashDetector = JvmSignatureClashDetector(irClass, type, context)
private val classOrigin = irClass.descriptorOrigin
private val visitor = state.factory.newVisitor(classOrigin, type, irClass.fileParent.loadSourceFilesInfo()).apply {
val signature = typeMapper.mapClassSignature(irClass, type)
// Ensure that the backend only produces class names that would be valid in the frontend for JVM.
if (context.state.classBuilderMode.generateBodies && signature.hasInvalidName()) {
throw IllegalStateException("Generating class with invalid name '${type.className}': ${irClass.dump()}")
}
defineClass(
irClass.psiElement,
state.classFileVersion,
irClass.getFlags(context.state.languageVersionSettings),
signature.name,
signature.javaGenericSignature,
signature.superclassName,
signature.interfaces.toTypedArray()
)
}
// TODO: the order of entries in this set depends on the order in which methods are generated; this means it is unstable
// under incremental compilation, as calls to `inline fun`s declared in this class cause them to be generated out of order.
private val innerClasses = linkedSetOf<IrClass>()
// TODO: the names produced by generators in this map depend on the order in which methods are generated; see above.
private val regeneratedObjectNameGenerators = mutableMapOf<String, NameGenerator>()
fun getRegeneratedObjectNameGenerator(function: IrFunction): NameGenerator {
val name = if (function.name.isSpecial) "special" else function.name.asString()
return regeneratedObjectNameGenerators.getOrPut(name) {
NameGenerator("${type.internalName}\$$name\$\$inlined")
}
}
private var generated = false
fun generate() {
// TODO: reject repeated generate() calls; currently, these can happen for objects in finally
// blocks since they are `accept`ed once per each CFG edge out of the try-finally.
if (generated) return
generated = true
// Generate PermittedSubclasses attribute for sealed class.
if (state.languageVersionSettings.supportsFeature(LanguageFeature.JvmPermittedSubclassesAttributeForSealed) &&
irClass.modality == Modality.SEALED &&
state.target >= JvmTarget.JVM_17
) {
generatePermittedSubclasses()
}
// Generating a method node may cause the addition of a field with an initializer if an inline function
// call uses `assert` and the JVM assertions mode is enabled. To avoid concurrent modification errors,
// there is a very specific generation order.
val smap = context.getSourceMapper(irClass)
// 1. Any method other than `<clinit>` can add a field and a `<clinit>` statement:
for (method in irClass.declarations.filterIsInstance<IrFunction>()) {
if (method.name.asString() != "<clinit>" &&
method.origin != JvmLoweredDeclarationOrigin.INLINE_LAMBDA &&
method.origin != IrDeclarationOrigin.ADAPTER_FOR_FUN_INTERFACE_CONSTRUCTOR
) {
generateMethod(method, smap)
}
}
// 2. `<clinit>` itself can add a field, but the statement is generated via the `return init` hack:
irClass.functions.find { it.name.asString() == "<clinit>" }?.let { generateMethod(it, smap) }
// 3. Now we have all the fields, including `$assertionsDisabled` if needed:
for (field in irClass.fields) {
generateField(field)
}
// 4. Generate nested classes at the end, to ensure that when the companion's metadata is serialized
// everything moved to the outer class has already been recorded in `globalSerializationBindings`.
for (declaration in irClass.declarations) {
if (declaration is IrClass) {
getOrCreate(declaration, context).generate()
}
}
object : AnnotationCodegen(this@ClassCodegen, context) {
override fun visitAnnotation(descr: String, visible: Boolean): AnnotationVisitor {
return visitor.visitor.visitAnnotation(descr, visible)
}
}.genAnnotations(irClass, null, null)
AnnotationCodegen.genAnnotationsOnTypeParametersAndBounds(
context,
irClass,
this,
TypeReference.CLASS_TYPE_PARAMETER,
TypeReference.CLASS_TYPE_PARAMETER_BOUND
) { typeRef: Int, typePath: TypePath?, descriptor: String, visible: Boolean ->
visitor.visitor.visitTypeAnnotation(typeRef, typePath, descriptor, visible)
}
generateKotlinMetadataAnnotation()
generateInnerAndOuterClasses()
if (withinInline || !smap.isTrivial) {
visitor.visitSMAP(smap, !context.state.languageVersionSettings.supportsFeature(LanguageFeature.CorrectSourceMappingSyntax))
} else {
smap.sourceInfo!!.sourceFileName?.let {
visitor.visitSource(it, null)
}
}
addReifiedParametersFromSignature()
visitor.done()
jvmSignatureClashDetector.reportErrors(classOrigin)
}
private fun generatePermittedSubclasses() {
val sealedSubclasses = irClass.sealedSubclasses
if (sealedSubclasses.isEmpty()) return
val classVisitor = visitor.visitor
for (sealedSubclassSymbol in sealedSubclasses) {
classVisitor.visitPermittedSubclass(typeMapper.mapClass(sealedSubclassSymbol.owner).internalName)
}
}
private fun addReifiedParametersFromSignature() {
for (type in irClass.superTypes) {
processTypeParameters(type)
}
}
private fun processTypeParameters(type: IrType) {
for (supertypeArgument in (type as? IrSimpleType)?.arguments ?: emptyList()) {
if (supertypeArgument is IrTypeProjection) {
val typeArgument = supertypeArgument.type
if (typeArgument.isReifiedTypeParameter) {
reifiedTypeParametersUsages.addUsedReifiedParameter(typeArgument.classifierOrFail.cast<IrTypeParameterSymbol>().owner.name.asString())
} else {
processTypeParameters(typeArgument)
}
}
}
}
fun generateAssertFieldIfNeeded(generatingClInit: Boolean): IrExpression? {
if (irClass.hasAssertionsDisabledField(context))
return null
val topLevelClass = generateSequence(this) { it.parentClassCodegen }.last().irClass
val field = irClass.buildAssertionsDisabledField(context, topLevelClass)
irClass.declarations.add(0, field)
// Normally, `InitializersLowering` would move the initializer to <clinit>, but it's obviously too late for that.
val init = with(field) {
IrSetFieldImpl(startOffset, endOffset, symbol, null, initializer!!.expression, context.irBuiltIns.unitType)
}
if (generatingClInit) {
// Too late to modify the IR; have to ask the currently active `ExpressionCodegen` to generate this statement
// directly. At least we know that nothing before this point uses the field.
return init
}
val classInitializer = irClass.functions.singleOrNull { it.name.asString() == "<clinit>" } ?: irClass.addFunction {
name = Name.special("<clinit>")
returnType = context.irBuiltIns.unitType
}.apply {
body = IrBlockBodyImpl(startOffset, endOffset)
}
// Should be initialized first in case some inline function call in `<clinit>` also uses assertions.
(classInitializer.body as IrBlockBody).statements.add(0, init)
return null
}
private fun generateKotlinMetadataAnnotation() {
val facadeClassName = context.multifileFacadeForPart[irClass.attributeOwnerId]
val metadata = irClass.metadata
val entry = irClass.fileParent.fileEntry
val kind = when {
facadeClassName != null -> KotlinClassHeader.Kind.MULTIFILE_CLASS_PART
metadata is MetadataSource.Class -> KotlinClassHeader.Kind.CLASS
metadata is MetadataSource.Script -> KotlinClassHeader.Kind.CLASS
metadata is MetadataSource.File -> KotlinClassHeader.Kind.FILE_FACADE
metadata is MetadataSource.Function -> KotlinClassHeader.Kind.SYNTHETIC_CLASS
entry is MultifileFacadeFileEntry -> KotlinClassHeader.Kind.MULTIFILE_CLASS
else -> KotlinClassHeader.Kind.SYNTHETIC_CLASS
}
val serializedIr = when (metadata) {
is MetadataSource.Class -> metadata.serializedIr
is MetadataSource.File -> metadata.serializedIr
else -> null
}
val isMultifileClassOrPart = kind == KotlinClassHeader.Kind.MULTIFILE_CLASS || kind == KotlinClassHeader.Kind.MULTIFILE_CLASS_PART
var extraFlags = context.backendExtension.generateMetadataExtraFlags(state.abiStability)
if (isMultifileClassOrPart && state.languageVersionSettings.getFlag(JvmAnalysisFlags.inheritMultifileParts)) {
extraFlags = extraFlags or JvmAnnotationNames.METADATA_MULTIFILE_PARTS_INHERIT_FLAG
}
if (metadata is MetadataSource.Script) {
extraFlags = extraFlags or JvmAnnotationNames.METADATA_SCRIPT_FLAG
}
// There are four kinds of classes which are regenerated during inlining.
// 1) Anonymous classes which are in the scope of an inline function.
// 2) SAM wrappers used in an inline function. These are identified by name, since they
// can be reused in different functions and are thus generated in the enclosing top-level
// class instead of inside of an inline function.
// 3) WhenMapping classes used from public inline functions. These are collected in
// `JvmBackendContext.publicAbiSymbols` in `MappedEnumWhenLowering`.
// 4) Annotation implementation classes used from public inline function. Similar to
// public WhenMapping classes, these are collected in `publicAbiSymbols` in
// `JvmAnnotationImplementationTransformer`.
val isPublicAbi = irClass.symbol in context.publicAbiSymbols || irClass.isInlineSamWrapper ||
type.isAnonymousClass && irClass.isInPublicInlineScope
writeKotlinMetadata(visitor, state, kind, isPublicAbi, extraFlags) { av ->
if (metadata != null) {
metadataSerializer.serialize(metadata)?.let { (proto, stringTable) ->
DescriptorAsmUtil.writeAnnotationData(av, proto, stringTable)
}
}
if (entry is MultifileFacadeFileEntry) {
val arv = av.visitArray(JvmAnnotationNames.METADATA_DATA_FIELD_NAME)
for (partFile in entry.partFiles) {
val fileClass = partFile.declarations.singleOrNull { it.isFileClass } as IrClass?
if (fileClass != null) arv.visit(null, typeMapper.mapClass(fileClass).internalName)
}
arv.visitEnd()
}
if (facadeClassName != null) {
av.visit(JvmAnnotationNames.METADATA_MULTIFILE_CLASS_NAME_FIELD_NAME, facadeClassName.internalName)
}
if (irClass in context.classNameOverride) {
val isFileClass = isMultifileClassOrPart || kind == KotlinClassHeader.Kind.FILE_FACADE
assert(isFileClass) { "JvmPackageName is not supported for classes: ${irClass.render()}" }
av.visit(JvmAnnotationNames.METADATA_PACKAGE_NAME_FIELD_NAME, irClass.fqNameWhenAvailable!!.parent().asString())
}
}
serializedIr?.let { storeSerializedIr(it) }
}
private fun IrFile.loadSourceFilesInfo(): List<File> {
val entry = fileEntry
if (entry is MultifileFacadeFileEntry) {
return entry.partFiles.flatMap { it.loadSourceFilesInfo() }
}
return listOf(File(entry.name))
}
private fun generateField(field: IrField) {
val fieldType = typeMapper.mapType(field)
val fieldSignature =
if (field.origin == IrDeclarationOrigin.PROPERTY_DELEGATE) null
else context.methodSignatureMapper.mapFieldSignature(field)
val fieldName = field.name.asString()
val flags = field.computeFieldFlags(context, state.languageVersionSettings)
val fv = visitor.newField(
field.descriptorOrigin, flags, fieldName, fieldType.descriptor,
fieldSignature, (field.initializer?.expression as? IrConst<*>)?.value
)
jvmSignatureClashDetector.trackField(field, RawSignature(fieldName, fieldType.descriptor, MemberKind.FIELD))
if (field.origin != JvmLoweredDeclarationOrigin.CONTINUATION_CLASS_RESULT_FIELD) {
val skipNullabilityAnnotations =
flags and (Opcodes.ACC_SYNTHETIC or Opcodes.ACC_ENUM) != 0 ||
(field.origin == IrDeclarationOrigin.FIELD_FOR_OBJECT_INSTANCE && irClass.isSyntheticSingleton)
object : AnnotationCodegen(this@ClassCodegen, context, skipNullabilityAnnotations) {
override fun visitAnnotation(descr: String, visible: Boolean): AnnotationVisitor {
return fv.visitAnnotation(descr, visible)
}
override fun visitTypeAnnotation(descr: String, path: TypePath?, visible: Boolean): AnnotationVisitor {
return fv.visitTypeAnnotation(TypeReference.newTypeReference(TypeReference.FIELD).value, path, descr, visible)
}
}.genAnnotations(field, fieldType, field.type)
}
(field.metadata as? MetadataSource.Property)?.let {
metadataSerializer.bindFieldMetadata(it, fieldType to fieldName)
}
if (irClass.hasAnnotation(JVM_RECORD_ANNOTATION_FQ_NAME) && !field.isStatic) {
// TODO: Write annotations to the component
visitor.addRecordComponent(fieldName, fieldType.descriptor, fieldSignature)
}
}
private val generatedInlineMethods = mutableMapOf<IrFunction, SMAPAndMethodNode>()
fun generateMethodNode(method: IrFunction): SMAPAndMethodNode {
if (!method.isInline && !method.isSuspendCapturingCrossinline()) {
// Inline methods can be used multiple times by `IrSourceCompilerForInline`, suspend methods
// are used twice (`f` and `f$$forInline`) if they capture crossinline lambdas, and everything
// else is only generated by `generateMethod` below so does not need caching.
// TODO: inline lambdas are not marked `isInline`, and are generally used once, but may be needed
// multiple times if declared in a `finally` block - should they be cached?
return FunctionCodegen(method, this).generate()
}
// Only allow generation of one inline method at a time, to avoid deadlocks when files call inline methods of each other.
val (node, smap) =
generatedInlineMethods[method] ?: synchronized(context.inlineMethodGenerationLock) {
generatedInlineMethods.getOrPut(method) { FunctionCodegen(method, this).generate() }
}
return SMAPAndMethodNode(cloneMethodNode(node), smap)
}
private fun generateMethod(method: IrFunction, classSMAP: SourceMapper) {
if (method.isFakeOverride) {
jvmSignatureClashDetector.trackFakeOverrideMethod(method)
return
}
val (node, smap) = generateMethodNode(method)
node.preprocessSuspendMarkers(
method.origin == JvmLoweredDeclarationOrigin.FOR_INLINE_STATE_MACHINE_TEMPLATE || method.isEffectivelyInlineOnly(),
method.origin == JvmLoweredDeclarationOrigin.FOR_INLINE_STATE_MACHINE_TEMPLATE_CAPTURES_CROSSINLINE
)
val mv = with(node) { visitor.newMethod(method.descriptorOrigin, access, name, desc, signature, exceptions.toTypedArray()) }
val smapCopier = SourceMapCopier(classSMAP, smap)
val smapCopyingVisitor = object : MethodVisitor(Opcodes.API_VERSION, mv) {
override fun visitLineNumber(line: Int, start: Label) =
super.visitLineNumber(smapCopier.mapLineNumber(line), start)
}
if (method.hasContinuation()) {
// Generate a state machine within this method. The continuation class for it should be generated
// lazily so that if tail call optimization kicks in, the unused class will not be written to the output.
val continuationClass = method.continuationClass() // null if `SuspendLambda.invokeSuspend` - `this` is continuation itself
val continuationClassCodegen = lazy { if (continuationClass != null) getOrCreate(continuationClass, context, method) else this }
// For suspend lambdas continuation class is null, and we need to use containing class to put L$ fields
val attributeContainer = continuationClass?.attributeOwnerId ?: irClass.attributeOwnerId
node.acceptWithStateMachine(
method,
this,
smapCopyingVisitor,
context.continuationClassesVarsCountByType[attributeContainer] ?: emptyMap()
) {
continuationClassCodegen.value.visitor
}
if (continuationClass != null && (continuationClassCodegen.isInitialized() || method.isSuspendCapturingCrossinline())) {
continuationClassCodegen.value.generate()
}
} else {
node.accept(smapCopyingVisitor)
}
jvmSignatureClashDetector.trackMethod(method, RawSignature(node.name, node.desc, MemberKind.METHOD))
when (val metadata = method.metadata) {
is MetadataSource.Property -> metadataSerializer.bindPropertyMetadata(metadata, Method(node.name, node.desc), method.origin)
is MetadataSource.Function -> metadataSerializer.bindMethodMetadata(metadata, Method(node.name, node.desc))
null -> Unit
else -> error("Incorrect metadata source $metadata for:\n${method.dump()}")
}
}
private fun generateInnerAndOuterClasses() {
// JVMS7 (4.7.6): a nested class or interface member will have InnerClasses information
// for each enclosing class and for each immediate member
parentClassCodegen?.innerClasses?.add(irClass)
for (codegen in generateSequence(this) { it.parentClassCodegen }.takeWhile { it.parentClassCodegen != null }) {
innerClasses.add(codegen.irClass)
}
// JVMS7 (4.7.7): A class must have an EnclosingMethod attribute if and only if
// it is a local class or an anonymous class.
//
// The attribute contains the innermost class that encloses the declaration of
// the current class. If the current class is immediately enclosed by a method
// or constructor, the name and type of the function is recorded as well.
if (parentClassCodegen != null) {
// In case there's no primary constructor, it's unclear which constructor should be the enclosing one, so we select the first.
val enclosingFunction = if (irClass.attributeOwnerId in context.isEnclosedInConstructor) {
val containerClass = parentClassCodegen!!.irClass
containerClass.primaryConstructor
?: containerClass.declarations.firstIsInstanceOrNull<IrConstructor>()
?: error("Class in a non-static initializer found, but container has no constructors: ${containerClass.render()}")
} else parentFunction
if (enclosingFunction != null || irClass.isAnonymousInnerClass) {
val method = enclosingFunction?.let(context.methodSignatureMapper::mapAsmMethod)?.takeIf { it.name != "<clinit>" }
visitor.visitOuterClass(parentClassCodegen!!.type.internalName, method?.name, method?.descriptor)
}
}
for (klass in innerClasses) {
val innerClass = typeMapper.classInternalName(klass)
val outerClass =
if (klass.isSamWrapper || klass.isAnnotationImplementation || klass.attributeOwnerId in context.isEnclosedInConstructor)
null
else {
when (val parent = klass.parent) {
is IrClass -> typeMapper.classInternalName(parent)
else -> null
}
}
val innerName = if (klass.isAnonymousInnerClass) null else klass.name.asString()
val accessFlags = klass.calculateInnerClassAccessFlags(context)
visitor.visitInnerClass(innerClass, outerClass, innerName, accessFlags)
}
}
private val IrClass.isAnonymousInnerClass: Boolean
get() = isSamWrapper || name.isSpecial || isAnnotationImplementation // NB '<Continuation>' is treated as anonymous inner class here
private val IrClass.isInlineSamWrapper: Boolean
get() = isSamWrapper && visibility == DescriptorVisibilities.PUBLIC
private val IrClass.isSamWrapper: Boolean
get() = origin == IrDeclarationOrigin.GENERATED_SAM_IMPLEMENTATION
private val IrClass.isAnnotationImplementation: Boolean
get() = origin == ANNOTATION_IMPLEMENTATION
override fun addInnerClassInfoFromAnnotation(innerClass: IrClass) {
// It's necessary for proper recovering of classId by plain string JVM descriptor when loading annotations
// See FileBasedKotlinClass.convertAnnotationVisitor
generateSequence<IrDeclaration>(innerClass) { it.parent as? IrDeclaration }.takeWhile { !it.isTopLevelDeclaration }.forEach {
if (it is IrClass) {
innerClasses.add(it)
}
}
}
private val IrDeclaration.descriptorOrigin: JvmDeclarationOrigin
get() {
val psiElement = PsiSourceManager.findPsiElement(this)
return if (origin == IrDeclarationOrigin.FILE_CLASS)
JvmDeclarationOrigin(JvmDeclarationOriginKind.PACKAGE_PART, psiElement, toIrBasedDescriptor())
else
OtherOrigin(psiElement, toIrBasedDescriptor())
}
private fun storeSerializedIr(serializedIr: ByteArray) {
val av = visitor.newAnnotation(JvmAnnotationNames.SERIALIZED_IR_DESC, true)
val partsVisitor = av.visitArray(JvmAnnotationNames.SERIALIZED_IR_BYTES_FIELD_NAME)
val serializedIrParts = BitEncoding.encodeBytes(serializedIr)
for (part in serializedIrParts) {
partsVisitor.visit(null, part)
}
partsVisitor.visitEnd()
av.visitEnd()
}
companion object {
fun getOrCreate(
irClass: IrClass,
context: JvmBackendContext,
// The `parentFunction` is only set for classes nested inside of functions. This is usually safe, since there is no
// way to refer to (inline) members of such a class from outside of the function unless the function in question is
// itself declared as inline. In that case, the function will be compiled before we can refer to the nested class.
//
// The one exception to this rule are anonymous objects defined as members of a class. These are nested inside of the
// class initializer, but can be referred to from anywhere within the scope of the class. That's why we have to ensure
// that all references to classes inside of <clinit> have a non-null `parentFunction`.
parentFunction: IrFunction? = irClass.parent.safeAs<IrFunction>()?.takeIf {
it.origin == JvmLoweredDeclarationOrigin.CLASS_STATIC_INITIALIZER
},
): ClassCodegen =
context.getOrCreateClassCodegen(irClass) { ClassCodegen(irClass, context, parentFunction) }.also {
assert(parentFunction == null || it.parentFunction == parentFunction) {
"inconsistent parent function for ${irClass.render()}:\n" +
"New: ${parentFunction!!.render()}\n" +
"Old: ${it.parentFunction?.render()}"
}
}
private fun JvmClassSignature.hasInvalidName() =
name.splitToSequence('/').any { identifier -> identifier.any { it in JvmSimpleNameBacktickChecker.INVALID_CHARS } }
}
}
private fun IrClass.getFlags(languageVersionSettings: LanguageVersionSettings): Int =
origin.flags or
getVisibilityAccessFlagForClass() or
(if (isAnnotatedWithDeprecated) Opcodes.ACC_DEPRECATED else 0) or
getSynthAccessFlag(languageVersionSettings) or
when {
isAnnotationClass -> Opcodes.ACC_ANNOTATION or Opcodes.ACC_INTERFACE or Opcodes.ACC_ABSTRACT
isInterface -> Opcodes.ACC_INTERFACE or Opcodes.ACC_ABSTRACT
isEnumClass -> Opcodes.ACC_ENUM or Opcodes.ACC_SUPER or modality.flags
hasAnnotation(JVM_RECORD_ANNOTATION_FQ_NAME) -> VersionIndependentOpcodes.ACC_RECORD or Opcodes.ACC_SUPER or modality.flags
else -> Opcodes.ACC_SUPER or modality.flags
}
private fun IrClass.getSynthAccessFlag(languageVersionSettings: LanguageVersionSettings): Int {
if (hasAnnotation(JVM_SYNTHETIC_ANNOTATION_FQ_NAME))
return Opcodes.ACC_SYNTHETIC
if (origin == IrDeclarationOrigin.GENERATED_SAM_IMPLEMENTATION &&
languageVersionSettings.supportsFeature(LanguageFeature.SamWrapperClassesAreSynthetic)
)
return Opcodes.ACC_SYNTHETIC
return 0
}
private fun IrField.computeFieldFlags(context: JvmBackendContext, languageVersionSettings: LanguageVersionSettings): Int =
origin.flags or visibility.flags or
(if (isDeprecatedCallable(context) ||
correspondingPropertySymbol?.owner?.isDeprecatedCallable(context) == true
) Opcodes.ACC_DEPRECATED else 0) or
(if (isFinal) Opcodes.ACC_FINAL else 0) or
(if (isStatic) Opcodes.ACC_STATIC else 0) or
(if (hasAnnotation(VOLATILE_ANNOTATION_FQ_NAME)) Opcodes.ACC_VOLATILE else 0) or
(if (hasAnnotation(TRANSIENT_ANNOTATION_FQ_NAME)) Opcodes.ACC_TRANSIENT else 0) or
(if (hasAnnotation(JVM_SYNTHETIC_ANNOTATION_FQ_NAME) ||
isPrivateCompanionFieldInInterface(languageVersionSettings)
) Opcodes.ACC_SYNTHETIC else 0)
private fun IrField.isPrivateCompanionFieldInInterface(languageVersionSettings: LanguageVersionSettings): Boolean =
origin == IrDeclarationOrigin.FIELD_FOR_OBJECT_INSTANCE &&
languageVersionSettings.supportsFeature(LanguageFeature.ProperVisibilityForCompanionObjectInstanceField) &&
parentAsClass.isJvmInterface &&
DescriptorVisibilities.isPrivate(parentAsClass.companionObject()!!.visibility)
private val IrDeclarationOrigin.flags: Int
get() = (if (isSynthetic) Opcodes.ACC_SYNTHETIC else 0) or
(if (this == IrDeclarationOrigin.FIELD_FOR_ENUM_ENTRY) Opcodes.ACC_ENUM else 0)
private val Modality.flags: Int
get() = when (this) {
Modality.ABSTRACT, Modality.SEALED -> Opcodes.ACC_ABSTRACT
Modality.FINAL -> Opcodes.ACC_FINAL
Modality.OPEN -> 0
else -> throw AssertionError("Unsupported modality $this")
}
private val DescriptorVisibility.flags: Int
get() = DescriptorAsmUtil.getVisibilityAccessFlag(this) ?: throw AssertionError("Unsupported visibility $this")
// From `isAnonymousClass` in inlineCodegenUtils.kt
private val Type.isAnonymousClass: Boolean
get() = internalName.substringAfterLast("$", "").toIntOrNull() != null
@@ -0,0 +1,142 @@
/*
* 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.codegen
import org.jetbrains.kotlin.backend.common.CodegenUtil
import org.jetbrains.kotlin.backend.common.ir.allOverridden
import org.jetbrains.kotlin.backend.jvm.InlineClassAbi
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
import org.jetbrains.kotlin.backend.jvm.ir.*
import org.jetbrains.kotlin.backend.jvm.unboxInlineClass
import org.jetbrains.kotlin.codegen.ClassBuilder
import org.jetbrains.kotlin.codegen.coroutines.CoroutineTransformerMethodVisitor
import org.jetbrains.kotlin.codegen.coroutines.reportSuspensionPointInsideMonitor
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrBlockBody
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrGetValue
import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.org.objectweb.asm.MethodVisitor
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.tree.MethodNode
internal fun MethodNode.acceptWithStateMachine(
irFunction: IrFunction,
classCodegen: ClassCodegen,
methodVisitor: MethodVisitor,
varsCountByType: Map<Type, Int>,
obtainContinuationClassBuilder: () -> ClassBuilder,
) {
val state = classCodegen.context.state
val languageVersionSettings = state.languageVersionSettings
assert(languageVersionSettings.supportsFeature(LanguageFeature.ReleaseCoroutines)) { "Experimental coroutines are unsupported in JVM_IR backend" }
val element = if (irFunction.isSuspend)
irFunction.psiElement ?: classCodegen.irClass.psiElement
else
classCodegen.context.suspendLambdaToOriginalFunctionMap[classCodegen.irClass.attributeOwnerId]!!.psiElement
val lineNumber = if (irFunction.isSuspend) {
val irFile = irFunction.file
if (irFunction.startOffset >= 0) {
// if it suspend function like `suspend fun foo(...)`
irFile.fileEntry.getLineNumber(irFunction.startOffset) + 1
} else {
val klass = classCodegen.irClass
if (klass.startOffset >= 0) {
// if it suspend lambda transformed into class `runSuspend { .... }`
irFile.fileEntry.getLineNumber(klass.startOffset) + 1
} else 0
}
} else element?.let { CodegenUtil.getLineNumberForElement(it, false) } ?: 0
val visitor = CoroutineTransformerMethodVisitor(
methodVisitor, access, name, desc, signature, exceptions.toTypedArray(),
containingClassInternalName = classCodegen.type.internalName,
obtainClassBuilderForCoroutineState = obtainContinuationClassBuilder,
isForNamedFunction = irFunction.isSuspend,
disableTailCallOptimizationForFunctionReturningUnit = irFunction.isSuspend && irFunction.suspendFunctionOriginal().let {
it.returnType.isUnit() && it.anyOfOverriddenFunctionsReturnsNonUnit()
},
reportSuspensionPointInsideMonitor = { reportSuspensionPointInsideMonitor(element as KtElement, state, it) },
lineNumber = lineNumber,
sourceFile = classCodegen.irClass.file.name, // SuspendLambda.invokeSuspend is not suspend
needDispatchReceiver = irFunction.isSuspend && (irFunction.dispatchReceiverParameter != null
|| irFunction.origin == JvmLoweredDeclarationOrigin.SUSPEND_IMPL_STATIC_FUNCTION),
internalNameForDispatchReceiver = classCodegen.type.internalName,
putContinuationParameterToLvt = false,
initialVarsCountByType = varsCountByType,
)
accept(visitor)
}
private fun IrFunction.anyOfOverriddenFunctionsReturnsNonUnit(): Boolean =
this is IrSimpleFunction && allOverridden().any { !it.returnType.isUnit() }
internal fun IrFunction.suspendForInlineToOriginal(): IrSimpleFunction? {
if (origin != JvmLoweredDeclarationOrigin.FOR_INLINE_STATE_MACHINE_TEMPLATE &&
origin != JvmLoweredDeclarationOrigin.FOR_INLINE_STATE_MACHINE_TEMPLATE_CAPTURES_CROSSINLINE
) return null
return parentAsClass.declarations.find {
// The function may not be named `it.name.asString() + FOR_INLINE_SUFFIX` due to name mangling,
// e.g., for internal declarations. We check for a function with the same `attributeOwnerId` instead.
// This is copied in `AddContinuationLowering`.
it is IrSimpleFunction && it.attributeOwnerId == (this as IrSimpleFunction).attributeOwnerId
} as IrSimpleFunction?
}
internal fun IrFunction.isSuspendCapturingCrossinline(): Boolean =
this is IrSimpleFunction && hasContinuation() && parentAsClass.declarations.any {
it is IrSimpleFunction && it.attributeOwnerId == attributeOwnerId &&
it.origin == JvmLoweredDeclarationOrigin.FOR_INLINE_STATE_MACHINE_TEMPLATE_CAPTURES_CROSSINLINE
}
internal fun IrFunction.continuationClass(): IrClass? =
(body as? IrBlockBody)?.statements?.find { it is IrClass && it.origin == JvmLoweredDeclarationOrigin.CONTINUATION_CLASS }
as IrClass?
internal fun IrExpression?.isReadOfInlineLambda(): Boolean = isReadOfCrossinline() ||
(this is IrGetValue && origin == IrStatementOrigin.VARIABLE_AS_FUNCTION && (symbol.owner as? IrValueParameter)?.isNoinline == false)
internal fun IrFunction.originalReturnTypeOfSuspendFunctionReturningUnboxedInlineClass(): IrType? {
if (this !is IrSimpleFunction || !isSuspend) return null
// Unlike `suspendFunctionOriginal()`, this also maps `$default` stubs to the original function.
val original = attributeOwnerId as IrSimpleFunction
val unboxedReturnType = InlineClassAbi.unboxType(original.returnType) ?: return null
// 1. Can't unbox into a primitive, since suspend functions have to return a reference type.
// 2. Force boxing if the function overrides function with different type modulo nullability ignoring type parameters
if (unboxedReturnType.isPrimitiveType() || original.overridesReturningDifferentType(original.returnType)) return null
return original.returnType
}
private fun IrSimpleFunction.overridesReturningDifferentType(returnType: IrType): Boolean {
val visited = hashSetOf<IrSimpleFunction>()
fun dfs(function: IrSimpleFunction): Boolean {
if (!visited.add(function)) return false
for (overridden in function.overriddenSymbols) {
val owner = overridden.owner
val overriddenReturnType = owner.returnType
if (!overriddenReturnType.erasedUpperBound.isInline) return true
if (overriddenReturnType.isNullable() &&
overriddenReturnType.makeNotNull().unboxInlineClass().isNullable()
) return true
if (overriddenReturnType.classOrNull != returnType.classOrNull) return true
if (dfs(owner)) return true
}
return false
}
return dfs(this)
}
@@ -0,0 +1,359 @@
/*
* 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.codegen
import org.jetbrains.kotlin.backend.common.ir.allOverridden
import org.jetbrains.kotlin.backend.common.ir.ir2string
import org.jetbrains.kotlin.backend.common.lower.BOUND_RECEIVER_PARAMETER
import org.jetbrains.kotlin.backend.common.lower.BOUND_VALUE_PARAMETER
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
import org.jetbrains.kotlin.backend.jvm.ir.*
import org.jetbrains.kotlin.backend.jvm.mapping.mapType
import org.jetbrains.kotlin.backend.jvm.mapping.mapTypeAsDeclaration
import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.codegen.inline.*
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.codegen.visitAnnotableParameterCount
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.descriptors.toIrBasedDescriptor
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.load.java.JavaDescriptorVisibilities
import org.jetbrains.kotlin.name.JvmNames.JVM_SYNTHETIC_ANNOTATION_FQ_NAME
import org.jetbrains.kotlin.name.JvmNames.STRICTFP_ANNOTATION_FQ_NAME
import org.jetbrains.kotlin.name.JvmNames.SYNCHRONIZED_ANNOTATION_FQ_NAME
import org.jetbrains.kotlin.resolve.annotations.JVM_THROWS_ANNOTATION_FQ_NAME
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodParameterKind
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import org.jetbrains.org.objectweb.asm.*
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
import org.jetbrains.org.objectweb.asm.tree.MethodNode
class FunctionCodegen(private val irFunction: IrFunction, private val classCodegen: ClassCodegen) {
private val context = classCodegen.context
fun generate(
inlinedInto: ExpressionCodegen? = null,
reifiedTypeParameters: ReifiedTypeParametersUsages = classCodegen.reifiedTypeParametersUsages
): SMAPAndMethodNode =
try {
doGenerate(inlinedInto, reifiedTypeParameters)
} catch (e: Throwable) {
throw RuntimeException("Exception while generating code for:\n${irFunction.dump()}", e)
}
private fun doGenerate(inlinedInto: ExpressionCodegen?, reifiedTypeParameters: ReifiedTypeParametersUsages): SMAPAndMethodNode {
val signature = context.methodSignatureMapper.mapSignatureWithGeneric(irFunction)
val flags = irFunction.calculateMethodFlags()
val isSynthetic = flags.and(Opcodes.ACC_SYNTHETIC) != 0
val methodNode = MethodNode(
Opcodes.API_VERSION,
flags,
signature.asmMethod.name,
signature.asmMethod.descriptor,
signature.genericsSignature
.takeIf {
(irFunction.isInline && irFunction.origin != IrDeclarationOrigin.FUNCTION_FOR_DEFAULT_PARAMETER) ||
!isSynthetic && irFunction.origin != IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA
},
getThrownExceptions(irFunction)?.toTypedArray()
)
val methodVisitor: MethodVisitor = wrapWithMaxLocalCalc(methodNode)
if (context.state.generateParametersMetadata && !isSynthetic) {
generateParameterNames(irFunction, methodVisitor, context.state)
}
if (irFunction.origin !in methodOriginsWithoutAnnotations) {
val skipNullabilityAnnotations = flags and Opcodes.ACC_PRIVATE != 0 || flags and Opcodes.ACC_SYNTHETIC != 0
object : AnnotationCodegen(classCodegen, context, skipNullabilityAnnotations) {
override fun visitAnnotation(descr: String, visible: Boolean): AnnotationVisitor {
return methodVisitor.visitAnnotation(descr, visible)
}
override fun visitTypeAnnotation(descr: String, path: TypePath?, visible: Boolean): AnnotationVisitor {
return methodVisitor.visitTypeAnnotation(
TypeReference.newTypeReference(TypeReference.METHOD_RETURN).value, path, descr, visible
)
}
}.genAnnotations(irFunction, signature.asmMethod.returnType, irFunction.returnType)
AnnotationCodegen.genAnnotationsOnTypeParametersAndBounds(
context,
irFunction,
classCodegen,
TypeReference.METHOD_TYPE_PARAMETER,
TypeReference.METHOD_TYPE_PARAMETER_BOUND
) { typeRef: Int, typePath: TypePath?, descriptor: String, visible: Boolean ->
methodVisitor.visitTypeAnnotation(typeRef, typePath, descriptor, visible)
}
if (shouldGenerateAnnotationsOnValueParameters()) {
generateParameterAnnotations(irFunction, methodVisitor, signature, classCodegen, context, skipNullabilityAnnotations)
}
}
// `$$forInline` versions of suspend functions have the same bodies as the originals, but with different
// name/flags/annotations and with no state machine.
val notForInline = irFunction.suspendForInlineToOriginal()
val smap = if (!context.state.classBuilderMode.generateBodies || flags.and(Opcodes.ACC_ABSTRACT) != 0 || irFunction.isExternal) {
generateAnnotationDefaultValueIfNeeded(methodVisitor)
SMAP(listOf())
} else if (notForInline != null) {
val (originalNode, smap) = classCodegen.generateMethodNode(notForInline)
originalNode.accept(MethodBodyVisitor(methodVisitor))
smap
} else {
val sourceMapper = context.getSourceMapper(classCodegen.irClass)
val frameMap = irFunction.createFrameMapWithReceivers()
context.state.globalInlineContext.enterDeclaration(irFunction.suspendFunctionOriginal().toIrBasedDescriptor())
try {
val adapter = InstructionAdapter(methodVisitor)
ExpressionCodegen(
irFunction, signature, frameMap, adapter, classCodegen, inlinedInto, sourceMapper, reifiedTypeParameters
).generate()
} finally {
context.state.globalInlineContext.exitDeclaration()
}
methodVisitor.visitMaxs(-1, -1)
SMAP(sourceMapper.resultMappings)
}
methodVisitor.visitEnd()
return SMAPAndMethodNode(methodNode, smap)
}
private fun shouldGenerateAnnotationsOnValueParameters(): Boolean =
when {
irFunction.origin == JvmLoweredDeclarationOrigin.SYNTHETIC_METHOD_FOR_PROPERTY_OR_TYPEALIAS_ANNOTATIONS ->
false
irFunction is IrConstructor && irFunction.parentAsClass.shouldNotGenerateConstructorParameterAnnotations() ->
false
else ->
true
}
// Since the only arguments to anonymous object constructors are captured variables and complex
// super constructor arguments, there shouldn't be any annotations on them other than @NonNull,
// and those are meaningless on synthetic parameters. (Also, the inliner cannot handle them and
// will throw an exception if we generate any.)
// The same applies for continuations.
private fun IrClass.shouldNotGenerateConstructorParameterAnnotations() =
isAnonymousObject || origin == JvmLoweredDeclarationOrigin.CONTINUATION_CLASS || origin == JvmLoweredDeclarationOrigin.SUSPEND_LAMBDA
private fun IrFunction.getVisibilityForDefaultArgumentStub(): Int =
when {
// TODO: maybe best to generate private default in interface as private
visibility == DescriptorVisibilities.PUBLIC || parentAsClass.isJvmInterface -> Opcodes.ACC_PUBLIC
visibility == JavaDescriptorVisibilities.PACKAGE_VISIBILITY -> AsmUtil.NO_FLAG_PACKAGE_PRIVATE
else -> throw IllegalStateException("Default argument stub should be either public or package private: ${ir2string(this)}")
}
private fun IrFunction.calculateMethodFlags(): Int {
if (origin == IrDeclarationOrigin.FUNCTION_FOR_DEFAULT_PARAMETER) {
return getVisibilityForDefaultArgumentStub() or Opcodes.ACC_SYNTHETIC or
(if (isDeprecatedFunction(context)) Opcodes.ACC_DEPRECATED else 0) or
(if (this is IrConstructor) 0 else Opcodes.ACC_STATIC)
}
val isVararg = valueParameters.lastOrNull()?.varargElementType != null && !isBridge()
val modalityFlag =
if (parentAsClass.isAnnotationClass) {
if (isStatic) 0 else Opcodes.ACC_ABSTRACT
} else {
when ((this as? IrSimpleFunction)?.modality) {
Modality.FINAL -> when {
origin == JvmLoweredDeclarationOrigin.CLASS_STATIC_INITIALIZER -> 0
origin == IrDeclarationOrigin.ENUM_CLASS_SPECIAL_MEMBER -> 0
parentAsClass.isInterface && body != null -> 0
else -> Opcodes.ACC_FINAL
}
Modality.ABSTRACT -> Opcodes.ACC_ABSTRACT
// TODO transform interface modality on lowering to DefaultImpls
else -> if (parentAsClass.isJvmInterface && body == null) Opcodes.ACC_ABSTRACT else 0
}
}
val isSynthetic = origin.isSynthetic ||
hasAnnotation(JVM_SYNTHETIC_ANNOTATION_FQ_NAME) ||
isReifiable() ||
isDeprecatedHidden()
val isStrict = hasAnnotation(STRICTFP_ANNOTATION_FQ_NAME) && origin != JvmLoweredDeclarationOrigin.JVM_OVERLOADS_WRAPPER
val isSynchronized = hasAnnotation(SYNCHRONIZED_ANNOTATION_FQ_NAME) && origin != JvmLoweredDeclarationOrigin.JVM_OVERLOADS_WRAPPER
return getVisibilityAccessFlag() or modalityFlag or
(if (isDeprecatedFunction(context)) Opcodes.ACC_DEPRECATED else 0) or
(if (isStatic) Opcodes.ACC_STATIC else 0) or
(if (isVararg) Opcodes.ACC_VARARGS else 0) or
(if (isExternal) Opcodes.ACC_NATIVE else 0) or
(if (isBridge()) Opcodes.ACC_BRIDGE else 0) or
(if (isSynthetic) Opcodes.ACC_SYNTHETIC else 0) or
(if (isStrict) Opcodes.ACC_STRICT else 0) or
(if (isSynchronized) Opcodes.ACC_SYNCHRONIZED else 0)
}
private fun IrFunction.isDeprecatedHidden(): Boolean {
val mightBeDeprecated = if (this is IrSimpleFunction) {
allOverridden(true).any {
it.isAnnotatedWithDeprecated || it.correspondingPropertySymbol?.owner?.isAnnotatedWithDeprecated == true
}
} else {
isAnnotatedWithDeprecated
}
return mightBeDeprecated && context.state.deprecationProvider.isDeprecatedHidden(toIrBasedDescriptor())
}
private fun getThrownExceptions(function: IrFunction): List<String>? {
if (context.state.languageVersionSettings.supportsFeature(LanguageFeature.DoNotGenerateThrowsForDelegatedKotlinMembers) &&
function.origin == IrDeclarationOrigin.DELEGATED_MEMBER
) return null
// @Throws(vararg exceptionClasses: KClass<out Throwable>)
val exceptionClasses = function.getAnnotation(JVM_THROWS_ANNOTATION_FQ_NAME)?.getValueArgument(0) ?: return null
return (exceptionClasses as IrVararg).elements.map { exceptionClass ->
context.typeMapper.mapType((exceptionClass as IrClassReference).classType).internalName
}
}
private fun generateAnnotationDefaultValueIfNeeded(methodVisitor: MethodVisitor) {
getAnnotationDefaultValueExpression()?.let { defaultValueExpression ->
val annotationCodegen = object : AnnotationCodegen(classCodegen, context) {
override fun visitAnnotation(descr: String, visible: Boolean): AnnotationVisitor {
return methodVisitor.visitAnnotationDefault()
}
}
annotationCodegen.generateAnnotationDefaultValue(defaultValueExpression)
}
}
private fun getAnnotationDefaultValueExpression(): IrExpression? {
if (!classCodegen.irClass.isAnnotationClass) return null
// TODO: any simpler way to get to the value expression?
// Are there other valid IR structures that represent the default value?
return irFunction.safeAs<IrSimpleFunction>()
?.correspondingPropertySymbol?.owner
?.backingField
?.initializer.safeAs<IrExpressionBody>()
?.expression?.safeAs<IrGetValue>()
?.symbol?.owner?.safeAs<IrValueParameter>()
?.defaultValue?.safeAs<IrExpressionBody>()
?.expression
}
private fun IrFunction.createFrameMapWithReceivers(): IrFrameMap {
val frameMap = IrFrameMap()
val receiver = if (this is IrConstructor) parentAsClass.thisReceiver else dispatchReceiverParameter
receiver?.let {
frameMap.enter(it, context.typeMapper.mapTypeAsDeclaration(it.type))
}
val contextReceivers = valueParameters.subList(0, contextReceiverParametersCount)
for (contextReceiver in contextReceivers) {
frameMap.enter(contextReceiver, context.typeMapper.mapType(contextReceiver.type))
}
extensionReceiverParameter?.let {
frameMap.enter(it, context.typeMapper.mapType(it))
}
val regularParameters = valueParameters.subList(contextReceiverParametersCount, valueParameters.size)
for (parameter in regularParameters) {
frameMap.enter(parameter, context.typeMapper.mapType(parameter.type))
}
return frameMap
}
// Borrowed from org.jetbrains.kotlin.codegen.FunctionCodegen.java
private fun generateParameterAnnotations(
irFunction: IrFunction,
mv: MethodVisitor,
jvmSignature: JvmMethodSignature,
innerClassConsumer: InnerClassConsumer,
context: JvmBackendContext,
skipNullabilityAnnotations: Boolean = false
) {
val iterator = irFunction.valueParameters.iterator()
val kotlinParameterTypes = jvmSignature.valueParameters
val syntheticParameterCount = kotlinParameterTypes.count { it.kind.isSkippedInGenericSignature }
visitAnnotableParameterCount(mv, kotlinParameterTypes.size - syntheticParameterCount)
kotlinParameterTypes.forEachIndexed { i, parameterSignature ->
val kind = parameterSignature.kind
val annotated = when (kind) {
JvmMethodParameterKind.RECEIVER -> irFunction.extensionReceiverParameter
else -> iterator.next()
}
if (annotated != null && !kind.isSkippedInGenericSignature && !annotated.isSyntheticMarkerParameter()) {
object : AnnotationCodegen(innerClassConsumer, context, skipNullabilityAnnotations) {
override fun visitAnnotation(descr: String, visible: Boolean): AnnotationVisitor {
return mv.visitParameterAnnotation(
i - syntheticParameterCount,
descr,
visible
)
}
override fun visitTypeAnnotation(descr: String, path: TypePath?, visible: Boolean): AnnotationVisitor {
return mv.visitTypeAnnotation(
TypeReference.newFormalParameterReference(i - syntheticParameterCount).value,
path, descr, visible
)
}
}.genAnnotations(annotated, parameterSignature.asmType, annotated.type)
}
}
}
companion object {
internal val methodOriginsWithoutAnnotations =
setOf(
// Not generating parameter annotations for default stubs fixes KT-7892, though
// this certainly looks like a workaround for a javac bug.
IrDeclarationOrigin.FUNCTION_FOR_DEFAULT_PARAMETER,
JvmLoweredDeclarationOrigin.SYNTHETIC_ACCESSOR,
IrDeclarationOrigin.ENUM_CLASS_SPECIAL_MEMBER,
IrDeclarationOrigin.GENERATED_INLINE_CLASS_MEMBER,
IrDeclarationOrigin.BRIDGE,
IrDeclarationOrigin.BRIDGE_SPECIAL,
JvmLoweredDeclarationOrigin.ABSTRACT_BRIDGE_STUB,
JvmLoweredDeclarationOrigin.TO_ARRAY,
IrDeclarationOrigin.IR_BUILTINS_STUB,
IrDeclarationOrigin.PROPERTY_DELEGATE,
)
}
}
private fun IrValueParameter.isSyntheticMarkerParameter(): Boolean =
origin == IrDeclarationOrigin.DEFAULT_CONSTRUCTOR_MARKER ||
origin == JvmLoweredDeclarationOrigin.SYNTHETIC_MARKER_PARAMETER
private fun generateParameterNames(irFunction: IrFunction, mv: MethodVisitor, state: GenerationState) {
irFunction.extensionReceiverParameter?.let {
mv.visitParameter(irFunction.extensionReceiverName(state), Opcodes.ACC_MANDATED)
}
for (irParameter in irFunction.valueParameters) {
// A construct emitted by a Java compiler must be marked as synthetic if it does not correspond to a construct declared
// explicitly or implicitly in source code, unless the emitted construct is a class initialization method (JVMS §2.9).
// A construct emitted by a Java compiler must be marked as mandated if it corresponds to a formal parameter
// declared implicitly in source code (§8.8.1, §8.8.9, §8.9.3, §15.9.5.1).
val access = when {
irParameter.origin == JvmLoweredDeclarationOrigin.FIELD_FOR_OUTER_THIS -> Opcodes.ACC_MANDATED
// TODO mark these backend-common origins as synthetic? (note: ExpressionCodegen is still expected
// to generate LVT entries for them)
irParameter.origin == IrDeclarationOrigin.MOVED_EXTENSION_RECEIVER -> Opcodes.ACC_MANDATED
irParameter.origin == IrDeclarationOrigin.MOVED_DISPATCH_RECEIVER -> Opcodes.ACC_SYNTHETIC
irParameter.origin == BOUND_VALUE_PARAMETER -> Opcodes.ACC_SYNTHETIC
irParameter.origin == BOUND_RECEIVER_PARAMETER -> Opcodes.ACC_SYNTHETIC
irParameter.origin.isSynthetic -> Opcodes.ACC_SYNTHETIC
else -> 0
}
mv.visitParameter(irParameter.name.asString(), access)
}
}
@@ -0,0 +1,54 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.backend.jvm.codegen
import org.jetbrains.kotlin.backend.jvm.mapping.IrCallableMethod
import org.jetbrains.kotlin.ir.declarations.IrValueParameter
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.org.objectweb.asm.Type
interface IrCallGenerator {
fun genCall(
callableMethod: IrCallableMethod,
codegen: ExpressionCodegen,
expression: IrFunctionAccessExpression,
isInsideIfCondition: Boolean,
) {
with(callableMethod) {
codegen.mv.visitMethodInsn(invokeOpcode, owner.internalName, asmMethod.name, asmMethod.descriptor, isInterfaceMethod)
}
}
fun beforeCallStart() {}
fun beforeValueParametersStart() {}
fun afterCallEnd() {}
fun genValueAndPut(
irValueParameter: IrValueParameter,
argumentExpression: IrExpression,
parameterType: Type,
codegen: ExpressionCodegen,
blockInfo: BlockInfo
) {
with(codegen) { gen(argumentExpression, parameterType, irValueParameter.realType, blockInfo) }
}
object DefaultCallGenerator : IrCallGenerator
}
@@ -0,0 +1,47 @@
/*
* 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.codegen
import org.jetbrains.kotlin.backend.common.psi.PsiSourceManager
import org.jetbrains.kotlin.backend.jvm.ir.psiElement
import org.jetbrains.kotlin.backend.jvm.ir.suspendFunctionOriginal
import org.jetbrains.kotlin.backend.jvm.mapping.IrCallableMethod
import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.ir.descriptors.toIrBasedDescriptor
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
interface IrInlineCallGenerator : IrCallGenerator {
override fun genCall(
callableMethod: IrCallableMethod,
codegen: ExpressionCodegen,
expression: IrFunctionAccessExpression,
isInsideIfCondition: Boolean,
) {
val element = PsiSourceManager.findPsiElement(expression, codegen.irFunction)
?: PsiSourceManager.findPsiElement(codegen.irFunction)
val descriptor = expression.symbol.owner.suspendFunctionOriginal().toIrBasedDescriptor()
if (!codegen.state.globalInlineContext.enterIntoInlining(descriptor, element)) {
genCycleStub(expression.psiElement?.text ?: "<no source>", codegen)
return
}
try {
genInlineCall(callableMethod, codegen, expression, isInsideIfCondition)
} finally {
codegen.state.globalInlineContext.exitFromInlining()
}
}
fun genInlineCall(
callableMethod: IrCallableMethod,
codegen: ExpressionCodegen,
expression: IrFunctionAccessExpression,
isInsideIfCondition: Boolean,
)
fun genCycleStub(text: String, codegen: ExpressionCodegen) {
AsmUtil.genThrow(codegen.visitor, "java/lang/UnsupportedOperationException", "Call is a part of inline call cycle: $text")
}
}
@@ -0,0 +1,217 @@
/*
* 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.codegen
import org.jetbrains.kotlin.backend.jvm.ir.isInlineOnly
import org.jetbrains.kotlin.backend.jvm.ir.isInlineParameter
import org.jetbrains.kotlin.backend.jvm.ir.unwrapInlineLambda
import org.jetbrains.kotlin.backend.jvm.mapping.IrCallableMethod
import org.jetbrains.kotlin.codegen.IrExpressionLambda
import org.jetbrains.kotlin.codegen.JvmKotlinType
import org.jetbrains.kotlin.codegen.StackValue
import org.jetbrains.kotlin.codegen.ValueKind
import org.jetbrains.kotlin.codegen.inline.*
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.IrValueParameter
import org.jetbrains.kotlin.ir.descriptors.toIrBasedKotlinType
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.util.dump
import org.jetbrains.kotlin.ir.util.explicitParameters
import org.jetbrains.kotlin.ir.util.getArgumentsWithIr
import org.jetbrains.kotlin.ir.util.isSuspendFunction
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.Method
class IrInlineCodegen(
codegen: ExpressionCodegen,
state: GenerationState,
private val function: IrFunction,
signature: JvmMethodSignature,
typeParameterMappings: TypeParameterMappings<IrType>,
sourceCompiler: SourceCompilerForInline,
reifiedTypeInliner: ReifiedTypeInliner<IrType>
) :
InlineCodegen<ExpressionCodegen>(codegen, state, signature, typeParameterMappings, sourceCompiler, reifiedTypeInliner),
IrInlineCallGenerator {
private val inlineArgumentsInPlace = canInlineArgumentsInPlace()
private fun canInlineArgumentsInPlace(): Boolean {
if (!function.isInlineOnly())
return false
var actualParametersCount = function.valueParameters.size
if (function.dispatchReceiverParameter != null)
++actualParametersCount
if (function.extensionReceiverParameter != null)
++actualParametersCount
if (actualParametersCount == 0)
return false
if (function.valueParameters.any { it.isInlineParameter() })
return false
return canInlineArgumentsInPlace(sourceCompiler.compileInlineFunction(jvmSignature).node)
}
override fun beforeCallStart() {
if (inlineArgumentsInPlace) {
codegen.visitor.addInplaceCallStartMarker()
}
}
override fun afterCallEnd() {
if (inlineArgumentsInPlace) {
codegen.visitor.addInplaceCallEndMarker()
}
}
override fun generateAssertField() {
// May be inlining code into `<clinit>`, in which case it's too late to modify the IR and
// `generateAssertFieldIfNeeded` will return a statement for which we need to emit bytecode.
val isClInit = sourceCompiler.inlineCallSiteInfo.method.name == "<clinit>"
codegen.classCodegen.generateAssertFieldIfNeeded(isClInit)?.accept(codegen, BlockInfo())?.discard()
}
override fun genValueAndPut(
irValueParameter: IrValueParameter,
argumentExpression: IrExpression,
parameterType: Type,
codegen: ExpressionCodegen,
blockInfo: BlockInfo
) {
val inlineLambda = argumentExpression.unwrapInlineLambda()
if (inlineLambda != null) {
val lambdaInfo = IrExpressionLambdaImpl(codegen, inlineLambda)
rememberClosure(parameterType, irValueParameter.index, lambdaInfo)
lambdaInfo.generateLambdaBody(sourceCompiler)
lambdaInfo.reference.getArgumentsWithIr().forEachIndexed { index, (_, ir) ->
val param = lambdaInfo.capturedVars[index]
val onStack = codegen.genOrGetLocal(ir, param.type, ir.type, BlockInfo())
putCapturedToLocalVal(onStack, param, ir.type.toIrBasedKotlinType())
}
} else {
val isInlineParameter = irValueParameter.isInlineParameter()
val kind = when {
irValueParameter.origin == IrDeclarationOrigin.MASK_FOR_DEFAULT_FUNCTION ->
ValueKind.DEFAULT_MASK
irValueParameter.origin == IrDeclarationOrigin.METHOD_HANDLER_IN_DEFAULT_FUNCTION ->
ValueKind.METHOD_HANDLE_IN_DEFAULT
argumentExpression is IrContainerExpression && argumentExpression.origin == IrStatementOrigin.DEFAULT_VALUE ->
if (isInlineParameter)
ValueKind.DEFAULT_INLINE_PARAMETER
else
ValueKind.DEFAULT_PARAMETER
isInlineParameter && irValueParameter.type.isSuspendFunction() ->
if (argumentExpression.isReadOfInlineLambda())
ValueKind.READ_OF_INLINE_LAMBDA_FOR_INLINE_SUSPEND_PARAMETER
else
ValueKind.READ_OF_OBJECT_FOR_INLINE_SUSPEND_PARAMETER
else ->
ValueKind.GENERAL
}
val onStack = when (kind) {
ValueKind.METHOD_HANDLE_IN_DEFAULT ->
StackValue.constant(null, AsmTypes.OBJECT_TYPE)
ValueKind.DEFAULT_MASK ->
StackValue.constant((argumentExpression as IrConst<*>).value, Type.INT_TYPE)
ValueKind.DEFAULT_PARAMETER, ValueKind.DEFAULT_INLINE_PARAMETER ->
StackValue.createDefaultValue(parameterType)
else -> {
if (inlineArgumentsInPlace) {
codegen.visitor.addInplaceArgumentStartMarker()
}
// Here we replicate the old backend: reusing the locals for everything except extension receivers.
// TODO when stopping at a breakpoint placed in an inline function, arguments which reuse an existing
// local will not be visible in the debugger, so this needs to be reconsidered.
val argValue = if (irValueParameter.index >= 0)
codegen.genOrGetLocal(argumentExpression, parameterType, irValueParameter.type, blockInfo)
else
codegen.gen(argumentExpression, parameterType, irValueParameter.type, blockInfo)
if (inlineArgumentsInPlace) {
codegen.visitor.addInplaceArgumentEndMarker()
}
argValue
}
}
val expectedType = JvmKotlinType(parameterType, irValueParameter.type.toIrBasedKotlinType())
putArgumentToLocalVal(expectedType, onStack, irValueParameter.index, kind)
}
}
override fun beforeValueParametersStart() {
invocationParamBuilder.markValueParametersStart()
}
override fun genInlineCall(
callableMethod: IrCallableMethod,
codegen: ExpressionCodegen,
expression: IrFunctionAccessExpression,
isInsideIfCondition: Boolean,
) {
performInline(isInsideIfCondition, function.isInlineOnly())
}
override fun genCycleStub(text: String, codegen: ExpressionCodegen) {
generateStub(text, codegen)
}
}
class IrExpressionLambdaImpl(
codegen: ExpressionCodegen,
val reference: IrFunctionReference,
) : ExpressionLambda(), IrExpressionLambda {
override val isExtensionLambda: Boolean = function.extensionReceiverParameter != null && reference.extensionReceiver == null
val function: IrFunction
get() = reference.symbol.owner
override val hasDispatchReceiver: Boolean
get() = false
// This name doesn't actually matter: it is used internally to tell this lambda's captured
// arguments apart from any other scope's. So long as it's unique, any value is fine.
// This particular string slightly aids in debugging internal compiler errors as it at least
// points towards the function containing the lambda.
override val lambdaClassType: Type = codegen.context.getLocalClassType(reference)
?: throw AssertionError("callable reference ${reference.dump()} has no name in context")
override val capturedVars: List<CapturedParamDesc>
override val invokeMethod: Method
override val invokeMethodParameters: List<KotlinType?>
override val invokeMethodReturnType: KotlinType
init {
val asmMethod = codegen.methodSignatureMapper.mapAsmMethod(function)
val capturedParameters = reference.getArgumentsWithIr()
val captureStart = if (isExtensionLambda) 1 else 0 // extension receiver comes before captures
val captureEnd = captureStart + capturedParameters.size
capturedVars = capturedParameters.mapIndexed { index, (parameter, _) ->
val isSuspend = parameter.isInlineParameter() && parameter.type.isSuspendFunction()
capturedParamDesc(parameter.name.asString(), asmMethod.argumentTypes[captureStart + index], isSuspend)
}
// The parameter list should include the continuation if this is a suspend lambda. In the IR backend,
// the lambda is suspend iff the inline function's parameter is marked suspend, so FunctionN.invoke call
// inside the inline function already has a (real) continuation value as the last argument.
val freeParameters = function.explicitParameters.let { it.take(captureStart) + it.drop(captureEnd) }
val freeAsmParameters = asmMethod.argumentTypes.let { it.take(captureStart) + it.drop(captureEnd) }
// The return type, on the other hand, should be the original type if this is a suspend lambda that returns
// an unboxed inline class value so that the inliner will box it (FunctionN.invoke should return a boxed value).
val unboxedReturnType = function.originalReturnTypeOfSuspendFunctionReturningUnboxedInlineClass()
val unboxedAsmReturnType = unboxedReturnType?.let(codegen.typeMapper::mapType)
invokeMethod = Method(asmMethod.name, unboxedAsmReturnType ?: asmMethod.returnType, freeAsmParameters.toTypedArray())
invokeMethodParameters = freeParameters.map { it.type.toIrBasedKotlinType() }
invokeMethodReturnType = (unboxedReturnType ?: function.returnType).toIrBasedKotlinType()
}
}
@@ -0,0 +1,61 @@
/*
* 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.codegen
import org.jetbrains.kotlin.backend.jvm.mapping.IrCallableMethod
import org.jetbrains.kotlin.codegen.inline.MethodBodyVisitor
import org.jetbrains.kotlin.codegen.inline.SourceMapCopier
import org.jetbrains.kotlin.ir.declarations.IrValueParameter
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.ir.expressions.IrGetValue
import org.jetbrains.kotlin.ir.expressions.IrTypeOperatorCall
import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.org.objectweb.asm.Type
/**
* A specialization of IrInlineCodegen for calls to the underlying method in a $default handler.
* Such calls are inlined verbatim in the JVM backend (see InlineCodegenForDefaultBody.kt).
* For compatibility we have to do the same thing in the JVM IR backend.
*/
object IrInlineDefaultCodegen : IrInlineCallGenerator {
override fun genValueAndPut(
irValueParameter: IrValueParameter,
argumentExpression: IrExpression,
parameterType: Type,
codegen: ExpressionCodegen,
blockInfo: BlockInfo
) {
// This codegen is only used for calls to the underlying function in a $default stub.
// For such calls we know that we are passing along the value parameters and reusing the same indices.
// There is no need to generate any code.
assert(argumentExpression is IrGetValue || argumentExpression is IrTypeOperatorCall && argumentExpression.argument is IrGetValue)
}
override fun genInlineCall(
callableMethod: IrCallableMethod,
codegen: ExpressionCodegen,
expression: IrFunctionAccessExpression,
isInsideIfCondition: Boolean
) {
val function = expression.symbol.owner
val nodeAndSmap = codegen.classCodegen.generateMethodNode(function)
val childSourceMapper = SourceMapCopier(codegen.smap, nodeAndSmap.classSMAP)
val argsSize =
(Type.getArgumentsAndReturnSizes(callableMethod.asmMethod.descriptor) ushr 2) - if (function.isStatic) 1 else 0
nodeAndSmap.node.accept(object : MethodBodyVisitor(codegen.visitor) {
override fun visitLocalVariable(name: String, desc: String, signature: String?, start: Label, end: Label, index: Int) {
// We only copy LVT entries for local variables, since we already generated entries for the method parameters,
if (index >= argsSize) super.visitLocalVariable(name, desc, signature, start, end, index)
}
override fun visitLineNumber(line: Int, start: Label?) {
super.visitLineNumber(childSourceMapper.mapLineNumber(line), start)
}
})
}
}
@@ -0,0 +1,126 @@
/*
* 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.codegen
import org.jetbrains.kotlin.backend.common.ir.allParametersCount
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.intrinsics.SignatureString
import org.jetbrains.kotlin.backend.jvm.ir.getCallableReferenceOwnerKClassType
import org.jetbrains.kotlin.backend.jvm.ir.getCallableReferenceTopLevelFlag
import org.jetbrains.kotlin.backend.jvm.mapping.IrTypeMapper
import org.jetbrains.kotlin.builtins.jvm.JavaToKotlinClassMap
import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.codegen.inline.ReifiedTypeInliner
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.descriptors.toIrBasedKotlinType
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.classOrNull
import org.jetbrains.kotlin.ir.util.defaultType
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.jvm.AsmTypes.*
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmBackendErrors
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.model.TypeParameterMarker
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.Type.INT_TYPE
import org.jetbrains.org.objectweb.asm.Type.VOID_TYPE
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
class IrInlineIntrinsicsSupport(
private val context: JvmBackendContext,
private val typeMapper: IrTypeMapper,
private val reportErrorsOn: IrExpression,
private val containingFile: IrFile,
) : ReifiedTypeInliner.IntrinsicsSupport<IrType> {
override val state: GenerationState
get() = context.state
override fun putClassInstance(v: InstructionAdapter, type: IrType) {
ExpressionCodegen.generateClassInstance(v, type, typeMapper)
}
override fun generateTypeParameterContainer(v: InstructionAdapter, typeParameter: TypeParameterMarker) {
require(typeParameter is IrTypeParameterSymbol)
when (val parent = typeParameter.owner.parent) {
is IrClass -> putClassInstance(v, parent.defaultType).also { AsmUtil.wrapJavaClassIntoKClass(v) }
is IrSimpleFunction -> {
check(context.state.generateOptimizedCallableReferenceSuperClasses) {
"typeOf() of a non-reified type parameter is only allowed if optimized callable references are enabled.\n" +
"Please make sure API version is set to 1.4, and -Xno-optimized-callable-references is NOT used.\n" +
"Container: $parent"
}
val property = parent.correspondingPropertySymbol
if (property != null) {
generatePropertyReference(v, property.owner)
} else {
generateFunctionReference(v, parent)
}
}
else -> error("Unknown parent of type parameter: ${parent.render()} ${typeParameter.owner.name})")
}
}
private fun generateFunctionReference(v: InstructionAdapter, function: IrFunction) {
generateCallableReference(v, function, function, FUNCTION_REFERENCE_IMPL, true)
}
private fun generatePropertyReference(v: InstructionAdapter, property: IrProperty) {
// We're sure that this property has a getter because if a property is generic, it necessarily has extension receiver and
// thus cannot have a backing field, and is required to have a getter.
val getter = property.getter
?: error("Property without getter: ${property.render()}")
val arity = getter.allParametersCount
val implClass = (if (property.isVar) MUTABLE_PROPERTY_REFERENCE_IMPL else PROPERTY_REFERENCE_IMPL).getOrNull(arity)
?: error("No property reference impl class with arity $arity (${property.render()}")
generateCallableReference(v, property, getter, implClass, false)
}
private fun generateCallableReference(
v: InstructionAdapter, declaration: IrDeclarationWithName, function: IrFunction, implClass: Type, withArity: Boolean
) {
v.anew(implClass)
v.dup()
if (withArity) {
v.iconst(function.allParametersCount)
}
putClassInstance(v, declaration.parent.getCallableReferenceOwnerKClassType(context))
v.aconst(declaration.name.asString())
// TODO: generate correct signature for functions and property accessors which have inline class types in the signature.
SignatureString.generateSignatureString(v, function, context)
v.iconst(declaration.getCallableReferenceTopLevelFlag())
val parameterTypes =
(if (withArity) listOf(INT_TYPE) else emptyList()) +
listOf(JAVA_CLASS_TYPE, JAVA_STRING_TYPE, JAVA_STRING_TYPE, INT_TYPE)
v.invokespecial(
implClass.internalName, "<init>",
Type.getMethodDescriptor(VOID_TYPE, *parameterTypes.toTypedArray()),
false
)
}
override fun isMutableCollectionType(type: IrType): Boolean {
val classifier = type.classOrNull
return classifier != null && JavaToKotlinClassMap.isMutable(classifier.owner.fqNameWhenAvailable?.toUnsafe())
}
override fun toKotlinType(type: IrType): KotlinType = type.toIrBasedKotlinType()
override fun reportSuspendTypeUnsupported() {
context.ktDiagnosticReporter.at(reportErrorsOn, containingFile).report(JvmBackendErrors.TYPEOF_SUSPEND_TYPE)
}
override fun reportNonReifiedTypeParameterWithRecursiveBoundUnsupported(typeParameterName: Name) {
context.ktDiagnosticReporter.at(reportErrorsOn, containingFile)
.report(JvmBackendErrors.TYPEOF_NON_REIFIED_TYPE_PARAMETER_WITH_RECURSIVE_BOUND, typeParameterName.asString())
}
}
@@ -0,0 +1,135 @@
/*
* 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.codegen
import com.intellij.openapi.util.TextRange
import org.jetbrains.kotlin.backend.common.CodegenUtil
import org.jetbrains.kotlin.backend.common.ir.ir2string
import org.jetbrains.kotlin.backend.jvm.hasMangledReturnType
import org.jetbrains.kotlin.backend.jvm.ir.*
import org.jetbrains.kotlin.codegen.inline.*
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.diagnostics.DiagnosticUtils
import org.jetbrains.kotlin.incremental.components.LocationInfo
import org.jetbrains.kotlin.incremental.components.Position
import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.descriptors.toIrBasedDescriptor
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.ir.expressions.IrLoop
import org.jetbrains.kotlin.ir.util.isSuspend
import org.jetbrains.kotlin.ir.util.kotlinFqName
import org.jetbrains.kotlin.ir.util.module
import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.psi.doNotAnalyze
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmBackendErrors
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
import org.jetbrains.org.objectweb.asm.tree.MethodNode
class IrSourceCompilerForInline(
override val state: GenerationState,
override val callElement: IrFunctionAccessExpression,
private val callee: IrFunction,
internal val codegen: ExpressionCodegen,
private val data: BlockInfo
) : SourceCompilerForInline {
override val callElementText: String
get() = ir2string(callElement)
override val inlineCallSiteInfo: InlineCallSiteInfo
get() {
val root = generateSequence(codegen) { it.inlinedInto }.last()
return InlineCallSiteInfo(
root.classCodegen.type.internalName,
root.signature.asmMethod,
root.irFunction.inlineScopeVisibility,
root.irFunction.fileParent.getKtFile(),
callElement.psiElement?.let { CodegenUtil.getLineNumberForElement(it, false) } ?: 0
)
}
override val sourceMapper: SourceMapper
get() = codegen.smap
override fun generateLambdaBody(lambdaInfo: ExpressionLambda, reifiedTypeParameters: ReifiedTypeParametersUsages): SMAPAndMethodNode {
require(lambdaInfo is IrExpressionLambdaImpl)
for (typeParameter in lambdaInfo.function.typeParameters) {
if (typeParameter.isReified) {
reifiedTypeParameters.addUsedReifiedParameter(typeParameter.name.asString())
}
}
return FunctionCodegen(lambdaInfo.function, codegen.classCodegen).generate(codegen, reifiedTypeParameters)
}
override fun compileInlineFunction(jvmSignature: JvmMethodSignature): SMAPAndMethodNode {
generateInlineIntrinsicForIr(callee.toIrBasedDescriptor())?.let {
return it
}
if (jvmSignature.asmMethod.name != callee.name.asString()) {
val ktFile = codegen.irFunction.fileParent.getKtFile()
if (ktFile != null && ktFile.doNotAnalyze == null) {
state.trackLookup(callee.parentAsClass.kotlinFqName, jvmSignature.asmMethod.name, object : LocationInfo {
override val filePath = ktFile.virtualFilePath
override val position: Position
get() = DiagnosticUtils.getLineAndColumnInPsiFile(
ktFile,
TextRange(callElement.startOffset, callElement.endOffset)
).let { Position(it.line, it.column) }
})
}
}
callee.parentClassId?.let {
return loadCompiledInlineFunction(it, jvmSignature.asmMethod, callee.isSuspend, callee.hasMangledReturnType, state)
}
return ClassCodegen.getOrCreate(callee.parentAsClass, codegen.context).generateMethodNode(callee)
}
override fun hasFinallyBlocks() = data.hasFinallyBlocks()
override fun generateFinallyBlocks(finallyNode: MethodNode, curFinallyDepth: Int, returnType: Type, afterReturnLabel: Label, target: Label?) {
ExpressionCodegen(
codegen.irFunction, codegen.signature, codegen.frameMap, InstructionAdapter(finallyNode), codegen.classCodegen,
codegen.inlinedInto, codegen.smap, codegen.reifiedTypeParametersUsages
).also {
it.finallyDepth = curFinallyDepth
}.generateFinallyBlocksIfNeeded(returnType, afterReturnLabel, data, target)
}
@OptIn(ObsoleteDescriptorBasedAPI::class)
override val isCallInsideSameModuleAsCallee: Boolean
get() = callee.module == codegen.irFunction.module
override val isFinallyMarkerRequired: Boolean
get() = codegen.isFinallyMarkerRequired
override fun isSuspendLambdaCapturedByOuterObjectOrLambda(name: String): Boolean =
false // IR does not capture variables through outer this
override fun getContextLabels(): Map<String, Label?> {
val result = mutableMapOf<String, Label?>(codegen.irFunction.name.asString() to null)
for (info in data.infos) {
if (info !is LoopInfo)
continue
result[info.loop.nonLocalReturnLabel(false)] = info.continueLabel
result[info.loop.nonLocalReturnLabel(true)] = info.breakLabel
}
return result
}
// TODO: Find a way to avoid using PSI here
override fun reportSuspensionPointInsideMonitor(stackTraceElement: String) {
codegen.context.ktDiagnosticReporter
.at(callElement.symbol.owner as IrDeclaration)
.report(JvmBackendErrors.SUSPENSION_POINT_INSIDE_MONITOR, stackTraceElement)
}
}
internal fun IrLoop.nonLocalReturnLabel(forBreak: Boolean): String = "${label!!}\$${if (forBreak) "break" else "continue"}"
@@ -0,0 +1,199 @@
/*
* 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.codegen
import org.jetbrains.kotlin.backend.common.lower.ANNOTATION_IMPLEMENTATION
import org.jetbrains.kotlin.backend.common.psi.PsiSourceManager
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.diagnostics.KtDiagnosticFactory1
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.descriptors.toIrBasedDescriptor
import org.jetbrains.kotlin.ir.util.file
import org.jetbrains.kotlin.ir.util.isFakeOverride
import org.jetbrains.kotlin.resolve.jvm.diagnostics.*
import org.jetbrains.kotlin.utils.SmartSet
import org.jetbrains.org.objectweb.asm.Type
class JvmSignatureClashDetector(
private val irClass: IrClass,
private val type: Type,
private val context: JvmBackendContext
) {
private val methodsBySignature = LinkedHashMap<RawSignature, MutableSet<IrFunction>>()
private val fieldsBySignature = LinkedHashMap<RawSignature, MutableSet<IrField>>()
fun trackField(irField: IrField, rawSignature: RawSignature) {
fieldsBySignature.getOrPut(rawSignature) { SmartSet.create() }.add(irField)
}
fun trackMethod(irFunction: IrFunction, rawSignature: RawSignature) {
methodsBySignature.getOrPut(rawSignature) { SmartSet.create() }.add(irFunction)
}
fun trackFakeOverrideMethod(irFunction: IrFunction) {
if (irFunction.dispatchReceiverParameter != null) {
for (overriddenFunction in getOverriddenFunctions(irFunction as IrSimpleFunction)) {
trackMethod(irFunction, mapRawSignature(overriddenFunction))
}
} else {
trackMethod(irFunction, mapRawSignature(irFunction))
}
}
private fun mapRawSignature(irFunction: IrFunction): RawSignature {
val jvmSignature = context.methodSignatureMapper.mapSignatureSkipGeneric(irFunction)
return RawSignature(jvmSignature.asmMethod.name, jvmSignature.asmMethod.descriptor, MemberKind.METHOD)
}
private fun getOverriddenFunctions(irFunction: IrSimpleFunction): Set<IrFunction> {
val result = LinkedHashSet<IrFunction>()
collectOverridesOf(irFunction, result)
return result
}
private fun collectOverridesOf(irFunction: IrSimpleFunction, result: MutableSet<IrFunction>) {
for (overriddenSymbol in irFunction.overriddenSymbols) {
collectOverridesTree(overriddenSymbol.owner, result)
}
}
private fun collectOverridesTree(irFunction: IrSimpleFunction, visited: MutableSet<IrFunction>) {
if (!visited.add(irFunction)) return
collectOverridesOf(irFunction, visited)
}
private fun IrFunction.isSpecialOverride(): Boolean =
origin in SPECIAL_BRIDGES_AND_OVERRIDES
fun reportErrors(classOrigin: JvmDeclarationOrigin) {
reportMethodSignatureConflicts(classOrigin)
reportPredefinedMethodSignatureConflicts(classOrigin)
reportFieldSignatureConflicts(classOrigin)
}
private fun reportMethodSignatureConflicts(classOrigin: JvmDeclarationOrigin) {
for ((rawSignature, methods) in methodsBySignature) {
if (methods.size <= 1) continue
val fakeOverridesCount = methods.count { it.isFakeOverride }
val specialOverridesCount = methods.count { it.isSpecialOverride() }
val realMethodsCount = methods.size - fakeOverridesCount - specialOverridesCount
val conflictingJvmDeclarationsData = getConflictingJvmDeclarationsData(classOrigin, rawSignature, methods)
when {
realMethodsCount == 0 && (fakeOverridesCount > 1 || specialOverridesCount > 1) ->
if (irClass.origin != JvmLoweredDeclarationOrigin.DEFAULT_IMPLS) {
reportJvmSignatureClash(
JvmBackendErrors.CONFLICTING_INHERITED_JVM_DECLARATIONS,
listOf(irClass),
conflictingJvmDeclarationsData
)
}
fakeOverridesCount == 0 && specialOverridesCount == 0 -> {
// In IFoo$DefaultImpls we should report errors only if there are private methods among conflicting ones
// (otherwise such errors would be reported twice: once for IFoo and once for IFoo$DefaultImpls).
if (irClass.origin != JvmLoweredDeclarationOrigin.DEFAULT_IMPLS ||
methods.any { DescriptorVisibilities.isPrivate(it.visibility) }
) {
reportJvmSignatureClash(
JvmBackendErrors.CONFLICTING_JVM_DECLARATIONS,
methods,
conflictingJvmDeclarationsData
)
}
}
else ->
if (irClass.origin != JvmLoweredDeclarationOrigin.DEFAULT_IMPLS) {
reportJvmSignatureClash(
JvmBackendErrors.ACCIDENTAL_OVERRIDE,
methods.filter { !it.isFakeOverride && !it.isSpecialOverride() },
conflictingJvmDeclarationsData
)
}
}
}
}
private fun reportPredefinedMethodSignatureConflicts(classOrigin: JvmDeclarationOrigin) {
for (predefinedSignature in PREDEFINED_SIGNATURES) {
val knownMethods = methodsBySignature[predefinedSignature] ?: continue
val methods = knownMethods.filter { !it.isFakeOverride && !it.isSpecialOverride() }
if (methods.isEmpty()) continue
val conflictingJvmDeclarationsData = ConflictingJvmDeclarationsData(
type.internalName, classOrigin, predefinedSignature,
methods.map { it.getJvmDeclarationOrigin() } + JvmDeclarationOrigin(JvmDeclarationOriginKind.OTHER, null, null)
)
reportJvmSignatureClash(JvmBackendErrors.ACCIDENTAL_OVERRIDE, methods, conflictingJvmDeclarationsData)
}
}
private fun reportFieldSignatureConflicts(classOrigin: JvmDeclarationOrigin) {
for ((rawSignature, fields) in fieldsBySignature) {
if (fields.size <= 1) continue
val conflictingJvmDeclarationsData = getConflictingJvmDeclarationsData(classOrigin, rawSignature, fields)
reportJvmSignatureClash(JvmBackendErrors.CONFLICTING_JVM_DECLARATIONS, fields, conflictingJvmDeclarationsData)
}
}
private fun reportJvmSignatureClash(
diagnosticFactory1: KtDiagnosticFactory1<ConflictingJvmDeclarationsData>,
irDeclarations: Collection<IrDeclaration>,
conflictingJvmDeclarationsData: ConflictingJvmDeclarationsData
) {
irDeclarations.mapNotNullTo(LinkedHashSet()) { irDeclaration ->
context.ktDiagnosticReporter.atFirstValidFrom(irDeclaration, irClass, containingIrFile = irDeclaration.file)
}.forEach {
it.report(diagnosticFactory1, conflictingJvmDeclarationsData)
}
}
private fun getConflictingJvmDeclarationsData(
classOrigin: JvmDeclarationOrigin,
rawSignature: RawSignature,
methods: Collection<IrDeclaration>
): ConflictingJvmDeclarationsData =
ConflictingJvmDeclarationsData(
type.internalName,
classOrigin,
rawSignature,
methods.map { it.getJvmDeclarationOrigin() }
)
private fun IrDeclaration.getJvmDeclarationOrigin(): JvmDeclarationOrigin {
// It looks like 'JvmDeclarationOriginKind' is not really used in error reporting.
// However, if needed, we can provide more meaningful information regarding function origin.
return JvmDeclarationOrigin(
JvmDeclarationOriginKind.OTHER,
PsiSourceManager.findPsiElement(this),
toIrBasedDescriptor()
)
}
companion object {
val SPECIAL_BRIDGES_AND_OVERRIDES = setOf(
IrDeclarationOrigin.BRIDGE,
IrDeclarationOrigin.BRIDGE_SPECIAL,
IrDeclarationOrigin.IR_BUILTINS_STUB,
JvmLoweredDeclarationOrigin.TO_ARRAY,
JvmLoweredDeclarationOrigin.SUPER_INTERFACE_METHOD_BRIDGE,
ANNOTATION_IMPLEMENTATION
)
val PREDEFINED_SIGNATURES = listOf(
RawSignature("getClass", "()Ljava/lang/Class;", MemberKind.METHOD),
RawSignature("notify", "()V", MemberKind.METHOD),
RawSignature("notifyAll", "()V", MemberKind.METHOD),
RawSignature("wait", "()V", MemberKind.METHOD),
RawSignature("wait", "(J)V", MemberKind.METHOD),
RawSignature("wait", "(JI)V", MemberKind.METHOD)
)
}
}
@@ -0,0 +1,177 @@
/*
* Copyright 2010-2019 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.codegen
import org.jetbrains.kotlin.backend.jvm.InlineClassAbi
import org.jetbrains.kotlin.backend.jvm.inlineClassFieldName
import org.jetbrains.kotlin.backend.jvm.ir.eraseTypeParameters
import org.jetbrains.kotlin.backend.jvm.mapping.IrTypeMapper
import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.codegen.StackValue
import org.jetbrains.kotlin.ir.declarations.IrTypeParameter
import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.isTypeParameter
import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.load.kotlin.TypeMappingMode
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
// A value that may not have been fully constructed yet. The ability to "roll back" code generation
// is useful for certain optimizations.
abstract class PromisedValue(val codegen: ExpressionCodegen, val type: Type, val irType: IrType) {
// If this value is immaterial, construct an object on the top of the stack. This
// must always be done before generating other values or emitting raw bytecode.
open fun materializeAt(target: Type, irTarget: IrType, castForReified: Boolean) {
val erasedSourceType = irType.eraseTypeParameters()
val erasedTargetType = irTarget.eraseTypeParameters()
// Coerce inline classes
val isFromTypeUnboxed = InlineClassAbi.unboxType(erasedSourceType)?.let(typeMapper::mapType) == type
val isToTypeUnboxed = InlineClassAbi.unboxType(erasedTargetType)?.let(typeMapper::mapType) == target
if (isFromTypeUnboxed && !isToTypeUnboxed) {
val boxed = typeMapper.mapType(erasedSourceType, TypeMappingMode.CLASS_DECLARATION)
StackValue.boxInlineClass(type, boxed, erasedSourceType.isNullable(), mv)
return
}
if (!isFromTypeUnboxed && isToTypeUnboxed) {
val boxed = typeMapper.mapType(erasedTargetType, TypeMappingMode.CLASS_DECLARATION)
val irClass = codegen.irFunction.parentAsClass
if (irClass.isInline && irClass.symbol == irType.classifierOrNull && !irType.isNullable()) {
// Use getfield instead of unbox-impl inside inline classes
codegen.mv.getfield(boxed.internalName, irClass.inlineClassFieldName.asString(), target.descriptor)
} else {
StackValue.unboxInlineClass(type, boxed, target, erasedTargetType.isNullable(), mv)
}
return
}
if (type != target || (castForReified && irType.anyTypeArgument { it.isReified })) {
StackValue.coerce(type, target, mv, type == target)
}
}
abstract fun discard()
val mv: InstructionAdapter
get() = codegen.mv
val typeMapper: IrTypeMapper
get() = codegen.typeMapper
}
fun IrType.anyTypeArgument(check: (IrTypeParameter) -> Boolean): Boolean {
when {
isTypeParameter() -> if (check((classifierOrNull as IrTypeParameterSymbol).owner)) return true
this is IrSimpleType -> return arguments.any { it.typeOrNull?.anyTypeArgument(check) ?: false }
}
return false
}
// A value that *has* been fully constructed.
class MaterialValue(codegen: ExpressionCodegen, type: Type, irType: IrType) : PromisedValue(codegen, type, irType) {
override fun discard() {
if (type !== Type.VOID_TYPE)
AsmUtil.pop(mv, type)
}
}
// A value that can be branched on. JVM has certain branching instructions which can be used
// to optimize these.
abstract class BooleanValue(codegen: ExpressionCodegen) :
PromisedValue(codegen, Type.BOOLEAN_TYPE, codegen.context.irBuiltIns.booleanType) {
abstract fun jumpIfFalse(target: Label)
abstract fun jumpIfTrue(target: Label)
override fun materializeAt(target: Type, irTarget: IrType, castForReified: Boolean) {
val const0 = Label()
val end = Label()
jumpIfFalse(const0)
mv.iconst(1)
mv.goTo(end)
mv.mark(const0)
mv.iconst(0)
mv.mark(end)
if (Type.BOOLEAN_TYPE != target) {
StackValue.coerce(Type.BOOLEAN_TYPE, target, mv)
}
}
}
class BooleanConstant(codegen: ExpressionCodegen, val value: Boolean) : BooleanValue(codegen) {
override fun jumpIfFalse(target: Label) = if (value) Unit else mv.goTo(target)
override fun jumpIfTrue(target: Label) = if (value) mv.goTo(target) else Unit
override fun materializeAt(target: Type, irTarget: IrType, castForReified: Boolean) {
mv.iconst(if (value) 1 else 0)
if (Type.BOOLEAN_TYPE != target) {
StackValue.coerce(Type.BOOLEAN_TYPE, target, mv)
}
}
override fun discard() {}
}
fun PromisedValue.coerceToBoolean(): BooleanValue =
when (this) {
is BooleanValue -> this
else -> object : BooleanValue(codegen) {
override fun jumpIfFalse(target: Label) =
this@coerceToBoolean.materializeAt(Type.BOOLEAN_TYPE, codegen.context.irBuiltIns.booleanType, false).also { mv.ifeq(target) }
override fun jumpIfTrue(target: Label) =
this@coerceToBoolean.materializeAt(Type.BOOLEAN_TYPE, codegen.context.irBuiltIns.booleanType, false).also { mv.ifne(target) }
override fun discard() {
this@coerceToBoolean.discard()
}
}
}
fun PromisedValue.materializeAt(target: Type, irTarget: IrType) {
materializeAt(target, irTarget, false)
}
fun PromisedValue.materializedAt(target: Type, irTarget: IrType, castForReified: Boolean = false): MaterialValue {
materializeAt(target, irTarget, castForReified)
return MaterialValue(codegen, target, irTarget)
}
fun PromisedValue.materialized(): MaterialValue =
materializedAt(type, irType)
fun PromisedValue.materializedAt(irTarget: IrType): MaterialValue =
materializedAt(typeMapper.mapType(irTarget), irTarget)
fun PromisedValue.materializedAtBoxed(irTarget: IrType): MaterialValue =
materializedAt(typeMapper.boxType(irTarget), irTarget)
fun PromisedValue.materialize() {
materializeAt(type, irType)
}
fun PromisedValue.materializeAt(irTarget: IrType) {
materializeAt(typeMapper.mapType(irTarget), irTarget)
}
fun PromisedValue.materializeAtBoxed(irTarget: IrType) {
materializeAt(typeMapper.boxType(irTarget), irTarget)
}
// A Non-materialized value of Unit type that is only materialized through coercion.
val ExpressionCodegen.unitValue: PromisedValue
get() = MaterialValue(this, Type.VOID_TYPE, context.irBuiltIns.unitType)
val ExpressionCodegen.nullConstant: PromisedValue
get() = object : PromisedValue(this, AsmTypes.OBJECT_TYPE, context.irBuiltIns.nothingNType) {
override fun materializeAt(target: Type, irTarget: IrType, castForReified: Boolean) {
mv.aconst(null)
}
override fun discard() {}
}
@@ -0,0 +1,512 @@
/*
* Copyright 2010-2019 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.codegen
import org.jetbrains.kotlin.codegen.`when`.SwitchCodegen.Companion.preferLookupOverSwitch
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.dump
import org.jetbrains.kotlin.ir.util.hasAnnotation
import org.jetbrains.kotlin.ir.util.isTrueConst
import org.jetbrains.kotlin.load.java.JvmAnnotationNames
import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.org.objectweb.asm.Type
// TODO: eliminate the temporary variable
class SwitchGenerator(private val expression: IrWhen, private val data: BlockInfo, private val codegen: ExpressionCodegen) {
data class ExpressionToLabel(val expression: IrExpression, val label: Label)
data class CallToLabel(val call: IrCall, val label: Label)
data class ValueToLabel(val value: Any?, val label: Label)
// @return null if the IrWhen cannot be emitted as lookupswitch or tableswitch.
fun generate(): PromisedValue? {
val expressionToLabels = ArrayList<ExpressionToLabel>()
var elseExpression: IrExpression? = null
val callToLabels = ArrayList<CallToLabel>()
// Parse the when structure. Note that the condition can be nested. See matchConditions() for details.
for (branch in expression.branches) {
if (branch is IrElseBranch) {
elseExpression = branch.result
} else {
val conditions = matchConditions(branch.condition) ?: return null
val thenLabel = Label()
expressionToLabels.add(ExpressionToLabel(branch.result, thenLabel))
callToLabels += conditions.map { CallToLabel(it, thenLabel) }
}
}
// switch isn't applicable if there's no case at all, e.g., when() { else -> ... }
if (callToLabels.size == 0)
return null
val calls = callToLabels.map { it.call }
// To generate a switch from a when it must be a comparison of a single
// variable, the "subject", against a series of constants. We assume the
// subject is the left hand side of the first condition, provided the
// first condition is a comparison. If the first condition is of the form:
//
// CALL EQEQ(<unsafe-coerce><UInt, Int>(var),_)
//
// we must be trying to generate an _unsigned_ int switch, and need to
// account for unsafe-coerce in all comparisons that arise from the
// wrapping and unwrapping of the UInt inline class wrapper. Otherwise,
// this is a primitive Int or String switch, with a condition of the form
//
// CALL EQEQ(var,_)
val firstCondition = callToLabels[0].call
if (firstCondition.symbol != codegen.classCodegen.context.irBuiltIns.eqeqSymbol) return null
val subject = firstCondition.getValueArgument(0)
return when {
subject is IrCall && subject.isCoerceFromUIntToInt() ->
generateUIntSwitch(subject.getValueArgument(0)!! as? IrGetValue, calls, callToLabels, expressionToLabels, elseExpression)
subject is IrGetValue ->
generatePrimitiveSwitch(subject, calls, callToLabels, expressionToLabels, elseExpression)
else ->
null
}?.genOptimizedIfEnoughCases()
}
fun IrCall.isCoerceFromUIntToInt(): Boolean =
symbol == codegen.classCodegen.context.ir.symbols.unsafeCoerceIntrinsic
&& getTypeArgument(0)?.isUInt() == true
&& getTypeArgument(1)?.isInt() == true
private fun generateUIntSwitch(
subject: IrGetValue?,
conditions: List<IrCall>,
callToLabels: ArrayList<CallToLabel>,
expressionToLabels: ArrayList<ExpressionToLabel>,
elseExpression: IrExpression?
): Switch? {
if (subject == null) return null
// We check that all conditions are of the form
// CALL EQEQ (<unsafe-coerce><UInt,Int>(subject),
// <unsafe-coerce><UInt,Int>( Constant ))
if (!areConstUIntComparisons(conditions)) return null
// Filter repeated cases. Allowed in Kotlin but unreachable.
val cases = callToLabels.map {
val constCoercion = it.call.getValueArgument(1)!! as IrCall
val constValue = (constCoercion.getValueArgument(0) as IrConst<*>).value
ValueToLabel(
constValue,
it.label
)
}.distinctBy { it.value }
expressionToLabels.removeUnreachableLabels(cases)
return IntSwitch(
subject,
elseExpression,
expressionToLabels,
cases
)
}
private fun generatePrimitiveSwitch(
subject: IrGetValue,
conditions: List<IrCall>,
callToLabels: ArrayList<CallToLabel>,
expressionToLabels: ArrayList<ExpressionToLabel>,
elseExpression: IrExpression?
): Switch? {
// Checks if all conditions are CALL EQEQ(var,constant)
if (!areConstantComparisons(conditions)) return null
return when {
areConstIntComparisons(conditions) -> {
val cases = extractSwitchCasesAndFilterUnreachableLabels(callToLabels, expressionToLabels)
IntSwitch(
subject,
elseExpression,
expressionToLabels,
cases
)
}
areConstStringComparisons(conditions) -> {
val cases = extractSwitchCasesAndFilterUnreachableLabels(callToLabels, expressionToLabels)
StringSwitch(
subject,
elseExpression,
expressionToLabels,
cases
)
}
else ->
null
}
}
// Check that all conditions are of the form
//
// CALL EQEQ (<unsafe-coerce><UInt,Int>(subject), <unsafe-coerce><UInt,Int>( Constant ))
//
// where subject is taken to be the first variable compared on the left hand side, if any.
private fun areConstUIntComparisons(conditions: List<IrCall>): Boolean {
val lhs = conditions.map { it.takeIf { it.symbol == codegen.context.irBuiltIns.eqeqSymbol }?.getValueArgument(0) as? IrCall }
if (lhs.any { it == null || !it.isCoerceFromUIntToInt() }) return false
val lhsVariableAccesses = lhs.map { it!!.getValueArgument(0) as? IrGetValue }
if (lhsVariableAccesses.any { it == null || it.symbol != lhsVariableAccesses[0]!!.symbol }) return false
val rhs = conditions.map { it.getValueArgument(1) as? IrCall }
if (rhs.any { it == null || !it.isCoerceFromUIntToInt() || it.getValueArgument(0) !is IrConst<*> }) return false
return true
}
private fun areConstantComparisons(conditions: List<IrCall>): Boolean {
// All conditions are equality checks && all LHS refer to the same tmp variable.
val lhs = conditions.map { it.takeIf { it.symbol == codegen.context.irBuiltIns.eqeqSymbol }?.getValueArgument(0) as? IrGetValue }
if (lhs.any { it == null || it.symbol != lhs[0]!!.symbol })
return false
// All RHS are constants
if (conditions.any { it.getValueArgument(1) !is IrConst<*> })
return false
return true
}
private fun areConstIntComparisons(conditions: List<IrCall>): Boolean {
return checkTypeSpecifics(conditions, { it.isInt() }, { it.kind == IrConstKind.Int })
}
private fun areConstStringComparisons(conditions: List<IrCall>): Boolean {
return checkTypeSpecifics(
conditions,
{ it.isString() || it.isNullableString() },
{ it.kind == IrConstKind.String || it.kind == IrConstKind.Null })
}
private fun checkTypeSpecifics(
conditions: List<IrCall>,
subjectTypePredicate: (IrType) -> Boolean,
irConstPredicate: (IrConst<*>) -> Boolean
): Boolean {
val lhs = conditions.map { it.getValueArgument(0) as IrGetValue }
if (lhs.any { !subjectTypePredicate(it.type) })
return false
val rhs = conditions.map { it.getValueArgument(1) as IrConst<*> }
if (rhs.any { !irConstPredicate(it) })
return false
return true
}
private fun extractSwitchCasesAndFilterUnreachableLabels(
callToLabels: List<CallToLabel>,
expressionToLabels: ArrayList<ExpressionToLabel>
): List<ValueToLabel> {
// Don't generate repeated cases, which are unreachable but allowed in Kotlin.
// Only keep the first encountered case:
val cases =
callToLabels.map { ValueToLabel((it.call.getValueArgument(1) as IrConst<*>).value, it.label) }.distinctBy { it.value }
expressionToLabels.removeUnreachableLabels(cases)
return cases
}
private fun ArrayList<ExpressionToLabel>.removeUnreachableLabels(cases: List<ValueToLabel>) {
val reachableLabels = HashSet(cases.map { it.label })
removeIf { it.label !in reachableLabels }
}
// psi2ir lowers multiple cases to nested conditions. For example,
//
// when (subject) {
// a, b, c -> action
// }
//
// is lowered to
//
// if (if (subject == a)
// true
// else
// if (subject == b)
// true
// else
// subject == c) {
// action
// }
//
// fir2ir lowers the same to an or sequence:
//
// if (((subject == a) || (subject == b)) || (subject = c)) action
//
// @return true if the conditions are equality checks of constants.
private fun matchConditions(condition: IrExpression): ArrayList<IrCall>? {
if (condition is IrWhen && condition.origin == IrStatementOrigin.WHEN_COMMA) {
assert(condition.type.isBoolean()) { "WHEN_COMMA should always be a Boolean: ${condition.dump()}" }
val candidates = ArrayList<IrCall>()
// Match the following structure:
//
// when() {
// cond_1 -> true
// cond_2 -> true
// ...
// else -> cond_N
// }
//
// Namely, the structure which returns true if any one of the condition is true.
for (branch in condition.branches) {
candidates += if (branch is IrElseBranch) {
assert(branch.condition.isTrueConst()) { "IrElseBranch.condition should be const true: ${branch.condition.dump()}" }
matchConditions(branch.result) ?: return null
} else {
if (!branch.result.isTrueConst())
return null
matchConditions(branch.condition) ?: return null
}
}
return if (candidates.isNotEmpty()) candidates else return null
} else if (condition is IrCall && condition.symbol == codegen.context.irBuiltIns.ororSymbol) {
val candidates = ArrayList<IrCall>()
for (i in 0 until condition.valueArgumentsCount) {
val argument = condition.getValueArgument(i)!!
candidates += matchConditions(argument) ?: return null
}
return if (candidates.isNotEmpty()) candidates else return null
} else if (condition is IrCall) {
return arrayListOf(condition)
}
return null
}
abstract inner class Switch(
val subject: IrGetValue,
val elseExpression: IrExpression?,
val expressionToLabels: ArrayList<ExpressionToLabel>
) {
protected val defaultLabel = Label()
open fun shouldOptimize() = false
open fun genOptimizedIfEnoughCases(): PromisedValue? {
if (!shouldOptimize())
return null
genSwitch()
return genBranchTargets()
}
protected abstract fun genSwitch()
protected fun genIntSwitch(unsortedIntCases: List<ValueToLabel>) {
val intCases = unsortedIntCases.sortedBy { it.value as Int }
val caseMin = intCases.first().value as Int
val caseMax = intCases.last().value as Int
val rangeLength = caseMax.toLong() - caseMin.toLong() + 1L
// Emit either tableswitch or lookupswitch, depending on the code size.
//
// lookupswitch is 2X as large as tableswitch with the same entries. However, lookupswitch is sparse while tableswitch must
// enumerate all the entries in the range.
with(codegen) {
if (preferLookupOverSwitch(intCases.size, rangeLength)) {
mv.lookupswitch(defaultLabel, intCases.map { it.value as Int }.toIntArray(), intCases.map { it.label }.toTypedArray())
} else {
val labels = Array(rangeLength.toInt()) { defaultLabel }
for (case in intCases)
labels[case.value as Int - caseMin] = case.label
mv.tableswitch(caseMin, caseMax, defaultLabel, *labels)
}
}
}
private fun genBranchTargets(): PromisedValue {
with(codegen) {
val endLabel = Label()
for ((thenExpression, label) in expressionToLabels) {
mv.visitLabel(label)
thenExpression.accept(codegen, data).also {
if (elseExpression != null) {
it.materializedAt(expression.type)
} else {
it.discard()
}
}
mv.goTo(endLabel)
}
mv.visitLabel(defaultLabel)
val result = elseExpression?.accept(codegen, data)?.materializedAt(expression.type) ?: unitValue
mv.mark(endLabel)
return result
}
}
}
inner class IntSwitch(
subject: IrGetValue,
elseExpression: IrExpression?,
expressionToLabels: ArrayList<ExpressionToLabel>,
private val cases: List<ValueToLabel>
) : Switch(subject, elseExpression, expressionToLabels) {
// IF is more compact when there are only 1 or fewer branches, in addition to else.
override fun shouldOptimize() = cases.size > 1
override fun genSwitch() {
// Do not generate line numbers for the table switching. In particular,
// the subject is extracted from the condition of the first branch which
// will give the wrong stepping behavior for code such as:
//
// when {
// x == 42 -> 1
// x == 32 -> 2
// x == 24 -> 3
// ...
// }
//
// If the subject line number is generated, we will not stop on the line
// of the `when` but instead stop on the `x == 42` line. When x is 24,
// we would stop on the line `x == 42` and then step to the line `x == 24`.
// That is confusing and we prefer to stop on the `when` line and then step
// to the `x == 24` line. This is accomplished by ignoring the line number
// information for the subject as the `when` line number has already been
// emitted.
codegen.noLineNumberScope {
val subjectValue = subject.accept(codegen, data)
subjectValue.materializeAt(Type.INT_TYPE, subjectValue.irType)
}
genIntSwitch(cases)
}
}
// The following when structure:
//
// when (s) {
// s1, s2 -> e1,
// s3 -> e2,
// s4 -> e3,
// ...
// else -> e
// }
//
// is implemented as:
//
// // if s is String?, generate the following null check:
// if (s == null)
// // jump to the case where null is handled, if defined.
// // otherwise, jump out of the when().
// ...
// ...
// when (s.hashCode()) {
// h1 -> {
// if (s == s1)
// e1
// else if (s == s2)
// e1
// else if (s == s3)
// e2
// else
// e
// }
// h2 -> if (s == s3) e2 else e,
// ...
// else -> e
// }
//
// where s1.hashCode() == s2.hashCode() == s3.hashCode() == h1,
// s4.hashCode() == h2.
//
// A tableswitch or lookupswitch is then used for the hash code lookup.
inner class StringSwitch(
subject: IrGetValue,
elseExpression: IrExpression?,
expressionToLabels: ArrayList<ExpressionToLabel>,
private val cases: List<ValueToLabel>
) : Switch(subject, elseExpression, expressionToLabels) {
private val hashToStringAndExprLabels = HashMap<Int, ArrayList<ValueToLabel>>()
private val hashAndSwitchLabels = ArrayList<ValueToLabel>()
init {
for (case in cases)
if (case.value != null) // null is handled specially and will never be dispatched from the switch.
hashToStringAndExprLabels.getOrPut(case.value.hashCode()) { ArrayList() }.add(
ValueToLabel(case.value, case.label)
)
for (key in hashToStringAndExprLabels.keys)
hashAndSwitchLabels.add(ValueToLabel(key, Label()))
}
// Using a switch, the subject string has to be traversed at least twice
// (hash + comparison * N, where N is #strings hashed into the same bucket).
// The optimization isn't better than an IF cascade when #switch-targets <= 2.
//
// Generate "optimized" version for @EnhancedNullability subject type
// to model 1.0 behavior causing NPE in case of null value.
// TODO make 'when' with String subject behavior consistent.
// see:
// box/when/stringOptimization/enhancedNullability.kt
// box/when/stringOptimization/flexibleNullability.kt
override fun shouldOptimize() =
hashAndSwitchLabels.size > 2 ||
subject.type.hasAnnotation(JvmAnnotationNames.ENHANCED_NULLABILITY_ANNOTATION) && hashAndSwitchLabels.isNotEmpty()
override fun genSwitch() {
with(codegen) {
// Do not generate line numbers for the table switching. In particular,
// the subject is extracted from the condition of the first branch which
// will give the wrong stepping behavior for code such as:
//
// when {
// x == "x" -> 1
// x == "y" -> 2
// x == "z" -> 3
// ...
// }
//
// If the subject line number is generated, we will not stop on the line
// of the `when` but instead stop on the `x == "x"` line. When x is "z",
// we would stop on the line `x == "x"` and then step to the line `x == "z"`.
// That is confusing and we prefer to stop on the `when` line and then step
// to the `x == "z"` line. This is accomplished by ignoring the line number
// information for the subject as the `when` line number has already been
// emitted.
noLineNumberScope {
if (subject.type.isNullableString()) {
subject.accept(codegen, data).materialize()
mv.ifnull(cases.find { it.value == null }?.label ?: defaultLabel)
}
// Reevaluating the subject is fine here because it is a read of a temporary.
subject.accept(codegen, data).materialize()
mv.invokevirtual("java/lang/String", "hashCode", "()I", false)
}
genIntSwitch(hashAndSwitchLabels)
// Multiple strings can be hashed into the same bucket.
// Generate an if cascade to resolve that for each bucket.
for ((hash, switchLabel) in hashAndSwitchLabels) {
mv.visitLabel(switchLabel)
for ((string, label) in hashToStringAndExprLabels[hash]!!) {
noLineNumberScope {
subject.accept(codegen, data).materialize()
}
mv.aconst(string)
mv.invokevirtual("java/lang/String", "equals", "(Ljava/lang/Object;)Z", false)
mv.ifne(label)
}
mv.goTo(defaultLabel)
}
}
}
}
}
@@ -0,0 +1,331 @@
/*
* Copyright 2010-2018 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.codegen
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.backend.common.ir.allOverridden
import org.jetbrains.kotlin.backend.common.ir.ir2string
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
import org.jetbrains.kotlin.backend.jvm.MultifileFacadeFileEntry
import org.jetbrains.kotlin.backend.jvm.ir.fileParent
import org.jetbrains.kotlin.backend.jvm.ir.isInlineOnly
import org.jetbrains.kotlin.backend.jvm.ir.isJvmInterface
import org.jetbrains.kotlin.backend.jvm.ir.isPrivateInlineSuspend
import org.jetbrains.kotlin.backend.jvm.mapping.IrTypeMapper
import org.jetbrains.kotlin.backend.jvm.mapping.mapClass
import org.jetbrains.kotlin.backend.jvm.mapping.mapSupertype
import org.jetbrains.kotlin.builtins.StandardNames.FqNames
import org.jetbrains.kotlin.builtins.jvm.JavaToKotlinClassMap
import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.codegen.FrameMapBase
import org.jetbrains.kotlin.codegen.OwnerKind
import org.jetbrains.kotlin.codegen.SourceInfo
import org.jetbrains.kotlin.codegen.inline.SourceMapper
import org.jetbrains.kotlin.codegen.signature.BothSignatureWriter
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrExpressionBody
import org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpression
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.types.IrSimpleType
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.getClass
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.load.java.JavaDescriptorVisibilities
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmClassSignature
import org.jetbrains.kotlin.resolve.source.PsiSourceElement
import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.Type
class IrFrameMap : FrameMapBase<IrSymbol>() {
private val typeMap = mutableMapOf<IrSymbol, Type>()
override fun enter(key: IrSymbol, type: Type): Int {
typeMap[key] = type
return super.enter(key, type)
}
override fun leave(key: IrSymbol): Int {
typeMap.remove(key)
return super.leave(key)
}
fun typeOf(symbol: IrSymbol): Type = typeMap[symbol]
?: error("No mapping for symbol: ${symbol.owner.render()}")
}
internal val IrFunction.isStatic
get() = (this.dispatchReceiverParameter == null && this !is IrConstructor)
fun IrFrameMap.enter(irDeclaration: IrSymbolOwner, type: Type): Int {
return enter(irDeclaration.symbol, type)
}
fun IrFrameMap.leave(irDeclaration: IrSymbolOwner): Int {
return leave(irDeclaration.symbol)
}
fun JvmBackendContext.getSourceMapper(declaration: IrClass): SourceMapper {
val fileEntry = declaration.fileParent.fileEntry
// NOTE: apparently inliner requires the source range to cover the
// whole file the class is declared in rather than the class only.
val endLineNumber = when (fileEntry) {
is MultifileFacadeFileEntry -> 0
else -> fileEntry.getSourceRangeInfo(0, fileEntry.maxOffset).endLineNumber
}
val sourceFileName = when (fileEntry) {
is MultifileFacadeFileEntry -> fileEntry.partFiles.singleOrNull()?.name
else -> declaration.fileParent.name
}
return SourceMapper(
SourceInfo(
sourceFileName,
typeMapper.mapClass(declaration).internalName,
endLineNumber + 1
)
)
}
val IrType.isExtensionFunctionType: Boolean
get() = isFunctionTypeOrSubtype() && hasAnnotation(FqNames.extensionFunctionType)
/* Borrowed with modifications from AsmUtil.java */
private val NO_FLAG_LOCAL = 0
private fun IrDeclaration.getVisibilityAccessFlagForAnonymous(): Int =
if (isInlineOrContainedInInline(parent as? IrDeclaration)) Opcodes.ACC_PUBLIC else AsmUtil.NO_FLAG_PACKAGE_PRIVATE
fun IrClass.calculateInnerClassAccessFlags(context: JvmBackendContext): Int {
val isLambda = superTypes.any {
it.safeAs<IrSimpleType>()?.classifier === context.ir.symbols.lambdaClass
}
val visibility = when {
isLambda -> getVisibilityAccessFlagForAnonymous()
visibility === DescriptorVisibilities.LOCAL -> Opcodes.ACC_PUBLIC
else -> getVisibilityAccessFlag()
}
return visibility or
if (origin.isSynthetic) Opcodes.ACC_SYNTHETIC else 0 or
innerAccessFlagsForModalityAndKind() or
if (isInner) 0 else Opcodes.ACC_STATIC
}
private fun IrClass.innerAccessFlagsForModalityAndKind(): Int {
when (kind) {
ClassKind.INTERFACE -> return Opcodes.ACC_ABSTRACT or Opcodes.ACC_INTERFACE
ClassKind.ENUM_CLASS -> return Opcodes.ACC_FINAL or Opcodes.ACC_ENUM
ClassKind.ANNOTATION_CLASS -> return Opcodes.ACC_ABSTRACT or Opcodes.ACC_ANNOTATION or Opcodes.ACC_INTERFACE
else -> {
if (modality === Modality.FINAL) {
return Opcodes.ACC_FINAL
} else if (modality === Modality.ABSTRACT || modality === Modality.SEALED) {
return Opcodes.ACC_ABSTRACT
}
}
}
return 0
}
fun IrDeclarationWithVisibility.getVisibilityAccessFlag(kind: OwnerKind? = null): Int {
specialCaseVisibility(kind)?.let {
return it
}
return when (visibility) {
DescriptorVisibilities.PRIVATE -> Opcodes.ACC_PRIVATE
DescriptorVisibilities.PRIVATE_TO_THIS -> Opcodes.ACC_PRIVATE
DescriptorVisibilities.PROTECTED -> Opcodes.ACC_PROTECTED
JavaDescriptorVisibilities.PROTECTED_STATIC_VISIBILITY -> Opcodes.ACC_PROTECTED
JavaDescriptorVisibilities.PROTECTED_AND_PACKAGE -> Opcodes.ACC_PROTECTED
DescriptorVisibilities.PUBLIC -> Opcodes.ACC_PUBLIC
DescriptorVisibilities.INTERNAL -> Opcodes.ACC_PUBLIC
DescriptorVisibilities.LOCAL -> NO_FLAG_LOCAL
JavaDescriptorVisibilities.PACKAGE_VISIBILITY -> AsmUtil.NO_FLAG_PACKAGE_PRIVATE
else -> throw IllegalStateException("$visibility is not a valid visibility in backend for ${ir2string(this)}")
}
}
private fun IrDeclarationWithVisibility.specialCaseVisibility(kind: OwnerKind?): Int? {
if (this is IrClass && DescriptorVisibilities.isPrivate(visibility) && isCompanion && hasInterfaceParent()) {
// TODO: non-intrinsic
return Opcodes.ACC_PUBLIC
}
if (this is IrConstructor && parentAsClass.isInline && kind === OwnerKind.IMPLEMENTATION) {
return Opcodes.ACC_PRIVATE
}
if (isInlineOnlyPrivateInBytecode()) {
return Opcodes.ACC_PRIVATE
}
if (visibility === DescriptorVisibilities.LOCAL && this is IrFunction) {
return Opcodes.ACC_PUBLIC
}
if (this is IrClass && this.kind === ClassKind.ENUM_ENTRY) {
return AsmUtil.NO_FLAG_PACKAGE_PRIVATE
}
if (this is IrField && correspondingPropertySymbol?.owner?.isExternal == true) {
val method = correspondingPropertySymbol?.owner?.getter ?: correspondingPropertySymbol?.owner?.setter
?: error("No get/set method in SyntheticJavaPropertyDescriptor: ${ir2string(correspondingPropertySymbol?.owner)}")
return method.getVisibilityAccessFlag()
}
if (this is IrSimpleFunction && visibility === DescriptorVisibilities.PROTECTED &&
allOverridden().any { it.parentAsClass.isJvmInterface }
) {
return Opcodes.ACC_PUBLIC
}
if (!DescriptorVisibilities.isPrivate(visibility)) {
return null
}
if (this is IrConstructor && parentAsClass.kind === ClassKind.ENUM_ENTRY) {
return AsmUtil.NO_FLAG_PACKAGE_PRIVATE
}
return null
}
private tailrec fun isInlineOrContainedInInline(declaration: IrDeclaration?): Boolean = when {
declaration === null -> false
declaration is IrFunction && declaration.isInline -> true
else -> isInlineOrContainedInInline(declaration.parent as? IrDeclaration)
}
private fun IrDeclarationWithVisibility.isInlineOnlyPrivateInBytecode(): Boolean =
this is IrFunction && (isInlineOnly() || isPrivateInlineSuspend())
// Borrowed with modifications from ImplementationBodyCodegen.java
private val KOTLIN_MARKER_INTERFACES: Map<FqName, String> = run {
val kotlinMarkerInterfaces = mutableMapOf<FqName, String>()
for (platformMutabilityMapping in JavaToKotlinClassMap.mutabilityMappings) {
kotlinMarkerInterfaces[platformMutabilityMapping.kotlinReadOnly.asSingleFqName()] = "kotlin/jvm/internal/markers/KMappedMarker"
val mutableClassId = platformMutabilityMapping.kotlinMutable
kotlinMarkerInterfaces[mutableClassId.asSingleFqName()] =
"kotlin/jvm/internal/markers/K" + mutableClassId.relativeClassName.asString()
.replace("MutableEntry", "Entry") // kotlin.jvm.internal.markers.KMutableMap.Entry for some reason
.replace(".", "$")
}
kotlinMarkerInterfaces
}
internal fun IrTypeMapper.mapClassSignature(irClass: IrClass, type: Type): JvmClassSignature {
val sw = BothSignatureWriter(BothSignatureWriter.Mode.CLASS)
writeFormalTypeParameters(irClass.typeParameters, sw)
sw.writeSuperclass()
val superClassType = irClass.superTypes.find { it.getClass()?.isJvmInterface == false }
val superClassAsmType = if (superClassType == null) {
sw.writeClassBegin(AsmTypes.OBJECT_TYPE)
sw.writeClassEnd()
AsmTypes.OBJECT_TYPE
} else {
mapSupertype(superClassType, sw)
}
sw.writeSuperclassEnd()
val kotlinMarkerInterfaces = LinkedHashSet<String>()
if (irClass.superTypes.any { it.isSuspendFunction() || it.isKSuspendFunction() }) {
kotlinMarkerInterfaces.add("kotlin/coroutines/jvm/internal/SuspendFunction")
}
val superInterfaces = LinkedHashSet<String>()
for (superType in irClass.superTypes) {
val superClass = superType.safeAs<IrSimpleType>()?.classifier?.safeAs<IrClassSymbol>()?.owner ?: continue
if (superClass.isJvmInterface) {
sw.writeInterface()
superInterfaces.add(mapSupertype(superType, sw).internalName)
sw.writeInterfaceEnd()
kotlinMarkerInterfaces.addIfNotNull(KOTLIN_MARKER_INTERFACES[superClass.fqNameWhenAvailable!!])
}
}
for (kotlinMarkerInterface in kotlinMarkerInterfaces) {
sw.writeInterface()
sw.writeAsmType(Type.getObjectType(kotlinMarkerInterface))
sw.writeInterfaceEnd()
}
superInterfaces.addAll(kotlinMarkerInterfaces)
return JvmClassSignature(
type.internalName, superClassAsmType.internalName,
ArrayList(superInterfaces), sw.makeJavaGenericSignature()
)
}
/* Copied with modifications from AsmUtil.getVisibilityAccessFlagForClass */
/*
Use this method to get visibility flag for class to define it in byte code (v.defineClass method).
For other cases use getVisibilityAccessFlag(MemberDescriptor descriptor)
Classes in byte code should be public or package private
*/
fun IrClass.getVisibilityAccessFlagForClass(): Int {
/* Original had a check for SyntheticClassDescriptorForJava, never invoked in th IR backend. */
if (kind == ClassKind.ENUM_ENTRY) {
return AsmUtil.NO_FLAG_PACKAGE_PRIVATE
}
return if (visibility === DescriptorVisibilities.PUBLIC ||
visibility === DescriptorVisibilities.PROTECTED ||
// TODO: should be package private, but for now Kotlin's reflection can't access members of such classes
visibility === DescriptorVisibilities.LOCAL ||
visibility === DescriptorVisibilities.INTERNAL
) {
Opcodes.ACC_PUBLIC
} else AsmUtil.NO_FLAG_PACKAGE_PRIVATE
}
val IrDeclaration.isAnnotatedWithDeprecated: Boolean
get() = annotations.hasAnnotation(FqNames.deprecated)
internal fun IrDeclaration.isDeprecatedCallable(context: JvmBackendContext): Boolean =
isAnnotatedWithDeprecated ||
annotations.any { it.symbol == context.ir.symbols.javaLangDeprecatedConstructorWithDeprecatedFlag }
internal fun IrFunction.isDeprecatedFunction(context: JvmBackendContext): Boolean =
origin == JvmLoweredDeclarationOrigin.SYNTHETIC_METHOD_FOR_PROPERTY_OR_TYPEALIAS_ANNOTATIONS ||
isDeprecatedCallable(context) ||
(this as? IrSimpleFunction)?.correspondingPropertySymbol?.owner?.isDeprecatedCallable(context) == true ||
isAccessorForDeprecatedPropertyImplementedByDelegation ||
isAccessorForDeprecatedJvmStaticProperty(context)
private val IrFunction.isAccessorForDeprecatedPropertyImplementedByDelegation: Boolean
get() =
origin == IrDeclarationOrigin.DELEGATED_MEMBER &&
this is IrSimpleFunction &&
correspondingPropertySymbol != null &&
overriddenSymbols.any {
it.owner.correspondingPropertySymbol?.owner?.isAnnotatedWithDeprecated == true
}
private fun IrFunction.isAccessorForDeprecatedJvmStaticProperty(context: JvmBackendContext): Boolean {
if (origin != JvmLoweredDeclarationOrigin.JVM_STATIC_WRAPPER) return false
val irExpressionBody = this.body as? IrExpressionBody
?: throw AssertionError("IrExpressionBody expected for JvmStatic wrapper:\n${this.dump()}")
val irCall = irExpressionBody.expression as? IrCall
?: throw AssertionError("IrCall expected inside JvmStatic wrapper:\n${this.dump()}")
val callee = irCall.symbol.owner
val property = callee.correspondingPropertySymbol?.owner ?: return false
return property.isDeprecatedCallable(context)
}
@@ -0,0 +1,43 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. 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.intrinsics
import org.jetbrains.kotlin.backend.jvm.codegen.*
import org.jetbrains.kotlin.backend.jvm.ir.receiverAndArgs
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.org.objectweb.asm.Label
object AndAnd : IntrinsicMethod() {
private class BooleanConjunction(val arg0: IrExpression, val arg1: IrExpression, codegen: ExpressionCodegen, val data: BlockInfo) :
BooleanValue(codegen) {
override fun jumpIfFalse(target: Label) {
arg0.accept(codegen, data).coerceToBoolean().jumpIfFalse(target)
arg1.accept(codegen, data).coerceToBoolean().jumpIfFalse(target)
}
override fun jumpIfTrue(target: Label) {
val stayLabel = Label()
arg0.accept(codegen, data).coerceToBoolean().jumpIfFalse(stayLabel)
arg1.accept(codegen, data).coerceToBoolean().jumpIfTrue(target)
mv.visitLabel(stayLabel)
}
override fun discard() {
val end = Label()
arg0.accept(codegen, data).coerceToBoolean().jumpIfFalse(end)
arg1.accept(codegen, data).discard()
mv.visitLabel(end)
}
}
override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue? {
val (left, right) = expression.receiverAndArgs()
return BooleanConjunction(left, right, codegen, data)
}
}
@@ -0,0 +1,34 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.backend.jvm.intrinsics
import org.jetbrains.kotlin.backend.jvm.codegen.*
import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.org.objectweb.asm.Type
object ArrayGet : IntrinsicMethod() {
override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue? {
val dispatchReceiver = expression.dispatchReceiver!!
val receiver = dispatchReceiver.accept(codegen, data).materializedAt(dispatchReceiver.type)
val elementType = AsmUtil.correctElementType(receiver.type)
expression.getValueArgument(0)!!.accept(codegen, data)
.materializeAt(Type.INT_TYPE, codegen.context.irBuiltIns.intType)
codegen.mv.aload(elementType)
return MaterialValue(codegen, elementType, expression.type)
}
}
@@ -0,0 +1,43 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.backend.jvm.intrinsics
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.mapping.mapClass
import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
object ArrayIterator : IntrinsicMethod() {
override fun toCallable(
expression: IrFunctionAccessExpression,
signature: JvmMethodSignature,
context: JvmBackendContext
): IrIntrinsicFunction {
val owner = context.typeMapper.mapClass(expression.symbol.owner.parentAsClass)
return IrIntrinsicFunction.create(expression, signature, context, owner) {
val methodSignature = "(${owner.descriptor})${signature.returnType.descriptor}"
val intrinsicOwner =
if (AsmUtil.isPrimitive(owner.elementType))
"kotlin/jvm/internal/ArrayIteratorsKt"
else
"kotlin/jvm/internal/ArrayIteratorKt"
it.invokestatic(intrinsicOwner, "iterator", methodSignature, false)
}
}
}
@@ -0,0 +1,36 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.backend.jvm.intrinsics
import org.jetbrains.kotlin.backend.jvm.codegen.*
import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.ir.types.getArrayElementType
import org.jetbrains.org.objectweb.asm.Type
object ArraySet : IntrinsicMethod() {
override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue? {
val dispatchReceiver = expression.dispatchReceiver!!
val receiver = dispatchReceiver.accept(codegen, data).materializedAt(dispatchReceiver.type)
val elementType = AsmUtil.correctElementType(receiver.type)
val elementIrType = receiver.irType.getArrayElementType(codegen.context.irBuiltIns)
expression.getValueArgument(0)!!.accept(codegen, data).materializeAt(Type.INT_TYPE, codegen.context.irBuiltIns.intType)
expression.getValueArgument(1)!!.accept(codegen, data).materializeAt(elementType, elementIrType)
codegen.mv.astore(elementType)
return codegen.unitValue
}
}
@@ -0,0 +1,32 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.backend.jvm.intrinsics
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpression
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
object ArraySize : IntrinsicMethod() {
override fun toCallable(expression: IrFunctionAccessExpression, signature: JvmMethodSignature, context: JvmBackendContext): IrIntrinsicFunction {
return IrIntrinsicFunction.create(expression, signature, context) {
it.arraylength()
}
}
}
@@ -0,0 +1,52 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.backend.jvm.intrinsics
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.codegen.AsmUtil.numberFunctionOperandType
import org.jetbrains.kotlin.codegen.StackValue
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.ir.types.isChar
import org.jetbrains.kotlin.ir.util.defaultType
import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.org.objectweb.asm.Opcodes.*
import org.jetbrains.org.objectweb.asm.Type
class BinaryOp(private val opcode: Int) : IntrinsicMethod() {
private fun shift(): Boolean =
opcode == ISHL || opcode == ISHR || opcode == IUSHR
override fun toCallable(
expression: IrFunctionAccessExpression,
signature: JvmMethodSignature,
context: JvmBackendContext
): IrIntrinsicFunction {
val returnType = signature.returnType
val intermediateResultType = numberFunctionOperandType(returnType)
val argTypes = if (!expression.symbol.owner.parentAsClass.defaultType.isChar()) {
listOf(intermediateResultType, if (shift()) Type.INT_TYPE else intermediateResultType)
} else {
listOf(Type.CHAR_TYPE, signature.valueParameters[0].asmType)
}
return IrIntrinsicFunction.create(expression, signature, context, argTypes) {
it.visitInsn(returnType.getOpcode(opcode))
StackValue.coerce(intermediateResultType, returnType, it)
}
}
}
@@ -0,0 +1,46 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.backend.jvm.intrinsics
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.Type
object Clone : IntrinsicMethod() {
private val CLONEABLE_TYPE = Type.getObjectType("java/lang/Cloneable")
override fun toCallable(
expression: IrFunctionAccessExpression,
signature: JvmMethodSignature,
context: JvmBackendContext
): IrIntrinsicFunction {
val isSuperCall = expression is IrCall && expression.superQualifierSymbol != null
val opcode = if (isSuperCall) Opcodes.INVOKESPECIAL else Opcodes.INVOKEVIRTUAL
val newSignature = signature.newReturnType(AsmTypes.OBJECT_TYPE)
val argTypes0 = expression.argTypes(context)
// Don't upcast receiver to java.lang.Cloneable, since 'clone' is protected in java.lang.Object.
val argTypes = if (isSuperCall || argTypes0[0] == CLONEABLE_TYPE) listOf(AsmTypes.OBJECT_TYPE) else argTypes0
return IrIntrinsicFunction.create(expression, newSignature, context, argTypes) { mv ->
mv.visitMethodInsn(opcode, "java/lang/Object", "clone", "()Ljava/lang/Object;", false)
}
}
}
@@ -0,0 +1,211 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.backend.jvm.intrinsics
import com.intellij.psi.tree.IElementType
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.JvmSymbols
import org.jetbrains.kotlin.backend.jvm.codegen.*
import org.jetbrains.kotlin.backend.jvm.ir.isSmartcastFromHigherThanNullable
import org.jetbrains.kotlin.backend.jvm.ir.receiverAndArgs
import org.jetbrains.kotlin.builtins.PrimitiveType
import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.codegen.AsmUtil.comparisonOperandType
import org.jetbrains.kotlin.codegen.BranchedValue
import org.jetbrains.kotlin.codegen.NumberCompare
import org.jetbrains.kotlin.codegen.ObjectCompare
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.lexer.KtSingleValueToken
import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
object CompareTo : IntrinsicMethod() {
private fun genInvoke(type: Type?, v: InstructionAdapter) {
when (type) {
Type.CHAR_TYPE, Type.BYTE_TYPE, Type.SHORT_TYPE, Type.INT_TYPE ->
v.invokestatic(
JvmSymbols.INTRINSICS_CLASS_NAME,
"compare",
"(II)I",
false
)
Type.LONG_TYPE -> v.invokestatic(JvmSymbols.INTRINSICS_CLASS_NAME, "compare", "(JJ)I", false)
Type.FLOAT_TYPE -> v.invokestatic("java/lang/Float", "compare", "(FF)I", false)
Type.DOUBLE_TYPE -> v.invokestatic("java/lang/Double", "compare", "(DD)I", false)
else -> throw UnsupportedOperationException()
}
}
override fun toCallable(
expression: IrFunctionAccessExpression,
signature: JvmMethodSignature,
context: JvmBackendContext
): IrIntrinsicFunction {
val parameterType = comparisonOperandType(
expressionType(expression.dispatchReceiver ?: expression.extensionReceiver!!, context),
signature.valueParameters.single().asmType
)
return IrIntrinsicFunction.create(expression, signature, context, listOf(parameterType, parameterType)) {
genInvoke(parameterType, it)
}
}
}
class IntegerZeroComparison(val op: IElementType, val a: MaterialValue) : BooleanValue(a.codegen) {
override fun jumpIfFalse(target: Label) {
mv.visitJumpInsn(Opcodes.IFNE, target)
}
override fun jumpIfTrue(target: Label) {
mv.visitJumpInsn(Opcodes.IFEQ, target)
}
override fun discard() {
a.discard()
}
}
class BooleanComparison(val op: IElementType, val a: MaterialValue, val b: MaterialValue) : BooleanValue(a.codegen) {
override fun jumpIfFalse(target: Label) {
// TODO 1. get rid of the dependency; 2. take `b.type` into account.
val opcode = if (a.type.sort == Type.OBJECT)
ObjectCompare.getObjectCompareOpcode(op)
else
NumberCompare.patchOpcode(NumberCompare.getNumberCompareOpcode(op), mv, op, a.type)
mv.visitJumpInsn(opcode, target)
}
override fun jumpIfTrue(target: Label) {
val opcode = if (a.type.sort == Type.OBJECT)
BranchedValue.negatedOperations[ObjectCompare.getObjectCompareOpcode(op)]!!
else
NumberCompare.patchOpcode(BranchedValue.negatedOperations[NumberCompare.getNumberCompareOpcode(op)]!!, mv, op, a.type)
mv.visitJumpInsn(opcode, target)
}
override fun discard() {
b.discard()
a.discard()
}
}
class NonIEEE754FloatComparison(val op: IElementType, val a: MaterialValue, val b: MaterialValue) : BooleanValue(a.codegen) {
private val numberCompareOpcode = NumberCompare.getNumberCompareOpcode(op)
private fun invokeStaticComparison(type: Type) {
when (type) {
Type.FLOAT_TYPE -> mv.invokestatic("java/lang/Float", "compare", "(FF)I", false)
Type.DOUBLE_TYPE -> mv.invokestatic("java/lang/Double", "compare", "(DD)I", false)
else -> throw UnsupportedOperationException()
}
}
override fun jumpIfFalse(target: Label) {
invokeStaticComparison(a.type)
mv.visitJumpInsn(numberCompareOpcode, target)
}
override fun jumpIfTrue(target: Label) {
invokeStaticComparison(a.type)
mv.visitJumpInsn(BranchedValue.negatedOperations[numberCompareOpcode]!!, target)
}
override fun discard() {
b.discard()
a.discard()
}
}
class PrimitiveToObjectComparison(
private val op: IElementType,
private val leftIsPrimitive: Boolean,
private val left: MaterialValue,
private val right: MaterialValue
) : BooleanValue(left.codegen) {
private fun checkTypeAndCompare(onWrongType: Label): BooleanValue {
val compareLabel = Label()
// If it's the left value that needs unboxing, it should be moved to the top of the stack. `AsmUtil.swap`
// is theoretically OK, but in practice breaks peephole optimization passes that unbox longs/doubles,
// so just storing in a variable is safer.
val tmp = if (leftIsPrimitive) -1 else codegen.frameMap.enterTemp(right.type).also { mv.store(it, right.type) }
mv.dup()
if (AsmUtil.isBoxedPrimitiveType(if (leftIsPrimitive) right.type else left.type)) {
mv.ifnonnull(compareLabel)
} else {
mv.instanceOf(AsmUtil.boxType(if (leftIsPrimitive) left.type else right.type))
mv.ifne(compareLabel)
}
// Type checking of the object failed, values are irrelevant now:
if (leftIsPrimitive) right.discard() // else it's already popped by `mv.store`
left.discard()
mv.goTo(onWrongType)
mv.mark(compareLabel)
// Type checking OK, can unbox and compare:
return if (leftIsPrimitive) {
BooleanComparison(op, left, right.materializedAt(left.type, right.irType))
} else {
val leftUnboxed = left.materializedAt(right.type, left.irType)
mv.load(tmp, right.type)
codegen.frameMap.leaveTemp(right.type)
BooleanComparison(op, leftUnboxed, right)
}
}
override fun jumpIfFalse(target: Label) {
checkTypeAndCompare(target).jumpIfFalse(target)
}
override fun jumpIfTrue(target: Label) {
val wrongType = Label()
checkTypeAndCompare(wrongType).jumpIfTrue(target)
mv.mark(wrongType)
}
override fun discard() {
right.discard()
left.discard()
}
}
class PrimitiveComparison(
private val primitiveNumberType: PrimitiveType,
private val operatorToken: KtSingleValueToken
) : IntrinsicMethod() {
override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue? {
val parameterType = Type.getType(JvmPrimitiveType.get(primitiveNumberType).desc)
val (left, right) = expression.receiverAndArgs()
val a = left.accept(codegen, data).materializedAt(parameterType, left.type)
val b = right.accept(codegen, data).materializedAt(parameterType, right.type)
val useNonIEEE754Comparison =
!codegen.context.state.languageVersionSettings.supportsFeature(LanguageFeature.ProperIeee754Comparisons)
&& (parameterType == Type.FLOAT_TYPE || parameterType == Type.DOUBLE_TYPE)
&& (left.isSmartcastFromHigherThanNullable(codegen.context) || right.isSmartcastFromHigherThanNullable(codegen.context))
return if (useNonIEEE754Comparison) {
NonIEEE754FloatComparison(operatorToken, a, b)
} else {
BooleanComparison(operatorToken, a, b)
}
}
}
@@ -0,0 +1,70 @@
/*
* 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.intrinsics
import org.jetbrains.kotlin.backend.jvm.codegen.BlockInfo
import org.jetbrains.kotlin.backend.jvm.codegen.ExpressionCodegen
import org.jetbrains.kotlin.backend.jvm.codegen.MaterialValue
import org.jetbrains.kotlin.backend.jvm.codegen.materializedAt
import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.codegen.inline.ReifiedTypeInliner
import org.jetbrains.kotlin.codegen.putReifiedOperationMarkerIfTypeIsReifiedParameter
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.org.objectweb.asm.Type
object EnumValueOf : IntrinsicMethod() {
override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo) = with(codegen) {
val type = expression.getTypeArgument(0)!!
val result = expression.getValueArgument(0)!!.accept(this, data)
.materializedAt(AsmTypes.JAVA_STRING_TYPE, codegen.context.irBuiltIns.stringType)
if (type.isReifiedTypeParameter) {
// Note that the inliner expects exactly the following sequence of instructions.
// <REIFIED-OPERATIONS-MARKER>
// ACONST_NULL
// ALOAD n
// INVOKESTATIC java/lang/Enum.valueOf...
val temporary = frameMap.enterTemp(result.type)
mv.store(temporary, result.type)
putReifiedOperationMarkerIfTypeIsReifiedParameter(type, ReifiedTypeInliner.OperationKind.ENUM_REIFIED)
mv.aconst(null)
mv.load(temporary, result.type)
val descriptor = Type.getMethodDescriptor(AsmTypes.ENUM_TYPE, AsmTypes.JAVA_CLASS_TYPE, AsmTypes.JAVA_STRING_TYPE)
mv.invokestatic("java/lang/Enum", "valueOf", descriptor, false)
frameMap.leaveTemp(result.type)
MaterialValue(codegen, AsmTypes.ENUM_TYPE, expression.type)
} else {
val returnType = typeMapper.mapType(type)
val descriptor = Type.getMethodDescriptor(returnType, AsmTypes.JAVA_STRING_TYPE)
mv.invokestatic(returnType.internalName, "valueOf", descriptor, false)
MaterialValue(codegen, returnType, expression.type)
}
}
}
object EnumValues : IntrinsicMethod() {
override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo) = with(codegen) {
val type = expression.getTypeArgument(0)!!
if (type.isReifiedTypeParameter) {
// Note that the inliner expects exactly the following sequence of instructions.
// <REIFIED-OPERATIONS-MARKER>
// ICONST_0
// ANEWARRAY Ljava/lang/Enum;
putReifiedOperationMarkerIfTypeIsReifiedParameter(type, ReifiedTypeInliner.OperationKind.ENUM_REIFIED)
mv.iconst(0)
mv.newarray(AsmTypes.ENUM_TYPE)
MaterialValue(codegen, ENUM_ARRAY_TYPE, expression.type)
} else {
val enumType = typeMapper.mapType(type)
val enumArrayType = AsmUtil.getArrayType(enumType)
val descriptor = Type.getMethodDescriptor(enumArrayType)
mv.invokestatic(enumType.internalName, "values", descriptor, false)
MaterialValue(codegen, enumArrayType, expression.type)
}
}
private val ENUM_ARRAY_TYPE = AsmUtil.getArrayType(AsmTypes.ENUM_TYPE)
}
@@ -0,0 +1,186 @@
/*
* Copyright 2010-2018 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.intrinsics
import com.intellij.psi.tree.IElementType
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.codegen.*
import org.jetbrains.kotlin.backend.jvm.ir.isSmartcastFromHigherThanNullable
import org.jetbrains.kotlin.backend.jvm.ir.receiverAndArgs
import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.codegen.AsmUtil.isPrimitive
import org.jetbrains.kotlin.codegen.DescriptorAsmUtil.genAreEqualCall
import org.jetbrains.kotlin.codegen.StackValue
import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.ir.descriptors.toIrBasedKotlinType
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
import org.jetbrains.kotlin.ir.types.classOrNull
import org.jetbrains.kotlin.ir.types.isNullable
import org.jetbrains.kotlin.ir.util.isEnumClass
import org.jetbrains.kotlin.ir.util.isEnumEntry
import org.jetbrains.kotlin.ir.util.isIntegerConst
import org.jetbrains.kotlin.ir.util.isNullConst
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.kotlin.types.isNullable
import org.jetbrains.kotlin.types.typeUtil.isPrimitiveNumberOrNullableType
import org.jetbrains.kotlin.types.typeUtil.upperBoundedByPrimitiveNumberOrNullableType
import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
class ExplicitEquals : IntrinsicMethod() {
override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue? {
val (a, b) = expression.receiverAndArgs()
// TODO use specialized boxed type - this might require types like 'java.lang.Integer' in IR
a.accept(codegen, data).materializeAt(AsmTypes.OBJECT_TYPE, codegen.context.irBuiltIns.anyNType)
b.accept(codegen, data).materializeAt(AsmTypes.OBJECT_TYPE, codegen.context.irBuiltIns.anyNType)
codegen.mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL,
AsmTypes.OBJECT_TYPE.internalName,
"equals",
Type.getMethodDescriptor(Type.BOOLEAN_TYPE, AsmTypes.OBJECT_TYPE),
false
)
return MaterialValue(codegen, Type.BOOLEAN_TYPE, codegen.context.irBuiltIns.booleanType)
}
}
class Equals(val operator: IElementType) : IntrinsicMethod() {
private class BooleanNullCheck(val value: PromisedValue) : BooleanValue(value.codegen) {
override fun jumpIfFalse(target: Label) = value.materialize().also { mv.ifnonnull(target) }
override fun jumpIfTrue(target: Label) = value.materialize().also { mv.ifnull(target) }
override fun discard() {
value.discard()
}
}
override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue? {
val (a, b) = expression.receiverAndArgs()
if (a.isNullConst() || b.isNullConst()) {
val irValue = if (a.isNullConst()) b else a
val value = irValue.accept(codegen, data)
return if (!isPrimitive(value.type) && (irValue.type.classOrNull?.owner?.isInline != true || irValue.type.isNullable()))
BooleanNullCheck(value)
else {
value.discard()
BooleanConstant(codegen, false)
}
}
val leftType = with(codegen) { a.asmType }
val rightType = with(codegen) { b.asmType }
val opToken = expression.origin
// Avoid boxing for `primitive == object` and `boxed primitive == primitive` where we know
// what comparison means. The optimization does not apply to `object == primitive` as equals
// could be overridden for the object.
if ((opToken == IrStatementOrigin.EQEQ || opToken == IrStatementOrigin.EXCLEQ) &&
((AsmUtil.isIntOrLongPrimitive(leftType) && !isPrimitive(rightType)) ||
(AsmUtil.isIntOrLongPrimitive(rightType) && AsmUtil.isBoxedPrimitiveType(leftType)))
) {
val aValue = a.accept(codegen, data).materializedAt(leftType, a.type)
val bValue = b.accept(codegen, data).materializedAt(rightType, b.type)
return PrimitiveToObjectComparison(operator, AsmUtil.isIntOrLongPrimitive(leftType), aValue, bValue)
}
val aIsEnum = a.type.classOrNull?.owner?.run { isEnumClass || isEnumEntry } == true
val bIsEnum = b.type.classOrNull?.owner?.run { isEnumClass || isEnumEntry } == true
val useEquals = opToken !== IrStatementOrigin.EQEQEQ && opToken !== IrStatementOrigin.EXCLEQEQ &&
// `==` is `equals` for objects and floating-point numbers. In the latter case, the difference
// is that `equals` is a total order (-0 < +0 and NaN == NaN) and `===` is IEEE754-compliant.
(!isPrimitive(leftType) || leftType != rightType || leftType == Type.FLOAT_TYPE || leftType == Type.DOUBLE_TYPE)
// Reference equality can be used for enums.
&& !aIsEnum && !bIsEnum
return if (useEquals) {
a.accept(codegen, data).materializeAt(AsmTypes.OBJECT_TYPE, codegen.context.irBuiltIns.anyNType)
b.accept(codegen, data).materializeAt(AsmTypes.OBJECT_TYPE, codegen.context.irBuiltIns.anyNType)
genAreEqualCall(codegen.mv)
MaterialValue(codegen, Type.BOOLEAN_TYPE, codegen.context.irBuiltIns.booleanType)
} else {
val operandType = if (!isPrimitive(leftType)) AsmTypes.OBJECT_TYPE else leftType
if (operandType == Type.INT_TYPE && (a.isIntegerConst(0) || b.isIntegerConst(0))) {
val nonZero = if (a.isIntegerConst(0)) b else a
IntegerZeroComparison(operator, nonZero.accept(codegen, data).materializedAt(operandType, nonZero.type))
} else {
val aValue = a.accept(codegen, data).materializedAt(operandType, a.type)
val bValue = b.accept(codegen, data).materializedAt(operandType, b.type)
BooleanComparison(operator, aValue, bValue)
}
}
}
}
class Ieee754Equals(val operandType: Type) : IntrinsicMethod() {
private val boxedOperandType = AsmUtil.boxType(operandType)
override fun toCallable(
expression: IrFunctionAccessExpression,
signature: JvmMethodSignature,
context: JvmBackendContext
): IrIntrinsicFunction {
class Ieee754AreEqual(
val left: Type,
val right: Type
) : IrIntrinsicFunction(expression, signature, context, listOf(left, right)) {
override fun genInvokeInstruction(v: InstructionAdapter) {
v.invokestatic(
IntrinsicMethods.INTRINSICS_CLASS_NAME, "areEqual",
Type.getMethodDescriptor(Type.BOOLEAN_TYPE, left, right),
false
)
}
}
val arg0 = expression.getValueArgument(0)!!
val arg1 = expression.getValueArgument(1)!!
val arg0Type = arg0.type.toIrBasedKotlinType()
if (!arg0Type.isPrimitiveNumberOrNullableType() && !arg0Type.upperBoundedByPrimitiveNumberOrNullableType())
throw AssertionError("Should be primitive or nullable primitive type: $arg0Type")
val arg1Type = arg1.type.toIrBasedKotlinType()
if (!arg1Type.isPrimitiveNumberOrNullableType() && !arg1Type.upperBoundedByPrimitiveNumberOrNullableType())
throw AssertionError("Should be primitive or nullable primitive type: $arg1Type")
val arg0isNullable = arg0Type.isNullable()
val arg1isNullable = arg1Type.isNullable()
val useNonIEEE754Comparison =
!context.state.languageVersionSettings.supportsFeature(LanguageFeature.ProperIeee754Comparisons)
&& (arg0.isSmartcastFromHigherThanNullable(context) || arg1.isSmartcastFromHigherThanNullable(context))
return when {
useNonIEEE754Comparison ->
Ieee754AreEqual(AsmTypes.OBJECT_TYPE, AsmTypes.OBJECT_TYPE)
!arg0isNullable && !arg1isNullable ->
object : IrIntrinsicFunction(expression, signature, context, listOf(operandType, operandType)) {
override fun genInvokeInstruction(v: InstructionAdapter) {
StackValue.cmp(KtTokens.EQEQ, operandType, StackValue.onStack(operandType), StackValue.onStack(operandType))
.put(Type.BOOLEAN_TYPE, v)
}
}
arg0isNullable && !arg1isNullable ->
Ieee754AreEqual(boxedOperandType, operandType)
!arg0isNullable && arg1isNullable ->
Ieee754AreEqual(operandType, boxedOperandType)
else ->
Ieee754AreEqual(boxedOperandType, boxedOperandType)
}
}
}
@@ -0,0 +1,68 @@
/*
* 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.intrinsics
import org.jetbrains.kotlin.backend.jvm.codegen.BlockInfo
import org.jetbrains.kotlin.backend.jvm.codegen.ExpressionCodegen
import org.jetbrains.kotlin.backend.jvm.codegen.PromisedValue
import org.jetbrains.kotlin.backend.jvm.codegen.materialize
import org.jetbrains.kotlin.backend.jvm.mapping.mapTypeAsDeclaration
import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.codegen.inline.ReifiedTypeInliner
import org.jetbrains.kotlin.codegen.putReifiedOperationMarkerIfTypeIsReifiedParameter
import org.jetbrains.kotlin.ir.expressions.IrClassReference
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.ir.expressions.IrGetClass
import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.org.objectweb.asm.Type
object GetJavaObjectType : IntrinsicMethod() {
override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue? =
when (val receiver = expression.extensionReceiver) {
is IrClassReference -> {
val symbol = receiver.symbol
if (symbol is IrTypeParameterSymbol) {
val success = codegen.putReifiedOperationMarkerIfTypeIsReifiedParameter(
receiver.classType,
ReifiedTypeInliner.OperationKind.JAVA_CLASS
)
assert(success) {
"Non-reified type parameter under ::class should be rejected by type checker: ${receiver.render()}"
}
}
codegen.mv.aconst(AsmUtil.boxType(codegen.typeMapper.mapTypeAsDeclaration(receiver.classType)))
with(codegen) { expression.onStack }
}
is IrGetClass -> {
val argumentValue = receiver.argument.accept(codegen, data)
argumentValue.materialize()
val argumentType = argumentValue.type
when {
argumentType == Type.VOID_TYPE ->
codegen.mv.aconst(AsmTypes.UNIT_TYPE)
AsmUtil.isPrimitive(argumentType) ||
AsmUtil.unboxPrimitiveTypeOrNull(argumentType) != null -> {
AsmUtil.pop(codegen.mv, argumentType)
codegen.mv.aconst(AsmUtil.boxType(argumentType))
}
else ->
codegen.mv.invokevirtual("java/lang/Object", "getClass", "()Ljava/lang/Class;", false)
}
with(codegen) { expression.onStack }
}
else ->
null
}
}
@@ -0,0 +1,84 @@
/*
* 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.intrinsics
import org.jetbrains.kotlin.backend.jvm.codegen.BlockInfo
import org.jetbrains.kotlin.backend.jvm.codegen.ExpressionCodegen
import org.jetbrains.kotlin.backend.jvm.codegen.PromisedValue
import org.jetbrains.kotlin.backend.jvm.codegen.materialize
import org.jetbrains.kotlin.backend.jvm.mapping.mapTypeAsDeclaration
import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.ir.expressions.IrClassReference
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.ir.expressions.IrGetClass
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.classOrNull
import org.jetbrains.kotlin.ir.types.isNullablePrimitiveType
import org.jetbrains.kotlin.ir.types.isPrimitiveType
import org.jetbrains.kotlin.ir.util.isTypeParameter
import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.org.objectweb.asm.Type
object GetJavaPrimitiveType : IntrinsicMethod() {
override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue? {
val receiver = expression.extensionReceiver ?: return null
val argumentType =
when (receiver) {
is IrGetClass -> receiver.argument.type
is IrClassReference -> receiver.classType
else -> return null
}
if (argumentType.isTypeParameter()) return null
val argumentAsmType = codegen.typeMapper.mapTypeAsDeclaration(argumentType)
val isPrimitiveTypeOrWrapper =
argumentType.isPrimitiveType() ||
argumentType.isNullablePrimitiveType() ||
!argumentType.isInlineClassType() && argumentAsmType.isVoidOrPrimitiveWrapper()
return when (receiver) {
is IrGetClass -> {
if (!isPrimitiveTypeOrWrapper) return null
val argumentValue = receiver.argument.accept(codegen, data)
argumentValue.materialize()
AsmUtil.pop(codegen.mv, argumentValue.type)
putPrimitiveType(codegen, argumentAsmType)
with(codegen) { expression.onStack }
}
is IrClassReference -> {
if (!isPrimitiveTypeOrWrapper) {
codegen.mv.aconst(null)
} else {
putPrimitiveType(codegen, argumentAsmType)
}
with(codegen) { expression.onStack }
}
else ->
throw AssertionError("IrGetClass or IrClassReference expected: ${receiver.render()}")
}
}
private fun putPrimitiveType(codegen: ExpressionCodegen, type: Type) {
codegen.mv.getstatic(AsmUtil.boxType(type).internalName, "TYPE", "Ljava/lang/Class;")
}
private fun IrType.isInlineClassType(): Boolean {
return (classOrNull ?: return false).owner.isInline
}
private fun Type.isVoidOrPrimitiveWrapper(): Boolean =
this == AsmTypes.VOID_WRAPPER_TYPE || AsmUtil.unboxPrimitiveTypeOrNull(this) != null
}
@@ -0,0 +1,64 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.backend.jvm.intrinsics
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
import org.jetbrains.kotlin.backend.jvm.codegen.*
import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.codegen.DescriptorAsmUtil
import org.jetbrains.kotlin.config.JvmTarget
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.Type
object HashCode : IntrinsicMethod() {
override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo) = with(codegen) {
val receiver = expression.dispatchReceiver ?: error("No receiver for hashCode: ${expression.render()}")
val receiverIrType = receiver.type
val receiverJvmType = typeMapper.mapType(receiverIrType)
val receiverValue = receiver.accept(this, data).materialized()
val receiverType = receiverValue.type
val target = context.state.target
when {
irFunction.origin == JvmLoweredDeclarationOrigin.INLINE_CLASS_GENERATED_IMPL_METHOD ||
irFunction.origin == IrDeclarationOrigin.GENERATED_DATA_CLASS_MEMBER -> {
// TODO generate or lower IR for data class / inline class 'hashCode'?
DescriptorAsmUtil.genHashCode(mv, mv, receiverType, target)
}
target >= JvmTarget.JVM_1_8 && AsmUtil.isPrimitive(receiverJvmType) -> {
val boxedType = AsmUtil.boxPrimitiveType(receiverJvmType)
?: throw AssertionError("Primitive type expected: $receiverJvmType")
receiverValue.materializeAt(receiverJvmType, receiverIrType)
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
boxedType.internalName,
"hashCode",
Type.getMethodDescriptor(Type.INT_TYPE, receiverJvmType),
false
)
}
else -> {
receiverValue.materializeAtBoxed(receiverIrType)
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Object", "hashCode", "()I", false)
}
}
MaterialValue(codegen, Type.INT_TYPE, codegen.context.irBuiltIns.intType)
}
}
@@ -0,0 +1,30 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.backend.jvm.intrinsics
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.codegen.DescriptorAsmUtil.genIncrement
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
class Increment(private val myDelta: Int) : IntrinsicMethod() {
override fun toCallable(expression: IrFunctionAccessExpression, signature: JvmMethodSignature, context: JvmBackendContext): IrIntrinsicFunction {
return IrIntrinsicFunction.create(expression, signature, context) {
genIncrement(signature.returnType, myDelta, it)
}
}
}
@@ -0,0 +1,53 @@
/*
* Copyright 2010-2018 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.intrinsics
import org.jetbrains.kotlin.backend.jvm.IntrinsicMarker
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.codegen.BlockInfo
import org.jetbrains.kotlin.backend.jvm.codegen.ExpressionCodegen
import org.jetbrains.kotlin.backend.jvm.codegen.MaterialValue
import org.jetbrains.kotlin.backend.jvm.codegen.PromisedValue
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpression
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.Method
abstract class IntrinsicMethod : IntrinsicMarker {
open fun toCallable(
expression: IrFunctionAccessExpression,
signature: JvmMethodSignature,
context: JvmBackendContext
): IrIntrinsicFunction = TODO("implement toCallable() or invoke() of $this")
open fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue? =
with(codegen) {
val descriptor = methodSignatureMapper.mapSignatureSkipGeneric(expression.symbol.owner)
val stackValue = toCallable(expression, descriptor, context).invoke(mv, codegen, data, expression)
stackValue.put(mv)
return MaterialValue(this, stackValue.type, expression.type)
}
companion object {
fun calcReceiverType(call: IrMemberAccessExpression<*>, context: JvmBackendContext): Type {
return context.typeMapper.mapType(call.dispatchReceiver?.type ?: call.extensionReceiver!!.type)
}
fun expressionType(expression: IrExpression, context: JvmBackendContext): Type {
return context.typeMapper.mapType(expression.type)
}
fun JvmMethodSignature.newReturnType(type: Type): JvmMethodSignature {
val newMethod = with(asmMethod) {
Method(name, type, argumentTypes)
}
return JvmMethodSignature(newMethod, valueParameters)
}
}
}
@@ -0,0 +1,21 @@
/*
* Copyright 2010-2021 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.intrinsics
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
object IntrinsicShouldHaveBeenLowered : IntrinsicMethod() {
override fun toCallable(
expression: IrFunctionAccessExpression,
signature: JvmMethodSignature,
context: JvmBackendContext
): IrIntrinsicFunction {
error("Intrinsic should have been lowered: '${expression.symbol.owner.render()}'")
}
}
@@ -0,0 +1,41 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.backend.jvm.intrinsics
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.codegen.AsmUtil.numberFunctionOperandType
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpression
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.org.objectweb.asm.Type
object Inv : IntrinsicMethod() {
/*TODO new this type*/
override fun toCallable(expression: IrFunctionAccessExpression, signature: JvmMethodSignature, context: JvmBackendContext): IrIntrinsicFunction {
val returnType = signature.returnType
val type = numberFunctionOperandType(returnType)
return IrIntrinsicFunction.create(expression, signature, context, type) {
if (returnType == Type.LONG_TYPE) {
it.lconst(-1)
}
else {
it.iconst(-1)
}
it.xor(returnType)
}
}
}
@@ -0,0 +1,40 @@
/*
* Copyright 2010-2019 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.intrinsics
import org.jetbrains.kotlin.backend.jvm.codegen.*
import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.org.objectweb.asm.Type
object IrCheckNotNull : IntrinsicMethod() {
override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue? {
val arg0 = expression.getValueArgument(0)!!.accept(codegen, data)
if (AsmUtil.isPrimitive(arg0.type)) return arg0
return object : PromisedValue(codegen, arg0.type, arg0.irType) {
override fun materializeAt(target: Type, irTarget: IrType, castForReified: Boolean) =
arg0.materialized().also { codegen.checkTopValueForNull() }.materializeAt(target, irTarget, castForReified)
override fun discard() =
arg0.materialized().also { codegen.checkTopValueForNull() }.discard()
}
}
private fun ExpressionCodegen.checkTopValueForNull() {
mv.dup()
if (state.unifiedNullChecks) {
mv.invokestatic(IntrinsicMethods.INTRINSICS_CLASS_NAME, "checkNotNull", "(Ljava/lang/Object;)V", false)
} else {
val ifNonNullLabel = Label()
mv.ifnonnull(ifNonNullLabel)
mv.invokestatic(IntrinsicMethods.INTRINSICS_CLASS_NAME, "throwNpe", "()V", false)
mv.mark(ifNonNullLabel)
}
}
}
@@ -0,0 +1,24 @@
/*
* Copyright 2019 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.intrinsics
import org.jetbrains.kotlin.backend.jvm.codegen.BlockInfo
import org.jetbrains.kotlin.backend.jvm.codegen.ExpressionCodegen
import org.jetbrains.kotlin.backend.jvm.codegen.PromisedValue
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.ir.util.isPrimitiveArray
object IrDataClassArrayMemberHashCode : IntrinsicMethod() {
override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue? =
with(codegen) {
val arrayType = expression.getValueArgument(0)!!.type
val asmArrayType = context.typeMapper.mapType(arrayType)
gen(expression.getValueArgument(0)!!, asmArrayType, arrayType, data)
val hashCodeArgumentDescriptor = if (arrayType.isPrimitiveArray()) asmArrayType.descriptor else "[Ljava/lang/Object;"
mv.invokestatic("java/util/Arrays", "hashCode", "($hashCodeArgumentDescriptor)I", false)
return expression.onStack
}
}
@@ -0,0 +1,24 @@
/*
* Copyright 2019 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.intrinsics
import org.jetbrains.kotlin.backend.jvm.codegen.BlockInfo
import org.jetbrains.kotlin.backend.jvm.codegen.ExpressionCodegen
import org.jetbrains.kotlin.backend.jvm.codegen.PromisedValue
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.ir.util.isPrimitiveArray
object IrDataClassArrayMemberToString : IntrinsicMethod() {
override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue? =
with(codegen) {
val arrayType = expression.getValueArgument(0)!!.type
val asmArrayType = context.typeMapper.mapType(arrayType)
gen(expression.getValueArgument(0)!!, asmArrayType, arrayType, data)
val toStringArgumentDescriptor = if (arrayType.isPrimitiveArray()) asmArrayType.descriptor else "[Ljava/lang/Object;"
mv.invokestatic("java/util/Arrays", "toString", "($toStringArgumentDescriptor)Ljava/lang/String;", false)
return expression.onStack
}
}
@@ -0,0 +1,61 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.backend.jvm.intrinsics
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.codegen.BlockInfo
import org.jetbrains.kotlin.backend.jvm.codegen.ExpressionCodegen
import org.jetbrains.kotlin.codegen.StackValue
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.resolve.jvm.AsmTypes.JAVA_STRING_TYPE
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
object IrIllegalArgumentException : IntrinsicMethod() {
val exceptionTypeDescriptor = Type.getType(IllegalArgumentException::class.java)!!
override fun toCallable(
expression: IrFunctionAccessExpression,
signature: JvmMethodSignature,
context: JvmBackendContext
): IrIntrinsicFunction {
return object : IrIntrinsicFunction(expression, signature, context, listOf(JAVA_STRING_TYPE)) {
override fun genInvokeInstruction(v: InstructionAdapter) {
v.invokespecial(
exceptionTypeDescriptor.internalName,
"<init>",
Type.getMethodDescriptor(Type.VOID_TYPE, JAVA_STRING_TYPE),
false
)
v.athrow()
}
override fun invoke(
v: InstructionAdapter,
codegen: ExpressionCodegen,
data: BlockInfo,
expression: IrFunctionAccessExpression
): StackValue {
codegen.markLineNumber(expression)
v.anew(exceptionTypeDescriptor)
v.dup()
return super.invoke(v, codegen, data, expression)
}
}
}
}
@@ -0,0 +1,156 @@
/*
* Copyright 2010-2018 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.intrinsics
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.codegen.BlockInfo
import org.jetbrains.kotlin.backend.jvm.codegen.ExpressionCodegen
import org.jetbrains.kotlin.backend.jvm.mapping.mapClass
import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.codegen.Callable
import org.jetbrains.kotlin.codegen.StackValue
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.util.dump
import org.jetbrains.kotlin.ir.util.isVararg
import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.ir.util.substitute
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
open class IrIntrinsicFunction(
val expression: IrFunctionAccessExpression,
val signature: JvmMethodSignature,
val context: JvmBackendContext,
val argsTypes: List<Type> = expression.argTypes(context)
) : Callable {
override val owner: Type
get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates.
override val dispatchReceiverType: Type?
get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates.
override val dispatchReceiverKotlinType: KotlinType?
get() = null
override val extensionReceiverType: Type?
get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates.
override val extensionReceiverKotlinType: KotlinType?
get() = null
override val generateCalleeType: Type?
get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates.
override val valueParameterTypes: List<Type>
get() = signature.valueParameters.map { it.asmType }
override val parameterTypes: Array<Type>
get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates.
override val returnType: Type
get() = signature.returnType
override val returnKotlinType: KotlinType?
get() = null
override fun isStaticCall(): Boolean {
return false
}
override fun genInvokeInstruction(v: InstructionAdapter) {
TODO("not implemented for $this")
}
open fun genInvokeInstructionWithResult(v: InstructionAdapter): Type {
genInvokeInstruction(v)
return returnType
}
open fun invoke(
v: InstructionAdapter,
codegen: ExpressionCodegen,
data: BlockInfo,
expression: IrFunctionAccessExpression
): StackValue {
loadArguments(codegen, data)
codegen.markLineNumber(expression)
return StackValue.onStack(genInvokeInstructionWithResult(v))
}
private fun loadArguments(codegen: ExpressionCodegen, data: BlockInfo) {
var offset = 0
expression.dispatchReceiver?.let { genArg(it, codegen, offset++, data) }
expression.extensionReceiver?.let { genArg(it, codegen, offset++, data) }
for ((i, valueParameter) in expression.symbol.owner.valueParameters.withIndex()) {
val argument = expression.getValueArgument(i)
when {
argument != null ->
genArg(argument, codegen, i + offset, data)
valueParameter.isVararg -> {
// TODO: is there an easier way to get the substituted type of an empty vararg argument?
val arrayType = codegen.typeMapper.mapType(
valueParameter.type.substitute(expression.symbol.owner.typeParameters, expression.typeArguments)
)
StackValue.operation(arrayType) {
it.aconst(0)
it.newarray(AsmUtil.correctElementType(arrayType))
}.put(arrayType, codegen.mv)
}
else -> error("Unknown parameter ${valueParameter.name} in: ${expression.dump()}")
}
}
}
private fun genArg(expression: IrExpression, codegen: ExpressionCodegen, index: Int, data: BlockInfo) {
codegen.gen(expression, argsTypes[index], expression.type, data)
}
private val IrFunctionAccessExpression.typeArguments: List<IrType>
get() = (0 until typeArgumentsCount).map { getTypeArgument(it)!! }
companion object {
fun create(
expression: IrFunctionAccessExpression,
signature: JvmMethodSignature,
context: JvmBackendContext,
argsTypes: List<Type> = expression.argTypes(context),
invokeInstruction: IrIntrinsicFunction.(InstructionAdapter) -> Unit
): IrIntrinsicFunction {
return object : IrIntrinsicFunction(expression, signature, context, argsTypes) {
override fun genInvokeInstruction(v: InstructionAdapter) = invokeInstruction(v)
}
}
fun createWithResult(
expression: IrFunctionAccessExpression, signature: JvmMethodSignature,
context: JvmBackendContext,
argsTypes: List<Type> = expression.argTypes(context),
invokeInstruction: IrIntrinsicFunction.(InstructionAdapter) -> Type
): IrIntrinsicFunction {
return object : IrIntrinsicFunction(expression, signature, context, argsTypes) {
override fun genInvokeInstructionWithResult(v: InstructionAdapter) = invokeInstruction(v)
}
}
fun create(
expression: IrFunctionAccessExpression,
signature: JvmMethodSignature,
context: JvmBackendContext,
type: Type,
invokeInstruction: IrIntrinsicFunction.(InstructionAdapter) -> Unit
): IrIntrinsicFunction {
return create(expression, signature, context, listOf(type), invokeInstruction)
}
}
}
fun IrFunctionAccessExpression.argTypes(context: JvmBackendContext): ArrayList<Type> {
val callee = symbol.owner
val signature = context.methodSignatureMapper.mapSignatureSkipGeneric(callee)
return arrayListOf<Type>().apply {
if (dispatchReceiver != null) {
add(context.typeMapper.mapClass(callee.parentAsClass))
}
addAll(signature.asmMethod.argumentTypes)
}
}
@@ -0,0 +1,254 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.backend.jvm.intrinsics
import org.jetbrains.kotlin.backend.jvm.JvmSymbols
import org.jetbrains.kotlin.builtins.PrimitiveType
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.ir.IrBuiltIns
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrPackageFragment
import org.jetbrains.kotlin.ir.declarations.IrTypeParameter
import org.jetbrains.kotlin.ir.declarations.IrValueParameter
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
import org.jetbrains.kotlin.ir.types.classOrNull
import org.jetbrains.kotlin.ir.types.classifierOrFail
import org.jetbrains.kotlin.ir.types.classifierOrNull
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
import org.jetbrains.kotlin.ir.util.isFileClass
import org.jetbrains.kotlin.lexer.KtSingleValueToken
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.types.expressions.OperatorConventions
import org.jetbrains.kotlin.util.capitalizeDecapitalize.decapitalizeAsciiOnly
import org.jetbrains.org.objectweb.asm.Opcodes.*
import org.jetbrains.org.objectweb.asm.Type
class IrIntrinsicMethods(val irBuiltIns: IrBuiltIns, val symbols: JvmSymbols) {
private val kotlinFqn = StandardNames.BUILT_INS_PACKAGE_FQ_NAME
private val kotlinJvmFqn = FqName("kotlin.jvm")
private val kotlinJvmInternalUnsafeFqn = FqName("kotlin.jvm.internal.unsafe")
private val kotlinReflectFqn = StandardNames.KOTLIN_REFLECT_FQ_NAME
private val anyFqn = StandardNames.FqNames.any.toSafe()
private val arrayFqn = StandardNames.FqNames.array.toSafe()
private val cloneableFqn = StandardNames.FqNames.cloneable.toSafe()
private val intFqn = StandardNames.FqNames._int.toSafe()
private val kClassFqn = StandardNames.FqNames.kClass.toSafe()
private val stringFqn = StandardNames.FqNames.string.toSafe()
private val intrinsicsMap = (
listOf(
Key(kotlinJvmFqn, FqName("T"), "<get-javaClass>", emptyList()) to JavaClassProperty,
Key(kotlinJvmFqn, kClassFqn, "<get-javaObjectType>", emptyList()) to GetJavaObjectType,
Key(kotlinJvmFqn, kClassFqn, "<get-javaPrimitiveType>", emptyList()) to GetJavaPrimitiveType,
Key(kotlinJvmFqn, kClassFqn, "<get-java>", emptyList()) to KClassJavaProperty,
Key(kotlinJvmInternalUnsafeFqn, null, "monitorEnter", listOf(anyFqn)) to MonitorInstruction.MONITOR_ENTER,
Key(kotlinJvmInternalUnsafeFqn, null, "monitorExit", listOf(anyFqn)) to MonitorInstruction.MONITOR_EXIT,
Key(kotlinJvmFqn, arrayFqn, "isArrayOf", emptyList()) to IsArrayOf,
Key(kotlinFqn, null, "arrayOfNulls", listOf(intFqn)) to NewArray,
Key(cloneableFqn, null, "clone", emptyList()) to Clone,
Key(kotlinFqn, null, "enumValues", listOf()) to EnumValues,
Key(kotlinFqn, null, "enumValueOf", listOf(stringFqn)) to EnumValueOf,
Key(kotlinFqn, stringFqn, "plus", listOf(anyFqn)) to StringPlus,
Key(kotlinReflectFqn, null, "typeOf", listOf()) to TypeOf,
irBuiltIns.eqeqSymbol.toKey()!! to Equals(KtTokens.EQEQ),
irBuiltIns.eqeqeqSymbol.toKey()!! to Equals(KtTokens.EQEQEQ),
irBuiltIns.ieee754equalsFunByOperandType[irBuiltIns.floatClass]!!.toKey()!! to Ieee754Equals(Type.FLOAT_TYPE),
irBuiltIns.ieee754equalsFunByOperandType[irBuiltIns.doubleClass]!!.toKey()!! to Ieee754Equals(Type.DOUBLE_TYPE),
irBuiltIns.booleanNotSymbol.toKey()!! to Not,
irBuiltIns.noWhenBranchMatchedExceptionSymbol.toKey()!! to IrNoWhenBranchMatchedException,
irBuiltIns.illegalArgumentExceptionSymbol.toKey()!! to IrIllegalArgumentException,
irBuiltIns.checkNotNullSymbol.toKey()!! to IrCheckNotNull,
irBuiltIns.andandSymbol.toKey()!! to AndAnd,
irBuiltIns.ororSymbol.toKey()!! to OrOr,
irBuiltIns.dataClassArrayMemberHashCodeSymbol.toKey()!! to IrDataClassArrayMemberHashCode,
irBuiltIns.dataClassArrayMemberToStringSymbol.toKey()!! to IrDataClassArrayMemberToString,
symbols.unsafeCoerceIntrinsic.toKey()!! to UnsafeCoerce,
symbols.signatureStringIntrinsic.toKey()!! to SignatureString,
symbols.throwNullPointerException.toKey()!! to ThrowException(Type.getObjectType("java/lang/NullPointerException")),
symbols.throwTypeCastException.toKey()!! to ThrowException(Type.getObjectType("kotlin/TypeCastException")),
symbols.throwUnsupportedOperationException.toKey()!! to ThrowException(Type.getObjectType("java/lang/UnsupportedOperationException")),
symbols.throwIllegalAccessException.toKey()!! to ThrowException(Type.getObjectType("java/lang/IllegalAccessException")),
symbols.throwKotlinNothingValueException.toKey()!! to ThrowKotlinNothingValueException,
symbols.jvmIndyIntrinsic.toKey()!! to JvmInvokeDynamic,
symbols.jvmDebuggerInvokeSpecialIntrinsic.toKey()!! to JvmDebuggerInvokeSpecial,
symbols.intPostfixIncr.toKey()!! to PostfixIinc(1),
symbols.intPostfixDecr.toKey()!! to PostfixIinc(-1)
) +
numberConversionMethods() +
unaryFunForPrimitives("plus", UnaryPlus) +
unaryFunForPrimitives("unaryPlus", UnaryPlus) +
unaryFunForPrimitives("minus", UnaryMinus) +
unaryFunForPrimitives("unaryMinus", UnaryMinus) +
unaryFunForPrimitives("inv", Inv) +
unaryFunForPrimitives("inc", INC) +
unaryFunForPrimitives("dec", DEC) +
unaryFunForPrimitives("hashCode", HashCode) +
binaryFunForPrimitives("equals", EXPLICIT_EQUALS, irBuiltIns.anyClass) +
binaryFunForPrimitivesAcrossPrimitives("rangeTo", RangeTo) +
binaryOp("plus", IADD) +
binaryOp("minus", ISUB) +
binaryOp("times", IMUL) +
binaryOp("div", IDIV) +
binaryOp("mod", IREM) +
binaryOp("rem", IREM) +
binaryOp("shl", ISHL) +
binaryOp("shr", ISHR) +
binaryOp("ushr", IUSHR) +
binaryOp("and", IAND) +
binaryOp("or", IOR) +
binaryOp("xor", IXOR) +
binaryFunForPrimitivesAcrossPrimitives("compareTo", CompareTo) +
createKeyMapping(Not, irBuiltIns.booleanClass, "not") +
createKeyMapping(StringGetChar, irBuiltIns.stringClass, "get", irBuiltIns.intClass) +
symbols.primitiveIteratorsByType.values.map { iteratorClass ->
createKeyMapping(IteratorNext, iteratorClass, "next")
} +
arrayMethods() +
primitiveComparisonIntrinsics(irBuiltIns.lessFunByOperandType, KtTokens.LT) +
primitiveComparisonIntrinsics(irBuiltIns.lessOrEqualFunByOperandType, KtTokens.LTEQ) +
primitiveComparisonIntrinsics(irBuiltIns.greaterFunByOperandType, KtTokens.GT) +
primitiveComparisonIntrinsics(irBuiltIns.greaterOrEqualFunByOperandType, KtTokens.GTEQ) +
intrinsicsThatShouldHaveBeenLowered()
).toMap()
private fun intrinsicsThatShouldHaveBeenLowered() =
(symbols.primitiveTypesToPrimitiveArrays.map { (_, primitiveClassSymbol) ->
val name = primitiveClassSymbol.owner.name.asString()
// IntArray -> intArrayOf
val arrayOfFunName = name.decapitalizeAsciiOnly() + "Of"
Key(kotlinFqn, null, arrayOfFunName, listOf(primitiveClassSymbol.owner.fqNameWhenAvailable))
} + listOf(
Key(kotlinFqn, anyFqn, "toString", emptyList()),
Key(kotlinFqn, null, "arrayOf", listOf(arrayFqn)),
Key(stringFqn, null, "plus", listOf(anyFqn)),
)).map { it to IntrinsicShouldHaveBeenLowered }
private val PrimitiveType.symbol
get() = irBuiltIns.primitiveTypeToIrType[this]!!.classOrNull!!
fun getIntrinsic(symbol: IrFunctionSymbol): IntrinsicMethod? = intrinsicsMap[symbol.toKey()]
private fun unaryFunForPrimitives(name: String, intrinsic: IntrinsicMethod): List<Pair<Key, IntrinsicMethod>> =
PrimitiveType.values().map { type ->
createKeyMapping(intrinsic, type.symbol, name)
}
private fun binaryFunForPrimitivesAcrossPrimitives(name: String, intrinsic: IntrinsicMethod): List<Pair<Key, IntrinsicMethod>> =
PrimitiveType.values().flatMap { parameter ->
binaryFunForPrimitives(name, intrinsic, parameter.symbol)
}
private fun binaryFunForPrimitives(
name: String,
intrinsic: IntrinsicMethod,
parameter: IrClassifierSymbol
): List<Pair<Key, IntrinsicMethod>> =
PrimitiveType.values().map { type ->
createKeyMapping(intrinsic, type.symbol, name, parameter)
}
private fun binaryOp(methodName: String, opcode: Int) = binaryFunForPrimitivesAcrossPrimitives(methodName, BinaryOp(opcode))
private fun numberConversionMethods(): List<Pair<Key, IntrinsicMethod>> =
PrimitiveType.NUMBER_TYPES.flatMap { type -> numberConversionMethods(type.symbol) } +
numberConversionMethods(irBuiltIns.numberClass)
private fun arrayMethods(): List<Pair<Key, IntrinsicMethod>> =
symbols.primitiveArraysToPrimitiveTypes.flatMap { (array, primitiveType) -> arrayMethods(primitiveType.symbol, array) } +
arrayMethods(symbols.array.owner.typeParameters.single().symbol, symbols.array)
private fun arrayMethods(elementClass: IrClassifierSymbol, arrayClass: IrClassSymbol) =
listOf(
createKeyMapping(ArraySize, arrayClass, "<get-size>"),
createKeyMapping(NewArray, arrayClass, "<init>", irBuiltIns.intClass),
createKeyMapping(ArraySet, arrayClass, "set", irBuiltIns.intClass, elementClass),
createKeyMapping(ArrayGet, arrayClass, "get", irBuiltIns.intClass),
createKeyMapping(Clone, arrayClass, "clone"),
createKeyMapping(ArrayIterator, arrayClass, "iterator")
)
private fun primitiveComparisonIntrinsics(
typeToIrFun: Map<IrClassifierSymbol, IrSimpleFunctionSymbol>,
operator: KtSingleValueToken
): List<Pair<Key, PrimitiveComparison>> =
PrimitiveType.values().mapNotNull { primitiveType ->
val irPrimitiveClassifier = irBuiltIns.primitiveTypeToIrType[primitiveType]!!.classifierOrFail
val irFunSymbol = typeToIrFun[irPrimitiveClassifier] ?: return@mapNotNull null
irFunSymbol.toKey()!! to PrimitiveComparison(primitiveType, operator)
}
data class Key(val owner: FqName, val receiverParameterTypeName: FqName?, val name: String, val valueParameterTypeNames: List<FqName?>)
companion object {
private val INC = Increment(1)
private val DEC = Increment(-1)
private val EXPLICIT_EQUALS = ExplicitEquals()
private fun IrFunctionSymbol.toKey(): Key? {
val parent = owner.parent
val ownerFqName = when {
parent is IrClass && parent.isFileClass ->
(parent.parent as IrPackageFragment).fqName
parent is IrClass -> parent.fqNameWhenAvailable ?: return null
parent is IrPackageFragment -> parent.fqName
else -> return null
}
return Key(
ownerFqName,
getParameterFqName(owner.extensionReceiverParameter),
owner.name.asString(),
owner.valueParameters.map(::getParameterFqName)
)
}
private fun getParameterFqName(parameter: IrValueParameter?): FqName? =
getParameterFqName(parameter?.type?.classifierOrNull)
private fun getParameterFqName(parameter: IrClassifierSymbol?): FqName? =
parameter?.owner?.let {
when (it) {
is IrClass -> it.fqNameWhenAvailable
is IrTypeParameter -> FqName(it.name.asString())
else -> null
}
}
private fun createKeyMapping(
intrinsic: IntrinsicMethod,
klass: IrClassSymbol,
name: String,
vararg args: IrClassifierSymbol
): Pair<Key, IntrinsicMethod> =
Key(klass.owner.fqNameWhenAvailable!!, null, name, args.map { getParameterFqName(it) }) to
intrinsic
private fun numberConversionMethods(numberClass: IrClassSymbol) =
OperatorConventions.NUMBER_CONVERSIONS.map { method ->
createKeyMapping(NumberCast, numberClass, method.asString())
}
}
}
@@ -0,0 +1,31 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.backend.jvm.intrinsics
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.codegen.AsmUtil.genThrow
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpression
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
object IrNoWhenBranchMatchedException : IntrinsicMethod() {
override fun toCallable(expression: IrFunctionAccessExpression, signature: JvmMethodSignature, context: JvmBackendContext): IrIntrinsicFunction {
return IrIntrinsicFunction.create(expression, signature, context) {
genThrow(it, "kotlin/NoWhenBranchMatchedException", null)
}
}
}
@@ -0,0 +1,22 @@
/*
* Copyright 2010-2018 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.intrinsics
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.ir.types.typeWith
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
object IsArrayOf : IntrinsicMethod() {
override fun toCallable(
expression: IrFunctionAccessExpression,
signature: JvmMethodSignature,
context: JvmBackendContext
): IrIntrinsicFunction = IrIntrinsicFunction.create(expression, signature, context) { v ->
val arrayType = context.irBuiltIns.arrayClass.typeWith(expression.getTypeArgument(0)!!)
v.instanceOf(context.typeMapper.mapType(arrayType))
}
}
@@ -0,0 +1,57 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.backend.jvm.intrinsics
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.builtins.StandardNames.COLLECTIONS_PACKAGE_FQ_NAME
import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.fileClasses.internalNameWithoutInnerClasses
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.org.objectweb.asm.Type
object IteratorNext : IntrinsicMethod() {
override fun toCallable(expression: IrFunctionAccessExpression, signature: JvmMethodSignature, context: JvmBackendContext): IrIntrinsicFunction {
// If the array element type is unboxed primitive, do not unbox. Otherwise AsmUtil.unbox throws exception
val type = if (AsmUtil.isBoxedPrimitiveType(signature.returnType)) AsmUtil.unboxType(signature.returnType) else signature.returnType
val newSignature = signature.newReturnType(type)
val primitiveClassName = getKotlinPrimitiveClassName(type)
return IrIntrinsicFunction.create(expression, newSignature, context, getPrimitiveIteratorType(primitiveClassName)) {
it.invokevirtual(
getPrimitiveIteratorType(primitiveClassName).internalName,
"next${primitiveClassName.asString()}",
"()" + type.descriptor,
false
)
}
}
// Type.CHAR_TYPE -> "Char"
private fun getKotlinPrimitiveClassName(type: Type): Name {
return JvmPrimitiveType.get(type.className).primitiveType.typeName
}
// "Char" -> type for kotlin.collections.CharIterator
fun getPrimitiveIteratorType(primitiveClassName: Name): Type {
val iteratorName = Name.identifier(primitiveClassName.asString() + "Iterator")
return Type.getObjectType(COLLECTIONS_PACKAGE_FQ_NAME.child(iteratorName).internalNameWithoutInnerClasses)
}
}
@@ -0,0 +1,50 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.backend.jvm.intrinsics
import org.jetbrains.kotlin.backend.jvm.codegen.*
import org.jetbrains.kotlin.codegen.AsmUtil.boxType
import org.jetbrains.kotlin.codegen.AsmUtil.isPrimitive
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.ir.types.classOrNull
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.org.objectweb.asm.Type
object JavaClassProperty : IntrinsicMethod() {
private fun invokeGetClass(value: PromisedValue) {
value.mv.invokevirtual("java/lang/Object", "getClass", "()Ljava/lang/Class;", false)
}
fun invokeWith(value: PromisedValue) =
when {
value.type == Type.VOID_TYPE ->
invokeGetClass(value.materializedAt(AsmTypes.UNIT_TYPE, value.codegen.context.irBuiltIns.unitType))
value.irType.classOrNull?.owner?.isInline == true ->
invokeGetClass(value.materializedAtBoxed(value.irType))
isPrimitive(value.type) -> {
value.discard()
value.mv.getstatic(boxType(value.type).internalName, "TYPE", "Ljava/lang/Class;")
}
else ->
invokeGetClass(value.materialized())
}
override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue? {
invokeWith(expression.extensionReceiver!!.accept(codegen, data))
return with(codegen) { expression.onStack }
}
}
@@ -0,0 +1,40 @@
/*
* Copyright 2010-2021 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.intrinsics
import org.jetbrains.kotlin.backend.jvm.codegen.*
import org.jetbrains.kotlin.backend.jvm.ir.getBooleanConstArgument
import org.jetbrains.kotlin.backend.jvm.ir.getStringConstArgument
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.org.objectweb.asm.Type
// This intrinsic enables IR lowerings to force the generation of a particular
// invokeSpecial instruction in the resulting JVM bytecode.
//
// The need for this is to coerce the IR codegen backend to generate an
// otherwise illegal invokeSpecial for the express purpose of being
// _interpreted_ by eval4j in the fragment evaluator and not actually run on
// the JVM. This allows the "evaluate expression" functionality of the Kotlin
// JVM Debugger Plug-in in IntelliJ to simulate the invocation of `super` calls
// in the context of a breakpoint.
//
// It uses the "trick" of encoding the desired operands as constants passed as
// arguments to the intrinsic, opening a direct line from the producing
// lowering straight through to JVM codegen without interference from
// lowerings in between.
object JvmDebuggerInvokeSpecial : IntrinsicMethod() {
override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue {
val owner = expression.getStringConstArgument(0)
val name = expression.getStringConstArgument(1)
val descriptor = expression.getStringConstArgument(2)
val isInterface = expression.getBooleanConstArgument(3)
expression.dispatchReceiver!!.accept(codegen, data).materialize()
codegen.mv.invokespecial(owner, name, descriptor, isInterface)
return MaterialValue(codegen, Type.getReturnType(descriptor), expression.type)
}
}
@@ -0,0 +1,124 @@
/*
* Copyright 2010-2021 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.intrinsics
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
import org.jetbrains.kotlin.backend.jvm.codegen.*
import org.jetbrains.kotlin.backend.jvm.ir.getBooleanConstArgument
import org.jetbrains.kotlin.backend.jvm.ir.getIntConstArgument
import org.jetbrains.kotlin.backend.jvm.ir.getStringConstArgument
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.util.dump
import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import org.jetbrains.org.objectweb.asm.Handle
import org.jetbrains.org.objectweb.asm.Type
object JvmInvokeDynamic : IntrinsicMethod() {
override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue {
fun fail(message: String): Nothing =
throw AssertionError("$message; expression:\n${expression.dump()}")
val dynamicCall = expression.getValueArgument(0) as? IrCall
?: fail("'dynamicCall' is expected to be a call")
val dynamicCallee = dynamicCall.symbol.owner
if (dynamicCallee.parent != codegen.context.ir.symbols.kotlinJvmInternalInvokeDynamicPackage ||
dynamicCallee.origin != JvmLoweredDeclarationOrigin.INVOKEDYNAMIC_CALL_TARGET
)
fail("Unexpected dynamicCallee: '${dynamicCallee.render()}'")
val bootstrapMethodHandleArg = expression.getValueArgument(1) as? IrCall
?: fail("'bootstrapMethodHandle' should be a call")
val bootstrapMethodHandle = evalMethodHandle(bootstrapMethodHandleArg)
val bootstrapMethodArgs = (expression.getValueArgument(2)?.safeAs<IrVararg>()
?: fail("'bootstrapMethodArgs' is expected to be a vararg"))
val asmBootstrapMethodArgs = bootstrapMethodArgs.elements
.map { generateBootstrapMethodArg(it, codegen) }
.toTypedArray()
val dynamicCalleeMethod = codegen.methodSignatureMapper.mapAsmMethod(dynamicCallee)
val dynamicCallGenerator = IrCallGenerator.DefaultCallGenerator
val dynamicCalleeArgumentTypes = dynamicCalleeMethod.argumentTypes
for (i in dynamicCallee.valueParameters.indices) {
val dynamicCalleeParameter = dynamicCallee.valueParameters[i]
val dynamicCalleeArgument = dynamicCall.getValueArgument(i)
?: fail("No argument #$i in 'dynamicCall'")
val dynamicCalleeArgumentType = dynamicCalleeArgumentTypes.getOrElse(i) {
fail("No argument type #$i in dynamic callee: $dynamicCalleeMethod")
}
dynamicCallGenerator.genValueAndPut(dynamicCalleeParameter, dynamicCalleeArgument, dynamicCalleeArgumentType, codegen, data)
}
codegen.mv.invokedynamic(dynamicCalleeMethod.name, dynamicCalleeMethod.descriptor, bootstrapMethodHandle, asmBootstrapMethodArgs)
return MaterialValue(codegen, dynamicCalleeMethod.returnType, expression.type)
}
private fun generateBootstrapMethodArg(element: IrVarargElement, codegen: ExpressionCodegen): Any =
when (element) {
is IrRawFunctionReference ->
generateMethodHandle(element, codegen)
is IrCall ->
evalBootstrapArgumentIntrinsicCall(element, codegen)
?: throw AssertionError("Unexpected callee in bootstrap method argument:\n${element.dump()}")
is IrConst<*> ->
when (element.kind) {
IrConstKind.Byte -> (element.value as Byte).toInt()
IrConstKind.Short -> (element.value as Short).toInt()
IrConstKind.Int -> element.value as Int
IrConstKind.Long -> element.value as Long
IrConstKind.Float -> element.value as Float
IrConstKind.Double -> element.value as Double
IrConstKind.String -> element.value as String
else ->
throw AssertionError("Unexpected constant expression in bootstrap method argument:\n${element.dump()}")
}
else ->
throw AssertionError("Unexpected bootstrap method argument:\n${element.dump()}")
}
private fun evalBootstrapArgumentIntrinsicCall(irCall: IrCall, codegen: ExpressionCodegen): Any? {
return when (irCall.symbol) {
codegen.context.ir.symbols.jvmOriginalMethodTypeIntrinsic ->
evalOriginalMethodType(irCall, codegen)
codegen.context.ir.symbols.jvmMethodType ->
evalMethodType(irCall)
codegen.context.ir.symbols.jvmMethodHandle ->
evalMethodHandle(irCall)
else ->
null
}
}
private fun evalMethodType(irCall: IrCall): Type {
val descriptor = irCall.getStringConstArgument(0)
return Type.getMethodType(descriptor)
}
private fun evalMethodHandle(irCall: IrCall): Handle {
val tag = irCall.getIntConstArgument(0)
val owner = irCall.getStringConstArgument(1)
val name = irCall.getStringConstArgument(2)
val descriptor = irCall.getStringConstArgument(3)
val isInterface = irCall.getBooleanConstArgument(4)
return Handle(tag, owner, name, descriptor, isInterface)
}
private fun generateMethodHandle(irRawFunctionReference: IrRawFunctionReference, codegen: ExpressionCodegen): Handle =
codegen.context.methodSignatureMapper.mapToMethodHandle(irRawFunctionReference.symbol.owner)
private fun evalOriginalMethodType(irCall: IrCall, codegen: ExpressionCodegen): Type {
val irRawFunRef = irCall.getValueArgument(0) as? IrRawFunctionReference
?: throw AssertionError(
"Argument in ${irCall.symbol.owner.name} call is expected to be a raw function reference:\n" +
irCall.dump()
)
val irFun = irRawFunRef.symbol.owner
val asmMethod = codegen.methodSignatureMapper.mapAsmMethod(irFun)
return Type.getMethodType(asmMethod.descriptor)
}
}
@@ -0,0 +1,33 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.backend.jvm.intrinsics
import org.jetbrains.kotlin.backend.jvm.codegen.BlockInfo
import org.jetbrains.kotlin.backend.jvm.codegen.ExpressionCodegen
import org.jetbrains.kotlin.backend.jvm.codegen.PromisedValue
import org.jetbrains.kotlin.ir.expressions.IrClassReference
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.ir.expressions.IrGetClass
object KClassJavaProperty : IntrinsicMethod() {
override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue? {
val extensionReceiver = expression.extensionReceiver
if (extensionReceiver !is IrClassReference && extensionReceiver !is IrGetClass)
return null
return codegen.generateClassLiteralReference(extensionReceiver, false, data)
}
}
@@ -0,0 +1,42 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.backend.jvm.intrinsics
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpression
import org.jetbrains.kotlin.resolve.jvm.AsmTypes.OBJECT_TYPE
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.org.objectweb.asm.Opcodes
class MonitorInstruction private constructor(private val opcode: Int) : IntrinsicMethod() {
companion object {
@JvmField
val MONITOR_ENTER: MonitorInstruction = MonitorInstruction(Opcodes.MONITORENTER)
@JvmField
val MONITOR_EXIT: MonitorInstruction = MonitorInstruction(Opcodes.MONITOREXIT)
}
/*TODO void return type*/
override fun toCallable(expression: IrFunctionAccessExpression, signature: JvmMethodSignature, context: JvmBackendContext): IrIntrinsicFunction {
return IrIntrinsicFunction.create(expression, signature, context, OBJECT_TYPE) {
it.visitInsn(opcode)
}
}
}
@@ -0,0 +1,33 @@
/*
* Copyright 2010-2018 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.intrinsics
import org.jetbrains.kotlin.backend.jvm.codegen.BlockInfo
import org.jetbrains.kotlin.backend.jvm.codegen.ExpressionCodegen
import org.jetbrains.kotlin.backend.jvm.codegen.PromisedValue
import org.jetbrains.kotlin.codegen.inline.ReifiedTypeInliner
import org.jetbrains.kotlin.codegen.putReifiedOperationMarkerIfTypeIsReifiedParameter
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.ir.types.getArrayElementType
import org.jetbrains.kotlin.ir.types.isArray
import org.jetbrains.kotlin.ir.types.isNullableArray
import org.jetbrains.org.objectweb.asm.Type
object NewArray : IntrinsicMethod() {
override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue {
codegen.gen(expression.getValueArgument(0)!!, Type.INT_TYPE, codegen.context.irBuiltIns.intType, data)
return with(codegen) {
val elementIrType = expression.type.getArrayElementType(context.irBuiltIns)
if (expression.type.isArray() || expression.type.isNullableArray()) {
putReifiedOperationMarkerIfTypeIsReifiedParameter(elementIrType, ReifiedTypeInliner.OperationKind.NEW_ARRAY)
mv.newarray(typeMapper.boxType(elementIrType))
} else {
mv.newarray(typeMapper.mapType(elementIrType))
}
expression.onStack
}
}
}
@@ -0,0 +1,37 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.backend.jvm.intrinsics
import org.jetbrains.kotlin.backend.jvm.codegen.BlockInfo
import org.jetbrains.kotlin.backend.jvm.codegen.BooleanValue
import org.jetbrains.kotlin.backend.jvm.codegen.ExpressionCodegen
import org.jetbrains.kotlin.backend.jvm.codegen.coerceToBoolean
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.org.objectweb.asm.Label
object Not : IntrinsicMethod() {
class BooleanNegation(val value: BooleanValue) : BooleanValue(value.codegen) {
override fun jumpIfFalse(target: Label) = value.jumpIfTrue(target)
override fun jumpIfTrue(target: Label) = value.jumpIfFalse(target)
override fun discard() {
value.discard()
}
}
override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo) =
BooleanNegation(expression.dispatchReceiver!!.accept(codegen, data).coerceToBoolean())
}
@@ -0,0 +1,33 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.backend.jvm.intrinsics
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.codegen.StackValue
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpression
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
object NumberCast : IntrinsicMethod() {
override fun toCallable(expression: IrFunctionAccessExpression, signature: JvmMethodSignature, context: JvmBackendContext): IrIntrinsicFunction {
return IrIntrinsicFunction.create(expression, signature, context) {
StackValue.coerce(argsTypes[0], signature.returnType, it)
}
}
}
@@ -0,0 +1,44 @@
/*
* Copyright 2010-2019 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.intrinsics
import org.jetbrains.kotlin.backend.jvm.codegen.*
import org.jetbrains.kotlin.backend.jvm.ir.receiverAndArgs
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.org.objectweb.asm.Label
object OrOr : IntrinsicMethod() {
private class BooleanDisjunction(
val arg0: IrExpression, val arg1: IrExpression, codegen: ExpressionCodegen, val data: BlockInfo
) : BooleanValue(codegen) {
override fun jumpIfFalse(target: Label) {
val stayLabel = Label()
arg0.accept(codegen, data).coerceToBoolean().jumpIfTrue(stayLabel)
arg1.accept(codegen, data).coerceToBoolean().jumpIfFalse(target)
mv.visitLabel(stayLabel)
}
override fun jumpIfTrue(target: Label) {
arg0.accept(codegen, data).coerceToBoolean().jumpIfTrue(target)
arg1.accept(codegen, data).coerceToBoolean().jumpIfTrue(target)
}
override fun discard() {
val end = Label()
arg0.accept(codegen, data).coerceToBoolean().jumpIfTrue(end)
arg1.accept(codegen, data).discard()
mv.visitLabel(end)
}
}
override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue? {
val (left, right) = expression.receiverAndArgs()
return BooleanDisjunction(left, right, codegen, data)
}
}
@@ -0,0 +1,26 @@
/*
* Copyright 2010-2021 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.intrinsics
import org.jetbrains.kotlin.backend.jvm.codegen.*
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.ir.expressions.IrGetValue
import org.jetbrains.kotlin.ir.util.dump
import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.org.objectweb.asm.Type
class PostfixIinc(private val delta: Int) : IntrinsicMethod() {
override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue? {
val argument = expression.getValueArgument(0) as? IrGetValue
?: error("IrGetValue expected: ${expression.dump()}")
val varIndex = codegen.frameMap.getIndex(argument.symbol)
if (varIndex == -1)
error("Unmapped variable: ${argument.render()}")
argument.accept(codegen, data).materialize()
codegen.mv.iinc(varIndex, delta)
return MaterialValue(codegen, Type.INT_TYPE, argument.type)
}
}
@@ -0,0 +1,66 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.backend.jvm.intrinsics
import org.jetbrains.kotlin.backend.jvm.codegen.BlockInfo
import org.jetbrains.kotlin.backend.jvm.codegen.ExpressionCodegen
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.codegen.StackValue
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.Type.*
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
import java.lang.IllegalStateException
object RangeTo : IntrinsicMethod() {
private fun rangeTypeToPrimitiveType(rangeType: Type): Type {
val fqName = rangeType.internalName
val name = fqName.substringAfter("kotlin/ranges/").substringBefore("Range")
return when (name) {
"Double" -> DOUBLE_TYPE
"Float" -> FLOAT_TYPE
"Long" -> LONG_TYPE
"Int" -> INT_TYPE
"Short" -> SHORT_TYPE
"Char" -> CHAR_TYPE
"Byte" -> BYTE_TYPE
else -> throw IllegalStateException("RangeTo intrinsic can only work for primitive types: $fqName")
}
}
override fun toCallable(expression: IrFunctionAccessExpression, signature: JvmMethodSignature, context: JvmBackendContext): IrIntrinsicFunction {
val argType = rangeTypeToPrimitiveType(signature.returnType)
return object: IrIntrinsicFunction(expression, signature, context, listOf(argType) + signature.valueParameters.map { argType }) {
override fun genInvokeInstruction(v: InstructionAdapter) {
v.invokespecial(signature.returnType.internalName, "<init>", Type.getMethodDescriptor(Type.VOID_TYPE, argType, argType), false)
}
override fun invoke(
v: InstructionAdapter,
codegen: ExpressionCodegen,
data: BlockInfo,
expression: IrFunctionAccessExpression
): StackValue {
codegen.markLineNumber(expression)
v.anew(returnType)
v.dup()
return super.invoke(v, codegen, data, expression)
}
}
}
}
@@ -0,0 +1,42 @@
/*
* Copyright 2010-2019 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.intrinsics
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.codegen.BlockInfo
import org.jetbrains.kotlin.backend.jvm.codegen.ExpressionCodegen
import org.jetbrains.kotlin.backend.jvm.codegen.MaterialValue
import org.jetbrains.kotlin.backend.jvm.codegen.PromisedValue
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.ir.expressions.IrFunctionReference
import org.jetbrains.kotlin.ir.util.collectRealOverrides
import org.jetbrains.kotlin.ir.util.isSuspend
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
/**
* Computes the JVM signature of a given IrFunction. The function is passed as an IrFunctionReference
* to the single argument of the intrinsic.
*/
object SignatureString : IntrinsicMethod() {
override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue {
val function = (expression.getValueArgument(0) as IrFunctionReference).symbol.owner
generateSignatureString(codegen.mv, function, codegen.context)
return MaterialValue(codegen, AsmTypes.JAVA_STRING_TYPE, codegen.context.irBuiltIns.stringType)
}
internal fun generateSignatureString(v: InstructionAdapter, function: IrFunction, context: JvmBackendContext) {
var resolved = if (function is IrSimpleFunction) function.collectRealOverrides().first() else function
if (resolved.isSuspend) {
resolved = context.suspendFunctionOriginalToView[resolved] ?: resolved
}
val method = context.methodSignatureMapper.mapAsmMethod(resolved)
val descriptor = method.name + method.descriptor
v.aconst(descriptor)
}
}
@@ -0,0 +1,30 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.backend.jvm.intrinsics
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpression
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
object StringGetChar : IntrinsicMethod() {
override fun toCallable(expression: IrFunctionAccessExpression, signature: JvmMethodSignature, context: JvmBackendContext): IrIntrinsicFunction {
return IrIntrinsicFunction.create(expression, signature, context) {
it.invokevirtual("java/lang/String", "charAt", "(I)C", false)
}
}
}
@@ -0,0 +1,30 @@
/*
* 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.intrinsics
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.org.objectweb.asm.Type
object StringPlus : IntrinsicMethod() {
override fun toCallable(
expression: IrFunctionAccessExpression,
signature: JvmMethodSignature,
context: JvmBackendContext
): IrIntrinsicFunction =
IrIntrinsicFunction.create(expression, signature, context, listOf(AsmTypes.JAVA_STRING_TYPE, AsmTypes.OBJECT_TYPE)) {
it.invokestatic(
IntrinsicMethods.INTRINSICS_CLASS_NAME,
"stringPlus",
Type.getMethodDescriptor(AsmTypes.JAVA_STRING_TYPE, AsmTypes.JAVA_STRING_TYPE, AsmTypes.OBJECT_TYPE),
false
)
}
}
@@ -0,0 +1,27 @@
/*
* 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.intrinsics
import org.jetbrains.kotlin.backend.jvm.codegen.BlockInfo
import org.jetbrains.kotlin.backend.jvm.codegen.ExpressionCodegen
import org.jetbrains.kotlin.backend.jvm.codegen.PromisedValue
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.org.objectweb.asm.Type
class ThrowException(val exceptionClass: Type) : IntrinsicMethod() {
override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue? {
with(codegen) {
mv.anew(exceptionClass)
mv.dup()
gen(expression.getValueArgument(0)!!, AsmTypes.JAVA_STRING_TYPE, codegen.context.irBuiltIns.stringType, data)
mv.invokespecial(exceptionClass.internalName, "<init>", "(Ljava/lang/String;)V", false)
mv.athrow()
return null
}
}
}
@@ -0,0 +1,29 @@
/*
* 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.intrinsics
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.org.objectweb.asm.Type
object ThrowKotlinNothingValueException : IntrinsicMethod() {
override fun toCallable(
expression: IrFunctionAccessExpression,
signature: JvmMethodSignature,
context: JvmBackendContext
): IrIntrinsicFunction =
IrIntrinsicFunction.create(expression, signature, context) { mv ->
if (context.state.useKotlinNothingValueException) {
mv.anew(Type.getObjectType("kotlin/KotlinNothingValueException"))
mv.dup()
mv.invokespecial("kotlin/KotlinNothingValueException", "<init>", "()V", false)
} else {
mv.aconst(null)
}
mv.athrow()
}
}
@@ -0,0 +1,28 @@
/*
* Copyright 2010-2021 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.intrinsics
import org.jetbrains.kotlin.backend.jvm.codegen.BlockInfo
import org.jetbrains.kotlin.backend.jvm.codegen.ExpressionCodegen
import org.jetbrains.kotlin.backend.jvm.codegen.IrInlineIntrinsicsSupport
import org.jetbrains.kotlin.backend.jvm.ir.fileParent
import org.jetbrains.kotlin.codegen.inline.ReifiedTypeInliner
import org.jetbrains.kotlin.codegen.inline.generateTypeOf
import org.jetbrains.kotlin.codegen.putReifiedOperationMarkerIfTypeIsReifiedParameter
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
object TypeOf : IntrinsicMethod() {
override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo) = with(codegen) {
val type = expression.getTypeArgument(0)!!
if (putReifiedOperationMarkerIfTypeIsReifiedParameter(type, ReifiedTypeInliner.OperationKind.TYPE_OF)) {
mv.aconst(null) // see ReifiedTypeInliner.processTypeOf
} else {
val support = IrInlineIntrinsicsSupport(context, typeMapper, expression, codegen.irFunction.fileParent)
typeMapper.typeSystem.generateTypeOf(mv, type, support)
}
expression.onStack
}
}
@@ -0,0 +1,34 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.backend.jvm.intrinsics
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.codegen.AsmUtil.numberFunctionOperandType
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpression
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
object UnaryMinus : IntrinsicMethod() {
/*TODO return type*/
override fun toCallable(expression: IrFunctionAccessExpression, signature: JvmMethodSignature, context: JvmBackendContext): IrIntrinsicFunction {
return IrIntrinsicFunction.create(expression, signature, context) {
it.neg(numberFunctionOperandType(signature.returnType))
}
}
}
@@ -0,0 +1,30 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.backend.jvm.intrinsics
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpression
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
object UnaryPlus : IntrinsicMethod() {
override fun toCallable(expression: IrFunctionAccessExpression, signature: JvmMethodSignature, context: JvmBackendContext): IrIntrinsicFunction {
return IrIntrinsicFunction.create(expression, signature, context) {
}
}
}
@@ -0,0 +1,42 @@
/*
* Copyright 2010-2019 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.intrinsics
import org.jetbrains.kotlin.backend.jvm.codegen.*
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.org.objectweb.asm.Type
/**
* Implicit coercion between IrTypes with the same underlying representation.
*
* A call of the form `coerce<A,B>(x)` allows us to coerce the value of `x` to type `A` but treat the result as if
* it had IrType `B`. This is useful for inline classes, whose coercion behavior depends on the IrType in
* addition to the underlying asmType.
*/
object UnsafeCoerce : IntrinsicMethod() {
override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue {
val from = expression.getTypeArgument(0)!!
val to = expression.getTypeArgument(1)!!
val fromType = codegen.typeMapper.mapType(from)
val toType = codegen.typeMapper.mapType(to)
require(fromType == toType) {
"Inline class types should have the same representation: $fromType != $toType"
}
val arg = expression.getValueArgument(0)!!
val result = arg.accept(codegen, data)
return object : PromisedValue(codegen, toType, to) {
override fun materializeAt(target: Type, irTarget: IrType, castForReified: Boolean) {
result.materializeAt(fromType, from)
super.materializeAt(target, irTarget, castForReified)
}
override fun discard() {
result.discard()
}
}
}
}