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:
Evgeny Gerashchenko
2014-07-01 15:59:48 +04:00
parent b86d7668b0
commit bf5c65bef6
22 changed files with 269 additions and 5 deletions
@@ -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: File): Boolean {
val fileBytes = file.readBytes()
public fun saveFileToCache(moduleId: String, sourceFiles: Collection<File>, 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<Pair<String, File>>, outDirectories: Map<String, File>) {
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<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 {
// Format of serialization to string: <module id> <path separator> <source file path> --> <package part JVM internal name>
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);
}
@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/");
@@ -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") }
}