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.lower.bridgeTarget
|
||||
import org.jetbrains.kotlin.descriptors.Modality
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.expressions.IrClassReference
|
||||
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
|
||||
|
||||
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) {
|
||||
private val DEBUG = 0
|
||||
|
||||
@@ -218,6 +279,8 @@ internal class ClassLayoutBuilder(val irClass: IrClass, val context: Context) {
|
||||
result
|
||||
}
|
||||
|
||||
lateinit var hierarchyInfo: ClassGlobalHierarchyInfo
|
||||
|
||||
/**
|
||||
* Fields declared in the class.
|
||||
*/
|
||||
|
||||
+2
@@ -225,6 +225,8 @@ internal class KonanSymbols(
|
||||
|
||||
override val ThrowTypeCastException = internalFunction("ThrowTypeCastException")
|
||||
|
||||
val throwClassCastException = internalFunction("ThrowClassCastException")
|
||||
|
||||
val throwInvalidReceiverTypeException = internalFunction("ThrowInvalidReceiverTypeException")
|
||||
val throwIllegalStateException = internalFunction("ThrowIllegalStateException")
|
||||
|
||||
|
||||
+6
-1
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.backend.konan.llvm
|
||||
|
||||
import llvm.*
|
||||
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.descriptors.ClassKind
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
@@ -37,7 +38,11 @@ internal val contextLLVMSetupPhase = makeKonanModuleOpPhase(
|
||||
internal val RTTIPhase = makeKonanModuleOpPhase(
|
||||
name = "RTTI",
|
||||
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(
|
||||
|
||||
+1
-1
@@ -434,7 +434,7 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) {
|
||||
val leaveFrameFunction = importModelSpecificRtFunction("LeaveFrame")
|
||||
val lookupOpenMethodFunction = importRtFunction("LookupOpenMethod")
|
||||
val isInstanceFunction = importRtFunction("IsInstance")
|
||||
val checkInstanceFunction = importRtFunction("CheckInstance")
|
||||
val isInstanceOfClassFastFunction = importRtFunction("IsInstanceOfClassFast")
|
||||
val throwExceptionFunction = importRtFunction("ThrowException")
|
||||
val appendToInitalizersTail = importRtFunction("AppendToInitializersTail")
|
||||
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)}"}
|
||||
val dstClass = value.typeOperand.getClass()!!
|
||||
|
||||
val srcArg = evaluateExpression(value.argument) // Evaluate src expression.
|
||||
val srcArg = evaluateExpression(value.argument)
|
||||
assert(srcArg.type == codegen.kObjHeaderPtr)
|
||||
|
||||
if (dstClass.defaultType.isObjCObjectType()) {
|
||||
with(functionGenerationContext) {
|
||||
ifThen(not(genInstanceOf(srcArg, dstClass))) {
|
||||
with(functionGenerationContext) {
|
||||
ifThen(not(genInstanceOf(srcArg, dstClass))) {
|
||||
if (dstClass.defaultType.isObjCObjectType()) {
|
||||
callDirect(
|
||||
context.ir.symbols.ThrowTypeCastException.owner,
|
||||
emptyList(),
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1322,7 +1321,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
||||
} else {
|
||||
// E.g. when generating type operation with reified type parameter in the original body of inline function.
|
||||
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)
|
||||
val bbInstanceOfResult = functionGenerationContext.currentBlock
|
||||
@@ -1340,11 +1339,15 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
||||
return genInstanceOfObjC(obj, dstClass)
|
||||
}
|
||||
|
||||
val dstTypeInfo = codegen.typeInfoValue(dstClass) // Get TypeInfo for dst type.
|
||||
val srcObjInfoPtr = functionGenerationContext.bitcast(codegen.kObjHeaderPtr, obj) // Cast src to ObjInfoPtr.
|
||||
val args = listOf(srcObjInfoPtr, dstTypeInfo) // Create arg list.
|
||||
|
||||
return call(context.llvm.isInstanceFunction, args) // Check if dst is subclass of src.
|
||||
val srcObjInfoPtr = functionGenerationContext.bitcast(codegen.kObjHeaderPtr, obj)
|
||||
return if (context.shouldOptimize() && !dstClass.isInterface) {
|
||||
val dstHierarchyInfo = context.getLayoutBuilder(dstClass).hierarchyInfo
|
||||
call(context.llvm.isInstanceOfClassFastFunction,
|
||||
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 {
|
||||
|
||||
+13
@@ -82,6 +82,7 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
|
||||
packageName: String?,
|
||||
relativeName: String?,
|
||||
flags: Int,
|
||||
classId: Int,
|
||||
writableTypeInfo: ConstPointer?,
|
||||
associatedObjects: ConstPointer?) :
|
||||
|
||||
@@ -112,6 +113,8 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
|
||||
|
||||
Int32(flags),
|
||||
|
||||
Int32(classId),
|
||||
|
||||
*listOfNotNull(writableTypeInfo).toTypedArray(),
|
||||
|
||||
associatedObjects
|
||||
@@ -208,6 +211,10 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
|
||||
|
||||
val reflectionInfo = getReflectionInfo(irClass)
|
||||
val typeInfoGlobal = llvmDeclarations.typeInfoGlobal
|
||||
val hierarchyInfo =
|
||||
if (context.shouldOptimize())
|
||||
context.getLayoutBuilder(irClass).hierarchyInfo
|
||||
else ClassGlobalHierarchyInfo.DUMMY
|
||||
val typeInfo = TypeInfo(
|
||||
irClass.typeInfoPtr,
|
||||
makeExtendedInfo(irClass),
|
||||
@@ -219,6 +226,7 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
|
||||
reflectionInfo.packageName,
|
||||
reflectionInfo.relativeName,
|
||||
flagsFromClass(irClass),
|
||||
hierarchyInfo.classIdLo,
|
||||
llvmDeclarations.writableTypeInfoGlobal?.pointer,
|
||||
associatedObjects = genAssociatedObjects(irClass)
|
||||
)
|
||||
@@ -388,6 +396,10 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
|
||||
val typeInfoWithVtableType = structType(runtime.typeInfoType, vtable.llvmType)
|
||||
val typeInfoWithVtableGlobal = staticData.createGlobal(typeInfoWithVtableType, "", isExported = false)
|
||||
val result = typeInfoWithVtableGlobal.pointer.getElementPtr(0)
|
||||
val typeHierarchyInfo = if (!context.shouldOptimize())
|
||||
ClassGlobalHierarchyInfo.DUMMY
|
||||
else
|
||||
ClassGlobalHierarchyInfo(-1, -1)
|
||||
val typeInfoWithVtable = Struct(TypeInfo(
|
||||
selfPtr = result,
|
||||
extendedInfo = NullPointer(runtime.extendedTypeInfoType),
|
||||
@@ -399,6 +411,7 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
|
||||
packageName = reflectionInfo.packageName,
|
||||
relativeName = reflectionInfo.relativeName,
|
||||
flags = flagsFromClass(irClass) or (if (immutable) TF_IMMUTABLE else 0),
|
||||
classId = typeHierarchyInfo.classIdLo,
|
||||
writableTypeInfo = writableTypeInfo,
|
||||
associatedObjects = null
|
||||
), vtable)
|
||||
|
||||
@@ -115,6 +115,9 @@ struct TypeInfo {
|
||||
// Various 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
|
||||
WritableTypeInfo* writableInfo_;
|
||||
#endif
|
||||
|
||||
@@ -38,18 +38,19 @@ KBoolean IsInstance(const ObjHeader* obj, const TypeInfo* type_info) {
|
||||
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) {
|
||||
RuntimeAssert(obj != nullptr, "Object must not be null");
|
||||
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) {
|
||||
return IsInstance(obj, reinterpret_cast<const TypeInfo*>(typeInfo));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user