Implemented fast type check for classes

This commit is contained in:
Igor Chevdar
2019-08-27 16:02:10 +03:00
parent 844597fbbb
commit 8a6146ac08
8 changed files with 117 additions and 27 deletions
@@ -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.
*/
@@ -225,6 +225,8 @@ internal class KonanSymbols(
override val ThrowTypeCastException = internalFunction("ThrowTypeCastException")
val throwClassCastException = internalFunction("ThrowClassCastException")
val throwInvalidReceiverTypeException = internalFunction("ThrowInvalidReceiverTypeException")
val throwIllegalStateException = internalFunction("ThrowIllegalStateException")
@@ -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(
@@ -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")
@@ -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 {
@@ -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)
+3
View File
@@ -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
+8 -7
View File
@@ -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));
}