Introduce new string table optimized for JVM class files
This format of the string table allows to reduce the size of the Kotlin metadata in JVM class files by reusing constants already present in the constant pool. Currently the string table produced by JvmStringTable is not fully optimized in serialization (in particular, the 'substring' operation which will be used to extract type names out of generic signature, is not used at all), but the format and its complete support in the deserialization (JvmNameResolver) allows future improvement without changing the binary version
This commit is contained in:
@@ -22,6 +22,45 @@ import "core/deserialization/src/descriptors.proto";
|
||||
option java_outer_classname = "JvmProtoBuf";
|
||||
option optimize_for = LITE_RUNTIME;
|
||||
|
||||
message StringTableTypes {
|
||||
message Record {
|
||||
// The number of times this record should be repeated; this is used to collapse identical subsequent records in the list
|
||||
optional int32 range = 1 [default = 1];
|
||||
|
||||
// Index of the predefined constant. If this field is present, the associated string is ignored
|
||||
optional int32 predefined_index = 2;
|
||||
|
||||
enum Operation {
|
||||
NONE = 0;
|
||||
|
||||
// replaceAll('$', '.')
|
||||
// java/util/Map$Entry -> java/util/Map.Entry;
|
||||
INTERNAL_TO_CLASS_ID = 1;
|
||||
|
||||
// substring(1, length - 1) and then replaceAll('$', '.')
|
||||
// Ljava/util/Map$Entry; -> java/util/Map.Entry
|
||||
DESC_TO_CLASS_ID = 2;
|
||||
}
|
||||
|
||||
// Perform a described operation on the string
|
||||
optional Operation operation = 3 [default = NONE];
|
||||
|
||||
// If this field is present, the "substring" operation must be performed with the first element of this list as the start index,
|
||||
// and the second element as the end index.
|
||||
// If an operation is not NONE, it's applied _after_ this substring operation
|
||||
repeated int32 substring_index = 4 [packed = true];
|
||||
|
||||
// If this field is present, the "replaceAll" operation must be performed with the first element of this list as the code point
|
||||
// of the character to replace, and the second element as the code point of the replacement character
|
||||
repeated int32 replace_char = 5 [packed = true];
|
||||
}
|
||||
|
||||
repeated Record record = 1;
|
||||
|
||||
// Indices of strings which are names of local classes or anonymous objects
|
||||
repeated int32 local_name = 5 [packed = true];
|
||||
}
|
||||
|
||||
message JvmMethodSignature {
|
||||
required int32 name = 1 [(string_id_in_table) = true];
|
||||
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.load.kotlin
|
||||
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.serialization.deserialization.NameResolver
|
||||
import org.jetbrains.kotlin.serialization.jvm.JvmProtoBuf
|
||||
import org.jetbrains.kotlin.serialization.jvm.JvmProtoBuf.StringTableTypes.Record
|
||||
import org.jetbrains.kotlin.serialization.jvm.JvmProtoBuf.StringTableTypes.Record.Operation.DESC_TO_CLASS_ID
|
||||
import org.jetbrains.kotlin.serialization.jvm.JvmProtoBuf.StringTableTypes.Record.Operation.INTERNAL_TO_CLASS_ID
|
||||
import org.jetbrains.kotlin.serialization.jvm.JvmProtoBuf.StringTableTypes.Record.Operation.NONE
|
||||
import java.util.*
|
||||
|
||||
class JvmNameResolver(
|
||||
private val types: JvmProtoBuf.StringTableTypes,
|
||||
private val strings: Array<String>
|
||||
) : NameResolver {
|
||||
private val localNameIndices = types.localNameList.run { if (isEmpty()) emptySet() else toSet() }
|
||||
|
||||
// Here we expand the 'range' field of the Record message for simplicity to a list of records
|
||||
private val records: List<Record> = ArrayList<Record>().apply {
|
||||
val records = types.recordList
|
||||
this.ensureCapacity(records.size())
|
||||
for (record in records) {
|
||||
repeat(record.range) {
|
||||
this.add(record)
|
||||
}
|
||||
}
|
||||
this.trimToSize()
|
||||
}
|
||||
|
||||
override fun getString(index: Int): String {
|
||||
val record = records[index]
|
||||
|
||||
var string =
|
||||
if (record.hasPredefinedIndex() && record.predefinedIndex in PREDEFINED_STRINGS.indices)
|
||||
PREDEFINED_STRINGS[record.predefinedIndex]
|
||||
else strings[index]
|
||||
|
||||
if (record.substringIndexCount >= 2) {
|
||||
val (begin, end) = record.substringIndexList
|
||||
if (0 <= begin && begin <= end && end <= string.length()) {
|
||||
string = string.substring(begin, end)
|
||||
}
|
||||
}
|
||||
|
||||
if (record.replaceCharCount >= 2) {
|
||||
val (from, to) = record.replaceCharList
|
||||
string = string.replace(from.toChar(), to.toChar())
|
||||
}
|
||||
|
||||
when (record.operation ?: NONE) {
|
||||
NONE -> {
|
||||
// Do nothing
|
||||
}
|
||||
INTERNAL_TO_CLASS_ID -> {
|
||||
string = string.replace('$', '.')
|
||||
}
|
||||
DESC_TO_CLASS_ID -> {
|
||||
if (string.length() >= 2) {
|
||||
string = string.substring(1, string.length() - 1)
|
||||
}
|
||||
string = string.replace('$', '.')
|
||||
}
|
||||
}
|
||||
|
||||
return string
|
||||
}
|
||||
|
||||
override fun getName(index: Int) = Name.guess(getString(index))
|
||||
|
||||
override fun getClassId(index: Int): ClassId {
|
||||
val string = getString(index)
|
||||
val lastSlash = string.lastIndexOf('/')
|
||||
val packageName =
|
||||
if (lastSlash < 0) FqName.ROOT
|
||||
else FqName(string.substring(0, lastSlash).replace('/', '.'))
|
||||
val className = FqName(string.substring(lastSlash + 1))
|
||||
return ClassId(packageName, className, index in localNameIndices)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val PREDEFINED_STRINGS = listOf(
|
||||
"kotlin/Any",
|
||||
"kotlin/Nothing",
|
||||
"kotlin/Unit",
|
||||
"kotlin/Throwable",
|
||||
"kotlin/Number",
|
||||
|
||||
"kotlin/Byte", "kotlin/Double", "kotlin/Float", "kotlin/Int",
|
||||
"kotlin/Long", "kotlin/Short", "kotlin/Boolean", "kotlin/Char",
|
||||
|
||||
"kotlin/CharSequence",
|
||||
"kotlin/String",
|
||||
"kotlin/Comparable",
|
||||
"kotlin/Enum",
|
||||
|
||||
"kotlin/Array",
|
||||
"kotlin/ByteArray", "kotlin/DoubleArray", "kotlin/FloatArray", "kotlin/IntArray",
|
||||
"kotlin/LongArray", "kotlin/ShortArray", "kotlin/BooleanArray", "kotlin/CharArray",
|
||||
|
||||
"kotlin/Cloneable",
|
||||
"kotlin/Annotation",
|
||||
|
||||
"kotlin/Iterable", "kotlin/MutableIterable",
|
||||
"kotlin/Collection", "kotlin/MutableCollection",
|
||||
"kotlin/List", "kotlin/MutableList",
|
||||
"kotlin/Set", "kotlin/MutableSet",
|
||||
"kotlin/Map", "kotlin/MutableMap",
|
||||
"kotlin/Map.Entry", "kotlin/MutableMap.MutableEntry",
|
||||
|
||||
"kotlin/Iterator", "kotlin/MutableIterator",
|
||||
"kotlin/ListIterator", "kotlin/MutableListIterator"
|
||||
)
|
||||
|
||||
private val PREDEFINED_STRINGS_MAP = PREDEFINED_STRINGS.withIndex().toMap({ it.value }, { it.index })
|
||||
|
||||
fun getPredefinedStringIndex(string: String): Int? = PREDEFINED_STRINGS_MAP[string]
|
||||
}
|
||||
}
|
||||
+1904
File diff suppressed because it is too large
Load Diff
+3
-3
@@ -17,10 +17,10 @@
|
||||
package org.jetbrains.kotlin.serialization.jvm
|
||||
|
||||
import com.google.protobuf.ExtensionRegistryLite
|
||||
import org.jetbrains.kotlin.load.kotlin.JvmNameResolver
|
||||
import org.jetbrains.kotlin.serialization.ClassData
|
||||
import org.jetbrains.kotlin.serialization.PackageData
|
||||
import org.jetbrains.kotlin.serialization.ProtoBuf
|
||||
import org.jetbrains.kotlin.serialization.deserialization.NameResolverImpl
|
||||
import java.io.ByteArrayInputStream
|
||||
|
||||
public object JvmProtoBufUtil {
|
||||
@@ -37,7 +37,7 @@ public object JvmProtoBufUtil {
|
||||
@JvmStatic
|
||||
public fun readClassDataFrom(bytes: ByteArray, strings: Array<String>): ClassData {
|
||||
val input = ByteArrayInputStream(bytes)
|
||||
val nameResolver = NameResolverImpl.read(input)
|
||||
val nameResolver = JvmNameResolver(JvmProtoBuf.StringTableTypes.parseDelimitedFrom(input, EXTENSION_REGISTRY), strings)
|
||||
val classProto = ProtoBuf.Class.parseFrom(input, EXTENSION_REGISTRY)
|
||||
return ClassData(nameResolver, classProto)
|
||||
}
|
||||
@@ -49,7 +49,7 @@ public object JvmProtoBufUtil {
|
||||
@JvmStatic
|
||||
public fun readPackageDataFrom(bytes: ByteArray, strings: Array<String>): PackageData {
|
||||
val input = ByteArrayInputStream(bytes)
|
||||
val nameResolver = NameResolverImpl.read(input)
|
||||
val nameResolver = JvmNameResolver(JvmProtoBuf.StringTableTypes.parseDelimitedFrom(input, EXTENSION_REGISTRY), strings)
|
||||
val packageProto = ProtoBuf.Package.parseFrom(input, EXTENSION_REGISTRY)
|
||||
return PackageData(nameResolver, packageProto)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user