From e38ac775ad12dccbb621a75d4ff2d2132963934c Mon Sep 17 00:00:00 2001 From: SvyatoslavScherbina Date: Thu, 1 Dec 2016 20:10:03 +0700 Subject: [PATCH] Calling Kotlin from C++ runtime (#107) * backend: support exporting Kotlin functions for C++ runtime * runtime: implement several functions in Kotlin * Throw{NullPointer,ArrayIndexOutOfBounds,ClassCast}Exception * runtime: add literal declared in Kotlin as empty string Also * add corresponding test * use empty string literal in strdedup2 (however keep it disabled) * fixup! runtime: add literal declared in Kotlin as empty string * Review feedback: add more TheEmptyString() usages Also improve string index checks in runtime --- .../kotlin/backend/konan/llvm/NameUtils.kt | 22 ++++++++++- backend.native/tests/build.gradle | 9 ++++- .../tests/datagen/literals/strdedup2.kt | 4 +- .../tests/runtime/basic/empty_substring.kt | 4 ++ runtime/src/main/cpp/Exceptions.cpp | 38 ++----------------- runtime/src/main/cpp/Exceptions.h | 8 +++- runtime/src/main/cpp/Natives.cpp | 19 +++++++--- runtime/src/main/cpp/Natives.h | 2 +- .../main/kotlin/konan/internal/Annotations.kt | 13 +++++++ .../kotlin/konan/internal/RuntimeUtils.kt | 19 ++++++++++ 10 files changed, 90 insertions(+), 48 deletions(-) create mode 100644 backend.native/tests/runtime/basic/empty_substring.kt create mode 100644 runtime/src/main/kotlin/konan/internal/Annotations.kt create mode 100644 runtime/src/main/kotlin/konan/internal/RuntimeUtils.kt diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/NameUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/NameUtils.kt index 8350886c90a..d2418b967f6 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/NameUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/NameUtils.kt @@ -4,16 +4,20 @@ import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassKind.* import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.PropertyDescriptor +import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.resolve.constants.StringValue import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.resolve.findOriginalTopMostOverriddenDescriptors import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.TypeUtils +import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty private val symbolNameAnnotation = FqName("konan.SymbolName") +private val exportForCppRuntimeAnnotation = FqName("konan.internal.ExportForCppRuntime") + fun typeToHashString(type: KotlinType): String { if (TypeUtils.isTypeParameter(type)) return "GENERIC" @@ -52,18 +56,32 @@ internal val FunctionDescriptor.symbolName: String get() { this.annotations.findAnnotation(symbolNameAnnotation)?.let { if (this.isExternal) { - val nameValue = it.allValueArguments.values.single() as StringValue - return nameValue.value + return getStringValue(it)!! } else { // ignore; TODO: report compile error } } + + this.annotations.findAnnotation(exportForCppRuntimeAnnotation)?.let { + val name = getStringValue(it) ?: this.name.asString() + return name // no wrapping currently required + } + val containingDeclarationPart = containingDeclaration.fqNameSafe.let { if (it.isRoot) "" else "$it." } return "kfun:$containingDeclarationPart$functionName" } +private fun getStringValue(annotation: AnnotationDescriptor): String? { + annotation.allValueArguments.values.ifNotEmpty { + val stringValue = this.single() as StringValue + return stringValue.value + } + + return null +} + internal val ClassDescriptor.symbolName: String get() = when (this.kind) { CLASS -> "kclass:" diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index 0370de4d016..186bc6d85b4 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -323,6 +323,11 @@ task tostring2(type: RunKonanTest) { source = "runtime/basic/tostring2.kt" } +task empty_substring(type: RunKonanTest) { + goldValue = "\n" + source = "runtime/basic/empty_substring.kt" +} + task array0(type: RunKonanTest) { goldValue = "5\n6\n7\n8\n9\n10\n11\n12\n13\n" source = "runtime/basic/array0.kt" @@ -395,7 +400,9 @@ task strdedup1(type: RunKonanTest) { } /* -TODO: enable after supporting Unit object +TODO: string deduplication across several components seems to require +linking them as bitcode modules before translating to machine code. + task strdedup2(type: RunKonanTest) { goldValue = "true\ntrue\n" source = "datagen/literals/strdedup2.kt" diff --git a/backend.native/tests/datagen/literals/strdedup2.kt b/backend.native/tests/datagen/literals/strdedup2.kt index 083611bec95..3da7f546b16 100644 --- a/backend.native/tests/datagen/literals/strdedup2.kt +++ b/backend.native/tests/datagen/literals/strdedup2.kt @@ -1,6 +1,6 @@ fun main(args : Array) { - val str1 = "kotlin.Unit" - val str2 = Unit.toString() + val str1 = "" + val str2 = "hello".subSequence(2, 2) println(str1 == str2) println(str1 === str2) } diff --git a/backend.native/tests/runtime/basic/empty_substring.kt b/backend.native/tests/runtime/basic/empty_substring.kt new file mode 100644 index 00000000000..1ac05561f70 --- /dev/null +++ b/backend.native/tests/runtime/basic/empty_substring.kt @@ -0,0 +1,4 @@ +fun main(args : Array) { + val hello = "Hello world" + println(hello.subSequence(1, 1).toString()) +} \ No newline at end of file diff --git a/runtime/src/main/cpp/Exceptions.cpp b/runtime/src/main/cpp/Exceptions.cpp index 183c6468d18..62882da5a91 100644 --- a/runtime/src/main/cpp/Exceptions.cpp +++ b/runtime/src/main/cpp/Exceptions.cpp @@ -1,41 +1,7 @@ -#include -#include -#include - #include "Assert.h" #include "Exceptions.h" #include "Types.h" -#ifdef __cplusplus -extern "C" { -#endif -void ThrowNullPointerException() { - void* pc = __builtin_return_address(0); - char message[100]; - snprintf(message, sizeof(message), "NullPointerException at %p", pc); - write(STDOUT_FILENO, message, strlen(message)); - // TODO: throw it for real. - RuntimeAssert(false, "Throwing is unsupported"); -} - -void ThrowArrayIndexOutOfBoundsException() { - void* pc = __builtin_return_address(0); - char message[100]; - snprintf(message, sizeof(message), "ArrayIndexOutOfBoundsException at %p", pc); - write(STDOUT_FILENO, message, strlen(message)); - // TODO: throw it for real. - RuntimeAssert(false, "Throwing is unsupported"); -} - -void ThrowClassCastException() { - void* pc = __builtin_return_address(0); - char message[100]; - snprintf(message, sizeof(message), "ClassCastException at %p", pc); - write(STDOUT_FILENO, message, strlen(message)); - // TODO: throw it for real. - RuntimeAssert(false, "Throwing is unsupported"); -} - class KotlinException { public: @@ -50,6 +16,10 @@ class KotlinException { }; }; +#ifdef __cplusplus +extern "C" { +#endif + void ThrowException(KRef exception) { RuntimeAssert(exception != nullptr && IsInstance(exception, theThrowableTypeInfo), "Throwing something non-throwable"); diff --git a/runtime/src/main/cpp/Exceptions.h b/runtime/src/main/cpp/Exceptions.h index 07d39d5aa03..563e144e827 100644 --- a/runtime/src/main/cpp/Exceptions.h +++ b/runtime/src/main/cpp/Exceptions.h @@ -6,6 +6,12 @@ #ifdef __cplusplus extern "C" { #endif + +// Throws arbitrary exception. +void ThrowException(KRef exception); + +// The functions below are implemented in Kotlin (at package konan.internal). + // Throws null pointer exception. Context is evaluated from caller's address. void ThrowNullPointerException(); // Throws array index out of bounds exception. @@ -13,8 +19,6 @@ void ThrowNullPointerException(); void ThrowArrayIndexOutOfBoundsException(); // Throws class cast exception. void ThrowClassCastException(); -// Throws arbitrary exception. -void ThrowException(KRef exception); #ifdef __cplusplus } // extern "C" diff --git a/runtime/src/main/cpp/Natives.cpp b/runtime/src/main/cpp/Natives.cpp index 8775183d8be..baae7e3ba37 100644 --- a/runtime/src/main/cpp/Natives.cpp +++ b/runtime/src/main/cpp/Natives.cpp @@ -11,9 +11,6 @@ extern "C" { -// TODO: remove, once can generate empty string constant in compile time. -KString theEmptyString = nullptr; - // Any.kt KBoolean Kotlin_Any_equals(KConstRef thiz, KConstRef other) { return thiz == other; @@ -82,9 +79,14 @@ KInt Kotlin_String_getStringLength(KString thiz) { KString Kotlin_String_fromUtf8Array( const ArrayHeader* array, KInt start, KInt size) { RuntimeAssert(array->type_info() == theByteArrayTypeInfo, "Must use a byte array"); - if (start < 0 || start + size > array->count_) { + if (start < 0 || size < 0 || start + size > array->count_) { ThrowArrayIndexOutOfBoundsException(); } + + if (size == 0) { + return TheEmptyString(); + } + // TODO: support full UTF-8. ArrayHeader* result = ArrayContainer(theStringTypeInfo, size).GetPlace(); memcpy( @@ -97,9 +99,14 @@ KString Kotlin_String_fromUtf8Array( KString Kotlin_String_fromCharArray( const ArrayHeader* array, KInt start, KInt size) { RuntimeAssert(array->type_info() == theCharArrayTypeInfo, "Must use a byte array"); - if (start < 0 || start + size > array->count_) { + if (start < 0 || size < 0 || start + size > array->count_) { ThrowArrayIndexOutOfBoundsException(); } + + if (size == 0) { + return TheEmptyString(); + } + // TODO: support full UTF-8. ArrayHeader* result = ArrayContainer(theStringTypeInfo, size).GetPlace(); for (KInt index = 0; index < size; ++index) { @@ -162,7 +169,7 @@ KString Kotlin_String_subSequence(KString thiz, KInt startIndex, KInt endIndex) ThrowArrayIndexOutOfBoundsException(); } if (startIndex == endIndex) { - return theEmptyString; + return TheEmptyString(); } // TODO: support UTF-8. KInt length = endIndex - startIndex; diff --git a/runtime/src/main/cpp/Natives.h b/runtime/src/main/cpp/Natives.h index c19d7e055fa..a707eb7ef39 100644 --- a/runtime/src/main/cpp/Natives.h +++ b/runtime/src/main/cpp/Natives.h @@ -37,7 +37,7 @@ inline const KRef* ArrayAddressOfElementAt(const ArrayHeader* obj, KInt index) { extern "C" { #endif -extern KString theEmptyString; +KString TheEmptyString(); // Any.kt KBoolean Kotlin_Any_equals(KConstRef thiz, KConstRef other); diff --git a/runtime/src/main/kotlin/konan/internal/Annotations.kt b/runtime/src/main/kotlin/konan/internal/Annotations.kt new file mode 100644 index 00000000000..992e0c75df2 --- /dev/null +++ b/runtime/src/main/kotlin/konan/internal/Annotations.kt @@ -0,0 +1,13 @@ +package konan.internal + +/** + * Makes this function to be possible to call by given name from C++ part of runtime using C ABI. + * The parameters are mapped in an implementation-dependent manner. + * + * The function to call from C++ can be a wrapper around the original function. + * + * If the name is not specified, the function to call will be available by its Kotlin unqualified name. + */ +//@Target(AnnotationTarget.FUNCTION) +//@Retention(AnnotationRetention.SOURCE) +annotation class ExportForCppRuntime(val name: String = "") \ No newline at end of file diff --git a/runtime/src/main/kotlin/konan/internal/RuntimeUtils.kt b/runtime/src/main/kotlin/konan/internal/RuntimeUtils.kt new file mode 100644 index 00000000000..52371c57162 --- /dev/null +++ b/runtime/src/main/kotlin/konan/internal/RuntimeUtils.kt @@ -0,0 +1,19 @@ +package konan.internal + +@ExportForCppRuntime +internal fun ThrowNullPointerException() { + throw NullPointerException() +} + +@ExportForCppRuntime +internal fun ThrowArrayIndexOutOfBoundsException() { + throw IndexOutOfBoundsException() +} + +@ExportForCppRuntime +internal fun ThrowClassCastException() { + throw ClassCastException() +} + +@ExportForCppRuntime +internal fun TheEmptyString() = "" \ No newline at end of file