diff --git a/jps-plugin/src/org/jetbrains/jet/jps/incremental/IncrementalCacheImpl.kt b/jps-plugin/src/org/jetbrains/jet/jps/incremental/IncrementalCacheImpl.kt index 56a64060821..4250d96496d 100644 --- a/jps-plugin/src/org/jetbrains/jet/jps/incremental/IncrementalCacheImpl.kt +++ b/jps-plugin/src/org/jetbrains/jet/jps/incremental/IncrementalCacheImpl.kt @@ -39,20 +39,25 @@ import com.google.common.collect.Maps import org.jetbrains.jet.lang.resolve.java.PackageClassUtils import com.intellij.util.containers.MultiMap 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 { class object { val PROTO_MAP = "proto.tab" val CONSTANTS_MAP = "constants.tab" + val INLINE_FUNCTIONS = "inline-functions.tab" val PACKAGE_PARTS = "package-parts.tab" } private val protoMap = ProtoMap() private val constantsMap = ConstantsMap() + private val inlineFunctionsMap = InlineFunctionsMap() private val packagePartMap = PackagePartMap() - public fun saveFileToCache(moduleId: String, sourceFiles: Collection, file: File): Boolean { - val fileBytes = file.readBytes() + public fun saveFileToCache(moduleId: String, sourceFiles: Collection, classFile: File): Boolean { + val fileBytes = classFile.readBytes() val classNameAndHeader = VirtualFileKotlinClass.readClassNameAndHeader(fileBytes) if (classNameAndHeader == null) return false @@ -65,7 +70,7 @@ public class IncrementalCacheImpl(val baseDir: File): IncrementalCache { return protoMap.put(moduleId, className, data) } KotlinClassHeader.Kind.CLASS -> { - return protoMap.put(moduleId, className, data) + return inlineFunctionsMap.process(moduleId, sourceFiles.first(), fileBytes) or protoMap.put(moduleId, className, data) } else -> { 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) { assert(sourceFiles.size == 1) { "Package part from several source files: $sourceFiles" } 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 @@ -84,6 +89,7 @@ public class IncrementalCacheImpl(val baseDir: File): IncrementalCache { public fun clearCacheForRemovedFiles(moduleIdsAndFiles: Collection>, outDirectories: Map) { for ((moduleId, sourceFile) in moduleIdsAndFiles) { constantsMap.remove(moduleId, sourceFile) + inlineFunctionsMap.remove(moduleId, sourceFile) packagePartMap.remove(moduleId, sourceFile) } @@ -101,6 +107,7 @@ public class IncrementalCacheImpl(val baseDir: File): IncrementalCache { public fun close() { protoMap.close() constantsMap.close() + inlineFunctionsMap.close() packagePartMap.close() } @@ -268,6 +275,99 @@ public class IncrementalCacheImpl(val baseDir: File): IncrementalCache { } } + private inner class InlineFunctionsMap { + private val map: PersistentHashMap> = 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 { + val result = HashMap() + + ClassReader(bytes).accept(object : ClassVisitor(Opcodes.ASM5) { + override fun visitMethod(access: Int, name: String, desc: String, signature: String?, exceptions: Array?): 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): 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> { + override fun save(out: DataOutput, map: Map?) { + out.writeInt(map!!.size) + for (name in map.keySet()) { + IOUtil.writeString(name, out) + out.writeLong(map[name]!!) + } + } + + override fun read(`in`: DataInput): Map? { + val size = `in`.readInt() + val map = Maps.newHashMapWithExpectedSize(size)!! + + for (i in size.indices) { + val name = IOUtil.readString(`in`)!! + val value = `in`.readLong() + + map[name] = value + } + + return map + } + + } + + private inner class PackagePartMap { // Format of serialization to string: --> private val map: PersistentHashMap = PersistentHashMap( diff --git a/jps-plugin/test/org/jetbrains/jet/jps/build/IncrementalJpsTestGenerated.java b/jps-plugin/test/org/jetbrains/jet/jps/build/IncrementalJpsTestGenerated.java index cc039994a2f..9ccc3cd59a1 100644 --- a/jps-plugin/test/org/jetbrains/jet/jps/build/IncrementalJpsTestGenerated.java +++ b/jps-plugin/test/org/jetbrains/jet/jps/build/IncrementalJpsTestGenerated.java @@ -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); } + @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") public void testClassRecreated() throws Exception { doTest("jps-plugin/testData/incremental/classRecreated/"); @@ -90,7 +100,7 @@ public class IncrementalJpsTestGenerated extends AbstractIncrementalJpsTest { public void testIndependentClasses() throws Exception { doTest("jps-plugin/testData/incremental/independentClasses/"); } - + @TestMetadata("multiplePackagesModified") public void testMultiplePackagesModified() throws Exception { doTest("jps-plugin/testData/incremental/multiplePackagesModified/"); @@ -126,6 +136,21 @@ public class IncrementalJpsTestGenerated extends AbstractIncrementalJpsTest { 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") public void testPackageRecreated() throws Exception { doTest("jps-plugin/testData/incremental/packageRecreated/"); diff --git a/jps-plugin/testData/incremental/classInlineFunctionChanged/build.log b/jps-plugin/testData/incremental/classInlineFunctionChanged/build.log new file mode 100644 index 00000000000..dc4ee3554aa --- /dev/null +++ b/jps-plugin/testData/incremental/classInlineFunctionChanged/build.log @@ -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 diff --git a/jps-plugin/testData/incremental/classInlineFunctionChanged/inline.kt b/jps-plugin/testData/incremental/classInlineFunctionChanged/inline.kt new file mode 100644 index 00000000000..4ac2a4efd18 --- /dev/null +++ b/jps-plugin/testData/incremental/classInlineFunctionChanged/inline.kt @@ -0,0 +1,8 @@ +package inline + +class Klass { + inline fun f(body: () -> Unit) { + println("i'm inline function") + body() + } +} diff --git a/jps-plugin/testData/incremental/classInlineFunctionChanged/inline.kt.new b/jps-plugin/testData/incremental/classInlineFunctionChanged/inline.kt.new new file mode 100644 index 00000000000..461d943cf4f --- /dev/null +++ b/jps-plugin/testData/incremental/classInlineFunctionChanged/inline.kt.new @@ -0,0 +1,8 @@ +package inline + +class Klass { + inline fun f(body: () -> Unit) { + body() + println("i'm inline function") + } +} diff --git a/jps-plugin/testData/incremental/classInlineFunctionChanged/usage.kt b/jps-plugin/testData/incremental/classInlineFunctionChanged/usage.kt new file mode 100644 index 00000000000..204a8e2537f --- /dev/null +++ b/jps-plugin/testData/incremental/classInlineFunctionChanged/usage.kt @@ -0,0 +1,5 @@ +package usage + +fun main(args: Array) { + inline.Klass().f { println("to be inlined") } +} diff --git a/jps-plugin/testData/incremental/classInlineFunctionUnchanged/build.log b/jps-plugin/testData/incremental/classInlineFunctionUnchanged/build.log new file mode 100644 index 00000000000..ea54d90c2ed --- /dev/null +++ b/jps-plugin/testData/incremental/classInlineFunctionUnchanged/build.log @@ -0,0 +1,6 @@ +Cleaning output files: +out/production/module/inline/Klass.class +End of files +Compiling files: +src/inline.kt +End of files diff --git a/jps-plugin/testData/incremental/classInlineFunctionUnchanged/inline.kt b/jps-plugin/testData/incremental/classInlineFunctionUnchanged/inline.kt new file mode 100644 index 00000000000..4ac2a4efd18 --- /dev/null +++ b/jps-plugin/testData/incremental/classInlineFunctionUnchanged/inline.kt @@ -0,0 +1,8 @@ +package inline + +class Klass { + inline fun f(body: () -> Unit) { + println("i'm inline function") + body() + } +} diff --git a/jps-plugin/testData/incremental/classInlineFunctionUnchanged/inline.kt.new b/jps-plugin/testData/incremental/classInlineFunctionUnchanged/inline.kt.new new file mode 100644 index 00000000000..4ac2a4efd18 --- /dev/null +++ b/jps-plugin/testData/incremental/classInlineFunctionUnchanged/inline.kt.new @@ -0,0 +1,8 @@ +package inline + +class Klass { + inline fun f(body: () -> Unit) { + println("i'm inline function") + body() + } +} diff --git a/jps-plugin/testData/incremental/classInlineFunctionUnchanged/usage.kt b/jps-plugin/testData/incremental/classInlineFunctionUnchanged/usage.kt new file mode 100644 index 00000000000..204a8e2537f --- /dev/null +++ b/jps-plugin/testData/incremental/classInlineFunctionUnchanged/usage.kt @@ -0,0 +1,5 @@ +package usage + +fun main(args: Array) { + inline.Klass().f { println("to be inlined") } +} diff --git a/jps-plugin/testData/incremental/packageInlineFunctionChanged/build.log b/jps-plugin/testData/incremental/packageInlineFunctionChanged/build.log new file mode 100644 index 00000000000..341ab0b4fd2 --- /dev/null +++ b/jps-plugin/testData/incremental/packageInlineFunctionChanged/build.log @@ -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 \ No newline at end of file diff --git a/jps-plugin/testData/incremental/packageInlineFunctionChanged/inline.kt b/jps-plugin/testData/incremental/packageInlineFunctionChanged/inline.kt new file mode 100644 index 00000000000..80d71e901a3 --- /dev/null +++ b/jps-plugin/testData/incremental/packageInlineFunctionChanged/inline.kt @@ -0,0 +1,6 @@ +package inline + +inline fun f(body: () -> Unit) { + println("i'm inline function") + body() +} diff --git a/jps-plugin/testData/incremental/packageInlineFunctionChanged/inline.kt.new b/jps-plugin/testData/incremental/packageInlineFunctionChanged/inline.kt.new new file mode 100644 index 00000000000..87422d45d88 --- /dev/null +++ b/jps-plugin/testData/incremental/packageInlineFunctionChanged/inline.kt.new @@ -0,0 +1,6 @@ +package inline + +inline fun f(body: () -> Unit) { + body() + println("i'm inline function") +} diff --git a/jps-plugin/testData/incremental/packageInlineFunctionChanged/usage.kt b/jps-plugin/testData/incremental/packageInlineFunctionChanged/usage.kt new file mode 100644 index 00000000000..3f8161245b6 --- /dev/null +++ b/jps-plugin/testData/incremental/packageInlineFunctionChanged/usage.kt @@ -0,0 +1,5 @@ +package usage + +fun main(args: Array) { + inline.f { println("to be inlined") } +} diff --git a/jps-plugin/testData/incremental/packageInlineFunctionFromOurPackage/build.log b/jps-plugin/testData/incremental/packageInlineFunctionFromOurPackage/build.log new file mode 100644 index 00000000000..7c79a56fe44 --- /dev/null +++ b/jps-plugin/testData/incremental/packageInlineFunctionFromOurPackage/build.log @@ -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 diff --git a/jps-plugin/testData/incremental/packageInlineFunctionFromOurPackage/inline.kt b/jps-plugin/testData/incremental/packageInlineFunctionFromOurPackage/inline.kt new file mode 100644 index 00000000000..fb5e59f5432 --- /dev/null +++ b/jps-plugin/testData/incremental/packageInlineFunctionFromOurPackage/inline.kt @@ -0,0 +1,6 @@ +package test + +inline fun f(body: () -> Unit) { + println("i'm inline function") + body() +} diff --git a/jps-plugin/testData/incremental/packageInlineFunctionFromOurPackage/usage.kt b/jps-plugin/testData/incremental/packageInlineFunctionFromOurPackage/usage.kt new file mode 100644 index 00000000000..ee2e6216365 --- /dev/null +++ b/jps-plugin/testData/incremental/packageInlineFunctionFromOurPackage/usage.kt @@ -0,0 +1,5 @@ +package test + +fun main(args: Array) { + f { println("to be inlined") } +} diff --git a/jps-plugin/testData/incremental/packageInlineFunctionFromOurPackage/usage.kt.new b/jps-plugin/testData/incremental/packageInlineFunctionFromOurPackage/usage.kt.new new file mode 100644 index 00000000000..ee2e6216365 --- /dev/null +++ b/jps-plugin/testData/incremental/packageInlineFunctionFromOurPackage/usage.kt.new @@ -0,0 +1,5 @@ +package test + +fun main(args: Array) { + f { println("to be inlined") } +} diff --git a/jps-plugin/testData/incremental/packageInlineFunctionUnchanged/build.log b/jps-plugin/testData/incremental/packageInlineFunctionUnchanged/build.log new file mode 100644 index 00000000000..b0e0bfb31e6 --- /dev/null +++ b/jps-plugin/testData/incremental/packageInlineFunctionUnchanged/build.log @@ -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 diff --git a/jps-plugin/testData/incremental/packageInlineFunctionUnchanged/inline.kt b/jps-plugin/testData/incremental/packageInlineFunctionUnchanged/inline.kt new file mode 100644 index 00000000000..80d71e901a3 --- /dev/null +++ b/jps-plugin/testData/incremental/packageInlineFunctionUnchanged/inline.kt @@ -0,0 +1,6 @@ +package inline + +inline fun f(body: () -> Unit) { + println("i'm inline function") + body() +} diff --git a/jps-plugin/testData/incremental/packageInlineFunctionUnchanged/inline.kt.new b/jps-plugin/testData/incremental/packageInlineFunctionUnchanged/inline.kt.new new file mode 100644 index 00000000000..80d71e901a3 --- /dev/null +++ b/jps-plugin/testData/incremental/packageInlineFunctionUnchanged/inline.kt.new @@ -0,0 +1,6 @@ +package inline + +inline fun f(body: () -> Unit) { + println("i'm inline function") + body() +} diff --git a/jps-plugin/testData/incremental/packageInlineFunctionUnchanged/usage.kt b/jps-plugin/testData/incremental/packageInlineFunctionUnchanged/usage.kt new file mode 100644 index 00000000000..3f8161245b6 --- /dev/null +++ b/jps-plugin/testData/incremental/packageInlineFunctionUnchanged/usage.kt @@ -0,0 +1,5 @@ +package usage + +fun main(args: Array) { + inline.f { println("to be inlined") } +}