Basic detection for bytecode of inline functions in incremental compilation.
Basic rule is: bytecode of inline function changed -> recompile everything.
Original commit: 6c8a9ba378
This commit is contained in:
@@ -39,20 +39,25 @@ import com.google.common.collect.Maps
|
|||||||
import org.jetbrains.jet.lang.resolve.java.PackageClassUtils
|
import org.jetbrains.jet.lang.resolve.java.PackageClassUtils
|
||||||
import com.intellij.util.containers.MultiMap
|
import com.intellij.util.containers.MultiMap
|
||||||
import com.intellij.openapi.util.io.FileUtil
|
import com.intellij.openapi.util.io.FileUtil
|
||||||
|
import com.google.common.hash.Hashing
|
||||||
|
|
||||||
|
val INLINE_ANNOTATION_DESC = "Lkotlin/inline;"
|
||||||
|
|
||||||
public class IncrementalCacheImpl(val baseDir: File): IncrementalCache {
|
public class IncrementalCacheImpl(val baseDir: File): IncrementalCache {
|
||||||
class object {
|
class object {
|
||||||
val PROTO_MAP = "proto.tab"
|
val PROTO_MAP = "proto.tab"
|
||||||
val CONSTANTS_MAP = "constants.tab"
|
val CONSTANTS_MAP = "constants.tab"
|
||||||
|
val INLINE_FUNCTIONS = "inline-functions.tab"
|
||||||
val PACKAGE_PARTS = "package-parts.tab"
|
val PACKAGE_PARTS = "package-parts.tab"
|
||||||
}
|
}
|
||||||
|
|
||||||
private val protoMap = ProtoMap()
|
private val protoMap = ProtoMap()
|
||||||
private val constantsMap = ConstantsMap()
|
private val constantsMap = ConstantsMap()
|
||||||
|
private val inlineFunctionsMap = InlineFunctionsMap()
|
||||||
private val packagePartMap = PackagePartMap()
|
private val packagePartMap = PackagePartMap()
|
||||||
|
|
||||||
public fun saveFileToCache(moduleId: String, sourceFiles: Collection<File>, file: File): Boolean {
|
public fun saveFileToCache(moduleId: String, sourceFiles: Collection<File>, classFile: File): Boolean {
|
||||||
val fileBytes = file.readBytes()
|
val fileBytes = classFile.readBytes()
|
||||||
val classNameAndHeader = VirtualFileKotlinClass.readClassNameAndHeader(fileBytes)
|
val classNameAndHeader = VirtualFileKotlinClass.readClassNameAndHeader(fileBytes)
|
||||||
if (classNameAndHeader == null) return false
|
if (classNameAndHeader == null) return false
|
||||||
|
|
||||||
@@ -65,7 +70,7 @@ public class IncrementalCacheImpl(val baseDir: File): IncrementalCache {
|
|||||||
return protoMap.put(moduleId, className, data)
|
return protoMap.put(moduleId, className, data)
|
||||||
}
|
}
|
||||||
KotlinClassHeader.Kind.CLASS -> {
|
KotlinClassHeader.Kind.CLASS -> {
|
||||||
return protoMap.put(moduleId, className, data)
|
return inlineFunctionsMap.process(moduleId, sourceFiles.first(), fileBytes) or protoMap.put(moduleId, className, data)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
throw IllegalStateException("Unexpected kind with annotationData: ${header.kind}")
|
throw IllegalStateException("Unexpected kind with annotationData: ${header.kind}")
|
||||||
@@ -75,7 +80,7 @@ public class IncrementalCacheImpl(val baseDir: File): IncrementalCache {
|
|||||||
if (header.syntheticClassKind == JvmAnnotationNames.KotlinSyntheticClass.Kind.PACKAGE_PART) {
|
if (header.syntheticClassKind == JvmAnnotationNames.KotlinSyntheticClass.Kind.PACKAGE_PART) {
|
||||||
assert(sourceFiles.size == 1) { "Package part from several source files: $sourceFiles" }
|
assert(sourceFiles.size == 1) { "Package part from several source files: $sourceFiles" }
|
||||||
packagePartMap.putPackagePartSourceData(moduleId, sourceFiles.first(), className)
|
packagePartMap.putPackagePartSourceData(moduleId, sourceFiles.first(), className)
|
||||||
return constantsMap.process(moduleId, sourceFiles.first(), fileBytes)
|
return inlineFunctionsMap.process(moduleId, sourceFiles.first(), fileBytes) or constantsMap.process(moduleId, sourceFiles.first(), fileBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
@@ -84,6 +89,7 @@ public class IncrementalCacheImpl(val baseDir: File): IncrementalCache {
|
|||||||
public fun clearCacheForRemovedFiles(moduleIdsAndFiles: Collection<Pair<String, File>>, outDirectories: Map<String, File>) {
|
public fun clearCacheForRemovedFiles(moduleIdsAndFiles: Collection<Pair<String, File>>, outDirectories: Map<String, File>) {
|
||||||
for ((moduleId, sourceFile) in moduleIdsAndFiles) {
|
for ((moduleId, sourceFile) in moduleIdsAndFiles) {
|
||||||
constantsMap.remove(moduleId, sourceFile)
|
constantsMap.remove(moduleId, sourceFile)
|
||||||
|
inlineFunctionsMap.remove(moduleId, sourceFile)
|
||||||
packagePartMap.remove(moduleId, sourceFile)
|
packagePartMap.remove(moduleId, sourceFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,6 +107,7 @@ public class IncrementalCacheImpl(val baseDir: File): IncrementalCache {
|
|||||||
public fun close() {
|
public fun close() {
|
||||||
protoMap.close()
|
protoMap.close()
|
||||||
constantsMap.close()
|
constantsMap.close()
|
||||||
|
inlineFunctionsMap.close()
|
||||||
packagePartMap.close()
|
packagePartMap.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,6 +275,99 @@ public class IncrementalCacheImpl(val baseDir: File): IncrementalCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private inner class InlineFunctionsMap {
|
||||||
|
private val map: PersistentHashMap<String, Map<String, Long>> = PersistentHashMap(
|
||||||
|
File(baseDir, INLINE_FUNCTIONS),
|
||||||
|
EnumeratorStringDescriptor(),
|
||||||
|
InlineFunctionsMapExternalizer
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun getKey(moduleId: String, sourceFile: File): String {
|
||||||
|
return moduleId + File.pathSeparator + sourceFile.getAbsolutePath()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getInlineFunctionsMap(bytes: ByteArray): Map<String, Long> {
|
||||||
|
val result = HashMap<String, Long>()
|
||||||
|
|
||||||
|
ClassReader(bytes).accept(object : ClassVisitor(Opcodes.ASM5) {
|
||||||
|
override fun visitMethod(access: Int, name: String, desc: String, signature: String?, exceptions: Array<out String>?): MethodVisitor? {
|
||||||
|
val dummyClassWriter = ClassWriter(Opcodes.ASM5)
|
||||||
|
return object : MethodVisitor(Opcodes.ASM5, dummyClassWriter.visitMethod(0, name, desc, null, exceptions)) {
|
||||||
|
var hasInlineAnnotation = false
|
||||||
|
|
||||||
|
override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? {
|
||||||
|
if (desc == INLINE_ANNOTATION_DESC) {
|
||||||
|
hasInlineAnnotation = true
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitEnd() {
|
||||||
|
if (hasInlineAnnotation) {
|
||||||
|
val dummyBytes = dummyClassWriter.toByteArray()!!
|
||||||
|
val hash = Hashing.md5()!!.hashBytes(dummyBytes)!!.asLong()
|
||||||
|
|
||||||
|
result[name + desc] = hash
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}, 0)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun process(moduleId: String, file: File, bytes: ByteArray): Boolean {
|
||||||
|
return put(moduleId, file, getInlineFunctionsMap(bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun put(moduleId: String, file: File, inlineFunctionsMap: Map<String, Long>): Boolean {
|
||||||
|
val key = getKey(moduleId, file)
|
||||||
|
|
||||||
|
val oldMap = map[key]
|
||||||
|
if (oldMap == inlineFunctionsMap) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
map.put(key, inlineFunctionsMap)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun remove(moduleId: String, file: File) {
|
||||||
|
map.remove(getKey(moduleId, file))
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun close() {
|
||||||
|
map.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private object InlineFunctionsMapExternalizer: DataExternalizer<Map<String, Long>> {
|
||||||
|
override fun save(out: DataOutput, map: Map<String, Long>?) {
|
||||||
|
out.writeInt(map!!.size)
|
||||||
|
for (name in map.keySet()) {
|
||||||
|
IOUtil.writeString(name, out)
|
||||||
|
out.writeLong(map[name]!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(`in`: DataInput): Map<String, Long>? {
|
||||||
|
val size = `in`.readInt()
|
||||||
|
val map = Maps.newHashMapWithExpectedSize<String, Long>(size)!!
|
||||||
|
|
||||||
|
for (i in size.indices) {
|
||||||
|
val name = IOUtil.readString(`in`)!!
|
||||||
|
val value = `in`.readLong()
|
||||||
|
|
||||||
|
map[name] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private inner class PackagePartMap {
|
private inner class PackagePartMap {
|
||||||
// Format of serialization to string: <module id> <path separator> <source file path> --> <package part JVM internal name>
|
// Format of serialization to string: <module id> <path separator> <source file path> --> <package part JVM internal name>
|
||||||
private val map: PersistentHashMap<String, String> = PersistentHashMap(
|
private val map: PersistentHashMap<String, String> = PersistentHashMap(
|
||||||
|
|||||||
@@ -51,6 +51,16 @@ public class IncrementalJpsTestGenerated extends AbstractIncrementalJpsTest {
|
|||||||
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("jps-plugin/testData/incremental"), Pattern.compile("^([^\\.]+)$"), false);
|
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("jps-plugin/testData/incremental"), Pattern.compile("^([^\\.]+)$"), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("classInlineFunctionChanged")
|
||||||
|
public void testClassInlineFunctionChanged() throws Exception {
|
||||||
|
doTest("jps-plugin/testData/incremental/classInlineFunctionChanged/");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("classInlineFunctionUnchanged")
|
||||||
|
public void testClassInlineFunctionUnchanged() throws Exception {
|
||||||
|
doTest("jps-plugin/testData/incremental/classInlineFunctionUnchanged/");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("classRecreated")
|
@TestMetadata("classRecreated")
|
||||||
public void testClassRecreated() throws Exception {
|
public void testClassRecreated() throws Exception {
|
||||||
doTest("jps-plugin/testData/incremental/classRecreated/");
|
doTest("jps-plugin/testData/incremental/classRecreated/");
|
||||||
@@ -90,7 +100,7 @@ public class IncrementalJpsTestGenerated extends AbstractIncrementalJpsTest {
|
|||||||
public void testIndependentClasses() throws Exception {
|
public void testIndependentClasses() throws Exception {
|
||||||
doTest("jps-plugin/testData/incremental/independentClasses/");
|
doTest("jps-plugin/testData/incremental/independentClasses/");
|
||||||
}
|
}
|
||||||
|
|
||||||
@TestMetadata("multiplePackagesModified")
|
@TestMetadata("multiplePackagesModified")
|
||||||
public void testMultiplePackagesModified() throws Exception {
|
public void testMultiplePackagesModified() throws Exception {
|
||||||
doTest("jps-plugin/testData/incremental/multiplePackagesModified/");
|
doTest("jps-plugin/testData/incremental/multiplePackagesModified/");
|
||||||
@@ -126,6 +136,21 @@ public class IncrementalJpsTestGenerated extends AbstractIncrementalJpsTest {
|
|||||||
doTest("jps-plugin/testData/incremental/packageFilesChangedInTurn/");
|
doTest("jps-plugin/testData/incremental/packageFilesChangedInTurn/");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("packageInlineFunctionChanged")
|
||||||
|
public void testPackageInlineFunctionChanged() throws Exception {
|
||||||
|
doTest("jps-plugin/testData/incremental/packageInlineFunctionChanged/");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("packageInlineFunctionFromOurPackage")
|
||||||
|
public void testPackageInlineFunctionFromOurPackage() throws Exception {
|
||||||
|
doTest("jps-plugin/testData/incremental/packageInlineFunctionFromOurPackage/");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("packageInlineFunctionUnchanged")
|
||||||
|
public void testPackageInlineFunctionUnchanged() throws Exception {
|
||||||
|
doTest("jps-plugin/testData/incremental/packageInlineFunctionUnchanged/");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("packageRecreated")
|
@TestMetadata("packageRecreated")
|
||||||
public void testPackageRecreated() throws Exception {
|
public void testPackageRecreated() throws Exception {
|
||||||
doTest("jps-plugin/testData/incremental/packageRecreated/");
|
doTest("jps-plugin/testData/incremental/packageRecreated/");
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
Cleaning output files:
|
||||||
|
out/production/module/inline/Klass.class
|
||||||
|
End of files
|
||||||
|
Compiling files:
|
||||||
|
src/inline.kt
|
||||||
|
End of files
|
||||||
|
Cleaning output files:
|
||||||
|
out/production/module/usage/UsagePackage-usage-*.class
|
||||||
|
out/production/module/usage/UsagePackage.class
|
||||||
|
End of files
|
||||||
|
Compiling files:
|
||||||
|
src/usage.kt
|
||||||
|
End of files
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package inline
|
||||||
|
|
||||||
|
class Klass {
|
||||||
|
inline fun f(body: () -> Unit) {
|
||||||
|
println("i'm inline function")
|
||||||
|
body()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package inline
|
||||||
|
|
||||||
|
class Klass {
|
||||||
|
inline fun f(body: () -> Unit) {
|
||||||
|
body()
|
||||||
|
println("i'm inline function")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package usage
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
inline.Klass().f { println("to be inlined") }
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
Cleaning output files:
|
||||||
|
out/production/module/inline/Klass.class
|
||||||
|
End of files
|
||||||
|
Compiling files:
|
||||||
|
src/inline.kt
|
||||||
|
End of files
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package inline
|
||||||
|
|
||||||
|
class Klass {
|
||||||
|
inline fun f(body: () -> Unit) {
|
||||||
|
println("i'm inline function")
|
||||||
|
body()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package inline
|
||||||
|
|
||||||
|
class Klass {
|
||||||
|
inline fun f(body: () -> Unit) {
|
||||||
|
println("i'm inline function")
|
||||||
|
body()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package usage
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
inline.Klass().f { println("to be inlined") }
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
Cleaning output files:
|
||||||
|
out/production/module/inline/InlinePackage-inline-*.class
|
||||||
|
out/production/module/inline/InlinePackage.class
|
||||||
|
End of files
|
||||||
|
Compiling files:
|
||||||
|
src/inline.kt
|
||||||
|
End of files
|
||||||
|
Cleaning output files:
|
||||||
|
out/production/module/usage/UsagePackage-usage-*.class
|
||||||
|
out/production/module/usage/UsagePackage.class
|
||||||
|
End of files
|
||||||
|
Compiling files:
|
||||||
|
src/usage.kt
|
||||||
|
End of files
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package inline
|
||||||
|
|
||||||
|
inline fun f(body: () -> Unit) {
|
||||||
|
println("i'm inline function")
|
||||||
|
body()
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package inline
|
||||||
|
|
||||||
|
inline fun f(body: () -> Unit) {
|
||||||
|
body()
|
||||||
|
println("i'm inline function")
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package usage
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
inline.f { println("to be inlined") }
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
Cleaning output files:
|
||||||
|
out/production/module/test/TestPackage-usage-*.class
|
||||||
|
out/production/module/test/TestPackage.class
|
||||||
|
End of files
|
||||||
|
Compiling files:
|
||||||
|
src/usage.kt
|
||||||
|
End of files
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
inline fun f(body: () -> Unit) {
|
||||||
|
println("i'm inline function")
|
||||||
|
body()
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
f { println("to be inlined") }
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
f { println("to be inlined") }
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
Cleaning output files:
|
||||||
|
out/production/module/inline/InlinePackage-inline-*.class
|
||||||
|
out/production/module/inline/InlinePackage.class
|
||||||
|
End of files
|
||||||
|
Compiling files:
|
||||||
|
src/inline.kt
|
||||||
|
End of files
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package inline
|
||||||
|
|
||||||
|
inline fun f(body: () -> Unit) {
|
||||||
|
println("i'm inline function")
|
||||||
|
body()
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package inline
|
||||||
|
|
||||||
|
inline fun f(body: () -> Unit) {
|
||||||
|
println("i'm inline function")
|
||||||
|
body()
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package usage
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
inline.f { println("to be inlined") }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user