Basic detection for bytecode of inline functions in incremental compilation.
Basic rule is: bytecode of inline function changed -> recompile everything.
This commit is contained in:
@@ -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") }
|
||||
}
|
||||
Reference in New Issue
Block a user