From 93820ba5bbc466b1f03bf70a6f83c65e39fe5905 Mon Sep 17 00:00:00 2001 From: Nikolay Igotti Date: Wed, 9 May 2018 03:36:57 +0200 Subject: [PATCH] Additional RTTI for debugging. (#1568) --- .../jetbrains/kotlin/backend/konan/Context.kt | 46 +++---- .../kotlin/backend/konan/LinkStage.kt | 2 +- .../backend/konan/llvm/LlvmDeclarations.kt | 3 +- .../backend/konan/llvm/RTTIGenerator.kt | 85 ++++++++++--- .../kotlin/backend/konan/llvm/Runtime.kt | 1 + .../konan/optimizations/Devirtualization.kt | 2 +- runtime/src/main/cpp/KDebug.cpp | 118 +++++++++++++----- runtime/src/main/cpp/KDebug.h | 29 ++--- runtime/src/main/cpp/TypeInfo.h | 31 +++++ 9 files changed, 224 insertions(+), 93 deletions(-) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt index 586d8d893b7..91e7cb50041 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt @@ -351,47 +351,31 @@ internal class Context(config: KonanConfig) : KonanBackendContext(config) { printBitCode() } - fun shouldVerifyDescriptors(): Boolean { - return config.configuration.getBoolean(KonanConfigKeys.VERIFY_DESCRIPTORS) - } + fun shouldVerifyDescriptors() = config.configuration.getBoolean(KonanConfigKeys.VERIFY_DESCRIPTORS) - fun shouldVerifyIr(): Boolean { - return config.configuration.getBoolean(KonanConfigKeys.VERIFY_IR) - } + fun shouldVerifyIr() = config.configuration.getBoolean(KonanConfigKeys.VERIFY_IR) - fun shouldVerifyBitCode(): Boolean { - return config.configuration.getBoolean(KonanConfigKeys.VERIFY_BITCODE) - } + fun shouldVerifyBitCode() = config.configuration.getBoolean(KonanConfigKeys.VERIFY_BITCODE) - fun shouldPrintDescriptors(): Boolean { - return config.configuration.getBoolean(KonanConfigKeys.PRINT_DESCRIPTORS) - } + fun shouldPrintDescriptors() = config.configuration.getBoolean(KonanConfigKeys.PRINT_DESCRIPTORS) - fun shouldPrintIr(): Boolean { - return config.configuration.getBoolean(KonanConfigKeys.PRINT_IR) - } + fun shouldPrintIr() = config.configuration.getBoolean(KonanConfigKeys.PRINT_IR) - fun shouldPrintIrWithDescriptors(): Boolean { - return config.configuration.getBoolean(KonanConfigKeys.PRINT_IR_WITH_DESCRIPTORS) - } + fun shouldPrintIrWithDescriptors()= + config.configuration.getBoolean(KonanConfigKeys.PRINT_IR_WITH_DESCRIPTORS) - fun shouldPrintBitCode(): Boolean { - return config.configuration.getBoolean(KonanConfigKeys.PRINT_BITCODE) - } + fun shouldPrintBitCode() = config.configuration.getBoolean(KonanConfigKeys.PRINT_BITCODE) - fun shouldPrintLocations(): Boolean { - return config.configuration.getBoolean(KonanConfigKeys.PRINT_LOCATIONS) - } + fun shouldPrintLocations() = config.configuration.getBoolean(KonanConfigKeys.PRINT_LOCATIONS) - fun shouldProfilePhases(): Boolean { - return config.configuration.getBoolean(KonanConfigKeys.TIME_PHASES) - } + fun shouldProfilePhases() = config.configuration.getBoolean(KonanConfigKeys.TIME_PHASES) - fun shouldContainDebugInfo(): Boolean { - return config.configuration.getBoolean(KonanConfigKeys.DEBUG) - } + fun shouldContainDebugInfo() = config.configuration.getBoolean(KonanConfigKeys.DEBUG) - fun shouldGenerateTestRunner(): Boolean = config.configuration.getBoolean(KonanConfigKeys.GENERATE_TEST_RUNNER) + fun shouldOptimize() = config.configuration.getBoolean(KonanConfigKeys.OPTIMIZATION) + + fun shouldGenerateTestRunner() = + config.configuration.getBoolean(KonanConfigKeys.GENERATE_TEST_RUNNER) override fun log(message: () -> String) { if (phase?.verbose ?: false) { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/LinkStage.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/LinkStage.kt index 3f201898c36..a25a85f2340 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/LinkStage.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/LinkStage.kt @@ -33,7 +33,7 @@ internal class LinkStage(val context: Context) { private val platform = context.config.platform private val linker = platform.linker - private val optimize = config.get(KonanConfigKeys.OPTIMIZATION) ?: false + private val optimize = context.shouldOptimize() private val debug = config.get(KonanConfigKeys.DEBUG) ?: false private val linkerOutput = when (context.config.produce) { CompilerOutputKind.DYNAMIC, CompilerOutputKind.FRAMEWORK -> LinkerOutputKind.DYNAMIC_LIBRARY diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmDeclarations.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmDeclarations.kt index bc8e09d0a61..b361a8be109 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmDeclarations.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmDeclarations.kt @@ -416,7 +416,8 @@ private class DeclarationsGeneratorVisitor(override val context: Context) : LLVMAddFunction(context.llvmModule, symbolName, llvmFunctionType)!! } - if (!context.config.configuration.getBoolean(KonanConfigKeys.OPTIMIZATION)) { + // TODO: do we still need it? + if (!context.shouldOptimize()) { LLVMAddTargetDependentFunctionAttr(llvmFunction, "no-frame-pointer-elim", "true") } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt index 243ede26140..7a6d6cf0278 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt @@ -50,6 +50,7 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { fieldsCount: Int, packageName: String?, relativeName: String?, + extendedInfo: ConstPointer, writableTypeInfo: ConstPointer?) : Struct( @@ -77,6 +78,8 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { kotlinStringLiteral(packageName), kotlinStringLiteral(relativeName), + extendedInfo, + *listOfNotNull(writableTypeInfo).toTypedArray() ) @@ -97,22 +100,36 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { } private val arrayClasses = mapOf( - "kotlin.Array" to -LLVMABISizeOfType(llvmTargetData, kObjHeaderPtr).toInt(), - "kotlin.ByteArray" to -1, - "kotlin.CharArray" to -2, - "kotlin.ShortArray" to -2, - "kotlin.IntArray" to -4, - "kotlin.LongArray" to -8, - "kotlin.FloatArray" to -4, - "kotlin.DoubleArray" to -8, - "kotlin.BooleanArray" to -1, - "kotlin.String" to -2, - "konan.ImmutableBinaryBlob" to -1 + "kotlin.Array" to kObjHeaderPtr, + "kotlin.ByteArray" to LLVMInt8Type()!!, + "kotlin.CharArray" to LLVMInt16Type()!!, + "kotlin.ShortArray" to LLVMInt16Type()!!, + "kotlin.IntArray" to LLVMInt32Type()!!, + "kotlin.LongArray" to LLVMInt64Type()!!, + "kotlin.FloatArray" to LLVMFloatType()!!, + "kotlin.DoubleArray" to LLVMDoubleType()!!, + "kotlin.BooleanArray" to LLVMInt8Type()!!, + "kotlin.String" to LLVMInt16Type()!!, + "konan.ImmutableBinaryBlob" to LLVMInt8Type()!! + ) + + // Keep in sync with Konan_RuntimeType. + private val runtimeTypeMap = mapOf( + kObjHeaderPtr to 1, + LLVMInt8Type()!! to 2, + LLVMInt16Type()!! to 3, + LLVMInt32Type()!! to 4, + LLVMInt64Type()!! to 5, + LLVMFloatType()!! to 6, + LLVMDoubleType()!! to 7, + kInt8Ptr to 8, + LLVMInt1Type()!! to 9 ) private fun getInstanceSize(classType: LLVMTypeRef?, className: FqName) : Int { - val arraySize = arrayClasses.get(className.asString()); - if (arraySize != null) return arraySize; + val elementType = arrayClasses.get(className.asString()) + // Check if it is an array. + if (elementType != null) return -LLVMABISizeOfType(llvmTargetData, elementType).toInt() return LLVMStoreSizeOfType(llvmTargetData, classType).toInt() } @@ -180,6 +197,7 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { fieldsPtr, if (classDesc.isInterface) -1 else fields.size, reflectionInfo.packageName, reflectionInfo.relativeName, + makeExtendedInfo(classDesc), llvmDeclarations.writableTypeInfoGlobal?.pointer ) @@ -225,7 +243,45 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { }.sortedBy { it.nameSignature.value } } - // TODO: extract more code common with generate() + private fun mapRuntimeType(type: LLVMTypeRef): Int = + runtimeTypeMap[type] ?: throw Error("Unmapped type: ${llvmtype2string(type)}") + + private fun makeExtendedInfo(descriptor: ClassDescriptor): ConstPointer { + // TODO: shall we actually do that? + if (context.shouldOptimize()) + return NullPointer(runtime.extendedTypeInfoType) + + val className = descriptor.fqNameSafe.toString() + val llvmDeclarations = context.llvmDeclarations.forClass(descriptor) + val bodyType = llvmDeclarations.bodyType + val elementType = arrayClasses[className] + val value = if (elementType != null) { + // An array type. + val runtimeElementType = mapRuntimeType(elementType) + Struct(runtime.extendedTypeInfoType, + Int32(-runtimeElementType), + NullPointer(int32Type), NullPointer(int8Type), NullPointer(kInt8Ptr)) + } else { + data class FieldRecord(val offset: Int, val type: Int, val name: String) + val fields = getStructElements(bodyType).mapIndexedNotNull { index, type -> + FieldRecord( + LLVMOffsetOfElement(llvmTargetData, bodyType, index).toInt(), + mapRuntimeType(type), + llvmDeclarations.fields[index].name.asString()) + } + val offsetsPtr = staticData.placeGlobalConstArray("kextoff:$className", int32Type, + fields.map { Int32(it.offset) }) + val typesPtr = staticData.placeGlobalConstArray("kexttype:$className", int8Type, + fields.map { Int8(it.type.toByte()) }) + val namesPtr = staticData.placeGlobalConstArray("kextname:$className", kInt8Ptr, + fields.map { staticData.placeCStringLiteral(it.name) }) + Struct(runtime.extendedTypeInfoType, Int32(fields.size), offsetsPtr, typesPtr, namesPtr) + } + val result = staticData.placeGlobal("", value) + return result.pointer + } + + // TODO: extract more code common with generate(). fun generateSyntheticInterfaceImpl( descriptor: ClassDescriptor, methodImpls: Map @@ -283,6 +339,7 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { fields = fieldsPtr, fieldsCount = fieldsCount, packageName = reflectionInfo.packageName, relativeName = reflectionInfo.relativeName, + extendedInfo = NullPointer(runtime.extendedTypeInfoType), writableTypeInfo = writableTypeInfo ), vtable) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/Runtime.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/Runtime.kt index 695d9abd4d1..21ba06a6b82 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/Runtime.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/Runtime.kt @@ -34,6 +34,7 @@ class Runtime(bitcodeFile: String) { ?: throw Error("struct.$name is not found in the Runtime module.") val typeInfoType = getStructType("TypeInfo") + val extendedTypeInfoType = getStructType("ExtendedTypeInfo") val writableTypeInfoType = getStructTypeOrNull("WritableTypeInfo") val fieldTableRecordType = getStructType("FieldTableRecord") val methodTableRecordType = getStructType("MethodTableRecord") diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/Devirtualization.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/Devirtualization.kt index 870074e7dda..07d5221df24 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/Devirtualization.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/Devirtualization.kt @@ -911,7 +911,7 @@ internal object Devirtualization { devirtualizedCallSites: Map) { val nativePtrType = context.builtIns.nativePtr.defaultType val nativePtrEqualityOperatorSymbol = context.ir.symbols.areEqualByValue.single { it.descriptor.valueParameters[0].type == nativePtrType } - val optimize = context.config.configuration.get(KonanConfigKeys.OPTIMIZATION) ?: false + val optimize = context.shouldOptimize() irModule.transformChildrenVoid(object: IrElementTransformerVoidWithContext() { override fun visitCall(expression: IrCall): IrExpression { diff --git a/runtime/src/main/cpp/KDebug.cpp b/runtime/src/main/cpp/KDebug.cpp index 8418b1c9f50..5d4ffb760ea 100644 --- a/runtime/src/main/cpp/KDebug.cpp +++ b/runtime/src/main/cpp/KDebug.cpp @@ -32,6 +32,19 @@ namespace { char debugBuffer[4096]; +constexpr int runtimeTypeSize[] = { + -1, // INVALID + sizeof(ObjHeader*), // OBJECT + 1, // INT8 + 2, // INT16 + 4, // INT32 + 8, // INT64 + 4, // FLOAT32 + 8, // FLOAT64 + sizeof(void*), // NATIVE_PTR + 1 // BOOLEAN +}; + } // namespace extern "C" { @@ -63,39 +76,88 @@ RUNTIME_USED KInt Konan_DebugPrint(KRef obj) { return 0; } -RUNTIME_USED int Konan_DebugGetFieldType(KRef obj, int index) { - if (obj == nullptr || index < 0) return Konan_RuntimeType::INVALID; - auto typeInfo = obj->type_info(); - if (typeInfo->instanceSize_ < 0) { - // Arrays. - if (index >= obj->array()->count_) return Konan_RuntimeType::INVALID; - if (typeInfo == theArrayTypeInfo) { - return Konan_RuntimeType::OBJECT; - } - // TODO: support other array types. - return Konan_RuntimeType::INVALID; - } +RUNTIME_USED int Konan_DebugIsArray(KRef obj) { + return obj == nullptr || IsArray(obj) ? 1 : 0; +} - // TODO: support primitive type fields as well! - if (index >= typeInfo->objOffsetsCount_) return Konan_RuntimeType::INVALID; - return Konan_RuntimeType::OBJECT; +RUNTIME_USED int Konan_DebugGetFieldCount(KRef obj) { + if (obj == nullptr) + return 0; + + auto typeInfo = obj->type_info(); + auto extendedTypeInfo = typeInfo->extendedInfo_; + + if (extendedTypeInfo == nullptr) + return 0; + + if (IsArray(obj)) + return obj->array()->count_; + + return extendedTypeInfo->fieldsCount_; +} + + +RUNTIME_USED int Konan_DebugGetFieldType(KRef obj, int index) { + if (obj == nullptr || index < 0) + return Konan_RuntimeType::RT_INVALID; + + auto typeInfo = obj->type_info(); + auto extendedTypeInfo = typeInfo->extendedInfo_; + + if (extendedTypeInfo == nullptr) + return Konan_RuntimeType::RT_INVALID; + + if (extendedTypeInfo->fieldsCount_ < 0) + return -typeInfo->fieldsCount_; + + if (index >= extendedTypeInfo->fieldsCount_) + return Konan_RuntimeType::RT_INVALID; + + return extendedTypeInfo->fieldTypes_[index]; } RUNTIME_USED void* Konan_DebugGetFieldAddress(KRef obj, int index) { - if (obj == nullptr || index < 0) return nullptr; - auto typeInfo = obj->type_info(); - if (typeInfo->instanceSize_ < 0) { - // Arrays. - if (index >= obj->array()->count_) return nullptr; - if (typeInfo == theArrayTypeInfo) { - return ArrayAddressOfElementAt(obj->array(), index); - } - // TODO: support other array types. + if (obj == nullptr || index < 0) return nullptr; - } - // TODO: support primitive type fields as well! - if (index >= typeInfo->objOffsetsCount_) return nullptr; - return reinterpret_cast(obj + 1) + typeInfo->objOffsets_[index]; + + auto typeInfo = obj->type_info(); + auto extendedTypeInfo = typeInfo->extendedInfo_; + + if (extendedTypeInfo == nullptr) + return nullptr; + + if (extendedTypeInfo->fieldsCount_ < 0) { + if (index >= obj->array()->count_) + return nullptr; + + return reinterpret_cast(obj + 1) + index * runtimeTypeSize[-typeInfo->fieldsCount_]; + } + + if (index >= extendedTypeInfo->fieldsCount_) + return nullptr; + + return reinterpret_cast(obj + 1) + extendedTypeInfo->fieldOffsets_[index]; +} + +// Compute address of field or an array element at the index, or null, if incorrect. +RUNTIME_USED const char* Konan_DebugGetFieldName(KRef obj, int index) { + if (obj == nullptr || index < 0) + return nullptr; + + auto typeInfo = obj->type_info(); + auto extendedTypeInfo = typeInfo->extendedInfo_; + + if (extendedTypeInfo == nullptr) + return nullptr; + + // For arrays, field name makes not much sense. + if (extendedTypeInfo->fieldsCount_ < 0) + return ""; + + if (index >= extendedTypeInfo->fieldsCount_) + return nullptr; + + return extendedTypeInfo->fieldNames_[index]; } } // extern "C" diff --git a/runtime/src/main/cpp/KDebug.h b/runtime/src/main/cpp/KDebug.h index 34acca62b03..ba60740662b 100644 --- a/runtime/src/main/cpp/KDebug.h +++ b/runtime/src/main/cpp/KDebug.h @@ -24,18 +24,6 @@ #ifndef KONAN_NO_DEBUG_API -// Type for runtime representation of Konan object. -enum Konan_RuntimeType { - INVALID = 0, - OBJECT = 1, - INT8 = 2, - INT16 = 3, - INT32 = 4, - INT64 = 5, - FLOAT32 = 6, - FLOAT64 = 7 -}; - #ifdef __cplusplus extern "C" { #endif @@ -46,25 +34,32 @@ char* Konan_DebugBuffer(); // Get size of memory buffer where debugger can put data in Konan app process. RUNTIME_USED -int Konan_DebugBufferSize(); +int32_t Konan_DebugBufferSize(); // Put string representation of an object to the provided buffer. RUNTIME_USED -int Konan_DebugObjectToUtf8Array(KRef obj, char* buffer, int bufferSize); +int32_t Konan_DebugObjectToUtf8Array(KRef obj, char* buffer, int bufferSize); // Print to console string representation of an object. RUNTIME_USED -int Konan_DebugPrint(KRef obj); +int32_t Konan_DebugPrint(KRef obj); + +// Returns 1 if obj refers to an array, string or binary blob and 0 otherwise. +RUNTIME_USED int Konan_DebugIsArray(KRef obj); + +// Returns number of fields in an objects, or elements in an array. +RUNTIME_USED int Konan_DebugGetFieldCount(KRef obj); // Compute type of field or an array element at the index, or 0, if incorrect, // see Konan_RuntimeType. -// TODO: currently, only object fields are supported, will be fixed soon. RUNTIME_USED int Konan_DebugGetFieldType(KRef obj, int index); // Compute address of field or an array element at the index, or null, if incorrect. -// TODO: currently, only object fields are supported, will be fixed soon. RUNTIME_USED void* Konan_DebugGetFieldAddress(KRef obj, int index); +// Compute address of field or an array element at the index, or null, if incorrect. +RUNTIME_USED const char* Konan_DebugGetFieldName(KRef obj, int index); + #ifdef __cplusplus } #endif diff --git a/runtime/src/main/cpp/TypeInfo.h b/runtime/src/main/cpp/TypeInfo.h index 0f94321676a..e3142518a40 100644 --- a/runtime/src/main/cpp/TypeInfo.h +++ b/runtime/src/main/cpp/TypeInfo.h @@ -42,6 +42,34 @@ struct FieldTableRecord { int fieldOffset_; }; +// Type for runtime representation of Konan object. +// Keep in sync with runtimeTypeMap in RTTIGenerator. +enum Konan_RuntimeType { + RT_INVALID = 0, + RT_OBJECT = 1, + RT_INT8 = 2, + RT_INT16 = 3, + RT_INT32 = 4, + RT_INT64 = 5, + RT_FLOAT32 = 6, + RT_FLOAT64 = 7, + RT_NATIVE_PTR = 8, + RT_BOOLEAN = 9 +}; + +// Extended information about a type. +struct ExtendedTypeInfo { + // Number of fields (negated Konan_RuntimeType for array types). + int32_t fieldsCount_; + // Offsets of all fields. + const int32_t* fieldOffsets_; + // Types of all fields. + const uint8_t* fieldTypes_; + // Names of all fields. + const char** fieldNames_; + // TODO: do we want any other info here? +}; + // This struct represents runtime type information and by itself is the compile time // constant. struct TypeInfo { @@ -74,6 +102,9 @@ struct TypeInfo { // or `null` if the class is anonymous. ObjHeader* relativeName_; + // Extended RTTI. + const ExtendedTypeInfo* extendedInfo_; + #if KONAN_TYPE_INFO_HAS_WRITABLE_PART WritableTypeInfo* writableInfo_; #endif