Extract descriptor-related behavior in StringTable to separate interface

Descriptor-related code is only needed to be able to compute
"replacement" names for local classes, which are different in JVM and JS
This commit is contained in:
Alexander Udalov
2018-03-09 17:29:06 +01:00
parent 043f4c3d01
commit acf357e9b5
12 changed files with 104 additions and 96 deletions
@@ -151,7 +151,7 @@ public class ClassFileFactory implements OutputFileCollection {
ProtoBuf.Annotation.Builder annotation = ProtoBuf.Annotation.newBuilder();
ClassId classId = DescriptorUtilsKt.getClassId(descriptor);
if (classId != null) {
annotation.setId(stringTable.getClassIdIndex(classId));
annotation.setId(stringTable.getQualifiedClassNameIndex(classId.asString(), false));
builder.addAnnotation(annotation);
}
}
@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.codegen.ClassBuilder
import org.jetbrains.kotlin.codegen.StackValue
import org.jetbrains.kotlin.codegen.coroutines.COROUTINE_IMPL_ASM_TYPE
import org.jetbrains.kotlin.codegen.serialization.JvmCodegenStringTable
import org.jetbrains.kotlin.codegen.serialization.JvmStringTable
import org.jetbrains.kotlin.codegen.writeKotlinMetadata
import org.jetbrains.kotlin.load.java.JvmAnnotationNames
@@ -202,7 +203,7 @@ class AnonymousObjectTransformer(
when (header.kind) {
KotlinClassHeader.Kind.CLASS -> {
val (nameResolver, classProto) = JvmProtoBufUtil.readClassDataFrom(data, strings)
val newStringTable = JvmStringTable(state.typeMapper, nameResolver)
val newStringTable = JvmCodegenStringTable(state.typeMapper, nameResolver)
val newProto = classProto.toBuilder().apply {
setExtension(JvmProtoBuf.anonymousObjectOriginName, newStringTable.getStringIndex(oldObjectType.internalName))
}.build()
@@ -210,7 +211,7 @@ class AnonymousObjectTransformer(
}
KotlinClassHeader.Kind.SYNTHETIC_CLASS -> {
val (nameResolver, functionProto) = JvmProtoBufUtil.readFunctionDataFrom(data, strings)
val newStringTable = JvmStringTable(state.typeMapper, nameResolver)
val newStringTable = JvmCodegenStringTable(state.typeMapper, nameResolver)
val newProto = functionProto.toBuilder().apply {
setExtension(JvmProtoBuf.lambdaClassOriginName, newStringTable.getStringIndex(oldObjectType.internalName))
}.build()
@@ -0,0 +1,33 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. 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.codegen.serialization
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
import org.jetbrains.kotlin.descriptors.ClassifierDescriptorWithTypeParameters
import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmNameResolver
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.serialization.DescriptorAwareStringTable
class JvmCodegenStringTable @JvmOverloads constructor(
private val typeMapper: KotlinTypeMapper,
nameResolver: JvmNameResolver? = null
) : JvmStringTable(nameResolver), DescriptorAwareStringTable {
override fun getLocalClassIdReplacement(descriptor: ClassifierDescriptorWithTypeParameters): ClassId {
val container = descriptor.containingDeclaration
return when (container) {
is ClassifierDescriptorWithTypeParameters -> getLocalClassIdReplacement(container).createNestedClassId(descriptor.name)
is PackageFragmentDescriptor -> {
throw IllegalStateException("getLocalClassIdReplacement should only be called for local classes: $descriptor")
}
else -> {
val fqName = FqName(typeMapper.mapClass(descriptor).internalName.replace('/', '.'))
ClassId(fqName.parent(), FqName.topLevel(fqName.shortName()), true)
}
}
}
}
@@ -43,7 +43,6 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt;
import org.jetbrains.kotlin.serialization.AnnotationSerializer;
import org.jetbrains.kotlin.serialization.DescriptorSerializer;
import org.jetbrains.kotlin.serialization.SerializerExtension;
import org.jetbrains.kotlin.serialization.StringTable;
import org.jetbrains.kotlin.types.FlexibleType;
import org.jetbrains.kotlin.types.KotlinType;
import org.jetbrains.org.objectweb.asm.Type;
@@ -57,7 +56,7 @@ public class JvmSerializerExtension extends SerializerExtension {
private final JvmSerializationBindings bindings;
private final BindingContext codegenBinding;
private final KotlinTypeMapper typeMapper;
private final StringTable stringTable;
private final JvmCodegenStringTable stringTable;
private final AnnotationSerializer annotationSerializer;
private final boolean useTypeTable;
private final String moduleName;
@@ -67,7 +66,7 @@ public class JvmSerializerExtension extends SerializerExtension {
this.bindings = bindings;
this.codegenBinding = state.getBindingContext();
this.typeMapper = state.getTypeMapper();
this.stringTable = new JvmStringTable(typeMapper);
this.stringTable = new JvmCodegenStringTable(typeMapper);
this.annotationSerializer = new AnnotationSerializer(stringTable);
this.useTypeTable = state.getUseTypeTableInSerializer();
this.moduleName = state.getModuleName();
@@ -76,7 +75,7 @@ public class JvmSerializerExtension extends SerializerExtension {
@NotNull
@Override
public StringTable getStringTable() {
public JvmCodegenStringTable getStringTable() {
return stringTable;
}
@@ -16,33 +16,28 @@
package org.jetbrains.kotlin.codegen.serialization
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassifierDescriptorWithTypeParameters
import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor
import org.jetbrains.kotlin.metadata.jvm.JvmProtoBuf
import org.jetbrains.kotlin.metadata.jvm.JvmProtoBuf.StringTableTypes.Record
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmNameResolver
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.serialization.StringTable
import org.jetbrains.kotlin.types.ErrorUtils
import java.io.OutputStream
// TODO: optimize by reordering records to minimize storage of 'range' fields
class JvmStringTable(private val typeMapper: KotlinTypeMapper) : StringTable {
open class JvmStringTable(nameResolver: JvmNameResolver? = null) : StringTable {
val strings = ArrayList<String>()
private val records = ArrayList<Record.Builder>()
private val map = HashMap<String, Int>()
private val localNames = LinkedHashSet<Int>()
constructor(typeMapper: KotlinTypeMapper, nameResolver: JvmNameResolver) : this(typeMapper) {
strings.addAll(nameResolver.strings)
nameResolver.records.mapTo(records, JvmProtoBuf.StringTableTypes.Record::toBuilder)
for (index in strings.indices) {
map[nameResolver.getString(index)] = index
init {
if (nameResolver != null) {
strings.addAll(nameResolver.strings)
nameResolver.records.mapTo(records, JvmProtoBuf.StringTableTypes.Record::toBuilder)
for (index in strings.indices) {
map[nameResolver.getString(index)] = index
}
localNames.addAll(nameResolver.types.localNameList)
}
localNames.addAll(nameResolver.types.localNameList)
}
override fun getStringIndex(string: String): Int =
@@ -62,14 +57,6 @@ class JvmStringTable(private val typeMapper: KotlinTypeMapper) : StringTable {
return !hasPredefinedIndex() && !hasOperation() && substringIndexCount == 0 && replaceCharCount == 0
}
override fun getFqNameIndex(descriptor: ClassifierDescriptorWithTypeParameters): Int {
if (ErrorUtils.isError(descriptor)) {
throw IllegalStateException("Cannot get FQ name of error class: " + descriptor)
}
return getClassIdIndex(descriptor.classId)
}
// We use the following format to encode ClassId: "pkg/Outer.Inner".
// It represents a unique name, but such names don't usually appear in the constant pool, so we're writing "Lpkg/Outer$Inner;"
// instead and an instruction to drop the first and the last character in this string and replace all '$' with '.'.
@@ -78,30 +65,28 @@ class JvmStringTable(private val typeMapper: KotlinTypeMapper) : StringTable {
// string literally: "pkg/Outer.Inner$with$dollars"
// - the class is local or nested in local. In this case we're also storing the literal string, and also storing the fact that
// this name represents a local class in a separate list
override fun getClassIdIndex(classId: ClassId): Int {
val string = classId.asString()
map[string]?.let { recordedIndex ->
override fun getQualifiedClassNameIndex(className: String, isLocal: Boolean): Int {
map[className]?.let { recordedIndex ->
// If we already recorded such string, we only return its index if it's local and our name is local
// OR it's not local and our name is not local as well
if (classId.isLocal == (recordedIndex in localNames)) {
if (isLocal == (recordedIndex in localNames)) {
return recordedIndex
}
}
val index = strings.size
if (classId.isLocal) {
if (isLocal) {
localNames.add(index)
}
val record = Record.newBuilder()
// If the class is local or any of its outer class names contains '$', store a literal string
if (classId.isLocal || '$' in string) {
strings.add(string)
if (isLocal || '$' in className) {
strings.add(className)
}
else {
val predefinedIndex = JvmNameResolver.getPredefinedStringIndex(string)
val predefinedIndex = JvmNameResolver.getPredefinedStringIndex(className)
if (predefinedIndex != null) {
record.predefinedIndex = predefinedIndex
// TODO: move all records with predefined names to the end and do not write associated strings for them (since they are ignored)
@@ -109,30 +94,17 @@ class JvmStringTable(private val typeMapper: KotlinTypeMapper) : StringTable {
}
else {
record.operation = Record.Operation.DESC_TO_CLASS_ID
strings.add("L${string.replace('.', '$')};")
strings.add("L${className.replace('.', '$')};")
}
}
records.add(record)
map[string] = index
map[className] = index
return index
}
private val ClassifierDescriptorWithTypeParameters.classId: ClassId
get() {
val container = containingDeclaration
return when (container) {
is ClassDescriptor -> container.classId.createNestedClassId(name)
is PackageFragmentDescriptor -> ClassId(container.fqName, name)
else -> {
val fqName = FqName(typeMapper.mapClass(this).internalName.replace('/', '.'))
ClassId(fqName.parent(), FqName.topLevel(fqName.shortName()), /* isLocal = */ true)
}
}
}
override fun serializeTo(output: OutputStream) {
with(JvmProtoBuf.StringTableTypes.newBuilder()) {
addAllRecord(records.map { it.build() })
@@ -18,18 +18,16 @@ package org.jetbrains.kotlin.serialization.builtins
import org.jetbrains.kotlin.builtins.BuiltInSerializerProtocol
import org.jetbrains.kotlin.metadata.ProtoBuf
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.serialization.KotlinSerializerExtensionBase
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.UnresolvedType
class BuiltInsSerializerExtension : KotlinSerializerExtensionBase(BuiltInSerializerProtocol) {
private val shortNameToClassId = mapOf(
"IntRange" to "kotlin.ranges.IntRange",
"LongRange" to "kotlin.ranges.LongRange",
"CharRange" to "kotlin.ranges.CharRange"
).mapValues { (_, value) -> ClassId.topLevel(FqName(value)) }
"IntRange" to "kotlin/ranges/IntRange",
"LongRange" to "kotlin/ranges/LongRange",
"CharRange" to "kotlin/ranges/CharRange"
)
override fun shouldUseTypeTable(): Boolean = true
@@ -39,9 +37,9 @@ class BuiltInsSerializerExtension : KotlinSerializerExtensionBase(BuiltInSeriali
throw UnsupportedOperationException("Error types which are not UnresolvedType instances are not supported here: $unwrapped")
}
val classId = shortNameToClassId[unwrapped.presentableName]
?: throw UnsupportedOperationException("Unsupported unresolved type: $unwrapped")
val className = shortNameToClassId[unwrapped.presentableName]
?: throw UnsupportedOperationException("Unsupported unresolved type: $unwrapped")
builder.className = stringTable.getClassIdIndex(classId)
builder.className = stringTable.getQualifiedClassNameIndex(className, false)
}
}
@@ -26,7 +26,7 @@ import org.jetbrains.kotlin.resolve.constants.*
import org.jetbrains.kotlin.resolve.descriptorUtil.annotationClass
import org.jetbrains.kotlin.types.ErrorUtils
class AnnotationSerializer(private val stringTable: StringTable) {
class AnnotationSerializer(private val stringTable: DescriptorAwareStringTable) {
fun serializeAnnotation(annotation: AnnotationDescriptor): ProtoBuf.Annotation = ProtoBuf.Annotation.newBuilder().apply {
val annotationClass = annotation.annotationClass ?: error("Annotation type is not a class: ${annotation.type}")
if (ErrorUtils.isError(annotationClass)) {
@@ -79,7 +79,7 @@ class AnnotationSerializer(private val stringTable: StringTable) {
override fun visitEnumValue(value: EnumValue, data: Unit) {
type = Type.ENUM
classId = stringTable.getClassIdIndex(value.enumClassId)
classId = stringTable.getQualifiedClassNameIndex(value.enumClassId.asString(), value.enumClassId.isLocal)
enumValueId = stringTable.getStringIndex(value.enumEntryName.asString())
}
@@ -48,7 +48,7 @@ class DescriptorSerializer private constructor(
DescriptorSerializer(descriptor, Interner(typeParameters), extension, typeTable, versionRequirementTable,
serializeTypeTableToFunction = false)
val stringTable: StringTable
val stringTable: DescriptorAwareStringTable
get() = extension.stringTable
private fun useTypeTable(): Boolean = extension.shouldUseTypeTable()
@@ -23,7 +23,7 @@ import org.jetbrains.kotlin.types.FlexibleType
import org.jetbrains.kotlin.types.KotlinType
abstract class SerializerExtension {
abstract val stringTable: StringTable
abstract val stringTable: DescriptorAwareStringTable
val annotationSerializer by lazy { AnnotationSerializer(stringTable) }
@@ -18,14 +18,33 @@ package org.jetbrains.kotlin.serialization
import org.jetbrains.kotlin.descriptors.ClassifierDescriptorWithTypeParameters
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.resolve.descriptorUtil.classId
import org.jetbrains.kotlin.types.ErrorUtils
import java.io.OutputStream
interface StringTable {
fun getStringIndex(string: String): Int
fun getFqNameIndex(descriptor: ClassifierDescriptorWithTypeParameters): Int
fun getClassIdIndex(classId: ClassId): Int
/**
* @param className the fully qualified name of some class in the format: `org/foo/bar/Test.Inner`
*/
fun getQualifiedClassNameIndex(className: String, isLocal: Boolean): Int
fun serializeTo(output: OutputStream)
}
interface DescriptorAwareStringTable : StringTable {
fun getFqNameIndex(descriptor: ClassifierDescriptorWithTypeParameters): Int {
if (ErrorUtils.isError(descriptor)) {
throw IllegalStateException("Cannot get FQ name of error class: $descriptor")
}
val classId = descriptor.classId
?: getLocalClassIdReplacement(descriptor)
?: throw IllegalStateException("Cannot get FQ name of local class: $descriptor")
return getQualifiedClassNameIndex(classId.asString(), classId.isLocal)
}
fun getLocalClassIdReplacement(descriptor: ClassifierDescriptorWithTypeParameters): ClassId? = null
}
@@ -16,18 +16,14 @@
package org.jetbrains.kotlin.serialization
import org.jetbrains.kotlin.descriptors.ClassifierDescriptorWithTypeParameters
import org.jetbrains.kotlin.metadata.ProtoBuf
import org.jetbrains.kotlin.metadata.ProtoBuf.QualifiedNameTable.QualifiedName
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.descriptorUtil.classId
import org.jetbrains.kotlin.types.ErrorUtils
import org.jetbrains.kotlin.utils.Interner
import java.io.OutputStream
open class StringTableImpl : StringTable {
open class StringTableImpl : DescriptorAwareStringTable {
private class FqNameProto(val fqName: QualifiedName.Builder) {
override fun hashCode(): Int {
var result = 13
@@ -50,25 +46,17 @@ open class StringTableImpl : StringTable {
private val strings = Interner<String>()
private val qualifiedNames = Interner<FqNameProto>()
fun getSimpleNameIndex(name: Name): Int = getStringIndex(name.asString())
override fun getStringIndex(string: String): Int = strings.intern(string)
override fun getFqNameIndex(descriptor: ClassifierDescriptorWithTypeParameters): Int {
if (ErrorUtils.isError(descriptor)) {
throw IllegalStateException("Cannot get FQ name of error class: $descriptor")
}
override fun getQualifiedClassNameIndex(className: String, isLocal: Boolean): Int =
getQualifiedClassNameIndex(ClassId.fromString(className, isLocal))
val classId = descriptor.classId ?: return getFqNameIndexOfLocalAnonymousClass(descriptor)
return getClassIdIndex(classId)
}
override fun getClassIdIndex(classId: ClassId): Int {
private fun getQualifiedClassNameIndex(classId: ClassId): Int {
val builder = QualifiedName.newBuilder()
builder.kind = QualifiedName.Kind.CLASS
builder.parentQualifiedName =
classId.outerClassId?.let(this::getClassIdIndex)
classId.outerClassId?.let(this::getQualifiedClassNameIndex)
?: getPackageFqNameIndex(classId.packageFqName)
builder.shortName = getStringIndex(classId.shortClassName.asString())
@@ -76,15 +64,11 @@ open class StringTableImpl : StringTable {
return qualifiedNames.intern(FqNameProto(builder))
}
open fun getFqNameIndexOfLocalAnonymousClass(descriptor: ClassifierDescriptorWithTypeParameters): Int {
throw IllegalStateException("Cannot get FQ name of local class: " + descriptor)
}
fun getPackageFqNameIndex(fqName: FqName): Int {
var result = -1
for (segment in fqName.pathSegments()) {
val builder = QualifiedName.newBuilder()
builder.shortName = getSimpleNameIndex(segment)
builder.shortName = getStringIndex(segment.asString())
if (result != -1) {
builder.parentQualifiedName = result
}
@@ -16,30 +16,32 @@
package org.jetbrains.kotlin.serialization.js
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.ClassifierDescriptorWithTypeParameters
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.descriptorUtil.classId
import org.jetbrains.kotlin.resolve.descriptorUtil.getAllSuperClassifiers
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.serialization.StringTableImpl
class JavaScriptStringTable : StringTableImpl() {
override fun getFqNameIndexOfLocalAnonymousClass(descriptor: ClassifierDescriptorWithTypeParameters): Int {
override fun getLocalClassIdReplacement(descriptor: ClassifierDescriptorWithTypeParameters): ClassId? {
return if (descriptor.containingDeclaration is CallableMemberDescriptor) {
val superClassifiers = descriptor.getAllSuperClassifiers()
.mapNotNull { it as ClassifierDescriptorWithTypeParameters }
.filter { it != descriptor }
.toList()
if (superClassifiers.size == 1) {
getFqNameIndex(superClassifiers[0])
superClassifiers[0].classId
}
else {
val superClass = superClassifiers.find { !DescriptorUtils.isInterface(it) }
getFqNameIndex(superClass ?: descriptor.module.builtIns.any)
superClass?.classId ?: ClassId.topLevel(KotlinBuiltIns.FQ_NAMES.any.toSafe())
}
}
else {
super.getFqNameIndexOfLocalAnonymousClass(descriptor)
super.getLocalClassIdReplacement(descriptor)
}
}
}