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
This commit is contained in:
committed by
Nikolay Igotti
parent
f9167ea91e
commit
e38ac775ad
+20
-2
@@ -4,16 +4,20 @@ import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
|||||||
import org.jetbrains.kotlin.descriptors.ClassKind.*
|
import org.jetbrains.kotlin.descriptors.ClassKind.*
|
||||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
||||||
|
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
import org.jetbrains.kotlin.resolve.constants.StringValue
|
import org.jetbrains.kotlin.resolve.constants.StringValue
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||||
import org.jetbrains.kotlin.resolve.findOriginalTopMostOverriddenDescriptors
|
import org.jetbrains.kotlin.resolve.findOriginalTopMostOverriddenDescriptors
|
||||||
import org.jetbrains.kotlin.types.KotlinType
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
import org.jetbrains.kotlin.types.TypeUtils
|
import org.jetbrains.kotlin.types.TypeUtils
|
||||||
|
import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty
|
||||||
|
|
||||||
|
|
||||||
private val symbolNameAnnotation = FqName("konan.SymbolName")
|
private val symbolNameAnnotation = FqName("konan.SymbolName")
|
||||||
|
|
||||||
|
private val exportForCppRuntimeAnnotation = FqName("konan.internal.ExportForCppRuntime")
|
||||||
|
|
||||||
fun typeToHashString(type: KotlinType): String {
|
fun typeToHashString(type: KotlinType): String {
|
||||||
if (TypeUtils.isTypeParameter(type)) return "GENERIC"
|
if (TypeUtils.isTypeParameter(type)) return "GENERIC"
|
||||||
|
|
||||||
@@ -52,18 +56,32 @@ internal val FunctionDescriptor.symbolName: String
|
|||||||
get() {
|
get() {
|
||||||
this.annotations.findAnnotation(symbolNameAnnotation)?.let {
|
this.annotations.findAnnotation(symbolNameAnnotation)?.let {
|
||||||
if (this.isExternal) {
|
if (this.isExternal) {
|
||||||
val nameValue = it.allValueArguments.values.single() as StringValue
|
return getStringValue(it)!!
|
||||||
return nameValue.value
|
|
||||||
} else {
|
} else {
|
||||||
// ignore; TODO: report compile error
|
// 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 {
|
val containingDeclarationPart = containingDeclaration.fqNameSafe.let {
|
||||||
if (it.isRoot) "" else "$it."
|
if (it.isRoot) "" else "$it."
|
||||||
}
|
}
|
||||||
return "kfun:$containingDeclarationPart$functionName"
|
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
|
internal val ClassDescriptor.symbolName: String
|
||||||
get() = when (this.kind) {
|
get() = when (this.kind) {
|
||||||
CLASS -> "kclass:"
|
CLASS -> "kclass:"
|
||||||
|
|||||||
@@ -323,6 +323,11 @@ task tostring2(type: RunKonanTest) {
|
|||||||
source = "runtime/basic/tostring2.kt"
|
source = "runtime/basic/tostring2.kt"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task empty_substring(type: RunKonanTest) {
|
||||||
|
goldValue = "\n"
|
||||||
|
source = "runtime/basic/empty_substring.kt"
|
||||||
|
}
|
||||||
|
|
||||||
task array0(type: RunKonanTest) {
|
task array0(type: RunKonanTest) {
|
||||||
goldValue = "5\n6\n7\n8\n9\n10\n11\n12\n13\n"
|
goldValue = "5\n6\n7\n8\n9\n10\n11\n12\n13\n"
|
||||||
source = "runtime/basic/array0.kt"
|
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) {
|
task strdedup2(type: RunKonanTest) {
|
||||||
goldValue = "true\ntrue\n"
|
goldValue = "true\ntrue\n"
|
||||||
source = "datagen/literals/strdedup2.kt"
|
source = "datagen/literals/strdedup2.kt"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
fun main(args : Array<String>) {
|
fun main(args : Array<String>) {
|
||||||
val str1 = "kotlin.Unit"
|
val str1 = ""
|
||||||
val str2 = Unit.toString()
|
val str2 = "hello".subSequence(2, 2)
|
||||||
println(str1 == str2)
|
println(str1 == str2)
|
||||||
println(str1 === str2)
|
println(str1 === str2)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
fun main(args : Array<String>) {
|
||||||
|
val hello = "Hello world"
|
||||||
|
println(hello.subSequence(1, 1).toString())
|
||||||
|
}
|
||||||
@@ -1,41 +1,7 @@
|
|||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include "Assert.h"
|
#include "Assert.h"
|
||||||
#include "Exceptions.h"
|
#include "Exceptions.h"
|
||||||
#include "Types.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 {
|
class KotlinException {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
@@ -50,6 +16,10 @@ class KotlinException {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
void ThrowException(KRef exception) {
|
void ThrowException(KRef exception) {
|
||||||
RuntimeAssert(exception != nullptr && IsInstance(exception, theThrowableTypeInfo),
|
RuntimeAssert(exception != nullptr && IsInstance(exception, theThrowableTypeInfo),
|
||||||
"Throwing something non-throwable");
|
"Throwing something non-throwable");
|
||||||
|
|||||||
@@ -6,6 +6,12 @@
|
|||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#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.
|
// Throws null pointer exception. Context is evaluated from caller's address.
|
||||||
void ThrowNullPointerException();
|
void ThrowNullPointerException();
|
||||||
// Throws array index out of bounds exception.
|
// Throws array index out of bounds exception.
|
||||||
@@ -13,8 +19,6 @@ void ThrowNullPointerException();
|
|||||||
void ThrowArrayIndexOutOfBoundsException();
|
void ThrowArrayIndexOutOfBoundsException();
|
||||||
// Throws class cast exception.
|
// Throws class cast exception.
|
||||||
void ThrowClassCastException();
|
void ThrowClassCastException();
|
||||||
// Throws arbitrary exception.
|
|
||||||
void ThrowException(KRef exception);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
|
|||||||
@@ -11,9 +11,6 @@
|
|||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
// TODO: remove, once can generate empty string constant in compile time.
|
|
||||||
KString theEmptyString = nullptr;
|
|
||||||
|
|
||||||
// Any.kt
|
// Any.kt
|
||||||
KBoolean Kotlin_Any_equals(KConstRef thiz, KConstRef other) {
|
KBoolean Kotlin_Any_equals(KConstRef thiz, KConstRef other) {
|
||||||
return thiz == other;
|
return thiz == other;
|
||||||
@@ -82,9 +79,14 @@ KInt Kotlin_String_getStringLength(KString thiz) {
|
|||||||
KString Kotlin_String_fromUtf8Array(
|
KString Kotlin_String_fromUtf8Array(
|
||||||
const ArrayHeader* array, KInt start, KInt size) {
|
const ArrayHeader* array, KInt start, KInt size) {
|
||||||
RuntimeAssert(array->type_info() == theByteArrayTypeInfo, "Must use a byte array");
|
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();
|
ThrowArrayIndexOutOfBoundsException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (size == 0) {
|
||||||
|
return TheEmptyString();
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: support full UTF-8.
|
// TODO: support full UTF-8.
|
||||||
ArrayHeader* result = ArrayContainer(theStringTypeInfo, size).GetPlace();
|
ArrayHeader* result = ArrayContainer(theStringTypeInfo, size).GetPlace();
|
||||||
memcpy(
|
memcpy(
|
||||||
@@ -97,9 +99,14 @@ KString Kotlin_String_fromUtf8Array(
|
|||||||
KString Kotlin_String_fromCharArray(
|
KString Kotlin_String_fromCharArray(
|
||||||
const ArrayHeader* array, KInt start, KInt size) {
|
const ArrayHeader* array, KInt start, KInt size) {
|
||||||
RuntimeAssert(array->type_info() == theCharArrayTypeInfo, "Must use a byte array");
|
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();
|
ThrowArrayIndexOutOfBoundsException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (size == 0) {
|
||||||
|
return TheEmptyString();
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: support full UTF-8.
|
// TODO: support full UTF-8.
|
||||||
ArrayHeader* result = ArrayContainer(theStringTypeInfo, size).GetPlace();
|
ArrayHeader* result = ArrayContainer(theStringTypeInfo, size).GetPlace();
|
||||||
for (KInt index = 0; index < size; ++index) {
|
for (KInt index = 0; index < size; ++index) {
|
||||||
@@ -162,7 +169,7 @@ KString Kotlin_String_subSequence(KString thiz, KInt startIndex, KInt endIndex)
|
|||||||
ThrowArrayIndexOutOfBoundsException();
|
ThrowArrayIndexOutOfBoundsException();
|
||||||
}
|
}
|
||||||
if (startIndex == endIndex) {
|
if (startIndex == endIndex) {
|
||||||
return theEmptyString;
|
return TheEmptyString();
|
||||||
}
|
}
|
||||||
// TODO: support UTF-8.
|
// TODO: support UTF-8.
|
||||||
KInt length = endIndex - startIndex;
|
KInt length = endIndex - startIndex;
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ inline const KRef* ArrayAddressOfElementAt(const ArrayHeader* obj, KInt index) {
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
extern KString theEmptyString;
|
KString TheEmptyString();
|
||||||
|
|
||||||
// Any.kt
|
// Any.kt
|
||||||
KBoolean Kotlin_Any_equals(KConstRef thiz, KConstRef other);
|
KBoolean Kotlin_Any_equals(KConstRef thiz, KConstRef other);
|
||||||
|
|||||||
@@ -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 = "")
|
||||||
@@ -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() = ""
|
||||||
Reference in New Issue
Block a user