Implemented fast type check for classes
This commit is contained in:
+63
@@ -12,9 +12,13 @@ import org.jetbrains.kotlin.backend.konan.llvm.functionName
|
|||||||
import org.jetbrains.kotlin.backend.konan.llvm.localHash
|
import org.jetbrains.kotlin.backend.konan.llvm.localHash
|
||||||
import org.jetbrains.kotlin.backend.konan.lower.bridgeTarget
|
import org.jetbrains.kotlin.backend.konan.lower.bridgeTarget
|
||||||
import org.jetbrains.kotlin.descriptors.Modality
|
import org.jetbrains.kotlin.descriptors.Modality
|
||||||
|
import org.jetbrains.kotlin.ir.IrElement
|
||||||
import org.jetbrains.kotlin.ir.declarations.*
|
import org.jetbrains.kotlin.ir.declarations.*
|
||||||
import org.jetbrains.kotlin.ir.expressions.IrClassReference
|
import org.jetbrains.kotlin.ir.expressions.IrClassReference
|
||||||
import org.jetbrains.kotlin.ir.util.*
|
import org.jetbrains.kotlin.ir.util.*
|
||||||
|
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
|
||||||
|
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
|
||||||
|
import org.jetbrains.kotlin.ir.visitors.acceptVoid
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
|
|
||||||
internal class OverriddenFunctionInfo(
|
internal class OverriddenFunctionInfo(
|
||||||
@@ -77,6 +81,63 @@ internal class OverriddenFunctionInfo(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal class ClassGlobalHierarchyInfo(val classIdLo: Int, val classIdHi: Int) {
|
||||||
|
companion object {
|
||||||
|
val DUMMY = ClassGlobalHierarchyInfo(0, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class GlobalHierarchyAnalysis(val context: Context) {
|
||||||
|
fun run() {
|
||||||
|
/*
|
||||||
|
* Here's the explanation of what's happening here:
|
||||||
|
* Given a tree we can traverse it with the DFS and save for each vertex two times:
|
||||||
|
* the enter time (the first time we saw this vertex) and the exit time (the last time we saw it).
|
||||||
|
* It turns out that if we assign then for each vertex the interval (enterTime, exitTime),
|
||||||
|
* then the following claim holds for any two vertices v and w:
|
||||||
|
* ----- v is ancestor of w iff interval(v) contains interval(w) ------
|
||||||
|
* Now apply this idea to the classes hierarchy tree and we'll get a fast type check.
|
||||||
|
*
|
||||||
|
* And one more observation: for each pair of intervals they either don't intersect or
|
||||||
|
* one contains the other. With that in mind, we can save in a type info only one end of an interval.
|
||||||
|
*/
|
||||||
|
val root = context.irBuiltIns.anyClass.owner
|
||||||
|
val immediateInheritors = mutableMapOf<IrClass, MutableList<IrClass>>()
|
||||||
|
val allClasses = mutableListOf<IrClass>()
|
||||||
|
context.irModule!!.acceptVoid(object: IrElementVisitorVoid {
|
||||||
|
override fun visitElement(element: IrElement) {
|
||||||
|
element.acceptChildrenVoid(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitClass(declaration: IrClass) {
|
||||||
|
if (declaration.isInterface)
|
||||||
|
context.getLayoutBuilder(declaration).hierarchyInfo = ClassGlobalHierarchyInfo(0, 0)
|
||||||
|
else {
|
||||||
|
allClasses += declaration
|
||||||
|
if (declaration != root) {
|
||||||
|
val superClass = declaration.getSuperClassNotAny() ?: root
|
||||||
|
val inheritors = immediateInheritors.getOrPut(superClass) { mutableListOf() }
|
||||||
|
inheritors.add(declaration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.visitClass(declaration)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
var time = 0
|
||||||
|
|
||||||
|
fun dfs(irClass: IrClass) {
|
||||||
|
++time
|
||||||
|
// Make the Any's interval's left border -1 in order to correctly generate classes for ObjC blocks.
|
||||||
|
val enterTime = if (irClass == root) -1 else time
|
||||||
|
immediateInheritors[irClass]?.forEach { dfs(it) }
|
||||||
|
val exitTime = time
|
||||||
|
context.getLayoutBuilder(irClass).hierarchyInfo = ClassGlobalHierarchyInfo(enterTime, exitTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
dfs(root)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal class ClassLayoutBuilder(val irClass: IrClass, val context: Context) {
|
internal class ClassLayoutBuilder(val irClass: IrClass, val context: Context) {
|
||||||
private val DEBUG = 0
|
private val DEBUG = 0
|
||||||
|
|
||||||
@@ -218,6 +279,8 @@ internal class ClassLayoutBuilder(val irClass: IrClass, val context: Context) {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lateinit var hierarchyInfo: ClassGlobalHierarchyInfo
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fields declared in the class.
|
* Fields declared in the class.
|
||||||
*/
|
*/
|
||||||
|
|||||||
+2
@@ -225,6 +225,8 @@ internal class KonanSymbols(
|
|||||||
|
|
||||||
override val ThrowTypeCastException = internalFunction("ThrowTypeCastException")
|
override val ThrowTypeCastException = internalFunction("ThrowTypeCastException")
|
||||||
|
|
||||||
|
val throwClassCastException = internalFunction("ThrowClassCastException")
|
||||||
|
|
||||||
val throwInvalidReceiverTypeException = internalFunction("ThrowInvalidReceiverTypeException")
|
val throwInvalidReceiverTypeException = internalFunction("ThrowInvalidReceiverTypeException")
|
||||||
val throwIllegalStateException = internalFunction("ThrowIllegalStateException")
|
val throwIllegalStateException = internalFunction("ThrowIllegalStateException")
|
||||||
|
|
||||||
|
|||||||
+6
-1
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.backend.konan.llvm
|
|||||||
|
|
||||||
import llvm.*
|
import llvm.*
|
||||||
import org.jetbrains.kotlin.backend.konan.*
|
import org.jetbrains.kotlin.backend.konan.*
|
||||||
|
import org.jetbrains.kotlin.backend.konan.descriptors.GlobalHierarchyAnalysis
|
||||||
import org.jetbrains.kotlin.backend.konan.optimizations.*
|
import org.jetbrains.kotlin.backend.konan.optimizations.*
|
||||||
import org.jetbrains.kotlin.descriptors.ClassKind
|
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||||
import org.jetbrains.kotlin.ir.IrElement
|
import org.jetbrains.kotlin.ir.IrElement
|
||||||
@@ -37,7 +38,11 @@ internal val contextLLVMSetupPhase = makeKonanModuleOpPhase(
|
|||||||
internal val RTTIPhase = makeKonanModuleOpPhase(
|
internal val RTTIPhase = makeKonanModuleOpPhase(
|
||||||
name = "RTTI",
|
name = "RTTI",
|
||||||
description = "RTTI generation",
|
description = "RTTI generation",
|
||||||
op = { context, irModule -> irModule.acceptVoid(RTTIGeneratorVisitor(context)) }
|
op = { context, irModule ->
|
||||||
|
if (context.shouldOptimize())
|
||||||
|
GlobalHierarchyAnalysis(context).run()
|
||||||
|
irModule.acceptVoid(RTTIGeneratorVisitor(context))
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
internal val generateDebugInfoHeaderPhase = makeKonanModuleOpPhase(
|
internal val generateDebugInfoHeaderPhase = makeKonanModuleOpPhase(
|
||||||
|
|||||||
+1
-1
@@ -434,7 +434,7 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) {
|
|||||||
val leaveFrameFunction = importModelSpecificRtFunction("LeaveFrame")
|
val leaveFrameFunction = importModelSpecificRtFunction("LeaveFrame")
|
||||||
val lookupOpenMethodFunction = importRtFunction("LookupOpenMethod")
|
val lookupOpenMethodFunction = importRtFunction("LookupOpenMethod")
|
||||||
val isInstanceFunction = importRtFunction("IsInstance")
|
val isInstanceFunction = importRtFunction("IsInstance")
|
||||||
val checkInstanceFunction = importRtFunction("CheckInstance")
|
val isInstanceOfClassFastFunction = importRtFunction("IsInstanceOfClassFast")
|
||||||
val throwExceptionFunction = importRtFunction("ThrowException")
|
val throwExceptionFunction = importRtFunction("ThrowException")
|
||||||
val appendToInitalizersTail = importRtFunction("AppendToInitializersTail")
|
val appendToInitalizersTail = importRtFunction("AppendToInitializersTail")
|
||||||
val initRuntimeIfNeeded = importRtFunction("Kotlin_initRuntimeIfNeeded")
|
val initRuntimeIfNeeded = importRtFunction("Kotlin_initRuntimeIfNeeded")
|
||||||
|
|||||||
+21
-18
@@ -1271,28 +1271,27 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
|||||||
context.log{"evaluateCast : ${ir2string(value)}"}
|
context.log{"evaluateCast : ${ir2string(value)}"}
|
||||||
val dstClass = value.typeOperand.getClass()!!
|
val dstClass = value.typeOperand.getClass()!!
|
||||||
|
|
||||||
val srcArg = evaluateExpression(value.argument) // Evaluate src expression.
|
val srcArg = evaluateExpression(value.argument)
|
||||||
assert(srcArg.type == codegen.kObjHeaderPtr)
|
assert(srcArg.type == codegen.kObjHeaderPtr)
|
||||||
|
|
||||||
if (dstClass.defaultType.isObjCObjectType()) {
|
with(functionGenerationContext) {
|
||||||
with(functionGenerationContext) {
|
ifThen(not(genInstanceOf(srcArg, dstClass))) {
|
||||||
ifThen(not(genInstanceOf(srcArg, dstClass))) {
|
if (dstClass.defaultType.isObjCObjectType()) {
|
||||||
callDirect(
|
callDirect(
|
||||||
context.ir.symbols.ThrowTypeCastException.owner,
|
context.ir.symbols.ThrowTypeCastException.owner,
|
||||||
emptyList(),
|
emptyList(),
|
||||||
Lifetime.GLOBAL
|
Lifetime.GLOBAL
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
val dstTypeInfo = functionGenerationContext.bitcast(kInt8Ptr, codegen.typeInfoValue(dstClass))
|
||||||
|
callDirect(
|
||||||
|
context.ir.symbols.throwClassCastException.owner,
|
||||||
|
listOf(srcArg, dstTypeInfo),
|
||||||
|
Lifetime.GLOBAL
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return srcArg
|
|
||||||
}
|
}
|
||||||
// Note: the code above would actually work for any classes.
|
|
||||||
// However, the code generated below is shorter. Consider it to be a specialization.
|
|
||||||
|
|
||||||
val dstTypeInfo = codegen.typeInfoValue(dstClass) // Get TypeInfo for dst type.
|
|
||||||
val srcObjInfoPtr = functionGenerationContext.bitcast(codegen.kObjHeaderPtr, srcArg) // Cast src to ObjInfoPtr.
|
|
||||||
val args = listOf(srcObjInfoPtr, dstTypeInfo) // Create arg list.
|
|
||||||
call(context.llvm.checkInstanceFunction, args) // Check if dst is subclass of src.
|
|
||||||
return srcArg
|
return srcArg
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1322,7 +1321,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
|||||||
} else {
|
} else {
|
||||||
// E.g. when generating type operation with reified type parameter in the original body of inline function.
|
// E.g. when generating type operation with reified type parameter in the original body of inline function.
|
||||||
kTrue
|
kTrue
|
||||||
// TODO: these code should be unreachable, however [BridgesBuilding] generates IR with such type checks.
|
// TODO: this code should be unreachable, however [BridgesBuilding] generates IR with such type checks.
|
||||||
}
|
}
|
||||||
functionGenerationContext.br(bbExit)
|
functionGenerationContext.br(bbExit)
|
||||||
val bbInstanceOfResult = functionGenerationContext.currentBlock
|
val bbInstanceOfResult = functionGenerationContext.currentBlock
|
||||||
@@ -1340,11 +1339,15 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
|||||||
return genInstanceOfObjC(obj, dstClass)
|
return genInstanceOfObjC(obj, dstClass)
|
||||||
}
|
}
|
||||||
|
|
||||||
val dstTypeInfo = codegen.typeInfoValue(dstClass) // Get TypeInfo for dst type.
|
val srcObjInfoPtr = functionGenerationContext.bitcast(codegen.kObjHeaderPtr, obj)
|
||||||
val srcObjInfoPtr = functionGenerationContext.bitcast(codegen.kObjHeaderPtr, obj) // Cast src to ObjInfoPtr.
|
return if (context.shouldOptimize() && !dstClass.isInterface) {
|
||||||
val args = listOf(srcObjInfoPtr, dstTypeInfo) // Create arg list.
|
val dstHierarchyInfo = context.getLayoutBuilder(dstClass).hierarchyInfo
|
||||||
|
call(context.llvm.isInstanceOfClassFastFunction,
|
||||||
return call(context.llvm.isInstanceFunction, args) // Check if dst is subclass of src.
|
listOf(srcObjInfoPtr, Int32(dstHierarchyInfo.classIdLo).llvm, Int32(dstHierarchyInfo.classIdHi).llvm))
|
||||||
|
} else {
|
||||||
|
val dstTypeInfo = codegen.typeInfoValue(dstClass)
|
||||||
|
call(context.llvm.isInstanceFunction, listOf(srcObjInfoPtr, dstTypeInfo))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun genInstanceOfObjC(obj: LLVMValueRef, dstClass: IrClass): LLVMValueRef {
|
private fun genInstanceOfObjC(obj: LLVMValueRef, dstClass: IrClass): LLVMValueRef {
|
||||||
|
|||||||
+13
@@ -82,6 +82,7 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
|
|||||||
packageName: String?,
|
packageName: String?,
|
||||||
relativeName: String?,
|
relativeName: String?,
|
||||||
flags: Int,
|
flags: Int,
|
||||||
|
classId: Int,
|
||||||
writableTypeInfo: ConstPointer?,
|
writableTypeInfo: ConstPointer?,
|
||||||
associatedObjects: ConstPointer?) :
|
associatedObjects: ConstPointer?) :
|
||||||
|
|
||||||
@@ -112,6 +113,8 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
|
|||||||
|
|
||||||
Int32(flags),
|
Int32(flags),
|
||||||
|
|
||||||
|
Int32(classId),
|
||||||
|
|
||||||
*listOfNotNull(writableTypeInfo).toTypedArray(),
|
*listOfNotNull(writableTypeInfo).toTypedArray(),
|
||||||
|
|
||||||
associatedObjects
|
associatedObjects
|
||||||
@@ -208,6 +211,10 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
|
|||||||
|
|
||||||
val reflectionInfo = getReflectionInfo(irClass)
|
val reflectionInfo = getReflectionInfo(irClass)
|
||||||
val typeInfoGlobal = llvmDeclarations.typeInfoGlobal
|
val typeInfoGlobal = llvmDeclarations.typeInfoGlobal
|
||||||
|
val hierarchyInfo =
|
||||||
|
if (context.shouldOptimize())
|
||||||
|
context.getLayoutBuilder(irClass).hierarchyInfo
|
||||||
|
else ClassGlobalHierarchyInfo.DUMMY
|
||||||
val typeInfo = TypeInfo(
|
val typeInfo = TypeInfo(
|
||||||
irClass.typeInfoPtr,
|
irClass.typeInfoPtr,
|
||||||
makeExtendedInfo(irClass),
|
makeExtendedInfo(irClass),
|
||||||
@@ -219,6 +226,7 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
|
|||||||
reflectionInfo.packageName,
|
reflectionInfo.packageName,
|
||||||
reflectionInfo.relativeName,
|
reflectionInfo.relativeName,
|
||||||
flagsFromClass(irClass),
|
flagsFromClass(irClass),
|
||||||
|
hierarchyInfo.classIdLo,
|
||||||
llvmDeclarations.writableTypeInfoGlobal?.pointer,
|
llvmDeclarations.writableTypeInfoGlobal?.pointer,
|
||||||
associatedObjects = genAssociatedObjects(irClass)
|
associatedObjects = genAssociatedObjects(irClass)
|
||||||
)
|
)
|
||||||
@@ -388,6 +396,10 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
|
|||||||
val typeInfoWithVtableType = structType(runtime.typeInfoType, vtable.llvmType)
|
val typeInfoWithVtableType = structType(runtime.typeInfoType, vtable.llvmType)
|
||||||
val typeInfoWithVtableGlobal = staticData.createGlobal(typeInfoWithVtableType, "", isExported = false)
|
val typeInfoWithVtableGlobal = staticData.createGlobal(typeInfoWithVtableType, "", isExported = false)
|
||||||
val result = typeInfoWithVtableGlobal.pointer.getElementPtr(0)
|
val result = typeInfoWithVtableGlobal.pointer.getElementPtr(0)
|
||||||
|
val typeHierarchyInfo = if (!context.shouldOptimize())
|
||||||
|
ClassGlobalHierarchyInfo.DUMMY
|
||||||
|
else
|
||||||
|
ClassGlobalHierarchyInfo(-1, -1)
|
||||||
val typeInfoWithVtable = Struct(TypeInfo(
|
val typeInfoWithVtable = Struct(TypeInfo(
|
||||||
selfPtr = result,
|
selfPtr = result,
|
||||||
extendedInfo = NullPointer(runtime.extendedTypeInfoType),
|
extendedInfo = NullPointer(runtime.extendedTypeInfoType),
|
||||||
@@ -399,6 +411,7 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
|
|||||||
packageName = reflectionInfo.packageName,
|
packageName = reflectionInfo.packageName,
|
||||||
relativeName = reflectionInfo.relativeName,
|
relativeName = reflectionInfo.relativeName,
|
||||||
flags = flagsFromClass(irClass) or (if (immutable) TF_IMMUTABLE else 0),
|
flags = flagsFromClass(irClass) or (if (immutable) TF_IMMUTABLE else 0),
|
||||||
|
classId = typeHierarchyInfo.classIdLo,
|
||||||
writableTypeInfo = writableTypeInfo,
|
writableTypeInfo = writableTypeInfo,
|
||||||
associatedObjects = null
|
associatedObjects = null
|
||||||
), vtable)
|
), vtable)
|
||||||
|
|||||||
@@ -115,6 +115,9 @@ struct TypeInfo {
|
|||||||
// Various flags.
|
// Various flags.
|
||||||
int32_t flags_;
|
int32_t flags_;
|
||||||
|
|
||||||
|
// Class id built with the whole class hierarchy taken into account. The details are in ClassLayoutBuilder.
|
||||||
|
int32_t classId_;
|
||||||
|
|
||||||
#if KONAN_TYPE_INFO_HAS_WRITABLE_PART
|
#if KONAN_TYPE_INFO_HAS_WRITABLE_PART
|
||||||
WritableTypeInfo* writableInfo_;
|
WritableTypeInfo* writableInfo_;
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -38,18 +38,19 @@ KBoolean IsInstance(const ObjHeader* obj, const TypeInfo* type_info) {
|
|||||||
return obj_type_info != nullptr;
|
return obj_type_info != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
KBoolean IsInstanceOfClassFast(const ObjHeader* obj, int32_t lo, int32_t hi) {
|
||||||
|
// We assume null check is handled by caller.
|
||||||
|
RuntimeAssert(obj != nullptr, "must not be null");
|
||||||
|
const TypeInfo* obj_type_info = obj->type_info();
|
||||||
|
// Super type's interval should contain our interval.
|
||||||
|
return obj_type_info->classId_ >= lo && obj_type_info->classId_ <= hi;
|
||||||
|
}
|
||||||
|
|
||||||
KBoolean IsArray(KConstRef obj) {
|
KBoolean IsArray(KConstRef obj) {
|
||||||
RuntimeAssert(obj != nullptr, "Object must not be null");
|
RuntimeAssert(obj != nullptr, "Object must not be null");
|
||||||
return obj->type_info()->instanceSize_ < 0;
|
return obj->type_info()->instanceSize_ < 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CheckInstance(const ObjHeader* obj, const TypeInfo* type_info) {
|
|
||||||
if (IsInstance(obj, type_info)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ThrowClassCastException(obj, type_info);
|
|
||||||
}
|
|
||||||
|
|
||||||
KBoolean Kotlin_TypeInfo_isInstance(KConstRef obj, KNativePtr typeInfo) {
|
KBoolean Kotlin_TypeInfo_isInstance(KConstRef obj, KNativePtr typeInfo) {
|
||||||
return IsInstance(obj, reinterpret_cast<const TypeInfo*>(typeInfo));
|
return IsInstance(obj, reinterpret_cast<const TypeInfo*>(typeInfo));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user