From 8f06e59d3b2b2f96bb4dc53db60190dc58e780e7 Mon Sep 17 00:00:00 2001 From: "Denis.Zharkov" Date: Tue, 16 Mar 2021 17:34:03 +0300 Subject: [PATCH] Implement new faster version of Jar virtual file system It's only enabled by default in FIR and might be turned on with a CLI flag The main idea is that default FarFS re-read ZIP file list each time when class file is requested that is quite slow. We read it once and them reading bytes from the known offset. Also, unlike the default version we don't perform attributes check on each access On the one hand, it works faster on the other it might not notice that one of the JAR has been changed during compilation process But looks like it's not supposed to be a frequent even during compilation of a single module --- .../arguments/K2JVMCompilerArguments.kt | 6 + .../cli/jvm/compiler/KotlinCoreEnvironment.kt | 11 +- .../jvm/compiler/jarfs/FastJarFileSystem.kt | 44 ++++ .../cli/jvm/compiler/jarfs/FastJarHandler.kt | 165 ++++++++++++ .../jvm/compiler/jarfs/FastJarVirtualFile.kt | 105 ++++++++ .../jvm/compiler/jarfs/ZipImplementation.kt | 242 ++++++++++++++++++ .../jetbrains/kotlin/cli/jvm/jvmArguments.kt | 5 + .../kotlin/config/JVMConfigurationKeys.java | 3 + compiler/testData/cli/jvm/extraHelp.out | 1 + .../services/CompilerConfigurationProvider.kt | 5 + 10 files changed, 586 insertions(+), 1 deletion(-) create mode 100644 compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/jarfs/FastJarFileSystem.kt create mode 100644 compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/jarfs/FastJarHandler.kt create mode 100644 compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/jarfs/FastJarVirtualFile.kt create mode 100644 compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/jarfs/ZipImplementation.kt diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.kt index 7f69f3005ba..45b4a6f34fd 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.kt @@ -214,6 +214,12 @@ class K2JVMCompilerArguments : CommonCompilerArguments() { ) var useOldClassFilesReading: Boolean by FreezableVar(false) + @Argument( + value = "-Xuse-fast-jar-file-system", + description = "Use fast implementation on Jar FS. This may speed up compilation time, but currently it's an experimental mode" + ) + var useFastJarFileSystem: Boolean by FreezableVar(false) + @Argument( value = "-Xdump-declarations-to", valueDescription = "", diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt index 9c32047869e..f0b1bd52d95 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt @@ -68,6 +68,7 @@ import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.STRONG_W import org.jetbrains.kotlin.cli.common.messages.MessageCollector import org.jetbrains.kotlin.cli.common.toBooleanLenient import org.jetbrains.kotlin.cli.jvm.JvmRuntimeVersionsConsistencyChecker +import org.jetbrains.kotlin.cli.jvm.compiler.jarfs.FastJarFileSystem import org.jetbrains.kotlin.cli.jvm.config.* import org.jetbrains.kotlin.cli.jvm.index.* import org.jetbrains.kotlin.cli.jvm.javac.JavacWrapperRegistrar @@ -158,6 +159,8 @@ class KotlinCoreEnvironment private constructor( val configuration: CompilerConfiguration = initialConfiguration.apply { setupJdkClasspathRoots(configFiles) }.copy() + private val jarFileSystem: VirtualFileSystem + init { PersistentFSConstants::class.java.getDeclaredField("ourMaxIntellisenseFileSize") .apply { isAccessible = true } @@ -167,6 +170,12 @@ class KotlinCoreEnvironment private constructor( val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY) + jarFileSystem = when { + configuration.getBoolean(JVMConfigurationKeys.USE_FAST_JAR_FILE_SYSTEM) || + configuration.getBoolean(CommonConfigurationKeys.USE_FIR) -> FastJarFileSystem() + else -> applicationEnvironment.jarFileSystem + } + (projectEnvironment as? ProjectEnvironment)?.registerExtensionsFromPlugins(configuration) // otherwise consider that project environment is properly configured before passing to the environment // TODO: consider some asserts to check important extension points @@ -391,7 +400,7 @@ class KotlinCoreEnvironment private constructor( } private fun findJarRoot(file: File): VirtualFile? = - applicationEnvironment.jarFileSystem.findFileByPath("$file${URLUtil.JAR_SEPARATOR}") + jarFileSystem.findFileByPath("$file${URLUtil.JAR_SEPARATOR}") private fun getSourceRootsCheckingForDuplicates(): List { val uniqueSourceRoots = hashSetOf() diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/jarfs/FastJarFileSystem.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/jarfs/FastJarFileSystem.kt new file mode 100644 index 00000000000..7036e7a0a38 --- /dev/null +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/jarfs/FastJarFileSystem.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ +package org.jetbrains.kotlin.cli.jvm.compiler.jarfs + +import com.intellij.openapi.util.Couple +import com.intellij.openapi.vfs.DeprecatedVirtualFileSystem +import com.intellij.openapi.vfs.StandardFileSystems +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.util.containers.ConcurrentFactoryMap + +class FastJarFileSystem : DeprecatedVirtualFileSystem() { + private val myHandlers: MutableMap = + ConcurrentFactoryMap.createMap { key: String -> FastJarHandler(this@FastJarFileSystem, key) } + + override fun getProtocol(): String { + return StandardFileSystems.JAR_PROTOCOL + } + + override fun findFileByPath(path: String): VirtualFile? { + val pair = splitPath(path) + return myHandlers[pair.first]!!.findFileByPath(pair.second) + } + + override fun refresh(asynchronous: Boolean) {} + override fun refreshAndFindFileByPath(path: String): VirtualFile? { + return findFileByPath(path) + } + + fun clearHandlersCache() { + myHandlers.clear() + } + + companion object { + fun splitPath(path: String): Couple { + val separator = path.indexOf("!/") + require(separator >= 0) { "Path in JarFileSystem must contain a separator: $path" } + val localPath = path.substring(0, separator) + val pathInJar = path.substring(separator + 2) + return Couple.of(localPath, pathInJar) + } + } +} diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/jarfs/FastJarHandler.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/jarfs/FastJarHandler.kt new file mode 100644 index 00000000000..1eb7519b25c --- /dev/null +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/jarfs/FastJarHandler.kt @@ -0,0 +1,165 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ +package org.jetbrains.kotlin.cli.jvm.compiler.jarfs + +import com.intellij.openapi.util.text.StringUtil +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.openapi.vfs.impl.ZipHandler +import com.intellij.util.containers.FactoryMap +import com.intellij.util.io.FileAccessorCache +import com.intellij.util.text.ByteArrayCharSequence +import java.io.File +import java.io.FileNotFoundException +import java.io.IOException +import java.io.RandomAccessFile +import java.util.* + +internal class FastJarHandler(val fileSystem: FastJarFileSystem, path: String) : ZipHandler(path) { + private val myRoot: VirtualFile? + + private val ourEntryMap: Map + private val cachedManifest: ByteArray? + + init { + RandomAccessFile(file, "r").use { randomAccessFile -> + val bufferedRandomAccessFile = BufferedRandomAccessFile(randomAccessFile) + ourEntryMap = bufferedRandomAccessFile.parseCentralDirectory().associateBy { it.relativePath } + cachedManifest = ourEntryMap[MANIFEST_PATH]?.let(bufferedRandomAccessFile::contentsToByteArray) + } + + val entries: MutableMap = HashMap() + val entriesMap = entriesMap + val childrenMap = FactoryMap.create> { ArrayList() } + for (info in entriesMap.values) { + val file = getOrCreateFile(info, entries) + val parent = file.parent + if (parent != null) { + childrenMap[parent]?.add(file) + } + } + + val rootInfo = getEntryInfo("") + myRoot = rootInfo?.let { getOrCreateFile(it, entries) } + + for ((key, childList) in childrenMap) { + key.children = childList.toTypedArray() + } + } + + private fun getOrCreateFile(info: EntryInfo, entries: MutableMap): FastJarVirtualFile { + var file = entries[info] + if (file == null) { + val parent = info.parent + file = FastJarVirtualFile(this, info.shortName, + if (info.isDirectory) -1 else info.length, + info.timestamp, + parent?.let { getOrCreateFile(it, entries) }) + entries[info] = file + } + return file + } + + fun findFileByPath(pathInJar: String): VirtualFile? { + return myRoot?.findFileByRelativePath(pathInJar) + } + + @Throws(IOException::class) + override fun createEntriesMap(): Map { + val mapToEntryInfo = mutableMapOf() + mapToEntryInfo[""] = EntryInfo("", true, DEFAULT_LENGTH, DEFAULT_TIMESTAMP, null) + for (zipEntry in ourEntryMap.values) { + getOrCreate(zipEntry, mapToEntryInfo) + } + + return mapToEntryInfo + } + + private fun getOrCreate(entry: ZipEntryDescription, map: MutableMap): EntryInfo { + var isDirectory = entry.isDirectory + var entryName = entry.relativePath + if (StringUtil.endsWithChar(entryName, '/')) { + entryName = entryName.substring(0, entryName.length - 1) + isDirectory = true + } + if (StringUtil.startsWithChar(entryName, '/') || StringUtil.startsWithChar(entryName, '\\')) { + entryName = entryName.substring(1) + } + + var info = map[entryName] + if (info != null) return info + + val path = splitPathAndFix(entryName) + + val parentInfo = getOrCreateDirectory(path.first, map) + if ("." == path.second) { + return parentInfo + } + info = store(map, parentInfo, path.second, isDirectory, entry.uncompressedSize.toLong(), 0, path.third) + return info + } + + private fun getOrCreateDirectory(entryName: String, map: MutableMap): EntryInfo { + var info = map[entryName] + + if (info == null) { + val entry = ourEntryMap["$entryName/"] + if (entry != null) { + return getOrCreate(entry, map) + } + val path = splitPathAndFix(entryName) + require(entryName != path.first) { + "invalid entry name: '" + entryName + "' in " + this.file.absolutePath + "; after split: " + path + } + val parentInfo = getOrCreateDirectory(path.first, map) + info = store(map, parentInfo, path.second, true, DEFAULT_LENGTH, DEFAULT_TIMESTAMP, path.third) + } + + return info + } + + private fun store( + map: MutableMap, + parentInfo: EntryInfo?, + shortName: CharSequence, + isDirectory: Boolean, + size: Long, + time: Long, + entryName: String + ): EntryInfo { + val sequence = ByteArrayCharSequence.convertToBytesIfPossible(shortName) + val info = EntryInfo(sequence, isDirectory, size, time, parentInfo) + map[entryName] = info + return info + } + + override fun contentsToByteArray(relativePath: String): ByteArray { + if (relativePath == MANIFEST_PATH) return cachedManifest ?: throw FileNotFoundException("$file!/$relativePath") + val zipEntryDescription = ourEntryMap[relativePath] ?: throw FileNotFoundException("$file!/$relativePath") + return cachedOpenFileHandles[file].use { + synchronized(it) { + it.get().contentsToByteArray(zipEntryDescription) + } + } + } +} + +private const val MANIFEST_PATH = "META-INF/MANIFEST.MF" + +private val cachedOpenFileHandles: FileAccessorCache = + object : FileAccessorCache(20, 10) { + @Throws(IOException::class) + override fun createAccessor(file: File): BufferedRandomAccessFile { + return BufferedRandomAccessFile(RandomAccessFile(file, "r")) + } + + @Throws(IOException::class) + override fun disposeAccessor(fileAccessor: BufferedRandomAccessFile) { + fileAccessor.close() + } + + override fun isEqual(val1: File, val2: File): Boolean { + return val1 == val2 // reference equality to handle different jars for different ZipHandlers on the same path + } + } diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/jarfs/FastJarVirtualFile.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/jarfs/FastJarVirtualFile.kt new file mode 100644 index 00000000000..d2c70e10167 --- /dev/null +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/jarfs/FastJarVirtualFile.kt @@ -0,0 +1,105 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ +package org.jetbrains.kotlin.cli.jvm.compiler.jarfs + +import com.intellij.openapi.util.Couple +import com.intellij.openapi.util.io.BufferExposingByteArrayInputStream +import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.openapi.vfs.VirtualFileSystem +import java.io.IOException +import java.io.InputStream +import java.io.OutputStream + +internal class FastJarVirtualFile( + private val myHandler: FastJarHandler, + private val myName: CharSequence, + private val myLength: Long, + private val myTimestamp: Long, + parent: FastJarVirtualFile? +) : VirtualFile() { + private val myParent: VirtualFile? = parent + private var myChildren = EMPTY_ARRAY + fun setChildren(children: Array) { + myChildren = children + } + + override fun getName(): String { + return myName.toString() + } + + override fun getNameSequence(): CharSequence { + return myName + } + + override fun getFileSystem(): VirtualFileSystem { + return myHandler.fileSystem + } + + override fun getPath(): String { + if (myParent == null) { + return FileUtil.toSystemIndependentName(myHandler.file.path) + "!/" + } + val parentPath = myParent.path + val answer = StringBuilder(parentPath.length + 1 + myName.length) + answer.append(parentPath) + if (answer[answer.length - 1] != '/') { + answer.append('/') + } + answer.append(myName) + return answer.toString() + } + + override fun isWritable(): Boolean { + return false + } + + override fun isDirectory(): Boolean { + return myLength < 0 + } + + override fun isValid(): Boolean { + return true + } + + override fun getParent(): VirtualFile? { + return myParent + } + + override fun getChildren(): Array { + return myChildren + } + + @Throws(IOException::class) + override fun getOutputStream(requestor: Any, newModificationStamp: Long, newTimeStamp: Long): OutputStream { + throw UnsupportedOperationException("JarFileSystem is read-only") + } + + @Throws(IOException::class) + override fun contentsToByteArray(): ByteArray { + val pair: Couple = FastJarFileSystem.splitPath( + path + ) + return myHandler.contentsToByteArray(pair.second) + } + + override fun getTimeStamp(): Long { + return myTimestamp + } + + override fun getLength(): Long { + return myLength + } + + override fun refresh(asynchronous: Boolean, recursive: Boolean, postRunnable: Runnable?) {} + @Throws(IOException::class) + override fun getInputStream(): InputStream { + return BufferExposingByteArrayInputStream(contentsToByteArray()) + } + + override fun getModificationStamp(): Long { + return 0 + } +} diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/jarfs/ZipImplementation.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/jarfs/ZipImplementation.kt new file mode 100644 index 00000000000..181452d2eb0 --- /dev/null +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/jarfs/ZipImplementation.kt @@ -0,0 +1,242 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.cli.jvm.compiler.jarfs + +import java.io.File +import java.io.RandomAccessFile +import java.util.zip.Inflater + + +class ZipEntryDescription( + val relativePath: String, + val compressedSize: Int, + val uncompressedSize: Int, + val offsetInFile: Int, + val compressionKind: CompressionKind, + val fileNameSize: Int +) { + + enum class CompressionKind { + PLAIN, DEFLATE + } + + val isDirectory get() = uncompressedSize == 0 +} + +private const val END_OF_CENTRAL_DIR_SIZE = 22 +private const val LOCAL_FILE_HEADER_EXTRA_OFFSET = 28 +private const val LOCAL_FILE_HEADER_SIZE = LOCAL_FILE_HEADER_EXTRA_OFFSET + 2 + +fun File.contentsToByteArray(zipEntryDescription: ZipEntryDescription): ByteArray { + return RandomAccessFile(this, "r").use { randomAccessFile -> + val bufferedRandomAccessFile = BufferedRandomAccessFile(randomAccessFile) + bufferedRandomAccessFile.contentsToByteArray(zipEntryDescription) + } +} + +fun BufferedRandomAccessFile.contentsToByteArray( + zipEntryDescription: ZipEntryDescription +): ByteArray { + val extraSize = + readShortLittleEndianFromOffset((zipEntryDescription.offsetInFile + LOCAL_FILE_HEADER_EXTRA_OFFSET).toLong()) + + seek( + (zipEntryDescription.offsetInFile + LOCAL_FILE_HEADER_SIZE + zipEntryDescription.fileNameSize + extraSize).toLong() + ) + val compressed = ByteArray(zipEntryDescription.compressedSize + 1) + readFully(compressed, zipEntryDescription.compressedSize) + + return when (zipEntryDescription.compressionKind) { + ZipEntryDescription.CompressionKind.DEFLATE -> { + val inflater = Inflater(true) + inflater.setInput(compressed, 0, zipEntryDescription.compressedSize) + + val result = ByteArray(zipEntryDescription.uncompressedSize) + + inflater.inflate(result) + + result + } + ZipEntryDescription.CompressionKind.PLAIN -> compressed.copyOf(zipEntryDescription.compressedSize) + } +} + +fun File.parseCentralDirectory(): List { + return RandomAccessFile(this, "r").use { randomAccessFile -> + BufferedRandomAccessFile(randomAccessFile).parseCentralDirectory() + } +} + +fun BufferedRandomAccessFile.parseCentralDirectory(): List { + val randomAccessFile = randomAccessFile + + val endOfCentralDirectoryOffset = (randomAccessFile.length() - END_OF_CENTRAL_DIR_SIZE downTo 0).first { offset -> + // header of "End of central directory" + randomAccessFile.readIntLittleEndianFromOffset(offset) == 0x06054b50 + } + + val entriesNumber = randomAccessFile.readShortLittleEndianFromOffset(endOfCentralDirectoryOffset + 10) + val offsetOfCentralDirectory = randomAccessFile.readIntLittleEndianFromOffset(endOfCentralDirectoryOffset + 16).toLong() + + var currentOffset = offsetOfCentralDirectory + + val result = mutableListOf() + for (i in 0 until entriesNumber) { + val headerConst = readIntLittleEndianFromOffset(currentOffset) + require(headerConst == 0x02014b50) { + "$i: $headerConst" + } + + val versionNeededToExtract = + readShortLittleEndianFromOffset(currentOffset + 6) + + val compressionMethod = readShortLittleEndianFromOffset(currentOffset + 10) + + val compressedSize = readIntLittleEndianFromOffset(currentOffset + 20) + val uncompressedSize = readIntLittleEndian() + val fileNameLength = readShortLittleEndian() + val extraLength = readShortLittleEndian() + val fileCommentLength = readShortLittleEndian() + + val offsetOfFileData = readIntLittleEndianFromOffset(currentOffset + 42) + + val name = readString(fileNameLength) + + currentOffset += 46 + fileNameLength + extraLength + fileCommentLength + + require(versionNeededToExtract == 10 || versionNeededToExtract == 20) { + "Unexpected versionNeededToExtract ($versionNeededToExtract) at $name" + } + + val compressionKind = when (compressionMethod) { + 0 -> ZipEntryDescription.CompressionKind.PLAIN + 8 -> ZipEntryDescription.CompressionKind.DEFLATE + else -> error("Unexpected compression method ($compressionMethod) at $name") + } + + result += ZipEntryDescription( + name, compressedSize, uncompressedSize, offsetOfFileData, compressionKind, + fileNameLength + ) + } + + return result +} + +class BufferedRandomAccessFile( + val randomAccessFile: RandomAccessFile +) { + private val buffer = ByteArray(BUFFER_SIZE) + private var offsetInBuffer: Int = -1 + private var currentBegin: Long = -1L + + fun seek(globalOffset: Long, force: Boolean = false) { + if (!force && currentBegin != -1L && globalOffset >= currentBegin && globalOffset < currentBegin + BUFFER_SIZE) { + offsetInBuffer = (globalOffset - currentBegin).toInt() + return + } + offsetInBuffer = 0 + currentBegin = globalOffset + randomAccessFile.seek(globalOffset) + randomAccessFile.readFully(buffer, 0, minOf(randomAccessFile.length() - globalOffset, BUFFER_SIZE.toLong()).toInt()) + } + + fun read(): Int { + require(offsetInBuffer < BUFFER_SIZE) + return buffer[offsetInBuffer++].let { + if (offsetInBuffer == BUFFER_SIZE && currentBegin + BUFFER_SIZE < randomAccessFile.length()) { + seek(currentBegin + BUFFER_SIZE) + } + + java.lang.Byte.toUnsignedInt(it) + } + } + + fun readString(length: Int): String { + if (length > BUFFER_SIZE) { + randomAccessFile.seek(currentBegin + offsetInBuffer) + val byteArray = ByteArray(length) + randomAccessFile.readFully(byteArray) + return String(byteArray) + } + + if (length > BUFFER_SIZE - offsetInBuffer) { + seek(currentBegin + offsetInBuffer, force = true) + } + + return String(buffer, offsetInBuffer, length) + } + + fun readFully(dst: ByteArray, length: Int) { + if (length > BUFFER_SIZE) { + randomAccessFile.seek(currentBegin + offsetInBuffer) + randomAccessFile.readFully(dst, 0, length) + return + } + + if (length > BUFFER_SIZE - offsetInBuffer) { + seek(currentBegin + offsetInBuffer, force = true) + } + + System.arraycopy(buffer, offsetInBuffer, dst, 0, length) + } + + fun close() { + randomAccessFile.close() + } + + companion object { + private const val BUFFER_SIZE = 50000 + } +} + +private fun RandomAccessFile.readIntLittleEndianFromOffset(offset: Long): Int { + seek(offset) + return readIntLittleEndian() +} + +private fun RandomAccessFile.readIntLittleEndian(): Int { + val a = this.read() + val b = this.read() + val c = this.read() + val d = this.read() + return (d shl 24) + (c shl 16) + (b shl 8) + a +} + +private fun RandomAccessFile.readShortLittleEndianFromOffset(offset: Long): Int { + seek(offset) + return readShortLittleEndian() +} + +private fun RandomAccessFile.readShortLittleEndian(): Int { + val a = this.read() + val b = this.read() + return (b shl 8) + a +} + +private fun BufferedRandomAccessFile.readIntLittleEndianFromOffset(offset: Long): Int { + seek(offset) + return readIntLittleEndian() +} + +private fun BufferedRandomAccessFile.readIntLittleEndian(): Int { + val a = this.read() + val b = this.read() + val c = this.read() + val d = this.read() + return (d shl 24) + (c shl 16) + (b shl 8) + a +} + +private fun BufferedRandomAccessFile.readShortLittleEndianFromOffset(offset: Long): Int { + seek(offset) + return readShortLittleEndian() +} + +private fun BufferedRandomAccessFile.readShortLittleEndian(): Int { + val a = this.read() + val b = this.read() + return (b shl 8) + a +} diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/jvmArguments.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/jvmArguments.kt index 1eae0f92f8b..f403032344c 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/jvmArguments.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/jvmArguments.kt @@ -280,11 +280,16 @@ fun CompilerConfiguration.configureAdvancedJvmOptions(arguments: K2JVMCompilerAr put(JVMConfigurationKeys.USE_TYPE_TABLE, arguments.useTypeTable) put(JVMConfigurationKeys.SKIP_RUNTIME_VERSION_CHECK, arguments.skipRuntimeVersionCheck) put(JVMConfigurationKeys.USE_PSI_CLASS_FILES_READING, arguments.useOldClassFilesReading) + put(JVMConfigurationKeys.USE_FAST_JAR_FILE_SYSTEM, arguments.useFastJarFileSystem) if (arguments.useOldClassFilesReading) { messageCollector.report(INFO, "Using the old java class files reading implementation") } + if (arguments.useFastJarFileSystem) { + messageCollector.report(INFO, "Using fast Jar FS implementation") + } + put(CLIConfigurationKeys.ALLOW_KOTLIN_PACKAGE, arguments.allowKotlinPackage) put(JVMConfigurationKeys.USE_SINGLE_MODULE, arguments.singleModule) put(JVMConfigurationKeys.USE_OLD_SPILLED_VAR_TYPE_ANALYSIS, arguments.useOldSpilledVarTypeAnalysis) diff --git a/compiler/config.jvm/src/org/jetbrains/kotlin/config/JVMConfigurationKeys.java b/compiler/config.jvm/src/org/jetbrains/kotlin/config/JVMConfigurationKeys.java index 28d35ab5b83..a76435685b0 100644 --- a/compiler/config.jvm/src/org/jetbrains/kotlin/config/JVMConfigurationKeys.java +++ b/compiler/config.jvm/src/org/jetbrains/kotlin/config/JVMConfigurationKeys.java @@ -99,6 +99,9 @@ public class JVMConfigurationKeys { public static final CompilerConfigurationKey USE_PSI_CLASS_FILES_READING = CompilerConfigurationKey.create("use a slower (PSI-based) class files reading implementation"); + public static final CompilerConfigurationKey USE_FAST_JAR_FILE_SYSTEM = + CompilerConfigurationKey.create("use a faster JAR filesystem implementation"); + public static final CompilerConfigurationKey USE_JAVAC = CompilerConfigurationKey.create("use javac [experimental]"); diff --git a/compiler/testData/cli/jvm/extraHelp.out b/compiler/testData/cli/jvm/extraHelp.out index c1e27203736..a5764ab19aa 100644 --- a/compiler/testData/cli/jvm/extraHelp.out +++ b/compiler/testData/cli/jvm/extraHelp.out @@ -135,6 +135,7 @@ where advanced options include: Suppress the "cannot access built-in declaration" error (useful with -no-stdlib) -Xtype-enhancement-improvements-strict-mode Enable strict mode for some improvements in the type enhancement for loaded Java types based on nullability annotations,including freshly supported reading of the type use annotations from class files. See KT-45671 for more details + -Xuse-fast-jar-file-system Use fast implementation on Jar FS. This may speed up compilation time, but currently it's an experimental mode -Xuse-ir Use the IR backend. This option has no effect unless the language version less than 1.5 is used -Xuse-javac Use javac for Java source and class files analysis -Xuse-old-backend Use the old JVM backend diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/CompilerConfigurationProvider.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/CompilerConfigurationProvider.kt index 762ee4fe90c..50a8bbd49ce 100644 --- a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/CompilerConfigurationProvider.kt +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/CompilerConfigurationProvider.kt @@ -26,6 +26,7 @@ import org.jetbrains.kotlin.platform.konan.isNative import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.test.ApplicationEnvironmentDisposer import org.jetbrains.kotlin.test.TestInfrastructureInternals +import org.jetbrains.kotlin.test.model.FrontendKinds import org.jetbrains.kotlin.test.model.TestFile import org.jetbrains.kotlin.test.model.TestModule import java.io.File @@ -98,6 +99,10 @@ open class CompilerConfigurationProviderImpl( val configuration = CompilerConfiguration() configuration[CommonConfigurationKeys.MODULE_NAME] = module.name + if (module.frontendKind == FrontendKinds.FIR) { + configuration[CommonConfigurationKeys.USE_FIR] = true + } + configuration[CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY] = object : MessageCollector { override fun clear() {}