Skip to content

Commit 1036506

Browse files
committed
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
1 parent 542bfab commit 1036506

File tree

19 files changed

+4719
-67
lines changed

19 files changed

+4719
-67
lines changed

compiler/backend/src/org/jetbrains/kotlin/codegen/AsmUtil.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@
4646
import org.jetbrains.kotlin.resolve.jvm.JvmPackage;
4747
import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType;
4848
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin;
49+
import org.jetbrains.kotlin.serialization.DescriptorSerializer;
50+
import org.jetbrains.kotlin.codegen.serialization.JvmStringTable;
4951
import org.jetbrains.kotlin.serialization.jvm.BitEncoding;
5052
import org.jetbrains.kotlin.synthetic.SyntheticJavaPropertyDescriptor;
5153
import org.jetbrains.kotlin.types.JetType;
@@ -842,15 +844,17 @@ public static void writeKotlinSyntheticClassAnnotation(@NotNull ClassBuilder v,
842844
av.visitEnd();
843845
}
844846

845-
public static void writeAnnotationData(@NotNull AnnotationVisitor av, @NotNull byte[] bytes) {
847+
public static void writeAnnotationData(@NotNull AnnotationVisitor av, @NotNull DescriptorSerializer serializer, @NotNull byte[] bytes) {
846848
JvmCodegenUtil.writeAbiVersion(av);
847849
AnnotationVisitor data = av.visitArray(JvmAnnotationNames.DATA_FIELD_NAME);
848850
for (String string : BitEncoding.encodeBytes(bytes)) {
849851
data.visit(null, string);
850852
}
851853
data.visitEnd();
852854
AnnotationVisitor strings = av.visitArray(JvmAnnotationNames.STRINGS_FIELD_NAME);
853-
// TODO: write the actual string table
855+
for (String string : ((JvmStringTable) serializer.getStringTable()).getStrings()) {
856+
strings.visit(null, string);
857+
}
854858
strings.visitEnd();
855859
}
856860

compiler/backend/src/org/jetbrains/kotlin/codegen/ClosureCodegen.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ protected void generateKotlinAnnotation() {
230230
ProtoBuf.Callable callableProto = serializer.callableProto(funDescriptor).build();
231231

232232
AnnotationVisitor av = v.getVisitor().visitAnnotation(asmDescByFqNameWithoutInnerClasses(JvmAnnotationNames.KOTLIN_CALLABLE), true);
233-
writeAnnotationData(av, serializer.serialize(callableProto));
233+
writeAnnotationData(av, serializer, serializer.serialize(callableProto));
234234
av.visitEnd();
235235
}
236236

compiler/backend/src/org/jetbrains/kotlin/codegen/ImplementationBodyCodegen.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ else if (isTopLevelOrInnerClass(descriptor)) {
253253
ProtoBuf.Class classProto = serializer.classProto(descriptor).build();
254254

255255
AnnotationVisitor av = v.getVisitor().visitAnnotation(asmDescByFqNameWithoutInnerClasses(JvmAnnotationNames.KOTLIN_CLASS), true);
256-
writeAnnotationData(av, serializer.serialize(classProto));
256+
writeAnnotationData(av, serializer, serializer.serialize(classProto));
257257
if (kind != null) {
258258
av.visitEnum(
259259
JvmAnnotationNames.KIND_FIELD_NAME,

compiler/backend/src/org/jetbrains/kotlin/codegen/JvmSerializerExtension.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.jetbrains.annotations.NotNull;
2121
import org.jetbrains.annotations.Nullable;
2222
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
23+
import org.jetbrains.kotlin.codegen.serialization.JvmStringTable;
2324
import org.jetbrains.kotlin.codegen.state.JetTypeMapper;
2425
import org.jetbrains.kotlin.descriptors.*;
2526
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor;
@@ -39,7 +40,7 @@
3940
public class JvmSerializerExtension extends SerializerExtension {
4041
private final JvmSerializationBindings bindings;
4142
private final JetTypeMapper typeMapper;
42-
private final StringTableImpl stringTable = new StringTableImpl(this);
43+
private final StringTable stringTable = new JvmStringTable(this);
4344
private final AnnotationSerializer annotationSerializer = new AnnotationSerializer(stringTable);
4445

4546
public JvmSerializerExtension(@NotNull JvmSerializationBindings bindings, @NotNull JetTypeMapper typeMapper) {

compiler/backend/src/org/jetbrains/kotlin/codegen/MultifileClassPartCodegen.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public class MultifileClassPartCodegen(
9595
if (packageProto.memberCount == 0) return
9696

9797
val av = v.newAnnotation(AsmUtil.asmDescByFqNameWithoutInnerClasses(JvmAnnotationNames.KOTLIN_MULTIFILE_CLASS_PART), true)
98-
AsmUtil.writeAnnotationData(av, serializer.serialize(packageProto))
98+
AsmUtil.writeAnnotationData(av, serializer, serializer.serialize(packageProto))
9999
av.visit(JvmAnnotationNames.MULTIFILE_CLASS_NAME_FIELD_NAME, multifileClassFqName.shortName().asString())
100100
av.visitEnd()
101101
}

compiler/backend/src/org/jetbrains/kotlin/codegen/PackageCodegen.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ private void writeKotlinPackageAnnotationIfNeeded(@NotNull JvmSerializationBindi
290290
ProtoBuf.Package packageProto = serializer.packageProtoWithoutDescriptors().build();
291291

292292
AnnotationVisitor av = v.newAnnotation(asmDescByFqNameWithoutInnerClasses(JvmAnnotationNames.KOTLIN_PACKAGE), true);
293-
AsmUtil.writeAnnotationData(av, serializer.serialize(packageProto));
293+
AsmUtil.writeAnnotationData(av, serializer, serializer.serialize(packageProto));
294294
av.visitEnd();
295295
}
296296

compiler/backend/src/org/jetbrains/kotlin/codegen/PackagePartCodegen.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ protected void generateKotlinAnnotation() {
128128
if (packageProto.getMemberCount() == 0) return;
129129

130130
AnnotationVisitor av = v.newAnnotation(asmDescByFqNameWithoutInnerClasses(JvmAnnotationNames.KOTLIN_FILE_FACADE), true);
131-
writeAnnotationData(av, serializer.serialize(packageProto));
131+
writeAnnotationData(av, serializer, serializer.serialize(packageProto));
132132
av.visitEnd();
133133
}
134134
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/*
2+
* Copyright 2010-2015 JetBrains s.r.o.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.jetbrains.kotlin.codegen.serialization
18+
19+
import org.jetbrains.kotlin.descriptors.ClassDescriptor
20+
import org.jetbrains.kotlin.descriptors.ClassOrPackageFragmentDescriptor
21+
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
22+
import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor
23+
import org.jetbrains.kotlin.load.kotlin.JvmNameResolver
24+
import org.jetbrains.kotlin.resolve.descriptorUtil.classId
25+
import org.jetbrains.kotlin.serialization.SerializerExtension
26+
import org.jetbrains.kotlin.serialization.StringTable
27+
import org.jetbrains.kotlin.serialization.jvm.JvmProtoBuf
28+
import org.jetbrains.kotlin.serialization.jvm.JvmProtoBuf.StringTableTypes.Record
29+
import org.jetbrains.kotlin.types.ErrorUtils
30+
import java.io.OutputStream
31+
import java.util.*
32+
33+
class JvmStringTable(private val extension: SerializerExtension) : StringTable {
34+
public val strings = ArrayList<String>()
35+
private val records = ArrayList<Record>()
36+
private val map = HashMap<String, Int>()
37+
private val localNames = HashSet<Int>()
38+
39+
override fun getStringIndex(string: String): Int =
40+
map.getOrPut(string) {
41+
strings.size().apply {
42+
strings.add(string)
43+
records.add(Record.newBuilder().apply {
44+
// TODO: optimize, don't always store the operation
45+
setOperation(Record.Operation.NONE)
46+
}.build())
47+
}
48+
}
49+
50+
override fun getFqNameIndex(descriptor: ClassDescriptor): Int {
51+
if (ErrorUtils.isError(descriptor)) {
52+
throw IllegalStateException("Cannot get FQ name of error class: " + descriptor)
53+
}
54+
55+
val string: String
56+
val storeAsLiteral: Boolean
57+
58+
val parent = sequence(descriptor, DeclarationDescriptor::getContainingDeclaration).first { it !is ClassDescriptor }
59+
if (parent is PackageFragmentDescriptor) {
60+
val classId = descriptor.classId
61+
val packageName = classId.packageFqName
62+
val className = classId.relativeClassName.asString()
63+
string =
64+
if (packageName.isRoot) className
65+
else packageName.asString().replace('.', '/') + "/" + className
66+
67+
// If any of the class names contains '$', we can't simply replace all '$' with '.' upon deserialization.
68+
// This case is rather rare, so we're storing a literal string
69+
storeAsLiteral = '$' in string && classId.relativeClassName.pathSegments().any { '$' in it.asString() }
70+
}
71+
else {
72+
storeAsLiteral = true
73+
string = descriptor.localClassName()
74+
}
75+
76+
val isLocal = descriptor.containingDeclaration !is ClassOrPackageFragmentDescriptor
77+
78+
map[string]?.let { recordedIndex ->
79+
if (isLocal == (recordedIndex in localNames)) {
80+
return recordedIndex
81+
}
82+
}
83+
84+
val index = strings.size()
85+
if (isLocal) {
86+
localNames.add(index)
87+
}
88+
89+
val record = Record.newBuilder()
90+
91+
if (storeAsLiteral) {
92+
strings.add(string)
93+
// TODO: optimize, don't always store the operation
94+
record.setOperation(Record.Operation.NONE)
95+
}
96+
else {
97+
val predefinedIndex = JvmNameResolver.getPredefinedStringIndex(string)
98+
if (predefinedIndex != null) {
99+
record.setPredefinedIndex(predefinedIndex)
100+
record.setOperation(Record.Operation.NONE)
101+
// TODO: move all records with predefined names to the end and do not write associated strings for them (since they are ignored)
102+
strings.add("")
103+
}
104+
else {
105+
record.setOperation(Record.Operation.DESC_TO_CLASS_ID)
106+
strings.add("L$string;")
107+
}
108+
}
109+
110+
records.add(record.build())
111+
112+
map[string] = index
113+
114+
return index
115+
}
116+
117+
private fun ClassDescriptor.localClassName(): String {
118+
val container = containingDeclaration
119+
return when (container) {
120+
is ClassDescriptor -> container.localClassName() + "." + name.asString()
121+
else -> extension.getLocalClassName(this)
122+
}
123+
}
124+
125+
override fun serializeTo(output: OutputStream) {
126+
with(JvmProtoBuf.StringTableTypes.newBuilder()) {
127+
addAllRecord(records)
128+
addAllLocalName(localNames)
129+
build().writeDelimitedTo(output)
130+
}
131+
}
132+
}

compiler/testData/codegen/bytecodeText/interfaces/noPrivateMemberInJavaInterface.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@ interface A {
1212
}
1313
}
1414

15-
// 1 foo
15+
// 1 foo\(
1616
// 1 getProp
17-
// 1 defaultFun\$
17+
// 1 defaultFun\$

compiler/tests/org/jetbrains/kotlin/serialization/DebugProtoBuf.java

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)