From beb10306dded93c3bb84d4ed86f32d954d6556b9 Mon Sep 17 00:00:00 2001 From: Nikolay Igotti Date: Wed, 2 Aug 2017 18:55:52 +0300 Subject: [PATCH] Add Kotlin object inspection to simplify debugging (#763) --- .../kotlin/backend/konan/LinkStage.kt | 14 ++- backend.native/konan.properties | 19 ++-- runtime/src/launcher/cpp/launcher.cpp | 2 +- runtime/src/main/cpp/Common.h | 2 + runtime/src/main/cpp/KDebug.cpp | 103 ++++++++++++++++++ runtime/src/main/cpp/KDebug.h | 74 +++++++++++++ runtime/src/main/cpp/KString.h | 6 +- runtime/src/main/cpp/ToString.cpp | 97 ++++++++--------- .../src/main/kotlin/konan/internal/Util.kt | 36 ++++++ runtime/src/main/kotlin/kotlin/Boolean.kt | 7 +- 10 files changed, 283 insertions(+), 77 deletions(-) create mode 100644 runtime/src/main/cpp/KDebug.cpp create mode 100644 runtime/src/main/cpp/KDebug.h create mode 100644 runtime/src/main/kotlin/konan/internal/Util.kt 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 d932b8b375d..e9fed37a151 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 @@ -211,6 +211,10 @@ internal class LinkStage(val context: Context) { private val emitted = context.bitcodeFileName private val libraries = context.config.libraries + private fun MutableList.addNonEmpty(elements: List) { + addAll(elements.filter { !it.isEmpty() }) + } + private fun llvmLto(files: List): ObjectFile { val tmpCombined = createTempFile("combined", ".o") tmpCombined.deleteOnExit() @@ -218,13 +222,13 @@ internal class LinkStage(val context: Context) { val tool = distribution.llvmLto val command = mutableListOf(tool, "-o", combined) - command.addAll(platform.llvmLtoFlags) + command.addNonEmpty(platform.llvmLtoFlags) when { - optimize -> command.addAll(platform.llvmLtoOptFlags) - debug -> command.addAll(platform.llvmDebugOptFlags) - else -> command.addAll(platform.llvmLtoNooptFlags) + optimize -> command.addNonEmpty(platform.llvmLtoOptFlags) + debug -> command.addNonEmpty(platform.llvmDebugOptFlags) + else -> command.addNonEmpty(platform.llvmLtoNooptFlags) } - command.addAll(files) + command.addNonEmpty(files) runTool(*command.toTypedArray()) return combined diff --git a/backend.native/konan.properties b/backend.native/konan.properties index edf2c7aca39..1e782c6f875 100644 --- a/backend.native/konan.properties +++ b/backend.native/konan.properties @@ -30,7 +30,7 @@ targetToolchain.osx = clang-llvm-3.9.0-darwin-macos arch.osx = x86_64 targetSysRoot.osx = target-sysroot-1-darwin-macos libffiDir.osx = libffi-3.2.1-2-darwin-macos -llvmLtoFlags.osx = -exported-symbol=_Konan_main +llvmLtoFlags.osx = llvmLtoOptFlags.osx = -O3 -function-sections llvmLtoNooptFlags.osx = -O1 linkerKonanFlags.osx = -lc++ @@ -55,7 +55,7 @@ arch.ios = arm64 entrySelector.ios = -alias _Konan_main _main targetSysRoot.ios = target-sysroot-2-darwin-ios libffiDir.ios = libffi-3.2.1-2-darwin-ios -llvmLtoFlags.ios = -exported-symbol=_Konan_main +llvmLtoFlags.ios = llvmLtoOptFlags.ios = -O3 -function-sections linkerDebugFlags.ios = -S llvmLtoNooptFlags.ios = -O1 @@ -76,7 +76,7 @@ arch.ios_sim = x86_64 entrySelector.ios_sim = -alias _Konan_main _main targetSysRoot.ios_sim = target-sysroot-1-darwin-ios-sim libffiDir.ios_sim = libffi-3.2.1-2-darwin-ios-sim -llvmLtoFlags.ios_sim = -exported-symbol=_Konan_main +llvmLtoFlags.ios_sim = llvmLtoOptFlags.ios_sim = -O3 -function-sections llvmLtoNooptFlags.ios_sim = -O1 linkerKonanFlags.ios_sim = -lc++ @@ -97,7 +97,7 @@ targetSysRoot.linux = target-gcc-toolchain-3-linux-x86-64/x86_64-unknown-linux-g libffiDir.linux = libffi-3.2.1-2-linux-x86-64 # targetSysroot-relative. libGcc.linux = ../../lib/gcc/x86_64-unknown-linux-gnu/4.8.5 -llvmLtoFlags.linux = -exported-symbol=Konan_main +llvmLtoFlags.linux = llvmLtoOptFlags.linux = -O3 -function-sections llvmLtoNooptFlags.linux = -O1 llvmLlcFlags.linux = -march=x86-64 @@ -132,7 +132,7 @@ libffiDir.raspberrypi = libffi-3.2.1-2-raspberrypi linkerKonanFlags.raspberrypi = -Bstatic -lstdc++ -Bdynamic -ldl -lm -lpthread # targetSysroot-relative. libGcc.raspberrypi = lib/gcc/arm-linux-gnueabihf/4.8.3 -llvmLtoFlags.raspberrypi = -exported-symbol=Konan_main +llvmLtoFlags.raspberrypi = llvmLtoOptFlags.raspberrypi = -O3 -function-sections llvmLtoNooptFlags.raspberrypi = -O1 dynamicLinker.raspberrypi = /lib/ld-linux-armhf.so.3 @@ -159,7 +159,7 @@ dependencies.linux-android_arm32 = \ quadruple.android_arm32 = arm-linux-androideabi entrySelector.android_arm32 = -Wl,--defsym -Wl,main=Konan_main -llvmLtoFlags.android_arm32 = -exported-symbol=Konan_main -emulated-tls -relocation-model=pic +llvmLtoFlags.android_arm32 = -emulated-tls -relocation-model=pic llvmLtoOptFlags.android_arm32 = -O3 -function-sections linkerDebugFlags.android_arm32 = -Wl,-S llvmLtoNooptFlags.android_arm32 = -O1 @@ -184,7 +184,7 @@ dependencies.linux-android_arm64 = \ quadruple.android_arm64 = aarch64-linux-android entrySelector.android_arm64 = -Wl,--defsym -Wl,main=Konan_main -llvmLtoFlags.android_arm64 = -exported-symbol=Konan_main -emulated-tls -relocation-model=pic +llvmLtoFlags.android_arm64 = -emulated-tls -relocation-model=pic targetSysRoot.android_arm64 = target-sysroot-21-android_arm64 libffiDir.android_arm64 = libffi-3.2.1-2-android_arm64 linkerKonanFlags.android_arm64 = -lm -latomic -lstdc++ -landroid @@ -198,7 +198,7 @@ targetToolchain.mingw = msys2-mingw-w64-x86_64-gcc-6.3.0-clang-llvm-3.9.1-window quadruple.mingw = x86_64-w64-mingw32 targetSysRoot.mingw = msys2-mingw-w64-x86_64-gcc-6.3.0-clang-llvm-3.9.1-windows-x86-64 libffiDir.mingw = libffi-3.2.1-mingw-w64-x86-64 -llvmLtoFlags.mingw = -exported-symbol=Konan_main +llvmLtoFlags.mingw = llvmLtoOptFlags.mingw = -O3 -function-sections llvmLtoNooptFlags.mingw = -O1 linkerDebugFlags.mingw = -Wl,-S @@ -219,10 +219,11 @@ dependencies.osx-wasm32 = \ quadruple.wasm32 = wasm32 entrySelector.wasm32 = -Wl,--defsym -Wl,main=Konan_main -llvmLtoFlags.wasm32 = -exported-symbol=Konan_main +llvmLtoFlags.wasm32 = targetSysRoot.wasm32 = target-sysroot-1-wasm clangFlags.wasm32 = -O1 -fno-rtti -fno-exceptions \ -D_LIBCPP_ABI_VERSION=2 -DKONAN_NO_FFI=1 -DKONAN_NO_THREADS=1 -DKONAN_NO_EXCEPTIONS=1 \ + -DKONAN_NO_DEBUG_API=1 \ -nostdinc -Xclang -nobuiltininc -Xclang -nostdsysteminc \ -Xclang -isystem/include/libcxx -Xclang -isystem/lib/libcxxabi/include \ -Xclang -isystem/include/compat -Xclang -isystem/include/libc diff --git a/runtime/src/launcher/cpp/launcher.cpp b/runtime/src/launcher/cpp/launcher.cpp index 7f92019342e..cfb630e7ade 100644 --- a/runtime/src/launcher/cpp/launcher.cpp +++ b/runtime/src/launcher/cpp/launcher.cpp @@ -38,7 +38,7 @@ OBJ_GETTER(setupArgs, int argc, const char** argv) { //--- main --------------------------------------------------------------------// extern "C" KInt Konan_start(const ObjHeader*); -extern "C" int Konan_main(int argc, const char** argv) { +extern "C" RUNTIME_USED int Konan_main(int argc, const char** argv) { RuntimeState* state = InitRuntime(); if (state == nullptr) { diff --git a/runtime/src/main/cpp/Common.h b/runtime/src/main/cpp/Common.h index 21c1170fab1..9b40141461d 100644 --- a/runtime/src/main/cpp/Common.h +++ b/runtime/src/main/cpp/Common.h @@ -20,6 +20,8 @@ #define RUNTIME_NOTHROW __attribute__((nothrow)) #define RUNTIME_CONST __attribute__((const)) #define RUNTIME_PURE __attribute__((pure)) +#define RUNTIME_USED __attribute__((used)) + #if KONAN_NO_THREADS #define THREAD_LOCAL_VARIABLE #else diff --git a/runtime/src/main/cpp/KDebug.cpp b/runtime/src/main/cpp/KDebug.cpp new file mode 100644 index 00000000000..6415abd902f --- /dev/null +++ b/runtime/src/main/cpp/KDebug.cpp @@ -0,0 +1,103 @@ +/* + * Copyright 2010-2017 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. + */ + +#include "KDebug.h" + +#include + +#include "Assert.h" +#include "Memory.h" +#include "Natives.h" +#include "Porting.h" +#include "Types.h" + +#ifndef KONAN_NO_DEBUG_API + +extern "C" OBJ_GETTER(KonanObjectToUtf8Array, KRef object); + +namespace { + +char debugBuffer[4096]; + +} // namespace + +extern "C" { + +// Buffer that can be used by debugger for inspections. +RUNTIME_USED char* Konan_DebugBuffer() { + return debugBuffer; +} + +RUNTIME_USED int Konan_DebugBufferSize() { + return sizeof(debugBuffer); +} + +// Auxilary function which can be called by developer/debugger to inspect an object. +RUNTIME_USED KInt Konan_DebugObjectToUtf8Array(KRef obj, char* buffer, KInt bufferSize) { + ObjHolder stringHolder; + auto data = KonanObjectToUtf8Array(obj, stringHolder.slot())->array(); + if (data == nullptr) return 0; + KInt toCopy = data->count_ > bufferSize - 1 ? bufferSize - 1 : data->count_; + ::memcpy(buffer, ByteArrayAddressOfElementAt(data, 0), toCopy); + buffer[toCopy + 1] = '\0'; + return toCopy + 1; +} + +RUNTIME_USED KInt Konan_DebugPrint(KRef obj) { + KInt size = Konan_DebugObjectToUtf8Array(obj, Konan_DebugBuffer(), Konan_DebugBufferSize()); + if (size > 1) + konan::consoleWriteUtf8(Konan_DebugBuffer(), size - 1); + 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; + } + + // TODO: support primitive type fields as well! + if (index >= typeInfo->objOffsetsCount_) return Konan_RuntimeType::INVALID; + return Konan_RuntimeType::OBJECT; +} + +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. + return nullptr; + } + // TODO: support primitive type fields as well! + if (index >= typeInfo->objOffsetsCount_) return nullptr; + return reinterpret_cast(obj + 1) + typeInfo->objOffsets_[index]; +} + +} // extern "C" + +#endif // !KONAN_NO_DEBUG_API diff --git a/runtime/src/main/cpp/KDebug.h b/runtime/src/main/cpp/KDebug.h new file mode 100644 index 00000000000..34acca62b03 --- /dev/null +++ b/runtime/src/main/cpp/KDebug.h @@ -0,0 +1,74 @@ +/* + * Copyright 2010-2017 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. + */ + +#ifndef RUNTIME_KDEBUG_H +#define RUNTIME_KDEBUG_H + +#include "Common.h" +#include "Memory.h" +#include "Types.h" +#include "TypeInfo.h" + +#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 + +// Get memory buffer where debugger can put data in Konan app process. +RUNTIME_USED +char* Konan_DebugBuffer(); + +// Get size of memory buffer where debugger can put data in Konan app process. +RUNTIME_USED +int Konan_DebugBufferSize(); + +// Put string representation of an object to the provided buffer. +RUNTIME_USED +int Konan_DebugObjectToUtf8Array(KRef obj, char* buffer, int bufferSize); + +// Print to console string representation of an object. +RUNTIME_USED +int Konan_DebugPrint(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); + +#ifdef __cplusplus +} +#endif + +#endif // !KONAN_NO_DEBUG_API + +#endif // RUNTIME_KDEBUG_H diff --git a/runtime/src/main/cpp/KString.h b/runtime/src/main/cpp/KString.h index 5994e628927..129a3c0c0ae 100644 --- a/runtime/src/main/cpp/KString.h +++ b/runtime/src/main/cpp/KString.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef RUNTIME_STRING_H -#define RUNTIME_STRING_H +#ifndef RUNTIME_KSTRING_H +#define RUNTIME_KSTRING_H #include "Common.h" #include "Memory.h" @@ -52,4 +52,4 @@ int binarySearchRange(const T* array, int arrayLength, T needle) { return middle - (needle < value ? 1 : 0); } -#endif // RUNTIME_STRING_H +#endif // RUNTIME_KSTRING_H diff --git a/runtime/src/main/cpp/ToString.cpp b/runtime/src/main/cpp/ToString.cpp index 5517991e66b..063369870a0 100644 --- a/runtime/src/main/cpp/ToString.cpp +++ b/runtime/src/main/cpp/ToString.cpp @@ -23,54 +23,60 @@ #include "Memory.h" #include "Natives.h" #include "KString.h" +#include "Porting.h" #include "Types.h" namespace { - char int_to_digit(uint32_t value) { - if (value < 10) { - return '0' + value; - } else { - return 'a' + (value - 10); - } - } - - // Radix is checked on the Kotlin side. - template OBJ_GETTER(Kotlin_toStringRadix, T value, KInt radix) { - if (value == 0) { - RETURN_RESULT_OF(CreateStringFromCString, "0"); - } - char cstring[sizeof(T) * CHAR_BIT + 1]; - bool negative = (value < 0); - if (!negative) { - value = -value; - } - - int32_t length = 0; - while (value < 0) { - cstring[length++] = int_to_digit(-(value % radix)); - value /= radix; - } - if (negative) { - cstring[length++] = '-'; - } - for (int i = 0, j = length - 1; i < j; i++, j--) { - char tmp = cstring[i]; - cstring[i] = cstring[j]; - cstring[j] = tmp; - } - cstring[length] = '\0'; - RETURN_RESULT_OF(CreateStringFromCString, cstring); +char int_to_digit(uint32_t value) { + if (value < 10) { + return '0' + value; + } else { + return 'a' + (value - 10); } } +// Radix is checked on the Kotlin side. +template OBJ_GETTER(Kotlin_toStringRadix, T value, KInt radix) { + if (value == 0) { + RETURN_RESULT_OF(CreateStringFromCString, "0"); + } + char cstring[sizeof(T) * CHAR_BIT + 1]; + bool negative = (value < 0); + if (!negative) { + value = -value; + } + + int32_t length = 0; + while (value < 0) { + cstring[length++] = int_to_digit(-(value % radix)); + value /= radix; + } + if (negative) { + cstring[length++] = '-'; + } + for (int i = 0, j = length - 1; i < j; i++, j--) { + char tmp = cstring[i]; + cstring[i] = cstring[j]; + cstring[j] = tmp; + } + cstring[length] = '\0'; + RETURN_RESULT_OF(CreateStringFromCString, cstring); +} + +} // namespace + extern "C" { OBJ_GETTER(Kotlin_Any_toString, KConstRef thiz) { char cstring[80]; - snprintf(cstring, sizeof(cstring), "%s %p type %p", - IsArray(thiz) ? "array" : "object", - thiz, thiz->type_info_); + if (IsArray(thiz)) { + konan::snprintf(cstring, sizeof(cstring), "%d@%p: array of %d", + Kotlin_Any_hashCode(thiz), thiz->type_info_, thiz->array()->count_); + } else { + konan::snprintf(cstring, sizeof(cstring), "%d@%p: object", + Kotlin_Any_hashCode(thiz), thiz->type_info_); + } RETURN_RESULT_OF(CreateStringFromCString, cstring); } @@ -113,21 +119,4 @@ OBJ_GETTER(Kotlin_Long_toStringRadix, KLong value, KInt radix) { RETURN_RESULT_OF(Kotlin_toStringRadix, value, radix) } -// TODO: use David Gay's dtoa() here instead. It's *very* big and ugly. -OBJ_GETTER(Kotlin_Float_toString, KFloat value) { - char cstring[32]; - snprintf(cstring, sizeof(cstring), "%G", value); - RETURN_RESULT_OF(CreateStringFromCString, cstring); -} - -OBJ_GETTER(Kotlin_Double_toString, KDouble value) { - char cstring[32]; - snprintf(cstring, sizeof(cstring), "%G", value); - RETURN_RESULT_OF(CreateStringFromCString, cstring); -} - -OBJ_GETTER(Kotlin_Boolean_toString, KBoolean value) { - RETURN_RESULT_OF(CreateStringFromCString, value ? "true" : "false"); -} - } // extern "C" diff --git a/runtime/src/main/kotlin/konan/internal/Util.kt b/runtime/src/main/kotlin/konan/internal/Util.kt new file mode 100644 index 00000000000..364391e2e29 --- /dev/null +++ b/runtime/src/main/kotlin/konan/internal/Util.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2010-2017 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 konan.internal + +import kotlin.text.toUtf8Array + +// Called by debugger. +@ExportForCppRuntime +fun KonanObjectToUtf8Array(value: Any?): ByteArray { + val string = when (value) { + is Array<*> -> value.contentToString() + is CharArray -> value.contentToString() + is BooleanArray -> value.contentToString() + is ByteArray -> value.contentToString() + is ShortArray -> value.contentToString() + is IntArray -> value.contentToString() + is LongArray -> value.contentToString() + is FloatArray -> value.contentToString() + is DoubleArray -> value.contentToString() + else -> value.toString() + } + return toUtf8Array(string, 0, string.length) +} \ No newline at end of file diff --git a/runtime/src/main/kotlin/kotlin/Boolean.kt b/runtime/src/main/kotlin/kotlin/Boolean.kt index c8e3c597214..75aeb0b50e6 100644 --- a/runtime/src/main/kotlin/kotlin/Boolean.kt +++ b/runtime/src/main/kotlin/kotlin/Boolean.kt @@ -54,10 +54,7 @@ public final class Boolean : Comparable { public override fun equals(other: Any?): Boolean = other is Boolean && konan.internal.areEqualByValue(this, other) - @SymbolName("Kotlin_Boolean_toString") - external public override fun toString(): String + public override fun toString() = if (this) "true" else "false" - public override fun hashCode(): Int { - return if (this) 1 else 0; - } + public override fun hashCode() = if (this) 1 else 0 } \ No newline at end of file