Compare commits

..

1 Commits

Author SHA1 Message Date
Nikita Nazarov
48eb95346f [EE_IR] Update ignore directive 2021-06-16 23:49:54 +03:00
57001 changed files with 1249597 additions and 203212 deletions

3
.bunch
View File

@@ -1 +1,2 @@
203
202
as42

3
.gitignore vendored
View File

@@ -12,7 +12,7 @@
/android-studio/sdk
out/
/tmp
/intellij
kotlin-ide/
workspace.xml
*.versionsBackup
/idea/testData/debugger/tinyApp/classes*
@@ -68,4 +68,3 @@ distTmp/
outTmp/
/test.output
/kotlin-native/dist
kotlin-ide/

View File

@@ -1,9 +0,0 @@
<component name="ProjectDictionaryState">
<dictionary name="igor">
<words>
<w>addr</w>
<w>descr</w>
<w>exprs</w>
</words>
</dictionary>
</component>

View File

@@ -2,7 +2,6 @@
<dictionary name="skuzmich">
<words>
<w>anyref</w>
<w>dataref</w>
<w>ushr</w>
<w>wasm</w>
</words>

2
.idea/kotlinc.xml generated
View File

@@ -13,6 +13,6 @@
</option>
</component>
<component name="KotlinCompilerSettings">
<option name="additionalArguments" value="-version -Xallow-kotlin-package -Xskip-metadata-version-check" />
<option name="additionalArguments" value="-version -Xallow-kotlin-package -Xskip-metadata-version-check -Xread-deserialized-contracts" />
</component>
</project>

View File

@@ -11,7 +11,7 @@
<option name="taskNames">
<list>
<option value=":compiler:fir:checkers:generateCheckersComponents" />
<option value=":idea-frontend-fir:generateCode" />
<option value=":idea:idea-frontend-fir:generateCode" />
</list>
</option>
<option name="vmOptions" value="" />

20
.idea/runConfigurations/IDEA.xml generated Normal file
View File

@@ -0,0 +1,20 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="IDEA" type="GradleRunConfiguration" factoryName="Gradle" singleton="true" folderName="IDEA">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="runIde" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<method />
</configuration>
</component>

View File

@@ -0,0 +1,20 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="IDEA Ultimate" type="GradleRunConfiguration" factoryName="Gradle" folderName="IDEA">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="-P intellijUltimateEnabled" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="runUltimate" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<method />
</configuration>
</component>

View File

@@ -0,0 +1,20 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="IDEA Ultimate (No ProcessCanceledException) " type="GradleRunConfiguration" factoryName="Gradle" folderName="IDEA">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="-PnoPCE" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="runUltimate" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<method />
</configuration>
</component>

View File

@@ -0,0 +1,20 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="IDEA (No ProcessCanceledException)" type="GradleRunConfiguration" factoryName="Gradle" folderName="IDEA">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="-PnoPCE" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="runIde" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<method />
</configuration>
</component>

View File

@@ -0,0 +1,19 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="IDEA (Not Internal)" type="GradleRunConfiguration" factoryName="Gradle" folderName="IDEA">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="-Pidea.is.internal=false" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="runIde" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
</configuration>
</component>

View File

@@ -4,7 +4,7 @@
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="--tests &quot;org.jetbrains.kotlin.gradle.CommonizerHierarchicalIT&quot; --tests &quot;org.jetbrains.kotlin.gradle.CommonizerIT&quot; --tests &quot;org.jetbrains.kotlin.commonizer.**&quot; --tests &quot;org.jetbrains.kotlin.gradle.native.CocoaPodsIT.testCinteropCommonization*&quot;" />
<option name="scriptParameters" value="--tests &quot;org.jetbrains.kotlin.gradle.CommonizerHierarchicalIT&quot; --tests &quot;org.jetbrains.kotlin.gradle.CommonizerIT&quot; --tests &quot;org.jetbrains.kotlin.commonizer.**&quot;" />
<option name="taskDescriptions">
<list />
</option>

View File

@@ -1,23 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Test: Commonizer / Light" type="GradleRunConfiguration" factoryName="Gradle" folderName="Tests">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value=":native:kotlin-klib-commonizer-api:test" />
<option value=":native:kotlin-klib-commonizer:test" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2" />
</configuration>
</component>

File diff suppressed because it is too large Load Diff

View File

@@ -35,30 +35,26 @@ Support for multiplatform programming is one of Kotlins key benefits. It redu
## Editing Kotlin
* [Kotlin IntelliJ IDEA Plugin](https://kotlinlang.org/docs/tutorials/getting-started.html) ([source code](https://github.com/JetBrains/intellij-community/tree/master/plugins/kotlin))
* [Kotlin IntelliJ IDEA Plugin](https://kotlinlang.org/docs/tutorials/getting-started.html)
* [Kotlin Eclipse Plugin](https://kotlinlang.org/docs/tutorials/getting-started-eclipse.html)
* [Kotlin Sublime Text Package](https://github.com/vkostyukov/kotlin-sublime-package)
## Build environment requirements
This repository is using [Gradle toolchains](https://docs.gradle.org/current/userguide/toolchains.html) feature
to select and auto-provision required JDKs from [AdoptOpenJdk](https://adoptopenjdk.net) project.
In order to build Kotlin distribution you need to have:
Unfortunately [AdoptOpenJdk](https://adoptopenjdk.net) project does not provide required JDK 1.6 and 1.7 images,
so you could either download them manually and provide path to installation via `JDK_16` and `JDK_17` environment variables or
use following SDK managers:
- [Asdf-vm](https://asdf-vm.com/)
- [Jabba](https://github.com/shyiko/jabba)
- [SDKMAN!](https://sdkman.io/)
- JDK 1.6, 1.7, 1.8 and 9
- Setup environment variables as following:
Alternatively, it is still possible to only provide required JDKs via environment variables
(see [gradle.properties](./gradle.properties#L5) for supported variable names). To ensure Gradle uses only JDKs
from environmental variables - disable Gradle toolchain auto-detection by passing `-Porg.gradle.java.installations.auto-detect=false` option
(or put it into `$GRADLE_USER_HOME/gradle.properties`).
JAVA_HOME="path to JDK 1.8"
JDK_16="path to JDK 1.6"
JDK_17="path to JDK 1.7"
JDK_18="path to JDK 1.8"
JDK_9="path to JDK 9"
For local development, if you're not working on the standard library, it's OK to avoid installing JDK 1.6 and JDK 1.7.
Add `kotlin.build.isObsoleteJdkOverrideEnabled=true` to the `local.properties` file, so build will only use JDK 1.8+. Note, that in this
case, build will have Gradle remote build cache misses for some tasks.
For local development, if you're not working on bytecode generation or the standard library, it's OK to have only JDK 1.8 and JDK 9 installed, and to point `JDK_16` and `JDK_17` environment variables to your JDK 1.8 installation.
You also can use [Gradle properties](https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties) to setup `JDK_*` variables.
Note: The JDK 6 for MacOS is not available on Oracle's site. You can install it by
@@ -97,10 +93,13 @@ command line parameters on the first run:
- `clean` - clean build results
- `dist` - assembles the compiler distribution into `dist/kotlinc/` folder
- `ideaPlugin` - assembles the Kotlin IDEA plugin distribution into `dist/artifacts/ideaPlugin/Kotlin/` folder
- `install` - build and install all public artifacts into local maven repository
- `runIde` - build IDEA plugin and run IDEA with it
- `coreLibsTest` - build and run stdlib, reflect and kotlin-test tests
- `gradlePluginTest` - build and run gradle plugin tests
- `compilerTest` - build and run all compiler tests
- `ideaPluginTest` - build and run all IDEA plugin tests
To reproduce TeamCity build use `-Pteamcity=true` flag. Local builds don't run proguard and have jar compression disabled by default.
@@ -140,6 +139,14 @@ To be able to run tests from IntelliJ easily, check `Delegate IDE build/run acti
At this time, you can use the latest released `1.3.x` version of the Kotlin plugin for working with the code. To make sure you have the latest version installed, use `Tools` -> `Kotlin` -> `Configure Kotlin Plugin Updates`.
### Compiling and running
From this root project there are Run/Debug Configurations for running `IDEA` or the `Generate Compiler Tests` for example; so if you want to try out the latest and greatest IDEA plugin
* `VCS` -> `Git` -> `Pull`
* Run the `IDEA` run configuration in the project
* A child IntelliJ IDEA with the Kotlin plugin will then startup
### Dependency verification
We have a [dependencies verification](https://docs.gradle.org/current/userguide/dependency_verification.html) feature enabled in the

View File

@@ -25,7 +25,6 @@ import org.jetbrains.kotlin.context.withModule
import org.jetbrains.kotlin.context.withProject
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
import org.jetbrains.kotlin.diagnostics.Severity
import org.jetbrains.kotlin.fir.builder.PsiHandlingMode
import org.jetbrains.kotlin.fir.builder.RawFirBuilder
import org.jetbrains.kotlin.fir.createSessionForTests
import org.jetbrains.kotlin.fir.java.FirJavaElementFinder
@@ -155,7 +154,7 @@ abstract class AbstractSimpleFileBenchmark {
.uniteWith(TopDownAnalyzerFacadeForJVM.AllJavaSourcesInProjectScope(env.project))
val session = createSessionForTests(env, scope)
val firProvider = session.firProvider as FirProviderImpl
val builder = RawFirBuilder(session, firProvider.kotlinScopeProvider, PsiHandlingMode.COMPILER)
val builder = RawFirBuilder(session, firProvider.kotlinScopeProvider)
val totalTransformer = FirTotalResolveProcessor(session)
val firFile = builder.buildFirFile(file).also(firProvider::recordFile)

View File

@@ -40,7 +40,7 @@ abstract class BuildMetaInfoFactory<T : BuildMetaInfo>(private val metaInfoClass
): T
fun create(args: CommonCompilerArguments): T {
val languageVersion = args.languageVersion?.let { LanguageVersion.fromVersionString(it) } ?: LanguageVersion.LATEST_STABLE
val languageVersion = args.languageVersion?.let((LanguageVersion)::fromVersionString) ?: LanguageVersion.LATEST_STABLE
return create(
isEAP = languageVersion.isPreRelease(),
@@ -51,7 +51,7 @@ abstract class BuildMetaInfoFactory<T : BuildMetaInfo>(private val metaInfoClass
ownVersion = OWN_VERSION,
coroutinesVersion = COROUTINES_VERSION,
multiplatformVersion = MULTIPLATFORM_VERSION,
metadataVersionArray = args.metadataVersion?.let { BinaryVersion.parseVersionArray(it) }
metadataVersionArray = args.metadataVersion?.let((BinaryVersion)::parseVersionArray)
)
}

View File

@@ -17,7 +17,6 @@ enum class BuildAttributeKind : Serializable {
enum class BuildAttribute(val kind: BuildAttributeKind) : Serializable {
NO_BUILD_HISTORY(BuildAttributeKind.REBUILD_REASON),
NO_ABI_SNAPSHOT(BuildAttributeKind.REBUILD_REASON),
CACHE_CORRUPTION(BuildAttributeKind.REBUILD_REASON),
UNKNOWN_CHANGES_IN_GRADLE_INPUTS(BuildAttributeKind.REBUILD_REASON),
JAVA_CHANGE_UNTRACKED_FILE_IS_REMOVED(BuildAttributeKind.REBUILD_REASON),

View File

@@ -9,7 +9,6 @@ import java.io.Serializable
@Suppress("Reformat")
enum class BuildTime(val parent: BuildTime? = null) : Serializable {
GRADLE_TASK_ACTION,
GRADLE_TASK,
CLEAR_OUTPUT(GRADLE_TASK),
BACKUP_OUTPUT(GRADLE_TASK),
@@ -21,10 +20,6 @@ enum class BuildTime(val parent: BuildTime? = null) : Serializable {
NON_INCREMENTAL_COMPILATION_OUT_OF_PROCESS(RUN_COMPILER),
NON_INCREMENTAL_COMPILATION_DAEMON(RUN_COMPILER),
INCREMENTAL_COMPILATION(RUN_COMPILER),
STORE_BUILD_INFO(INCREMENTAL_COMPILATION),
JAR_SNAPSHOT(INCREMENTAL_COMPILATION),
SET_UP_ABI_SNAPSHOTS(JAR_SNAPSHOT),
IC_ANALYZE_JAR_FILES(JAR_SNAPSHOT),
IC_CALCULATE_INITIAL_DIRTY_SET(INCREMENTAL_COMPILATION),
IC_ANALYZE_CHANGES_IN_DEPENDENCIES(IC_CALCULATE_INITIAL_DIRTY_SET),
IC_FIND_HISTORY_FILES(IC_ANALYZE_CHANGES_IN_DEPENDENCIES),

View File

@@ -30,28 +30,6 @@ class ChangesCollector {
private val changedMembers = hashMapOf<FqName, MutableSet<String>>()
private val areSubclassesAffected = hashMapOf<FqName, Boolean>()
//TODO for test only: ProtoData or ProtoBuf
private val storage = hashMapOf<FqName, ProtoData>()
private val removed = ArrayList<FqName>()
//TODO change to immutable map
fun protoDataChanges(): Map<FqName, ProtoData> = storage
fun protoDataRemoved(): List<FqName> = removed
companion object {
fun <T> T.getNonPrivateNames(nameResolver: NameResolver, vararg members: T.() -> List<MessageLite>) =
members.flatMap { this.it().filterNot { it.isPrivate }.names(nameResolver) }.toSet()
fun ClassProtoData.getNonPrivateMemberNames(): Set<String> {
return proto.getNonPrivateNames(
nameResolver,
ProtoBuf.Class::getConstructorList,
ProtoBuf.Class::getFunctionList,
ProtoBuf.Class::getPropertyList
) + proto.enumEntryList.map { nameResolver.getString(it.name) }
}
}
fun changes(): List<ChangeInfo> {
val changes = arrayListOf<ChangeInfo>()
@@ -79,7 +57,7 @@ class ChangesCollector {
}
private fun <T, R> MutableMap<T, MutableSet<R>>.getSet(key: T) =
getOrPut(key) { HashSet() }
getOrPut(key) { HashSet() }
private fun collectChangedMember(scope: FqName, name: String) {
changedMembers.getSet(scope).add(name)
@@ -101,35 +79,11 @@ class ChangesCollector {
}
}
fun collectProtoChanges(oldData: ProtoData?, newData: ProtoData?, collectAllMembersForNewClass: Boolean = false, packageProtoKey: String? = null) {
fun collectProtoChanges(oldData: ProtoData?, newData: ProtoData?, collectAllMembersForNewClass: Boolean = false) {
if (oldData == null && newData == null) {
throw IllegalStateException("Old and new value are null")
}
if (newData != null) {
when (newData) {
is ClassProtoData -> {
val fqName = newData.nameResolver.getClassId(newData.proto.fqName).asSingleFqName()
storage[fqName] = newData
}
is PackagePartProtoData -> {
//TODO fqName is not unique. It's package and can be present in both java and kotlin
val fqName = newData.packageFqName
storage[packageProtoKey?.let { FqName(it) } ?: fqName] = newData
}
}
} else if (oldData != null) {
when (oldData) {
is ClassProtoData -> {
removed.add(oldData.nameResolver.getClassId(oldData.proto.fqName).asSingleFqName())
}
is PackagePartProtoData -> {
//TODO fqName is not unique. It's package and can be present in both java and kotlin
removed.add(packageProtoKey?.let { FqName(it) } ?: oldData.packageFqName)
}
}
}
if (oldData == null) {
newData!!.collectAll(isRemoved = false, isAdded = true, collectAllMembersForNewClass = collectAllMembersForNewClass)
return
@@ -171,8 +125,8 @@ class ChangesCollector {
}
}
fun <T> T.getNonPrivateNames(nameResolver: NameResolver, vararg members: T.() -> List<MessageLite>) =
members.flatMap { this.it().filterNot { it.isPrivate }.names(nameResolver) }.toSet()
private fun <T> T.getNonPrivateNames(nameResolver: NameResolver, vararg members: T.() -> List<MessageLite>): Set<String> =
members.flatMap { this.it().filterNot { it.isPrivate }.names(nameResolver) }.toSet()
//TODO remember all sealed parent classes
private fun ProtoData.collectAll(isRemoved: Boolean, isAdded: Boolean, collectAllMembersForNewClass: Boolean = false) =
@@ -183,15 +137,16 @@ class ChangesCollector {
private fun PackagePartProtoData.collectAllFromPackage(isRemoved: Boolean) {
val memberNames =
proto.getNonPrivateNames(
nameResolver,
ProtoBuf.Package::getFunctionList,
ProtoBuf.Package::getPropertyList
)
proto.getNonPrivateNames(
nameResolver,
ProtoBuf.Package::getFunctionList,
ProtoBuf.Package::getPropertyList
)
if (isRemoved) {
collectRemovedMembers(packageFqName, memberNames)
} else {
}
else {
collectChangedMembers(packageFqName, memberNames)
}
}
@@ -206,7 +161,8 @@ class ChangesCollector {
val collectMember = if (isRemoved) this@ChangesCollector::collectRemovedMember else this@ChangesCollector::collectChangedMember
collectMember(classFqName.parent(), classFqName.shortName().asString())
memberNames.forEach { collectMember(classFqName, it) }
} else {
}
else {
if (!isRemoved && collectAllMembersForNewClass) {
val memberNames = getNonPrivateMemberNames()
memberNames.forEach { this@ChangesCollector.collectChangedMember(classFqName, it) }
@@ -233,6 +189,15 @@ class ChangesCollector {
addChangedParents(fqName, changedParentsFqNames)
}
private fun ClassProtoData.getNonPrivateMemberNames(): Set<String> {
return proto.getNonPrivateNames(
nameResolver,
ProtoBuf.Class::getConstructorList,
ProtoBuf.Class::getFunctionList,
ProtoBuf.Class::getPropertyList
) + proto.enumEntryList.map { nameResolver.getString(it.name) }
}
fun collectMemberIfValueWasChanged(scope: FqName, name: String, oldValue: Any?, newValue: Any?) {
if (oldValue == null && newValue == null) {
throw IllegalStateException("Old and new value are null for $scope#$name")
@@ -240,7 +205,8 @@ class ChangesCollector {
if (oldValue != null && newValue == null) {
collectRemovedMember(scope, name)
} else if (oldValue != newValue) {
}
else if (oldValue != newValue) {
collectChangedMember(scope, name)
}
}

View File

@@ -1,76 +0,0 @@
/*
* 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.incremental
import org.jetbrains.kotlin.name.FqName
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.io.Serializable
/**
* Changes to the classpath of the `KotlinCompile` task, used to compute the source files that need to be recompiled during an incremental
* run.
*/
sealed class ClasspathChanges : Serializable {
class Available() : ClasspathChanges() {
lateinit var lookupSymbols: List<LookupSymbol>
private set
lateinit var fqNames: List<FqName>
private set
constructor(lookupSymbols: List<LookupSymbol>, fqNames: List<FqName>) : this() {
this.lookupSymbols = lookupSymbols
this.fqNames = fqNames
}
private fun writeObject(out: ObjectOutputStream) {
out.writeInt(lookupSymbols.size)
lookupSymbols.forEach {
out.writeUTF(it.name)
out.writeUTF(it.scope)
}
out.writeInt(fqNames.size)
fqNames.forEach {
out.writeUTF(it.asString())
}
}
private fun readObject(ois: ObjectInputStream) {
val lookupSymbolsSize = ois.readInt()
val lookupSymbols = ArrayList<LookupSymbol>(lookupSymbolsSize)
repeat(lookupSymbolsSize) {
val name = ois.readUTF()
val scope = ois.readUTF()
lookupSymbols.add(LookupSymbol(name, scope))
}
this.lookupSymbols = lookupSymbols
val fqNamesSize = ois.readInt()
val fqNames = ArrayList<FqName>(fqNamesSize)
repeat(fqNamesSize) {
val fqNameString = ois.readUTF()
fqNames.add(FqName(fqNameString))
}
this.fqNames = fqNames
}
companion object {
private const val serialVersionUID = 0L
}
}
sealed class NotAvailable : ClasspathChanges() {
object UnableToCompute : NotAvailable()
object ForNonIncrementalRun : NotAvailable()
object ClasspathSnapshotIsDisabled : NotAvailable()
object ReservedForTestsOnly : NotAvailable()
object ForJSCompiler : NotAvailable()
}
}

View File

@@ -137,8 +137,8 @@ open class IncrementalJsCache(
}
for ((srcFile, irData) in incrementalResults.irFileData) {
val (fileData, types, signatures, strings, declarations, bodies, fqn, debugInfos) = irData
irTranslationResults.put(srcFile, fileData, types, signatures, strings, declarations, bodies, fqn, debugInfos)
val (fileData, types, signatures, strings, declarations, bodies, fqn) = irData
irTranslationResults.put(srcFile, fileData, types, signatures, strings, declarations, bodies, fqn)
}
}
@@ -269,7 +269,6 @@ private object IrTranslationResultValueExternalizer : DataExternalizer<IrTransla
output.writeArray(value.declarations)
output.writeArray(value.bodies)
output.writeArray(value.fqn)
value.debugInfo?.let { output.writeArray(it) }
}
private fun DataOutput.writeArray(array: ByteArray) {
@@ -284,17 +283,6 @@ private object IrTranslationResultValueExternalizer : DataExternalizer<IrTransla
return filedata
}
private fun DataInput.readArrayOrNull(): ByteArray? {
try {
val dataSize = readInt()
val filedata = ByteArray(dataSize)
readFully(filedata)
return filedata
} catch (e: Throwable) {
return null
}
}
override fun read(input: DataInput): IrTranslationResultValue {
val fileData = input.readArray()
val types = input.readArray()
@@ -303,9 +291,8 @@ private object IrTranslationResultValueExternalizer : DataExternalizer<IrTransla
val declarations = input.readArray()
val bodies = input.readArray()
val fqn = input.readArray()
val debugInfos = input.readArrayOrNull()
return IrTranslationResultValue(fileData, types, signatures, strings, declarations, bodies, fqn, debugInfos)
return IrTranslationResultValue(fileData, types, signatures, strings, declarations, bodies, fqn)
}
}
@@ -330,11 +317,10 @@ private class IrTranslationResultMap(
newStrings: ByteArray,
newDeclarations: ByteArray,
newBodies: ByteArray,
fqn: ByteArray,
debugInfos: ByteArray?
fqn: ByteArray
) {
storage[pathConverter.toPath(sourceFile)] =
IrTranslationResultValue(newFiledata, newTypes, newSignatures, newStrings, newDeclarations, newBodies, fqn, debugInfos)
IrTranslationResultValue(newFiledata, newTypes, newSignatures, newStrings, newDeclarations, newBodies, fqn)
}
operator fun get(sourceFile: File): IrTranslationResultValue? =

View File

@@ -25,7 +25,6 @@ import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.build.GeneratedJvmClass
import org.jetbrains.kotlin.incremental.storage.*
import org.jetbrains.kotlin.inline.inlineFunctionsJvmNames
import org.jetbrains.kotlin.load.kotlin.FileBasedKotlinClass
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache
import org.jetbrains.kotlin.load.kotlin.incremental.components.JvmPackagePartProto
@@ -40,11 +39,12 @@ import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import org.jetbrains.org.objectweb.asm.*
import java.io.File
import java.security.MessageDigest
import java.util.*
val KOTLIN_CACHE_DIRECTORY_NAME = "kotlin"
open class IncrementalJvmCache(
targetDataRoot: File,
private val targetDataRoot: File,
targetOutputDir: File?,
pathConverter: FileToPathConverter
) : AbstractIncrementalCache<JvmClassName>(
@@ -114,45 +114,32 @@ open class IncrementalJvmCache(
}
open fun saveFileToCache(generatedClass: GeneratedJvmClass, changesCollector: ChangesCollector) {
saveClassToCache(KotlinClassInfo.createFrom(generatedClass.outputClass), generatedClass.sourceFiles, changesCollector)
}
/**
* Saves information about the given (Kotlin) class to this cache, and stores changes between this class and its previous version into
* the given [ChangesCollector].
*
* @param kotlinClassInfo Information about a Kotlin class
* @param sourceFiles The source files that the given class was generated from, or `null` if this information is not available
* @param changesCollector A [ChangesCollector]
*/
fun saveClassToCache(kotlinClassInfo: KotlinClassInfo, sourceFiles: List<File>?, changesCollector: ChangesCollector) {
val className = kotlinClassInfo.className
val sourceFiles: Collection<File> = generatedClass.sourceFiles
val kotlinClass: LocalFileKotlinClass = generatedClass.outputClass
val className = kotlinClass.className
dirtyOutputClassesMap.notDirty(className)
if (sourceFiles != null) {
sourceFiles.forEach {
sourceToClassesMap.add(it, className)
}
internalNameToSource[className.internalName] = sourceFiles
sourceFiles.forEach {
sourceToClassesMap.add(it, className)
}
if (kotlinClassInfo.classId.isLocal) return
internalNameToSource[className.internalName] = sourceFiles
when (kotlinClassInfo.classKind) {
if (kotlinClass.classId.isLocal) return
val header = kotlinClass.classHeader
when (header.kind) {
KotlinClassHeader.Kind.FILE_FACADE -> {
if (sourceFiles != null) {
assert(sourceFiles.size == 1) { "Package part from several source files: $sourceFiles" }
}
assert(sourceFiles.size == 1) { "Package part from several source files: $sourceFiles" }
packagePartMap.addPackagePart(className)
protoMap.process(kotlinClassInfo, changesCollector)
constantsMap.process(kotlinClassInfo, changesCollector)
inlineFunctionsMap.process(kotlinClassInfo, changesCollector)
protoMap.process(kotlinClass, changesCollector)
constantsMap.process(kotlinClass, changesCollector)
inlineFunctionsMap.process(kotlinClass, changesCollector)
}
KotlinClassHeader.Kind.MULTIFILE_CLASS -> {
val partNames = kotlinClassInfo.classHeaderData.toList()
check(partNames.isNotEmpty()) { "Multifile class has no parts: $className" }
val partNames = kotlinClass.classHeader.data?.toList()
?: throw AssertionError("Multifile class has no parts: ${kotlinClass.className}")
multifileFacadeToParts[className] = partNames
// When a class is replaced with a facade with the same name,
// the class' proto wouldn't ever be deleted,
@@ -167,29 +154,25 @@ open class IncrementalJvmCache(
internalNameToSource.remove(className.internalName)
// TODO NO_CHANGES? (delegates only)
constantsMap.process(kotlinClassInfo, changesCollector)
inlineFunctionsMap.process(kotlinClassInfo, changesCollector)
constantsMap.process(kotlinClass, changesCollector)
inlineFunctionsMap.process(kotlinClass, changesCollector)
}
KotlinClassHeader.Kind.MULTIFILE_CLASS_PART -> {
if (sourceFiles != null) {
assert(sourceFiles.size == 1) { "Multifile class part from several source files: $sourceFiles" }
}
assert(sourceFiles.size == 1) { "Multifile class part from several source files: $sourceFiles" }
packagePartMap.addPackagePart(className)
partToMultifileFacade.set(className.internalName, kotlinClassInfo.multifileClassName!!)
partToMultifileFacade.set(className.internalName, header.multifileClassName!!)
protoMap.process(kotlinClassInfo, changesCollector)
constantsMap.process(kotlinClassInfo, changesCollector)
inlineFunctionsMap.process(kotlinClassInfo, changesCollector)
protoMap.process(kotlinClass, changesCollector)
constantsMap.process(kotlinClass, changesCollector)
inlineFunctionsMap.process(kotlinClass, changesCollector)
}
KotlinClassHeader.Kind.CLASS -> {
if (sourceFiles != null) {
assert(sourceFiles.size == 1) { "Class is expected to have only one source file: $sourceFiles" }
addToClassStorage(kotlinClassInfo, sourceFiles.first())
}
assert(sourceFiles.size == 1) { "Class is expected to have only one source file: $sourceFiles" }
addToClassStorage(kotlinClass, sourceFiles.first())
protoMap.process(kotlinClassInfo, changesCollector)
constantsMap.process(kotlinClassInfo, changesCollector)
inlineFunctionsMap.process(kotlinClassInfo, changesCollector)
protoMap.process(kotlinClass, changesCollector)
constantsMap.process(kotlinClass, changesCollector)
inlineFunctionsMap.process(kotlinClass, changesCollector)
}
KotlinClassHeader.Kind.UNKNOWN, KotlinClassHeader.Kind.SYNTHETIC_CLASS -> {
}
@@ -202,7 +185,7 @@ open class IncrementalJvmCache(
sourceToClassesMap.add(source, jvmClassName)
val (proto, nameResolver) = serializedJavaClass.toProtoData()
addToClassStorage(proto, nameResolver, source)
// collector.addJavaProto(ClassProtoData(proto, nameResolver))
dirtyOutputClassesMap.notDirty(jvmClassName)
}
@@ -295,8 +278,8 @@ open class IncrementalJvmCache(
private inner class ProtoMap(storageFile: File) : BasicStringMap<ProtoMapValue>(storageFile, ProtoMapValueExternalizer) {
@Synchronized
fun process(kotlinClassInfo: KotlinClassInfo, changesCollector: ChangesCollector) {
return put(kotlinClassInfo, changesCollector)
fun process(kotlinClass: LocalFileKotlinClass, changesCollector: ChangesCollector) {
return put(kotlinClass, changesCollector)
}
// A module mapping (.kotlin_module file) is stored in a cache,
@@ -312,18 +295,20 @@ open class IncrementalJvmCache(
}
@Synchronized
private fun put(kotlinClassInfo: KotlinClassInfo, changesCollector: ChangesCollector) {
val key = kotlinClassInfo.className.internalName
private fun put(kotlinClass: LocalFileKotlinClass, changesCollector: ChangesCollector) {
val header = kotlinClass.classHeader
val key = kotlinClass.className.internalName
val oldData = storage[key]
val newData = ProtoMapValue(
kotlinClassInfo.classKind != KotlinClassHeader.Kind.CLASS,
BitEncoding.decodeBytes(kotlinClassInfo.classHeaderData),
kotlinClassInfo.classHeaderStrings
header.kind != KotlinClassHeader.Kind.CLASS,
BitEncoding.decodeBytes(header.data!!),
header.strings!!
)
storage[key] = newData
val packageFqName = kotlinClassInfo.className.packageFqName
changesCollector.collectProtoChanges(oldData?.toProtoData(packageFqName), newData.toProtoData(packageFqName), packageProtoKey = key)
val packageFqName = kotlinClass.className.packageFqName
changesCollector.collectProtoChanges(oldData?.toProtoData(packageFqName), newData.toProtoData(packageFqName))
}
operator fun contains(className: JvmClassName): Boolean =
@@ -382,18 +367,32 @@ open class IncrementalJvmCache(
}
// todo: reuse code with InlineFunctionsMap?
private inner class ConstantsMap(storageFile: File) :
BasicStringMap<LinkedHashMap<String, Any>>(storageFile, LinkedHashMapExternalizer(StringExternalizer, ConstantExternalizer)) {
private inner class ConstantsMap(storageFile: File) : BasicStringMap<Map<String, Any>>(storageFile, ConstantsMapExternalizer) {
private fun getConstantsMap(bytes: ByteArray): Map<String, Any> {
val result = HashMap<String, Any>()
ClassReader(bytes).accept(object : ClassVisitor(Opcodes.API_VERSION) {
override fun visitField(access: Int, name: String, desc: String, signature: String?, value: Any?): FieldVisitor? {
val staticFinal = Opcodes.ACC_STATIC or Opcodes.ACC_FINAL or Opcodes.ACC_PRIVATE
if (value != null && access and staticFinal == Opcodes.ACC_STATIC or Opcodes.ACC_FINAL) {
result[name] = value
}
return null
}
}, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES)
return result
}
operator fun contains(className: JvmClassName): Boolean =
className.internalName in storage
@Synchronized
fun process(kotlinClassInfo: KotlinClassInfo, changesCollector: ChangesCollector) {
val key = kotlinClassInfo.className.internalName
fun process(kotlinClass: LocalFileKotlinClass, changesCollector: ChangesCollector) {
val key = kotlinClass.className.internalName
val oldMap = storage[key] ?: emptyMap()
val newMap = kotlinClassInfo.constantsMap
val newMap = getConstantsMap(kotlinClass.fileContents)
if (newMap.isNotEmpty()) {
storage[key] = newMap
} else {
@@ -402,18 +401,8 @@ open class IncrementalJvmCache(
for (const in oldMap.keys + newMap.keys) {
//Constant can be declared via companion object or via const field declaration
changesCollector.collectMemberIfValueWasChanged(
kotlinClassInfo.scopeFqName(companion = true),
const,
oldMap[const],
newMap[const]
)
changesCollector.collectMemberIfValueWasChanged(
kotlinClassInfo.scopeFqName(companion = false),
const,
oldMap[const],
newMap[const]
)
changesCollector.collectMemberIfValueWasChanged(kotlinClass.scopeFqName(companion = true), const, oldMap[const], newMap[const])
changesCollector.collectMemberIfValueWasChanged(kotlinClass.scopeFqName(companion = false), const, oldMap[const], newMap[const])
}
}
@@ -422,7 +411,7 @@ open class IncrementalJvmCache(
storage.remove(className.internalName)
}
override fun dumpValue(value: LinkedHashMap<String, Any>): String =
override fun dumpValue(value: Map<String, Any>): String =
value.dumpMap(Any::toString)
}
@@ -501,20 +490,67 @@ open class IncrementalJvmCache(
value.dumpCollection()
}
private fun addToClassStorage(classInfo: KotlinClassInfo, srcFile: File) {
val (nameResolver, proto) = JvmProtoBufUtil.readClassDataFrom(classInfo.classHeaderData, classInfo.classHeaderStrings)
private fun addToClassStorage(kotlinClass: LocalFileKotlinClass, srcFile: File) {
val (nameResolver, proto) = JvmProtoBufUtil.readClassDataFrom(kotlinClass.classHeader.data!!, kotlinClass.classHeader.strings!!)
addToClassStorage(proto, nameResolver, srcFile)
}
private inner class InlineFunctionsMap(storageFile: File) :
BasicStringMap<LinkedHashMap<String, Long>>(storageFile, LinkedHashMapExternalizer(StringExternalizer, LongExternalizer)) {
BasicStringMap<Map<String, Long>>(storageFile, StringToLongMapExternalizer) {
private fun getInlineFunctionsMap(header: KotlinClassHeader, bytes: ByteArray): Map<String, Long> {
val inlineFunctions = inlineFunctionsJvmNames(header)
if (inlineFunctions.isEmpty()) return emptyMap()
val result = HashMap<String, Long>()
var dummyVersion: Int = -1
ClassReader(bytes).accept(object : ClassVisitor(Opcodes.API_VERSION) {
override fun visit(
version: Int,
access: Int,
name: String?,
signature: String?,
superName: String?,
interfaces: Array<out String>?
) {
super.visit(version, access, name, signature, superName, interfaces)
dummyVersion = version
}
override fun visitMethod(
access: Int,
name: String,
desc: String,
signature: String?,
exceptions: Array<out String>?
): MethodVisitor? {
val dummyClassWriter = ClassWriter(0)
dummyClassWriter.visit(dummyVersion, 0, "dummy", null, AsmTypes.OBJECT_TYPE.internalName, null)
return object : MethodVisitor(Opcodes.API_VERSION, dummyClassWriter.visitMethod(0, name, desc, null, exceptions)) {
override fun visitEnd() {
val jvmName = name + desc
if (jvmName !in inlineFunctions) return
val dummyBytes = dummyClassWriter.toByteArray()!!
val hash = dummyBytes.md5()
result[jvmName] = hash
}
}
}
}, 0)
return result
}
@Synchronized
fun process(kotlinClassInfo: KotlinClassInfo, changesCollector: ChangesCollector) {
val key = kotlinClassInfo.className.internalName
fun process(kotlinClass: LocalFileKotlinClass, changesCollector: ChangesCollector) {
val key = kotlinClass.className.internalName
val oldMap = storage[key] ?: emptyMap()
val newMap = kotlinClassInfo.inlineFunctionsMap
val newMap = getInlineFunctionsMap(kotlinClass.classHeader, kotlinClass.fileContents)
if (newMap.isNotEmpty()) {
storage[key] = newMap
} else {
@@ -523,7 +559,7 @@ open class IncrementalJvmCache(
for (fn in oldMap.keys + newMap.keys) {
changesCollector.collectMemberIfValueWasChanged(
kotlinClassInfo.scopeFqName(),
kotlinClass.scopeFqName(),
functionNameBySignature(fn),
oldMap[fn],
newMap[fn]
@@ -540,7 +576,7 @@ open class IncrementalJvmCache(
storage.remove(className.internalName)
}
override fun dumpValue(value: LinkedHashMap<String, Long>): String =
override fun dumpValue(value: Map<String, Long>): String =
value.dumpMap { java.lang.Long.toHexString(it) }
}
}
@@ -566,6 +602,13 @@ sealed class ChangeInfo(val fqName: FqName) {
}
}
private fun LocalFileKotlinClass.scopeFqName(companion: Boolean = false) = when (classHeader.kind) {
KotlinClassHeader.Kind.CLASS -> {
className.fqNameForClassNameWithoutDollars.let { if (companion) it.child(DEFAULT_NAME_FOR_COMPANION_OBJECT) else it }
}
else -> className.packageFqName
}
fun ByteArray.md5(): Long {
val d = MessageDigest.getInstance("MD5").digest(this)!!
return ((d[0].toLong() and 0xFFL)
@@ -597,125 +640,3 @@ fun <K : Comparable<K>, V> Map<K, V>.dumpMap(dumpValue: (V) -> String): String =
@TestOnly
fun <T : Comparable<T>> Collection<T>.dumpCollection(): String =
"[${sorted().joinToString(", ", transform = Any::toString)}]"
/**
* Minimal information about a Kotlin class to compute recompilation-triggering changes during an incremental run of the `KotlinCompile`
* task (see [IncrementalJvmCache.saveClassToCache]).
*
* It's important that this class contain only the minimal required information, as it will be part of the classpath snapshot of the
* `KotlinCompile` task and the task needs to support compile avoidance. For example, this class should contain public method signatures,
* and should not contain private method signatures, or method implementations.
*/
class KotlinClassInfo constructor(
val classId: ClassId,
val classKind: KotlinClassHeader.Kind,
val classHeaderData: Array<String>, // Can be empty
val classHeaderStrings: Array<String>, // Can be empty
@Suppress("SpellCheckingInspection") val multifileClassName: String?,
val constantsMap: LinkedHashMap<String, Any>,
val inlineFunctionsMap: LinkedHashMap<String, Long>
) {
val className: JvmClassName by lazy { JvmClassName.byClassId(classId) }
fun scopeFqName(companion: Boolean = false) = when (classKind) {
KotlinClassHeader.Kind.CLASS -> {
className.fqNameForClassNameWithoutDollars.let { if (companion) it.child(DEFAULT_NAME_FOR_COMPANION_OBJECT) else it }
}
else -> className.packageFqName
}
companion object {
fun createFrom(kotlinClass: LocalFileKotlinClass): KotlinClassInfo {
return KotlinClassInfo(
kotlinClass.classId,
kotlinClass.classHeader.kind,
kotlinClass.classHeader.data ?: emptyArray(),
kotlinClass.classHeader.strings ?: emptyArray(),
kotlinClass.classHeader.multifileClassName,
getConstantsMap(kotlinClass.fileContents),
getInlineFunctionsMap(kotlinClass.classHeader, kotlinClass.fileContents)
)
}
/** Creates [KotlinClassInfo] from the given classContents, or returns `null` if the class is not a Kotlin class. */
fun tryCreateFrom(classContents: ByteArray): KotlinClassInfo? {
return FileBasedKotlinClass.create(classContents) { classId, _, classHeader, _ ->
KotlinClassInfo(
classId,
classHeader.kind,
classHeader.data ?: emptyArray(),
classHeader.strings ?: emptyArray(),
classHeader.multifileClassName,
getConstantsMap(classContents),
getInlineFunctionsMap(classHeader, classContents)
)
}
}
}
}
private fun getConstantsMap(bytes: ByteArray): LinkedHashMap<String, Any> {
val result = LinkedHashMap<String, Any>()
ClassReader(bytes).accept(object : ClassVisitor(Opcodes.API_VERSION) {
override fun visitField(access: Int, name: String, desc: String, signature: String?, value: Any?): FieldVisitor? {
val staticFinal = Opcodes.ACC_STATIC or Opcodes.ACC_FINAL or Opcodes.ACC_PRIVATE
if (value != null && access and staticFinal == Opcodes.ACC_STATIC or Opcodes.ACC_FINAL) {
result[name] = value
}
return null
}
}, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES)
return result
}
private fun getInlineFunctionsMap(header: KotlinClassHeader, bytes: ByteArray): LinkedHashMap<String, Long> {
val inlineFunctions = inlineFunctionsJvmNames(header)
if (inlineFunctions.isEmpty()) return LinkedHashMap()
val result = LinkedHashMap<String, Long>()
var dummyVersion: Int = -1
ClassReader(bytes).accept(object : ClassVisitor(Opcodes.API_VERSION) {
override fun visit(
version: Int,
access: Int,
name: String?,
signature: String?,
superName: String?,
interfaces: Array<out String>?
) {
super.visit(version, access, name, signature, superName, interfaces)
dummyVersion = version
}
override fun visitMethod(
access: Int,
name: String,
desc: String,
signature: String?,
exceptions: Array<out String>?
): MethodVisitor {
val dummyClassWriter = ClassWriter(0)
dummyClassWriter.visit(dummyVersion, 0, "dummy", null, AsmTypes.OBJECT_TYPE.internalName, null)
return object : MethodVisitor(Opcodes.API_VERSION, dummyClassWriter.visitMethod(0, name, desc, null, exceptions)) {
override fun visitEnd() {
val jvmName = name + desc
if (jvmName !in inlineFunctions) return
val dummyBytes = dummyClassWriter.toByteArray()!!
val hash = dummyBytes.md5()
result[jvmName] = hash
}
}
}
}, 0)
return result
}

View File

@@ -12,8 +12,7 @@ data class IncrementalModuleEntry(
private val projectPath: String,
val name: String,
val buildDir: File,
val buildHistoryFile: File,
val abiSnapshot: File
val buildHistoryFile: File
) : Serializable {
companion object {
private const val serialVersionUID = 0L
@@ -27,9 +26,7 @@ class IncrementalModuleInfo(
val nameToModules: Map<String, Set<IncrementalModuleEntry>>,
val jarToClassListFile: Map<File, File>,
// only for js and mpp
val jarToModule: Map<File, IncrementalModuleEntry>,
//for JVM only
val jarToAbiSnapshot: Map<File, File>
val jarToModule: Map<File, IncrementalModuleEntry>
) : Serializable {
companion object {
private const val serialVersionUID = 1L

View File

@@ -16,7 +16,6 @@
package org.jetbrains.kotlin.incremental
import com.intellij.openapi.diagnostic.Logger
import com.intellij.util.containers.MultiMap
import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.incremental.components.LookupTracker
@@ -34,8 +33,6 @@ open class LookupStorage(
targetDataDir: File,
pathConverter: FileToPathConverter
) : BasicMapsOwner(targetDataDir) {
val LOG = Logger.getInstance("#org.jetbrains.kotlin.jps.build.KotlinBuilder")
companion object {
private val DELETED_TO_SIZE_TRESHOLD = 0.5
private val MINIMUM_GARBAGE_COLLECTIBLE_SIZE = 10000
@@ -44,49 +41,36 @@ open class LookupStorage(
private val countersFile = "counters".storageFile
private val idToFile = registerMap(IdToFileMap("id-to-file".storageFile, pathConverter))
private val fileToId = registerMap(FileToIdMap("file-to-id".storageFile, pathConverter))
val lookupMap = registerMap(LookupMap("lookups".storageFile))
private val lookupMap = registerMap(LookupMap("lookups".storageFile))
@Volatile
private var size: Int = 0
private var oldSize: Int = 0
@Volatile
private var deletedCount: Int = 0
init {
try {
if (countersFile.exists()) {
val lines = countersFile.readLines()
size = lines.firstOrNull()?.toIntOrNull() ?: throw IOException("$countersFile exists, but it is empty. " +
"Counters file is corrupted")
oldSize = size
size = lines[0].toInt()
deletedCount = lines[1].toInt()
}
} catch (e: IOException) {
throw e
} catch (e: Exception) {
throw IOException("Could not read $countersFile", e)
}
}
@Synchronized
fun get(lookupSymbol: LookupSymbol): Collection<String> {
val key = LookupSymbolKey(lookupSymbol.name, lookupSymbol.scope)
val fileIds = lookupMap[key] ?: return emptySet()
val paths = mutableSetOf<String>()
val filtered = mutableSetOf<Int>()
for (fileId in fileIds) {
val path = idToFile[fileId]?.path
if (path != null) {
paths.add(path)
filtered.add(fileId)
}
return fileIds.mapNotNull {
// null means it's outdated
idToFile[it]?.path
}
if (size > MINIMUM_GARBAGE_COLLECTIBLE_SIZE && filtered.size.toDouble() / fileIds.size.toDouble() < DELETED_TO_SIZE_TRESHOLD) {
lookupMap[key] = filtered
}
return paths
}
@Synchronized
@@ -97,8 +81,8 @@ open class LookupStorage(
val key = LookupSymbolKey(lookupSymbol.name, lookupSymbol.scope)
val paths = lookups[lookupSymbol]
val fileIds = paths.mapTo(TreeSet()) { pathToId[it]!! }
lookupMap.append(key, fileIds)
fileIds.addAll(lookupMap[key] ?: emptySet())
lookupMap[key] = fileIds
}
}
@@ -108,6 +92,7 @@ open class LookupStorage(
val id = fileToId[file] ?: continue
idToFile.remove(id)
fileToId.remove(file)
deletedCount++
}
}
@@ -118,6 +103,7 @@ open class LookupStorage(
}
size = 0
deletedCount = 0
super.clean()
}
@@ -125,17 +111,18 @@ open class LookupStorage(
@Synchronized
override fun flush(memoryCachesOnly: Boolean) {
try {
if (size != oldSize) {
if (size > 0) {
if (!countersFile.exists()) {
countersFile.parentFile.mkdirs()
countersFile.createNewFile()
}
removeGarbageIfNeeded()
countersFile.writeText("$size\n0")
if (size > 0) {
if (!countersFile.exists()) {
countersFile.parentFile.mkdirs()
countersFile.createNewFile()
}
countersFile.writeText("$size\n$deletedCount")
}
} finally {
}
finally {
super.flush(memoryCachesOnly)
}
}
@@ -150,7 +137,13 @@ open class LookupStorage(
return id
}
private fun removeGarbageForTests() {
private fun removeGarbageIfNeeded(force: Boolean = false) {
if (force || (size > MINIMUM_GARBAGE_COLLECTIBLE_SIZE && deletedCount.toDouble() / size > DELETED_TO_SIZE_TRESHOLD)) {
doRemoveGarbage()
}
}
private fun doRemoveGarbage() {
for (hash in lookupMap.keys) {
lookupMap[hash] = lookupMap[hash]!!.filter { it in idToFile }.toSet()
}
@@ -160,6 +153,7 @@ open class LookupStorage(
idToFile.clean()
fileToId.clean()
size = 0
deletedCount = 0
for ((file, oldId) in oldFileToId.entries.sortedBy { it.key.path }) {
val newId = addFileIfNeeded(file)
@@ -171,16 +165,15 @@ open class LookupStorage(
if (fileIds.isEmpty()) {
lookupMap.remove(lookup)
} else {
}
else {
lookupMap[lookup] = fileIds
}
}
}
@TestOnly
fun forceGC() {
removeGarbageForTests()
@TestOnly fun forceGC() {
removeGarbageIfNeeded(force = true)
flush(false)
}

View File

@@ -1104,11 +1104,6 @@ open class ProtoCompareGenerated(
if (!checkEquals(old.setter, new.setter)) return false
}
if (old.hasDelegateMethod() != new.hasDelegateMethod()) return false
if (old.hasDelegateMethod()) {
if (!checkEquals(old.delegateMethod, new.delegateMethod)) return false
}
return true
}
@@ -2358,10 +2353,6 @@ fun JvmProtoBuf.JvmPropertySignature.hashCode(stringIndexes: (Int) -> Int, fqNam
hashCode = 31 * hashCode + setter.hashCode(stringIndexes, fqNameIndexes, typeById)
}
if (hasDelegateMethod()) {
hashCode = 31 * hashCode + delegateMethod.hashCode(stringIndexes, fqNameIndexes, typeById)
}
return hashCode
}

View File

@@ -255,23 +255,22 @@ fun withSubtypes(
typeFqName: FqName,
caches: Iterable<IncrementalCacheCommon>
): Set<FqName> {
val typesToProccess = LinkedHashSet(listOf(typeFqName))
val proccessedTypes = hashSetOf<FqName>()
val types = LinkedHashSet(listOf(typeFqName))
val subtypes = hashSetOf<FqName>()
while (typesToProccess.isNotEmpty()) {
val iterator = typesToProccess.iterator()
while (types.isNotEmpty()) {
val iterator = types.iterator()
val unprocessedType = iterator.next()
iterator.remove()
caches.asSequence()
.flatMap { it.getSubtypesOf(unprocessedType) }
.filter { it !in proccessedTypes }
.forEach { typesToProccess.add(it) }
.filter { it !in subtypes }
.forEach { types.add(it) }
proccessedTypes.add(unprocessedType)
subtypes.add(unprocessedType)
}
return proccessedTypes
return subtypes
}

View File

@@ -33,19 +33,15 @@ class CachingLazyStorage<K, V>(
private val valueExternalizer: DataExternalizer<V>
) : LazyStorage<K, V> {
private var storage: PersistentHashMap<K, V>? = null
private var isStorageFileExist = true
private fun getStorageIfExists(): PersistentHashMap<K, V>? {
if (storage != null) return storage
if (!isStorageFileExist) return null
if (storageFile.exists()) {
storage = createMap()
return storage
}
isStorageFileExist = false
return null
}

View File

@@ -18,7 +18,7 @@ package org.jetbrains.kotlin.incremental.storage
import java.io.File
class LookupMap(storage: File) : BasicMap<LookupSymbolKey, Collection<Int>>(storage, LookupSymbolKeyDescriptor, IntCollectionExternalizer) {
internal class LookupMap(storage: File) : BasicMap<LookupSymbolKey, Collection<Int>>(storage, LookupSymbolKeyDescriptor, IntCollectionExternalizer) {
override fun dumpKey(key: LookupSymbolKey): String = key.toString()
override fun dumpValue(value: Collection<Int>): String = value.toString()
@@ -27,10 +27,6 @@ class LookupMap(storage: File) : BasicMap<LookupSymbolKey, Collection<Int>>(stor
storage.append(LookupSymbolKey(name, scope), listOf(fileId))
}
fun append(lookup: LookupSymbolKey, fileIds: Collection<Int>) {
storage.append(lookup, fileIds)
}
operator fun get(key: LookupSymbolKey): Collection<Int>? = storage[key]
operator fun set(key: LookupSymbolKey, fileIds: Set<Int>) {

View File

@@ -17,53 +17,27 @@
package org.jetbrains.kotlin.incremental.storage
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.text.StringUtil
import com.intellij.util.io.DataExternalizer
import com.intellij.util.io.EnumeratorStringDescriptor
import com.intellij.util.io.IOUtil
import com.intellij.util.io.KeyDescriptor
import org.jetbrains.kotlin.cli.common.CompilerSystemProperties
import org.jetbrains.kotlin.cli.common.toBooleanLenient
import java.io.DataInput
import java.io.DataInputStream
import java.io.DataOutput
import java.io.File
import java.util.*
/**
* Storage versioning:
* 0 - only name and value hashes are saved
* 1 - name and scope are saved
*/
object LookupSymbolKeyDescriptor : KeyDescriptor<LookupSymbolKey> {
override fun read(input: DataInput): LookupSymbolKey {
val version = input.readByte()
return when (version.toInt()) {
0 -> {
val name = input.readUTF()
val scope = input.readUTF()
LookupSymbolKey(name.hashCode(), scope.hashCode(), name, scope)
}
1 -> {
val first = input.readInt()
val second = input.readInt()
LookupSymbolKey(first, second, "", "")
}
else -> throw RuntimeException("Unknown version of LookupSymbolKeyDescriptor=${version}")
}
val first = input.readInt()
val second = input.readInt()
return LookupSymbolKey(first, second)
}
private val storeFullFqName = CompilerSystemProperties.COMPILE_INCREMENTAL_WITH_CLASSPATH_SHAPSHOTS.value.toBooleanLenient() ?: false
override fun save(output: DataOutput, value: LookupSymbolKey) {
if (storeFullFqName) {
output.writeByte(0)
output.writeUTF(value.name)
output.writeUTF(value.scope)
} else {
output.writeByte(1)
output.writeInt(value.nameHash)
output.writeInt(value.scopeHash)
}
output.writeInt(value.nameHash)
output.writeInt(value.scopeHash)
}
override fun getHashCode(value: LookupSymbolKey): Int = value.hashCode()
@@ -94,12 +68,13 @@ object ProtoMapValueExternalizer : DataExternalizer<ProtoMapValue> {
}
}
abstract class StringMapExternalizer<T> : DataExternalizer<Map<String, T>> {
override fun save(output: DataOutput, map: Map<String, T>?) {
output.writeInt(map!!.size)
for ((key, value) in map.entries) {
output.writeString(key)
IOUtil.writeString(key, output)
writeValue(output, value)
}
}
@@ -109,7 +84,7 @@ abstract class StringMapExternalizer<T> : DataExternalizer<Map<String, T>> {
val map = HashMap<String, T>(size)
repeat(size) {
val name = input.readString()
val name = IOUtil.readString(input)!!
map[name] = readValue(input)
}
@@ -120,6 +95,7 @@ abstract class StringMapExternalizer<T> : DataExternalizer<Map<String, T>> {
protected abstract fun readValue(input: DataInput): T
}
object StringToLongMapExternalizer : StringMapExternalizer<Long>() {
override fun readValue(input: DataInput): Long = input.readLong()
@@ -128,43 +104,58 @@ object StringToLongMapExternalizer : StringMapExternalizer<Long>() {
}
}
/** [DataExternalizer] for a Kotlin constant. */
object ConstantExternalizer : DataExternalizer<Any> {
override fun save(output: DataOutput, value: Any) {
when (value) {
is Int -> {
output.writeByte(Kind.INT.ordinal)
output.writeInt(value)
object ConstantsMapExternalizer : DataExternalizer<Map<String, Any>> {
override fun save(output: DataOutput, map: Map<String, Any>?) {
output.writeInt(map!!.size)
for (name in map.keys.sorted()) {
IOUtil.writeString(name, output)
val value = map[name]!!
when (value) {
is Int -> {
output.writeByte(Kind.INT.ordinal)
output.writeInt(value)
}
is Float -> {
output.writeByte(Kind.FLOAT.ordinal)
output.writeFloat(value)
}
is Long -> {
output.writeByte(Kind.LONG.ordinal)
output.writeLong(value)
}
is Double -> {
output.writeByte(Kind.DOUBLE.ordinal)
output.writeDouble(value)
}
is String -> {
output.writeByte(Kind.STRING.ordinal)
IOUtil.writeString(value, output)
}
else -> throw IllegalStateException("Unexpected constant class: ${value::class.java}")
}
is Float -> {
output.writeByte(Kind.FLOAT.ordinal)
output.writeFloat(value)
}
is Long -> {
output.writeByte(Kind.LONG.ordinal)
output.writeLong(value)
}
is Double -> {
output.writeByte(Kind.DOUBLE.ordinal)
output.writeDouble(value)
}
is String -> {
output.writeByte(Kind.STRING.ordinal)
output.writeString(value)
}
else -> throw IllegalStateException("Unexpected constant class: ${value::class.java}")
}
}
override fun read(input: DataInput): Any {
return when (Kind.values()[input.readByte().toInt()]) {
Kind.INT -> input.readInt()
Kind.FLOAT -> input.readFloat()
Kind.LONG -> input.readLong()
Kind.DOUBLE -> input.readDouble()
Kind.STRING -> input.readString()
override fun read(input: DataInput): Map<String, Any>? {
val size = input.readInt()
val map = HashMap<String, Any>(size)
repeat(size) {
val name = IOUtil.readString(input)!!
val kind = Kind.values()[input.readByte().toInt()]
val value: Any = when (kind) {
Kind.INT -> input.readInt()
Kind.FLOAT -> input.readFloat()
Kind.LONG -> input.readLong()
Kind.DOUBLE -> input.readDouble()
Kind.STRING -> IOUtil.readString(input)!!
}
map[name] = value
}
return map
}
private enum class Kind {
@@ -173,47 +164,22 @@ object ConstantExternalizer : DataExternalizer<Any> {
}
object IntExternalizer : DataExternalizer<Int> {
override fun save(output: DataOutput, value: Int) = output.writeInt(value)
override fun read(input: DataInput): Int = input.readInt()
override fun save(output: DataOutput, value: Int) {
output.writeInt(value)
}
}
object LongExternalizer : DataExternalizer<Long> {
override fun save(output: DataOutput, value: Long) = output.writeLong(value)
override fun read(input: DataInput): Long = input.readLong()
}
object StringExternalizer : DataExternalizer<String> {
override fun save(output: DataOutput, value: String) = IOUtil.writeString(value, output)
override fun read(input: DataInput): String = IOUtil.readString(input)
}
// Should be consistent with org.jetbrains.jps.incremental.storage.PathStringDescriptor for correct work of portable caches
object PathStringDescriptor : EnumeratorStringDescriptor() {
private const val PORTABLE_CACHES_PROPERTY = "org.jetbrains.jps.portable.caches"
private val PORTABLE_CACHES = java.lang.Boolean.getBoolean(PORTABLE_CACHES_PROPERTY)
override fun getHashCode(value: String) = FileUtil.pathHashCode(value)
override fun getHashCode(path: String): Int {
if (!PORTABLE_CACHES) return FileUtil.pathHashCode(path)
// On case insensitive OS hash calculated from value converted to lower case
return if (StringUtil.isEmpty(path)) 0 else FileUtil.toCanonicalPath(path).hashCode()
}
override fun isEqual(val1: String, val2: String?): Boolean {
if (!PORTABLE_CACHES) return FileUtil.pathsEqual(val1, val2)
// On case insensitive OS hash calculated from path converted to lower case
if (val1 == val2) return true
if (val2 == null) return false
val path1 = FileUtil.toCanonicalPath(val1)
val path2 = FileUtil.toCanonicalPath(val2)
return path1 == path2
}
override fun isEqual(val1: String, val2: String?) = FileUtil.pathsEqual(val1, val2)
}
open class CollectionExternalizer<T>(
private val elementExternalizer: DataExternalizer<T>,
private val newCollection: () -> MutableCollection<T>
private val elementExternalizer: DataExternalizer<T>,
private val newCollection: () -> MutableCollection<T>
) : DataExternalizer<Collection<T>> {
override fun read(input: DataInput): Collection<T> {
val result = newCollection()
@@ -234,69 +200,3 @@ open class CollectionExternalizer<T>(
object StringCollectionExternalizer : CollectionExternalizer<String>(EnumeratorStringDescriptor(), { HashSet() })
object IntCollectionExternalizer : CollectionExternalizer<Int>(IntExternalizer, { HashSet() })
fun DataOutput.writeString(value: String) = StringExternalizer.save(this, value)
fun DataInput.readString(): String = StringExternalizer.read(this)
class ListExternalizer<T>(
private val elementExternalizer: DataExternalizer<T>
) : DataExternalizer<List<T>> {
override fun save(output: DataOutput, value: List<T>) {
output.writeInt(value.size)
value.forEach {
elementExternalizer.save(output, it)
}
}
override fun read(input: DataInput): List<T> {
val size = input.readInt()
val list = ArrayList<T>(size)
repeat(size) {
list.add(elementExternalizer.read(input))
}
return list
}
}
class LinkedHashMapExternalizer<K, V>(
private val keyExternalizer: DataExternalizer<K>,
private val valueExternalizer: DataExternalizer<V>
) : DataExternalizer<LinkedHashMap<K, V>> {
override fun save(output: DataOutput, map: LinkedHashMap<K, V>) {
output.writeInt(map.size)
for ((key, value) in map) {
keyExternalizer.save(output, key)
valueExternalizer.save(output, value)
}
}
override fun read(input: DataInput): LinkedHashMap<K, V> {
val size = input.readInt()
val map = LinkedHashMap<K, V>(size)
repeat(size) {
val key = keyExternalizer.read(input)
val value = valueExternalizer.read(input)
map[key] = value
}
return map
}
}
class NullableValueExternalizer<T>(private val valueExternalizer: DataExternalizer<T>) : DataExternalizer<T> {
override fun save(output: DataOutput, value: T?) {
output.writeBoolean(value != null)
value?.let {
valueExternalizer.save(output, it)
}
}
override fun read(input: DataInput): T? {
return if (input.readBoolean()) {
valueExternalizer.read(input)
} else null
}
}

View File

@@ -16,8 +16,8 @@
package org.jetbrains.kotlin.incremental.storage
data class LookupSymbolKey(val nameHash: Int, val scopeHash: Int, val name:String, val scope:String) : Comparable<LookupSymbolKey> {
constructor(name: String, scope: String) : this(name.hashCode(), scope.hashCode(), name, scope)
data class LookupSymbolKey(val nameHash: Int, val scopeHash: Int) : Comparable<LookupSymbolKey> {
constructor(name: String, scope: String) : this(name.hashCode(), scope.hashCode())
override fun compareTo(other: LookupSymbolKey): Int {
val nameCmp = nameHash.compareTo(other.nameHash)
@@ -26,26 +26,6 @@ data class LookupSymbolKey(val nameHash: Int, val scopeHash: Int, val name:Strin
return scopeHash.compareTo(other.scopeHash)
}
override fun hashCode(): Int {
var result = nameHash
result = 31 * result + scopeHash
return result
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as LookupSymbolKey
if (nameHash != other.nameHash) return false
if (scopeHash != other.scopeHash) return false
return true
}
}
data class ProtoMapValue(val isPackageFacade: Boolean, val bytes: ByteArray, val strings: Array<String>)

View File

@@ -3443,34 +3443,6 @@ public final class DebugJvmProtoBuf {
* <code>optional .org.jetbrains.kotlin.metadata.jvm.JvmMethodSignature setter = 4;</code>
*/
org.jetbrains.kotlin.metadata.jvm.DebugJvmProtoBuf.JvmMethodSignatureOrBuilder getSetterOrBuilder();
/**
* <code>optional .org.jetbrains.kotlin.metadata.jvm.JvmMethodSignature delegate_method = 5;</code>
*
* <pre>
* The delegate field of delegated properties may be optimized out; `getDelegate` should
* then call this method instead
* </pre>
*/
boolean hasDelegateMethod();
/**
* <code>optional .org.jetbrains.kotlin.metadata.jvm.JvmMethodSignature delegate_method = 5;</code>
*
* <pre>
* The delegate field of delegated properties may be optimized out; `getDelegate` should
* then call this method instead
* </pre>
*/
org.jetbrains.kotlin.metadata.jvm.DebugJvmProtoBuf.JvmMethodSignature getDelegateMethod();
/**
* <code>optional .org.jetbrains.kotlin.metadata.jvm.JvmMethodSignature delegate_method = 5;</code>
*
* <pre>
* The delegate field of delegated properties may be optimized out; `getDelegate` should
* then call this method instead
* </pre>
*/
org.jetbrains.kotlin.metadata.jvm.DebugJvmProtoBuf.JvmMethodSignatureOrBuilder getDelegateMethodOrBuilder();
}
/**
* Protobuf type {@code org.jetbrains.kotlin.metadata.jvm.JvmPropertySignature}
@@ -3576,19 +3548,6 @@ public final class DebugJvmProtoBuf {
bitField0_ |= 0x00000008;
break;
}
case 42: {
org.jetbrains.kotlin.metadata.jvm.DebugJvmProtoBuf.JvmMethodSignature.Builder subBuilder = null;
if (((bitField0_ & 0x00000010) == 0x00000010)) {
subBuilder = delegateMethod_.toBuilder();
}
delegateMethod_ = input.readMessage(org.jetbrains.kotlin.metadata.jvm.DebugJvmProtoBuf.JvmMethodSignature.PARSER, extensionRegistry);
if (subBuilder != null) {
subBuilder.mergeFrom(delegateMethod_);
delegateMethod_ = subBuilder.buildPartial();
}
bitField0_ |= 0x00000010;
break;
}
}
}
} catch (org.jetbrains.kotlin.protobuf.InvalidProtocolBufferException e) {
@@ -3725,48 +3684,11 @@ public final class DebugJvmProtoBuf {
return setter_;
}
public static final int DELEGATE_METHOD_FIELD_NUMBER = 5;
private org.jetbrains.kotlin.metadata.jvm.DebugJvmProtoBuf.JvmMethodSignature delegateMethod_;
/**
* <code>optional .org.jetbrains.kotlin.metadata.jvm.JvmMethodSignature delegate_method = 5;</code>
*
* <pre>
* The delegate field of delegated properties may be optimized out; `getDelegate` should
* then call this method instead
* </pre>
*/
public boolean hasDelegateMethod() {
return ((bitField0_ & 0x00000010) == 0x00000010);
}
/**
* <code>optional .org.jetbrains.kotlin.metadata.jvm.JvmMethodSignature delegate_method = 5;</code>
*
* <pre>
* The delegate field of delegated properties may be optimized out; `getDelegate` should
* then call this method instead
* </pre>
*/
public org.jetbrains.kotlin.metadata.jvm.DebugJvmProtoBuf.JvmMethodSignature getDelegateMethod() {
return delegateMethod_;
}
/**
* <code>optional .org.jetbrains.kotlin.metadata.jvm.JvmMethodSignature delegate_method = 5;</code>
*
* <pre>
* The delegate field of delegated properties may be optimized out; `getDelegate` should
* then call this method instead
* </pre>
*/
public org.jetbrains.kotlin.metadata.jvm.DebugJvmProtoBuf.JvmMethodSignatureOrBuilder getDelegateMethodOrBuilder() {
return delegateMethod_;
}
private void initFields() {
field_ = org.jetbrains.kotlin.metadata.jvm.DebugJvmProtoBuf.JvmFieldSignature.getDefaultInstance();
syntheticMethod_ = org.jetbrains.kotlin.metadata.jvm.DebugJvmProtoBuf.JvmMethodSignature.getDefaultInstance();
getter_ = org.jetbrains.kotlin.metadata.jvm.DebugJvmProtoBuf.JvmMethodSignature.getDefaultInstance();
setter_ = org.jetbrains.kotlin.metadata.jvm.DebugJvmProtoBuf.JvmMethodSignature.getDefaultInstance();
delegateMethod_ = org.jetbrains.kotlin.metadata.jvm.DebugJvmProtoBuf.JvmMethodSignature.getDefaultInstance();
}
private byte memoizedIsInitialized = -1;
public final boolean isInitialized() {
@@ -3793,9 +3715,6 @@ public final class DebugJvmProtoBuf {
if (((bitField0_ & 0x00000008) == 0x00000008)) {
output.writeMessage(4, setter_);
}
if (((bitField0_ & 0x00000010) == 0x00000010)) {
output.writeMessage(5, delegateMethod_);
}
getUnknownFields().writeTo(output);
}
@@ -3821,10 +3740,6 @@ public final class DebugJvmProtoBuf {
size += org.jetbrains.kotlin.protobuf.CodedOutputStream
.computeMessageSize(4, setter_);
}
if (((bitField0_ & 0x00000010) == 0x00000010)) {
size += org.jetbrains.kotlin.protobuf.CodedOutputStream
.computeMessageSize(5, delegateMethod_);
}
size += getUnknownFields().getSerializedSize();
memoizedSerializedSize = size;
return size;
@@ -3938,7 +3853,6 @@ public final class DebugJvmProtoBuf {
getSyntheticMethodFieldBuilder();
getGetterFieldBuilder();
getSetterFieldBuilder();
getDelegateMethodFieldBuilder();
}
}
private static Builder create() {
@@ -3971,12 +3885,6 @@ public final class DebugJvmProtoBuf {
setterBuilder_.clear();
}
bitField0_ = (bitField0_ & ~0x00000008);
if (delegateMethodBuilder_ == null) {
delegateMethod_ = org.jetbrains.kotlin.metadata.jvm.DebugJvmProtoBuf.JvmMethodSignature.getDefaultInstance();
} else {
delegateMethodBuilder_.clear();
}
bitField0_ = (bitField0_ & ~0x00000010);
return this;
}
@@ -4037,14 +3945,6 @@ public final class DebugJvmProtoBuf {
} else {
result.setter_ = setterBuilder_.build();
}
if (((from_bitField0_ & 0x00000010) == 0x00000010)) {
to_bitField0_ |= 0x00000010;
}
if (delegateMethodBuilder_ == null) {
result.delegateMethod_ = delegateMethod_;
} else {
result.delegateMethod_ = delegateMethodBuilder_.build();
}
result.bitField0_ = to_bitField0_;
onBuilt();
return result;
@@ -4073,9 +3973,6 @@ public final class DebugJvmProtoBuf {
if (other.hasSetter()) {
mergeSetter(other.getSetter());
}
if (other.hasDelegateMethod()) {
mergeDelegateMethod(other.getDelegateMethod());
}
this.mergeUnknownFields(other.getUnknownFields());
return this;
}
@@ -4603,167 +4500,6 @@ public final class DebugJvmProtoBuf {
return setterBuilder_;
}
private org.jetbrains.kotlin.metadata.jvm.DebugJvmProtoBuf.JvmMethodSignature delegateMethod_ = org.jetbrains.kotlin.metadata.jvm.DebugJvmProtoBuf.JvmMethodSignature.getDefaultInstance();
private org.jetbrains.kotlin.protobuf.SingleFieldBuilder<
org.jetbrains.kotlin.metadata.jvm.DebugJvmProtoBuf.JvmMethodSignature, org.jetbrains.kotlin.metadata.jvm.DebugJvmProtoBuf.JvmMethodSignature.Builder, org.jetbrains.kotlin.metadata.jvm.DebugJvmProtoBuf.JvmMethodSignatureOrBuilder> delegateMethodBuilder_;
/**
* <code>optional .org.jetbrains.kotlin.metadata.jvm.JvmMethodSignature delegate_method = 5;</code>
*
* <pre>
* The delegate field of delegated properties may be optimized out; `getDelegate` should
* then call this method instead
* </pre>
*/
public boolean hasDelegateMethod() {
return ((bitField0_ & 0x00000010) == 0x00000010);
}
/**
* <code>optional .org.jetbrains.kotlin.metadata.jvm.JvmMethodSignature delegate_method = 5;</code>
*
* <pre>
* The delegate field of delegated properties may be optimized out; `getDelegate` should
* then call this method instead
* </pre>
*/
public org.jetbrains.kotlin.metadata.jvm.DebugJvmProtoBuf.JvmMethodSignature getDelegateMethod() {
if (delegateMethodBuilder_ == null) {
return delegateMethod_;
} else {
return delegateMethodBuilder_.getMessage();
}
}
/**
* <code>optional .org.jetbrains.kotlin.metadata.jvm.JvmMethodSignature delegate_method = 5;</code>
*
* <pre>
* The delegate field of delegated properties may be optimized out; `getDelegate` should
* then call this method instead
* </pre>
*/
public Builder setDelegateMethod(org.jetbrains.kotlin.metadata.jvm.DebugJvmProtoBuf.JvmMethodSignature value) {
if (delegateMethodBuilder_ == null) {
if (value == null) {
throw new NullPointerException();
}
delegateMethod_ = value;
onChanged();
} else {
delegateMethodBuilder_.setMessage(value);
}
bitField0_ |= 0x00000010;
return this;
}
/**
* <code>optional .org.jetbrains.kotlin.metadata.jvm.JvmMethodSignature delegate_method = 5;</code>
*
* <pre>
* The delegate field of delegated properties may be optimized out; `getDelegate` should
* then call this method instead
* </pre>
*/
public Builder setDelegateMethod(
org.jetbrains.kotlin.metadata.jvm.DebugJvmProtoBuf.JvmMethodSignature.Builder builderForValue) {
if (delegateMethodBuilder_ == null) {
delegateMethod_ = builderForValue.build();
onChanged();
} else {
delegateMethodBuilder_.setMessage(builderForValue.build());
}
bitField0_ |= 0x00000010;
return this;
}
/**
* <code>optional .org.jetbrains.kotlin.metadata.jvm.JvmMethodSignature delegate_method = 5;</code>
*
* <pre>
* The delegate field of delegated properties may be optimized out; `getDelegate` should
* then call this method instead
* </pre>
*/
public Builder mergeDelegateMethod(org.jetbrains.kotlin.metadata.jvm.DebugJvmProtoBuf.JvmMethodSignature value) {
if (delegateMethodBuilder_ == null) {
if (((bitField0_ & 0x00000010) == 0x00000010) &&
delegateMethod_ != org.jetbrains.kotlin.metadata.jvm.DebugJvmProtoBuf.JvmMethodSignature.getDefaultInstance()) {
delegateMethod_ =
org.jetbrains.kotlin.metadata.jvm.DebugJvmProtoBuf.JvmMethodSignature.newBuilder(delegateMethod_).mergeFrom(value).buildPartial();
} else {
delegateMethod_ = value;
}
onChanged();
} else {
delegateMethodBuilder_.mergeFrom(value);
}
bitField0_ |= 0x00000010;
return this;
}
/**
* <code>optional .org.jetbrains.kotlin.metadata.jvm.JvmMethodSignature delegate_method = 5;</code>
*
* <pre>
* The delegate field of delegated properties may be optimized out; `getDelegate` should
* then call this method instead
* </pre>
*/
public Builder clearDelegateMethod() {
if (delegateMethodBuilder_ == null) {
delegateMethod_ = org.jetbrains.kotlin.metadata.jvm.DebugJvmProtoBuf.JvmMethodSignature.getDefaultInstance();
onChanged();
} else {
delegateMethodBuilder_.clear();
}
bitField0_ = (bitField0_ & ~0x00000010);
return this;
}
/**
* <code>optional .org.jetbrains.kotlin.metadata.jvm.JvmMethodSignature delegate_method = 5;</code>
*
* <pre>
* The delegate field of delegated properties may be optimized out; `getDelegate` should
* then call this method instead
* </pre>
*/
public org.jetbrains.kotlin.metadata.jvm.DebugJvmProtoBuf.JvmMethodSignature.Builder getDelegateMethodBuilder() {
bitField0_ |= 0x00000010;
onChanged();
return getDelegateMethodFieldBuilder().getBuilder();
}
/**
* <code>optional .org.jetbrains.kotlin.metadata.jvm.JvmMethodSignature delegate_method = 5;</code>
*
* <pre>
* The delegate field of delegated properties may be optimized out; `getDelegate` should
* then call this method instead
* </pre>
*/
public org.jetbrains.kotlin.metadata.jvm.DebugJvmProtoBuf.JvmMethodSignatureOrBuilder getDelegateMethodOrBuilder() {
if (delegateMethodBuilder_ != null) {
return delegateMethodBuilder_.getMessageOrBuilder();
} else {
return delegateMethod_;
}
}
/**
* <code>optional .org.jetbrains.kotlin.metadata.jvm.JvmMethodSignature delegate_method = 5;</code>
*
* <pre>
* The delegate field of delegated properties may be optimized out; `getDelegate` should
* then call this method instead
* </pre>
*/
private org.jetbrains.kotlin.protobuf.SingleFieldBuilder<
org.jetbrains.kotlin.metadata.jvm.DebugJvmProtoBuf.JvmMethodSignature, org.jetbrains.kotlin.metadata.jvm.DebugJvmProtoBuf.JvmMethodSignature.Builder, org.jetbrains.kotlin.metadata.jvm.DebugJvmProtoBuf.JvmMethodSignatureOrBuilder>
getDelegateMethodFieldBuilder() {
if (delegateMethodBuilder_ == null) {
delegateMethodBuilder_ = new org.jetbrains.kotlin.protobuf.SingleFieldBuilder<
org.jetbrains.kotlin.metadata.jvm.DebugJvmProtoBuf.JvmMethodSignature, org.jetbrains.kotlin.metadata.jvm.DebugJvmProtoBuf.JvmMethodSignature.Builder, org.jetbrains.kotlin.metadata.jvm.DebugJvmProtoBuf.JvmMethodSignatureOrBuilder>(
getDelegateMethod(),
getParentForChildren(),
isClean());
delegateMethod_ = null;
}
return delegateMethodBuilder_;
}
// @@protoc_insertion_point(builder_scope:org.jetbrains.kotlin.metadata.jvm.JvmPropertySignature)
}
@@ -5004,7 +4740,7 @@ public final class DebugJvmProtoBuf {
"\020DESC_TO_CLASS_ID\020\002\"<\n\022JvmMethodSignatur" +
"e\022\022\n\004name\030\001 \001(\005B\004\230\265\030\001\022\022\n\004desc\030\002 \001(\005B\004\230\265\030" +
"\001\";\n\021JvmFieldSignature\022\022\n\004name\030\001 \001(\005B\004\230\265" +
"\030\001\022\022\n\004desc\030\002 \001(\005B\004\230\265\030\001\"\212\003\n\024JvmPropertySi" +
"\030\001\022\022\n\004desc\030\002 \001(\005B\004\230\265\030\001\"\272\002\n\024JvmPropertySi" +
"gnature\022C\n\005field\030\001 \001(\01324.org.jetbrains.k" +
"otlin.metadata.jvm.JvmFieldSignature\022O\n\020",
"synthetic_method\030\002 \001(\01325.org.jetbrains.k" +
@@ -5012,13 +4748,11 @@ public final class DebugJvmProtoBuf {
"\006getter\030\003 \001(\01325.org.jetbrains.kotlin.met" +
"adata.jvm.JvmMethodSignature\022E\n\006setter\030\004" +
" \001(\01325.org.jetbrains.kotlin.metadata.jvm" +
".JvmMethodSignature\022N\n\017delegate_method\030\005" +
" \001(\01325.org.jetbrains.kotlin.metadata.jvm" +
".JvmMethodSignature:\200\001\n\025constructor_sign" +
"ature\022*.org.jetbrains.kotlin.metadata.Co" +
"nstructor\030d \001(\01325.org.jetbrains.kotlin.m",
"nstructor\030d \001(\01325.org.jetbrains.kotlin.m" +
"etadata.jvm.JvmMethodSignature:x\n\020method" +
"_signature\022\'.org.jetbrains.kotlin.metada" +
"_signature\022\'.org.jetbrains.kotlin.metada",
"ta.Function\030d \001(\01325.org.jetbrains.kotlin" +
".metadata.jvm.JvmMethodSignature:O\n\030lamb" +
"da_class_origin_name\022\'.org.jetbrains.kot" +
@@ -5026,9 +4760,9 @@ public final class DebugJvmProtoBuf {
"perty_signature\022\'.org.jetbrains.kotlin.m" +
"etadata.Property\030d \001(\01327.org.jetbrains.k" +
"otlin.metadata.jvm.JvmPropertySignature:" +
"9\n\005flags\022\'.org.jetbrains.kotlin.metadata",
"9\n\005flags\022\'.org.jetbrains.kotlin.metadata" +
".Property\030e \001(\005:\0010:g\n\017type_annotation\022#." +
"org.jetbrains.kotlin.metadata.Type\030d \003(\013" +
"org.jetbrains.kotlin.metadata.Type\030d \003(\013",
"2).org.jetbrains.kotlin.metadata.Annotat" +
"ion:3\n\006is_raw\022#.org.jetbrains.kotlin.met" +
"adata.Type\030e \001(\010:z\n\031type_parameter_annot" +
@@ -5036,9 +4770,9 @@ public final class DebugJvmProtoBuf {
"peParameter\030d \003(\0132).org.jetbrains.kotlin" +
".metadata.Annotation:E\n\021class_module_nam" +
"e\022$.org.jetbrains.kotlin.metadata.Class\030" +
"e \001(\005B\004\230\265\030\001:k\n\024class_local_variable\022$.or",
"e \001(\005B\004\230\265\030\001:k\n\024class_local_variable\022$.or" +
"g.jetbrains.kotlin.metadata.Class\030f \003(\0132" +
"\'.org.jetbrains.kotlin.metadata.Property" +
"\'.org.jetbrains.kotlin.metadata.Property",
":P\n\034anonymous_object_origin_name\022$.org.j" +
"etbrains.kotlin.metadata.Class\030g \001(\005B\004\230\265" +
"\030\001:@\n\017jvm_class_flags\022$.org.jetbrains.ko" +
@@ -5046,7 +4780,7 @@ public final class DebugJvmProtoBuf {
"module_name\022&.org.jetbrains.kotlin.metad" +
"ata.Package\030e \001(\005B\004\230\265\030\001:o\n\026package_local" +
"_variable\022&.org.jetbrains.kotlin.metadat" +
"a.Package\030f \003(\0132\'.org.jetbrains.kotlin.m",
"a.Package\030f \003(\0132\'.org.jetbrains.kotlin.m" +
"etadata.PropertyB\022B\020DebugJvmProtoBuf"
};
org.jetbrains.kotlin.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
@@ -5092,7 +4826,7 @@ public final class DebugJvmProtoBuf {
internal_static_org_jetbrains_kotlin_metadata_jvm_JvmPropertySignature_fieldAccessorTable = new
org.jetbrains.kotlin.protobuf.GeneratedMessage.FieldAccessorTable(
internal_static_org_jetbrains_kotlin_metadata_jvm_JvmPropertySignature_descriptor,
new java.lang.String[] { "Field", "SyntheticMethod", "Getter", "Setter", "DelegateMethod", });
new java.lang.String[] { "Field", "SyntheticMethod", "Getter", "Setter", });
constructorSignature.internalInit(descriptor.getExtensions().get(0));
methodSignature.internalInit(descriptor.getExtensions().get(1));
lambdaClassOriginName.internalInit(descriptor.getExtensions().get(2));

View File

@@ -1,6 +1,7 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.gradle.crypto.checksum.Checksum
import org.gradle.plugins.ide.idea.model.IdeaModel
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import proguard.gradle.ProGuardTask
buildscript {
@@ -29,7 +30,7 @@ buildscript {
dependencies {
bootstrapCompilerClasspath(kotlin("compiler-embeddable", bootstrapKotlinVersion))
classpath("org.jetbrains.kotlin:kotlin-build-gradle-plugin:0.0.32")
classpath("org.jetbrains.kotlin:kotlin-build-gradle-plugin:0.0.30")
classpath(kotlin("gradle-plugin", bootstrapKotlinVersion))
classpath(kotlin("serialization", bootstrapKotlinVersion))
classpath("org.jetbrains.dokka:dokka-gradle-plugin:0.9.17")
@@ -55,6 +56,13 @@ pill {
val isTeamcityBuild = project.kotlinBuildProperties.isTeamcityBuild
val configuredJdks: List<JdkId> =
getConfiguredJdks().also {
it.forEach { jdkId ->
logger.info("Using ${jdkId.majorVersion} home: ${jdkId.homeDir}")
}
}
val defaultSnapshotVersion: String by extra
val buildNumber by extra(findProperty("build.number")?.toString() ?: defaultSnapshotVersion)
val kotlinVersion by extra(
@@ -106,6 +114,37 @@ subprojects {
extra["kotlinNativeVersion"] = kotlinNativeVersionObject
}
// Work-around necessary to avoid setting null javaHome. Will be removed after support of lazy task configuration
val jdkNotFoundConst = "JDK NOT FOUND"
if (isTeamcityBuild) {
extra["JDK_16"] = jdkPath("1.6")
extra["JDK_17"] = jdkPath("1.7")
} else {
extra["JDK_16"] = jdkPath("1.6", "1.8")
extra["JDK_17"] = jdkPath("1.7", "1.8")
}
extra["JDK_18"] = jdkPath("1.8")
extra["JDK_9"] = jdkPath("9")
extra["JDK_10"] = jdkPath("10")
extra["JDK_11"] = jdkPath("11")
extra["JDK_15"] = jdkPath("15")
// allow opening the project without setting up all env variables (see KT-26413)
if (!kotlinBuildProperties.isInIdeaSync) {
checkJDK()
}
fun checkJDK() {
val missingEnvVars = JdkMajorVersion.values()
.filter { it.isMandatory() && extra[it.name] == jdkNotFoundConst }
.mapTo(ArrayList()) { it.name }
if (missingEnvVars.isNotEmpty()) {
throw GradleException("Required environment variables are missing: ${missingEnvVars.joinToString()}")
}
}
rootProject.apply {
from(rootProject.file("gradle/versions.gradle.kts"))
from(rootProject.file("gradle/report.gradle.kts"))
@@ -128,9 +167,8 @@ extra["versions.junit"] = "4.12"
extra["versions.javaslang"] = "2.0.6"
extra["versions.ant"] = "1.10.7"
extra["versions.android"] = "2.3.1"
extra["versions.kotlinx-coroutines-core"] = "1.5.0"
extra["versions.kotlinx-coroutines-core-jvm"] = "1.5.0"
extra["versions.kotlinx-coroutines-jdk8"] = "1.5.0"
extra["versions.kotlinx-coroutines-core"] = "1.3.8"
extra["versions.kotlinx-coroutines-jdk8"] = "1.3.8"
extra["versions.json"] = "20160807"
extra["versions.native-platform"] = "0.14"
extra["versions.robolectric"] = "4.0"
@@ -148,9 +186,12 @@ extra["versions.kotlinx-collections-immutable-jvm"] = immutablesVersion
extra["versions.ktor-network"] = "1.0.1"
if (!project.hasProperty("versions.kotlin-native")) {
extra["versions.kotlin-native"] = "1.6.0-dev-2972"
extra["versions.kotlin-native"] = "1.5.30-dev-1916"
}
val effectSystemEnabled by extra(project.getBooleanProperty("kotlin.compiler.effectSystemEnabled") ?: false)
val newInferenceEnabled by extra(project.getBooleanProperty("kotlin.compiler.newInferenceEnabled") ?: false)
val useJvmIrBackend by extra(project.kotlinBuildProperties.useIR)
val useJvmFir by extra(project.kotlinBuildProperties.useFir)
val intellijSeparateSdks = project.getBooleanProperty("intellijSeparateSdks") ?: false
@@ -159,7 +200,7 @@ extra["intellijSeparateSdks"] = intellijSeparateSdks
extra["IntellijCoreDependencies"] =
listOf(
"asm-all-9.0",
"asm-all-8.0.1",
"guava",
"jdom",
"jna",
@@ -211,7 +252,6 @@ extra["compilerModules"] = arrayOf(
":compiler:incremental-compilation-impl",
":compiler:compiler.version",
":js:js.ast",
":js:js.sourcemap",
":js:js.serializer",
":js:js.parser",
":js:js.config",
@@ -293,11 +333,6 @@ extra["compilerArtifactsForIde"] = listOf(
":prepare:ide-plugin-dependencies:lombok-compiler-plugin-for-ide",
":prepare:ide-plugin-dependencies:kotlin-compiler-tests-for-ide",
":prepare:ide-plugin-dependencies:kotlin-compiler-testdata-for-ide",
":prepare:ide-plugin-dependencies:kotlin-stdlib-minimal-for-test-for-ide",
":prepare:ide-plugin-dependencies:low-level-api-fir-for-ide",
":prepare:ide-plugin-dependencies:high-level-api-for-ide",
":prepare:ide-plugin-dependencies:high-level-api-fir-for-ide",
":prepare:ide-plugin-dependencies:high-level-api-fir-tests-for-ide",
":kotlin-script-runtime",
":kotlin-script-util",
":kotlin-scripting-common",
@@ -315,7 +350,15 @@ extra["compilerArtifactsForIde"] = listOf(
// TODO: fix remaining warnings and remove this property.
extra["tasksWithWarnings"] = listOf(
":kotlin-gradle-plugin:compileKotlin"
":kotlin-stdlib:compileTestKotlin",
":kotlin-stdlib-jdk7:compileTestKotlin",
":kotlin-stdlib-jdk8:compileTestKotlin",
":plugins:uast-kotlin-base:compileKotlin",
":plugins:uast-kotlin-base:compileTestKotlin",
":plugins:uast-kotlin:compileKotlin",
":plugins:uast-kotlin:compileTestKotlin",
":plugins:uast-kotlin-fir:compileKotlin",
":plugins:uast-kotlin-fir:compileTestKotlin"
)
val tasksWithWarnings: List<String> by extra
@@ -340,12 +383,7 @@ val coreLibProjects = listOfNotNull(
val projectsWithDisabledFirBootstrap = coreLibProjects + listOf(
":kotlin-gradle-plugin",
":kotlinx-metadata",
":kotlinx-metadata-jvm",
// For some reason stdlib isn't imported correctly for this module
// Probably it's related to kotlin-test module usage
":kotlin-gradle-statistics",
// Requires serialization plugin
":wasm:wasm.ir"
":kotlinx-metadata-jvm"
)
val gradlePluginProjects = listOf(
@@ -379,6 +417,8 @@ fun Task.listConfigurationContents(configName: String) {
}
}
val defaultJvmTarget = "1.8"
val defaultJavaHome = jdkPath(if (Platform[203].orHigher()) "11" else defaultJvmTarget)
val ignoreTestFailures by extra(project.kotlinBuildProperties.ignoreTestFailures)
allprojects {
@@ -424,25 +464,21 @@ allprojects {
}
}
jvmTarget = defaultJvmTarget
javaHome = defaultJavaHome
// There are problems with common build dir:
// - some tests (in particular js and binary-compatibility-validator depend on the fixed (default) location
// - idea seems unable to exclude common buildDir from indexing
// therefore it is disabled by default
// buildDir = File(commonBuildDir, project.name)
project.configureJvmDefaultToolchain()
plugins.withId("java-base") {
project.configureShadowJarSubstitutionInCompileClasspath()
}
tasks.withType<JavaCompile> {
options.compilerArgs.add("-Xlint:deprecation")
options.compilerArgs.add("-Xlint:unchecked")
options.compilerArgs.add("-Werror")
}
configureJvmProject(javaHome!!, jvmTarget!!)
val commonCompilerArgs = listOfNotNull(
"-Xopt-in=kotlin.RequiresOptIn",
"-Xread-deserialized-contracts",
"-progressive".takeIf { hasProperty("test.progressive.mode") }
)
@@ -458,26 +494,34 @@ allprojects {
"-Xjvm-default=compatibility",
"-Xno-optimized-callable-references",
"-Xno-kotlin-nothing-value-exception",
"-Xskip-runtime-version-check",
"-Xsuppress-deprecated-jvm-target-warning" // Remove as soon as there are no modules for JDK 1.6 & 1.7
"-Xnormalize-constructor-calls=enable"
)
tasks.withType<org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompile> {
kotlinOptions {
freeCompilerArgs = commonCompilerArgs + jvmCompilerArgs
if (useJvmFir && this@allprojects.path !in projectsWithDisabledFirBootstrap) {
if (useJvmIrBackend) {
useIR = true
}
if (useJvmFir && this@allprojects.name !in projectsWithDisabledFirBootstrap) {
freeCompilerArgs += "-Xuse-fir"
freeCompilerArgs += "-Xabi-stability=stable"
}
}
}
if (!kotlinBuildProperties.disableWerror) {
tasks.withType<org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompile> {
if (path !in tasksWithWarnings) {
kotlinOptions {
allWarningsAsErrors = true
if (!kotlinBuildProperties.isInJpsBuildIdeaSync && !kotlinBuildProperties.useFir && !kotlinBuildProperties.disableWerror) {
// For compiler and stdlib, allWarningsAsErrors is configured in the corresponding "root" projects
// (compiler/build.gradle.kts and libraries/commonConfiguration.gradle).
val projectsWithWarningsAsErrors = listOf("core", "plugins").map { File(it).absoluteFile }
if (projectsWithWarningsAsErrors.any(projectDir::startsWith)) {
tasks.withType<org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompile> {
if (path !in tasksWithWarnings) {
kotlinOptions {
allWarningsAsErrors = true
}
}
}
}
@@ -531,6 +575,11 @@ allprojects {
apply(from = "$rootDir/gradle/cacheRedirector.gradle.kts")
afterEvaluate {
if (javaHome != defaultJavaHome || jvmTarget != defaultJvmTarget) {
logger.info("configuring project $name to compile to the target jvm version $jvmTarget using jdk: $javaHome")
configureJvmProject(javaHome!!, jvmTarget!!)
} // else we will actually fail during the first task execution. We could not fail before configuration is done due to impact on import in IDE
fun File.toProjectRootRelativePathOrSelf() = (relativeToOrNull(rootDir)?.takeUnless { it.startsWith("..") } ?: this).path
fun FileCollection.printClassPath(role: String) =
@@ -766,7 +815,6 @@ tasks {
register("distTest") {
dependsOn("compilerTest")
dependsOn("frontendApiTests")
dependsOn("toolsTest")
dependsOn("gradlePluginTest")
dependsOn("examplesTest")
@@ -828,23 +876,14 @@ tasks {
dependsOn("dist")
dependsOn(
":idea:idea-fir:test",
":idea:idea-frontend-fir:fir-low-level-api-ide-impl:test",
":idea:idea-frontend-api:test",
":idea:idea-frontend-fir:test",
":idea:idea-frontend-fir:idea-fir-low-level-api:test",
":plugins:uast-kotlin-fir:test",
":idea:idea-fir-fe10-binding:test"
)
}
register("frontendApiTests") {
dependsOn("dist")
dependsOn(
":idea-frontend-api:test",
":idea-frontend-fir:test",
":idea-frontend-fir:idea-fir-low-level-api:test"
)
}
register("android-ide-tests") {
dependsOn("dist")
dependsOn(
@@ -1044,6 +1083,66 @@ configure<IdeaModel> {
}
}
fun jdkPathOrNull(version: String): String? {
val jdkName = "JDK_${version.replace(".", "")}"
val jdkMajorVersion = JdkMajorVersion.valueOf(jdkName)
return configuredJdks.find { it.majorVersion == jdkMajorVersion }?.homeDir?.canonicalPath
}
fun jdkPath(version: String, vararg replacementVersions: String): String {
return jdkPathOrNull(version) ?: run {
replacementVersions.asSequence().map { jdkPathOrNull(it) }.find { it != null }
} ?: jdkNotFoundConst
}
fun Project.configureJvmProject(javaHome: String, javaVersion: String) {
val currentJavaHome = File(System.getProperty("java.home")!!).canonicalPath
val shouldFork = !currentJavaHome.startsWith(File(javaHome).canonicalPath)
tasks.withType<JavaCompile> {
if (name != "compileJava9Java") {
sourceCompatibility = javaVersion
targetCompatibility = javaVersion
options.isFork = shouldFork
options.forkOptions.javaHome = file(javaHome)
options.compilerArgs.add("-proc:none")
options.encoding = "UTF-8"
}
}
tasks.withType<KotlinCompile> {
kotlinOptions.jdkHome = javaHome.takeUnless { kotlinBuildProperties.suppressJdkHomeWarning }
kotlinOptions.jvmTarget = javaVersion
kotlinOptions.freeCompilerArgs += "-Xjvm-default=compatibility"
}
tasks.withType<Test> {
executable = File(javaHome, "bin/java").canonicalPath
}
plugins.withId("java-base") {
configureShadowJarSubstitutionInCompileClasspath()
}
}
fun Project.configureShadowJarSubstitutionInCompileClasspath() {
val substitutionMap = mapOf(":kotlin-reflect" to ":kotlin-reflect-api")
fun configureSubstitution(substitution: DependencySubstitution) {
val requestedProject = (substitution.requested as? ProjectComponentSelector)?.projectPath ?: return
val replacementProject = substitutionMap[requestedProject] ?: return
substitution.useTarget(project(replacementProject), "Non-default shadow jars should not be used in compile classpath")
}
sourceSets.all {
for (configName in listOf(compileOnlyConfigurationName, compileClasspathConfigurationName)) {
configurations.getByName(configName).resolutionStrategy.dependencySubstitution {
all(::configureSubstitution)
}
}
}
}
tasks.register("findShadowJarsInClasspath") {
doLast {
fun Collection<File>.printSorted(indent: String = " ") {
@@ -1111,19 +1210,3 @@ if (disableVerificationTasks) {
}
}
}
plugins.withType(org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin::class) {
extensions.configure(org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension::class.java) {
nodeVersion = "16.2.0"
}
}
afterEvaluate {
val cacheRedirectorEnabled = findProperty("cacheRedirectorEnabled")?.toString()?.toBoolean() == true
if (cacheRedirectorEnabled) {
rootProject.plugins.withType(org.jetbrains.kotlin.gradle.targets.js.yarn.YarnPlugin::class.java) {
rootProject.the<org.jetbrains.kotlin.gradle.targets.js.yarn.YarnRootExtension>().downloadBaseUrl =
"https://cache-redirector.jetbrains.com/github.com/yarnpkg/yarn/releases/download"
}
}
}

View File

@@ -22,7 +22,7 @@ buildscript {
}
dependencies {
classpath("org.jetbrains.kotlin:kotlin-build-gradle-plugin:0.0.32")
classpath("org.jetbrains.kotlin:kotlin-build-gradle-plugin:0.0.30")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${project.bootstrapKotlinVersion}")
classpath("org.jetbrains.kotlin:kotlin-sam-with-receiver:${project.bootstrapKotlinVersion}")
}
@@ -143,7 +143,7 @@ java {
dependencies {
implementation(kotlin("stdlib", embeddedKotlinVersion))
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:${project.bootstrapKotlinVersion}")
implementation("org.jetbrains.kotlin:kotlin-build-gradle-plugin:0.0.32")
implementation("org.jetbrains.kotlin:kotlin-build-gradle-plugin:0.0.30")
implementation("com.gradle.publish:plugin-publish-plugin:0.14.0")
implementation("net.rubygrapefruit:native-platform:${property("versions.native-platform")}")
@@ -155,10 +155,10 @@ dependencies {
implementation("net.sf.proguard:proguard-gradle:6.2.2")
implementation("org.jetbrains.intellij.deps:asm-all:8.0.1")
implementation("gradle.plugin.org.jetbrains.gradle.plugin.idea-ext:gradle-idea-ext:1.0.1")
implementation("gradle.plugin.org.jetbrains.gradle.plugin.idea-ext:gradle-idea-ext:0.5")
implementation("org.gradle:test-retry-gradle-plugin:1.2.0")
implementation("com.gradle.enterprise:test-distribution-gradle-plugin:2.1")
implementation("com.gradle.enterprise:test-distribution-gradle-plugin:1.2.1")
compileOnly(gradleApi())

View File

@@ -79,6 +79,7 @@ val jpsStandalone by configurations.creating
val jpsStandaloneForIde by configurations.creating
val intellijCore by configurations.creating
val intellijCoreForIde by configurations.creating
val nodeJSPlugin by configurations.creating
/**
* Special repository for annotations.jar required for idea runtime only.

View File

@@ -19,17 +19,6 @@ val KotlinBuildProperties.jarCompression: Boolean get() = getBoolean("kotlin.bui
val KotlinBuildProperties.ignoreTestFailures: Boolean get() = getBoolean("ignoreTestFailures", isTeamcityBuild)
val KotlinBuildProperties.disableWerror: Boolean
get() = getBoolean("kotlin.build.disable.werror") || useFir || isInJpsBuildIdeaSync || getBoolean("test.progressive.mode")
val KotlinBuildProperties.disableWerror: Boolean get() = getBoolean("kotlin.build.disable.werror", false)
val KotlinBuildProperties.pathToKotlinModularizedTestData: String?
get() = getOrNull("kotlin.fir.modularized.testdata.kotlin") as? String
val KotlinBuildProperties.pathToIntellijModularizedTestData: String?
get() = getOrNull("kotlin.fir.modularized.testdata.intellij") as? String
val KotlinBuildProperties.pathToYoutrackModularizedTestData: String?
get() = getOrNull("kotlin.fir.modularized.testdata.youtrack") as? String
val KotlinBuildProperties.isObsoleteJdkOverrideEnabled: Boolean
get() = getBoolean("kotlin.build.isObsoleteJdkOverrideEnabled", false)
val KotlinBuildProperties.suppressJdkHomeWarning: Boolean get() = getBoolean("kotlin.suppress.jdkHome.warning", false)

View File

@@ -4,36 +4,32 @@
*/
import groovy.lang.Closure
import org.gradle.api.JavaVersion
import org.gradle.api.file.FileCollection
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.*
import org.gradle.jvm.toolchain.JavaLauncher
import org.gradle.kotlin.dsl.property
import org.gradle.internal.jvm.Jvm
import org.gradle.internal.jvm.inspection.JvmVersionDetector
import proguard.ClassSpecification
import java.io.File
import javax.inject.Inject
@CacheableTask
open class CacheableProguardTask : proguard.gradle.ProGuardTask() {
open class CacheableProguardTask @Inject constructor(
private val jvmVersionDetector: JvmVersionDetector
) : proguard.gradle.ProGuardTask() {
@get:Internal
val javaLauncher: Property<JavaLauncher> = project.objects.property()
@get:Internal
val jdkHomePath: Provider<File> = javaLauncher.map { it.metadata.installationPath.asFile }
@Internal
var jdkHome: File? = null
@get:Optional
@get:Input
internal val jdkMajorVersion: Provider<JavaVersion> = javaLauncher.map {
JavaVersion.toVersion(it.metadata.languageVersion.toString())
}
internal val jdkMajorVersion: String?
get() = jdkHome?.let { jvmVersionDetector.getJavaVersion(Jvm.forHome(jdkHome)) }?.majorVersion
@CompileClasspath
override fun getLibraryJarFileCollection(): FileCollection = super.getLibraryJarFileCollection()
.filter { libraryFile ->
jdkHomePath.orNull?.let { !libraryFile.absoluteFile.startsWith(it.absoluteFile) } ?: true
}
override fun getLibraryJarFileCollection(): FileCollection = super.getLibraryJarFileCollection().filter { libraryFile ->
jdkHome?.let { !libraryFile.absoluteFile.startsWith(it.absoluteFile) } ?: true
}
@InputFiles
@PathSensitive(PathSensitivity.RELATIVE)

View File

@@ -86,7 +86,4 @@ val Project.isConfigurationCacheDisabled
get() = (gradle.startParameter as? org.gradle.api.internal.StartParameterInternal)?.isConfigurationCache != true
val Project.isIdeaActive
get() = providers.systemProperty("idea.active").forUseAtConfigurationTime().isPresent
val Project.intellijCommunityDir: File
get() = rootDir.resolve("intellij/community").takeIf { it.isDirectory } ?: rootDir.resolve("intellij")
get() = providers.systemProperty("idea.active").forUseAtConfigurationTime().isPresent

View File

@@ -1,144 +0,0 @@
/*
* 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.
*/
import com.github.jengelman.gradle.plugins.shadow.relocation.RelocateClassContext
import com.github.jengelman.gradle.plugins.shadow.transformers.Transformer
import com.github.jengelman.gradle.plugins.shadow.transformers.TransformerContext
import org.gradle.api.file.FileTreeElement
import shadow.org.apache.tools.zip.ZipEntry
import shadow.org.apache.tools.zip.ZipOutputStream
import shadow.org.codehaus.plexus.util.IOUtil
import shadow.org.codehaus.plexus.util.ReaderFactory
import shadow.org.codehaus.plexus.util.WriterFactory
import shadow.org.codehaus.plexus.util.xml.Xpp3Dom
import shadow.org.codehaus.plexus.util.xml.Xpp3DomBuilder
import shadow.org.codehaus.plexus.util.xml.Xpp3DomWriter
import java.io.*
import java.lang.Exception
import java.util.LinkedHashMap
/**
* A resource processor that aggregates plexus `components.xml` files.
*
* Fixed version of [com.github.jengelman.gradle.plugins.shadow.transformers.ComponentsXmlResourceTransformer],
* may be dropped after [the fix in ShadowJAR](https://github.com/johnrengelman/shadow/pull/678/files) will be accepted
*/
class ComponentsXmlResourceTransformerPatched : Transformer {
private val components: MutableMap<String, Xpp3Dom> =
LinkedHashMap<String, Xpp3Dom>()
override fun canTransformResource(element: FileTreeElement): Boolean {
val path = element.relativePath.pathString
return COMPONENTS_XML_PATH == path
}
override fun transform(context: TransformerContext) {
val newDom: Xpp3Dom = try {
val bis: BufferedInputStream = object : BufferedInputStream(context.getIs()) {
override fun close() {
// leave ZIP open
}
}
val reader: Reader = ReaderFactory.newXmlReader(bis)
Xpp3DomBuilder.build(reader)
} catch (e: Exception) {
throw (IOException("Error parsing components.xml in " + context.getIs()).initCause(e) as IOException)
}
// Only try to merge in components if there are some elements in the component-set
if (newDom.getChild("components") == null) {
return
}
val children: Array<Xpp3Dom>? = newDom.getChild("components")?.getChildren("component")
children?.forEach { component ->
var role: String? = getValue(component, "role")
role = getRelocatedClass(role, context)
setValue(component, "role", role)
val roleHint = getValue(component, "role-hint")
var impl: String? = getValue(component, "implementation")
impl = getRelocatedClass(impl, context)
setValue(component, "implementation", impl)
val key = "$role:$roleHint"
if (components.containsKey(key)) {
// configuration carry over
val dom: Xpp3Dom? = components[key]
if (dom?.getChild("configuration") != null) {
component.addChild(dom.getChild("configuration"))
}
}
val requirements: Xpp3Dom? = component.getChild("requirements")
if (requirements != null && requirements.childCount > 0) {
for (r in requirements.childCount - 1 downTo 0) {
val requirement: Xpp3Dom = requirements.getChild(r)
var requiredRole: String? = getValue(requirement, "role")
requiredRole = getRelocatedClass(requiredRole, context)
setValue(requirement, "role", requiredRole)
}
}
components[key] = component
}
}
override fun modifyOutputStream(os: ZipOutputStream, preserveFileTimestamps: Boolean) {
val data = transformedResource
val entry = ZipEntry(COMPONENTS_XML_PATH)
entry.time = TransformerContext.getEntryTimestamp(preserveFileTimestamps, entry.time)
os.putNextEntry(entry)
IOUtil.copy(data, os)
components.clear()
}
override fun hasTransformedResource(): Boolean {
return components.isNotEmpty()
}
private val transformedResource: ByteArray
get() {
val baos = ByteArrayOutputStream(1024 * 4)
val writer: Writer = WriterFactory.newXmlWriter(baos)
try {
val dom = Xpp3Dom("component-set")
val componentDom = Xpp3Dom("components")
dom.addChild(componentDom)
for (component in components.values) {
componentDom.addChild(component)
}
Xpp3DomWriter.write(writer, dom)
} finally {
IOUtil.close(writer)
}
return baos.toByteArray()
}
companion object {
private const val COMPONENTS_XML_PATH = "META-INF/plexus/components.xml"
private fun getRelocatedClass(className: String?, context: TransformerContext): String? {
val relocators = context.relocators
val stats = context.stats
if (className != null && className.isNotEmpty() && relocators != null) {
for (relocator in relocators) {
if (relocator.canRelocateClass(className)) {
val relocateClassContext = RelocateClassContext(className, stats)
return relocator.relocateClass(relocateClassContext)
}
}
}
return className
}
private fun getValue(dom: Xpp3Dom, element: String): String {
val child: Xpp3Dom? = dom.getChild(element)
return if (child?.value != null) child.value else ""
}
private fun setValue(dom: Xpp3Dom, element: String, value: String?) {
val child: Xpp3Dom? = dom.getChild(element)
if (value == null || value.isEmpty()) {
return
}
child?.value = value
}
}
}

View File

@@ -26,7 +26,7 @@ fun CompatibilityPredicate.or(other: CompatibilityPredicate): CompatibilityPredi
}
enum class Platform : CompatibilityPredicate {
P203;
P202, P203;
val version: Int = name.drop(1).toInt()
@@ -43,7 +43,10 @@ enum class Platform : CompatibilityPredicate {
}
enum class Ide(val platform: Platform) : CompatibilityPredicate {
IJ203(Platform.P203);
IJ202(Platform.P202),
IJ203(Platform.P203),
AS42(Platform.P202);
val kind = Kind.values().first { it.shortName == name.take(2) }
val version = name.dropWhile { !it.isDigit() }.toInt()

View File

@@ -1,184 +0,0 @@
@file:JvmName("JvmToolchain")
import org.gradle.api.Project
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.jvm.toolchain.*
import org.gradle.kotlin.dsl.getByType
import org.gradle.kotlin.dsl.withType
import org.jetbrains.kotlin.gradle.dsl.KotlinTopLevelExtension
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
enum class JdkMajorVersion(
val majorVersion: Int,
val targetName: String = majorVersion.toString(),
val overrideMajorVersion: Int? = null,
private val mandatory: Boolean = true
) {
JDK_1_6(6, targetName = "1.6", overrideMajorVersion = 8),
JDK_1_7(7, targetName = "1.7", overrideMajorVersion = 8),
JDK_1_8(8, targetName = "1.8"),
JDK_9(9, overrideMajorVersion = 11),
JDK_10(10, mandatory = false, overrideMajorVersion = 11),
JDK_11(11, mandatory = false),
JDK_15(15, mandatory = false),
JDK_16(16, mandatory = false),
JDK_17(17, mandatory = false);
fun isMandatory(): Boolean = mandatory
companion object {
fun fromMajorVersion(majorVersion: Int) = values().first { it.majorVersion == majorVersion }
}
}
fun Project.configureJvmDefaultToolchain() {
configureJvmToolchain(JdkMajorVersion.JDK_1_8)
}
fun Project.shouldOverrideObsoleteJdk(
jdkVersion: JdkMajorVersion
): Boolean = kotlinBuildProperties.isObsoleteJdkOverrideEnabled &&
jdkVersion.overrideMajorVersion != null
fun Project.configureJvmToolchain(
jdkVersion: JdkMajorVersion
) {
// Ensure java only modules also set default toolchain
configureJavaOnlyToolchain(jdkVersion)
plugins.withId("org.jetbrains.kotlin.jvm") {
val kotlinExtension = extensions.getByType<KotlinTopLevelExtension>()
if (shouldOverrideObsoleteJdk(jdkVersion)) {
kotlinExtension.jvmToolchain {
(this as JavaToolchainSpec).languageVersion
.set(JavaLanguageVersion.of(jdkVersion.overrideMajorVersion!!))
}
updateJvmTarget(jdkVersion.targetName)
} else {
kotlinExtension.jvmToolchain {
(this as JavaToolchainSpec).languageVersion
.set(JavaLanguageVersion.of(jdkVersion.majorVersion))
}
}
tasks
.matching { it.name != "compileJava9Java" && it is JavaCompile }
.configureEach {
with(this as JavaCompile) {
options.compilerArgs.add("-proc:none")
options.encoding = "UTF-8"
}
}
tasks.withType<KotlinCompile>().configureEach {
kotlinOptions.freeCompilerArgs += "-Xjvm-default=compatibility"
}
}
}
fun Project.configureJavaOnlyToolchain(
jdkVersion: JdkMajorVersion
) {
plugins.withId("java-base") {
val javaExtension = extensions.getByType<JavaPluginExtension>()
if (shouldOverrideObsoleteJdk(jdkVersion)) {
javaExtension.toolchain {
languageVersion.set(
JavaLanguageVersion.of(jdkVersion.overrideMajorVersion!!)
)
}
tasks.withType<JavaCompile>().configureEach {
targetCompatibility = jdkVersion.targetName
sourceCompatibility = jdkVersion.targetName
}
} else {
javaExtension.toolchain {
languageVersion.set(
JavaLanguageVersion.of(jdkVersion.majorVersion)
)
}
}
}
}
fun KotlinCompile.configureTaskToolchain(
jdkVersion: JdkMajorVersion
) {
if (project.shouldOverrideObsoleteJdk(jdkVersion)) {
kotlinJavaToolchain.toolchain.use(
project.getToolchainLauncherFor(
JdkMajorVersion.fromMajorVersion(
jdkVersion.overrideMajorVersion!!
)
)
)
kotlinOptions {
jvmTarget = jdkVersion.targetName
}
} else {
kotlinJavaToolchain.toolchain.use(
project.getToolchainLauncherFor(jdkVersion)
)
}
}
fun JavaCompile.configureTaskToolchain(
jdkVersion: JdkMajorVersion
) {
if (project.shouldOverrideObsoleteJdk(jdkVersion)) {
javaCompiler.set(
project.getToolchainCompilerFor(
JdkMajorVersion.fromMajorVersion(
jdkVersion.overrideMajorVersion!!
)
)
)
targetCompatibility = jdkVersion.targetName
sourceCompatibility = jdkVersion.targetName
} else {
javaCompiler.set(project.getToolchainCompilerFor(jdkVersion))
}
}
fun Project.updateJvmTarget(
jvmTarget: String
) {
tasks.withType<KotlinCompile>().configureEach {
kotlinOptions.jvmTarget = jvmTarget
}
tasks.withType<JavaCompile>().configureEach {
sourceCompatibility = jvmTarget
targetCompatibility = jvmTarget
}
}
private fun Project.getToolchainCompilerFor(
jdkVersion: JdkMajorVersion
): Provider<JavaCompiler> {
val service = project.extensions.getByType<JavaToolchainService>()
return service.compilerFor {
this.languageVersion.set(JavaLanguageVersion.of(jdkVersion.majorVersion))
}
}
fun Project.getToolchainLauncherFor(
jdkVersion: JdkMajorVersion
): Provider<JavaLauncher> {
val service = project.extensions.getByType<JavaToolchainService>()
val jdkVersionWithOverride = project.getJdkVersionWithOverride(jdkVersion)
return service.launcherFor {
this.languageVersion.set(JavaLanguageVersion.of(jdkVersionWithOverride.majorVersion))
}
}
fun Project.getJdkVersionWithOverride(jdkVersion: JdkMajorVersion): JdkMajorVersion {
return if (project.shouldOverrideObsoleteJdk(jdkVersion)) {
JdkMajorVersion.fromMajorVersion(jdkVersion.overrideMajorVersion!!)
} else {
jdkVersion
}
}

View File

@@ -1,57 +0,0 @@
/*
* 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.
*/
@file:JvmName("LibrariesCommon")
import org.gradle.api.JavaVersion
import org.gradle.api.Project
import org.gradle.api.file.FileCollection
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.kotlin.dsl.get
import org.gradle.process.CommandLineArgumentProvider
@JvmOverloads
fun Project.configureJava9Compilation(
moduleName: String,
moduleOutputs: Collection<FileCollection> = setOf(sourceSets["main"].output)
) {
configurations["java9CompileClasspath"].extendsFrom(configurations["compileClasspath"])
tasks.named("compileJava9Java", JavaCompile::class.java) {
dependsOn(moduleOutputs)
targetCompatibility = JavaVersion.VERSION_1_9.toString()
sourceCompatibility = JavaVersion.VERSION_1_9.toString()
configureTaskToolchain(JdkMajorVersion.JDK_9)
// module-info.java should be in java9 source set by convention
val java9SourceSet = sourceSets["java9"].java
destinationDir = file("${java9SourceSet.outputDir}/META-INF/versions/9")
options.sourcepath = files(java9SourceSet.srcDirs)
val compileClasspath = configurations["java9CompileClasspath"]
val moduleFiles = objects.fileCollection().from(moduleOutputs)
val modulePath = compileClasspath.filter { it !in moduleFiles.files }
classpath = objects.fileCollection().from()
options.compilerArgumentProviders.add(
Java9AdditionalArgumentsProvider(
moduleName,
moduleFiles,
modulePath
)
)
}
}
private class Java9AdditionalArgumentsProvider(
private val moduleName: String,
private val moduleFiles: FileCollection,
private val modulePath: FileCollection
) : CommandLineArgumentProvider {
override fun asArguments(): Iterable<String> = listOf(
"--module-path", modulePath.asPath,
"--patch-module", "$moduleName=${moduleFiles.asPath}",
"-Xlint:-requires-transitive-automatic" // suppress automatic module transitive dependencies in kotlin.test
)
}

View File

@@ -1,6 +1,6 @@
@file:Suppress("unused") // usages in build scripts are not tracked properly
import com.gradle.publish.PublishTask
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.ConfigurablePublishArtifact
@@ -22,12 +22,12 @@ import org.gradle.api.publish.PublishingExtension
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.publish.tasks.GenerateModuleMetadata
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.Upload
import org.gradle.api.tasks.javadoc.Javadoc
import org.gradle.jvm.tasks.Jar
import org.gradle.kotlin.dsl.*
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetContainer
import plugins.KotlinBuildPublishingPlugin
import plugins.mainPublicationName
private const val MAGIC_DO_NOT_CHANGE_TEST_JAR_TASK_NAME = "testJar"
@@ -62,6 +62,8 @@ fun Project.removeArtifacts(configuration: Configuration, task: Task) {
fun Project.noDefaultJar() {
tasks.named("jar").configure {
enabled = false
actions = emptyList()
configurations.forEach { cfg ->
removeArtifacts(cfg, this)
}
@@ -110,8 +112,6 @@ fun <T : Jar> Project.runtimeJar(task: TaskProvider<T>, body: T.() -> Unit = {})
addVariantsFromConfiguration(runtimeJar) { }
}
(components.findByName("java") as AdhocComponentWithVariants?)?.addVariantsFromConfiguration(runtimeJar) { }
return task
}
@@ -225,23 +225,10 @@ fun Project.publish(moduleMetadata: Boolean = false, configure: MavenPublication
val publication = extensions.findByType<PublishingExtension>()
?.publications
?.findByName(mainPublicationName) as MavenPublication
?.findByName(KotlinBuildPublishingPlugin.PUBLICATION_NAME) as MavenPublication
publication.configure()
}
fun Project.publishGradlePlugin() {
mainPublicationName = "pluginMaven"
publish()
afterEvaluate {
tasks.withType<PublishTask> {
// Makes plugin publication task reuse poms and metadata from publication named "pluginMaven"
useAutomatedPublishing()
useGradleModuleMetadataIfAvailable()
}
}
}
fun Project.idePluginDependency(block: () -> Unit) {
val shouldActivate = rootProject.findProperty("publish.ide.plugin.dependencies")?.toString()?.toBoolean() == true
if (shouldActivate) {

View File

@@ -9,15 +9,15 @@
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.artifacts.*
import org.gradle.api.artifacts.Dependency
import org.gradle.api.artifacts.ExternalModuleDependency
import org.gradle.api.artifacts.ModuleDependency
import org.gradle.api.artifacts.ProjectDependency
import org.gradle.api.artifacts.dsl.DependencyHandler
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.FileCollection
import org.gradle.internal.jvm.Jvm
import org.gradle.jvm.toolchain.JavaLanguageVersion
import org.gradle.kotlin.dsl.accessors.runtime.addDependencyTo
import org.gradle.kotlin.dsl.closureOf
import org.gradle.kotlin.dsl.exclude
import org.gradle.kotlin.dsl.extra
import org.gradle.kotlin.dsl.project
import java.io.File
@@ -124,14 +124,6 @@ fun DependencyHandler.add(configurationName: String, dependencyNotation: Any, co
}
}
fun Project.disableDependencyVerification() {
configurations.all {
resolutionStrategy {
disableDependencyVerification()
}
}
}
fun DependencyHandler.jpsLikeJarDependency(
dependencyNotation: Any,
scope: JpsDepScope,
@@ -289,16 +281,14 @@ fun Project.firstFromJavaHomeThatExists(vararg paths: String, jdkHome: File = Fi
fun Project.toolsJarApi(): Any =
if (kotlinBuildProperties.isInJpsBuildIdeaSync)
toolsJar()
files(toolsJarFile() ?: error("tools.jar is not found!"))
else
dependencies.project(":dependencies:tools-jar-api")
fun Project.toolsJar(): FileCollection = files(
getToolchainLauncherFor(JdkMajorVersion.JDK_1_8)
.map {
Jvm.forHome(it.metadata.installationPath.asFile).toolsJar ?: throw GradleException("tools.jar not found!")
}
)
fun Project.toolsJar(): FileCollection = files(toolsJarFile() ?: error("tools.jar is not found!"))
fun Project.toolsJarFile(jdkHome: File = File(this.property("JDK_18") as String)): File? =
firstFromJavaHomeThatExists("lib/tools.jar", jdkHome = jdkHome)
val compilerManifestClassPath
get() = "annotations-13.0.jar kotlin-stdlib.jar kotlin-reflect.jar kotlin-script-runtime.jar trove4j.jar"

View File

@@ -2,8 +2,6 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.gradle.api.Project
import org.gradle.api.artifacts.DependencySubstitution
import org.gradle.api.artifacts.component.ProjectComponentSelector
import org.gradle.api.file.DuplicatesStrategy
import org.gradle.api.tasks.TaskProvider
import org.gradle.jvm.tasks.Jar
@@ -82,24 +80,6 @@ private fun Project.compilerShadowJar(taskName: String, body: ShadowJar.() -> Un
}
}
fun Project.configureShadowJarSubstitutionInCompileClasspath() {
val substitutionMap = mapOf(":kotlin-reflect" to ":kotlin-reflect-api")
fun configureSubstitution(substitution: DependencySubstitution) {
val requestedProject = (substitution.requested as? ProjectComponentSelector)?.projectPath ?: return
val replacementProject = substitutionMap[requestedProject] ?: return
substitution.useTarget(project(replacementProject), "Non-default shadow jars should not be used in compile classpath")
}
sourceSets.all {
for (configName in listOf(compileOnlyConfigurationName, compileClasspathConfigurationName)) {
configurations.getByName(configName).resolutionStrategy.dependencySubstitution {
all(::configureSubstitution)
}
}
}
}
fun Project.embeddableCompiler(taskName: String = "embeddable", body: ShadowJar.() -> Unit = {}): TaskProvider<out ShadowJar> =
compilerShadowJar(taskName) {
configureEmbeddableCompilerRelocation()

View File

@@ -8,7 +8,7 @@ plugins {
kotlin("jvm")
}
publishGradlePlugin()
publish()
standardPublicJars()
extensions.extraProperties["kotlin.stdlib.default.dependency"] = "false"
@@ -37,12 +37,12 @@ configurations {
}
tasks.withType<KotlinCompile> {
kotlinOptions.languageVersion = "1.4"
kotlinOptions.apiVersion = "1.4"
kotlinOptions.languageVersion = "1.3"
kotlinOptions.apiVersion = "1.3"
kotlinOptions.freeCompilerArgs += listOf(
"-Xskip-prerelease-check",
"-Xsuppress-version-warnings",
"-Xuse-ir" // Needed as long as languageVersion is less than 1.5.
"-Xskip-runtime-version-check",
"-Xsuppress-version-warnings"
)
}

View File

@@ -24,16 +24,16 @@ fun ProjectSettings.compiler(block: IdeaCompilerConfiguration.() -> Unit) =
fun ProjectSettings.delegateActions(block: ActionDelegationConfig.() -> Unit) =
(this@delegateActions as ExtensionAware).extensions.configure(block)
fun ProjectSettings.runConfigurations(block: RunConfigurationContainer.() -> Unit) =
fun ProjectSettings.runConfigurations(block: DefaultRunConfigurationContainer.() -> Unit) =
(this@runConfigurations as ExtensionAware).extensions.configure("runConfigurations", block)
inline fun <reified T: RunConfiguration> RunConfigurationContainer.defaults(noinline block: T.() -> Unit) =
inline fun <reified T: RunConfiguration> DefaultRunConfigurationContainer.defaults(noinline block: T.() -> Unit) =
defaults(T::class.java, block)
fun RunConfigurationContainer.junit(name: String, block: JUnit.() -> Unit) =
fun DefaultRunConfigurationContainer.junit(name: String, block: JUnit.() -> Unit) =
create(name, JUnit::class.java, block)
fun RunConfigurationContainer.application(name: String, block: Application.() -> Unit) =
fun DefaultRunConfigurationContainer.application(name: String, block: Application.() -> Unit) =
create(name, Application::class.java, block)
fun ProjectSettings.ideArtifacts(block: NamedDomainObjectContainer<org.jetbrains.gradle.ext.TopLevelArtifact>.() -> Unit) =

View File

@@ -0,0 +1,198 @@
@file:Suppress("unused") // usages in build scripts are not tracked properly
import net.rubygrapefruit.platform.Native
import net.rubygrapefruit.platform.WindowsRegistry
import org.gradle.api.GradleException
import org.gradle.api.Project
import java.nio.file.Paths
import java.io.File
import net.rubygrapefruit.platform.WindowsRegistry.Key.HKEY_LOCAL_MACHINE
import org.gradle.internal.os.OperatingSystem
enum class JdkMajorVersion(private val mandatory: Boolean = true) {
JDK_16, JDK_17, JDK_18, JDK_9, JDK_10(false), JDK_11(false), /*15.0*/JDK_15(false);
fun isMandatory(): Boolean = mandatory
}
val jdkAlternativeVarNames = mapOf(JdkMajorVersion.JDK_9 to listOf("JDK_19"), JdkMajorVersion.JDK_15 to listOf("JDK_15_0"))
data class JdkId(val explicit: Boolean, val majorVersion: JdkMajorVersion, var version: String, var homeDir: File)
fun Project.getConfiguredJdks(): List<JdkId> {
val res = arrayListOf<JdkId>()
for (jdkMajorVersion in JdkMajorVersion.values()) {
val explicitJdkEnvVal = findProperty(jdkMajorVersion.name)?.toString()
?: System.getenv(jdkMajorVersion.name)
?: jdkAlternativeVarNames[jdkMajorVersion]?.mapNotNull { System.getenv(it) }?.firstOrNull()
?: continue
val explicitJdk = Paths.get(explicitJdkEnvVal).toRealPath().toFile()
if (!explicitJdk.isDirectory) {
throw GradleException("Invalid environment value $jdkMajorVersion: $explicitJdkEnvVal, expecting JDK home path")
}
res.add(JdkId(true, jdkMajorVersion, "X", explicitJdk))
}
if (res.size < JdkMajorVersion.values().size) {
res.discoverJdks(this)
}
return res
}
// see JEP 223
private val javaMajorVersionRegex = Regex("""(?:1\.)?(\d+).*""")
private val javaVersionRegex = Regex("""(?:1\.)?(\d+)(\.\d+)?([+-_]\w+){0,3}""")
fun MutableCollection<JdkId>.addIfBetter(project: Project, version: String, id: String, homeDir: File): Boolean {
val matchString = javaMajorVersionRegex.matchEntire(version)?.groupValues?.get(1)
val majorJdkVersion = when (matchString) {
"6" -> JdkMajorVersion.JDK_16
"7" -> JdkMajorVersion.JDK_17
"8" -> JdkMajorVersion.JDK_18
"9" -> JdkMajorVersion.JDK_9
else -> {
project.logger.info("Cannot recognize version string '$version' (found version '$matchString')")
return false
}
}
val prev = find { it.majorVersion == majorJdkVersion }
if (prev == null) {
add(JdkId(false, majorJdkVersion, version, homeDir))
return true
}
if (prev.explicit) return false
val versionsComparisonRes = compareVersions(prev.version, version)
if (versionsComparisonRes < 0 || (versionsComparisonRes == 0 && id.contains("64"))) { // prefer 64-bit
prev.version = version
prev.homeDir = homeDir
return true
}
return false
}
private fun compareVersions(left: String, right: String): Int {
if (left == right) return 0
fun MatchResult.extractNumVer(): List<Int> =
groups.drop(2).map {
it?.value?.filter { it in '0'..'9' }?.toIntOrNull() ?: 0
}
val lmi = (javaVersionRegex.matchEntire(left)?.extractNumVer() ?: emptyList()).iterator()
val rmi = (javaVersionRegex.matchEntire(right)?.extractNumVer() ?: emptyList()).iterator()
while (lmi.hasNext() && rmi.hasNext()) {
val l = lmi.next()
val r = rmi.next()
when {
l < r -> return -1
l > r -> return 1
}
}
return when {
rmi.hasNext() -> -1
lmi.hasNext() -> 1
else -> 0
}
}
fun MutableCollection<JdkId>.discoverJdks(project: Project) {
val os = OperatingSystem.current()
when {
os.isWindows -> discoverJdksOnWindows(project)
os.isMacOsX -> discoverJdksOnMacOS(project)
else -> discoverJdksOnUnix(project)
}
}
private val macOsJavaHomeOutRegexes =
listOf(
Regex("""\s+(\S+),\s+(\S+):\s+".*?"\s+(.+)"""),
Regex("""\s+(\S+)\s+\((.*?)\):\s+(.+)"""),
Regex("""\s+(\S+)\s+\((.*?)\)\s+"[^"]*"\s+-\s+"[^"]*"\s(.+)"""),
Regex("""\s+(\S+)\s+\((.+)\)\s+".+"\s+-\s+".+"\s+(.+)"""))
fun MutableCollection<JdkId>.discoverJdksOnMacOS(project: Project) {
val procBuilder = ProcessBuilder("/usr/libexec/java_home", "-V").redirectErrorStream(true)
val process = procBuilder.start()
val retCode = process.waitFor()
if (retCode != 0) throw GradleException("Unable to run 'java_home', return code $retCode")
process.inputStream.bufferedReader().forEachLine { line ->
for (rex in macOsJavaHomeOutRegexes) {
val matchResult = rex.matchEntire(line)
if (matchResult != null) {
val jdkHomeDir = File(matchResult.groupValues[3])
// Filter out JRE installed at /Library/Internet Plug-Ins/JavaAppletPlugin.plugin/
// and shown by the java_home tool
if (!jdkHomeDir.path.contains("JavaAppletPlugin.plugin")) {
addIfBetter(project, matchResult.groupValues[1], matchResult.groupValues[0], jdkHomeDir)
break
}
}
}
}
}
private val unixConventionalJdkLocations = listOf(
"/usr/lib/jvm", // *deb, Arch
"/opt", // *rpm, Gentoo, HP/UX
"/usr/lib", // Slackware 32
"/usr/lib64", // Slackware 64
"/usr/local", // OpenBSD, FreeBSD
"/usr/pkg/java", // NetBSD
"/usr/jdk/instances") // Solaris
private val unixConventionalJdkDirRex = Regex("jdk|jre|java|zulu")
fun MutableCollection<JdkId>.discoverJdksOnUnix(project: Project) {
for (loc in unixConventionalJdkLocations) {
val installedJdks = File(loc).listFiles { dir ->
dir.isDirectory &&
unixConventionalJdkDirRex.containsMatchIn(dir.name) &&
fileFrom(dir, "bin", "java").isFile
} ?: continue
for (dir in installedJdks) {
val versionMatch = javaVersionRegex.find(dir.name)
if (versionMatch == null) {
project.logger.info("Unable to extract version from possible JDK dir: $dir")
}
else {
addIfBetter(project, versionMatch.value, dir.name, dir)
}
}
}
}
private val windowsConventionalJdkRegistryPaths = listOf(
"SOFTWARE\\JavaSoft\\Java Development Kit",
"SOFTWARE\\Wow6432Node\\JavaSoft\\Java Development Kit",
"SOFTWARE\\JavaSoft\\JDK",
"SOFTWARE\\Wow6432Node\\JavaSoft\\JDK")
fun MutableCollection<JdkId>.discoverJdksOnWindows(project: Project) {
val registry = Native.get(WindowsRegistry::class.java)
for (regPath in windowsConventionalJdkRegistryPaths) {
val jdkKeys = try {
registry.getSubkeys(HKEY_LOCAL_MACHINE, regPath)
} catch (e: RuntimeException) {
// ignore missing nodes
continue
}
for (jdkKey in jdkKeys) {
try {
val javaHome = registry.getStringValue(HKEY_LOCAL_MACHINE, regPath + "\\" + jdkKey, "JavaHome")
val versionMatch = javaVersionRegex.find(jdkKey)
if (versionMatch == null) {
project.logger.info("Unable to extract version from possible JDK location: $javaHome ($jdkKey)")
}
else {
javaHome.takeIf { it.isNotEmpty() }
?.let { File(it) }
?.takeIf { it.isDirectory && fileFrom(it, "bin", "java.exe").isFile }
?.let {
addIfBetter(project, versionMatch.value, jdkKey, it)
}
}
}
catch (e: RuntimeException) {
// Ignore
}
}
}
}

View File

@@ -17,11 +17,16 @@
// usages in build scripts are not tracked properly
@file:Suppress("unused")
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.artifacts.ModuleDependency
import org.gradle.api.artifacts.dsl.RepositoryHandler
import org.gradle.api.artifacts.repositories.IvyArtifactRepository
import org.gradle.api.tasks.JavaExec
import org.gradle.api.tasks.TaskProvider
import org.gradle.kotlin.dsl.DependencyHandlerScope
import org.gradle.kotlin.dsl.extra
import org.gradle.kotlin.dsl.register
import java.io.File
private fun Project.kotlinBuildLocalDependenciesDir(): File =
@@ -82,6 +87,8 @@ fun Project.intellijCoreDep() = "kotlin.build:intellij-core:${rootProject.extra[
fun Project.jpsStandalone() = "kotlin.build:jps-standalone:${rootProject.extra["versions.intellijSdk"]}"
fun Project.nodeJSPlugin() = "kotlin.build:NodeJS:${rootProject.extra["versions.idea.NodeJS"]}"
fun Project.jpsBuildTest() = "com.jetbrains.intellij.idea:jps-build-test:${rootProject.extra["versions.intellijSdk"]}"
fun Project.kotlinxCollectionsImmutable() = "org.jetbrains.kotlinx:kotlinx-collections-immutable-jvm:${rootProject.extra["versions.kotlinx-collections-immutable"]}"
@@ -139,4 +146,70 @@ fun ModuleDependency.includeIntellijCoreJarDependencies(project: Project, jarsFi
rootProject = project.rootProject
)
fun Project.intellijRootDir() = IntellijRootUtils.getIntellijRootDir(project)
fun Project.intellijRootDir() = IntellijRootUtils.getIntellijRootDir(project)
fun DependencyHandlerScope.excludeInAndroidStudio(rootProject: Project, block: DependencyHandlerScope.() -> Unit) {
if (!rootProject.extra.has("versions.androidStudioRelease")) {
block()
}
}
fun Project.runIdeTask(name: String, ideaPluginDir: File, ideaSandboxDir: File, body: JavaExec.() -> Unit): TaskProvider<JavaExec> {
return tasks.register<JavaExec>(name) {
val ideaSandboxConfigDir = File(ideaSandboxDir, "config")
classpath = mainSourceSet.runtimeClasspath
mainClass.set("com.intellij.idea.Main")
workingDir = File(intellijRootDir(), "bin")
jvmArgs(
"-Xmx1250m",
"-XX:ReservedCodeCacheSize=240m",
"-XX:+HeapDumpOnOutOfMemoryError",
"-ea",
"-Didea.debug.mode=true",
"-Didea.system.path=$ideaSandboxDir",
"-Didea.config.path=$ideaSandboxConfigDir",
"-Didea.tooling.debug=true",
"-Dfus.internal.test.mode=true",
"-Dapple.laf.useScreenMenuBar=true",
"-Dapple.awt.graphics.UseQuartz=true",
"-Dsun.io.useCanonCaches=false",
"-Dplugin.path=${ideaPluginDir.absolutePath}"
)
jvmArgs("-Didea.platform.prefix=Idea")
if (rootProject.findProperty("versions.androidStudioRelease") != null) {
jvmArgs("-Didea.platform.prefix=AndroidStudio")
}
if (project.hasProperty("noPCE")) {
jvmArgs("-Didea.ProcessCanceledException=disabled")
}
jvmArgs("-Didea.is.internal=${project.findProperty("idea.is.internal") ?: true}")
project.findProperty("idea.args")?.let { arguments ->
jvmArgs(arguments.toString().split(" "))
}
args()
doFirst {
val disabledPluginsFile = File(ideaSandboxConfigDir, "disabled_plugins.txt")
val disabledPluginsContents = disabledPluginsFile.takeIf { it.isFile }?.readLines()
val filteredContents = disabledPluginsContents?.filterNot { it.contains("org.jetbrains.kotlin") }
if (filteredContents != null && filteredContents.size != disabledPluginsContents.size) {
with(disabledPluginsFile.printWriter()) {
filteredContents.forEach(this::println)
}
}
}
body()
}
}

View File

@@ -14,7 +14,6 @@ import org.gradle.api.tasks.bundling.Jar
import org.gradle.kotlin.dsl.*
import plugins.KotlinBuildPublishingPlugin
import plugins.configureRepository
import plugins.mainPublicationName
import java.util.*
internal const val PLUGIN_MARKER_SUFFIX = ".gradle.plugin"
@@ -27,7 +26,7 @@ fun Project.publishPluginMarkers(withEmptyJars: Boolean = true) {
val pluginDevelopment = extensions.getByType<PluginBundleExtension>()
val publishingExtension = extensions.getByType<PublishingExtension>()
val mainPublication = publishingExtension.publications[mainPublicationName] as MavenPublication
val mainPublication = publishingExtension.publications[KotlinBuildPublishingPlugin.PUBLICATION_NAME] as MavenPublication
pluginDevelopment.plugins.forEach { declaration ->
val markerPublication = createMavenMarkerPublication(declaration, mainPublication, publishingExtension.publications)

View File

@@ -68,7 +68,7 @@ class KotlinBuildPublishingPlugin @Inject constructor(
configure<PublishingExtension> {
publications {
create<MavenPublication>(project.mainPublicationName) {
create<MavenPublication>(PUBLICATION_NAME) {
from(kotlinLibraryComponent)
configureKotlinPomAttributes(project)
@@ -79,26 +79,16 @@ class KotlinBuildPublishingPlugin @Inject constructor(
}
companion object {
const val DEFAULT_MAIN_PUBLICATION_NAME = "Main"
const val MAIN_PUBLICATION_NAME_PROPERTY = "MainPublicationName"
const val PUBLICATION_NAME = "Main"
const val REPOSITORY_NAME = "Maven"
const val ADHOC_COMPONENT_NAME = "kotlinLibrary"
const val COMPILE_CONFIGURATION = "publishedCompile"
const val RUNTIME_CONFIGURATION = "publishedRuntime"
}
}
var Project.mainPublicationName: String
get() {
return if (project.extra.has(KotlinBuildPublishingPlugin.MAIN_PUBLICATION_NAME_PROPERTY))
project.extra.get(KotlinBuildPublishingPlugin.MAIN_PUBLICATION_NAME_PROPERTY) as String
else KotlinBuildPublishingPlugin.DEFAULT_MAIN_PUBLICATION_NAME
}
set(value) {
project.extra.set(KotlinBuildPublishingPlugin.MAIN_PUBLICATION_NAME_PROPERTY, value)
}
@OptIn(ExperimentalStdlibApi::class)
private fun humanReadableName(name: String) =
name.split("-").joinToString(separator = " ") { it.capitalize(Locale.ROOT) }
@@ -171,18 +161,16 @@ fun Project.configureDefaultPublishing() {
.all { configureRepository() }
}
private fun Project.getSensitiveProperty(name: String): String? {
return project.findProperty(name) as? String ?: System.getenv(name)
}
private fun Project.configureSigning() {
configure<SigningExtension> {
sign(extensions.getByType<PublishingExtension>().publications) // all publications
val signKeyId = project.getSensitiveProperty("signKeyId")
val signKeyId = project.findProperty("signKeyId") as? String
if (!signKeyId.isNullOrBlank()) {
val signKeyPrivate = project.getSensitiveProperty("signKeyPrivate") ?: error("Parameter `signKeyPrivate` not found")
val signKeyPassphrase = project.getSensitiveProperty("signKeyPassphrase") ?: error("Parameter `signKeyPassphrase` not found")
val signKeyPrivate = project.findProperty("signKeyPrivate") as? String
?: error("Parameter `signKeyPrivate` not found")
val signKeyPassphrase = project.findProperty("signKeyPassphrase") as? String
?: error("Parameter `signKeyPassphrase` not found")
useInMemoryPgpKeys(signKeyId, signKeyPrivate, signKeyPassphrase)
} else {
useGpgCmd()

View File

@@ -9,13 +9,13 @@ import org.gradle.internal.os.OperatingSystem
fun Test.configureTestDistribution(configure: TestDistributionExtension.() -> Unit = {}) {
if (extensions.findByType(TestDistributionExtension::class.java) == null) return
val isTeamcityBuild = project.kotlinBuildProperties.isTeamcityBuild
val testDistributionEnabled =
project.findProperty("kotlin.build.test.distribution.enabled")?.toString()?.toBoolean() ?: false
useJUnitPlatform()
extensions.configure(TestDistributionExtension::class.java) {
enabled.set(testDistributionEnabled)
enabled.set(true)
maxRemoteExecutors.set(20)
if (isTeamcityBuild) {
requirements.set(setOf("os=${OperatingSystem.current().familyName}"))

View File

@@ -24,6 +24,7 @@ dependencies {
testApi(projectTests(":compiler:tests-compiler-utils"))
testApi(projectTests(":compiler:tests-common-new"))
testCompile(projectTests(":jps-plugin"))
testCompile(commonDep("junit:junit"))
testCompile(intellijDep()) { includeJars("util", "idea", "idea_rt", rootProject = rootProject) }
@@ -33,9 +34,6 @@ dependencies {
testCompile(jpsStandalone()) { includeJars("jps-model") }
testCompile(jpsBuildTest())
testRuntimeOnly(compile(intellijCoreDep()) { includeJars("intellij-core") })
testRuntimeOnly(compile(intellijDep()) { includeJars("jna", rootProject = rootProject) })
testCompile("org.junit.platform:junit-platform-launcher:${commonVer("org.junit.platform", "")}")
}

View File

@@ -15,10 +15,7 @@ import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.codegen.CodegenTestFiles
import org.jetbrains.kotlin.codegen.GenerationUtils
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JvmTarget
import org.jetbrains.kotlin.config.languageVersionSettings
import org.jetbrains.kotlin.config.*
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.platform.jvm.JvmPlatforms
import org.jetbrains.kotlin.psi.KtFile
@@ -26,11 +23,11 @@ import org.jetbrains.kotlin.test.*
import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
import org.jetbrains.kotlin.test.model.DependencyKind
import org.jetbrains.kotlin.test.model.FrontendKinds
import org.jetbrains.kotlin.test.model.ResultingArtifact
import org.jetbrains.kotlin.test.runners.AbstractKotlinCompilerTest
import org.jetbrains.kotlin.test.services.*
import org.jetbrains.kotlin.test.services.configuration.CommonEnvironmentConfigurator
import org.jetbrains.kotlin.test.services.configuration.JvmEnvironmentConfigurator
import org.jetbrains.kotlin.test.services.impl.BackendKindExtractorImpl
import org.jetbrains.kotlin.test.services.impl.TemporaryDirectoryManagerImpl
import org.jetbrains.kotlin.test.services.sourceProviders.AdditionalDiagnosticsSourceFilesProvider
import org.jetbrains.kotlin.test.services.sourceProviders.CodegenHelpersSourceFilesProvider
@@ -385,7 +382,6 @@ class CodegenTestsOnAndroidGenerator private constructor(private val pathManager
"test${testDataFile.nameWithoutExtension.replaceFirstChar(Char::uppercaseChar)}",
emptySet()
)
startingArtifactFactory = { ResultingArtifact.Source() }
}.build(testDataFile.path)
}
@@ -410,6 +406,7 @@ class CodegenTestsOnAndroidGenerator private constructor(private val pathManager
assertions = JUnit5Assertions
useAdditionalService<TemporaryDirectoryManager>(::TemporaryDirectoryManagerImpl)
useAdditionalService<BackendKindExtractor>(::BackendKindExtractorImpl)
useSourcePreprocessor(*AbstractKotlinCompilerTest.defaultPreprocessors.toTypedArray())
useDirectives(*AbstractKotlinCompilerTest.defaultDirectiveContainers.toTypedArray())
}

View File

@@ -5,10 +5,13 @@
package org.jetbrains.kotlin.backend.common
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.resolve.sam.getAbstractMembers
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.typeUtil.replaceArgumentsWithNothing
class SamType constructor(val type: KotlinType) {
@@ -34,3 +37,54 @@ class SamType constructor(val type: KotlinType) {
}
}
open class SamTypeFactory {
fun createByValueParameter(valueParameter: ValueParameterDescriptor): SamType? {
val singleArgumentType: KotlinType
val originalSingleArgumentType: KotlinType?
val varargElementType = valueParameter.varargElementType
if (varargElementType != null) {
singleArgumentType = varargElementType
originalSingleArgumentType = valueParameter.original.varargElementType
assert(originalSingleArgumentType != null) {
"Value parameter and original value parameter have inconsistent varargs: " +
valueParameter + "; " + valueParameter.original
}
} else {
singleArgumentType = valueParameter.type
originalSingleArgumentType = valueParameter.original.type
}
if (singleArgumentType.isError || originalSingleArgumentType!!.isError) {
return null
}
// This can be true in case when the value parameter is in the method of a generic type with out-projection.
// We approximate Inv<Captured#1> to Nothing, while Inv itself can be a SAM interface safe to call here
// (see testData genericSamProjectedOut.kt for details)
// In such a case we can't have a proper supertype since wildcards are not allowed there,
// so we use Nothing arguments instead that leads to a raw type used for a SAM wrapper.
// See org.jetbrains.kotlin.codegen.state.KotlinTypeMapper#writeGenericType to understand how
// raw types and Nothing arguments relate.
val originalTypeToUse =
if (KotlinBuiltIns.isNothing(singleArgumentType))
originalSingleArgumentType.replaceArgumentsWithNothing()
else singleArgumentType
return create(originalTypeToUse.removeExternalProjections())
}
open fun isSamType(type: KotlinType): Boolean {
val descriptor = type.constructor.declarationDescriptor
return descriptor is ClassDescriptor && descriptor.isFun
}
fun create(originalType: KotlinType): SamType? {
return if (isSamType(originalType)) SamType(originalType) else null
}
private fun KotlinType.removeExternalProjections(): KotlinType {
val newArguments = arguments.map { TypeProjectionImpl(Variance.INVARIANT, it.type) }
return replace(newArguments)
}
companion object {
val INSTANCE = SamTypeFactory()
}
}

View File

@@ -1,76 +0,0 @@
/*
* 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.common
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.checker.intersectWrappedTypes
import org.jetbrains.kotlin.types.typeUtil.replaceArgumentsWithNothing
class SamTypeApproximator(builtIns: KotlinBuiltIns, languageVersionSettings: LanguageVersionSettings) {
private val typeApproximator = TypeApproximator(builtIns, languageVersionSettings)
fun getSamTypeForValueParameter(valueParameter: ValueParameterDescriptor): KotlinType? {
val singleArgumentType: KotlinType
val originalSingleArgumentType: KotlinType?
val varargElementType = valueParameter.varargElementType
if (varargElementType != null) {
singleArgumentType = varargElementType
originalSingleArgumentType = valueParameter.original.varargElementType
assert(originalSingleArgumentType != null) {
"Value parameter and original value parameter have inconsistent varargs: " +
valueParameter + "; " + valueParameter.original
}
} else {
singleArgumentType = valueParameter.type
originalSingleArgumentType = valueParameter.original.type
}
if (singleArgumentType.isError || originalSingleArgumentType!!.isError) {
return null
}
// This can be true in case when the value parameter is in the method of a generic type with out-projection.
// We approximate Inv<Captured#1> to Nothing, while Inv itself can be a SAM interface safe to call here
// (see testData genericSamProjectedOut.kt for details)
// In such a case we can't have a proper supertype since wildcards are not allowed there,
// so we use Nothing arguments instead that leads to a raw type used for a SAM wrapper.
// See org.jetbrains.kotlin.codegen.state.KotlinTypeMapper#writeGenericType to understand how
// raw types and Nothing arguments relate.
val originalTypeToUse =
if (KotlinBuiltIns.isNothing(singleArgumentType))
originalSingleArgumentType.replaceArgumentsWithNothing()
else
singleArgumentType
val approximatedOriginalTypeToUse =
typeApproximator.approximateToSubType(
originalTypeToUse,
TypeApproximatorConfiguration.UpperBoundAwareIntersectionTypeApproximator
) ?: originalTypeToUse
approximatedOriginalTypeToUse as KotlinType
return approximatedOriginalTypeToUse.removeExternalProjections()
}
private fun KotlinType.removeExternalProjections(): KotlinType {
val newArguments = arguments.map { TypeProjectionImpl(Variance.INVARIANT, it.type) }
return replace(newArguments)
}
}
open class SamTypeFactory {
open fun isSamType(type: KotlinType): Boolean {
val descriptor = type.constructor.declarationDescriptor
return descriptor is ClassDescriptor && descriptor.isFun
}
fun create(originalType: KotlinType): SamType? {
return if (isSamType(originalType)) SamType(originalType) else null
}
}

View File

@@ -5,15 +5,21 @@
package org.jetbrains.kotlin.backend.common
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.config.coroutinesIntrinsicsPackageFqName
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.isTopLevelInPackage
import org.jetbrains.kotlin.name.Name
val COROUTINE_SUSPENDED_NAME = Name.identifier("COROUTINE_SUSPENDED")
fun FunctionDescriptor.isBuiltInSuspendCoroutineUninterceptedOrReturn(): Boolean =
fun FunctionDescriptor.isBuiltInIntercepted(languageVersionSettings: LanguageVersionSettings): Boolean =
!languageVersionSettings.supportsFeature(LanguageFeature.ReleaseCoroutines) &&
isTopLevelInPackage("intercepted", languageVersionSettings.coroutinesIntrinsicsPackageFqName().asString())
fun FunctionDescriptor.isBuiltInSuspendCoroutineUninterceptedOrReturn(languageVersionSettings: LanguageVersionSettings): Boolean =
isTopLevelInPackage(
"suspendCoroutineUninterceptedOrReturn",
StandardNames.COROUTINES_INTRINSICS_PACKAGE_FQ_NAME.asString()
languageVersionSettings.coroutinesIntrinsicsPackageFqName().asString()
)

View File

@@ -19,9 +19,7 @@ interface TypeMappingContext<Writer : JvmDescriptorTypeWriter<Type>> {
fun getClassInternalName(typeConstructor: TypeConstructorMarker): String
fun getScriptInternalName(typeConstructor: TypeConstructorMarker): String
// NB: The counterpart, [KotlinTypeMapper#writeGenericType], doesn't have restriction on [type]
fun Writer.writeGenericType(type: KotlinTypeMarker, asmType: Type, mode: TypeMappingMode)
fun Writer.writeGenericType(type: SimpleTypeMarker, asmType: Type, mode: TypeMappingMode)
}
object AbstractTypeMapper {
@@ -46,7 +44,6 @@ object AbstractTypeMapper {
sw: Writer? = null
): Type = context.typeContext.mapType(context, type, mode, sw)
// NB: The counterpart, [descriptorBasedTypeSignatureMapping#mapType] doesn't have restriction on [type].
@OptIn(ExperimentalStdlibApi::class)
private fun <Writer : JvmDescriptorTypeWriter<Type>> TypeSystemCommonBackendContextForTypeMapping.mapType(
context: TypeMappingContext<Writer>,
@@ -54,7 +51,10 @@ object AbstractTypeMapper {
mode: TypeMappingMode = TypeMappingMode.DEFAULT,
sw: Writer? = null
): Type {
if (type is SimpleTypeMarker && type.isSuspendFunction()) {
if (type !is SimpleTypeMarker) {
error("Unexpected type: $type (original Kotlin type=$type of ${type.let { it::class }})")
}
if (type.isSuspendFunction()) {
val argumentsCount = type.argumentsCount()
val argumentsList = type.asArgumentList()
@@ -79,7 +79,7 @@ object AbstractTypeMapper {
val typeConstructor = type.typeConstructor()
when {
type is SimpleTypeMarker && type.isArrayOrNullableArray() -> {
type.isArrayOrNullableArray() -> {
val typeArgument = type.asArgumentList()[0]
val (variance, memberType) = when {
typeArgument.isStarProjection() -> Variance.OUT_VARIANCE to nullableAnyType()
@@ -99,7 +99,7 @@ object AbstractTypeMapper {
return AsmUtil.getArrayType(arrayElementType)
}
type is SimpleTypeMarker && typeConstructor.isClassTypeConstructor() -> {
typeConstructor.isClassTypeConstructor() -> {
if (typeConstructor.isInlineClass() && !mode.needInlineClassWrapping) {
val expandedType = computeExpandedTypeForInlineClass(type)
require(expandedType is SimpleTypeMarker?)

View File

@@ -17,7 +17,7 @@
package org.jetbrains.kotlin.codegen
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.name.SpecialNames
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeSubstitutor
@@ -26,7 +26,7 @@ class AccessorForConstructorDescriptor(
containingDeclaration: DeclarationDescriptor,
override val superCallTarget: ClassDescriptor?,
override val accessorKind: AccessorKind
) : AbstractAccessorForFunctionDescriptor(containingDeclaration, SpecialNames.INIT),
) : AbstractAccessorForFunctionDescriptor(containingDeclaration, Name.special("<init>")),
ClassConstructorDescriptor,
AccessorForCallableDescriptor<ConstructorDescriptor> {

View File

@@ -11,7 +11,6 @@ import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument
import org.jetbrains.kotlin.resolve.calls.model.ExpressionValueArgument
import org.jetbrains.kotlin.resolve.calls.model.VarargValueArgument
import org.jetbrains.kotlin.resolve.inline.InlineUtil
import org.jetbrains.kotlin.resolve.jvm.AsmTypes.OBJECT_TYPE
import org.jetbrains.kotlin.types.upperIfFlexible
import org.jetbrains.org.objectweb.asm.Type
@@ -52,7 +51,7 @@ class CallBasedArgumentGenerator(
callGenerator.putValueIfNeeded(
getJvmKotlinType(i),
StackValue.createDefaultValue(valueParameterTypes[i]),
if (InlineUtil.isInlineParameter(valueParameters[i])) ValueKind.DEFAULT_INLINE_PARAMETER else ValueKind.DEFAULT_PARAMETER,
ValueKind.DEFAULT_PARAMETER,
i
)
}

View File

@@ -15,9 +15,9 @@ enum class ValueKind {
GENERAL,
GENERAL_VARARG,
DEFAULT_PARAMETER,
DEFAULT_INLINE_PARAMETER,
DEFAULT_MASK,
METHOD_HANDLE_IN_DEFAULT,
CAPTURED,
NON_INLINEABLE_ARGUMENT_FOR_INLINE_PARAMETER_CALLED_IN_SUSPEND,
NON_INLINEABLE_ARGUMENT_FOR_INLINE_SUSPEND_PARAMETER
}

View File

@@ -27,16 +27,24 @@ import org.jetbrains.annotations.TestOnly;
import org.jetbrains.kotlin.backend.common.output.OutputFile;
import org.jetbrains.kotlin.backend.common.output.OutputFileCollection;
import org.jetbrains.kotlin.codegen.state.GenerationState;
import org.jetbrains.kotlin.config.AnalysisFlags;
import org.jetbrains.kotlin.config.JvmAnalysisFlags;
import org.jetbrains.kotlin.descriptors.ClassDescriptor;
import org.jetbrains.kotlin.descriptors.DescriptorUtilKt;
import org.jetbrains.kotlin.descriptors.ModuleDescriptor;
import org.jetbrains.kotlin.incremental.components.NoLookupLocation;
import org.jetbrains.kotlin.load.kotlin.ModuleMappingUtilKt;
import org.jetbrains.kotlin.metadata.ProtoBuf;
import org.jetbrains.kotlin.metadata.jvm.JvmModuleProtoBuf;
import org.jetbrains.kotlin.metadata.jvm.deserialization.ModuleMapping;
import org.jetbrains.kotlin.metadata.jvm.deserialization.ModuleMappingKt;
import org.jetbrains.kotlin.metadata.jvm.deserialization.PackageParts;
import org.jetbrains.kotlin.metadata.serialization.StringTable;
import org.jetbrains.kotlin.name.ClassId;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.psi.KtFile;
import org.jetbrains.kotlin.resolve.CompilerDeserializationConfiguration;
import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt;
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin;
import org.jetbrains.kotlin.serialization.StringTableImpl;
import org.jetbrains.org.objectweb.asm.Type;
@@ -44,6 +52,7 @@ import org.jetbrains.org.objectweb.asm.Type;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import static org.jetbrains.kotlin.codegen.JvmCodegenUtil.getMappingFileName;
@@ -125,6 +134,11 @@ public class ClassFileFactory implements OutputFileCollection {
StringTableImpl stringTable = new StringTableImpl();
ClassFileUtilsKt.addDataFromCompiledModule(builder, packagePartRegistry, stringTable, state);
List<String> experimental = state.getLanguageVersionSettings().getFlag(AnalysisFlags.getExperimental());
if (!experimental.isEmpty()) {
writeExperimentalMarkers(state.getModule(), builder, experimental, stringTable);
}
Pair<ProtoBuf.StringTable, ProtoBuf.QualifiedNameTable> tables = stringTable.buildProto();
builder.setStringTable(tables.getFirst());
builder.setQualifiedNameTable(tables.getSecond());
@@ -148,6 +162,26 @@ public class ClassFileFactory implements OutputFileCollection {
});
}
private static void writeExperimentalMarkers(
@NotNull ModuleDescriptor module,
@NotNull JvmModuleProtoBuf.Module.Builder builder,
@NotNull List<String> experimental,
@NotNull StringTable stringTable
) {
for (String fqName : experimental) {
ClassDescriptor descriptor =
DescriptorUtilKt.resolveClassByFqName(module, new FqName(fqName), NoLookupLocation.FOR_ALREADY_TRACKED);
if (descriptor != null) {
ProtoBuf.Annotation.Builder annotation = ProtoBuf.Annotation.newBuilder();
ClassId classId = DescriptorUtilsKt.getClassId(descriptor);
if (classId != null) {
annotation.setId(stringTable.getQualifiedClassNameIndex(classId.asString(), false));
builder.addAnnotation(annotation);
}
}
}
}
@NotNull
@Override
public List<OutputFile> asList() {

View File

@@ -475,7 +475,7 @@ public class ClosureCodegen extends MemberCodegen<KtElement> {
List<Type> superCtorArgTypes = new ArrayList<>();
if (superClassAsmType.equals(LAMBDA) || functionReferenceTarget != null ||
CoroutineCodegenUtilKt.isCoroutineSuperClass(superClassAsmType.getInternalName())
CoroutineCodegenUtilKt.isCoroutineSuperClass(state.getLanguageVersionSettings(), superClassAsmType.getInternalName())
) {
iv.iconst(CodegenUtilKt.getArity(funDescriptor));
superCtorArgTypes.add(Type.INT_TYPE);

View File

@@ -7,7 +7,7 @@ package org.jetbrains.kotlin.codegen
import org.jetbrains.kotlin.codegen.binding.CodegenBinding
import org.jetbrains.kotlin.codegen.inline.loadCompiledInlineFunction
import org.jetbrains.kotlin.codegen.optimization.nullCheck.usesLocalExceptParameterNullCheck
import org.jetbrains.kotlin.codegen.optimization.nullCheck.isCheckParameterIsNotNull
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
@@ -17,6 +17,10 @@ import org.jetbrains.kotlin.resolve.FunctionImportedFromObject
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.jvm.requiresFunctionNameManglingForReturnType
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DescriptorWithContainerSource
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode
import org.jetbrains.org.objectweb.asm.tree.MethodNode
import org.jetbrains.org.objectweb.asm.tree.VarInsnNode
class DelegatedPropertiesCodegenHelper(private val state: GenerationState) {
@@ -71,7 +75,20 @@ class DelegatedPropertiesCodegenHelper(private val state: GenerationState) {
val asmMethod = state.typeMapper.mapAsmMethod(calleeDescriptor)
val isMangled = requiresFunctionNameManglingForReturnType(calleeDescriptor)
val methodNode = loadCompiledInlineFunction(containerId, asmMethod, calleeDescriptor.isSuspend, isMangled, state).node
return methodNode.usesLocalExceptParameterNullCheck(metadataParameterIndex)
return isMetadataParameterUsedInCompiledMethodBody(metadataParameterIndex, methodNode)
}
private fun isMetadataParameterUsedInCompiledMethodBody(metadataParameterIndex: Int, methodNode: MethodNode): Boolean =
methodNode.instructions.toArray().any { insn ->
insn is VarInsnNode && insn.opcode == Opcodes.ALOAD && insn.`var` == metadataParameterIndex &&
!isParameterNullCheckArgument(insn)
}
private fun isParameterNullCheckArgument(insn: AbstractInsnNode): Boolean {
val next1 = insn.next
val next2 = next1.next
return next1 != null && next2 != null &&
next1.opcode == Opcodes.LDC && next2.isCheckParameterIsNotNull()
}
private fun getMetadataParameterIndex(calleeDescriptor: FunctionDescriptor): Int {

View File

@@ -89,6 +89,7 @@ import org.jetbrains.kotlin.synthetic.SyntheticJavaPropertyDescriptor;
import org.jetbrains.kotlin.types.*;
import org.jetbrains.kotlin.types.checker.ClassicTypeSystemContextImpl;
import org.jetbrains.kotlin.types.expressions.DoubleColonLHS;
import org.jetbrains.kotlin.types.model.KotlinTypeMarker;
import org.jetbrains.kotlin.types.model.TypeParameterMarker;
import org.jetbrains.kotlin.types.typesApproximation.CapturedTypeApproximationKt;
import org.jetbrains.kotlin.util.OperatorNameConventions;
@@ -1263,7 +1264,7 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
// Thus, do not generate it. Otherwise, it leads to VerifyError on run-time.
boolean isCrossinlineLambda = (callGenerator instanceof PsiInlineCodegen) &&
Objects.requireNonNull(((PsiInlineCodegen) callGenerator).getActiveLambda(),
"no active lambda found").isCrossInline();
"no active lambda found").isCrossInline;
if (!isCrossinlineLambda) {
v.aconst(null);
}
@@ -1287,11 +1288,11 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
@NotNull
private static CallableDescriptor unwrapOriginalReceiverOwnerForSuspendLambda(@NotNull MethodContext context) {
FunctionDescriptor originalForInvokeSuspend =
context.getFunctionDescriptor().getUserData(CoroutineCodegenUtilKt.INITIAL_SUSPEND_DESCRIPTOR_FOR_INVOKE_SUSPEND);
FunctionDescriptor originalForDoResume =
context.getFunctionDescriptor().getUserData(CoroutineCodegenUtilKt.INITIAL_SUSPEND_DESCRIPTOR_FOR_DO_RESUME);
if (originalForInvokeSuspend != null) {
return originalForInvokeSuspend;
if (originalForDoResume != null) {
return originalForDoResume;
}
if (context.getFunctionDescriptor().isSuspend()) {
@@ -2616,6 +2617,26 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
? coroutineInstanceValueForSuspensionPoint
: getContinuationParameterFromEnclosingSuspendFunction(resolvedCall);
if (coroutineInstanceValue != null && needsExperimentalCoroutinesWrapper(resolvedCall.getCandidateDescriptor())) {
StackValue releaseContinuation = coroutineInstanceValue;
coroutineInstanceValue = new StackValue(CoroutineCodegenUtilKt.EXPERIMENTAL_CONTINUATION_ASM_TYPE) {
@Override
public void putSelector(
@NotNull Type type, @Nullable KotlinType kotlinType, @NotNull InstructionAdapter v
) {
releaseContinuation.put(CoroutineCodegenUtilKt.RELEASE_CONTINUATION_ASM_TYPE, v);
invokeCoroutineMigrationMethod(
v,
"toExperimentalContinuation",
Type.getMethodDescriptor(
CoroutineCodegenUtilKt.EXPERIMENTAL_CONTINUATION_ASM_TYPE,
CoroutineCodegenUtilKt.RELEASE_CONTINUATION_ASM_TYPE
)
);
}
};
}
tempVariables.put(continuationExpression, coroutineInstanceValue);
}
@@ -2753,7 +2774,7 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
}
SuspensionPointKind suspensionPointKind =
CoroutineCodegenUtilKt.isSuspensionPoint(resolvedCall, this);
CoroutineCodegenUtilKt.isSuspensionPoint(resolvedCall, this, state.getLanguageVersionSettings());
boolean maybeSuspensionPoint = suspensionPointKind != SuspensionPointKind.NEVER && !insideCallableReference();
boolean isConstructor = resolvedCall.getResultingDescriptor() instanceof ConstructorDescriptor;
if (!(callableMethod instanceof IntrinsicWithSpecialReceiver)) {
@@ -2812,7 +2833,7 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
KotlinType unboxedInlineClass = CoroutineCodegenUtilKt.originalReturnTypeOfSuspendFunctionReturningUnboxedInlineClass(
(FunctionDescriptor) resolvedCall.getResultingDescriptor(), typeMapper);
if (unboxedInlineClass != null) {
CoroutineCodegenUtilKt.generateCoroutineSuspendedCheck(v);
CoroutineCodegenUtilKt.generateCoroutineSuspendedCheck(v, state.getLanguageVersionSettings());
}
}
@@ -2927,7 +2948,7 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
FunctionDescriptor original =
CoroutineCodegenUtilKt.getOriginalSuspendFunctionView(
unwrapInitialSignatureDescriptor(DescriptorUtils.unwrapFakeOverride((FunctionDescriptor) descriptor.getOriginal())),
bindingContext
bindingContext, state
);
FunctionDescriptor functionDescriptor =
@@ -2950,10 +2971,8 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
if (isDefaultCompilation) {
return new InlineCodegenForDefaultBody(functionDescriptor, this, state, signature, sourceCompiler);
} else {
return new PsiInlineCodegen(
this, state, functionDescriptor, signature, typeParameterMappings, sourceCompiler,
typeMapper.mapImplementationOwner(functionDescriptor), typeMapper.mapOwner(descriptor), callElement
);
return new PsiInlineCodegen(this, state, functionDescriptor, signature, typeParameterMappings, sourceCompiler,
typeMapper.mapImplementationOwner(functionDescriptor), typeMapper.mapOwner(descriptor));
}
}
@@ -5222,7 +5241,7 @@ The "returned" value of try expression with no finally is either the last expres
}
CodegenUtilKt.generateAsCast(
v, rightKotlinType, boxedRightType, safeAs, state.getUnifiedNullChecks()
v, rightKotlinType, boxedRightType, safeAs, state.getLanguageVersionSettings(), state.getUnifiedNullChecks()
);
return Unit.INSTANCE;
@@ -5276,7 +5295,7 @@ The "returned" value of try expression with no finally is either the last expres
return null;
}
CodegenUtilKt.generateIsCheck(v, rhsKotlinType, type);
CodegenUtilKt.generateIsCheck(v, rhsKotlinType, type, state.getLanguageVersionSettings().supportsFeature(LanguageFeature.ReleaseCoroutines));
return null;
});
}

View File

@@ -481,7 +481,8 @@ public class FunctionCodegen {
}
if (!functionDescriptor.isExternal()) {
generateMethodBody(mv, functionDescriptor, methodContext, jvmSignature, strategy, memberCodegen, state.getJvmDefaultMode());
generateMethodBody(mv, functionDescriptor, methodContext, jvmSignature, strategy, memberCodegen, state.getJvmDefaultMode(),
state.getLanguageVersionSettings().supportsFeature(LanguageFeature.ReleaseCoroutines));
}
else if (staticInCompanionObject) {
// native @JvmStatic foo() in companion object should delegate to the static native function moved to the outer class
@@ -582,7 +583,8 @@ public class FunctionCodegen {
@NotNull JvmMethodSignature signature,
@NotNull FunctionGenerationStrategy strategy,
@NotNull MemberCodegen<?> parentCodegen,
@NotNull JvmDefaultMode jvmDefaultMode
@NotNull JvmDefaultMode jvmDefaultMode,
boolean isReleaseCoroutines
) {
mv.visitCode();
@@ -592,7 +594,8 @@ public class FunctionCodegen {
KotlinTypeMapper typeMapper = parentCodegen.typeMapper;
if (BuiltinSpecialBridgesUtil.shouldHaveTypeSafeBarrier(functionDescriptor, typeMapper::mapAsmMethod)) {
generateTypeCheckBarrierIfNeeded(
new InstructionAdapter(mv), functionDescriptor, signature.getReturnType(), null, typeMapper);
new InstructionAdapter(mv), functionDescriptor, signature.getReturnType(), null, typeMapper,
isReleaseCoroutines);
}
Label methodEntry = null;
@@ -1427,7 +1430,8 @@ public class FunctionCodegen {
MemberCodegen.markLineNumberForDescriptor(owner.getThisDescriptor(), iv);
if (delegateTo.getArgumentTypes().length > 0 && isSpecialBridge) {
generateTypeCheckBarrierIfNeeded(iv, descriptor, bridge.getReturnType(), delegateTo.getArgumentTypes(), typeMapper);
generateTypeCheckBarrierIfNeeded(iv, descriptor, bridge.getReturnType(), delegateTo.getArgumentTypes(), typeMapper,
state.getLanguageVersionSettings().supportsFeature(LanguageFeature.ReleaseCoroutines));
}
iv.load(0, OBJECT_TYPE);
@@ -1473,7 +1477,8 @@ public class FunctionCodegen {
@NotNull FunctionDescriptor descriptor,
@NotNull Type returnType,
@Nullable Type[] delegateParameterTypes,
@NotNull KotlinTypeMapper typeMapper
@NotNull KotlinTypeMapper typeMapper,
boolean isReleaseCoroutines
) {
BuiltinMethodsWithSpecialGenericSignature.TypeSafeBarrierDescription typeSafeBarrierDescription =
BuiltinMethodsWithSpecialGenericSignature.getDefaultValueForOverriddenBuiltinFunction(descriptor);
@@ -1507,7 +1512,7 @@ public class FunctionCodegen {
} else {
targetBoxedType = boxType(delegateParameterTypes[i]);
}
CodegenUtilKt.generateIsCheck(iv, kotlinType, targetBoxedType);
CodegenUtilKt.generateIsCheck(iv, kotlinType, targetBoxedType, isReleaseCoroutines);
iv.ifeq(defaultBranch);
}
}

View File

@@ -5,12 +5,13 @@
package org.jetbrains.kotlin.codegen
import org.jetbrains.kotlin.builtins.StandardNames.COROUTINES_JVM_INTERNAL_PACKAGE_FQ_NAME
import org.jetbrains.kotlin.builtins.createFunctionType
import org.jetbrains.kotlin.codegen.coroutines.coroutinesJvmInternalPackageFqName
import org.jetbrains.kotlin.codegen.coroutines.getOrCreateJvmSuspendFunctionView
import org.jetbrains.kotlin.codegen.coroutines.isSuspendLambdaOrLocalFunction
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.config.isReleaseCoroutines
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor
@@ -32,7 +33,8 @@ class JvmRuntimeTypes(
private val generateOptimizedCallableReferenceSuperClasses: Boolean
) {
private val kotlinJvmInternalPackage = MutablePackageFragmentDescriptor(module, FqName("kotlin.jvm.internal"))
private val kotlinCoroutinesJvmInternalPackage = MutablePackageFragmentDescriptor(module, COROUTINES_JVM_INTERNAL_PACKAGE_FQ_NAME)
private val kotlinCoroutinesJvmInternalPackage =
MutablePackageFragmentDescriptor(module, languageVersionSettings.coroutinesJvmInternalPackageFqName())
private fun internal(className: String, packageFragment: PackageFragmentDescriptor = kotlinJvmInternalPackage): Lazy<ClassDescriptor> =
lazy { createClass(packageFragment, className) }
@@ -51,16 +53,24 @@ class JvmRuntimeTypes(
private val localVariableReference: ClassDescriptor by internal("LocalVariableReference")
private val mutableLocalVariableReference: ClassDescriptor by internal("MutableLocalVariableReference")
private val coroutineImpl: ClassDescriptor by internal("CoroutineImpl", kotlinCoroutinesJvmInternalPackage)
private val continuationImpl: ClassDescriptor by coroutinesInternal("ContinuationImpl")
private val restrictedContinuationImpl: ClassDescriptor by coroutinesInternal("RestrictedContinuationImpl")
private val suspendLambda: ClassDescriptor by coroutinesInternal("SuspendLambda")
private val restrictedSuspendLambda: ClassDescriptor by coroutinesInternal("RestrictedSuspendLambda")
private val suspendFunctionInterface: ClassDescriptor? by lazy {
createClass(kotlinCoroutinesJvmInternalPackage, "SuspendFunction", ClassKind.INTERFACE)
if (languageVersionSettings.isReleaseCoroutines())
createClass(kotlinCoroutinesJvmInternalPackage, "SuspendFunction", ClassKind.INTERFACE)
else null
}
private fun createCoroutineSuperClass(className: String): ClassDescriptor = createClass(kotlinCoroutinesJvmInternalPackage, className)
private fun createCoroutineSuperClass(className: String): ClassDescriptor {
return if (languageVersionSettings.isReleaseCoroutines())
createClass(kotlinCoroutinesJvmInternalPackage, className)
else
coroutineImpl
}
private val propertyReferences: List<ClassDescriptor> by propertyClasses("PropertyReference", "")
private val mutablePropertyReferences: List<ClassDescriptor> by propertyClasses("MutablePropertyReference", "")
@@ -84,7 +94,7 @@ class JvmRuntimeTypes(
fun getSupertypesForClosure(descriptor: FunctionDescriptor): Collection<KotlinType> {
val actualFunctionDescriptor =
if (descriptor.isSuspend)
getOrCreateJvmSuspendFunctionView(descriptor)
getOrCreateJvmSuspendFunctionView(descriptor, languageVersionSettings.supportsFeature(LanguageFeature.ReleaseCoroutines))
else
descriptor
@@ -108,7 +118,7 @@ class JvmRuntimeTypes(
if (descriptor.isSuspend) {
return mutableListOf<KotlinType>().apply {
if (actualFunctionDescriptor.extensionReceiverParameter?.type
?.isRestrictsSuspensionReceiver() == true
?.isRestrictsSuspensionReceiver(languageVersionSettings) == true
) {
if (descriptor.isSuspendLambdaOrLocalFunction()) {
add(restrictedSuspendLambda.defaultType)

View File

@@ -5,11 +5,10 @@
package org.jetbrains.kotlin.codegen
import org.jetbrains.kotlin.backend.common.SamTypeFactory
import org.jetbrains.kotlin.load.java.sam.JavaSingleAbstractMethodUtils
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.backend.common.SamTypeFactory
class JvmSamTypeFactory : SamTypeFactory() {
override fun isSamType(type: KotlinType) =
JavaSingleAbstractMethodUtils.isSamType(type)
object JvmSamTypeFactory : SamTypeFactory() {
override fun isSamType(type: KotlinType) = JavaSingleAbstractMethodUtils.isSamType(type)
}

View File

@@ -22,9 +22,7 @@ import java.lang.StringBuilder
class StringConcatGenerator(val mode: JvmStringConcat, val mv: InstructionAdapter) {
private val template = StringBuilder("")
private val specialSymbolsInTemplate = arrayListOf<String>()
private val paramTypes = arrayListOf<Type>()
private var paramSlots = 0
private var justFlushed = false
@JvmOverloads
@@ -44,16 +42,7 @@ class StringConcatGenerator(val mode: JvmStringConcat, val mv: InstructionAdapte
if (mode == JvmStringConcat.INDY_WITH_CONSTANTS) {
when (stackValue) {
is StackValue.Constant -> {
val value = stackValue.value
if (value is String && (value.contains("\u0001") || value.contains("\u0002"))) {
template.append("\u0002") //reference to special symbols added on next line
specialSymbolsInTemplate.add(value)
} else if (value is Char && (value == 1.toChar() || value == 2.toChar())) {
template.append("\u0002") //reference to special symbols added on next line
specialSymbolsInTemplate.add(value.toString())
} else {
template.append(value)
}
template.append(stackValue.value)
return
}
TRUE -> {
@@ -85,9 +74,8 @@ class StringConcatGenerator(val mode: JvmStringConcat, val mv: InstructionAdapte
} else {
justFlushed = false
paramTypes.add(type)
paramSlots += type.size
template.append("\u0001")
if (paramSlots >= 199) {
if (paramTypes.size == 200) {
// Concatenate current arguments into string
// because of `StringConcatFactory` limitation add use it as new argument for further processing:
// "The number of parameter slots in {@code concatType} is less than or equal to 200"
@@ -116,7 +104,7 @@ class StringConcatGenerator(val mode: JvmStringConcat, val mv: InstructionAdapte
"makeConcatWithConstants",
Type.getMethodDescriptor(JAVA_STRING_TYPE, *paramTypes.toTypedArray()),
bootstrap,
arrayOf(template.toString()) + specialSymbolsInTemplate
arrayOf(template.toString())
)
} else {
val bootstrap = Handle(
@@ -134,15 +122,10 @@ class StringConcatGenerator(val mode: JvmStringConcat, val mv: InstructionAdapte
arrayOf()
)
}
//clear old template
template.clear()
specialSymbolsInTemplate.clear()
paramTypes.clear()
//add just flushed string
paramTypes.add(JAVA_STRING_TYPE)
template.append("\u0001")
paramSlots = JAVA_STRING_TYPE.size
}
}
@@ -166,4 +149,4 @@ class StringConcatGenerator(val mode: JvmStringConcat, val mv: InstructionAdapte
StringConcatGenerator(state.runtimeStringConcat, mv)
}
}
}

View File

@@ -15,7 +15,6 @@ import kotlin.Pair;
import kotlin.collections.CollectionsKt;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.backend.common.SamTypeApproximator;
import org.jetbrains.kotlin.builtins.FunctionTypesKt;
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
import org.jetbrains.kotlin.builtins.ReflectionTypes;
@@ -35,10 +34,8 @@ import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor;
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor;
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil;
import org.jetbrains.kotlin.load.java.JvmAbi;
import org.jetbrains.kotlin.load.java.sam.JavaSingleAbstractMethodUtils;
import org.jetbrains.kotlin.name.ClassId;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.name.SpecialNames;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.psi.stubs.KotlinFileStub;
import org.jetbrains.kotlin.resolve.BindingContext;
@@ -93,7 +90,6 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid {
private final ClassBuilderMode classBuilderMode;
private final DelegatedPropertiesCodegenHelper delegatedPropertiesCodegenHelper;
private final JvmDefaultMode jvmDefaultMode;
private final SamTypeApproximator samTypeApproximator;
public CodegenAnnotatingVisitor(@NotNull GenerationState state) {
this.bindingTrace = state.getBindingTrace();
@@ -104,8 +100,7 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid {
this.languageVersionSettings = state.getLanguageVersionSettings();
this.classBuilderMode = state.getClassBuilderMode();
this.delegatedPropertiesCodegenHelper = new DelegatedPropertiesCodegenHelper(state);
this.jvmDefaultMode = state.getJvmDefaultMode();
this.samTypeApproximator = new SamTypeApproximator(state.getModule().getBuiltIns(), state.getLanguageVersionSettings());
jvmDefaultMode = state.getJvmDefaultMode();
}
@NotNull
@@ -299,7 +294,7 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid {
private String getName(ClassDescriptor classDescriptor) {
String base = peekFromStack(nameStack);
Name descriptorName = SpecialNames.safeIdentifier(classDescriptor.getName());
Name descriptorName = safeIdentifier(classDescriptor.getName());
if (DescriptorUtils.isTopLevelDeclaration(classDescriptor)) {
return base.isEmpty() ? descriptorName.asString() : base + '/' + descriptorName;
}
@@ -477,6 +472,7 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid {
SimpleFunctionDescriptor jvmSuspendFunctionView =
CoroutineCodegenUtilKt.getOrCreateJvmSuspendFunctionView(
functionDescriptor,
languageVersionSettings.supportsFeature(LanguageFeature.ReleaseCoroutines),
this.bindingContext
);
@@ -713,6 +709,54 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid {
super.visitCallExpression(expression);
checkSamCall(expression);
checkCrossinlineCall(expression);
recordSuspendFunctionTypeWrapperForArguments(expression);
}
private void recordSuspendFunctionTypeWrapperForArguments(@NotNull KtCallExpression expression) {
ResolvedCall<?> call = CallUtilKt.getResolvedCall(expression, bindingContext);
if (call == null) return;
CallableDescriptor descriptor = call.getResultingDescriptor();
if (!CodegenUtilKt.needsExperimentalCoroutinesWrapper(descriptor)) return;
List<ResolvedValueArgument> argumentsByIndex = call.getValueArgumentsByIndex();
if (argumentsByIndex == null) return;
for (ValueParameterDescriptor parameter : descriptor.getValueParameters()) {
ResolvedValueArgument resolvedValueArgument = argumentsByIndex.get(parameter.getIndex());
if (!(resolvedValueArgument instanceof ExpressionValueArgument)) continue;
ValueArgument valueArgument = ((ExpressionValueArgument) resolvedValueArgument).getValueArgument();
if (valueArgument == null) continue;
KtExpression argumentExpression = valueArgument.getArgumentExpression();
if (argumentExpression == null) continue;
recordSuspendFunctionTypeWrapperForArgument(parameter, argumentExpression);
}
ReceiverValue receiver = call.getExtensionReceiver();
if (descriptor.getExtensionReceiverParameter() != null && receiver instanceof ExpressionReceiver) {
recordSuspendFunctionTypeWrapperForArgument(
descriptor.getExtensionReceiverParameter(),
((ExpressionReceiver) receiver).getExpression()
);
}
}
private void recordSuspendFunctionTypeWrapperForArgument(ParameterDescriptor parameter, KtExpression argumentExpression) {
if (FunctionTypesKt.isSuspendFunctionTypeOrSubtype(parameter.getType())) {
// SuspendFunctionN type is mapped to is mapped to FunctionTypeN+1, but we also need to remove an argument for return type
// So, it could be parameter.getType().getArguments().size() + 1 - 1
int functionTypeArity = parameter.getType().getArguments().size();
Type functionType = Type.getObjectType(NUMBERED_FUNCTION_PREFIX + functionTypeArity);
bindingTrace.record(
FUNCTION_TYPE_FOR_SUSPEND_WRAPPER,
argumentExpression,
functionType
);
}
}
private void checkCrossinlineCall(@NotNull KtCallExpression expression) {
@@ -811,20 +855,6 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid {
}
}
@Nullable
private SamType createSamType(KotlinType kotlinType) {
if (!JavaSingleAbstractMethodUtils.isSamType(kotlinType)) return null;
return new SamType(kotlinType);
}
@Nullable
private SamType createSamTypeByValueParameter(ValueParameterDescriptor valueParameterDescriptor) {
KotlinType kotlinSamType = samTypeApproximator.getSamTypeForValueParameter(valueParameterDescriptor);
if (kotlinSamType == null) return null;
if (!JavaSingleAbstractMethodUtils.isSamType(kotlinSamType)) return null;
return new SamType(kotlinSamType);
}
private void writeSamValueForValueParameters(
@NotNull Collection<ValueParameterDescriptor> valueParametersWithSAMConversion,
@Nullable List<ResolvedValueArgument> valueArguments
@@ -832,7 +862,7 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid {
if (valueArguments == null) return;
for (ValueParameterDescriptor valueParameter : valueParametersWithSAMConversion) {
SamType samType = createSamTypeByValueParameter(valueParameter);
SamType samType = JvmSamTypeFactory.INSTANCE.createByValueParameter(valueParameter);
if (samType == null) continue;
ResolvedValueArgument resolvedValueArgument = valueArguments.get(valueParameter.getIndex());
@@ -844,7 +874,7 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid {
}
private void recordSamTypeOnArgumentExpression(ValueParameterDescriptor valueParameter, ValueArgument valueArgument) {
SamType samType = createSamTypeByValueParameter(valueParameter);
SamType samType = JvmSamTypeFactory.INSTANCE.createByValueParameter(valueParameter);
if (samType == null) return;
recordSamTypeOnArgumentExpression(samType, valueArgument);
@@ -925,7 +955,8 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid {
KtExpression argumentExpression = argument.getArgumentExpression();
bindingTrace.record(SAM_CONSTRUCTOR_TO_ARGUMENT, expression, argumentExpression);
SamType samType = createSamType(callableDescriptor.getReturnType());
//noinspection ConstantConditions
SamType samType = JvmSamTypeFactory.INSTANCE.create(callableDescriptor.getReturnType());
bindingTrace.record(SAM_VALUE, argumentExpression, samType);
}
@@ -942,7 +973,7 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid {
FunctionDescriptor original = SamCodegenUtil.getOriginalIfSamAdapter((FunctionDescriptor) operationDescriptor);
if (original == null) return;
SamType samType = createSamTypeByValueParameter(original.getValueParameters().get(0));
SamType samType = JvmSamTypeFactory.INSTANCE.createByValueParameter(original.getValueParameters().get(0));
if (samType == null) return;
IElementType token = expression.getOperationToken();
@@ -971,7 +1002,7 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid {
List<KtExpression> indexExpressions = expression.getIndexExpressions();
List<ValueParameterDescriptor> parameters = original.getValueParameters();
for (ValueParameterDescriptor valueParameter : parameters) {
SamType samType = createSamTypeByValueParameter(valueParameter);
SamType samType = JvmSamTypeFactory.INSTANCE.createByValueParameter(valueParameter);
if (samType == null) continue;
if (isSetter && valueParameter.getIndex() == parameters.size() - 1) {

View File

@@ -13,13 +13,15 @@ import org.jetbrains.kotlin.codegen.binding.CodegenBinding
import org.jetbrains.kotlin.codegen.context.CodegenContext
import org.jetbrains.kotlin.codegen.context.FieldOwnerContext
import org.jetbrains.kotlin.codegen.context.MultifileClassFacadeContext
import org.jetbrains.kotlin.codegen.coroutines.CONTINUATION_ASM_TYPE
import org.jetbrains.kotlin.codegen.coroutines.continuationAsmType
import org.jetbrains.kotlin.codegen.coroutines.unwrapInitialDescriptorForSuspendFunction
import org.jetbrains.kotlin.codegen.inline.NUMBERED_FUNCTION_PREFIX
import org.jetbrains.kotlin.codegen.inline.ReificationArgument
import org.jetbrains.kotlin.codegen.intrinsics.TypeIntrinsics
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.config.isReleaseCoroutines
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.deserialization.PLATFORM_DEPENDENT_ANNOTATION_FQ_NAME
import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl
@@ -43,6 +45,8 @@ import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
import org.jetbrains.kotlin.resolve.jvm.diagnostics.Synthetic
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodGenericSignature
import org.jetbrains.kotlin.resolve.scopes.receivers.TransientReceiver
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedMemberDescriptor
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedMemberDescriptor.CoroutinesCompatibilityMode
import org.jetbrains.kotlin.types.ErrorUtils
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeSystemCommonBackendContext
@@ -66,7 +70,8 @@ internal val JAVA_LANG_DEPRECATED = Type.getType(Deprecated::class.java).descrip
fun generateIsCheck(
v: InstructionAdapter,
kotlinType: KotlinType,
asmType: Type
asmType: Type,
isReleaseCoroutines: Boolean
) {
if (TypeUtils.isNullableType(kotlinType)) {
val nope = Label()
@@ -77,7 +82,7 @@ fun generateIsCheck(
ifnull(nope)
TypeIntrinsics.instanceOf(this, kotlinType, asmType)
TypeIntrinsics.instanceOf(this, kotlinType, asmType, isReleaseCoroutines)
goTo(end)
@@ -88,7 +93,7 @@ fun generateIsCheck(
mark(end)
}
} else {
TypeIntrinsics.instanceOf(v, kotlinType, asmType)
TypeIntrinsics.instanceOf(v, kotlinType, asmType, isReleaseCoroutines)
}
}
@@ -97,6 +102,7 @@ fun generateAsCast(
kotlinType: KotlinType,
asmType: Type,
isSafe: Boolean,
languageVersionSettings: LanguageVersionSettings,
unifiedNullChecks: Boolean,
) {
if (!isSafe) {
@@ -106,7 +112,7 @@ fun generateAsCast(
} else {
with(v) {
dup()
TypeIntrinsics.instanceOf(v, kotlinType, asmType)
TypeIntrinsics.instanceOf(v, kotlinType, asmType, languageVersionSettings.isReleaseCoroutines())
val ok = Label()
ifne(ok)
pop()
@@ -438,10 +444,14 @@ fun KotlinType.isInlineClassTypeWithPrimitiveEquality(): Boolean {
return false
}
fun CallableDescriptor.needsExperimentalCoroutinesWrapper() =
(this as? DeserializedMemberDescriptor)?.coroutinesExperimentalCompatibilityMode == CoroutinesCompatibilityMode.NEEDS_WRAPPER
fun recordCallLabelForLambdaArgument(declaration: KtFunctionLiteral, bindingTrace: BindingTrace) {
val labelName = getCallLabelForLambdaArgument(declaration, bindingTrace.bindingContext) ?: return
val functionDescriptor = bindingTrace[BindingContext.FUNCTION, declaration] ?: return
bindingTrace.record(CodegenBinding.CALL_LABEL_FOR_LAMBDA_ARGUMENT, functionDescriptor, labelName)
}
fun getCallLabelForLambdaArgument(declaration: KtFunctionLiteral, bindingContext: BindingContext): String? {
@@ -637,7 +647,7 @@ private fun generateLambdaForRunSuspend(
}
visitVarInsn(ALOAD, 1)
val continuationInternalName = CONTINUATION_ASM_TYPE.internalName
val continuationInternalName = state.languageVersionSettings.continuationAsmType().internalName
visitTypeInsn(
CHECKCAST,

View File

@@ -6,9 +6,9 @@
package org.jetbrains.kotlin.codegen.context
import org.jetbrains.kotlin.backend.common.isBuiltInSuspendCoroutineUninterceptedOrReturn
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.codegen.OwnerKind
import org.jetbrains.kotlin.codegen.binding.MutableClosure
import org.jetbrains.kotlin.config.coroutinesPackageFqName
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor
import org.jetbrains.kotlin.descriptors.isTopLevelInPackage
@@ -17,26 +17,25 @@ import org.jetbrains.kotlin.resolve.calls.callUtil.getParentResolvedCall
import org.jetbrains.kotlin.resolve.source.getPsi
class InlineLambdaContext(
functionDescriptor: FunctionDescriptor,
contextKind: OwnerKind,
parentContext: CodegenContext<*>,
closure: MutableClosure?,
val isCrossInline: Boolean,
private val isPropertyReference: Boolean
functionDescriptor: FunctionDescriptor,
contextKind: OwnerKind,
parentContext: CodegenContext<*>,
closure: MutableClosure?,
val isCrossInline: Boolean,
private val isPropertyReference: Boolean
) : MethodContext(functionDescriptor, contextKind, parentContext, closure, false) {
override fun getFirstCrossInlineOrNonInlineContext(): CodegenContext<*> {
if (isCrossInline && !isSuspendIntrinsicParameter()) return this
val parent = if (isPropertyReference) parentContext as? AnonymousClassContext else {
parentContext as? ClosureContext
} ?: throw AssertionError(
"Parent of inlining lambda body should be " +
"${if (isPropertyReference) "ClosureContext" else "AnonymousClassContext"}, but: $parentContext"
)
val parent = if (isPropertyReference) parentContext as? AnonymousClassContext else { parentContext as? ClosureContext } ?:
throw AssertionError(
"Parent of inlining lambda body should be " +
"${if (isPropertyReference) "ClosureContext" else "AnonymousClassContext"}, but: $parentContext"
)
val grandParent =
parent.parentContext ?: throw AssertionError("Parent context of lambda class context should exist: $contextDescriptor")
val grandParent = parent.parentContext ?:
throw AssertionError("Parent context of lambda class context should exist: $contextDescriptor")
return grandParent.firstCrossInlineOrNonInlineContext
}
@@ -45,7 +44,7 @@ class InlineLambdaContext(
if (contextDescriptor !is AnonymousFunctionDescriptor) return false
val resolvedCall = (contextDescriptor.source.getPsi() as? KtElement).getParentResolvedCall(state.bindingContext) ?: return false
val descriptor = resolvedCall.resultingDescriptor as? FunctionDescriptor ?: return false
return descriptor.isBuiltInSuspendCoroutineUninterceptedOrReturn()
|| descriptor.isTopLevelInPackage("suspendCoroutine", StandardNames.COROUTINES_PACKAGE_FQ_NAME.asString())
return descriptor.isBuiltInSuspendCoroutineUninterceptedOrReturn(state.languageVersionSettings)
|| descriptor.isTopLevelInPackage("suspendCoroutine", state.languageVersionSettings.coroutinesPackageFqName().asString())
}
}

View File

@@ -5,7 +5,6 @@
package org.jetbrains.kotlin.codegen.coroutines
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.codegen.optimization.boxing.isPrimitiveBoxing
import org.jetbrains.kotlin.codegen.optimization.common.asSequence
import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer
@@ -16,9 +15,10 @@ import org.jetbrains.kotlin.utils.sure
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.tree.MethodInsnNode
import org.jetbrains.org.objectweb.asm.tree.MethodNode
import java.util.*
private val BOXING_CLASS_INTERNAL_NAME =
StandardNames.COROUTINES_JVM_INTERNAL_PACKAGE_FQ_NAME.child(Name.identifier("Boxing")).topLevelClassInternalName()
RELEASE_COROUTINES_VERSION_SETTINGS.coroutinesJvmInternalPackageFqName().child(Name.identifier("Boxing")).topLevelClassInternalName()
@OptIn(ExperimentalStdlibApi::class)
object ChangeBoxingMethodTransformer : MethodTransformer() {

View File

@@ -7,7 +7,6 @@ package org.jetbrains.kotlin.codegen.coroutines
import com.intellij.util.ArrayUtil
import org.jetbrains.kotlin.backend.common.CodegenUtil
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.codegen.*
import org.jetbrains.kotlin.codegen.binding.CodegenBinding
import org.jetbrains.kotlin.codegen.binding.CodegenBinding.CAPTURES_CROSSINLINE_LAMBDA
@@ -18,6 +17,8 @@ import org.jetbrains.kotlin.codegen.serialization.JvmSerializationBindings.METHO
import org.jetbrains.kotlin.codegen.serialization.JvmSerializerExtension
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.config.isReleaseCoroutines
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor
@@ -40,6 +41,7 @@ import org.jetbrains.kotlin.resolve.jvm.diagnostics.OtherOrigin
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.kotlin.serialization.DescriptorSerializer
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.makeNullable
import org.jetbrains.kotlin.utils.sure
import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.org.objectweb.asm.MethodVisitor
@@ -54,7 +56,7 @@ abstract class AbstractCoroutineCodegen(
element: KtElement,
closureContext: ClosureContext,
classBuilder: ClassBuilder,
private val userDataForInvokeSuspend: Map<out CallableDescriptor.UserDataKey<*>, *>? = null
private val userDataForDoResume: Map<out CallableDescriptor.UserDataKey<*>, *>? = null
) : ClosureCodegen(
outerExpressionCodegen.state,
element, null, closureContext, null,
@@ -65,10 +67,17 @@ abstract class AbstractCoroutineCodegen(
protected val languageVersionSettings = outerExpressionCodegen.state.languageVersionSettings
protected val methodToImplement =
createImplMethod(
INVOKE_SUSPEND_METHOD_NAME,
SUSPEND_CALL_RESULT_NAME to classDescriptor.module.getResult(classDescriptor.builtIns.anyType)
)
if (languageVersionSettings.isReleaseCoroutines())
createImplMethod(
INVOKE_SUSPEND_METHOD_NAME,
SUSPEND_CALL_RESULT_NAME to classDescriptor.module.getResult(classDescriptor.builtIns.anyType)
)
else
createImplMethod(
DO_RESUME_METHOD_NAME,
"data" to classDescriptor.builtIns.nullableAnyType,
"throwable" to classDescriptor.builtIns.throwable.defaultType.makeNullable()
)
private fun createImplMethod(name: String, vararg parameters: Pair<String, KotlinType>) =
SimpleFunctionDescriptorImpl.create(
@@ -85,7 +94,7 @@ abstract class AbstractCoroutineCodegen(
builtIns.nullableAnyType,
Modality.FINAL,
DescriptorVisibilities.PUBLIC,
userDataForInvokeSuspend
userDataForDoResume
)
}
@@ -100,7 +109,7 @@ abstract class AbstractCoroutineCodegen(
override fun generateConstructor(): Method {
val args = calculateConstructorParameters(typeMapper, languageVersionSettings, closure, asmType)
val argTypes = args.map { it.fieldType }.plus(CONTINUATION_ASM_TYPE).toTypedArray()
val argTypes = args.map { it.fieldType }.plus(languageVersionSettings.continuationAsmType()).toTypedArray()
val constructor = Method("<init>", Type.VOID_TYPE, argTypes)
val mv = v.newMethod(
@@ -115,17 +124,18 @@ abstract class AbstractCoroutineCodegen(
iv.generateClosureFieldsInitializationFromParameters(closure, args)
iv.load(0, AsmTypes.OBJECT_TYPE)
if (passArityToSuperClass) {
iv.iconst(funDescriptor.arity)
val hasArityParameter = !languageVersionSettings.isReleaseCoroutines() || passArityToSuperClass
if (hasArityParameter) {
iv.iconst(if (passArityToSuperClass) funDescriptor.arity else 0)
}
iv.load(argTypes.map { it.size }.sum(), AsmTypes.OBJECT_TYPE)
val parameters =
if (passArityToSuperClass)
listOf(Type.INT_TYPE, CONTINUATION_ASM_TYPE)
if (hasArityParameter)
listOf(Type.INT_TYPE, languageVersionSettings.continuationAsmType())
else
listOf(CONTINUATION_ASM_TYPE)
listOf(languageVersionSettings.continuationAsmType())
val superClassConstructorDescriptor = Type.getMethodDescriptor(
Type.VOID_TYPE,
@@ -138,7 +148,9 @@ abstract class AbstractCoroutineCodegen(
FunctionCodegen.endVisit(iv, "constructor", element)
}
v.newField(JvmDeclarationOrigin.NO_ORIGIN, AsmUtil.NO_FLAG_PACKAGE_PRIVATE, "label", "I", null, null)
if (languageVersionSettings.isReleaseCoroutines()) {
v.newField(JvmDeclarationOrigin.NO_ORIGIN, AsmUtil.NO_FLAG_PACKAGE_PRIVATE, "label", "I", null, null)
}
return constructor
}
@@ -155,7 +167,7 @@ class CoroutineCodegenForLambda private constructor(
private val forInline: Boolean
) : AbstractCoroutineCodegen(
outerExpressionCodegen, element, closureContext, classBuilder,
userDataForInvokeSuspend = mapOf(INITIAL_SUSPEND_DESCRIPTOR_FOR_INVOKE_SUSPEND to originalSuspendFunctionDescriptor)
userDataForDoResume = mapOf(INITIAL_SUSPEND_DESCRIPTOR_FOR_DO_RESUME to originalSuspendFunctionDescriptor)
) {
private val builtIns = funDescriptor.builtIns
@@ -203,7 +215,8 @@ class CoroutineCodegenForLambda private constructor(
funDescriptor.typeParameters,
funDescriptor.valueParameters,
funDescriptor.module.getContinuationOfTypeOrAny(
builtIns.unitType
builtIns.unitType,
state.languageVersionSettings.supportsFeature(LanguageFeature.ReleaseCoroutines)
),
funDescriptor.modality,
DescriptorVisibilities.PUBLIC
@@ -218,11 +231,14 @@ class CoroutineCodegenForLambda private constructor(
"too many arguments of create to have an erased signature: $argumentsNum: $typedCreate"
}
return typedCreate.module.resolveClassByFqName(
StandardNames.COROUTINES_JVM_INTERNAL_PACKAGE_FQ_NAME.child(
Name.identifier("BaseContinuationImpl")
languageVersionSettings.coroutinesJvmInternalPackageFqName().child(
if (languageVersionSettings.isReleaseCoroutines())
Name.identifier("BaseContinuationImpl")
else
Name.identifier("CoroutineImpl")
),
NoLookupLocation.FROM_BACKEND
).sure { "BaseContinuationImpl is not found" }.defaultType.memberScope
).sure { "BaseContinuationImpl or CoroutineImpl is not found" }.defaultType.memberScope
.getContributedFunctions(typedCreate.name, NoLookupLocation.FROM_BACKEND)
.find { it.valueParameters.size == argumentsNum }
.sure { "erased parent of $typedCreate is not found" }
@@ -349,7 +365,7 @@ class CoroutineCodegenForLambda private constructor(
v.thisName,
typeMapper.mapFunctionName(createCoroutineDescriptor, null),
Type.getMethodDescriptor(
CONTINUATION_ASM_TYPE,
languageVersionSettings.continuationAsmType(),
*createArgumentTypes.toTypedArray()
),
false
@@ -357,7 +373,11 @@ class CoroutineCodegenForLambda private constructor(
checkcast(Type.getObjectType(v.thisName))
// .doResume(Unit)
invokeInvokeSuspendWithUnit(v.thisName)
if (languageVersionSettings.isReleaseCoroutines()) {
invokeInvokeSuspendWithUnit(v.thisName)
} else {
invokeDoResumeWithUnit(v.thisName)
}
areturn(AsmTypes.OBJECT_TYPE)
}
@@ -520,14 +540,15 @@ class CoroutineCodegenForLambda private constructor(
override fun wrapMethodVisitor(mv: MethodVisitor, access: Int, name: String, desc: String): MethodVisitor {
val stateMachineBuilder = CoroutineTransformerMethodVisitor(
mv, access, name, desc, null, null,
containingClassInternalName = v.thisName,
obtainClassBuilderForCoroutineState = { v },
isForNamedFunction = false,
shouldPreserveClassInitialization = constructorCallNormalizationMode.shouldPreserveClassInitialization,
disableTailCallOptimizationForFunctionReturningUnit = false,
reportSuspensionPointInsideMonitor = { reportSuspensionPointInsideMonitor(element, state, it) },
lineNumber = CodegenUtil.getLineNumberForElement(element, false) ?: 0,
sourceFile = element.containingKtFile.name,
shouldPreserveClassInitialization = constructorCallNormalizationMode.shouldPreserveClassInitialization,
containingClassInternalName = v.thisName,
isForNamedFunction = false,
languageVersionSettings = languageVersionSettings,
disableTailCallOptimizationForFunctionReturningUnit = false,
useOldSpilledVarTypeAnalysis = state.configuration.getBoolean(JVMConfigurationKeys.USE_OLD_SPILLED_VAR_TYPE_ANALYSIS),
initialVarsCountByType = varsCountByType
)
@@ -604,7 +625,7 @@ class CoroutineCodegenForNamedFunction private constructor(
private val labelFieldStackValue by lazy {
StackValue.field(
FieldInfo.createForHiddenField(
Type.getObjectType(v.thisName),
computeLabelOwner(languageVersionSettings, v.thisName),
Type.INT_TYPE,
COROUTINE_LABEL_FIELD_NAME
),
@@ -626,25 +647,44 @@ class CoroutineCodegenForNamedFunction private constructor(
}
override fun generateClosureBody() {
generateInvokeSuspend()
generateResumeImpl()
if (!languageVersionSettings.isReleaseCoroutines()) {
generateGetLabelMethod()
generateSetLabelMethod()
}
v.newField(
JvmDeclarationOrigin.NO_ORIGIN, Opcodes.ACC_SYNTHETIC or AsmUtil.NO_FLAG_PACKAGE_PRIVATE,
CONTINUATION_RESULT_FIELD_NAME, AsmTypes.OBJECT_TYPE.descriptor, null, null
languageVersionSettings.dataFieldName(), AsmTypes.OBJECT_TYPE.descriptor, null, null
)
if (!languageVersionSettings.isReleaseCoroutines()) {
v.newField(
JvmDeclarationOrigin.NO_ORIGIN, Opcodes.ACC_SYNTHETIC or AsmUtil.NO_FLAG_PACKAGE_PRIVATE,
EXCEPTION_FIELD_NAME, AsmTypes.JAVA_THROWABLE_TYPE.descriptor, null, null
)
}
}
private fun generateInvokeSuspend() {
private fun generateResumeImpl() {
functionCodegen.generateMethod(
OtherOrigin(element),
methodToImplement,
object : FunctionGenerationStrategy.CodegenBased(state) {
override fun doGenerateBody(codegen: ExpressionCodegen, signature: JvmMethodSignature) {
StackValue.field(
AsmTypes.OBJECT_TYPE, Type.getObjectType(v.thisName), CONTINUATION_RESULT_FIELD_NAME, false,
AsmTypes.OBJECT_TYPE, Type.getObjectType(v.thisName), languageVersionSettings.dataFieldName(), false,
StackValue.LOCAL_0
).store(StackValue.local(1, AsmTypes.OBJECT_TYPE), codegen.v)
if (!languageVersionSettings.isReleaseCoroutines()) {
StackValue.field(
AsmTypes.JAVA_THROWABLE_TYPE, Type.getObjectType(v.thisName), EXCEPTION_FIELD_NAME, false,
StackValue.LOCAL_0
).store(StackValue.local(2, AsmTypes.JAVA_THROWABLE_TYPE), codegen.v)
}
labelFieldStackValue.store(
StackValue.operation(Type.INT_TYPE) {
labelFieldStackValue.put(Type.INT_TYPE, it)
@@ -696,7 +736,7 @@ class CoroutineCodegenForNamedFunction private constructor(
with(codegen.v) {
// We need to box the returned inline class in resume path.
// But first, check for COROUTINE_SUSPENDED, since the function can return it
generateCoroutineSuspendedCheck()
generateCoroutineSuspendedCheck(languageVersionSettings)
// Now we box the inline class
StackValue.coerce(AsmTypes.OBJECT_TYPE, typeMapper.mapType(inlineClassToBoxInInvokeSuspend), this)
StackValue.boxInlineClass(inlineClassToBoxInInvokeSuspend, this, typeMapper)

View File

@@ -9,10 +9,15 @@ import org.jetbrains.kotlin.codegen.*
import org.jetbrains.kotlin.codegen.inline.*
import org.jetbrains.kotlin.codegen.optimization.common.*
import org.jetbrains.kotlin.codegen.optimization.fixStack.FixStackMethodTransformer
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.config.isReleaseCoroutines
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
import org.jetbrains.kotlin.utils.sure
import org.jetbrains.org.objectweb.asm.*
import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.org.objectweb.asm.MethodVisitor
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
import org.jetbrains.org.objectweb.asm.tree.*
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue
@@ -44,6 +49,7 @@ class CoroutineTransformerMethodVisitor(
obtainClassBuilderForCoroutineState: () -> ClassBuilder,
private val isForNamedFunction: Boolean,
private val shouldPreserveClassInitialization: Boolean,
private val languageVersionSettings: LanguageVersionSettings,
// Since tail-call optimization of functions with Unit return type relies on ability of call-site to recognize them,
// in order to ignore return value and push Unit, when we cannot ensure this ability, for example, when the function overrides function,
// returning Any, we need to disable tail-call optimization for these functions.
@@ -67,6 +73,7 @@ class CoroutineTransformerMethodVisitor(
private var continuationIndex = if (isForNamedFunction) -1 else 0
private var dataIndex = if (isForNamedFunction) -1 else 1
private var exceptionIndex = if (isForNamedFunction || languageVersionSettings.isReleaseCoroutines()) -1 else 2
override fun performTransformations(methodNode: MethodNode) {
removeFakeContinuationConstructorCall(methodNode)
@@ -82,7 +89,9 @@ class CoroutineTransformerMethodVisitor(
val suspensionPoints = collectSuspensionPoints(methodNode)
RedundantLocalsEliminationMethodTransformer(suspensionPoints)
.transform(containingClassInternalName, methodNode)
ChangeBoxingMethodTransformer.transform(containingClassInternalName, methodNode)
if (languageVersionSettings.isReleaseCoroutines()) {
ChangeBoxingMethodTransformer.transform(containingClassInternalName, methodNode)
}
updateMaxStack(methodNode)
checkForSuspensionPointInsideMonitor(methodNode, suspensionPoints)
@@ -96,6 +105,7 @@ class CoroutineTransformerMethodVisitor(
}
val examiner = MethodNodeExaminer(
languageVersionSettings,
containingClassInternalName,
methodNode,
suspensionPoints,
@@ -109,6 +119,9 @@ class CoroutineTransformerMethodVisitor(
}
dataIndex = methodNode.maxLocals++
if (!languageVersionSettings.isReleaseCoroutines()) {
exceptionIndex = methodNode.maxLocals++
}
continuationIndex = methodNode.maxLocals++
prepareMethodNodePreludeForNamedFunction(methodNode)
@@ -123,6 +136,8 @@ class CoroutineTransformerMethodVisitor(
UninitializedStoresProcessor(methodNode, shouldPreserveClassInitialization).run()
updateLvtAccordingToLiveness(methodNode, isForNamedFunction)
val spilledToVariableMapping = spillVariables(suspensionPoints, methodNode)
val suspendMarkerVarIndex = methodNode.maxLocals++
@@ -145,7 +160,7 @@ class CoroutineTransformerMethodVisitor(
insertBefore(
actualCoroutineStart,
insnListOf(
*withInstructionAdapter { loadCoroutineSuspendedMarker() }.toArray(),
*withInstructionAdapter { loadCoroutineSuspendedMarker(languageVersionSettings) }.toArray(),
tableSwitchLabel,
// Allow debugger to stop on enter into suspend function
LineNumberNode(lineNumber, tableSwitchLabel),
@@ -163,7 +178,7 @@ class CoroutineTransformerMethodVisitor(
)
insert(firstStateLabel, withInstructionAdapter {
generateResumeWithExceptionCheck(dataIndex)
generateResumeWithExceptionCheck(languageVersionSettings.isReleaseCoroutines(), dataIndex, exceptionIndex)
})
insert(last, defaultLabel)
@@ -179,9 +194,9 @@ class CoroutineTransformerMethodVisitor(
dropUnboxInlineClassMarkers(methodNode, suspensionPoints)
methodNode.removeEmptyCatchBlocks()
updateLvtAccordingToLiveness(methodNode, isForNamedFunction, stateLabels)
writeDebugMetadata(methodNode, suspensionPointLineNumbers, spilledToVariableMapping)
if (languageVersionSettings.isReleaseCoroutines()) {
writeDebugMetadata(methodNode, suspensionPointLineNumbers, spilledToVariableMapping)
}
}
// When suspension point is inlined, it is in range of fake inliner variables.
@@ -248,7 +263,7 @@ class CoroutineTransformerMethodVisitor(
methodNode.localVariables.add(
LocalVariableNode(
SUSPEND_FUNCTION_COMPLETION_PARAMETER_NAME,
CONTINUATION_ASM_TYPE.descriptor,
languageVersionSettings.continuationAsmType().descriptor,
null,
startLabel,
endLabel,
@@ -387,7 +402,7 @@ class CoroutineTransformerMethodVisitor(
methodNode.instructions.add(withInstructionAdapter { mark(endLabel) })
methodNode.visitLocalVariable(
CONTINUATION_VARIABLE_NAME,
CONTINUATION_ASM_TYPE.descriptor,
languageVersionSettings.continuationAsmType().descriptor,
null,
startLabel,
endLabel,
@@ -415,17 +430,33 @@ class CoroutineTransformerMethodVisitor(
}
private fun InstructionAdapter.getLabel() {
getfield(
Type.getObjectType(classBuilderForCoroutineState.thisName).internalName,
COROUTINE_LABEL_FIELD_NAME, Type.INT_TYPE.descriptor
)
if (isForNamedFunction && !languageVersionSettings.isReleaseCoroutines())
invokevirtual(
classBuilderForCoroutineState.thisName,
"getLabel",
Type.getMethodDescriptor(Type.INT_TYPE),
false
)
else
getfield(
computeLabelOwner(languageVersionSettings, classBuilderForCoroutineState.thisName).internalName,
COROUTINE_LABEL_FIELD_NAME, Type.INT_TYPE.descriptor
)
}
private fun InstructionAdapter.setLabel() {
putfield(
Type.getObjectType(classBuilderForCoroutineState.thisName).internalName,
COROUTINE_LABEL_FIELD_NAME, Type.INT_TYPE.descriptor
)
if (isForNamedFunction && !languageVersionSettings.isReleaseCoroutines())
invokevirtual(
classBuilderForCoroutineState.thisName,
"setLabel",
Type.getMethodDescriptor(Type.VOID_TYPE, Type.INT_TYPE),
false
)
else
putfield(
computeLabelOwner(languageVersionSettings, classBuilderForCoroutineState.thisName).internalName,
COROUTINE_LABEL_FIELD_NAME, Type.INT_TYPE.descriptor
)
}
private fun updateMaxStack(methodNode: MethodNode) {
@@ -503,7 +534,8 @@ class CoroutineTransformerMethodVisitor(
needDispatchReceiver,
internalNameForDispatchReceiver,
containingClassInternalName,
classBuilderForCoroutineState
classBuilderForCoroutineState,
languageVersionSettings
)
visitVarInsn(Opcodes.ASTORE, continuationIndex)
@@ -511,13 +543,19 @@ class CoroutineTransformerMethodVisitor(
visitLabel(afterCoroutineStateCreated)
visitVarInsn(Opcodes.ALOAD, continuationIndex)
getfield(classBuilderForCoroutineState.thisName, CONTINUATION_RESULT_FIELD_NAME, AsmTypes.OBJECT_TYPE.descriptor)
getfield(classBuilderForCoroutineState.thisName, languageVersionSettings.dataFieldName(), AsmTypes.OBJECT_TYPE.descriptor)
visitVarInsn(Opcodes.ASTORE, dataIndex)
val resultStartLabel = Label()
visitLabel(resultStartLabel)
addContinuationAndResultToLvt(methodNode, afterCoroutineStateCreated, resultStartLabel)
if (!languageVersionSettings.isReleaseCoroutines()) {
visitVarInsn(Opcodes.ALOAD, continuationIndex)
getfield(classBuilderForCoroutineState.thisName, EXCEPTION_FIELD_NAME, AsmTypes.JAVA_THROWABLE_TYPE.descriptor)
visitVarInsn(Opcodes.ASTORE, exceptionIndex)
}
})
}
@@ -654,7 +692,7 @@ class CoroutineTransformerMethodVisitor(
// k + 1 - data
// k + 2 - exception
for (slot in 0 until localsCount) {
if (slot == continuationIndex || slot == dataIndex) continue
if (slot == continuationIndex || slot == dataIndex || slot == exceptionIndex) continue
val value = frame.getLocal(slot)
if (value.type == null || !livenessFrame.isAlive(slot)) continue
@@ -753,30 +791,19 @@ class CoroutineTransformerMethodVisitor(
// Mutate method node
fun generateSpillAndUnspill(suspension: SuspensionPoint, slot: Int, spillableVariable: SpillableVariable?) {
fun splitLvtRecord(local: LocalVariableNode?, localRestart: LabelNode) {
// Split the local variable range for the local so that it is visible until the next state label, but is
// not visible until it has been unspilled from the continuation on the reentry path.
if (local != null) {
val previousEnd = local.end
local.end = suspension.stateLabel
// Add the local back, but end it at the next state label.
methodNode.localVariables.add(local)
// Add a new entry that starts after the local variable is restored from the continuation.
methodNode.localVariables.add(
LocalVariableNode(
local.name,
local.desc,
local.signature,
localRestart,
previousEnd,
local.index
)
)
if (spillableVariable == null) {
with(instructions) {
insert(suspension.tryCatchBlockEndLabelAfterSuspensionCall, withInstructionAdapter {
aconst(null)
store(slot, AsmTypes.OBJECT_TYPE)
})
}
return
}
// Find and remove the local variable node, if any, in the local variable table corresponding to the slot that is spilled.
var local: LocalVariableNode? = null
val localRestart = LabelNode().linkWithLabel()
val iterator = methodNode.localVariables.listIterator()
while (iterator.hasNext()) {
val node = iterator.next()
@@ -790,19 +817,6 @@ class CoroutineTransformerMethodVisitor(
}
}
if (spillableVariable == null) {
with(instructions) {
insert(suspension.tryCatchBlockEndLabelAfterSuspensionCall, withInstructionAdapter {
aconst(null)
store(slot, AsmTypes.OBJECT_TYPE)
})
}
val newStart = suspension.tryCatchBlocksContinuationLabel.findNextOrNull { it is LabelNode } as? LabelNode ?: return
splitLvtRecord(local, newStart)
return
}
val localRestart = LabelNode().linkWithLabel()
with(instructions) {
// store variable before suspension call
insertBefore(suspension.suspensionCallBegin, withInstructionAdapter {
@@ -832,7 +846,25 @@ class CoroutineTransformerMethodVisitor(
})
}
splitLvtRecord(local, localRestart)
// Split the local variable range for the local so that it is visible until the next state label, but is
// not visible until it has been unspilled from the continuation on the reentry path.
if (local != null) {
val previousEnd = local.end
local.end = suspension.stateLabel
// Add the local back, but end it at the next state label.
methodNode.localVariables.add(local)
// Add a new entry that starts after the local variable is restored from the continuation.
methodNode.localVariables.add(
LocalVariableNode(
local.name,
local.desc,
local.signature,
localRestart,
previousEnd,
local.index
)
)
}
}
fun cleanUpField(suspension: SuspensionPoint, fieldIndex: Int) {
@@ -954,7 +986,7 @@ class CoroutineTransformerMethodVisitor(
insert(possibleTryCatchBlockStart, withInstructionAdapter {
nop()
generateResumeWithExceptionCheck(dataIndex)
generateResumeWithExceptionCheck(languageVersionSettings.isReleaseCoroutines(), dataIndex, exceptionIndex)
// Load continuation argument just like suspending function returns it
load(dataIndex, AsmTypes.OBJECT_TYPE)
@@ -1080,7 +1112,8 @@ internal fun InstructionAdapter.generateContinuationConstructorCall(
needDispatchReceiver: Boolean,
internalNameForDispatchReceiver: String?,
containingClassInternalName: String,
classBuilderForCoroutineState: ClassBuilder
classBuilderForCoroutineState: ClassBuilder,
languageVersionSettings: LanguageVersionSettings
) {
anew(objectTypeForState)
dup()
@@ -1089,7 +1122,8 @@ internal fun InstructionAdapter.generateContinuationConstructorCall(
getParameterTypesIndicesForCoroutineConstructor(
methodNode.desc,
methodNode.access,
needDispatchReceiver, internalNameForDispatchReceiver ?: containingClassInternalName
needDispatchReceiver, internalNameForDispatchReceiver ?: containingClassInternalName,
languageVersionSettings
)
for ((type, index) in parameterTypesAndIndices) {
load(index, type)
@@ -1109,11 +1143,22 @@ internal fun InstructionAdapter.generateContinuationConstructorCall(
)
}
private fun InstructionAdapter.generateResumeWithExceptionCheck(dataIndex: Int) {
private fun InstructionAdapter.generateResumeWithExceptionCheck(isReleaseCoroutines: Boolean, dataIndex: Int, exceptionIndex: Int) {
// Check if resumeWithException has been called
load(dataIndex, AsmTypes.OBJECT_TYPE)
invokestatic("kotlin/ResultKt", "throwOnFailure", "(Ljava/lang/Object;)V", false)
if (isReleaseCoroutines) {
load(dataIndex, AsmTypes.OBJECT_TYPE)
invokestatic("kotlin/ResultKt", "throwOnFailure", "(Ljava/lang/Object;)V", false)
} else {
load(exceptionIndex, AsmTypes.OBJECT_TYPE)
dup()
val noExceptionLabel = Label()
ifnull(noExceptionLabel)
athrow()
mark(noExceptionLabel)
pop()
}
}
private fun Type.fieldNameForVar(index: Int) = descriptor.first() + "$" + index
@@ -1184,15 +1229,16 @@ private fun getParameterTypesIndicesForCoroutineConstructor(
desc: String,
containingFunctionAccess: Int,
needDispatchReceiver: Boolean,
thisName: String
thisName: String,
languageVersionSettings: LanguageVersionSettings
): Collection<Pair<Type, Int>> {
return mutableListOf<Pair<Type, Int>>().apply {
if (needDispatchReceiver) {
add(Type.getObjectType(thisName) to 0)
}
val continuationIndex =
getAllParameterTypes(desc, !isStatic(containingFunctionAccess), thisName).dropLast(1).sumOf(Type::getSize)
add(CONTINUATION_ASM_TYPE to continuationIndex)
getAllParameterTypes(desc, !isStatic(containingFunctionAccess), thisName).dropLast(1).map(Type::getSize).sum()
add(languageVersionSettings.continuationAsmType() to continuationIndex)
}
}
@@ -1208,16 +1254,13 @@ internal fun replaceFakeContinuationsWithRealOnes(methodNode: MethodNode, contin
}
}
private fun MethodNode.nodeTextWithLiveness(liveness: List<VariableLivenessFrame>): String =
liveness.zip(this.instructions.asSequence().toList()).joinToString("\n") { (a, b) -> "$a|${b.insnText}" }
/* We do not want to spill dead variables, thus, we shrink its LVT record to region, where the variable is alive,
* so, the variable will not be visible in debugger. User can still prolong life span of the variable by using it.
*
* This means, that function parameters do not longer span the whole function, including `this`.
* This might and will break some bytecode processors, including old versions of R8. See KT-24510.
*/
private fun updateLvtAccordingToLiveness(method: MethodNode, isForNamedFunction: Boolean, suspensionPoints: List<LabelNode>) {
private fun updateLvtAccordingToLiveness(method: MethodNode, isForNamedFunction: Boolean) {
val liveness = analyzeLiveness(method)
fun List<LocalVariableNode>.findRecord(insnIndex: Int, variableIndex: Int): LocalVariableNode? {
@@ -1245,15 +1288,17 @@ private fun updateLvtAccordingToLiveness(method: MethodNode, isForNamedFunction:
fun min(a: LabelNode, b: LabelNode): LabelNode =
if (method.instructions.indexOf(a) < method.instructions.indexOf(b)) a else b
fun max(a: LabelNode, b: LabelNode): LabelNode =
if (method.instructions.indexOf(a) < method.instructions.indexOf(b)) b else a
val oldLvt = arrayListOf<LocalVariableNode>()
for (record in method.localVariables) {
oldLvt += record
}
method.localVariables.clear()
val oldLvtNodeToLatestNewLvtNode = mutableMapOf<LocalVariableNode, LocalVariableNode>()
// Skip `this` for suspend lambda
val start = if (isForNamedFunction) 0 else 1
val oldLvtNodeToLatestNewLvtNode = mutableMapOf<LocalVariableNode, LocalVariableNode>()
for (variableIndex in start until method.maxLocals) {
if (oldLvt.none { it.index == variableIndex }) continue
var startLabel: LabelNode? = null
@@ -1265,7 +1310,7 @@ private fun updateLvtAccordingToLiveness(method: MethodNode, isForNamedFunction:
if (isAlive(insnIndex, variableIndex) && !isAlive(insnIndex + 1, variableIndex)) {
// No variable in LVT -> do not add one
val lvtRecord = oldLvt.findRecord(insnIndex, variableIndex) ?: continue
if (lvtRecord.name == CONTINUATION_VARIABLE_NAME || lvtRecord.name == SUSPEND_CALL_RESULT_NAME) continue
if (lvtRecord.name == CONTINUATION_VARIABLE_NAME) continue
// End the local when it is no longer live. Since it is not live, we will not spill and unspill it across
// suspension points. It is tempting to keep it alive until the next suspension point to leave it visible in
// the debugger for as long as possible. However, in the case of loops, the resumption after suspension can
@@ -1291,21 +1336,11 @@ private fun updateLvtAccordingToLiveness(method: MethodNode, isForNamedFunction:
val endLabel = nextLabel(insn.next)?.let { min(lvtRecord.end, it) } ?: lvtRecord.end
// startLabel can be null in case of parameters
@Suppress("NAME_SHADOWING") val startLabel = startLabel ?: lvtRecord.start
// Attempt to extend existing local variable node corresponding to the record in
// the original local variable table, if there is no back-edge
val latest = oldLvtNodeToLatestNewLvtNode[lvtRecord]
// if we can extend the previous range to where the local variable dies, we do not need a
// new entry, we know we cannot extend it to the lvt.endOffset, if we could we would have
// done so when we added it below.
val extended = latest?.extendRecordIfPossible(method, suspensionPoints, lvtRecord.end) ?: false
if (!extended) {
val new = LocalVariableNode(lvtRecord.name, lvtRecord.desc, lvtRecord.signature, startLabel, endLabel, lvtRecord.index)
oldLvtNodeToLatestNewLvtNode[lvtRecord] = new
method.localVariables.add(new)
// see if we can extend it all the way to the old end
new.extendRecordIfPossible(method, suspensionPoints, lvtRecord.end)
val node = LocalVariableNode(lvtRecord.name, lvtRecord.desc, lvtRecord.signature, startLabel, endLabel, lvtRecord.index)
if (lvtRecord !in oldLvtNodeToLatestNewLvtNode) {
method.localVariables.add(node)
}
oldLvtNodeToLatestNewLvtNode[lvtRecord] = node
}
}
}
@@ -1327,35 +1362,3 @@ private fun updateLvtAccordingToLiveness(method: MethodNode, isForNamedFunction:
}
}
}
/* We cannot extend a record if there is STORE instruction or a back-edge.
* STORE instructions can signify a unspilling operation, in which case, the variable will become visible before it unspilled,
* back-edges occur in loops.
*
* @return true if the range has been extended
*/
private fun LocalVariableNode.extendRecordIfPossible(
method: MethodNode,
suspensionPoints: List<LabelNode>,
endLabel: LabelNode
): Boolean {
val nextSuspensionPointLabel = suspensionPoints.find { it in InsnSequence(end, endLabel) } ?: endLabel
var current: AbstractInsnNode? = end
while (current != null && current != nextSuspensionPointLabel) {
if (current is JumpInsnNode) {
if (method.instructions.indexOf(current.label) < method.instructions.indexOf(current)) {
return false
}
}
// TODO: HACK
// TODO: Find correct label, which is OK to be used as end label.
if (current.opcode == Opcodes.ARETURN && nextSuspensionPointLabel != endLabel) return false
if (current.isStoreOperation() && (current as VarInsnNode).`var` == index) {
return false
}
current = current.next
}
end = nextSuspensionPointLabel
return true
}

View File

@@ -6,7 +6,7 @@
package org.jetbrains.kotlin.codegen.coroutines
import org.jetbrains.kotlin.codegen.optimization.boxing.isUnitInstance
import org.jetbrains.kotlin.codegen.optimization.common.FastMethodAnalyzer
import org.jetbrains.kotlin.codegen.optimization.common.MethodAnalyzer
import org.jetbrains.kotlin.codegen.optimization.common.asSequence
import org.jetbrains.kotlin.codegen.optimization.common.removeAll
import org.jetbrains.kotlin.codegen.optimization.fixStack.top
@@ -85,7 +85,7 @@ private class UnitSourceInterpreter(private val localVariables: Set<Int>) : Basi
}
fun run(internalClassName: String, methodNode: MethodNode): Array<Frame<BasicValue>?> {
val frames = FastMethodAnalyzer<BasicValue>(internalClassName, methodNode, this).analyze()
val frames = MethodAnalyzer<BasicValue>(internalClassName, methodNode, this).analyze()
// The ASM analyzer does not visit POP instructions, so we do so here.
for ((insn, frame) in methodNode.instructions.asSequence().zip(frames.asSequence())) {
if (frame != null && insn.opcode == Opcodes.POP) {

View File

@@ -6,7 +6,7 @@
package org.jetbrains.kotlin.codegen.coroutines
import org.jetbrains.kotlin.codegen.StackValue
import org.jetbrains.kotlin.codegen.optimization.common.FastMethodAnalyzer
import org.jetbrains.kotlin.codegen.optimization.common.MethodAnalyzer
import org.jetbrains.kotlin.codegen.optimization.common.OptimizationBasicInterpreter
import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.org.objectweb.asm.Opcodes
@@ -130,11 +130,11 @@ internal fun performSpilledVariableFieldTypesAnalysis(
thisName: String
): Array<out Frame<BasicValue>?> {
val interpreter = IntLikeCoerceInterpreter()
FastMethodAnalyzer(thisName, methodNode, interpreter).analyze()
MethodAnalyzer(thisName, methodNode, interpreter).analyze()
for ((insn, type) in interpreter.needsToBeCoerced) {
methodNode.instructions.insert(insn, withInstructionAdapter { coerceInt(type, this) })
}
return FastMethodAnalyzer(thisName, methodNode, OptimizationBasicInterpreter()).analyze()
return MethodAnalyzer(thisName, methodNode, OptimizationBasicInterpreter()).analyze()
}
private fun coerceInt(to: Type, v: InstructionAdapter) {

View File

@@ -88,17 +88,18 @@ class SuspendFunctionGenerationStrategy(
return CoroutineTransformerMethodVisitor(
mv, access, name, desc, null, null, containingClassInternalName, this::classBuilderForCoroutineState,
isForNamedFunction = true,
shouldPreserveClassInitialization = constructorCallNormalizationMode.shouldPreserveClassInitialization,
disableTailCallOptimizationForFunctionReturningUnit = originalSuspendDescriptor.returnType?.isUnit() == true &&
originalSuspendDescriptor.overriddenDescriptors.isNotEmpty() &&
!originalSuspendDescriptor.allOverriddenFunctionsReturnUnit(),
reportSuspensionPointInsideMonitor = { reportSuspensionPointInsideMonitor(declaration, state, it) },
lineNumber = CodegenUtil.getLineNumberForElement(declaration, false) ?: 0,
sourceFile = declaration.containingKtFile.name,
shouldPreserveClassInitialization = constructorCallNormalizationMode.shouldPreserveClassInitialization,
needDispatchReceiver = originalSuspendDescriptor.dispatchReceiverParameter != null,
internalNameForDispatchReceiver = (originalSuspendDescriptor.containingDeclaration as? ClassDescriptor)?.let {
if (it.isInlineClass()) state.typeMapper.mapType(it).internalName else null
} ?: containingClassInternalNameOrNull(),
languageVersionSettings = languageVersionSettings,
disableTailCallOptimizationForFunctionReturningUnit = originalSuspendDescriptor.returnType?.isUnit() == true &&
originalSuspendDescriptor.overriddenDescriptors.isNotEmpty() &&
!originalSuspendDescriptor.allOverriddenFunctionsReturnUnit(),
useOldSpilledVarTypeAnalysis = state.configuration.getBoolean(JVMConfigurationKeys.USE_OLD_SPILLED_VAR_TYPE_ANALYSIS)
)
}
@@ -154,7 +155,8 @@ class SuspendFunctionGenerationStrategy(
needDispatchReceiver,
internalNameForDispatchReceiver,
containingClassInternalName,
classBuilderForCoroutineState
classBuilderForCoroutineState,
languageVersionSettings
)
addFakeContinuationConstructorCallMarker(this, false)
pop() // Otherwise stack-transformation breaks

View File

@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.codegen.optimization.common.asSequence
import org.jetbrains.kotlin.codegen.optimization.common.isMeaningful
import org.jetbrains.kotlin.codegen.optimization.fixStack.top
import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.utils.sure
import org.jetbrains.org.objectweb.asm.Label
@@ -26,6 +27,7 @@ import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue
import org.jetbrains.org.objectweb.asm.tree.analysis.Frame
internal class MethodNodeExaminer(
val languageVersionSettings: LanguageVersionSettings,
containingClassInternalName: String,
val methodNode: MethodNode,
suspensionPoints: List<SuspensionPoint>,
@@ -39,6 +41,7 @@ internal class MethodNodeExaminer(
private val popsBeforeSafeUnitInstances = mutableSetOf<AbstractInsnNode>()
private val areturnsAfterSafeUnitInstances = mutableSetOf<AbstractInsnNode>()
private val meaningfulSuccessorsCache = hashMapOf<AbstractInsnNode, List<AbstractInsnNode>>()
private val meaningfulPredecessorsCache = hashMapOf<AbstractInsnNode, List<AbstractInsnNode>>()
init {
if (!disableTailCallOptimizationForFunctionReturningUnit) {
@@ -51,8 +54,10 @@ internal class MethodNodeExaminer(
for (pop in popsBeforeUnitInstances) {
val units = pop.meaningfulSuccessors()
val allUnitsAreSafe = units.all { unit ->
// check they have only returns among successors
unit.meaningfulSuccessors().all { it.opcode == Opcodes.ARETURN }
// check no other predecessor exists
unit.meaningfulPredecessors().all { it in popsBeforeUnitInstances } &&
// check they have only returns among successors
unit.meaningfulSuccessors().all { it.opcode == Opcodes.ARETURN }
}
if (!allUnitsAreSafe) continue
// save them all to the properties
@@ -71,22 +76,35 @@ internal class MethodNodeExaminer(
private fun AbstractInsnNode.isAreturnAfterSafeUnitInstance(): Boolean = this in areturnsAfterSafeUnitInstances
private fun AbstractInsnNode.meaningfulSuccessors(): List<AbstractInsnNode> = meaningfulSuccessorsCache.getOrPut(this) {
meaningfulSuccessorsOrPredecessors(true)
}
private fun AbstractInsnNode.meaningfulPredecessors(): List<AbstractInsnNode> = meaningfulPredecessorsCache.getOrPut(this) {
meaningfulSuccessorsOrPredecessors(false)
}
private fun AbstractInsnNode.meaningfulSuccessorsOrPredecessors(isSuccessors: Boolean): List<AbstractInsnNode> {
fun AbstractInsnNode.isMeaningful() = isMeaningful && opcode != Opcodes.NOP && opcode != Opcodes.GOTO && this !is LineNumberNode
fun AbstractInsnNode.getIndices() =
if (isSuccessors) controlFlowGraph.getSuccessorsIndices(this)
else controlFlowGraph.getPredecessorsIndices(this)
val visited = mutableSetOf<AbstractInsnNode>()
fun dfs(insn: AbstractInsnNode) {
if (insn in visited) return
visited += insn
if (!insn.isMeaningful()) {
for (succIndex in controlFlowGraph.getSuccessorsIndices(insn)) {
for (succIndex in insn.getIndices()) {
dfs(methodNode.instructions[succIndex])
}
}
}
for (succIndex in controlFlowGraph.getSuccessorsIndices(this)) {
for (succIndex in getIndices()) {
dfs(methodNode.instructions[succIndex])
}
visited.filter { it.isMeaningful() }
return visited.filter { it.isMeaningful() }
}
fun replacePopsBeforeSafeUnitInstancesWithCoroutineSuspendedChecks() {
@@ -98,7 +116,7 @@ internal class MethodNodeExaminer(
val label = Label()
methodNode.instructions.insertBefore(pop, withInstructionAdapter {
dup()
loadCoroutineSuspendedMarker()
loadCoroutineSuspendedMarker(languageVersionSettings)
ifacmpne(label)
areturn(AsmTypes.OBJECT_TYPE)
mark(label)
@@ -141,8 +159,8 @@ internal class MethodNodeExaminer(
* @return indices of safely reachable returns for each instruction in the method node
*/
private fun findSafelyReachableReturns(): Array<Set<Int>?> {
val insns = methodNode.instructions.toArray()
val reachableReturnsIndices = Array(insns.size) init@{ index ->
val insns = methodNode.instructions
val reachableReturnsIndices = Array(insns.size()) init@{ index ->
val insn = insns[index]
if (insn.opcode == Opcodes.ARETURN && !insn.isAreturnAfterSafeUnitInstance()) {
@@ -164,7 +182,7 @@ internal class MethodNodeExaminer(
var changed: Boolean
do {
changed = false
for (index in insns.indices.reversed()) {
for (index in 0 until insns.size()) {
if (insns[index].opcode == Opcodes.ARETURN) continue
@Suppress("RemoveExplicitTypeArguments")

View File

@@ -9,8 +9,6 @@ import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.backend.common.COROUTINE_SUSPENDED_NAME
import org.jetbrains.kotlin.backend.common.isBuiltInSuspendCoroutineUninterceptedOrReturn
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.builtins.StandardNames.COROUTINES_INTRINSICS_PACKAGE_FQ_NAME
import org.jetbrains.kotlin.builtins.StandardNames.COROUTINES_JVM_INTERNAL_PACKAGE_FQ_NAME
import org.jetbrains.kotlin.builtins.isBuiltinFunctionalClassDescriptor
import org.jetbrains.kotlin.codegen.*
import org.jetbrains.kotlin.codegen.binding.CodegenBinding
@@ -56,27 +54,67 @@ import org.jetbrains.org.objectweb.asm.tree.MethodNode
const val COROUTINE_LABEL_FIELD_NAME = "label"
const val SUSPEND_FUNCTION_CREATE_METHOD_NAME = "create"
const val DO_RESUME_METHOD_NAME = "doResume"
const val INVOKE_SUSPEND_METHOD_NAME = "invokeSuspend"
const val CONTINUATION_RESULT_FIELD_NAME = "result"
const val EXCEPTION_FIELD_NAME = "exception"
private const val GET_CONTEXT_METHOD_NAME = "getContext"
val RELEASE_COROUTINES_VERSION_SETTINGS = LanguageVersionSettingsImpl(LanguageVersion.KOTLIN_1_3, ApiVersion.KOTLIN_1_3)
val DEBUG_METADATA_ANNOTATION_ASM_TYPE: Type =
COROUTINES_JVM_INTERNAL_PACKAGE_FQ_NAME.child(Name.identifier("DebugMetadata")).topLevelClassAsmType()
fun LanguageVersionSettings.isResumeImplMethodName(name: String) =
if (isReleaseCoroutines())
name == INVOKE_SUSPEND_METHOD_NAME
else
name == DO_RESUME_METHOD_NAME
fun coroutineContextAsmType(): Type =
StandardNames.COROUTINES_PACKAGE_FQ_NAME.child(Name.identifier("CoroutineContext")).topLevelClassAsmType()
fun LanguageVersionSettings.dataFieldName(): String = if (isReleaseCoroutines()) "result" else "data"
fun String.isCoroutineSuperClass(): Boolean =
COROUTINES_JVM_INTERNAL_PACKAGE_FQ_NAME.identifiedChild("ContinuationImpl") == this ||
COROUTINES_JVM_INTERNAL_PACKAGE_FQ_NAME.identifiedChild("RestrictedContinuationImpl") == this ||
COROUTINES_JVM_INTERNAL_PACKAGE_FQ_NAME.identifiedChild("SuspendLambda") == this ||
COROUTINES_JVM_INTERNAL_PACKAGE_FQ_NAME.identifiedChild("RestrictedSuspendLambda") == this
fun isResumeImplMethodNameFromAnyLanguageSettings(name: String) = name == INVOKE_SUSPEND_METHOD_NAME || name == DO_RESUME_METHOD_NAME
fun LanguageVersionSettings.coroutinesJvmInternalPackageFqName() =
coroutinesPackageFqName().child(Name.identifier("jvm")).child(Name.identifier("internal"))
val DEBUG_METADATA_ANNOTATION_ASM_TYPE = RELEASE_COROUTINES_VERSION_SETTINGS.coroutinesJvmInternalPackageFqName()
.child(Name.identifier("DebugMetadata")).topLevelClassAsmType()
fun LanguageVersionSettings.continuationAsmType() =
continuationInterfaceFqName().topLevelClassAsmType()
fun continuationAsmTypes() = listOf(
LanguageVersionSettingsImpl(LanguageVersion.KOTLIN_1_3, ApiVersion.KOTLIN_1_3).continuationAsmType(),
LanguageVersionSettingsImpl(LanguageVersion.KOTLIN_1_2, ApiVersion.KOTLIN_1_2).continuationAsmType()
)
fun LanguageVersionSettings.coroutineContextAsmType() =
coroutinesPackageFqName().child(Name.identifier("CoroutineContext")).topLevelClassAsmType()
fun LanguageVersionSettings.isCoroutineSuperClass(internalName: String): Boolean {
val coroutinesJvmInternalPackage = coroutinesJvmInternalPackageFqName()
return if (isReleaseCoroutines())
coroutinesJvmInternalPackage.identifiedChild("ContinuationImpl") == internalName ||
coroutinesJvmInternalPackage.identifiedChild("RestrictedContinuationImpl") == internalName ||
coroutinesJvmInternalPackage.identifiedChild("SuspendLambda") == internalName ||
coroutinesJvmInternalPackage.identifiedChild("RestrictedSuspendLambda") == internalName
else
coroutinesJvmInternalPackage.identifiedChild("CoroutineImpl") == internalName
}
private fun FqName.identifiedChild(name: String) = child(Name.identifier(name)).topLevelClassInternalName()
private val coroutinesIntrinsicsFileFacadeInternalName: Type =
COROUTINES_INTRINSICS_PACKAGE_FQ_NAME.child(Name.identifier("IntrinsicsKt")).topLevelClassAsmType()
private fun LanguageVersionSettings.coroutinesIntrinsicsFileFacadeInternalName() =
coroutinesIntrinsicsPackageFqName().child(Name.identifier("IntrinsicsKt")).topLevelClassAsmType()
private fun LanguageVersionSettings.internalCoroutineIntrinsicsOwnerInternalName() =
coroutinesJvmInternalPackageFqName().child(Name.identifier("CoroutineIntrinsics")).topLevelClassInternalName()
fun computeLabelOwner(languageVersionSettings: LanguageVersionSettings, thisName: String): Type =
if (languageVersionSettings.isReleaseCoroutines())
Type.getObjectType(thisName)
else
languageVersionSettings.coroutinesJvmInternalPackageFqName().child(Name.identifier("CoroutineImpl")).topLevelClassAsmType()
private const val NORMALIZE_CONTINUATION_METHOD_NAME = "normalizeContinuation"
private const val GET_CONTEXT_METHOD_NAME = "getContext"
data class ResolvedCallWithRealDescriptor(val resolvedCall: ResolvedCall<*>, val fakeContinuationExpression: KtExpression)
@@ -84,7 +122,7 @@ data class ResolvedCallWithRealDescriptor(val resolvedCall: ResolvedCall<*>, val
val INITIAL_DESCRIPTOR_FOR_SUSPEND_FUNCTION = object : CallableDescriptor.UserDataKey<FunctionDescriptor> {}
@JvmField
val INITIAL_SUSPEND_DESCRIPTOR_FOR_INVOKE_SUSPEND = object : CallableDescriptor.UserDataKey<FunctionDescriptor> {}
val INITIAL_SUSPEND_DESCRIPTOR_FOR_DO_RESUME = object : CallableDescriptor.UserDataKey<FunctionDescriptor> {}
val CONTINUATION_PARAMETER_NAME = Name.identifier("continuation")
@@ -120,9 +158,9 @@ fun ResolvedCall<*>.replaceSuspensionFunctionWithRealDescriptor(
val newCandidateDescriptor =
when (function) {
is FunctionImportedFromObject ->
getOrCreateJvmSuspendFunctionView(function.callableFromObject, bindingContext).asImportedFromObject()
getOrCreateJvmSuspendFunctionView(function.callableFromObject, isReleaseCoroutines, bindingContext).asImportedFromObject()
is SimpleFunctionDescriptor ->
getOrCreateJvmSuspendFunctionView(function, bindingContext)
getOrCreateJvmSuspendFunctionView(function, isReleaseCoroutines, bindingContext)
else ->
throw AssertionError("Unexpected suspend function descriptor: $function")
}
@@ -185,10 +223,10 @@ private fun NewResolvedCallImpl<VariableDescriptor>.asDummyOldResolvedCall(bindi
enum class SuspensionPointKind { NEVER, NOT_INLINE, ALWAYS }
fun ResolvedCall<*>.isSuspensionPoint(codegen: ExpressionCodegen): SuspensionPointKind {
fun ResolvedCall<*>.isSuspensionPoint(codegen: ExpressionCodegen, languageVersionSettings: LanguageVersionSettings): SuspensionPointKind {
val functionDescriptor = resultingDescriptor as? FunctionDescriptor ?: return SuspensionPointKind.NEVER
if (!functionDescriptor.unwrapInitialDescriptorForSuspendFunction().isSuspend) return SuspensionPointKind.NEVER
if (functionDescriptor.isBuiltInSuspendCoroutineUninterceptedOrReturnInJvm()) return SuspensionPointKind.ALWAYS
if (functionDescriptor.isBuiltInSuspendCoroutineUninterceptedOrReturnInJvm(languageVersionSettings)) return SuspensionPointKind.ALWAYS
if (functionDescriptor.isInline) return SuspensionPointKind.NEVER
val isInlineLambda = this.safeAs<VariableAsFunctionResolvedCall>()
@@ -204,6 +242,7 @@ fun CallableDescriptor.isSuspendFunctionNotSuspensionView(): Boolean {
fun <D : FunctionDescriptor> getOrCreateJvmSuspendFunctionView(function: D, state: GenerationState): D = getOrCreateJvmSuspendFunctionView(
function,
state.languageVersionSettings.supportsFeature(LanguageFeature.ReleaseCoroutines),
state.bindingContext
)
@@ -213,6 +252,7 @@ fun <D : FunctionDescriptor> getOrCreateJvmSuspendFunctionView(function: D, stat
@JvmOverloads
fun <D : FunctionDescriptor> getOrCreateJvmSuspendFunctionView(
function: D,
isReleaseCoroutines: Boolean,
bindingContext: BindingContext? = null
): D {
assert(function.isSuspend) {
@@ -232,7 +272,7 @@ fun <D : FunctionDescriptor> getOrCreateJvmSuspendFunctionView(
outType = if (function.containingDeclaration.safeAs<ClassDescriptor>()?.isBuiltinFunctionalClassDescriptor == true)
function.builtIns.nullableAnyType
else
function.getContinuationParameterTypeOfSuspendFunction(),
function.getContinuationParameterTypeOfSuspendFunction(isReleaseCoroutines),
declaresDefaultValue = false, isCrossinline = false,
isNoinline = false, varargElementType = null,
source = SourceElement.NO_SOURCE
@@ -269,7 +309,8 @@ fun <D : FunctionDescriptor> D.createCustomCopy(
return result as D
}
private fun FunctionDescriptor.getContinuationParameterTypeOfSuspendFunction() = module.getContinuationOfTypeOrAny(returnType!!)
private fun FunctionDescriptor.getContinuationParameterTypeOfSuspendFunction(isReleaseCoroutines: Boolean) =
module.getContinuationOfTypeOrAny(returnType!!, if (this.needsExperimentalCoroutinesWrapper()) false else isReleaseCoroutines)
fun ModuleDescriptor.getResult(kotlinType: KotlinType) =
module.resolveTopLevelClass(
@@ -282,11 +323,44 @@ fun ModuleDescriptor.getResult(kotlinType: KotlinType) =
)
} ?: ErrorUtils.createErrorType("For Result")
fun FunctionDescriptor.isBuiltInSuspendCoroutineUninterceptedOrReturnInJvm() =
getUserData(INITIAL_DESCRIPTOR_FOR_SUSPEND_FUNCTION)?.isBuiltInSuspendCoroutineUninterceptedOrReturn() == true
private fun MethodNode.invokeNormalizeContinuation(languageVersionSettings: LanguageVersionSettings) {
visitMethodInsn(
Opcodes.INVOKESTATIC,
languageVersionSettings.internalCoroutineIntrinsicsOwnerInternalName(),
NORMALIZE_CONTINUATION_METHOD_NAME,
Type.getMethodDescriptor(languageVersionSettings.continuationAsmType(), languageVersionSettings.continuationAsmType()),
false
)
}
fun createMethodNodeForCoroutineContext(functionDescriptor: FunctionDescriptor): MethodNode {
assert(functionDescriptor.isBuiltInCoroutineContext()) {
fun FunctionDescriptor.isBuiltInSuspendCoroutineUninterceptedOrReturnInJvm(languageVersionSettings: LanguageVersionSettings) =
getUserData(INITIAL_DESCRIPTOR_FOR_SUSPEND_FUNCTION)?.isBuiltInSuspendCoroutineUninterceptedOrReturn(languageVersionSettings) == true
fun createMethodNodeForIntercepted(languageVersionSettings: LanguageVersionSettings): MethodNode {
val node =
MethodNode(
Opcodes.API_VERSION,
Opcodes.ACC_STATIC,
"fake",
Type.getMethodDescriptor(languageVersionSettings.continuationAsmType(), languageVersionSettings.continuationAsmType()),
null, null
)
node.visitVarInsn(Opcodes.ALOAD, 0)
node.invokeNormalizeContinuation(languageVersionSettings)
node.visitInsn(Opcodes.ARETURN)
node.visitMaxs(1, 1)
return node
}
fun createMethodNodeForCoroutineContext(
functionDescriptor: FunctionDescriptor,
languageVersionSettings: LanguageVersionSettings
): MethodNode {
assert(functionDescriptor.isBuiltInCoroutineContext(languageVersionSettings)) {
"functionDescriptor must be kotlin.coroutines.intrinsics.coroutineContext property getter"
}
@@ -295,7 +369,7 @@ fun createMethodNodeForCoroutineContext(functionDescriptor: FunctionDescriptor):
Opcodes.API_VERSION,
Opcodes.ACC_STATIC,
"fake",
Type.getMethodDescriptor(coroutineContextAsmType()),
Type.getMethodDescriptor(languageVersionSettings.coroutineContextAsmType()),
null, null
)
@@ -303,20 +377,20 @@ fun createMethodNodeForCoroutineContext(functionDescriptor: FunctionDescriptor):
addFakeContinuationMarker(v)
v.invokeGetContext()
v.invokeGetContext(languageVersionSettings)
node.visitMaxs(1, 1)
return node
}
fun createMethodNodeForSuspendCoroutineUninterceptedOrReturn(): MethodNode {
fun createMethodNodeForSuspendCoroutineUninterceptedOrReturn(languageVersionSettings: LanguageVersionSettings): MethodNode {
val node =
MethodNode(
Opcodes.API_VERSION,
Opcodes.ACC_STATIC,
"fake",
Type.getMethodDescriptor(OBJECT_TYPE, AsmTypes.FUNCTION1, CONTINUATION_ASM_TYPE),
Type.getMethodDescriptor(OBJECT_TYPE, AsmTypes.FUNCTION1, languageVersionSettings.continuationAsmType()),
null, null
)
@@ -331,22 +405,25 @@ fun createMethodNodeForSuspendCoroutineUninterceptedOrReturn(): MethodNode {
"($OBJECT_TYPE)$OBJECT_TYPE"
)
val elseLabel = Label()
// if (result === COROUTINE_SUSPENDED) {
dup()
loadCoroutineSuspendedMarker()
ifacmpne(elseLabel)
// DebugProbesKt.probeCoroutineSuspended(continuation)
load(1, OBJECT_TYPE) // continuation
checkcast(CONTINUATION_ASM_TYPE)
invokestatic(
COROUTINES_JVM_INTERNAL_PACKAGE_FQ_NAME.child(Name.identifier("DebugProbesKt")).topLevelClassAsmType().internalName,
"probeCoroutineSuspended",
"($CONTINUATION_ASM_TYPE)V",
false
)
// }
mark(elseLabel)
if (languageVersionSettings.supportsFeature(LanguageFeature.ReleaseCoroutines)) {
val elseLabel = Label()
// if (result === COROUTINE_SUSPENDED) {
dup()
loadCoroutineSuspendedMarker(languageVersionSettings)
ifacmpne(elseLabel)
// DebugProbesKt.probeCoroutineSuspended(continuation)
load(1, OBJECT_TYPE) // continuation
checkcast(languageVersionSettings.continuationAsmType())
invokestatic(
languageVersionSettings.coroutinesJvmInternalPackageFqName().child(Name.identifier("DebugProbesKt"))
.topLevelClassAsmType().internalName,
"probeCoroutineSuspended",
"(${languageVersionSettings.continuationAsmType()})V",
false
)
// }
mark(elseLabel)
}
}
node.visitInsn(Opcodes.ARETURN)
@@ -356,13 +433,13 @@ fun createMethodNodeForSuspendCoroutineUninterceptedOrReturn(): MethodNode {
}
private fun InstructionAdapter.invokeGetContext() {
private fun InstructionAdapter.invokeGetContext(languageVersionSettings: LanguageVersionSettings) {
invokeinterface(
CONTINUATION_ASM_TYPE.internalName,
languageVersionSettings.continuationAsmType().internalName,
GET_CONTEXT_METHOD_NAME,
Type.getMethodDescriptor(coroutineContextAsmType())
Type.getMethodDescriptor(languageVersionSettings.coroutineContextAsmType())
)
areturn(coroutineContextAsmType())
areturn(languageVersionSettings.coroutineContextAsmType())
}
@Suppress("UNCHECKED_CAST")
@@ -370,12 +447,15 @@ fun <D : CallableDescriptor?> D.unwrapInitialDescriptorForSuspendFunction(): D =
this.safeAs<SimpleFunctionDescriptor>()?.getUserData(INITIAL_DESCRIPTOR_FOR_SUSPEND_FUNCTION) as D ?: this
fun FunctionDescriptor.getOriginalSuspendFunctionView(bindingContext: BindingContext): FunctionDescriptor =
fun FunctionDescriptor.getOriginalSuspendFunctionView(bindingContext: BindingContext, isReleaseCoroutines: Boolean): FunctionDescriptor =
if (isSuspend)
getOrCreateJvmSuspendFunctionView(unwrapInitialDescriptorForSuspendFunction().original, bindingContext)
getOrCreateJvmSuspendFunctionView(unwrapInitialDescriptorForSuspendFunction().original, isReleaseCoroutines, bindingContext)
else
this
fun FunctionDescriptor.getOriginalSuspendFunctionView(bindingContext: BindingContext, state: GenerationState) =
getOriginalSuspendFunctionView(bindingContext, state.languageVersionSettings.supportsFeature(LanguageFeature.ReleaseCoroutines))
// For each suspend function, we have a corresponding JVM view function that has an extra continuation parameter,
// and, more importantly, returns 'kotlin.Any' (so that it can return as a reference value or a special COROUTINE_SUSPENDED object).
// This also causes boxing of primitives and inline class values.
@@ -402,24 +482,38 @@ fun FunctionDescriptor.originalReturnTypeOfSuspendFunctionReturningUnboxedInline
return originalReturnType
}
fun InstructionAdapter.loadCoroutineSuspendedMarker() {
fun InstructionAdapter.loadCoroutineSuspendedMarker(languageVersionSettings: LanguageVersionSettings) {
invokestatic(
coroutinesIntrinsicsFileFacadeInternalName.internalName,
languageVersionSettings.coroutinesIntrinsicsFileFacadeInternalName().internalName,
"get$COROUTINE_SUSPENDED_NAME",
Type.getMethodDescriptor(OBJECT_TYPE),
false
)
}
fun InstructionAdapter.generateCoroutineSuspendedCheck() {
fun InstructionAdapter.generateCoroutineSuspendedCheck(languageVersionSettings: LanguageVersionSettings) {
dup()
loadCoroutineSuspendedMarker()
loadCoroutineSuspendedMarker(languageVersionSettings)
val elseLabel = Label()
ifacmpne(elseLabel)
areturn(OBJECT_TYPE)
mark(elseLabel)
}
fun InstructionAdapter.invokeDoResumeWithUnit(thisName: String) {
// .doResume(Unit, null)
StackValue.putUnitInstance(this)
aconst(null)
invokevirtual(
thisName,
DO_RESUME_METHOD_NAME,
Type.getMethodDescriptor(OBJECT_TYPE, OBJECT_TYPE, AsmTypes.JAVA_THROWABLE_TYPE),
false
)
}
fun InstructionAdapter.invokeInvokeSuspendWithUnit(thisName: String) {
StackValue.putUnitInstance(this)
@@ -443,14 +537,18 @@ fun FunctionDescriptor.isSuspendLambdaOrLocalFunction() = this.isSuspend && when
}
fun FunctionDescriptor.isLocalSuspendFunctionNotSuspendLambda() = isSuspendLambdaOrLocalFunction() && this !is AnonymousFunctionDescriptor
@JvmField
val CONTINUATION_ASM_TYPE = StandardNames.CONTINUATION_INTERFACE_FQ_NAME.topLevelClassAsmType()
val EXPERIMENTAL_CONTINUATION_ASM_TYPE = StandardNames.CONTINUATION_INTERFACE_FQ_NAME_EXPERIMENTAL.topLevelClassAsmType()
@JvmField
val RELEASE_CONTINUATION_ASM_TYPE = StandardNames.CONTINUATION_INTERFACE_FQ_NAME_RELEASE.topLevelClassAsmType()
fun FunctionDescriptor.isInvokeSuspendOfLambda(): Boolean {
if (this !is SimpleFunctionDescriptor) return false
if (valueParameters.size != 1 ||
valueParameters[0].name.asString() != SUSPEND_CALL_RESULT_NAME ||
name.asString() != INVOKE_SUSPEND_METHOD_NAME
name.asString() != "invokeSuspend"
) return false
return containingDeclaration is SyntheticClassDescriptorForLambda
}

View File

@@ -73,8 +73,8 @@ import kotlin.math.max
* - restore constructor arguments
*/
class UninitializedStoresProcessor(
private val methodNode: MethodNode,
private val shouldPreserveClassInitialization: Boolean
private val methodNode: MethodNode,
private val shouldPreserveClassInitialization: Boolean
) {
// <init> method is "special", because it will invoke <init> from this class or from a base class for #0
//
@@ -87,10 +87,10 @@ class UninitializedStoresProcessor(
fun run() {
val interpreter = UninitializedNewValueMarkerInterpreter(methodNode.instructions)
if (methodNode.instructions.toArray().none { it.opcode == Opcodes.NEW })
return
val frames = CustomFramesMethodAnalyzer("fake", methodNode, interpreter, this::UninitializedNewValueFrame).analyze()
val frames = CustomFramesMethodAnalyzer(
"fake", methodNode, interpreter,
this::UninitializedNewValueFrame
).analyze()
interpreter.analyzePopInstructions(frames)
@@ -115,12 +115,12 @@ class UninitializedStoresProcessor(
// POP
val typeNameForClass = newInsn.desc.replace('/', '.')
insertBefore(newInsn, LdcInsnNode(typeNameForClass))
insertBefore(
newInsn,
MethodInsnNode(Opcodes.INVOKESTATIC, "java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;", false)
)
insertBefore(newInsn, MethodInsnNode(
Opcodes.INVOKESTATIC, "java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;", false
))
set(newInsn, InsnNode(Opcodes.POP))
} else {
}
else {
remove(newInsn)
}
}
@@ -138,7 +138,10 @@ class UninitializedStoresProcessor(
}
methodNode.maxLocals = max(methodNode.maxLocals, nextVarIndex)
methodNode.instructions.insertBefore(insn, insnListOf(TypeInsnNode(Opcodes.NEW, newInsn.desc), InsnNode(Opcodes.DUP)))
methodNode.instructions.insertBefore(insn, insnListOf(
TypeInsnNode(Opcodes.NEW, newInsn.desc),
InsnNode(Opcodes.DUP)
))
for (type in storedTypes.reversed()) {
nextVarIndex -= type.size
@@ -171,11 +174,11 @@ class UninitializedStoresProcessor(
assert(insn.opcode == Opcodes.INVOKESPECIAL) { "Expected opcode Opcodes.INVOKESPECIAL for <init>, but ${insn.opcode} found" }
val paramsCountIncludingReceiver = Type.getArgumentTypes((insn as MethodInsnNode).desc).size + 1
val newValue = peek(paramsCountIncludingReceiver) as? UninitializedNewValue
?: if (isInSpecialMethod)
return null
else
error("Expected value generated with NEW")
val newValue = peek(paramsCountIncludingReceiver) as? UninitializedNewValue ?:
if (isInSpecialMethod)
return null
else
error("Expected value generated with NEW")
assert(peek(paramsCountIncludingReceiver - 1) is UninitializedNewValue) {
"Next value after NEW should be one generated by DUP"
@@ -185,8 +188,8 @@ class UninitializedStoresProcessor(
}
private class UninitializedNewValue(
val newInsn: TypeInsnNode,
val internalName: String
val newInsn: TypeInsnNode,
val internalName: String
) : StrictBasicValue(Type.getObjectType(internalName)) {
override fun toString() = "UninitializedNewValue(internalName='$internalName')"
}
@@ -233,8 +236,7 @@ class UninitializedStoresProcessor(
private fun checkUninitializedObjectCopy(newInsn: TypeInsnNode, usageInsn: AbstractInsnNode) {
when (usageInsn.opcode) {
Opcodes.DUP, Opcodes.ASTORE, Opcodes.ALOAD -> {
}
Opcodes.DUP, Opcodes.ASTORE, Opcodes.ALOAD -> {}
else -> error("Unexpected copy instruction for ${newInsn.debugText}: ${usageInsn.debugText}")
}
}

View File

@@ -62,7 +62,7 @@ class AnonymousObjectTransformer(
createClassReader().accept(object : ClassVisitor(Opcodes.API_VERSION, classBuilder.visitor) {
override fun visit(version: Int, access: Int, name: String, signature: String?, superName: String, interfaces: Array<String>) {
classBuilder.defineClass(null, maxOf(version, state.classFileVersion), access, name, signature, superName, interfaces)
if (superName.isCoroutineSuperClass()) {
if (languageVersionSettings.isCoroutineSuperClass(superName)) {
inliningContext.isContinuation = true
}
superClassName = superName
@@ -295,6 +295,7 @@ class AnonymousObjectTransformer(
capturedBuilder: ParametersBuilder,
isConstructor: Boolean
): InlineResult {
val typeParametersToReify = inliningContext.root.inlineMethodReifier.reifyInstructions(sourceNode)
val parameters =
if (isConstructor) capturedBuilder.buildParameters() else getMethodParametersWithCaptured(capturedBuilder, sourceNode)
@@ -303,10 +304,7 @@ class AnonymousObjectTransformer(
transformationInfo.capturedLambdasToInline, parentRemapper, isConstructor
)
val reifiedTypeParametersUsages = if (inliningContext.shouldReifyTypeParametersInObjects)
inliningContext.root.inlineMethodReifier.reifyInstructions(sourceNode)
else null
val result = MethodInliner(
val inliner = MethodInliner(
sourceNode,
parameters,
inliningContext.subInline(transformationInfo.nameGenerator),
@@ -320,10 +318,11 @@ class AnonymousObjectTransformer(
inliningContext.callSiteInfo.isInlineOrInsideInline,
inliningContext.callSiteInfo.file,
inliningContext.callSiteInfo.lineNumber
),
null
).doInline(deferringVisitor, LocalVarRemapper(parameters, 0), false, mapOf())
reifiedTypeParametersUsages?.let(result.reifiedTypeParametersUsages::mergeAll)
), null
)
val result = inliner.doInline(deferringVisitor, LocalVarRemapper(parameters, 0), false, mapOf())
result.reifiedTypeParametersUsages.mergeAll(typeParametersToReify)
deferringVisitor.visitMaxs(-1, -1)
return result
}
@@ -422,13 +421,11 @@ class AnonymousObjectTransformer(
}
private fun getMethodParametersWithCaptured(capturedBuilder: ParametersBuilder, sourceNode: MethodNode): Parameters {
val builder = ParametersBuilder.newBuilder()
if (sourceNode.access and Opcodes.ACC_STATIC == 0) {
builder.addThis(oldObjectType, skipped = false)
}
for (type in Type.getArgumentTypes(sourceNode.desc)) {
builder.addNextParameter(type, false)
}
val builder = ParametersBuilder.initializeBuilderFrom(
oldObjectType,
sourceNode.desc,
isStatic = sourceNode.access and Opcodes.ACC_STATIC != 0
)
for (param in capturedBuilder.listCaptured()) {
builder.addCapturedParamCopy(param)
}
@@ -520,10 +517,13 @@ class AnonymousObjectTransformer(
val paramTypes = transformationInfo.constructorDesc?.let { Type.getArgumentTypes(it) } ?: emptyArray()
for (type in paramTypes) {
val info = indexToFunctionalArgument[constructorParamBuilder.nextParameterOffset]
val isCaptured = capturedParams.contains(constructorParamBuilder.nextParameterOffset)
val parameterInfo = constructorParamBuilder.addNextParameter(type, info is LambdaInfo)
parameterInfo.functionalArgument = info
parameterInfo.isCaptured = isCaptured
if (capturedParams.contains(parameterInfo.index)) {
parameterInfo.isCaptured = true
} else {
//otherwise it's super constructor parameter
}
}
//For all inlined lambdas add their captured parameters

View File

@@ -32,9 +32,10 @@ abstract class InlineCodegen<out T : BaseExpressionCodegen>(
private val initialFrameSize = codegen.frameMap.currentSize
protected val invocationParamBuilder = ParametersBuilder.newBuilder()
private val maskValues = ArrayList<Int>()
private var maskStartIndex = -1
private var methodHandleInDefaultMethodIndex = -1
protected val expressionMap = linkedMapOf<Int, FunctionalArgument>()
protected val maskValues = ArrayList<Int>()
protected var maskStartIndex = -1
protected var methodHandleInDefaultMethodIndex = -1
protected fun generateStub(text: String, codegen: BaseExpressionCodegen) {
leaveTemps()
@@ -70,23 +71,14 @@ abstract class InlineCodegen<out T : BaseExpressionCodegen>(
private fun inlineCall(nodeAndSmap: SMAPAndMethodNode, isInlineOnly: Boolean): InlineResult {
val node = nodeAndSmap.node
if (maskStartIndex != -1) {
val parameters = invocationParamBuilder.buildParameters()
val infos = expandMaskConditionsAndUpdateVariableNodes(
node, maskStartIndex, maskValues, methodHandleInDefaultMethodIndex,
parameters.parameters.filter { it.functionalArgument === DefaultValueOfInlineParameter }
.mapTo(mutableSetOf()) { parameters.getDeclarationSlot(it) }
)
for (info in infos) {
val lambda = DefaultLambda(info, sourceCompiler)
parameters.getParameterByDeclarationSlot(info.offset).functionalArgument = lambda
if (info.needReification) {
for (lambda in extractDefaultLambdas(node)) {
invocationParamBuilder.buildParameters().getParameterByDeclarationSlot(lambda.offset).functionalArgument = lambda
val prev = expressionMap.put(lambda.offset, lambda)
assert(prev == null) { "Lambda with offset ${lambda.offset} already exists: $prev" }
if (lambda.needReification) {
lambda.reifiedTypeParametersUsages.mergeAll(reifiedTypeInliner.reifyInstructions(lambda.node.node))
}
for (captured in lambda.capturedVars) {
val param = invocationParamBuilder.addCapturedParam(captured, captured.fieldName, false)
param.remapValue = StackValue.local(codegen.frameMap.enterTemp(param.type), param.type)
param.isSynthetic = true
}
rememberCapturedForDefaultLambda(lambda)
}
}
@@ -95,7 +87,7 @@ abstract class InlineCodegen<out T : BaseExpressionCodegen>(
val parameters = invocationParamBuilder.buildParameters()
val info = RootInliningContext(
state, codegen.inlineNameGenerator.subGenerator(jvmSignature.asmMethod.name),
expressionMap, state, codegen.inlineNameGenerator.subGenerator(jvmSignature.asmMethod.name),
sourceCompiler, sourceCompiler.inlineCallSiteInfo, reifiedTypeInliner, typeParameterMappings
)
@@ -129,9 +121,7 @@ abstract class InlineCodegen<out T : BaseExpressionCodegen>(
// In case `codegen.visitor` is `<clinit>`, initializer for the `$assertionsDisabled` field
// needs to be inserted before the code that actually uses it.
if (info.generateAssertField) {
generateAssertField()
}
generateAssertFieldIfNeeded(info)
val shouldSpillStack = node.requiresEmptyStackOnEntry()
if (shouldSpillStack) {
@@ -144,6 +134,16 @@ abstract class InlineCodegen<out T : BaseExpressionCodegen>(
return result
}
abstract fun extractDefaultLambdas(node: MethodNode): List<DefaultLambda>
protected inline fun <T> extractDefaultLambdas(
node: MethodNode, parameters: Map<Int, T>, block: ExtractedDefaultLambda.(T) -> DefaultLambda
): List<DefaultLambda> = expandMaskConditionsAndUpdateVariableNodes(
node, maskStartIndex, maskValues, methodHandleInDefaultMethodIndex, parameters.keys
).map {
it.block(parameters[it.offset]!!)
}
private fun generateAndInsertFinallyBlocks(
intoNode: MethodNode,
insertPoints: List<MethodInliner.PointForExternalFinallyBlocks>,
@@ -184,9 +184,6 @@ abstract class InlineCodegen<out T : BaseExpressionCodegen>(
val splitBy = SimpleInterval(start.info as LabelNode, extension.finallyIntervalEnd)
processor.tryBlocksMetaInfo.splitAndRemoveCurrentIntervals(splitBy, true)
processor.localVarsMetaInfo.splitAndRemoveCurrentIntervals(splitBy, true)
finallyNode.localVariables.forEach {
processor.localVarsMetaInfo.addNewInterval(LocalVarNodeWrapper(it))
}
}
curInstr = curInstr.next
@@ -196,7 +193,7 @@ abstract class InlineCodegen<out T : BaseExpressionCodegen>(
processor.substituteLocalVarTable(intoNode)
}
protected abstract fun generateAssertField()
protected abstract fun generateAssertFieldIfNeeded(info: RootInliningContext)
private fun isInlinedToInlineFunInKotlinRuntime(): Boolean {
val codegen = this.codegen as? ExpressionCodegen ?: return false
@@ -210,7 +207,9 @@ abstract class InlineCodegen<out T : BaseExpressionCodegen>(
}
protected fun rememberClosure(parameterType: Type, index: Int, lambdaInfo: LambdaInfo) {
invocationParamBuilder.addNextValueParameter(parameterType, true, null, index).functionalArgument = lambdaInfo
val closureInfo = invocationParamBuilder.addNextValueParameter(parameterType, true, null, index)
closureInfo.functionalArgument = lambdaInfo
expressionMap[closureInfo.index] = lambdaInfo
}
protected fun putCapturedToLocalVal(stackValue: StackValue, capturedParam: CapturedParamDesc, kotlinType: KotlinType?) {
@@ -237,12 +236,10 @@ abstract class InlineCodegen<out T : BaseExpressionCodegen>(
NonInlineableArgumentForInlineableParameterCalledInSuspend
ValueKind.NON_INLINEABLE_ARGUMENT_FOR_INLINE_SUSPEND_PARAMETER ->
NonInlineableArgumentForInlineableSuspendParameter
ValueKind.DEFAULT_INLINE_PARAMETER ->
DefaultValueOfInlineParameter
else -> null
}
when {
kind === ValueKind.DEFAULT_PARAMETER || kind === ValueKind.DEFAULT_INLINE_PARAMETER ->
kind === ValueKind.DEFAULT_PARAMETER ->
codegen.frameMap.enterTemp(info.type) // the inline function will put the value into this slot
stackValue.isLocalWithNoBoxing(jvmKotlinType) ->
info.remapValue = stackValue
@@ -261,6 +258,14 @@ abstract class InlineCodegen<out T : BaseExpressionCodegen>(
}
}
private fun rememberCapturedForDefaultLambda(defaultLambda: DefaultLambda) {
for (captured in defaultLambda.capturedVars) {
val info = invocationParamBuilder.addCapturedParam(captured, captured.fieldName, false)
info.remapValue = StackValue.local(codegen.frameMap.enterTemp(info.type), info.type)
info.isSynthetic = true
}
}
private fun processDefaultMaskOrMethodHandler(value: StackValue, kind: ValueKind) {
assert(value is StackValue.Constant) { "Additional default method argument should be constant, but $value" }
val constantValue = (value as StackValue.Constant).value

View File

@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.codegen.ClassBuilder
import org.jetbrains.kotlin.codegen.state.GenerationState
class RootInliningContext(
expressionMap: Map<Int, FunctionalArgument>,
state: GenerationState,
nameGenerator: NameGenerator,
val sourceCompilerForInline: SourceCompilerForInline,
@@ -16,11 +17,12 @@ class RootInliningContext(
val inlineMethodReifier: ReifiedTypeInliner<*>,
typeParameterMappings: TypeParameterMappings<*>
) : InliningContext(
null, state, nameGenerator, TypeRemapper.createRoot(typeParameterMappings), null, false
null, expressionMap, state, nameGenerator, TypeRemapper.createRoot(typeParameterMappings), null, false
)
class RegeneratedClassContext(
parent: InliningContext,
expressionMap: Map<Int, FunctionalArgument>,
state: GenerationState,
nameGenerator: NameGenerator,
typeRemapper: TypeRemapper,
@@ -28,29 +30,22 @@ class RegeneratedClassContext(
override val callSiteInfo: InlineCallSiteInfo,
override val transformationInfo: TransformationInfo
) : InliningContext(
parent, state, nameGenerator, typeRemapper, lambdaInfo, true
parent, expressionMap, state, nameGenerator, typeRemapper, lambdaInfo, true
) {
val continuationBuilders: MutableMap<String, ClassBuilder> = hashMapOf()
}
open class InliningContext(
val parent: InliningContext?,
val expressionMap: Map<Int, FunctionalArgument>,
val state: GenerationState,
val nameGenerator: NameGenerator,
val typeRemapper: TypeRemapper,
val lambdaInfo: LambdaInfo?,
val classRegeneration: Boolean
) {
val isInliningLambda
get() = lambdaInfo != null
// Consider this arrangement:
// inline fun <reified T> f(x: () -> Unit = { /* uses `T` in a local class */ }) = x()
// inline fun <reified V> g() = f<...> { /* uses `V` in a local class */ }
// When inlining `f` into `g`, we need to reify the contents of the default for `x` (if it was used), but not the
// contents of the lambda passed as the argument in `g` as all reified type parameters used by the latter are not from `f`.
val shouldReifyTypeParametersInObjects: Boolean
get() = lambdaInfo == null || lambdaInfo is DefaultLambda
val isInliningLambda = lambdaInfo != null
var generateAssertField = false
@@ -59,8 +54,7 @@ open class InliningContext(
var isContinuation: Boolean = false
val isRoot: Boolean
get() = parent == null
val isRoot: Boolean = parent == null
val root: RootInliningContext
get() = if (isRoot) this as RootInliningContext else parent!!.root
@@ -94,7 +88,7 @@ open class InliningContext(
callSiteInfo: InlineCallSiteInfo,
transformationInfo: TransformationInfo
): InliningContext = RegeneratedClassContext(
this, state, generator, TypeRemapper.createFrom(typeRemapper, newTypeMappings),
this, expressionMap, state, generator, TypeRemapper.createFrom(typeRemapper, newTypeMappings),
lambdaInfo, callSiteInfo, transformationInfo
)
@@ -107,7 +101,7 @@ open class InliningContext(
): InliningContext {
val isInliningLambda = lambdaInfo != null
return InliningContext(
this, state, generator,
this, expressionMap, state, generator,
TypeRemapper.createFrom(
typeRemapper,
additionalTypeMappings,

View File

@@ -1,386 +0,0 @@
/*
* 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.codegen.inline
import org.jetbrains.kotlin.codegen.optimization.boxing.isMethodInsnWith
import org.jetbrains.kotlin.codegen.optimization.common.InsnSequence
import org.jetbrains.kotlin.codegen.optimization.common.removeUnusedLocalVariables
import org.jetbrains.kotlin.codegen.optimization.common.updateMaxStack
import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer
import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.tree.*
class InplaceArgumentsMethodTransformer : MethodTransformer() {
override fun transform(internalClassName: String, methodNode: MethodNode) {
val methodContext = parseMethodOrNull(methodNode)
if (methodContext != null) {
if (methodContext.calls.isEmpty()) return
collectStartToEnd(methodContext)
collectLvtEntryInstructions(methodContext)
collectSuspensionPoints(methodContext)
transformMethod(methodContext)
updateLvtEntriesForMovedInstructions(methodContext)
methodNode.removeUnusedLocalVariables()
methodNode.updateMaxStack()
}
stripMarkers(methodNode)
}
private class MethodContext(
val methodNode: MethodNode,
val calls: List<CallContext>
) {
val startArgToEndArg = HashMap<AbstractInsnNode, AbstractInsnNode>()
val lvtEntryForInstruction = HashMap<AbstractInsnNode, LocalVariableNode>()
val varInstructionMoved = HashMap<AbstractInsnNode, CallContext>()
val suspensionJumpLabels = HashSet<LabelNode>()
}
private class CallContext(
val callStartMarker: AbstractInsnNode,
val callEndMarker: AbstractInsnNode,
val args: List<ArgContext>,
val calls: List<CallContext>,
val endLabel: LabelNode
)
private class ArgContext(
val argStartMarker: AbstractInsnNode,
val argEndMarker: AbstractInsnNode,
val calls: List<CallContext>,
val storeInsn: VarInsnNode
) {
val loadOpcode = storeInsn.opcode - Opcodes.ISTORE + Opcodes.ILOAD
val varIndex = storeInsn.`var`
}
private fun parseMethodOrNull(methodNode: MethodNode): MethodContext? {
// We assume that the method body structure follows this grammar:
// METHOD ::= insn* (CALL insn*)*
// CALL ::= callStartMarker insn* (ARG insn*)* (CALL insn*)* callEndMarker
// ARG ::= argStartMarker insn* (CALL insn*)* argEndMarker storeInsn
val iter = methodNode.instructions.iterator()
val calls = ArrayList<CallContext>()
try {
while (iter.hasNext()) {
val insn = iter.next()
when {
insn.isInplaceCallStartMarker() ->
calls.add(parseCall(methodNode, insn, iter))
insn.isInplaceCallEndMarker() || insn.isInplaceArgumentStartMarker() || insn.isInplaceArgumentEndMarker() ->
throw ParseErrorException()
}
}
} catch (e: ParseErrorException) {
return null
}
return MethodContext(methodNode, calls)
}
private fun parseCall(methodNode: MethodNode, start: AbstractInsnNode, iter: ListIterator<AbstractInsnNode>): CallContext {
// CALL ::= callStartMarker insn* (ARG insn*)* (CALL insn*)* callEndMarker
val args = ArrayList<ArgContext>()
val calls = ArrayList<CallContext>()
while (iter.hasNext()) {
val insn = iter.next()
when {
insn.isInplaceCallStartMarker() ->
calls.add(parseCall(methodNode, insn, iter))
insn.isInplaceCallEndMarker() -> {
val previous = insn.previous
val endLabel =
if (previous.type == AbstractInsnNode.LABEL)
previous as LabelNode
else
LabelNode(Label()).also {
// Make sure each call with inplace arguments has an endLabel
// (we need it to update LVT after transformation).
methodNode.instructions.insertBefore(insn, it)
}
return CallContext(start, insn, args, calls, endLabel)
}
insn.isInplaceArgumentStartMarker() ->
args.add(parseArg(methodNode, insn, iter))
insn.isInplaceArgumentEndMarker() ->
throw ParseErrorException()
}
}
// Reached instruction list end, didn't find inplace-call-end marker
throw ParseErrorException()
}
private fun parseArg(methodNode: MethodNode, start: AbstractInsnNode, iter: ListIterator<AbstractInsnNode>): ArgContext {
// ARG ::= argStartMarker insn* (CALL insn*)* argEndMarker storeInsn
val calls = ArrayList<CallContext>()
while (iter.hasNext()) {
val insn = iter.next()
when {
insn.isInplaceCallStartMarker() ->
calls.add(parseCall(methodNode, insn, iter))
insn.isInplaceArgumentEndMarker() -> {
val next = insn.next
if (next is VarInsnNode && next.opcode in Opcodes.ISTORE..Opcodes.ASTORE) {
iter.next()
return ArgContext(start, insn, calls, next)
} else {
throw ParseErrorException()
}
}
insn.isInplaceCallEndMarker() || insn.isInplaceArgumentStartMarker() ->
throw ParseErrorException()
}
}
// Reached instruction list end, didn't find inplace-argument-end marker
throw ParseErrorException()
}
private class ParseErrorException : RuntimeException() {
override fun fillInStackTrace(): Throwable = this
}
private fun collectStartToEnd(methodContext: MethodContext) {
for (call in methodContext.calls) {
collectStartToEnd(methodContext, call)
}
}
private fun collectStartToEnd(methodContext: MethodContext, callContext: CallContext) {
for (arg in callContext.args) {
collectStartToEnd(methodContext, arg)
}
for (call in callContext.calls) {
collectStartToEnd(methodContext, call)
}
}
private fun collectStartToEnd(methodContext: MethodContext, argContext: ArgContext) {
methodContext.startArgToEndArg[argContext.argStartMarker] = argContext.argEndMarker
for (call in argContext.calls) {
collectStartToEnd(methodContext, call)
}
}
private fun collectLvtEntryInstructions(methodContext: MethodContext) {
val insnList = methodContext.methodNode.instructions
val insnArray = insnList.toArray()
for (lv in methodContext.methodNode.localVariables) {
val lvStartIndex = insnList.indexOf(lv.start)
val lvEndIndex = insnList.indexOf(lv.end)
for (i in lvStartIndex until lvEndIndex) {
val insn = insnArray[i]
if (insn.opcode in Opcodes.ILOAD..Opcodes.ALOAD || insn.opcode in Opcodes.ISTORE..Opcodes.ASTORE) {
if ((insn as VarInsnNode).`var` == lv.index) {
methodContext.lvtEntryForInstruction[insn] = lv
}
} else if (insn.opcode == Opcodes.IINC) {
if ((insn as IincInsnNode).`var` == lv.index) {
methodContext.lvtEntryForInstruction[insn] = lv
}
}
}
}
}
private fun collectSuspensionPoints(methodContext: MethodContext) {
val insnList = methodContext.methodNode.instructions
var insn = insnList.first
while (
!insn.isMethodInsnWith(Opcodes.INVOKESTATIC) {
owner == "kotlin/coroutines/intrinsics/IntrinsicsKt" &&
name == "getCOROUTINE_SUSPENDED" &&
desc == "()Ljava/lang/Object;"
}
) {
insn = insn.next ?: return
}
// Find a first TABLESWITCH and record its jump destinations
while (insn != null) {
if (insn.opcode != Opcodes.TABLESWITCH || insn.previous.opcode != Opcodes.GETFIELD) {
insn = insn.next
continue
}
val getFiendInsn = insn.previous as FieldInsnNode
if (getFiendInsn.name != "label" || getFiendInsn.desc != "I") {
insn = insn.next
continue
}
val tableSwitchInsn = insn as TableSwitchInsnNode
methodContext.suspensionJumpLabels.addAll(tableSwitchInsn.labels)
methodContext.suspensionJumpLabels.add(tableSwitchInsn.dflt)
return
}
}
private fun transformMethod(methodContext: MethodContext) {
for (call in methodContext.calls) {
transformCall(methodContext, call)
}
}
private fun transformCall(methodContext: MethodContext, callContext: CallContext) {
// Transform nested calls
for (arg in callContext.args) {
for (nestedCall in arg.calls) {
transformCall(methodContext, nestedCall)
}
}
for (call in callContext.calls) {
transformCall(methodContext, call)
}
// If an inplace argument contains a non-local jump,
// moving such argument inside inline function body can interfere with stack normalization.
// TODO investigate complex cases
if (callContext.args.any { it.isUnsafeToMove(methodContext) }) {
// Do not transform such call, just strip call and argument markers.
val insnList = methodContext.methodNode.instructions
for (arg in callContext.args) {
insnList.remove(arg.argStartMarker)
insnList.remove(arg.argEndMarker)
}
insnList.remove(callContext.callStartMarker)
insnList.remove(callContext.callEndMarker)
return
}
moveInplaceArgumentsFromStoresToLoads(methodContext, callContext)
}
private fun ArgContext.isUnsafeToMove(methodContext: MethodContext): Boolean {
val argInsns = InsnSequence(this.argStartMarker, this.argEndMarker)
val localLabels = argInsns.filterTo(HashSet()) { it is LabelNode }
return argInsns.any { insn ->
insn in methodContext.suspensionJumpLabels ||
insn.opcode == Opcodes.GOTO && (insn as JumpInsnNode).label !in localLabels
}
}
private fun moveInplaceArgumentsFromStoresToLoads(methodContext: MethodContext, callContext: CallContext) {
// Transform call
val insnList = methodContext.methodNode.instructions
val args = callContext.args.associateBy { it.varIndex }
var argsProcessed = 0
var insn: AbstractInsnNode = callContext.callStartMarker
while (insn != callContext.callEndMarker) {
when {
insn.isInplaceArgumentStartMarker() -> {
// Skip argument body
insn = methodContext.startArgToEndArg[insn]!!
}
insn.opcode in Opcodes.ILOAD..Opcodes.ALOAD -> {
// Load instruction
val loadInsn = insn as VarInsnNode
val varIndex = loadInsn.`var`
val arg = args[varIndex]
if (arg == null || arg.loadOpcode != insn.opcode) {
// Not an argument load
insn = insn.next
} else {
// For each argument within this call we have
// <inplaceArgStartMarker>
// <argumentBody>
// <inplaceArgEndMarker>
// store [arg]
// ...
// load [arg]
// Replace 'load [arg]' with '<argumentBody>', drop 'store [arg]' and argument markers.
var argInsn = arg.argStartMarker.next
while (argInsn != arg.argEndMarker) {
// If a LOAD/STORE/IINC instruction was moved,
// record it so that we can update corresponding LVT entry if needed.
// NB it's better to do so after all transformations, so that we don't recalculate node indices.
if (argInsn.opcode in Opcodes.ILOAD..Opcodes.ALOAD ||
argInsn.opcode in Opcodes.ISTORE..Opcodes.ASTORE ||
argInsn.opcode == Opcodes.IINC
) {
methodContext.varInstructionMoved[argInsn] = callContext
}
val argInsnNext = argInsn.next
insnList.remove(argInsn)
insnList.insertBefore(loadInsn, argInsn)
argInsn = argInsnNext
}
// Remove argument load and corresponding argument store instructions
insnList.remove(arg.storeInsn)
insn = loadInsn.next
insnList.remove(loadInsn)
// Replace subsequent argument loads with DUP instructions of appropriate size
while (insn.opcode == loadInsn.opcode && (insn as VarInsnNode).`var` == varIndex) {
if (insn.opcode == Opcodes.LLOAD || insn.opcode == Opcodes.DLOAD) {
insnList.insertBefore(insn, InsnNode(Opcodes.DUP2))
} else {
insnList.insertBefore(insn, InsnNode(Opcodes.DUP))
}
val next = insn.next
insnList.remove(insn)
insn = next
}
// Remove argument markers
insnList.remove(arg.argStartMarker)
insnList.remove(arg.argEndMarker)
// If there are no more inplace arguments left to process, we are done
++argsProcessed
if (argsProcessed >= callContext.args.size)
break
}
}
else ->
insn = insn.next
}
}
// Remove call start and call end markers
insnList.remove(callContext.callStartMarker)
insnList.remove(callContext.callEndMarker)
}
private fun updateLvtEntriesForMovedInstructions(methodContext: MethodContext) {
val insnList = methodContext.methodNode.instructions
for ((insn, callContext) in methodContext.varInstructionMoved.entries) {
// Extend local variable interval to call end label if needed
val lv = methodContext.lvtEntryForInstruction[insn] ?: continue
val lvEndIndex = insnList.indexOf(lv.end)
val endLabelIndex = insnList.indexOf(callContext.endLabel)
if (endLabelIndex > lvEndIndex) {
lv.end = callContext.endLabel
}
}
}
private fun stripMarkers(methodNode: MethodNode) {
var insn = methodNode.instructions.first
while (insn != null) {
if (insn.isInplaceCallStartMarker() ||
insn.isInplaceCallEndMarker() ||
insn.isInplaceArgumentStartMarker() ||
insn.isInplaceArgumentEndMarker()
) {
val next = insn.next
methodNode.instructions.remove(insn)
insn = next
continue
}
insn = insn.next
}
}
}

View File

@@ -6,7 +6,6 @@
package org.jetbrains.kotlin.codegen.inline
import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.codegen.coroutines.isCoroutineSuperClass
import org.jetbrains.kotlin.resolve.jvm.AsmTypes.*
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.org.objectweb.asm.ClassReader
@@ -17,7 +16,12 @@ import org.jetbrains.org.objectweb.asm.tree.FieldInsnNode
interface FunctionalArgument
abstract class LambdaInfo : FunctionalArgument {
abstract class LambdaInfo(@JvmField val isCrossInline: Boolean) : FunctionalArgument {
abstract val isBoundCallableReference: Boolean
abstract val isSuspend: Boolean
abstract val lambdaClassType: Type
abstract val invokeMethod: Method
@@ -35,17 +39,11 @@ abstract class LambdaInfo : FunctionalArgument {
val reifiedTypeParametersUsages = ReifiedTypeParametersUsages()
open val hasDispatchReceiver
get() = true
open val hasDispatchReceiver = true
fun addAllParameters(remapper: FieldRemapper): Parameters {
val builder = ParametersBuilder.newBuilder()
if (hasDispatchReceiver) {
builder.addThis(lambdaClassType, skipped = true).functionalArgument = this
}
for (type in Type.getArgumentTypes(invokeMethod.descriptor)) {
builder.addNextParameter(type, skipped = false)
}
val builder = ParametersBuilder.initializeBuilderFrom(OBJECT_TYPE, invokeMethod.descriptor, this)
for (info in capturedVars) {
val field = remapper.findField(FieldInsnNode(0, info.containingLambdaName, info.fieldName, ""))
?: error("Captured field not found: " + info.containingLambdaName + "." + info.fieldName)
@@ -67,73 +65,76 @@ abstract class LambdaInfo : FunctionalArgument {
object NonInlineableArgumentForInlineableParameterCalledInSuspend : FunctionalArgument
object NonInlineableArgumentForInlineableSuspendParameter : FunctionalArgument
object DefaultValueOfInlineParameter : FunctionalArgument
abstract class ExpressionLambda : LambdaInfo() {
abstract class ExpressionLambda(isCrossInline: Boolean) : LambdaInfo(isCrossInline) {
fun generateLambdaBody(sourceCompiler: SourceCompilerForInline) {
node = sourceCompiler.generateLambdaBody(this, reifiedTypeParametersUsages)
node.node.preprocessSuspendMarkers(forInline = true, keepFakeContinuation = false)
}
}
class DefaultLambda(info: ExtractedDefaultLambda, sourceCompiler: SourceCompilerForInline) : LambdaInfo() {
val isBoundCallableReference: Boolean
abstract class DefaultLambda(
final override val lambdaClassType: Type,
capturedArgs: Array<Type>,
isCrossinline: Boolean,
val offset: Int,
val needReification: Boolean,
sourceCompiler: SourceCompilerForInline
) : LambdaInfo(isCrossinline) {
final override val isSuspend
get() = false // TODO: it should probably be true sometimes, but it never was
final override val isBoundCallableReference: Boolean
final override val capturedVars: List<CapturedParamDesc>
override val lambdaClassType: Type = info.type
override val capturedVars: List<CapturedParamDesc>
override val invokeMethod: Method
final override val invokeMethod: Method
get() = Method(node.node.name, node.node.desc)
private val nullableAnyType = sourceCompiler.state.module.builtIns.nullableAnyType
override val invokeMethodParameters: List<KotlinType>
get() = List(invokeMethod.argumentTypes.size) { nullableAnyType }
override val invokeMethodReturnType: KotlinType
get() = nullableAnyType
val originalBoundReceiverType: Type?
protected val isPropertyReference: Boolean
protected val isFunctionReference: Boolean
init {
val classBytes =
sourceCompiler.state.inlineCache.classBytes.getOrPut(lambdaClassType.internalName) {
loadClassBytesByInternalName(sourceCompiler.state, lambdaClassType.internalName)
}
val classBytes = loadClass(sourceCompiler)
val superName = ClassReader(classBytes).superName
// TODO: suspend lambdas are their own continuations, so the body is pre-inlined into `invokeSuspend`
// and thus can't be detangled from the state machine. To make them inlinable, this needs to be redesigned.
// See `SuspendLambdaLowering`.
require(!superName.isCoroutineSuperClass()) {
"suspend default lambda ${lambdaClassType.internalName} cannot be inlined; use a function reference instead"
}
isPropertyReference = superName in PROPERTY_REFERENCE_SUPER_CLASSES
isFunctionReference = superName == FUNCTION_REFERENCE.internalName || superName == FUNCTION_REFERENCE_IMPL.internalName
val constructorMethod = Method("<init>", Type.VOID_TYPE, info.capturedArgs)
val constructor = getMethodNode(classBytes, lambdaClassType, constructorMethod)?.node
assert(constructor != null || info.capturedArgs.isEmpty()) {
"can't find constructor '$constructorMethod' for default lambda '${lambdaClassType.internalName}'"
val constructorDescriptor = Type.getMethodDescriptor(Type.VOID_TYPE, *capturedArgs)
val constructor = getMethodNode(classBytes, "<init>", constructorDescriptor, lambdaClassType)?.node
assert(constructor != null || capturedArgs.isEmpty()) {
"Can't find non-default constructor <init>$constructorDescriptor for default lambda $lambdaClassType"
}
val isPropertyReference = superName in PROPERTY_REFERENCE_SUPER_CLASSES
val isReference = isPropertyReference ||
superName == FUNCTION_REFERENCE.internalName || superName == FUNCTION_REFERENCE_IMPL.internalName
// This only works for primitives but not inline classes, since information about the Kotlin type of the bound
// receiver is not present anywhere. This is why with JVM_IR the constructor argument of bound references
// is already `Object`, and this field is never used.
originalBoundReceiverType =
info.capturedArgs.singleOrNull()?.takeIf { isReference && AsmUtil.isPrimitive(it) }
capturedArgs.singleOrNull()?.takeIf { (isFunctionReference || isPropertyReference) && AsmUtil.isPrimitive(it) }
capturedVars =
if (isReference)
info.capturedArgs.singleOrNull()?.let {
// See `InlinedLambdaRemapper`
listOf(capturedParamDesc(AsmUtil.RECEIVER_PARAMETER_NAME, OBJECT_TYPE, isSuspend = false))
if (isFunctionReference || isPropertyReference)
capturedArgs.singleOrNull()?.let {
listOf(capturedParamDesc(AsmUtil.RECEIVER_PARAMETER_NAME, AsmUtil.boxType(it), isSuspend = false))
} ?: emptyList()
else
constructor?.findCapturedFieldAssignmentInstructions()?.map { fieldNode ->
capturedParamDesc(fieldNode.name, Type.getType(fieldNode.desc), isSuspend = false)
}?.toList() ?: emptyList()
isBoundCallableReference = isReference && capturedVars.isNotEmpty()
node = loadDefaultLambdaBody(classBytes, lambdaClassType, isPropertyReference)
isBoundCallableReference = (isFunctionReference || isPropertyReference) && capturedVars.isNotEmpty()
}
private fun loadClass(sourceCompiler: SourceCompilerForInline): ByteArray =
sourceCompiler.state.inlineCache.classBytes.getOrPut(lambdaClassType.internalName) {
loadClassBytesByInternalName(sourceCompiler.state, lambdaClassType.internalName)
}
// Returns whether the loaded invoke is erased, i.e. the name equals the fallback and all types are `Object`.
protected fun loadInvoke(sourceCompiler: SourceCompilerForInline, erasedName: String, actualMethod: Method): Boolean {
val classBytes = loadClass(sourceCompiler)
// TODO: `signatureAmbiguity = true` ignores the argument types from `invokeMethod` and only looks at the count.
node = getMethodNode(classBytes, actualMethod.name, actualMethod.descriptor, lambdaClassType, signatureAmbiguity = true)
?: getMethodNode(classBytes, erasedName, actualMethod.descriptor, lambdaClassType, signatureAmbiguity = true)
?: error("Can't find method '$actualMethod' in '${lambdaClassType.internalName}'")
return invokeMethod.run { name == erasedName && returnType == OBJECT_TYPE && argumentTypes.all { it == OBJECT_TYPE } }
}
private companion object {

View File

@@ -6,15 +6,19 @@
package org.jetbrains.kotlin.codegen.inline
import org.jetbrains.kotlin.codegen.*
import org.jetbrains.kotlin.codegen.coroutines.CONTINUATION_ASM_TYPE
import org.jetbrains.kotlin.codegen.coroutines.continuationAsmType
import org.jetbrains.kotlin.codegen.inline.FieldRemapper.Companion.foldName
import org.jetbrains.kotlin.codegen.inline.coroutines.CoroutineTransformer
import org.jetbrains.kotlin.codegen.inline.coroutines.markNoinlineLambdaIfSuspend
import org.jetbrains.kotlin.codegen.inline.coroutines.surroundInvokesWithSuspendMarkersIfNeeded
import org.jetbrains.kotlin.codegen.optimization.ApiVersionCallsPreprocessingMethodTransformer
import org.jetbrains.kotlin.codegen.optimization.FixStackWithLabelNormalizationMethodTransformer
import org.jetbrains.kotlin.codegen.optimization.common.*
import org.jetbrains.kotlin.codegen.optimization.fixStack.*
import org.jetbrains.kotlin.codegen.optimization.common.ControlFlowGraph
import org.jetbrains.kotlin.codegen.optimization.common.InsnSequence
import org.jetbrains.kotlin.codegen.optimization.common.asSequence
import org.jetbrains.kotlin.codegen.optimization.common.isMeaningful
import org.jetbrains.kotlin.codegen.optimization.fixStack.peek
import org.jetbrains.kotlin.codegen.optimization.fixStack.top
import org.jetbrains.kotlin.codegen.optimization.nullCheck.isCheckParameterIsNotNull
import org.jetbrains.kotlin.codegen.pseudoInsns.PseudoInsn
import org.jetbrains.kotlin.config.LanguageFeature
@@ -32,6 +36,8 @@ import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
import org.jetbrains.org.objectweb.asm.commons.LocalVariablesSorter
import org.jetbrains.org.objectweb.asm.commons.MethodRemapper
import org.jetbrains.org.objectweb.asm.tree.*
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicInterpreter
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue
import org.jetbrains.org.objectweb.asm.tree.analysis.Frame
import org.jetbrains.org.objectweb.asm.util.Printer
import java.util.*
@@ -51,10 +57,8 @@ class MethodInliner(
) {
private val languageVersionSettings = inliningContext.state.languageVersionSettings
private val invokeCalls = ArrayList<InvokeCall>()
//keeps order
private val transformations = ArrayList<TransformationInfo>()
//current state
private val currentTypeMapping = HashMap<String, String?>()
private val result = InlineResult.create()
@@ -88,6 +92,10 @@ class MethodInliner(
): InlineResult {
//analyze body
var transformedNode = markPlacesForInlineAndRemoveInlinable(node, returnLabels, finallyDeepShift)
if (inliningContext.isInliningLambda && isDefaultLambdaWithReification(inliningContext.lambdaInfo!!)) {
//TODO maybe move reification in one place
inliningContext.root.inlineMethodReifier.reifyInstructions(transformedNode)
}
//substitute returns with "goto end" instruction to keep non local returns in lambdas
val end = linkedLabel()
@@ -224,7 +232,7 @@ class MethodInliner(
val expectedParameters = info.invokeMethod.argumentTypes
val expectedKotlinParameters = info.invokeMethodParameters
val argumentCount = Type.getArgumentTypes(desc).size.let {
if (info is PsiExpressionLambda && info.invokeMethodDescriptor.isSuspend && it < expectedParameters.size) {
if (!inliningContext.root.state.isIrBackend && info.isSuspend && it < expectedParameters.size) {
// Inlining suspend lambda into a function that takes a non-suspend lambda.
// In the IR backend, this cannot happen as inline lambdas are not lowered.
addFakeContinuationMarker(this)
@@ -269,8 +277,7 @@ class MethodInliner(
val varRemapper = LocalVarRemapper(lambdaParameters, valueParamShift)
//TODO add skipped this and receiver
val lambdaResult =
inliner.doInline(localVariablesSorter, varRemapper, true, info.returnLabels, invokeCall.finallyDepthShift)
val lambdaResult = inliner.doInline(localVariablesSorter, varRemapper, true, info.returnLabels, invokeCall.finallyDepthShift)
result.mergeWithNotChangeInfo(lambdaResult)
result.reifiedTypeParametersUsages.mergeAll(lambdaResult.reifiedTypeParametersUsages)
result.reifiedTypeParametersUsages.mergeAll(info.reifiedTypeParametersUsages)
@@ -300,7 +307,7 @@ class MethodInliner(
} else capturedParamDesc
visitFieldInsn(
Opcodes.GETSTATIC, realDesc.containingLambdaName,
foldName(realDesc.fieldName), realDesc.type.descriptor
FieldRemapper.foldName(realDesc.fieldName), realDesc.type.descriptor
)
}
super.visitMethodInsn(opcode, info.newClassName, name, info.newConstructorDescriptor, itf)
@@ -314,11 +321,10 @@ class MethodInliner(
} else {
super.visitMethodInsn(opcode, owner, name, desc, itf)
}
} else if (ReifiedTypeInliner.isNeedClassReificationMarker(MethodInsnNode(opcode, owner, name, desc, false))) {
// If objects are reified, the marker will be recreated by `handleAnonymousObjectRegeneration` above.
if (!inliningContext.shouldReifyTypeParametersInObjects) {
super.visitMethodInsn(opcode, owner, name, desc, itf)
}
} else if ((!inliningContext.isInliningLambda || isDefaultLambdaWithReification(inliningContext.lambdaInfo!!)) &&
ReifiedTypeInliner.isNeedClassReificationMarker(MethodInsnNode(opcode, owner, name, desc, false))
) {
//we shouldn't process here content of inlining lambda it should be reified at external level except default lambdas
} else {
super.visitMethodInsn(opcode, owner, name, desc, itf)
}
@@ -343,6 +349,9 @@ class MethodInliner(
return resultNode
}
private fun isDefaultLambdaWithReification(lambdaInfo: LambdaInfo) =
lambdaInfo is DefaultLambda && lambdaInfo.needReification
private fun prepareNode(node: MethodNode, finallyDeepShift: Int): MethodNode {
node.instructions.resetLabels()
@@ -372,7 +381,10 @@ class MethodInliner(
private fun getNewIndex(`var`: Int): Int {
val lambdaInfo = inliningContext.lambdaInfo
if (reorderIrLambdaParameters && lambdaInfo is IrExpressionLambda) {
val extensionSize = if (lambdaInfo.isExtensionLambda) lambdaInfo.invokeMethod.argumentTypes[0].size else 0
val extensionSize =
if (lambdaInfo.isExtensionLambda && !lambdaInfo.isBoundCallableReference)
lambdaInfo.invokeMethod.argumentTypes[0].size
else 0
return when {
// v-- extensionSize v-- argsSizeOnStack
// |- extension -|- captured -|- real -|- locals -| old descriptor
@@ -438,7 +450,7 @@ class MethodInliner(
else -> ""
}
val varName = if (varSuffix.isNotEmpty() && name == AsmUtil.THIS) AsmUtil.INLINE_DECLARATION_SITE_THIS else name
val varName = if (!varSuffix.isEmpty() && name == AsmUtil.THIS) AsmUtil.INLINE_DECLARATION_SITE_THIS else name
super.visitLocalVariable(varName + varSuffix, desc, signature, start, end, getNewIndex(index))
}
}
@@ -530,36 +542,36 @@ class MethodInliner(
cur.opcode == Opcodes.GETSTATIC -> {
val fieldInsnNode = cur as FieldInsnNode?
val className = fieldInsnNode!!.owner
when {
isAnonymousSingletonLoad(className, fieldInsnNode.name) -> {
recordTransformation(
AnonymousObjectTransformationInfo(
className, awaitClassReification, isAlreadyRegenerated(className), true,
inliningContext.nameGenerator
)
if (isAnonymousSingletonLoad(className, fieldInsnNode.name)) {
recordTransformation(
AnonymousObjectTransformationInfo(
className, awaitClassReification, isAlreadyRegenerated(className), true,
inliningContext.nameGenerator
)
awaitClassReification = false
}
isWhenMappingAccess(className, fieldInsnNode.name) -> {
recordTransformation(
WhenMappingTransformationInfo(
className, inliningContext.nameGenerator, isAlreadyRegenerated(className), fieldInsnNode
)
)
awaitClassReification = false
} else if (isWhenMappingAccess(className, fieldInsnNode.name)) {
recordTransformation(
WhenMappingTransformationInfo(
className, inliningContext.nameGenerator, isAlreadyRegenerated(className), fieldInsnNode
)
}
fieldInsnNode.isCheckAssertionsStatus() -> {
fieldInsnNode.owner = inlineCallSiteInfo.ownerClassName
when {
// In inline function itself:
inliningContext.parent == null -> inliningContext
// In method of regenerated object - field should already exist:
inliningContext.parent is RegeneratedClassContext -> inliningContext.parent
// In lambda inlined into the root function:
inliningContext.parent.parent == null -> inliningContext.parent
// In lambda inlined into a method of a regenerated object:
else -> inliningContext.parent.parent as? RegeneratedClassContext
?: throw AssertionError("couldn't find class for \$assertionsDisabled (context = $inliningContext)")
}.generateAssertField = true
)
} else if (fieldInsnNode.isCheckAssertionsStatus()) {
fieldInsnNode.owner = inlineCallSiteInfo.ownerClassName
if (inliningContext.isInliningLambda) {
if (inliningContext.lambdaInfo!!.isCrossInline) {
assert(inliningContext.parent?.parent is RegeneratedClassContext) {
"$inliningContext grandparent shall be RegeneratedClassContext but got ${inliningContext.parent?.parent}"
}
inliningContext.parent!!.parent!!.generateAssertField = true
} else {
assert(inliningContext.parent != null) {
"$inliningContext parent shall not be null"
}
inliningContext.parent!!.generateAssertField = true
}
} else {
inliningContext.generateAssertField = true
}
}
}
@@ -589,7 +601,7 @@ class MethodInliner(
assert(lambdaInfo.lambdaClassType.internalName == nodeRemapper.originalLambdaInternalName) {
"Wrong bytecode template for contract template: ${lambdaInfo.lambdaClassType.internalName} != ${nodeRemapper.originalLambdaInternalName}"
}
fieldInsn.name = foldName(fieldInsn.name)
fieldInsn.name = FieldRemapper.foldName(fieldInsn.name)
fieldInsn.opcode = Opcodes.PUTSTATIC
toDelete.addAll(stackTransformations)
}
@@ -629,8 +641,9 @@ class MethodInliner(
private fun replaceContinuationAccessesWithFakeContinuationsIfNeeded(processingNode: MethodNode) {
// in ir backend inline suspend lambdas do not use ALOAD 0 to get continuation, since they are generated as static functions
// instead they get continuation from parameter.
if (inliningContext.state.isIrBackend) return
val lambdaInfo = inliningContext.lambdaInfo ?: return
if (lambdaInfo !is PsiExpressionLambda || !lambdaInfo.invokeMethodDescriptor.isSuspend) return
if (!lambdaInfo.isSuspend) return
val sources = analyzeMethodNodeWithInterpreter(processingNode, Aload0Interpreter(processingNode))
val cfg = ControlFlowGraph.build(processingNode)
val aload0s = processingNode.instructions.asSequence().filter { it.opcode == Opcodes.ALOAD && it.safeAs<VarInsnNode>()?.`var` == 0 }
@@ -688,7 +701,7 @@ class MethodInliner(
}
for ((index, param) in paramTypes.reversed().withIndex()) {
if (param != CONTINUATION_ASM_TYPE && param != OBJECT_TYPE) continue
if (param != languageVersionSettings.continuationAsmType() && param != OBJECT_TYPE) continue
val sourceIndices = (frame.getStack(frame.stackSize - index - 1) as? Aload0BasicValue)?.indices ?: continue
for (sourceIndex in sourceIndices) {
val src = processingNode.instructions[sourceIndex]
@@ -733,7 +746,6 @@ class MethodInliner(
private fun preprocessNodeBeforeInline(node: MethodNode, returnLabels: Map<String, Label?>) {
try {
InplaceArgumentsMethodTransformer().transform("fake", node)
FixStackWithLabelNormalizationMethodTransformer().transform("fake", node)
} catch (e: Throwable) {
throw wrapException(e, node, "couldn't inline method call")
@@ -744,9 +756,7 @@ class MethodInliner(
ApiVersionCallsPreprocessingMethodTransformer(targetApiVersion).transform("fake", node)
}
removeFakeVariablesInitializationIfPresent(node)
val frames = FastStackAnalyzer("<fake>", node, FixStackInterpreter()).analyze()
val frames = analyzeMethodNodeWithInterpreter(node, BasicInterpreter())
val localReturnsNormalizer = LocalReturnsNormalizer()
@@ -768,73 +778,6 @@ class MethodInliner(
localReturnsNormalizer.transform(node)
}
private fun removeFakeVariablesInitializationIfPresent(node: MethodNode) {
// Before 1.6, we generated fake variable initialization instructions
// ICONST_0
// ISTORE x
// for all inline functions. Original intent was to mark inline function body for the debugger with corresponding LVT entry.
// However, for @InlineOnly functions corresponding LVT entries were not copied (assuming that nobody is actually debugging
// @InlineOnly functions).
// Since 1.6, we no longer generate fake variables for @InlineOnly functions
// Here we erase fake variable initialization for @InlineOnly functions inlined into existing bytecode (e.g., inline function
// inside third-party library).
// We consider a sequence of instructions 'ICONST_0; ISTORE x' a fake variable initialization if the corresponding variable 'x'
// is not used in the bytecode (see below).
val insnArray = node.instructions.toArray()
// Very conservative variable usage check.
// Here we look at integer variables only (this includes integral primitive types: byte, char, short, boolean).
// Variable is considered "used" if:
// - it's loaded with ILOAD instruction
// - it's incremented with IINC instruction
// - there's a local variable table entry for this variable
val usedIntegerVar = BooleanArray(node.maxLocals)
for (insn in insnArray) {
if (insn.type == AbstractInsnNode.VAR_INSN && insn.opcode == Opcodes.ILOAD) {
usedIntegerVar[(insn as VarInsnNode).`var`] = true
} else if (insn.type == AbstractInsnNode.IINC_INSN) {
usedIntegerVar[(insn as IincInsnNode).`var`] = true
}
}
for (localVariable in node.localVariables) {
val d0 = localVariable.desc[0]
// byte || char || short || int || boolean
if (d0 == 'B' || d0 == 'C' || d0 == 'S' || d0 == 'I' || d0 == 'Z') {
usedIntegerVar[localVariable.index] = true
}
}
// Looking for sequences of instructions:
// p0: ICONST_0
// p1: ISTORE x
// p2: <label>
// If variable 'x' is not "used" (see above), remove p0 and p1 instructions.
var changes = false
for (p0 in insnArray) {
if (p0.opcode != Opcodes.ICONST_0) continue
val p1 = p0.next ?: break
if (p1.opcode != Opcodes.ISTORE) continue
val p2 = p1.next ?: break
if (p2.type != AbstractInsnNode.LABEL) continue
val varIndex = (p1 as VarInsnNode).`var`
if (!usedIntegerVar[varIndex]) {
changes = true
node.instructions.remove(p0)
node.instructions.remove(p1)
}
}
if (changes) {
// If we removed some instructions, some TCBs could (in theory) become empty.
// Remove empty TCBs if there are any.
node.removeEmptyCatchBlocks()
}
}
private fun isAnonymousClassThatMustBeRegenerated(type: Type?): Boolean {
if (type == null || type.sort != Type.OBJECT) return false
return inliningContext.isRegeneratedAnonymousObject(type.internalName)
@@ -902,9 +845,9 @@ class MethodInliner(
if (inliningContext.isInliningLambda && inliningContext.lambdaInfo is IrExpressionLambda && !inliningContext.parent!!.isInliningLambda) {
val capturedVars = inliningContext.lambdaInfo.capturedVars
var offset = parameters.realParametersSizeOnStack
val map = capturedVars.associate {
val map = capturedVars.map {
offset to it.also { offset += it.type.size }
}
}.toMap()
var cur: AbstractInsnNode? = node.instructions.first
while (cur != null) {
@@ -949,7 +892,6 @@ class MethodInliner(
}
}
@Suppress("SameParameterValue")
private fun wrapException(originalException: Throwable, node: MethodNode, errorSuffix: String): RuntimeException {
return if (originalException is InlineException) {
InlineException("$errorPrefix: $errorSuffix", originalException)
@@ -962,7 +904,7 @@ class MethodInliner(
private class LocalReturn(
private val returnInsn: AbstractInsnNode,
private val insertBeforeInsn: AbstractInsnNode,
private val frame: Frame<FixStackValue>
private val frame: Frame<BasicValue>
) {
fun transform(insnList: InsnList, returnVariableIndex: Int) {
@@ -973,19 +915,22 @@ class MethodInliner(
if (expectedStackSize == actualStackSize) return
var stackSize = actualStackSize
val topValue = frame.getStack(stackSize - 1)
if (isReturnWithValue) {
insnList.insertBefore(insertBeforeInsn, VarInsnNode(topValue.storeOpcode, returnVariableIndex))
val storeOpcode = Opcodes.ISTORE + returnInsn.opcode - Opcodes.IRETURN
insnList.insertBefore(insertBeforeInsn, VarInsnNode(storeOpcode, returnVariableIndex))
stackSize--
}
while (stackSize > 0) {
insnList.insertBefore(insertBeforeInsn, InsnNode(frame.getStack(stackSize - 1).popOpcode))
val stackElementSize = frame.getStack(stackSize - 1).size
val popOpcode = if (stackElementSize == 1) Opcodes.POP else Opcodes.POP2
insnList.insertBefore(insertBeforeInsn, InsnNode(popOpcode))
stackSize--
}
if (isReturnWithValue) {
insnList.insertBefore(insertBeforeInsn, VarInsnNode(topValue.loadOpcode, returnVariableIndex))
val loadOpcode = Opcodes.ILOAD + returnInsn.opcode - Opcodes.IRETURN
insnList.insertBefore(insertBeforeInsn, VarInsnNode(loadOpcode, returnVariableIndex))
}
}
}
@@ -995,10 +940,10 @@ class MethodInliner(
private var returnVariableSize = 0
private var returnOpcode = -1
fun addLocalReturnToTransform(
internal fun addLocalReturnToTransform(
returnInsn: AbstractInsnNode,
insertBeforeInsn: AbstractInsnNode,
sourceValueFrame: Frame<FixStackValue>
sourceValueFrame: Frame<BasicValue>
) {
assert(isReturnOpcode(returnInsn.opcode)) { "return instruction expected" }
assert(returnOpcode < 0 || returnOpcode == returnInsn.opcode) { "Return op should be " + Printer.OPCODES[returnOpcode] + ", got " + Printer.OPCODES[returnInsn.opcode] }
@@ -1007,7 +952,11 @@ class MethodInliner(
localReturns.add(LocalReturn(returnInsn, insertBeforeInsn, sourceValueFrame))
if (returnInsn.opcode != Opcodes.RETURN) {
returnVariableSize = if (returnInsn.opcode == Opcodes.LRETURN || returnInsn.opcode == Opcodes.DRETURN) 2 else 1
returnVariableSize = if (returnInsn.opcode == Opcodes.LRETURN || returnInsn.opcode == Opcodes.DRETURN) {
2
} else {
1
}
}
}

View File

@@ -5,11 +5,11 @@
package org.jetbrains.kotlin.codegen.inline
import org.jetbrains.kotlin.codegen.optimization.common.FastMethodAnalyzer
import org.jetbrains.kotlin.codegen.optimization.common.InsnSequence
import org.jetbrains.kotlin.codegen.optimization.common.isMeaningful
import org.jetbrains.kotlin.codegen.optimization.nullCheck.isCheckParameterIsNotNull
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodParameterSignature
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode
import org.jetbrains.org.objectweb.asm.tree.FieldInsnNode
@@ -17,6 +17,15 @@ import org.jetbrains.org.objectweb.asm.tree.MethodNode
import org.jetbrains.org.objectweb.asm.tree.VarInsnNode
import org.jetbrains.org.objectweb.asm.tree.analysis.*
fun parameterOffsets(isStatic: Boolean, valueParameters: List<JvmMethodParameterSignature>): Array<Int> {
var nextOffset = if (isStatic) 0 else 1
return Array(valueParameters.size) { index ->
nextOffset.also {
nextOffset += valueParameters[index].asmType.size
}
}
}
fun MethodNode.remove(instructions: Sequence<AbstractInsnNode>) =
instructions.forEach {
this@remove.instructions.remove(it)
@@ -127,7 +136,7 @@ internal class Aload0Interpreter(private val node: MethodNode) : BasicInterprete
internal fun AbstractInsnNode.isAload0() = opcode == Opcodes.ALOAD && (this as VarInsnNode).`var` == 0
internal fun analyzeMethodNodeWithInterpreter(node: MethodNode, interpreter: BasicInterpreter): Array<out Frame<BasicValue>?> {
val analyzer = object : FastMethodAnalyzer<BasicValue>("fake", node, interpreter) {
val analyzer = object : Analyzer<BasicValue>(interpreter) {
override fun newFrame(nLocals: Int, nStack: Int): Frame<BasicValue> {
return object : Frame<BasicValue>(nLocals, nStack) {
@@ -142,7 +151,7 @@ internal fun analyzeMethodNodeWithInterpreter(node: MethodNode, interpreter: Bas
}
try {
return analyzer.analyze()
return analyzer.analyze("fake", node)
} catch (e: AnalyzerException) {
throw RuntimeException(e)
}

View File

@@ -107,5 +107,22 @@ class ParametersBuilder private constructor() {
fun newBuilder(): ParametersBuilder {
return ParametersBuilder()
}
@JvmOverloads
@JvmStatic
fun initializeBuilderFrom(
objectType: Type, descriptor: String, inlineLambda: LambdaInfo? = null, isStatic: Boolean = false
): ParametersBuilder {
val builder = newBuilder()
if (inlineLambda?.hasDispatchReceiver != false && !isStatic) {
//skipped this for inlined lambda cause it will be removed
builder.addThis(objectType, inlineLambda != null).functionalArgument = inlineLambda
}
for (type in Type.getArgumentTypes(descriptor)) {
builder.addNextParameter(type, false)
}
return builder
}
}
}

View File

@@ -5,13 +5,16 @@
package org.jetbrains.kotlin.codegen.inline
import org.jetbrains.kotlin.builtins.isSuspendFunctionType
import org.jetbrains.kotlin.builtins.isSuspendFunctionTypeOrSubtype
import org.jetbrains.kotlin.codegen.*
import org.jetbrains.kotlin.codegen.DescriptorAsmUtil.getMethodAsmFlags
import org.jetbrains.kotlin.codegen.binding.CalculatedClosure
import org.jetbrains.kotlin.codegen.binding.CodegenBinding
import org.jetbrains.kotlin.codegen.coroutines.getOrCreateJvmSuspendFunctionView
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.load.kotlin.TypeMappingMode
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
@@ -25,9 +28,12 @@ import org.jetbrains.kotlin.resolve.inline.isInlineOnly
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodParameterKind
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.util.OperatorNameConventions
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.Method
import org.jetbrains.org.objectweb.asm.tree.MethodNode
class PsiInlineCodegen(
codegen: ExpressionCodegen,
@@ -37,17 +43,19 @@ class PsiInlineCodegen(
typeParameterMappings: TypeParameterMappings<KotlinType>,
sourceCompiler: SourceCompilerForInline,
private val methodOwner: Type,
private val actualDispatchReceiver: Type,
reportErrorsOn: KtElement,
private val actualDispatchReceiver: Type
) : InlineCodegen<ExpressionCodegen>(
codegen, state, signature, typeParameterMappings, sourceCompiler,
ReifiedTypeInliner(
typeParameterMappings, PsiInlineIntrinsicsSupport(state, reportErrorsOn), codegen.typeSystem,
typeParameterMappings, PsiInlineIntrinsicsSupport(state), codegen.typeSystem,
state.languageVersionSettings, state.unifiedNullChecks
),
), CallGenerator {
override fun generateAssertField() =
codegen.parentCodegen.generateAssertField()
override fun generateAssertFieldIfNeeded(info: RootInliningContext) {
if (info.generateAssertField) {
codegen.parentCodegen.generateAssertField()
}
}
override fun genCallInner(
callableMethod: Callable,
@@ -62,16 +70,19 @@ class PsiInlineCodegen(
return
}
try {
for (info in closuresToGenerate) {
// Can't be done immediately in `rememberClosure` for some reason:
info.generateLambdaBody(sourceCompiler)
// Requires `generateLambdaBody` first if the closure is non-empty (for bound callable references,
// or indeed any callable references, it *is* empty, so this was done in `rememberClosure`):
if (!info.isBoundCallableReference) {
putClosureParametersOnStack(info, null)
val registerLineNumber = registerLineNumberAfterwards(resolvedCall)
for (info in expressionMap.values) {
if (info is PsiExpressionLambda) {
// Can't be done immediately in `rememberClosure` for some reason:
info.generateLambdaBody(sourceCompiler)
// Requires `generateLambdaBody` first if the closure is non-empty (for bound callable references,
// or indeed any callable references, it *is* empty, so this was done in `rememberClosure`):
if (!info.isBoundCallableReference) {
putClosureParametersOnStack(info, null)
}
}
}
performInline(registerLineNumberAfterwards(resolvedCall), functionDescriptor.isInlineOnly())
performInline(registerLineNumber, functionDescriptor.isInlineOnly())
} finally {
state.globalInlineContext.exitFromInlining()
}
@@ -86,7 +97,8 @@ class PsiInlineCodegen(
private val hiddenParameters = mutableListOf<Pair<ParameterInfo, Int>>()
override fun processHiddenParameters() {
if (!DescriptorAsmUtil.isStaticMethod((sourceCompiler as PsiSourceCompilerForInline).context.contextKind, functionDescriptor)) {
val contextKind = (sourceCompiler as PsiSourceCompilerForInline).context.contextKind
if (getMethodAsmFlags(functionDescriptor, contextKind, state) and Opcodes.ACC_STATIC == 0) {
hiddenParameters += invocationParamBuilder.addNextParameter(methodOwner, false, actualDispatchReceiver) to
codegen.frameMap.enterTemp(methodOwner)
}
@@ -108,6 +120,14 @@ class PsiInlineCodegen(
hiddenParameters.clear()
}
/*lambda or callable reference*/
private fun isInliningParameter(expression: KtExpression, valueParameterDescriptor: ValueParameterDescriptor): Boolean {
//TODO deparenthesize typed
val deparenthesized = KtPsiUtil.deparenthesize(expression)
return InlineUtil.isInlineParameter(valueParameterDescriptor) && isInlinableParameterExpression(deparenthesized)
}
override fun genValueAndPut(
valueParameterDescriptor: ValueParameterDescriptor?,
argumentExpression: KtExpression,
@@ -119,9 +139,7 @@ class PsiInlineCodegen(
"which cannot be declared in Kotlin and thus be inline: $codegen"
}
val isInlineParameter = InlineUtil.isInlineParameter(valueParameterDescriptor)
//TODO deparenthesize typed
if (isInlineParameter && isInlinableParameterExpression(KtPsiUtil.deparenthesize(argumentExpression))) {
if (isInliningParameter(argumentExpression, valueParameterDescriptor)) {
rememberClosure(argumentExpression, parameterType.type, valueParameterDescriptor)
} else {
val value = codegen.gen(argumentExpression)
@@ -141,8 +159,6 @@ class PsiInlineCodegen(
private fun isCallSiteIsSuspend(descriptor: ValueParameterDescriptor): Boolean =
state.bindingContext[CodegenBinding.CALL_SITE_IS_SUSPEND_FOR_CROSSINLINE_LAMBDA, descriptor] == true
private val closuresToGenerate = mutableListOf<PsiExpressionLambda>()
private fun rememberClosure(expression: KtExpression, type: Type, parameter: ValueParameterDescriptor) {
val ktLambda = KtPsiUtil.deparenthesize(expression)
assert(isInlinableParameterExpression(ktLambda)) { "Couldn't find inline expression in ${expression.text}" }
@@ -154,7 +170,6 @@ class PsiInlineCodegen(
val lambda = PsiExpressionLambda(ktLambda!!, state, parameter.isCrossinline, boundReceiver != null)
rememberClosure(type, parameter.index, lambda)
closuresToGenerate += lambda
if (boundReceiver != null) {
// Has to be done immediately to preserve evaluation order.
val receiver = codegen.generateReceiverValue(boundReceiver, false)
@@ -169,7 +184,7 @@ class PsiInlineCodegen(
}
}
var activeLambda: PsiExpressionLambda? = null
var activeLambda: LambdaInfo? = null
private set
private fun putClosureParametersOnStack(next: PsiExpressionLambda, receiverValue: StackValue?) {
@@ -185,6 +200,11 @@ class PsiInlineCodegen(
putCapturedToLocalVal(stackValue, activeLambda!!.capturedVars[paramIndex], stackValue.kotlinType)
override fun reorderArgumentsIfNeeded(actualArgsWithDeclIndex: List<ArgumentAndDeclIndex>, valueParameterTypes: List<Type>) = Unit
override fun extractDefaultLambdas(node: MethodNode): List<DefaultLambda> =
extractDefaultLambdas(node, extractDefaultLambdaOffsetAndDescriptor(jvmSignature, functionDescriptor)) { parameter ->
PsiDefaultLambda(type, capturedArgs, parameter, offset, needReification, sourceCompiler)
}
}
private val FunctionDescriptor.explicitParameters
@@ -193,9 +213,9 @@ private val FunctionDescriptor.explicitParameters
class PsiExpressionLambda(
expression: KtExpression,
private val state: GenerationState,
val isCrossInline: Boolean,
val isBoundCallableReference: Boolean
) : ExpressionLambda() {
isCrossInline: Boolean,
override val isBoundCallableReference: Boolean
) : ExpressionLambda(isCrossInline) {
override val lambdaClassType: Type
override val invokeMethod: Method
@@ -204,7 +224,7 @@ class PsiExpressionLambda(
override val invokeMethodParameters: List<KotlinType?>
get() {
val actualInvokeDescriptor = if (invokeMethodDescriptor.isSuspend)
val actualInvokeDescriptor = if (isSuspend)
getOrCreateJvmSuspendFunctionView(invokeMethodDescriptor, state)
else
invokeMethodDescriptor
@@ -222,6 +242,8 @@ class PsiExpressionLambda(
override val returnLabels: Map<String, Label?>
override val isSuspend: Boolean
val closure: CalculatedClosure
init {
@@ -250,6 +272,7 @@ class PsiExpressionLambda(
?: throw AssertionError("null closure for lambda ${expression.text}")
returnLabels = getDeclarationLabels(expression, invokeMethodDescriptor).associateWith { null }
invokeMethod = state.typeMapper.mapAsmMethod(invokeMethodDescriptor)
isSuspend = invokeMethodDescriptor.isSuspend
}
// This can only be computed after generating the body, hence `lazy`.
@@ -280,3 +303,32 @@ class PsiExpressionLambda(
val isPropertyReference: Boolean
get() = propertyReferenceInfo != null
}
class PsiDefaultLambda(
lambdaClassType: Type,
capturedArgs: Array<Type>,
parameterDescriptor: ValueParameterDescriptor,
offset: Int,
needReification: Boolean,
sourceCompiler: SourceCompilerForInline
) : DefaultLambda(lambdaClassType, capturedArgs, parameterDescriptor.isCrossinline, offset, needReification, sourceCompiler) {
private val invokeMethodDescriptor: FunctionDescriptor
override val invokeMethodParameters: List<KotlinType?>
get() = invokeMethodDescriptor.explicitParameters.map { it.returnType }
override val invokeMethodReturnType: KotlinType?
get() = invokeMethodDescriptor.returnType
init {
val name = if (isPropertyReference) OperatorNameConventions.GET else OperatorNameConventions.INVOKE
val descriptor = parameterDescriptor.type.memberScope
.getContributedFunctions(OperatorNameConventions.INVOKE, NoLookupLocation.FROM_BACKEND)
.single()
.let { if (parameterDescriptor.type.isSuspendFunctionType) getOrCreateJvmSuspendFunctionView(it, sourceCompiler.state) else it }
// This is technically wrong as it always uses `invoke`, but `loadInvoke` will fall back to `get` which is never mangled...
val asmMethod = sourceCompiler.state.typeMapper.mapAsmMethod(descriptor)
val invokeIsErased = loadInvoke(sourceCompiler, name.asString(), asmMethod)
invokeMethodDescriptor = if (invokeIsErased) descriptor.original else descriptor
}
}

View File

@@ -5,20 +5,13 @@
package org.jetbrains.kotlin.codegen.inline
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.builtins.jvm.JavaToKotlinClassMap
import org.jetbrains.kotlin.codegen.*
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
import org.jetbrains.kotlin.load.java.JvmAnnotationNames
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
import org.jetbrains.kotlin.resolve.jvm.AsmTypes.*
import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm.*
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.model.TypeParameterMarker
import org.jetbrains.org.objectweb.asm.Type
@@ -26,10 +19,7 @@ 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 PsiInlineIntrinsicsSupport(
override val state: GenerationState,
private val reportErrorsOn: KtElement,
) : ReifiedTypeInliner.IntrinsicsSupport<KotlinType> {
class PsiInlineIntrinsicsSupport(private val state: GenerationState) : ReifiedTypeInliner.IntrinsicsSupport<KotlinType> {
override fun putClassInstance(v: InstructionAdapter, type: KotlinType) {
DescriptorAsmUtil.putJavaLangClassInstance(v, state.typeMapper.mapType(type), type, state.typeMapper)
}
@@ -66,26 +56,5 @@ class PsiInlineIntrinsicsSupport(
)
}
override fun isMutableCollectionType(type: KotlinType): Boolean {
val classifier = type.constructor.declarationDescriptor
return classifier is ClassDescriptor && JavaToKotlinClassMap.isMutable(classifier.fqNameUnsafe)
}
override fun toKotlinType(type: KotlinType): KotlinType = type
override fun checkAnnotatedType(type: KotlinType) {
if (type.annotations.hasAnnotation(StandardNames.FqNames.extensionFunctionType)) {
state.diagnostics.report(TYPEOF_EXTENSION_FUNCTION_TYPE.on(reportErrorsOn))
} else if (type.annotations.any { it.fqName != JvmAnnotationNames.ENHANCED_NULLABILITY_ANNOTATION }) {
state.diagnostics.report(TYPEOF_ANNOTATED_TYPE.on(reportErrorsOn))
}
}
override fun reportSuspendTypeUnsupported() {
state.diagnostics.report(TYPEOF_SUSPEND_TYPE.on(reportErrorsOn))
}
override fun reportNonReifiedTypeParameterWithRecursiveBoundUnsupported(typeParameterName: Name) {
state.diagnostics.report(TYPEOF_NON_REIFIED_TYPE_PARAMETER_WITH_RECURSIVE_BOUND.on(reportErrorsOn, typeParameterName.asString()))
}
}

View File

@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.codegen.coroutines.getOrCreateJvmSuspendFunctionView
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.config.isReleaseCoroutines
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.incremental.KotlinLookupLocation
import org.jetbrains.kotlin.psi.*
@@ -22,11 +23,13 @@ import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.ImportedFromObjectCallableDescriptor
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCallWithAssert
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
import org.jetbrains.kotlin.resolve.isInlineClass
import org.jetbrains.kotlin.resolve.jvm.annotations.isCallableMemberCompiledToJvmDefault
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.kotlin.resolve.jvm.requiresFunctionNameManglingForReturnType
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DescriptorWithContainerSource
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.expressions.ExpressionTypingUtils
import org.jetbrains.kotlin.types.expressions.LabelResolver
import org.jetbrains.kotlin.utils.addIfNotNull
@@ -157,7 +160,8 @@ class PsiSourceCompilerForInline(
}
FunctionCodegen.generateMethodBody(
adapter, descriptor, context, jvmMethodSignature, strategy, parentCodegen, state.jvmDefaultMode
adapter, descriptor, context, jvmMethodSignature, strategy, parentCodegen, state.jvmDefaultMode,
state.languageVersionSettings.isReleaseCoroutines()
)
if (isLambda) {
@@ -233,7 +237,7 @@ class PsiSourceCompilerForInline(
}
override fun compileInlineFunction(jvmSignature: JvmMethodSignature): SMAPAndMethodNode {
generateInlineIntrinsic(functionDescriptor, jvmSignature.asmMethod, codegen.typeSystem)?.let {
generateInlineIntrinsic(state.languageVersionSettings, functionDescriptor, jvmSignature.asmMethod, codegen.typeSystem)?.let {
return it
}

View File

@@ -20,9 +20,9 @@ import org.jetbrains.kotlin.codegen.generateAsCast
import org.jetbrains.kotlin.codegen.generateIsCheck
import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods
import org.jetbrains.kotlin.codegen.optimization.common.intConstant
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.config.isReleaseCoroutines
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeSystemCommonBackendContext
@@ -63,19 +63,11 @@ class ReifiedTypeInliner<KT : KotlinTypeMarker>(
}
interface IntrinsicsSupport<KT : KotlinTypeMarker> {
val state: GenerationState
fun putClassInstance(v: InstructionAdapter, type: KT)
fun generateTypeParameterContainer(v: InstructionAdapter, typeParameter: TypeParameterMarker)
fun isMutableCollectionType(type: KT): Boolean
fun toKotlinType(type: KT): KotlinType
fun checkAnnotatedType(type: KT)
fun reportSuspendTypeUnsupported()
fun reportNonReifiedTypeParameterWithRecursiveBoundUnsupported(typeParameterName: Name)
}
companion object {
@@ -231,7 +223,7 @@ class ReifiedTypeInliner<KT : KotlinTypeMarker>(
if (stubCheckcast !is TypeInsnNode) return false
val newMethodNode = MethodNode(Opcodes.API_VERSION)
generateAsCast(InstructionAdapter(newMethodNode), kotlinType, asmType, safe, unifiedNullChecks)
generateAsCast(InstructionAdapter(newMethodNode), kotlinType, asmType, safe, languageVersionSettings, unifiedNullChecks)
instructions.insert(insn, newMethodNode.instructions)
// Keep stubCheckcast to avoid VerifyErrors on 1.8+ bytecode,
@@ -255,7 +247,7 @@ class ReifiedTypeInliner<KT : KotlinTypeMarker>(
if (stubInstanceOf !is TypeInsnNode) return false
val newMethodNode = MethodNode(Opcodes.API_VERSION)
generateIsCheck(InstructionAdapter(newMethodNode), kotlinType, asmType)
generateIsCheck(InstructionAdapter(newMethodNode), kotlinType, asmType, languageVersionSettings.isReleaseCoroutines())
instructions.insert(insn, newMethodNode.instructions)
instructions.remove(stubInstanceOf)

View File

@@ -78,12 +78,12 @@ fun loadCompiledInlineFunction(
state: GenerationState
): SMAPAndMethodNode {
val containerType = AsmUtil.asmTypeByClassId(containerId)
val bytes = state.inlineCache.classBytes.getOrPut(containerType.internalName) {
findVirtualFile(state, containerId)?.contentsToByteArray()
?: throw IllegalStateException("Couldn't find declaration file for $containerId")
}
val resultInCache = state.inlineCache.methodNodeById.getOrPut(MethodId(containerType.descriptor, asmMethod)) {
val bytes = state.inlineCache.classBytes.getOrPut(containerType.internalName) {
findVirtualFile(state, containerId)?.contentsToByteArray()
?: throw IllegalStateException("Couldn't find declaration file for $containerId")
}
getMethodNode(containerType, bytes, asmMethod, isSuspend, isMangled)
getMethodNode(containerType, bytes, asmMethod.name, asmMethod.descriptor, isSuspend, isMangled)
}
return SMAPAndMethodNode(cloneMethodNode(resultInCache.node), resultInCache.classSMAP)
}
@@ -91,24 +91,25 @@ fun loadCompiledInlineFunction(
private fun getMethodNode(
owner: Type,
bytes: ByteArray,
method: Method,
name: String,
descriptor: String,
isSuspend: Boolean,
isMangled: Boolean
): SMAPAndMethodNode {
getMethodNode(owner, bytes, method, isSuspend)?.let { return it }
getMethodNode(owner, bytes, name, descriptor, isSuspend)?.let { return it }
if (isMangled) {
// Compatibility with old inline class ABI versions.
val dashIndex = method.name.indexOf('-')
val nameWithoutManglingSuffix = if (dashIndex > 0) method.name.substring(0, dashIndex) else method.name
if (nameWithoutManglingSuffix != method.name) {
getMethodNode(owner, bytes, Method(nameWithoutManglingSuffix, method.descriptor), isSuspend)?.let { return it }
val dashIndex = name.indexOf('-')
val nameWithoutManglingSuffix = if (dashIndex > 0) name.substring(0, dashIndex) else name
if (nameWithoutManglingSuffix != name) {
getMethodNode(owner, bytes, nameWithoutManglingSuffix, descriptor, isSuspend)?.let { return it }
}
getMethodNode(owner, bytes, Method("$nameWithoutManglingSuffix-impl", method.descriptor), isSuspend)?.let { return it }
getMethodNode(owner, bytes, "$nameWithoutManglingSuffix-impl", descriptor, isSuspend)?.let { return it }
}
throw IllegalStateException("couldn't find inline method $owner.$method")
throw IllegalStateException("couldn't find inline method $owner.$name$descriptor")
}
// If an `inline suspend fun` has a state machine, it should have a `$$forInline` version without one.
private fun getMethodNode(owner: Type, bytes: ByteArray, method: Method, isSuspend: Boolean) =
(if (isSuspend) getMethodNode(bytes, owner, Method(method.name + FOR_INLINE_SUFFIX, method.descriptor)) else null)
?: getMethodNode(bytes, owner, method)
private fun getMethodNode(owner: Type, bytes: ByteArray, name: String, descriptor: String, isSuspend: Boolean) =
(if (isSuspend) getMethodNode(bytes, name + FOR_INLINE_SUFFIX, descriptor, owner) else null)
?: getMethodNode(bytes, name, descriptor, owner)

View File

@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.codegen.inline.*
import org.jetbrains.kotlin.codegen.optimization.common.asSequence
import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.config.isReleaseCoroutines
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
import org.jetbrains.org.objectweb.asm.MethodVisitor
@@ -48,16 +49,18 @@ class CoroutineTransformer(
fun suspendLambdaWithGeneratedStateMachine(node: MethodNode): Boolean =
!isContinuationNotLambda() && isSuspendLambda(node) && isStateMachine(node)
private fun isContinuationNotLambda(): Boolean = inliningContext.isContinuation && superClassName.endsWith("ContinuationImpl")
private fun isContinuationNotLambda(): Boolean = inliningContext.isContinuation &&
if (state.languageVersionSettings.isReleaseCoroutines()) superClassName.endsWith("ContinuationImpl")
else methods.any { it.name == "getLabel" }
private fun isStateMachine(node: MethodNode): Boolean =
node.instructions.asSequence().any { insn -> insn is LdcInsnNode && insn.cst == ILLEGAL_STATE_ERROR_MESSAGE }
private fun isSuspendLambda(node: MethodNode) = isInvokeSuspend(node)
private fun isSuspendLambda(node: MethodNode) = isResumeImpl(node)
fun newMethod(node: MethodNode): DeferredMethodVisitor {
return when {
isInvokeSuspend(node) -> {
isResumeImpl(node) -> {
assert(!isStateMachine(node)) {
"Inlining/transforming state-machine"
}
@@ -68,8 +71,9 @@ class CoroutineTransformer(
}
}
private fun isInvokeSuspend(node: MethodNode): Boolean =
node.name.removeSuffix(FOR_INLINE_SUFFIX) == INVOKE_SUSPEND_METHOD_NAME && inliningContext.isContinuation
private fun isResumeImpl(node: MethodNode): Boolean =
state.languageVersionSettings.isResumeImplMethodName(node.name.removeSuffix(FOR_INLINE_SUFFIX)) &&
inliningContext.isContinuation
private fun isSuspendFunctionWithFakeConstructorCall(node: MethodNode): Boolean = findFakeContinuationConstructorClassName(node) != null
@@ -84,15 +88,16 @@ class CoroutineTransformer(
val sourceCompilerForInline = inliningContext.root.sourceCompilerForInline
val stateMachineBuilder = CoroutineTransformerMethodVisitor(
createNewMethodFrom(node, name), node.access, name, node.desc, null, null,
containingClassInternalName = classBuilder.thisName,
obtainClassBuilderForCoroutineState = { classBuilder },
isForNamedFunction = false,
shouldPreserveClassInitialization = state.constructorCallNormalizationMode.shouldPreserveClassInitialization,
disableTailCallOptimizationForFunctionReturningUnit = false,
reportSuspensionPointInsideMonitor = { sourceCompilerForInline.reportSuspensionPointInsideMonitor(it) },
// TODO: this linenumbers might not be correct and since they are used only for step-over, check them.
lineNumber = inliningContext.callSiteInfo.lineNumber,
sourceFile = inliningContext.callSiteInfo.file?.name ?: "",
languageVersionSettings = state.languageVersionSettings,
shouldPreserveClassInitialization = state.constructorCallNormalizationMode.shouldPreserveClassInitialization,
containingClassInternalName = classBuilder.thisName,
isForNamedFunction = false,
disableTailCallOptimizationForFunctionReturningUnit = false,
useOldSpilledVarTypeAnalysis = state.configuration.getBoolean(JVMConfigurationKeys.USE_OLD_SPILLED_VAR_TYPE_ANALYSIS)
)
@@ -118,16 +123,17 @@ class CoroutineTransformer(
val sourceCompilerForInline = inliningContext.root.sourceCompilerForInline
val stateMachineBuilder = CoroutineTransformerMethodVisitor(
createNewMethodFrom(node, name), node.access, name, node.desc, null, null,
containingClassInternalName = classBuilder.thisName,
obtainClassBuilderForCoroutineState = { (inliningContext as RegeneratedClassContext).continuationBuilders[continuationClassName]!! },
isForNamedFunction = true,
shouldPreserveClassInitialization = state.constructorCallNormalizationMode.shouldPreserveClassInitialization,
disableTailCallOptimizationForFunctionReturningUnit = disableTailCallOptimization,
reportSuspensionPointInsideMonitor = { sourceCompilerForInline.reportSuspensionPointInsideMonitor(it) },
lineNumber = inliningContext.callSiteInfo.lineNumber,
sourceFile = inliningContext.callSiteInfo.file?.name ?: "",
languageVersionSettings = state.languageVersionSettings,
shouldPreserveClassInitialization = state.constructorCallNormalizationMode.shouldPreserveClassInitialization,
containingClassInternalName = classBuilder.thisName,
isForNamedFunction = true,
needDispatchReceiver = true,
internalNameForDispatchReceiver = classBuilder.thisName,
disableTailCallOptimizationForFunctionReturningUnit = disableTailCallOptimization,
putContinuationParameterToLvt = !state.isIrBackend,
useOldSpilledVarTypeAnalysis = state.configuration.getBoolean(JVMConfigurationKeys.USE_OLD_SPILLED_VAR_TYPE_ANALYSIS)
)

View File

@@ -16,16 +16,20 @@
package org.jetbrains.kotlin.codegen.inline
import org.jetbrains.kotlin.codegen.DescriptorAsmUtil
import org.jetbrains.kotlin.codegen.OwnerKind
import org.jetbrains.kotlin.codegen.inline.ReifiedTypeInliner.Companion.isNeedClassReificationMarker
import org.jetbrains.kotlin.codegen.optimization.common.InsnSequence
import org.jetbrains.kotlin.codegen.optimization.common.asSequence
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.inline.InlineUtil
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodParameterKind
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.Method
import org.jetbrains.org.objectweb.asm.tree.*
import kotlin.math.max
private data class Condition(
val mask: Int, val constant: Int,
@@ -37,6 +41,25 @@ private data class Condition(
val varIndex = varInsNode?.`var` ?: 0
}
fun extractDefaultLambdaOffsetAndDescriptor(
jvmSignature: JvmMethodSignature,
functionDescriptor: FunctionDescriptor
): Map<Int, ValueParameterDescriptor> {
val valueParameters = jvmSignature.valueParameters
val containingDeclaration = functionDescriptor.containingDeclaration
val kind =
if (DescriptorUtils.isInterface(containingDeclaration)) OwnerKind.DEFAULT_IMPLS
else OwnerKind.getMemberOwnerKind(containingDeclaration)
val parameterOffsets = parameterOffsets(DescriptorAsmUtil.isStaticMethod(kind, functionDescriptor), valueParameters)
val valueParameterOffset = valueParameters.takeWhile { it.kind != JvmMethodParameterKind.VALUE }.size
return functionDescriptor.valueParameters.filter {
InlineUtil.isInlineParameter(it) && it.declaresDefaultValue()
}.associateBy {
parameterOffsets[valueParameterOffset + it.index]
}
}
class ExtractedDefaultLambda(val type: Type, val capturedArgs: Array<Type>, val offset: Int, val needReification: Boolean)
fun expandMaskConditionsAndUpdateVariableNodes(
@@ -179,68 +202,3 @@ private fun defaultLambdaFakeCallStub(args: Array<Type>, lambdaOffset: Int): Met
false
)
}
fun loadDefaultLambdaBody(classBytes: ByteArray, classType: Type, isPropertyReference: Boolean): SMAPAndMethodNode {
// In general we can't know what the correct unboxed `invoke` is, and what Kotlin types its arguments have,
// as the type of this object may be any subtype of the parameter's type. All we know is that Function<N>
// has to have a `invoke` that takes `Object`s and returns an `Object`; everything else needs to be figured
// out from its contents. TODO: for > 22 arguments, the only argument is an array. `MethodInliner` can't do that.
val invokeName = if (isPropertyReference) OperatorNameConventions.GET.asString() else OperatorNameConventions.INVOKE.asString()
val invokeNode = getMethodNode(classBytes, classType) {
it.name == invokeName && it.returnType == AsmTypes.OBJECT_TYPE && it.argumentTypes.all { arg -> arg == AsmTypes.OBJECT_TYPE }
} ?: error("can't find erased invoke '$invokeName(Object...): Object' in default lambda '${classType.internalName}'")
return if (invokeNode.node.access.and(Opcodes.ACC_BRIDGE) == 0)
invokeNode
else
invokeNode.node.inlineBridge(classBytes, classType)
}
private fun MethodNode.inlineBridge(classBytes: ByteArray, classType: Type): SMAPAndMethodNode {
// If the erased invoke is a bridge, we need to locate the unboxed invoke and inline it. As mentioned above,
// we don't know what the Kotlin types of its arguments/returned value are, so we can't generate our own
// boxing/unboxing code; luckily, the bridge already has that.
val invokeInsn = instructions.singleOrNull { it is MethodInsnNode && it.owner == classType.internalName } as MethodInsnNode?
?: error("no single invoke of method on this in '${name}${desc}' of default lambda '${classType.internalName}'")
val targetMethod = Method(invokeInsn.name, invokeInsn.desc)
val target = getMethodNode(classBytes, classType, targetMethod)
?: error("can't find non-bridge invoke '$targetMethod' in default lambda '${classType.internalName}")
// Store unboxed/casted arguments in the correct variable slots
val targetArgs = targetMethod.argumentTypes
val targetArgsSize = targetArgs.sumOf { it.size } + if (target.node.access.and(Opcodes.ACC_STATIC) == 0) 1 else 0
var offset = targetArgsSize
for (type in targetArgs.reversed()) {
offset -= type.size
instructions.insertBefore(invokeInsn, VarInsnNode(type.getOpcode(Opcodes.ISTORE), offset))
}
if (target.node.access.and(Opcodes.ACC_STATIC) == 0) {
instructions.insertBefore(invokeInsn, InsnNode(Opcodes.POP)) // this
}
// Remap returns and ranges for arguments' LVT entries
val invokeLabel = LabelNode()
val returnLabel = LabelNode()
instructions.insertBefore(invokeInsn, invokeLabel)
instructions.insert(invokeInsn, returnLabel)
for (insn in target.node.instructions) {
if (insn.opcode in Opcodes.IRETURN..Opcodes.RETURN) {
target.node.instructions.set(insn, JumpInsnNode(Opcodes.GOTO, returnLabel))
}
}
for (local in target.node.localVariables) {
if (local.index < targetArgsSize) {
local.start = invokeLabel
local.end = returnLabel
}
}
// Insert contents of the method into the bridge
instructions.filterIsInstance<LineNumberNode>().forEach { instructions.remove(it) } // those are not meaningful
instructions.insertBefore(invokeInsn, target.node.instructions)
instructions.remove(invokeInsn)
localVariables = target.node.localVariables
tryCatchBlocks = target.node.tryCatchBlocks
maxLocals = max(maxLocals, target.node.maxLocals)
maxStack = max(maxStack, target.node.maxStack)
return SMAPAndMethodNode(this, target.classSMAP)
}

View File

@@ -1,223 +0,0 @@
/*
* 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.codegen.inline
import org.jetbrains.kotlin.codegen.optimization.nullCheck.isParameterCheckedForNull
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
import org.jetbrains.org.objectweb.asm.tree.*
fun canInlineArgumentsInPlace(methodNode: MethodNode): Boolean {
// Usual inline functions are inlined in the following way:
// <evaluate argument #1>
// <store argument to an argument variable V1>
// ...
// <evaluate argument #N>
// <store argument to an argument variable VN>
// <inline function method body with parameter variables Pi remapped to argument variables Vi>
// If an argument #k is already stored in a local variable W, this variable W is reused.
// When inlining arguments in-place, we instead replace corresponding variable load instructions in the inline function method body
// with bytecode for evaluating a given argument.
// We can do so if such transformation keeps the evaluation order intact, possibly disregarding class initialization.
//
// This is true for many simple @InlineOnly functions from Kotlin standard library.
// For example, bytecode for 'inline fun println(message: Any?)' is:
// GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
// ALOAD 0
// INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
// Basic inlining for 'println("Hello, world!")' would produce (skipping labels and line numbers):
// // evaluate arguments, storing them to local variables
// LDC "Hello, world!"
// ASTORE 0
// GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
// ALOAD 0
// INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
// With argument "Hello, world!" inlined in-place it would be:
// GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
// LDC "Hello, world!"
// INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
// Such inlining is possible because we consider it OK to reorder 'GETSTATIC java/lang/System.out : Ljava/io/PrintStream;' instruction
// with any argument evaluation instructions ('LDC "Hello, world!"' in this case).
val tcbStartLabels = methodNode.tryCatchBlocks.mapTo(HashSet()) { it.start }
val methodParameterTypes = Type.getArgumentTypes(methodNode.desc)
val jvmArgumentTypes = ArrayList<Type>(methodParameterTypes.size + 1)
if (methodNode.access and Opcodes.ACC_STATIC == 0) {
// Here we don't care much about the exact 'this' type,
// it's only important to remember that variable slot #0 holds an object reference.
jvmArgumentTypes.add(AsmTypes.OBJECT_TYPE)
}
jvmArgumentTypes.addAll(methodParameterTypes)
val argumentVarEnd = jvmArgumentTypes.sumOf { it.size }
var expectedArgumentVar = 0
var lastArgIndex = 0
var insn = methodNode.instructions.first
// During arguments evaluation, make sure that all arguments are loaded in expected order
// and there are no unexpected side effects in-between.
while (insn != null && expectedArgumentVar < argumentVarEnd) {
// Entering a try-catch block before all arguments are loaded breaks evaluation order.
if (insn in tcbStartLabels)
return false
// Some instructions break evaluation order.
if (insn.isProhibitedDuringArgumentsEvaluation())
return false
// Allow a limited list of 'GETSTATIC <owner> <name> <desc>' instructions.
if (insn.opcode == Opcodes.GETSTATIC) {
val fieldInsn = insn as FieldInsnNode
val fieldSignature = FieldSignature(fieldInsn.owner, fieldInsn.name, fieldInsn.desc)
if (fieldSignature !in whitelistedStaticFields)
return false
}
// Writing to or incrementing an argument variable forbids in-place argument inlining.
if (insn.opcode in Opcodes.ISTORE..Opcodes.ASTORE && (insn as VarInsnNode).`var` < argumentVarEnd)
return false
if (insn.opcode == Opcodes.IINC && (insn as IincInsnNode).`var` < argumentVarEnd)
return false
// Analyze variable loads.
if (insn.opcode in Opcodes.ILOAD..Opcodes.ALOAD) {
// Skip parameter null check: 'aload x; ldc "..."; invokestatic <check>'
if (insn.opcode == Opcodes.ALOAD && insn.isParameterCheckedForNull()) {
// Go directly to the instruction after 'invokestatic <check>'
insn = insn.next.next.next
continue
}
val varInsn = insn as VarInsnNode
val varIndex = (varInsn).`var`
if (varIndex == expectedArgumentVar) {
// Expected argument variable loaded.
expectedArgumentVar += jvmArgumentTypes[lastArgIndex].size
++lastArgIndex
// Skip a sequence of load instructions referring to the same argument variable
// (such sequence is present in functions like 'Array.copyOf' and can be replaced with DUP instructions).
do {
insn = insn.next
} while (insn != null && insn.opcode == varInsn.opcode && (insn as VarInsnNode).`var` == varIndex)
continue
} else if (varIndex < argumentVarEnd) {
// Loaded an argument variable, but not an expected one => broken evaluation order
return false
} else {
// It's OK to load any non-argument variable during argument evaluation.
insn = insn.next
continue
}
}
// Anything else is fine.
insn = insn.next
}
// Method body is over, but not all arguments were loaded on stack.
if (expectedArgumentVar < argumentVarEnd)
return false
// After arguments evaluation make sure that argument variables are no longer accessed
// (we are not going to store anything to those variables anyway).
while (insn != null) {
if (insn.opcode in Opcodes.ILOAD..Opcodes.ALOAD || insn.opcode in Opcodes.ISTORE..Opcodes.ASTORE) {
if ((insn as VarInsnNode).`var` < argumentVarEnd)
return false
} else if (insn.opcode == Opcodes.IINC) {
if ((insn as IincInsnNode).`var` < argumentVarEnd)
return false
}
insn = insn.next
}
// Didn't encounter anything suspicious.
return true
}
internal data class FieldSignature(
val owner: String,
val name: String,
val desc: String
)
private val whitelistedStaticFields: Set<FieldSignature> =
hashSetOf(
FieldSignature("java/lang/System", "out", "Ljava/io/PrintStream;"),
FieldSignature("kotlin/Result", "Companion", "Lkotlin/Result\$Companion;"),
FieldSignature("kotlin/_Assertions", "ENABLED", "Z")
)
private fun AbstractInsnNode.isProhibitedDuringArgumentsEvaluation() =
opcode in opcodeProhibitedDuringArgumentsEvaluation.indices &&
opcodeProhibitedDuringArgumentsEvaluation[opcode]
private val opcodeProhibitedDuringArgumentsEvaluation = BooleanArray(256).also { a ->
// Any kind of jump during arguments evaluation is a hazard.
// This includes all conditional jump instructions, switch instructions, return and throw instructions.
// Very conservative, but enough for practical cases.
for (i in Opcodes.IFEQ..Opcodes.RETURN) a[i] = true
a[Opcodes.IFNULL] = true
a[Opcodes.IFNONNULL] = true
a[Opcodes.ATHROW] = true
// Instruction with non-trivial side effects is a hazard.
// NB GETSTATIC is taken care of separately.
a[Opcodes.PUTSTATIC] = true
a[Opcodes.PUTFIELD] = true
a[Opcodes.INVOKEVIRTUAL] = true
a[Opcodes.INVOKESPECIAL] = true
a[Opcodes.INVOKESTATIC] = true
a[Opcodes.INVOKEINTERFACE] = true
a[Opcodes.INVOKEDYNAMIC] = true
a[Opcodes.MONITORENTER] = true
a[Opcodes.MONITOREXIT] = true
// Integer division instructions can throw exception
a[Opcodes.IDIV] = true
a[Opcodes.LDIV] = true
a[Opcodes.IREM] = true
a[Opcodes.LREM] = true
// CHECKCAST can throw exception
a[Opcodes.CHECKCAST] = true
// Array creation can throw exception (in case of negative array size)
a[Opcodes.NEWARRAY] = true
a[Opcodes.ANEWARRAY] = true
a[Opcodes.MULTIANEWARRAY] = true
// Array access instructions can throw exception
for (i in Opcodes.IALOAD..Opcodes.SALOAD) a[i] = true
for (i in Opcodes.IASTORE..Opcodes.SASTORE) a[i] = true
}
private const val MARKER_INPLACE_CALL_START = "<INPLACE-CALL-START>"
private const val MARKER_INPLACE_ARGUMENT_START = "<INPLACE-ARGUMENT-START>"
private const val MARKER_INPLACE_ARGUMENT_END = "<INPLACE-ARGUMENT-END>"
private const val MARKER_INPLACE_CALL_END = "<INPLACE-CALL-END>"
private fun InstructionAdapter.addMarker(name: String) {
visitMethodInsn(Opcodes.INVOKESTATIC, INLINE_MARKER_CLASS_NAME, name, "()V", false)
}
fun InstructionAdapter.addInplaceCallStartMarker() = addMarker(MARKER_INPLACE_CALL_START)
fun InstructionAdapter.addInplaceCallEndMarker() = addMarker(MARKER_INPLACE_CALL_END)
fun InstructionAdapter.addInplaceArgumentStartMarker() = addMarker(MARKER_INPLACE_ARGUMENT_START)
fun InstructionAdapter.addInplaceArgumentEndMarker() = addMarker(MARKER_INPLACE_ARGUMENT_END)
internal fun AbstractInsnNode.isInplaceCallStartMarker() = isInlineMarker(this, MARKER_INPLACE_CALL_START)
internal fun AbstractInsnNode.isInplaceCallEndMarker() = isInlineMarker(this, MARKER_INPLACE_CALL_END)
internal fun AbstractInsnNode.isInplaceArgumentStartMarker() = isInlineMarker(this, MARKER_INPLACE_ARGUMENT_START)
internal fun AbstractInsnNode.isInplaceArgumentEndMarker() = isInlineMarker(this, MARKER_INPLACE_ARGUMENT_END)

View File

@@ -43,7 +43,6 @@ import org.jetbrains.kotlin.util.OperatorNameConventions
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.commons.Method
import org.jetbrains.org.objectweb.asm.tree.*
import org.jetbrains.org.objectweb.asm.util.Printer
import org.jetbrains.org.objectweb.asm.util.Textifier
@@ -60,7 +59,7 @@ const val INLINE_FUN_VAR_SUFFIX = "\$iv"
internal const val FIRST_FUN_LABEL = "$$$$\$ROOT$$$$$"
internal const val SPECIAL_TRANSFORMATION_NAME = "\$special"
const val INLINE_TRANSFORMATION_SUFFIX = "\$inlined"
internal const val INLINE_CALL_TRANSFORMATION_SUFFIX = "$$INLINE_TRANSFORMATION_SUFFIX"
internal const val INLINE_CALL_TRANSFORMATION_SUFFIX = "$" + INLINE_TRANSFORMATION_SUFFIX
internal const val INLINE_FUN_THIS_0_SUFFIX = "\$inline_fun"
internal const val DEFAULT_LAMBDA_FAKE_CALL = "$$\$DEFAULT_LAMBDA_FAKE_CALL$$$"
internal const val CAPTURED_FIELD_FOLD_PREFIX = "$$$"
@@ -68,10 +67,11 @@ internal const val CAPTURED_FIELD_FOLD_PREFIX = "$$$"
private const val NON_LOCAL_RETURN = "$$$$\$NON_LOCAL_RETURN$$$$$"
const val CAPTURED_FIELD_PREFIX = "$"
private const val NON_CAPTURED_FIELD_PREFIX = "$$"
internal const val INLINE_MARKER_CLASS_NAME = "kotlin/jvm/internal/InlineMarker"
private const val INLINE_MARKER_CLASS_NAME = "kotlin/jvm/internal/InlineMarker"
private const val INLINE_MARKER_BEFORE_METHOD_NAME = "beforeInlineCall"
private const val INLINE_MARKER_AFTER_METHOD_NAME = "afterInlineCall"
private const val INLINE_MARKER_FINALLY_START = "finallyStart"
private const val INLINE_MARKER_FINALLY_END = "finallyEnd"
private const val INLINE_MARKER_BEFORE_SUSPEND_ID = 0
private const val INLINE_MARKER_AFTER_SUSPEND_ID = 1
@@ -84,18 +84,40 @@ private const val INLINE_MARKER_AFTER_INLINE_SUSPEND_ID = 7
private const val INLINE_MARKER_BEFORE_UNBOX_INLINE_CLASS = 8
private const val INLINE_MARKER_AFTER_UNBOX_INLINE_CLASS = 9
internal inline fun getMethodNode(classData: ByteArray, classType: Type, crossinline match: (Method) -> Boolean): SMAPAndMethodNode? {
internal fun getMethodNode(
classData: ByteArray,
methodName: String,
methodDescriptor: String,
classType: Type,
signatureAmbiguity: Boolean = false
): SMAPAndMethodNode? {
val cr = ClassReader(classData)
var node: MethodNode? = null
var sourceFile: String? = null
var sourceMap: String? = null
ClassReader(classData).accept(object : ClassVisitor(Opcodes.API_VERSION) {
val debugInfo = arrayOfNulls<String>(2)
cr.accept(object : ClassVisitor(Opcodes.API_VERSION) {
override fun visitSource(source: String?, debug: String?) {
sourceFile = source
sourceMap = debug
super.visitSource(source, debug)
debugInfo[0] = source
debugInfo[1] = debug
}
override fun visitMethod(access: Int, name: String, desc: String, signature: String?, exceptions: Array<String>?): MethodVisitor? {
if (!match(Method(name, desc))) return null
override fun visitMethod(
access: Int,
name: String,
desc: String,
signature: String?,
exceptions: Array<String>?
): MethodVisitor? {
if (methodName != name || (signatureAmbiguity && access.and(Opcodes.ACC_SYNTHETIC) != 0)) return null
if (methodDescriptor != desc) {
val sameNumberOfParameters = Type.getArgumentTypes(methodDescriptor).size == Type.getArgumentTypes(desc).size
if (!signatureAmbiguity || !sameNumberOfParameters) {
return null
}
}
node?.let { existing ->
throw AssertionError("Can't find proper '$name' method for inline: ambiguity between '${existing.name + existing.desc}' and '${name + desc}'")
}
@@ -104,14 +126,14 @@ internal inline fun getMethodNode(classData: ByteArray, classType: Type, crossin
}
}, ClassReader.SKIP_FRAMES or if (GENERATE_SMAP) 0 else ClassReader.SKIP_DEBUG)
return node?.let{
val (first, last) = listOfNotNull(it).lineNumberRange()
SMAPAndMethodNode(it, SMAPParser.parseOrCreateDefault(sourceMap, sourceFile, classType.internalName, first, last))
if (node == null) {
return null
}
}
internal fun getMethodNode(classData: ByteArray, classType: Type, method: Method): SMAPAndMethodNode? =
getMethodNode(classData, classType) { it == method }
val (first, last) = listOfNotNull(node).lineNumberRange()
val smap = SMAPParser.parseOrCreateDefault(debugInfo[1], debugInfo[0], classType.internalName, first, last)
return SMAPAndMethodNode(node!!, smap)
}
internal fun Collection<MethodNode>.lineNumberRange(): Pair<Int, Int> {
var minLine = Int.MAX_VALUE
@@ -301,7 +323,7 @@ internal fun firstLabelInChain(node: LabelNode): LabelNode {
internal fun areLabelsBeforeSameInsn(first: LabelNode, second: LabelNode): Boolean =
firstLabelInChain(first) == firstLabelInChain(second)
val MethodNode?.nodeText: String
internal val MethodNode?.nodeText: String
get() {
if (this == null) {
return "Not generated"
@@ -325,42 +347,8 @@ internal val AbstractInsnNode?.insnText: String
return sw.toString().trim()
}
fun AbstractInsnNode?.insnText(insnList: InsnList): String {
if (this == null) return "<null>"
fun AbstractInsnNode.indexOf() =
insnList.indexOf(this)
fun LabelNode.labelText() =
"L#${this.indexOf()}"
return when (this) {
is LabelNode ->
labelText()
is JumpInsnNode ->
"$insnOpcodeText ${label.labelText()}"
is LookupSwitchInsnNode ->
"$insnOpcodeText " +
this.keys.zip(this.labels).joinToString(prefix = "[", postfix = "]") { (key, label) -> "$key:${label.labelText()}" }
is TableSwitchInsnNode ->
"$insnOpcodeText " +
(min..max).zip(this.labels).joinToString(prefix = "[", postfix = "]") { (key, label) -> "$key:${label.labelText()}" }
else ->
insnText
}
}
internal val AbstractInsnNode?.insnOpcodeText: String
get() = when (this) {
null -> "null"
is LabelNode -> "LABEL"
is LineNumberNode -> "LINENUMBER"
is FrameNode -> "FRAME"
else -> Printer.OPCODES[opcode]
}
internal fun TryCatchBlockNode.text(insns: InsnList): String =
"[${insns.indexOf(start)} .. ${insns.indexOf(end)} -> ${insns.indexOf(handler)}]"
get() = if (this == null) "null" else Printer.OPCODES[opcode]
internal fun loadClassBytesByInternalName(state: GenerationState, internalName: String): ByteArray {
//try to find just compiled classes then in dependencies
@@ -534,15 +522,17 @@ internal fun isInlineMarker(insn: AbstractInsnNode): Boolean {
return isInlineMarker(insn, null)
}
internal fun isInlineMarker(insn: AbstractInsnNode, name: String?): Boolean {
if (insn.opcode != Opcodes.INVOKESTATIC) return false
private fun isInlineMarker(insn: AbstractInsnNode, name: String?): Boolean {
if (insn !is MethodInsnNode) {
return false
}
val methodInsn = insn as MethodInsnNode
return methodInsn.owner == INLINE_MARKER_CLASS_NAME &&
return insn.getOpcode() == Opcodes.INVOKESTATIC &&
insn.owner == INLINE_MARKER_CLASS_NAME &&
if (name != null)
methodInsn.name == name
insn.name == name
else
methodInsn.name == INLINE_MARKER_BEFORE_METHOD_NAME || methodInsn.name == INLINE_MARKER_AFTER_METHOD_NAME
insn.name == INLINE_MARKER_BEFORE_METHOD_NAME || insn.name == INLINE_MARKER_AFTER_METHOD_NAME
}
internal fun isBeforeInlineMarker(insn: AbstractInsnNode): Boolean {

View File

@@ -5,11 +5,13 @@
package org.jetbrains.kotlin.codegen.inline
import org.jetbrains.kotlin.backend.common.isBuiltInIntercepted
import org.jetbrains.kotlin.backend.common.isBuiltInSuspendCoroutineUninterceptedOrReturn
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.codegen.coroutines.createMethodNodeForCoroutineContext
import org.jetbrains.kotlin.codegen.coroutines.createMethodNodeForIntercepted
import org.jetbrains.kotlin.codegen.coroutines.createMethodNodeForSuspendCoroutineUninterceptedOrReturn
import org.jetbrains.kotlin.codegen.createMethodNodeForAlwaysEnabledAssert
import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicArrayConstructors
@@ -28,22 +30,25 @@ import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
import org.jetbrains.org.objectweb.asm.commons.Method
import org.jetbrains.org.objectweb.asm.tree.MethodNode
fun generateInlineIntrinsicForIr(descriptor: FunctionDescriptor): SMAPAndMethodNode? =
fun generateInlineIntrinsicForIr(languageVersionSettings: LanguageVersionSettings, descriptor: FunctionDescriptor): SMAPAndMethodNode? =
when {
// TODO: implement these as codegen intrinsics (see IrIntrinsicMethods)
descriptor.isBuiltInCoroutineContext() ->
createMethodNodeForCoroutineContext(descriptor)
descriptor.isBuiltInSuspendCoroutineUninterceptedOrReturn() ->
createMethodNodeForSuspendCoroutineUninterceptedOrReturn()
descriptor.isBuiltInIntercepted(languageVersionSettings) ->
createMethodNodeForIntercepted(languageVersionSettings)
descriptor.isBuiltInCoroutineContext(languageVersionSettings) ->
createMethodNodeForCoroutineContext(descriptor, languageVersionSettings)
descriptor.isBuiltInSuspendCoroutineUninterceptedOrReturn(languageVersionSettings) ->
createMethodNodeForSuspendCoroutineUninterceptedOrReturn(languageVersionSettings)
else -> null
}?.let { SMAPAndMethodNode(it, SMAP(listOf())) }
internal fun generateInlineIntrinsic(
languageVersionSettings: LanguageVersionSettings,
descriptor: FunctionDescriptor,
asmMethod: Method,
typeSystem: TypeSystemCommonBackendContext
): SMAPAndMethodNode? {
return generateInlineIntrinsicForIr(descriptor) ?: when {
return generateInlineIntrinsicForIr(languageVersionSettings, descriptor) ?: when {
isSpecialEnumMethod(descriptor) ->
createSpecialEnumMethodBody(descriptor.name.asString(), descriptor.original.typeParameters.single(), typeSystem)
TypeOfChecker.isTypeOf(descriptor) ->

Some files were not shown because too many files have changed in this diff Show More