From 44e8ae3571511226485aa0b9584b0e90952663af Mon Sep 17 00:00:00 2001 From: Alexey Tsvetkov Date: Mon, 21 Aug 2017 16:30:32 +0300 Subject: [PATCH] JS IC: detect inline function changes --- .../kotlin/incremental/IncrementalJsCache.kt | 39 +++++++++++++++++- .../IncrementalJsCompilerRunner.kt | 2 +- .../js/IncrementalResultsConsumer.kt | 41 +++++++++++++++++++ .../expression/FunctionTranslator.kt | 30 ++++++++++++-- 4 files changed, 106 insertions(+), 6 deletions(-) diff --git a/build-common/src/org/jetbrains/kotlin/incremental/IncrementalJsCache.kt b/build-common/src/org/jetbrains/kotlin/incremental/IncrementalJsCache.kt index 3d57998a74c..09633cc905e 100644 --- a/build-common/src/org/jetbrains/kotlin/incremental/IncrementalJsCache.kt +++ b/build-common/src/org/jetbrains/kotlin/incremental/IncrementalJsCache.kt @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.incremental import com.intellij.util.io.DataExternalizer +import org.jetbrains.kotlin.incremental.js.IncrementalResultsConsumerImpl import org.jetbrains.kotlin.incremental.js.TranslationResultValue import org.jetbrains.kotlin.incremental.storage.* import org.jetbrains.kotlin.name.ClassId @@ -34,12 +35,14 @@ open class IncrementalJsCache(cachesDir: File) : IncrementalCacheCommon(cachesDi companion object { private val TRANSLATION_RESULT_MAP = "translation-result" private val SOURCES_TO_CLASSES_FQNS = "sources-to-classes" + private val INLINE_FUNCTIONS = "inline-functions" private val HEADER_FILE_NAME = "header.meta" } private val dirtySources = arrayListOf() private val translationResults = registerMap(TranslationResultMap(TRANSLATION_RESULT_MAP.storageFile)) private val sourcesToClasses = registerMap(SourceToClassesMap(SOURCES_TO_CLASSES_FQNS.storageFile)) + private val inlineFunctions = registerMap(InlineFunctionsMap(INLINE_FUNCTIONS.storageFile)) private val headerFile: File get() = File(cachesDir, HEADER_FILE_NAME) @@ -55,10 +58,13 @@ open class IncrementalJsCache(cachesDir: File) : IncrementalCacheCommon(cachesDi dirtySources.addAll(removedAndCompiledSources) } - fun compareAndUpdate(translatedFiles: Map, changesCollector: ChangesCollector) { + fun compareAndUpdate(incrementalResults: IncrementalResultsConsumerImpl, changesCollector: ChangesCollector) { + val translatedFiles = incrementalResults.packageParts + dirtySources.forEach { if (it !in translatedFiles) { translationResults.remove(it, changesCollector) + inlineFunctions.remove(it) } removeAllFromClassStorage(sourcesToClasses[it]) @@ -84,6 +90,10 @@ open class IncrementalJsCache(cachesDir: File) : IncrementalCacheCommon(cachesDi translationResults.put(srcFile, binaryMetadata, binaryAst) } + + for ((srcFile, inlineDeclarations) in incrementalResults.inlineFunctions) { + inlineFunctions.process(srcFile, inlineDeclarations, changesCollector) + } } fun nonDirtyPackageParts(): Map = @@ -186,4 +196,31 @@ fun getProtoData(sourceFile: File, metadata: ByteArray): Map classes[packagePartClassId] = PackagePartProtoData(this, nameResolver, packageFqName) } return classes +} + +private class InlineFunctionsMap(storageFile: File) : BasicStringMap>(storageFile, StringToLongMapExternalizer) { + fun process(srcFile: File, newMap: Map, changesCollector: ChangesCollector) { + val key = srcFile.canonicalPath + val oldMap = storage[key] ?: emptyMap() + + if (newMap.isNotEmpty()) { + storage[key] = newMap + } + else { + storage.remove(key) + } + + for (fn in oldMap.keys + newMap.keys) { + val fqNameSegments = fn.removePrefix("").removePrefix("").split(".") + val fqName = FqName.fromSegments(fqNameSegments) + changesCollector.collectMemberIfValueWasChanged(fqName.parent(), fqName.shortName().asString(), oldMap[fn], newMap[fn]) + } + } + + fun remove(sourceFile: File) { + storage.remove(sourceFile.canonicalPath) + } + + override fun dumpValue(value: Map): String = + value.dumpMap { java.lang.Long.toHexString(it) } } \ No newline at end of file diff --git a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalJsCompilerRunner.kt b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalJsCompilerRunner.kt index 8cfb717aea6..2b64a8c576c 100644 --- a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalJsCompilerRunner.kt +++ b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalJsCompilerRunner.kt @@ -111,7 +111,7 @@ class IncrementalJsCompilerRunner( val jsCache = caches.platformCache jsCache.header = incrementalResults.headerMetadata - return jsCache.compareAndUpdate(incrementalResults.packageParts, changesCollector) + return jsCache.compareAndUpdate(incrementalResults, changesCollector) } override fun runCompiler( diff --git a/js/js.frontend/src/org/jetbrains/kotlin/incremental/js/IncrementalResultsConsumer.kt b/js/js.frontend/src/org/jetbrains/kotlin/incremental/js/IncrementalResultsConsumer.kt index 04b5b2b0d68..ed87dd374ca 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/incremental/js/IncrementalResultsConsumer.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/incremental/js/IncrementalResultsConsumer.kt @@ -17,12 +17,18 @@ package org.jetbrains.kotlin.incremental.js import java.io.File +import java.security.MessageDigest interface IncrementalResultsConsumer { /** processes new header metadata (serialized [JsProtoBuf.Header]) */ fun processHeader(headerMetadata: ByteArray) /** processes new package part metadata and binary tree for compiled source file */ fun processPackagePart(sourceFile: File, packagePartMetadata: ByteArray, binaryAst: ByteArray) + /** [inlineFunction] is expected to be a body of inline function (an instance of [JsNode]), + * but [Any] is used to avoid classloader conflicts in tests where the compiler is isolated + * (such as [JsProtoComparisonTestGenerated]) + */ + fun processInlineFunction(sourceFile: File, fqName: String, inlineFunction: Any) } class IncrementalResultsConsumerImpl : IncrementalResultsConsumer { @@ -33,6 +39,24 @@ class IncrementalResultsConsumerImpl : IncrementalResultsConsumer { val packageParts: Map get() = _packageParts + private val _inlineFuncs = hashMapOf>() + val inlineFunctions: Map> + get() { + val result = HashMap>(_inlineFuncs.size) + + for ((file, inlineFnsFromFile) in _inlineFuncs) { + val functionsHashes = HashMap(inlineFnsFromFile.size) + + for ((fqName, expression) in inlineFnsFromFile) { + functionsHashes[fqName] = expression.toString().toByteArray().md5() + } + + result[file] = functionsHashes + } + + return result + } + override fun processHeader(headerMetadata: ByteArray) { this.headerMetadata = headerMetadata } @@ -40,5 +64,22 @@ class IncrementalResultsConsumerImpl : IncrementalResultsConsumer { override fun processPackagePart(sourceFile: File, packagePartMetadata: ByteArray, binaryAst: ByteArray) { _packageParts.put(sourceFile, TranslationResultValue(packagePartMetadata, binaryAst)) } + + override fun processInlineFunction(sourceFile: File, fqName: String, inlineFunction: Any) { + val mapForSource = _inlineFuncs.getOrPut(sourceFile) { hashMapOf() } + mapForSource[fqName] = inlineFunction + } + + private fun ByteArray.md5(): Long { + val d = MessageDigest.getInstance("MD5").digest(this)!! + return ((d[0].toLong() and 0xFFL) + or ((d[1].toLong() and 0xFFL) shl 8) + or ((d[2].toLong() and 0xFFL) shl 16) + or ((d[3].toLong() and 0xFFL) shl 24) + or ((d[4].toLong() and 0xFFL) shl 32) + or ((d[5].toLong() and 0xFFL) shl 40) + or ((d[6].toLong() and 0xFFL) shl 48) + or ((d[7].toLong() and 0xFFL) shl 56)) + } } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/FunctionTranslator.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/FunctionTranslator.kt index cf6e8904ac1..3b50e37172f 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/FunctionTranslator.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/FunctionTranslator.kt @@ -16,9 +16,9 @@ package org.jetbrains.kotlin.js.translate.expression -import org.jetbrains.kotlin.descriptors.DeclarationDescriptor -import org.jetbrains.kotlin.descriptors.FunctionDescriptor -import org.jetbrains.kotlin.descriptors.PropertyAccessorDescriptor +import com.intellij.openapi.vfs.VfsUtilCore +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.descriptors.impl.PropertyAccessorDescriptorImpl import org.jetbrains.kotlin.js.backend.ast.JsExpression import org.jetbrains.kotlin.js.backend.ast.JsFunction import org.jetbrains.kotlin.js.backend.ast.JsParameter @@ -26,6 +26,7 @@ import org.jetbrains.kotlin.js.backend.ast.JsScope import org.jetbrains.kotlin.js.backend.ast.metadata.descriptor import org.jetbrains.kotlin.js.backend.ast.metadata.functionDescriptor import org.jetbrains.kotlin.js.backend.ast.metadata.hasDefaultValue +import org.jetbrains.kotlin.js.config.JSConfigurationKeys import org.jetbrains.kotlin.js.config.JsConfig import org.jetbrains.kotlin.js.translate.context.Namer import org.jetbrains.kotlin.js.translate.context.TranslationContext @@ -36,8 +37,10 @@ import org.jetbrains.kotlin.js.translate.utils.JsAstUtils import org.jetbrains.kotlin.js.translate.utils.requiresStateMachineTransformation import org.jetbrains.kotlin.psi.KtDeclarationWithBody import org.jetbrains.kotlin.resolve.DescriptorUtils +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.resolve.descriptorUtil.hasDefaultValue import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyPublicApi +import org.jetbrains.kotlin.resolve.source.PsiSourceFile fun TranslationContext.translateAndAliasParameters( descriptor: FunctionDescriptor, @@ -104,7 +107,26 @@ fun TranslationContext.translateFunction(declaration: KtDeclarationWithBody, fun fun TranslationContext.wrapWithInlineMetadata(function: JsFunction, descriptor: FunctionDescriptor, config: JsConfig): JsExpression { return if (shouldBeInlined(descriptor, this) && descriptor.isEffectivelyPublicApi) { val metadata = InlineMetadata.compose(function, descriptor, config) - metadata.functionWithMetadata + val functionWithMetadata = metadata.functionWithMetadata + + config.configuration[JSConfigurationKeys.INCREMENTAL_RESULTS_CONSUMER]?.apply { + val psiFile = (descriptor.source.containingFile as? PsiSourceFile)?.psiFile ?: return@apply + val file = VfsUtilCore.virtualToIoFile(psiFile.virtualFile) + + val fqName = when (descriptor) { + is PropertyGetterDescriptor -> { + "" + descriptor.correspondingProperty.fqNameSafe.asString() + } + is PropertySetterDescriptor -> { + "" + descriptor.correspondingProperty.fqNameSafe.asString() + } + else -> descriptor.fqNameSafe.asString() + } + + processInlineFunction(file, fqName, functionWithMetadata) + } + + functionWithMetadata } else { function