[optmz][codegen] Rewrote escape analysis

This commit is contained in:
Igor Chevdar
2020-07-09 17:38:37 +05:00
parent 981b1e5632
commit 31f4655ebd
21 changed files with 1684 additions and 570 deletions
@@ -20,6 +20,7 @@ internal class DirectedGraphMultiNode<out K>(val nodes: Set<K>)
internal class DirectedGraphCondensation<out K>(val topologicalOrder: List<DirectedGraphMultiNode<K>>)
// The Kosoraju-Sharir algorithm.
internal class DirectedGraphCondensationBuilder<K, out N: DirectedGraphNode<K>>(private val graph: DirectedGraph<K, N>) {
private val visited = mutableSetOf<K>()
private val order = mutableListOf<N>()
@@ -522,7 +522,7 @@ internal fun PhaseConfig.disableUnless(phase: AnyNamedPhase, condition: Boolean)
internal fun PhaseConfig.konanPhasesConfig(config: KonanConfig) {
with(config.configuration) {
disable(compileTimeEvaluatePhase)
disable(escapeAnalysisPhase)
disable(localEscapeAnalysisPhase)
// Don't serialize anything to a final executable.
disableUnless(serializerPhase, config.produce == CompilerOutputKind.LIBRARY)
@@ -536,7 +536,7 @@ internal fun PhaseConfig.konanPhasesConfig(config: KonanConfig) {
disableIf(testProcessorPhase, getNotNull(KonanConfigKeys.GENERATE_TEST_RUNNER) == TestRunnerKind.NONE)
disableUnless(buildDFGPhase, getBoolean(KonanConfigKeys.OPTIMIZATION))
disableUnless(devirtualizationPhase, getBoolean(KonanConfigKeys.OPTIMIZATION))
disableUnless(localEscapeAnalysisPhase, getBoolean(KonanConfigKeys.OPTIMIZATION))
disableUnless(escapeAnalysisPhase, getBoolean(KonanConfigKeys.OPTIMIZATION))
disableUnless(dcePhase, getBoolean(KonanConfigKeys.OPTIMIZATION))
disableUnless(ghaPhase, getBoolean(KonanConfigKeys.OPTIMIZATION))
disableUnless(verifyBitcodePhase, config.needCompilerVerification || getBoolean(KonanConfigKeys.VERIFY_BITCODE))
@@ -129,10 +129,15 @@ internal val dcePhase = makeKonanModuleOpPhase(
context, context.moduleDFG!!,
externalModulesDFG,
context.devirtualizationAnalysisResult!!,
true
// For DCE we don't wanna miss any potentially reachable function.
nonDevirtualizedCallSitesUnfoldFactor = Int.MAX_VALUE
).build()
val referencedFunctions = mutableSetOf<IrFunction>()
callGraph.rootExternalFunctions.forEach {
if (!it.isGlobalInitializer)
referencedFunctions.add(it.irFunction ?: error("No IR for: $it"))
}
for (node in callGraph.directEdges.values) {
if (!node.symbol.isGlobalInitializer)
referencedFunctions.add(node.symbol.irFunction ?: error("No IR for: ${node.symbol}"))
@@ -200,22 +205,23 @@ internal val dcePhase = makeKonanModuleOpPhase(
)
internal val escapeAnalysisPhase = makeKonanModuleOpPhase(
// Disabled by default !!!!
name = "EscapeAnalysis",
description = "Escape analysis",
prerequisite = setOf(buildDFGPhase, devirtualizationPhase),
op = { context, _ ->
context.externalModulesDFG?.let { externalModulesDFG ->
val callGraph = CallGraphBuilder(
context, context.moduleDFG!!,
externalModulesDFG,
context.devirtualizationAnalysisResult!!,
false
).build()
EscapeAnalysis.computeLifetimes(
context, context.moduleDFG!!, externalModulesDFG, callGraph, context.lifetimes
)
}
val entryPoint = context.ir.symbols.entryPoint?.owner
val externalModulesDFG = ExternalModulesDFG(emptyList(), emptyMap(), emptyMap(), emptyMap())
val callGraph = CallGraphBuilder(
context, context.moduleDFG!!,
externalModulesDFG,
context.devirtualizationAnalysisResult!!,
// Can't tolerate any non-devirtualized call site for a library.
// TODO: What about private virtual functions?
nonDevirtualizedCallSitesUnfoldFactor = if (entryPoint == null) 0 else 5
).build()
EscapeAnalysis.computeLifetimes(
context, context.moduleDFG!!, externalModulesDFG, callGraph, context.lifetimes
)
}
)
@@ -15,7 +15,6 @@ import org.jetbrains.kotlin.konan.target.CompilerOutputKind
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.backend.konan.descriptors.resolveFakeOverride
import org.jetbrains.kotlin.backend.konan.ir.*
import org.jetbrains.kotlin.descriptors.konan.CompiledKlibModuleOrigin
import org.jetbrains.kotlin.ir.expressions.IrDelegatingConstructorCall
@@ -161,6 +160,143 @@ private inline fun <R> generateFunctionBody(
*/
internal data class LocationInfoRange(var start: LocationInfo, var end: LocationInfo?)
internal interface StackLocalsManager {
fun alloc(irClass: IrClass, cleanFieldsExplicitly: Boolean): LLVMValueRef
fun allocArray(irClass: IrClass, count: LLVMValueRef): LLVMValueRef
fun clean(refsOnly: Boolean)
}
internal class StackLocalsManagerImpl(
val functionGenerationContext: FunctionGenerationContext,
val bbInitStackLocals: LLVMBasicBlockRef
) : StackLocalsManager {
private class StackLocal(val isArray: Boolean, val irClass: IrClass,
val bodyPtr: LLVMValueRef, val objHeaderPtr: LLVMValueRef)
private val stackLocals = mutableListOf<StackLocal>()
override fun alloc(irClass: IrClass, cleanFieldsExplicitly: Boolean): LLVMValueRef = with(functionGenerationContext) {
val type = context.llvmDeclarations.forClass(irClass).bodyType
val stackLocal = appendingTo(bbInitStackLocals) {
val stackSlot = LLVMBuildAlloca(builder, type, "")!!
call(context.llvm.memsetFunction,
listOf(bitcast(kInt8Ptr, stackSlot),
Int8(0).llvm,
Int32(LLVMSizeOfTypeInBits(codegen.llvmTargetData, type).toInt() / 8).llvm,
Int1(0).llvm))
val objectHeader = structGep(stackSlot, 0, "objHeader")
val typeInfo = codegen.typeInfoForAllocation(irClass)
setTypeInfoForLocalObject(objectHeader, typeInfo)
StackLocal(false, irClass, stackSlot, objectHeader)
}
stackLocals += stackLocal
if (cleanFieldsExplicitly)
clean(stackLocal, false)
stackLocal.objHeaderPtr
}
// Returns generated special type for local array.
// It's needed to prevent changing variables order on stack.
private fun localArrayType(irClass: IrClass, count: Int) = with(functionGenerationContext) {
val name = "local#${irClass.name}${count}#internal"
// Create new type or get already created.
context.declaredLocalArrays.getOrPut(name) {
val fieldTypes = listOf(kArrayHeader, LLVMArrayType(arrayToElementType[irClass.symbol]!!, count))
val classType = LLVMStructCreateNamed(LLVMGetModuleContext(context.llvmModule), name)!!
LLVMStructSetBody(classType, fieldTypes.toCValues(), fieldTypes.size, 1)
classType
}
}
private val symbols = functionGenerationContext.context.ir.symbols
// TODO: find better place?
private val arrayToElementType = mapOf(
symbols.array to functionGenerationContext.kObjHeaderPtr,
symbols.byteArray to int8Type,
symbols.charArray to int16Type,
symbols.shortArray to int16Type,
symbols.intArray to int32Type,
symbols.longArray to int64Type,
symbols.floatArray to floatType,
symbols.doubleArray to doubleType,
symbols.booleanArray to int8Type
)
override fun allocArray(irClass: IrClass, count: LLVMValueRef) = with(functionGenerationContext) {
val stackLocal = appendingTo(bbInitStackLocals) {
val constCount = extractConstUnsignedInt(count).toInt()
val arrayType = localArrayType(irClass, constCount)
val typeInfo = codegen.typeInfoValue(irClass)
val arraySlot = LLVMBuildAlloca(builder, arrayType, "")!!
// Set array size in ArrayHeader.
val arrayHeaderSlot = structGep(arraySlot, 0, "arrayHeader")
setTypeInfoForLocalObject(arrayHeaderSlot, typeInfo)
val sizeField = structGep(arrayHeaderSlot, 1, "count_")
store(count, sizeField)
call(context.llvm.memsetFunction,
listOf(bitcast(kInt8Ptr, structGep(arraySlot, 1, "arrayBody")),
Int8(0).llvm,
Int32(constCount * LLVMSizeOfTypeInBits(codegen.llvmTargetData,
arrayToElementType[irClass.symbol]).toInt() / 8).llvm,
Int1(0).llvm))
StackLocal(true, irClass, arraySlot, arrayHeaderSlot)
}
stackLocals += stackLocal
bitcast(kObjHeaderPtr, stackLocal.objHeaderPtr)
}
override fun clean(refsOnly: Boolean) = stackLocals.forEach { clean(it, refsOnly) }
private fun clean(stackLocal: StackLocal, refsOnly: Boolean) = with(functionGenerationContext) {
if (stackLocal.isArray) {
if (stackLocal.irClass.symbol == context.ir.symbols.array)
call(context.llvm.zeroArrayRefsFunction, listOf(stackLocal.objHeaderPtr))
} else {
val type = context.llvmDeclarations.forClass(stackLocal.irClass).bodyType
for (field in context.getLayoutBuilder(stackLocal.irClass).fields) {
val fieldIndex = context.llvmDeclarations.forField(field).index
val fieldType = LLVMStructGetTypeAtIndex(type, fieldIndex)!!
if (isObjectType(fieldType)) {
val fieldPtr = LLVMBuildStructGEP(builder, stackLocal.bodyPtr, fieldIndex, "")!!
if (refsOnly)
storeHeapRef(kNullObjHeaderPtr, fieldPtr)
else
call(context.llvm.zeroHeapRefFunction, listOf(fieldPtr))
}
}
if (!refsOnly) {
val bodyPtr = ptrToInt(stackLocal.bodyPtr, codegen.intPtrType)
val bodySize = LLVMSizeOfTypeInBits(codegen.llvmTargetData, type).toInt() / 8
val serviceInfoSize = runtime.pointerSize
val serviceInfoSizeLlvm = LLVMConstInt(codegen.intPtrType, serviceInfoSize.toLong(), 1)!!
val bodyWithSkippedServiceInfoPtr = intToPtr(add(bodyPtr, serviceInfoSizeLlvm), kInt8Ptr)
call(context.llvm.memsetFunction,
listOf(bodyWithSkippedServiceInfoPtr,
Int8(0).llvm,
Int32(bodySize - serviceInfoSize).llvm,
Int1(0).llvm))
}
}
}
private fun setTypeInfoForLocalObject(objectHeader: LLVMValueRef, typeInfoPointer: LLVMValueRef) = with(functionGenerationContext) {
val typeInfo = structGep(objectHeader, 0, "typeInfoOrMeta_")
// Set tag OBJECT_TAG_PERMANENT_CONTAINER | OBJECT_TAG_NONTRIVIAL_CONTAINER.
val typeInfoValue = intToPtr(or(ptrToInt(typeInfoPointer, codegen.intPtrType),
codegen.immThreeIntPtrType), kTypeInfoPtr)
store(typeInfoValue, typeInfo)
}
}
internal class FunctionGenerationContext(val function: LLVMValueRef,
val codegen: CodeGenerator,
startLocation: LocationInfo?,
@@ -192,10 +328,13 @@ internal class FunctionGenerationContext(val function: LLVMValueRef,
private val prologueBb = basicBlockInFunction("prologue", startLocation)
private val localsInitBb = basicBlockInFunction("locals_init", startLocation)
private val stackLocalsInitBb = basicBlockInFunction("stack_locals_init", startLocation)
private val entryBb = basicBlockInFunction("entry", startLocation)
private val epilogueBb = basicBlockInFunction("epilogue", endLocation)
private val cleanupLandingpad = basicBlockInFunction("cleanup_landingpad", endLocation)
val stackLocalsManager = StackLocalsManagerImpl(this, stackLocalsInitBb)
/**
* TODO: consider merging this with [ExceptionHandler].
*/
@@ -348,7 +487,7 @@ internal class FunctionGenerationContext(val function: LLVMValueRef,
// This allows appropriate rootset accounting by just looking at the stack slots,
// along with ability to allocate in appropriate arena.
val resultSlot = when (resultLifetime.slotType) {
SlotType.ARENA -> {
SlotType.STACK -> {
localAllocs++
// Case of local call. Use memory allocated on stack.
val type = LLVMGetReturnType(LLVMGetElementType(llvmFunction.type))!!
@@ -426,84 +565,29 @@ internal class FunctionGenerationContext(val function: LLVMValueRef,
fun allocInstance(typeInfo: LLVMValueRef, lifetime: Lifetime): LLVMValueRef =
call(context.llvm.allocInstanceFunction, listOf(typeInfo), lifetime)
fun allocInstance(irClass: IrClass, lifetime: Lifetime): LLVMValueRef {
val typeInfo = codegen.typeInfoForAllocation(irClass)
return if (lifetime == Lifetime.LOCAL) {
val stackSlot = alloca(context.llvmDeclarations.forClass(irClass).bodyType)
val objectHeader = structGep(stackSlot, 0, "objHeader")
setTypeInfoForLocalObject(objectHeader, typeInfo)
objectHeader
} else {
allocInstance(typeInfo, lifetime)
}
}
fun allocInstance(irClass: IrClass, lifetime: Lifetime, stackLocalsManager: StackLocalsManager) =
if (lifetime == Lifetime.STACK)
stackLocalsManager.alloc(irClass,
// In case the allocation is not from the root scope, fields must be cleaned up explicitly,
// as the object might be being reused.
cleanFieldsExplicitly = stackLocalsManager != this.stackLocalsManager)
else
allocInstance(codegen.typeInfoForAllocation(irClass), lifetime)
// TODO: find better place?
val arrayToElementType = mapOf(
"kotlin.ByteArray" to int8Type,
"kotlin.CharArray" to int16Type,
"kotlin.ShortArray" to int16Type,
"kotlin.IntArray" to int32Type,
"kotlin.LongArray" to int64Type,
"kotlin.FloatArray" to floatType,
"kotlin.DoubleArray" to doubleType,
"kotlin.BooleanArray" to int8Type
)
// Returns generated special type for local array.
// It's needed to prevent changing variables order on stack.
// TODO: add alignment calculation. Now it isn't needed, because of working only with primitive types.
private fun localArrayType(className: String, count: Int): LLVMTypeRef {
val name = "local#${className}${count}#internal"
// Create new type or get already created.
return context.declaredLocalArrays.getOrPut(name) {
val fieldTypes = listOf(kArrayHeader, LLVMArrayType(arrayToElementType[className]!!, count))
val classType = LLVMStructCreateNamed(LLVMGetModuleContext(context.llvmModule), name)!!
LLVMStructSetBody(classType, fieldTypes.toCValues(), fieldTypes.size, 1)
classType
}
}
fun allocArray(irClass: IrClass,
count: LLVMValueRef,
lifetime: Lifetime,
exceptionHandler: ExceptionHandler): LLVMValueRef {
fun allocArray(
irClass: IrClass,
count: LLVMValueRef,
lifetime: Lifetime,
exceptionHandler: ExceptionHandler
): LLVMValueRef {
val typeInfo = codegen.typeInfoValue(irClass)
return if (lifetime == Lifetime.LOCAL) {
val className = irClass.fqNameForIrSerialization.asString()
assert(className in arrayToElementType)
allocArrayOnStack(className, count, typeInfo)
return if (lifetime == Lifetime.STACK) {
stackLocalsManager.allocArray(irClass, count)
} else {
call(context.llvm.allocArrayFunction, listOf(typeInfo, count), lifetime, exceptionHandler)
}
}
fun setTypeInfoForLocalObject(objectHeader: LLVMValueRef, typeInfoPointer: LLVMValueRef) {
val typeInfo = structGep(objectHeader, 0, "typeInfoOrMeta_")
// Set tag OBJECT_TAG_PERMANENT_CONTAINER | OBJECT_TAG_NONTRIVIAL_CONTAINER.
val typeInfoValue = intToPtr(or(ptrToInt(typeInfoPointer, codegen.intPtrType),
codegen.immThreeIntPtrType), kTypeInfoPtr)
store(typeInfoValue, typeInfo)
}
fun allocArrayOnStack(arrayTypeName: String, count: LLVMValueRef, typeInfo: LLVMValueRef): LLVMValueRef {
val constCount = extractConstUnsignedInt(count).toInt()
val arrayType = localArrayType(arrayTypeName, constCount)
val arraySlot = alloca(arrayType)
// Set array size in ArrayHeader.
val arrayHeaderSlot = structGep(arraySlot, 0, "arrayHeader")
setTypeInfoForLocalObject(arrayHeaderSlot, typeInfo)
val sizeField = structGep(arrayHeaderSlot, 1, "count_")
store(count, sizeField)
call(context.llvm.memsetFunction,
listOf(bitcast(kInt8Ptr, structGep(arraySlot, 1, "arrayBody")),
Int8(0).llvm,
Int32(constCount * LLVMSizeOfTypeInBits(codegen.llvmTargetData,
arrayToElementType[arrayTypeName]).toInt() / 8).llvm,
Int1(0).llvm))
return bitcast(kObjHeaderPtr, arrayHeaderSlot)
}
fun unreachable(): LLVMValueRef? {
if (context.config.debug) {
call(context.llvm.llvmTrap, emptyList())
@@ -1112,6 +1196,10 @@ internal class FunctionGenerationContext(val function: LLVMValueRef,
}
appendingTo(localsInitBb) {
br(stackLocalsInitBb)
}
appendingTo(stackLocalsInitBb) {
br(entryBb)
}
@@ -1293,6 +1381,7 @@ internal class FunctionGenerationContext(val function: LLVMValueRef,
call(context.llvm.leaveFrameFunction,
listOf(slotsPhi!!, Int32(vars.skipSlots).llvm, Int32(slotCount).llvm))
}
stackLocalsManager.clean(refsOnly = true) // Only bother about not leaving any dangling references.
}
}
@@ -34,6 +34,9 @@ import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
internal sealed class SlotType {
// An object is statically allocated on stack.
object STACK : SlotType()
// Frame local arena slot can be used.
object ARENA : SlotType()
@@ -58,6 +61,12 @@ internal sealed class SlotType {
// Lifetimes class of reference, computed by escape analysis.
internal sealed class Lifetime(val slotType: SlotType) {
object STACK : Lifetime(SlotType.STACK) {
override fun toString(): String {
return "STACK"
}
}
// If reference is frame-local (only obtained from some call and never leaves).
object LOCAL : Lifetime(SlotType.ARENA) {
override fun toString(): String {
@@ -66,7 +75,7 @@ internal sealed class Lifetime(val slotType: SlotType) {
}
// If reference is only returned.
object RETURN_VALUE : Lifetime(SlotType.RETURN) {
object RETURN_VALUE : Lifetime(SlotType.ANONYMOUS) {
override fun toString(): String {
return "RETURN_VALUE"
}
@@ -492,8 +501,11 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) {
val initInstanceFunction = importModelSpecificRtFunction("InitInstance")
val initSharedInstanceFunction = importModelSpecificRtFunction("InitSharedInstance")
val updateHeapRefFunction = importModelSpecificRtFunction("UpdateHeapRef")
val releaseHeapRefFunction = importModelSpecificRtFunction("ReleaseHeapRef")
val updateStackRefFunction = importModelSpecificRtFunction("UpdateStackRef")
val updateReturnRefFunction = importModelSpecificRtFunction("UpdateReturnRef")
val zeroHeapRefFunction = importRtFunction("ZeroHeapRef")
val zeroArrayRefsFunction = importRtFunction("ZeroArrayRefs")
val enterFrameFunction = importModelSpecificRtFunction("EnterFrame")
val leaveFrameFunction = importModelSpecificRtFunction("LeaveFrame")
val lookupOpenMethodFunction = importRtFunction("LookupOpenMethod")
@@ -103,6 +103,8 @@ internal interface IntrinsicGeneratorEnvironment {
val exceptionHandler: ExceptionHandler
val stackLocalsManager: StackLocalsManager
fun calculateLifetime(element: IrElement): Lifetime
fun evaluateCall(function: IrFunction, args: List<LLVMValueRef>, resultLifetime: Lifetime, superClass: IrClass? = null): LLVMValueRef
@@ -303,7 +305,7 @@ internal class IntrinsicGenerator(private val environment: IntrinsicGeneratorEnv
val typeParameterT = context.ir.symbols.createUninitializedInstance.descriptor.typeParameters[0]
val enumClass = callSite.getTypeArgument(typeParameterT)!!
val enumIrClass = enumClass.getClass()!!
return allocInstance(enumIrClass, environment.calculateLifetime(callSite))
return allocInstance(enumIrClass, environment.calculateLifetime(callSite), environment.stackLocalsManager)
}
private fun FunctionGenerationContext.emitGetPointerSize(): LLVMValueRef =
@@ -134,6 +134,8 @@ internal interface CodeContext {
fun genThrow(exception: LLVMValueRef)
val stackLocalsManager: StackLocalsManager
/**
* Declares the variable.
* @return index of declared variable.
@@ -218,6 +220,9 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
override val exceptionHandler: ExceptionHandler
get() = currentCodeContext.exceptionHandler
override val stackLocalsManager: StackLocalsManager
get() = currentCodeContext.stackLocalsManager
override fun evaluateCall(function: IrFunction, args: List<LLVMValueRef>, resultLifetime: Lifetime, superClass: IrClass?) =
evaluateSimpleFunctionCall(function, args, resultLifetime, superClass)
@@ -248,6 +253,8 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
override fun genThrow(exception: LLVMValueRef) = unsupported()
override val stackLocalsManager: StackLocalsManager get() = unsupported()
override fun genDeclareVariable(variable: IrVariable, value: LLVMValueRef?, variableLocation: VariableDebugLocation?) = unsupported(variable)
override fun getDeclaredVariable(variable: IrVariable) = -1
@@ -529,6 +536,9 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
else
super.genContinue(destination)
}
override val stackLocalsManager: StackLocalsManager =
object : StackLocalsManager by functionGenerationContext.stackLocalsManager { }
}
//-------------------------------------------------------------------------//
@@ -668,6 +678,8 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
functionGenerationContext.unreachable()
}
override val stackLocalsManager get() = functionGenerationContext.stackLocalsManager
override fun functionScope(): CodeContext = this
@@ -2178,7 +2190,8 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
constructedClass.isObjCClass() -> error("Call should've been lowered: ${callee.dump()}")
else -> functionGenerationContext.allocInstance(constructedClass, resultLifetime(callee))
else -> functionGenerationContext.allocInstance(constructedClass, resultLifetime(callee),
currentCodeContext.stackLocalsManager)
}
evaluateSimpleFunctionCall(callee.symbol.owner,
listOf(thisValue) + args, Lifetime.IRRELEVANT /* constructor doesn't return anything */)
@@ -857,7 +857,7 @@ private fun ObjCExportCodeGenerator.generateObjCImp(
val coroutineSuspended = callFromBridge(
codegen.llvmFunction(context.ir.symbols.objCExportGetCoroutineSuspended.owner),
emptyList(),
Lifetime.LOCAL
Lifetime.STACK
)
ifThen(icmpNe(targetResult!!, coroutineSuspended)) {
// Callee haven't suspended, so it isn't going to call the completion. Call it here:
@@ -11,18 +11,20 @@ import org.jetbrains.kotlin.backend.konan.DirectedGraph
import org.jetbrains.kotlin.backend.konan.DirectedGraphNode
import org.jetbrains.kotlin.backend.konan.Context
internal class CallGraphNode(val graph: CallGraph, val symbol: DataFlowIR.FunctionSymbol)
: DirectedGraphNode<DataFlowIR.FunctionSymbol> {
internal class CallGraphNode(val graph: CallGraph, val symbol: DataFlowIR.FunctionSymbol.Declared)
: DirectedGraphNode<DataFlowIR.FunctionSymbol.Declared> {
override val key get() = symbol
override val directEdges: List<DataFlowIR.FunctionSymbol> by lazy {
override val directEdges: List<DataFlowIR.FunctionSymbol.Declared> by lazy {
graph.directEdges[symbol]!!.callSites
.filter { !it.isVirtual }
.map { it.actualCallee }
.filter { graph.reversedEdges.containsKey(it) }
.filterIsInstance<DataFlowIR.FunctionSymbol.Declared>()
.filter { graph.directEdges.containsKey(it) }
}
override val reversedEdges: List<DataFlowIR.FunctionSymbol> by lazy {
override val reversedEdges: List<DataFlowIR.FunctionSymbol.Declared> by lazy {
graph.reversedEdges[symbol]!!
}
@@ -31,26 +33,31 @@ internal class CallGraphNode(val graph: CallGraph, val symbol: DataFlowIR.Functi
val callSites = mutableListOf<CallSite>()
}
internal class CallGraph(val directEdges: Map<DataFlowIR.FunctionSymbol, CallGraphNode>,
val reversedEdges: Map<DataFlowIR.FunctionSymbol, MutableList<DataFlowIR.FunctionSymbol>>)
: DirectedGraph<DataFlowIR.FunctionSymbol, CallGraphNode> {
internal class CallGraph(val directEdges: Map<DataFlowIR.FunctionSymbol.Declared, CallGraphNode>,
val reversedEdges: Map<DataFlowIR.FunctionSymbol.Declared, MutableList<DataFlowIR.FunctionSymbol.Declared>>,
val rootExternalFunctions: List<DataFlowIR.FunctionSymbol>)
: DirectedGraph<DataFlowIR.FunctionSymbol.Declared, CallGraphNode> {
override val nodes get() = directEdges.values
override fun get(key: DataFlowIR.FunctionSymbol) = directEdges[key]!!
override fun get(key: DataFlowIR.FunctionSymbol.Declared) = directEdges[key]!!
fun addEdge(caller: DataFlowIR.FunctionSymbol, callSite: CallGraphNode.CallSite) {
fun addEdge(caller: DataFlowIR.FunctionSymbol.Declared, callSite: CallGraphNode.CallSite) {
directEdges[caller]!!.callSites += callSite
reversedEdges[callSite.actualCallee]?.add(caller)
}
fun addReversedEdge(caller: DataFlowIR.FunctionSymbol.Declared, callee: DataFlowIR.FunctionSymbol.Declared) {
reversedEdges[callee]!!.add(caller)
}
}
internal class CallGraphBuilder(val context: Context,
val moduleDFG: ModuleDFG,
val externalModulesDFG: ExternalModulesDFG,
val devirtualizationAnalysisResult: Devirtualization.AnalysisResult,
val gotoExternal: Boolean) {
internal class CallGraphBuilder(
val context: Context,
val moduleDFG: ModuleDFG,
val externalModulesDFG: ExternalModulesDFG,
val devirtualizationAnalysisResult: Devirtualization.AnalysisResult,
val nonDevirtualizedCallSitesUnfoldFactor: Int
) {
private val DEBUG = 0
@@ -66,47 +73,45 @@ internal class CallGraphBuilder(val context: Context,
return this
}
private val visitedFunctions = mutableSetOf<DataFlowIR.FunctionSymbol>()
private val directEdges = mutableMapOf<DataFlowIR.FunctionSymbol, CallGraphNode>()
private val reversedEdges = mutableMapOf<DataFlowIR.FunctionSymbol, MutableList<DataFlowIR.FunctionSymbol>>()
private val callGraph = CallGraph(directEdges, reversedEdges)
private val functionStack = mutableListOf<DataFlowIR.FunctionSymbol>()
private val directEdges = mutableMapOf<DataFlowIR.FunctionSymbol.Declared, CallGraphNode>()
private val reversedEdges = mutableMapOf<DataFlowIR.FunctionSymbol.Declared, MutableList<DataFlowIR.FunctionSymbol.Declared>>()
private val externalRootFunctions = mutableListOf<DataFlowIR.FunctionSymbol>()
private val callGraph = CallGraph(directEdges, reversedEdges, externalRootFunctions)
private data class HandleFunctionParams(val caller: DataFlowIR.FunctionSymbol.Declared?,
val calleeFunction: DataFlowIR.Function)
private val functionStack = mutableListOf<HandleFunctionParams>()
fun build(): CallGraph {
val rootSet = Devirtualization.computeRootSet(context, moduleDFG, externalModulesDFG)
@Suppress("LoopToCallChain")
for (symbol in rootSet)
functionStack.push(symbol)
for (symbol in rootSet) {
val function = moduleDFG.functions[symbol]
if (function == null)
externalRootFunctions.add(symbol)
else
functionStack.push(HandleFunctionParams(null, function))
}
while (functionStack.isNotEmpty()) {
val symbol = functionStack.pop()
if (symbol !in visitedFunctions)
handleFunction(symbol)
val (caller, calleeFunction) = functionStack.pop()
val callee = calleeFunction.symbol as DataFlowIR.FunctionSymbol.Declared
val gotoCallee = !directEdges.containsKey(callee)
if (gotoCallee)
addNode(callee)
if (caller != null)
callGraph.addReversedEdge(caller, callee)
if (gotoCallee)
handleFunction(callee, calleeFunction)
}
DEBUG_OUTPUT(0) {
println("DirectEdges: ${directEdges.size}")
println("ReversedEdges: ${reversedEdges.size}")
println("Sum: ${directEdges.values.sumBy { it.callSites.size }}")
}
return callGraph
}
private fun addNode(symbol: DataFlowIR.FunctionSymbol) {
if (directEdges.containsKey(symbol))
return
val node = CallGraphNode(callGraph, symbol)
directEdges.put(symbol, node)
val list = mutableListOf<DataFlowIR.FunctionSymbol>()
reversedEdges.put(symbol, list)
private fun addNode(symbol: DataFlowIR.FunctionSymbol.Declared) {
directEdges[symbol] = CallGraphNode(callGraph, symbol)
reversedEdges[symbol] = mutableListOf()
}
private val symbols = context.ir.symbols
private val arrayGet = symbols.arrayGet[symbols.array]!!.owner
private val arraySet = symbols.arraySet[symbols.array]!!.owner
private inline fun DataFlowIR.FunctionBody.forEachCallSite(block: (DataFlowIR.Node.Call) -> Unit) =
private inline fun DataFlowIR.FunctionBody.forEachCallSite(block: (DataFlowIR.Node.Call) -> Unit): Unit =
forEachNonScopeNode { node ->
when (node) {
is DataFlowIR.Node.Call -> block(node)
@@ -137,92 +142,63 @@ internal class CallGraphBuilder(val context: Context,
returnType = node.symbol.returnParameter.type,
irCallSite = null
))
else -> { }
}
}
private fun staticCall(caller: DataFlowIR.FunctionSymbol, call: DataFlowIR.Node.Call, callee: DataFlowIR.FunctionSymbol) {
callGraph.addEdge(caller, CallGraphNode.CallSite(call, false, callee))
if (callee is DataFlowIR.FunctionSymbol.Declared
&& !directEdges.containsKey(callee))
functionStack.push(callee)
private fun staticCall(caller: DataFlowIR.FunctionSymbol.Declared, call: DataFlowIR.Node.Call, callee: DataFlowIR.FunctionSymbol) {
val resolvedCallee = callee.resolved()
val callSite = CallGraphNode.CallSite(call, false, resolvedCallee)
val function = moduleDFG.functions[resolvedCallee]
callGraph.addEdge(caller, callSite)
if (function != null)
functionStack.push(HandleFunctionParams(caller, function))
}
private fun handleFunction(symbol: DataFlowIR.FunctionSymbol) {
visitedFunctions += symbol
if (gotoExternal) {
addNode(symbol)
val function = moduleDFG.functions[symbol] ?: externalModulesDFG.functionDFGs[symbol] ?: return
val body = function.body
body.forEachCallSite { call ->
val devirtualizedCallSite = (call as? DataFlowIR.Node.VirtualCall)?.let { devirtualizedCallSites[it] }
when {
call !is DataFlowIR.Node.VirtualCall -> staticCall(symbol, call, call.callee.resolved())
private fun handleFunction(symbol: DataFlowIR.FunctionSymbol.Declared, function: DataFlowIR.Function) {
val body = function.body
body.forEachCallSite { call ->
val devirtualizedCallSite = (call as? DataFlowIR.Node.VirtualCall)?.let { devirtualizedCallSites[it] }
when {
call !is DataFlowIR.Node.VirtualCall -> staticCall(symbol, call, call.callee)
devirtualizedCallSite != null -> {
devirtualizedCallSite.possibleCallees.forEach {
staticCall(symbol, call, it.callee.resolved())
}
}
call.receiverType == DataFlowIR.Type.Virtual -> {
// Skip callsite. This can only be for invocations Any's methods on instances of ObjC classes.
}
else -> {
// Callsite has not been devirtualized - conservatively assume the worst:
// any inheritor of the receiver type is possible here.
val typeHierarchy = devirtualizationAnalysisResult.typeHierarchy
typeHierarchy.inheritorsOf(call.receiverType as DataFlowIR.Type.Declared).forEachBit {
val receiverType = typeHierarchy.allTypes[it]
if (receiverType.isAbstract) return@forEachBit
// TODO: Unconservative way - when we can use it?
//.filter { devirtualizationAnalysisResult.instantiatingClasses.contains(it) }
val actualCallee = when (call) {
is DataFlowIR.Node.VtableCall ->
receiverType.vtable[call.calleeVtableIndex]
is DataFlowIR.Node.ItableCall ->
receiverType.itable[call.calleeHash]!!
else -> error("Unreachable")
}
staticCall(symbol, call, actualCallee.resolved())
}
devirtualizedCallSite != null -> {
devirtualizedCallSite.possibleCallees.forEach {
staticCall(symbol, call, it.callee)
}
}
}
} else {
var function = moduleDFG.functions[symbol]
var local = true
if (function != null)
addNode(symbol)
else {
function = externalModulesDFG.functionDFGs[symbol]!!
local = false
}
val body = function.body
body.forEachCallSite { call ->
val devirtualizedCallSite = (call as? DataFlowIR.Node.VirtualCall)?.let { devirtualizedCallSites.get(it) }
if (devirtualizedCallSite == null) {
val callee = call.callee.resolved()
if (moduleDFG.functions.containsKey(callee))
addNode(callee)
if (local)
callGraph.addEdge(symbol, CallGraphNode.CallSite(call, call is DataFlowIR.Node.VirtualCall, callee))
if (callee is DataFlowIR.FunctionSymbol.Declared
&& call !is DataFlowIR.Node.VirtualCall
&& !visitedFunctions.contains(callee))
functionStack.push(callee)
} else {
devirtualizedCallSite.possibleCallees.forEach {
val callee = it.callee.resolved()
if (moduleDFG.functions.containsKey(callee))
addNode(callee)
if (local)
callGraph.addEdge(symbol, CallGraphNode.CallSite(call, false, callee))
if (callee is DataFlowIR.FunctionSymbol.Declared
&& !visitedFunctions.contains(callee))
functionStack.push(callee)
call.receiverType == DataFlowIR.Type.Virtual -> {
// Skip callsite. This can only be for invocations Any's methods on instances of ObjC classes.
}
else -> {
// Callsite has not been devirtualized - conservatively assume the worst:
// any inheritor of the receiver type is possible here.
val typeHierarchy = devirtualizationAnalysisResult.typeHierarchy
val allPossibleCallees = mutableListOf<DataFlowIR.FunctionSymbol>()
typeHierarchy.inheritorsOf(call.receiverType as DataFlowIR.Type.Declared).forEachBit {
val receiverType = typeHierarchy.allTypes[it]
if (receiverType.isAbstract) return@forEachBit
// TODO: Unconservative way - when we can use it?
//.filter { devirtualizationAnalysisResult.instantiatingClasses.contains(it) }
val actualCallee = when (call) {
is DataFlowIR.Node.VtableCall ->
receiverType.vtable[call.calleeVtableIndex]
is DataFlowIR.Node.ItableCall ->
receiverType.itable[call.calleeHash]!!
else -> error("Unreachable")
}
allPossibleCallees.add(actualCallee)
}
if (allPossibleCallees.size <= nonDevirtualizedCallSitesUnfoldFactor)
allPossibleCallees.forEach { staticCall(symbol, call, it) }
else {
val callSite = CallGraphNode.CallSite(call, true, call.callee)
callGraph.addEdge(symbol, callSite)
}
}
}
@@ -63,7 +63,7 @@ private class VariableValues {
}
fun add(variable: IrVariable, element: IrExpression) =
elementData[variable]!!.values.add(element)
elementData[variable]?.values?.add(element)
private fun add(variable: IrVariable, elements: Set<IrExpression>) =
elementData[variable]?.values?.addAll(elements)
@@ -736,7 +736,7 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag
createUninitializedInstanceSymbol ->
DataFlowIR.Node.AllocInstance(symbolTable.mapClassReferenceType(
value.getTypeArgument(0)!!.getClass()!!
))
), value)
reinterpret -> getNode(value.extensionReceiver!!).value
@@ -251,7 +251,7 @@ internal object DataFlowIR {
class Singleton(val type: Type, val constructor: FunctionSymbol?) : Node()
class AllocInstance(val type: Type) : Node()
class AllocInstance(val type: Type, val irCallSite: IrCall?) : Node()
class FunctionReference(val symbol: FunctionSymbol, val type: Type, val returnType: Type) : Node()
@@ -647,10 +647,15 @@ internal object DataFlowIR {
&& !irClass.isNonGeneratedAnnotation()
&& (it.isOverridableOrOverrides || bridgeTarget != null || function.isSpecial || !irClass.isFinal())
val symbolTableIndex = if (placeToFunctionsTable) module.numberOfFunctions++ else -1
if (it.isExported())
val frozen = it is IrConstructor && irClass!!.annotations.findAnnotation(KonanFqNames.frozen) != null
val functionSymbol = if (it.isExported())
FunctionSymbol.Public(name.localHash.value, module, symbolTableIndex, attributes, it, bridgeTargetSymbol, takeName { name })
else
FunctionSymbol.Private(privateFunIndex++, module, symbolTableIndex, attributes, it, bridgeTargetSymbol, takeName { name })
if (frozen) {
functionSymbol.escapes = 0b1 // Assume instances of frozen classes escape.
}
functionSymbol
}
}
functionMap[it] = symbol
@@ -9,7 +9,6 @@ import org.jetbrains.kotlin.backend.konan.llvm.Lifetime
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.backend.konan.Context
import org.jetbrains.kotlin.backend.konan.descriptors.isArrayWithFixedSizeItems
import org.jetbrains.kotlin.backend.konan.descriptors.isBuiltInOperator
internal object LocalEscapeAnalysis {
private val DEBUG = 0
@@ -187,7 +186,7 @@ internal object LocalEscapeAnalysis {
else -> null
}
ir?.let {
lifetimes.put(it, Lifetime.LOCAL)
lifetimes.put(it, Lifetime.STACK)
DEBUG_OUTPUT(3) { println("${ir} does not escape") }
}
}
+8
View File
@@ -3252,6 +3252,13 @@ void UpdateReturnRefRelaxed(ObjHeader** returnSlot, const ObjHeader* value) {
updateReturnRef<false>(returnSlot, value);
}
void ZeroArrayRefs(ArrayHeader* array) {
for (uint32_t index = 0; index < array->count_; ++index) {
ObjHeader** location = ArrayAddressOfElementAt(array, index);
zeroHeapRef(location);
}
}
void UpdateHeapRefIfNull(ObjHeader** location, const ObjHeader* object) {
updateHeapRefIfNull(location, object);
}
@@ -3426,6 +3433,7 @@ void FreezeSubgraph(ObjHeader* root) {
// This function is called from field mutators to check if object's header is frozen.
// If object is frozen or permanent, an exception is thrown.
void MutationCheck(ObjHeader* obj) {
if (obj->local()) return;
auto* container = obj->container();
if (container == nullptr || container->frozen())
ThrowInvalidMutabilityException(obj);
+3 -1
View File
@@ -501,7 +501,9 @@ MODEL_VARIANTS(void, SetStackRef, ObjHeader** location, const ObjHeader* object)
// Sets heap location.
MODEL_VARIANTS(void, SetHeapRef, ObjHeader** location, const ObjHeader* object);
// Zeroes heap location.
void ZeroHeapRef(ObjHeader** location);
void ZeroHeapRef(ObjHeader** location) RUNTIME_NOTHROW;
// Zeroes an array.
void ZeroArrayRefs(ArrayHeader* array) RUNTIME_NOTHROW;
// Zeroes stack location.
MODEL_VARIANTS(void, ZeroStackRef, ObjHeader** location);
// Updates stack location.
+2 -2
View File
@@ -54,7 +54,7 @@ public final class Array<T> {
* If the [index] is out of bounds of this array, throws an [IndexOutOfBoundsException].
*/
@SymbolName("Kotlin_Array_get")
@PointsTo(0b0100, 0, 0b0001) // <this> points to <return>, <return> points to <this>.
@PointsTo(0x000, 0x000, 0x002) // ret -> this.intestines
external public operator fun get(index: Int): T
/**
@@ -67,7 +67,7 @@ public final class Array<T> {
* If the [index] is out of bounds of this array, throws an [IndexOutOfBoundsException].
*/
@SymbolName("Kotlin_Array_set")
@PointsTo(0b0100, 0, 0b0001) // <this> points to <value>, <value> points to <this>.
@PointsTo(0x300, 0x000, 0x000) // this.intestines -> value
external public operator fun set(index: Int, value: T): Unit
/**
+1 -1
View File
@@ -528,7 +528,7 @@ public inline fun <reified @PureReifiable T> arrayOfNulls(size: Int): Array<T?>
* Returns an array containing the specified elements.
*/
@TypedIntrinsic(IntrinsicType.IDENTITY)
@PointsTo(0, 1) // <return> points to <array> argument.
@PointsTo(0x00, 0x01) // ret -> elements
public external inline fun <reified @PureReifiable T> arrayOf(vararg elements: T): Array<T>
@SymbolName("Kotlin_emptyArray")
@@ -14,7 +14,7 @@ import kotlin.native.internal.ExportForCppRuntime
* either throwing exception or returning some kind of implementation-specific default value.
*/
@PublishedApi
internal fun <E> arrayOfUninitializedElements(size: Int): Array<E> {
internal inline fun <E> arrayOfUninitializedElements(size: Int): Array<E> {
// TODO: special case for size == 0?
require(size >= 0) { "capacity must be non-negative." }
@Suppress("TYPE_PARAMETER_AS_REIFIED")
@@ -76,7 +76,7 @@ internal fun <E> Array<E>.resetAt(index: Int) {
}
@SymbolName("Kotlin_Array_fillImpl")
@PointsTo(0b01000, 0, 0, 0b00001) // <array> points to <value>, <value> points to <array>.
@PointsTo(0x3000, 0x0000, 0x0000, 0x0000) // array.intestines -> value
internal external fun <T> arrayFill(array: Array<T>, fromIndex: Int, toIndex: Int, value: T)
@SymbolName("Kotlin_ByteArray_fillImpl")
@@ -125,7 +125,7 @@ internal fun <E> Array<E>.resetRange(fromIndex: Int, toIndex: Int) {
}
@SymbolName("Kotlin_Array_copyImpl")
@PointsTo(0b000100, 0, 0b000001) // <array> points to <destination>, <destination> points to <array>.
@PointsTo(0x00000, 0x00000, 0x00004, 0x00000, 0x00000) // destination.intestines -> array.intestines
internal external fun arrayCopy(array: Array<Any?>, fromIndex: Int, destination: Array<Any?>, toIndex: Int, count: Int)
@SymbolName("Kotlin_ByteArray_copyImpl")
@@ -6,6 +6,7 @@ package kotlin.native
import kotlin.native.concurrent.isFrozen
import kotlin.native.concurrent.InvalidMutabilityException
import kotlin.native.internal.Escapes
/**
* Initializes Kotlin runtime for the current thread, if not inited already.
@@ -51,6 +52,7 @@ public fun setUnhandledExceptionHook(hook: ReportUnhandledExceptionHook): Report
}
@SymbolName("Kotlin_setUnhandledExceptionHook")
@Escapes(0b01) // <hook> escapes
external private fun setUnhandledExceptionHook0(hook: ReportUnhandledExceptionHook): ReportUnhandledExceptionHook?
/**
@@ -90,6 +90,7 @@ internal annotation class FixmeConcurrency
@Retention(AnnotationRetention.BINARY)
internal annotation class Escapes(val who: Int)
// Decyphering of binary values can be found in EscapeAnalysis.kt
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.BINARY)
internal annotation class PointsTo(vararg val onWhom: Int)
@@ -9,6 +9,7 @@ import kotlinx.cinterop.COpaquePointer
import kotlin.native.internal.ExportForCppRuntime
import kotlin.native.internal.Frozen
import kotlin.native.internal.NoReorderFields
import kotlin.native.internal.Escapes
/**
* Theory of operations:
@@ -53,6 +54,7 @@ internal abstract class WeakReferenceImpl {
// Get a counter from non-null object.
@SymbolName("Konan_getWeakReferenceImpl")
@Escapes(0b01) // referent escapes.
external internal fun getWeakReferenceImpl(referent: Any): WeakReferenceImpl
// Create a counter object.