[IR] Rewrote debug output using Context.log
This commit is contained in:
+9
-3
@@ -43,9 +43,6 @@ import org.jetbrains.kotlin.backend.konan.llvm.coverage.CoverageManager
|
||||
import org.jetbrains.kotlin.ir.descriptors.WrappedSimpleFunctionDescriptor
|
||||
import org.jetbrains.kotlin.ir.symbols.IrSymbol
|
||||
import org.jetbrains.kotlin.ir.symbols.impl.IrFieldSymbolImpl
|
||||
import org.jetbrains.kotlin.ir.types.IrType
|
||||
import org.jetbrains.kotlin.ir.types.defaultType
|
||||
import org.jetbrains.kotlin.ir.types.makeNullable
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.konan.library.KonanLibraryLayout
|
||||
import org.jetbrains.kotlin.library.SerializedIrModule
|
||||
@@ -429,3 +426,12 @@ private fun MemberScope.getContributedClassifier(name: String) =
|
||||
|
||||
private fun MemberScope.getContributedFunctions(name: String) =
|
||||
this.getContributedFunctions(Name.identifier(name), NoLookupLocation.FROM_BUILTINS)
|
||||
|
||||
internal class ContextLogger(val context: Context) {
|
||||
operator fun String.unaryPlus() = context.log { this }
|
||||
}
|
||||
|
||||
internal fun Context.logMultiple(messageBuilder: ContextLogger.() -> Unit) {
|
||||
if (!inVerbosePhase) return
|
||||
with(ContextLogger(this)) { messageBuilder() }
|
||||
}
|
||||
+22
-33
@@ -262,19 +262,12 @@ internal class GlobalHierarchyAnalysis(val context: Context, val irModule: IrMod
|
||||
}
|
||||
|
||||
internal class ClassLayoutBuilder(val irClass: IrClass, val context: Context, val isLowered: Boolean) {
|
||||
private val DEBUG = 0
|
||||
|
||||
private inline fun DEBUG_OUTPUT(severity: Int, block: () -> Unit) {
|
||||
if (DEBUG > severity) block()
|
||||
}
|
||||
|
||||
val vtableEntries: List<OverriddenFunctionInfo> by lazy {
|
||||
|
||||
assert(!irClass.isInterface)
|
||||
|
||||
DEBUG_OUTPUT(0) {
|
||||
println()
|
||||
println("BUILDING vTable for ${irClass.render()}")
|
||||
context.logMultiple {
|
||||
+""
|
||||
+"BUILDING vTable for ${irClass.render()}"
|
||||
}
|
||||
|
||||
val superVtableEntries = if (irClass.isSpecialClassWithNoSupertypes()) {
|
||||
@@ -288,17 +281,17 @@ internal class ClassLayoutBuilder(val irClass: IrClass, val context: Context, va
|
||||
val newVtableSlots = mutableListOf<OverriddenFunctionInfo>()
|
||||
val overridenVtableSlots = mutableMapOf<IrSimpleFunction, OverriddenFunctionInfo>()
|
||||
|
||||
DEBUG_OUTPUT(0) {
|
||||
println()
|
||||
println("SUPER vTable:")
|
||||
superVtableEntries.forEach { println(" ${it.overriddenFunction.render()} -> ${it.function.render()}") }
|
||||
context.logMultiple {
|
||||
+""
|
||||
+"SUPER vTable:"
|
||||
superVtableEntries.forEach { +" ${it.overriddenFunction.render()} -> ${it.function.render()}" }
|
||||
|
||||
println()
|
||||
println("METHODS:")
|
||||
methods.forEach { println(" ${it.render()}") }
|
||||
+""
|
||||
+"METHODS:"
|
||||
methods.forEach { +" ${it.render()}" }
|
||||
|
||||
println()
|
||||
println("BUILDING INHERITED vTable")
|
||||
+""
|
||||
+"BUILDING INHERITED vTable"
|
||||
}
|
||||
|
||||
val superVtableMap = superVtableEntries.groupBy { it.function }
|
||||
@@ -316,13 +309,9 @@ internal class ClassLayoutBuilder(val irClass: IrClass, val context: Context, va
|
||||
}
|
||||
val inheritedVtableSlots = superVtableEntries.map { superMethod ->
|
||||
overridenVtableSlots[superMethod.overriddenFunction]?.also {
|
||||
DEBUG_OUTPUT(0) {
|
||||
println("Taking overridden ${superMethod.overriddenFunction.render()} -> ${it.function.render()}")
|
||||
}
|
||||
context.log { "Taking overridden ${superMethod.overriddenFunction.render()} -> ${it.function.render()}" }
|
||||
} ?: superMethod.also {
|
||||
DEBUG_OUTPUT(0) {
|
||||
println("Taking super ${superMethod.overriddenFunction.render()} -> ${superMethod.function.render()}")
|
||||
}
|
||||
context.log { "Taking super ${superMethod.overriddenFunction.render()} -> ${superMethod.function.render()}" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,15 +325,15 @@ internal class ClassLayoutBuilder(val irClass: IrClass, val context: Context, va
|
||||
.distinctBy { it.function to it.bridgeDirections }
|
||||
.filter { it.function.isOverridable }
|
||||
|
||||
DEBUG_OUTPUT(0) {
|
||||
println()
|
||||
println("INHERITED vTable slots:")
|
||||
inheritedVtableSlots.forEach { println(" ${it.overriddenFunction.render()} -> ${it.function.render()}") }
|
||||
context.logMultiple {
|
||||
+""
|
||||
+"INHERITED vTable slots:"
|
||||
inheritedVtableSlots.forEach { +" ${it.overriddenFunction.render()} -> ${it.function.render()}" }
|
||||
|
||||
println()
|
||||
println("MY OWN vTable slots:")
|
||||
filteredNewVtableSlots.forEach { println(" ${it.overriddenFunction.render()} -> ${it.function.render()} ${it.function}") }
|
||||
println("DONE vTable for ${irClass.render()}")
|
||||
+""
|
||||
+"MY OWN vTable slots:"
|
||||
filteredNewVtableSlots.forEach { +" ${it.overriddenFunction.render()} -> ${it.function.render()} ${it.function}" }
|
||||
+"DONE vTable for ${irClass.render()}"
|
||||
}
|
||||
|
||||
inheritedVtableSlots + filteredNewVtableSlots.sortedBy { it.overriddenFunction.uniqueId }
|
||||
|
||||
-6
@@ -59,12 +59,6 @@ internal class CallGraphBuilder(
|
||||
val nonDevirtualizedCallSitesUnfoldFactor: Int
|
||||
) {
|
||||
|
||||
private val DEBUG = 0
|
||||
|
||||
private inline fun DEBUG_OUTPUT(severity: Int, block: () -> Unit) {
|
||||
if (DEBUG > severity) block()
|
||||
}
|
||||
|
||||
private val devirtualizedCallSites = devirtualizationAnalysisResult.devirtualizedCallSites
|
||||
|
||||
private fun DataFlowIR.FunctionSymbol.resolved(): DataFlowIR.FunctionSymbol {
|
||||
|
||||
+37
-47
@@ -13,7 +13,6 @@ import org.jetbrains.kotlin.backend.common.pop
|
||||
import org.jetbrains.kotlin.backend.common.push
|
||||
import org.jetbrains.kotlin.backend.konan.*
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.allOverriddenFunctions
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.target
|
||||
import org.jetbrains.kotlin.backend.konan.ir.*
|
||||
import org.jetbrains.kotlin.backend.konan.llvm.functionName
|
||||
import org.jetbrains.kotlin.backend.konan.llvm.localHash
|
||||
@@ -177,12 +176,6 @@ internal class ModuleDFG(val functions: Map<DataFlowIR.FunctionSymbol, DataFlowI
|
||||
|
||||
internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFragment) {
|
||||
|
||||
private val DEBUG = 0
|
||||
|
||||
private inline fun DEBUG_OUTPUT(severity: Int, block: () -> Unit) {
|
||||
if (DEBUG > severity) block()
|
||||
}
|
||||
|
||||
private val TAKE_NAMES = true // Take fqNames for all functions and types (for debug purposes).
|
||||
|
||||
private inline fun takeName(block: () -> String) = if (TAKE_NAMES) block() else null
|
||||
@@ -211,9 +204,9 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag
|
||||
assert (body != null || declaration.constructedClass.isNonGeneratedAnnotation()) {
|
||||
"Non-annotation class constructor has empty body"
|
||||
}
|
||||
DEBUG_OUTPUT(0) {
|
||||
println("Analysing function ${declaration.descriptor}")
|
||||
println("IR: ${ir2stringWhole(declaration)}")
|
||||
context.logMultiple {
|
||||
+"Analysing function ${declaration.descriptor}"
|
||||
+"IR: ${ir2stringWhole(declaration)}"
|
||||
}
|
||||
analyze(declaration, body)
|
||||
}
|
||||
@@ -224,9 +217,9 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag
|
||||
// External function or intrinsic.
|
||||
symbolTable.mapFunction(declaration)
|
||||
} else {
|
||||
DEBUG_OUTPUT(0) {
|
||||
println("Analysing function ${declaration.descriptor}")
|
||||
println("IR: ${ir2stringWhole(declaration)}")
|
||||
context.logMultiple {
|
||||
+"Analysing function ${declaration.descriptor}"
|
||||
+"IR: ${ir2stringWhole(declaration)}"
|
||||
}
|
||||
analyze(declaration, body)
|
||||
}
|
||||
@@ -235,9 +228,9 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag
|
||||
override fun visitField(declaration: IrField) {
|
||||
if (declaration.parent is IrFile)
|
||||
declaration.initializer?.let {
|
||||
DEBUG_OUTPUT(0) {
|
||||
println("Analysing global field ${declaration.descriptor}")
|
||||
println("IR: ${ir2stringWhole(declaration)}")
|
||||
context.logMultiple {
|
||||
+"Analysing global field ${declaration.descriptor}"
|
||||
+"IR: ${ir2stringWhole(declaration)}"
|
||||
}
|
||||
analyze(declaration, IrSetFieldImpl(it.startOffset, it.endOffset, declaration.symbol, null,
|
||||
it.expression, context.irBuiltIns.unitType))
|
||||
@@ -249,29 +242,25 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag
|
||||
val visitor = ElementFinderVisitor()
|
||||
body?.acceptVoid(visitor)
|
||||
|
||||
DEBUG_OUTPUT(0) {
|
||||
println("FIRST PHASE")
|
||||
visitor.variableValues.elementData.forEach { t, u ->
|
||||
println("VAR $t [LOOP ${u.loop}]:")
|
||||
u.values.forEach {
|
||||
println(" ${ir2stringWhole(it)}")
|
||||
}
|
||||
context.logMultiple {
|
||||
+"FIRST PHASE"
|
||||
visitor.variableValues.elementData.forEach { (t, u) ->
|
||||
+"VAR $t [LOOP ${u.loop}]:"
|
||||
u.values.forEach { +" ${ir2stringWhole(it)}" }
|
||||
}
|
||||
visitor.expressions.forEach { t ->
|
||||
println("EXP [LOOP ${t.value}] ${ir2stringWhole(t.key)}")
|
||||
+"EXP [LOOP ${t.value}] ${ir2stringWhole(t.key)}"
|
||||
}
|
||||
}
|
||||
|
||||
// Compute transitive closure of possible values for variables.
|
||||
visitor.variableValues.computeClosure()
|
||||
|
||||
DEBUG_OUTPUT(0) {
|
||||
println("SECOND PHASE")
|
||||
visitor.variableValues.elementData.forEach { t, u ->
|
||||
println("VAR $t [LOOP ${u.loop}]:")
|
||||
u.values.forEach {
|
||||
println(" ${ir2stringWhole(it)}")
|
||||
}
|
||||
context.logMultiple {
|
||||
+"SECOND PHASE"
|
||||
visitor.variableValues.elementData.forEach { (t, u) ->
|
||||
+"VAR $t [LOOP ${u.loop}]:"
|
||||
u.values.forEach { +" ${ir2stringWhole(it)}" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,27 +268,28 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag
|
||||
declaration, visitor.expressions, visitor.parentLoops, visitor.returnValues,
|
||||
visitor.thrownValues, visitor.catchParameters).build()
|
||||
|
||||
DEBUG_OUTPUT(0) {
|
||||
function.debugOutput()
|
||||
context.logMultiple {
|
||||
+function.debugString()
|
||||
+""
|
||||
}
|
||||
|
||||
functions.put(function.symbol, function)
|
||||
functions[function.symbol] = function
|
||||
}
|
||||
}, data = null)
|
||||
|
||||
DEBUG_OUTPUT(1) {
|
||||
println("SYMBOL TABLE:")
|
||||
context.logMultiple {
|
||||
+"SYMBOL TABLE:"
|
||||
symbolTable.classMap.forEach { irClass, type ->
|
||||
println(" DESCRIPTOR: ${irClass.descriptor}")
|
||||
println(" TYPE: $type")
|
||||
+" DESCRIPTOR: ${irClass.descriptor}"
|
||||
+" TYPE: $type"
|
||||
if (type !is DataFlowIR.Type.Declared)
|
||||
return@forEach
|
||||
println(" SUPER TYPES:")
|
||||
type.superTypes.forEach { println(" $it") }
|
||||
println(" VTABLE:")
|
||||
type.vtable.forEach { println(" $it") }
|
||||
println(" ITABLE:")
|
||||
type.itable.forEach { println(" ${it.key} -> ${it.value}") }
|
||||
+" SUPER TYPES:"
|
||||
type.superTypes.forEach { +" $it" }
|
||||
+" VTABLE:"
|
||||
type.vtable.forEach { +" $it" }
|
||||
+" ITABLE:"
|
||||
type.itable.forEach { +" ${it.key} -> ${it.value}" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -618,9 +608,9 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag
|
||||
return variables[valueDeclaration]!!
|
||||
}
|
||||
return nodes.getOrPut(expression) {
|
||||
DEBUG_OUTPUT(0) {
|
||||
println("Converting expression")
|
||||
println(ir2stringWhole(expression))
|
||||
context.logMultiple {
|
||||
+"Converting expression"
|
||||
+ir2stringWhole(expression)
|
||||
}
|
||||
val values = mutableListOf<IrExpression>()
|
||||
val edges = mutableListOf<DataFlowIR.Edge>()
|
||||
|
||||
+97
-139
@@ -8,7 +8,6 @@ package org.jetbrains.kotlin.backend.konan.optimizations
|
||||
import org.jetbrains.kotlin.backend.konan.*
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.isAbstract
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.isBuiltInOperator
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.target
|
||||
import org.jetbrains.kotlin.backend.konan.ir.allParameters
|
||||
import org.jetbrains.kotlin.backend.konan.ir.isOverridableOrOverrides
|
||||
import org.jetbrains.kotlin.backend.konan.llvm.functionName
|
||||
@@ -17,6 +16,7 @@ import org.jetbrains.kotlin.backend.konan.llvm.localHash
|
||||
import org.jetbrains.kotlin.backend.konan.llvm.symbolName
|
||||
import org.jetbrains.kotlin.backend.konan.lower.DECLARATION_ORIGIN_BRIDGE_METHOD
|
||||
import org.jetbrains.kotlin.backend.konan.lower.bridgeTarget
|
||||
import org.jetbrains.kotlin.backend.konan.optimizations.DataFlowIR.Function.Companion.appendCastTo
|
||||
import org.jetbrains.kotlin.descriptors.Modality
|
||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
@@ -285,17 +285,19 @@ internal object DataFlowIR {
|
||||
|
||||
class Function(val symbol: FunctionSymbol, val body: FunctionBody) {
|
||||
|
||||
fun debugOutput() {
|
||||
println("FUNCTION $symbol")
|
||||
println("Params: ${symbol.parameters.contentToString()}")
|
||||
fun debugOutput() = println(debugString())
|
||||
|
||||
fun debugString() = buildString {
|
||||
appendLine("FUNCTION $symbol")
|
||||
appendLine("Params: ${symbol.parameters.contentToString()}")
|
||||
val nodes = listOf(body.rootScope) + body.allScopes.flatMap { it.nodes }
|
||||
val ids = nodes.withIndex().associateBy({ it.value }, { it.index })
|
||||
nodes.forEach {
|
||||
println(" NODE #${ids[it]!!}")
|
||||
printNode(it, ids)
|
||||
appendLine(" NODE #${ids[it]!!}")
|
||||
appendLine(nodeToString(it, ids))
|
||||
}
|
||||
println(" RETURNS")
|
||||
printNode(body.returns, ids)
|
||||
appendLine(" RETURNS")
|
||||
append(nodeToString(body.returns, ids))
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -303,169 +305,125 @@ internal object DataFlowIR {
|
||||
|
||||
fun nodeToString(node: Node, ids: Map<Node, Int>) = when (node) {
|
||||
is Node.Const ->
|
||||
" CONST ${node.type}\n"
|
||||
" CONST ${node.type}"
|
||||
|
||||
Node.Null ->
|
||||
" NULL\n"
|
||||
" NULL"
|
||||
|
||||
is Node.Parameter ->
|
||||
" PARAM ${node.index}\n"
|
||||
" PARAM ${node.index}"
|
||||
|
||||
is Node.Singleton ->
|
||||
" SINGLETON ${node.type}\n"
|
||||
" SINGLETON ${node.type}"
|
||||
|
||||
is Node.AllocInstance ->
|
||||
" ALLOC INSTANCE ${node.type}\n"
|
||||
" ALLOC INSTANCE ${node.type}"
|
||||
|
||||
is Node.FunctionReference ->
|
||||
" FUNCTION REFERENCE ${node.symbol}\n"
|
||||
" FUNCTION REFERENCE ${node.symbol}"
|
||||
|
||||
is Node.StaticCall -> {
|
||||
buildString {
|
||||
appendLine(" STATIC CALL ${node.callee}. Return type = ${node.returnType}")
|
||||
node.arguments.forEach {
|
||||
append(" ARG #${ids[it.node]!!}")
|
||||
if (it.castToType == null)
|
||||
appendLine()
|
||||
else
|
||||
appendLine(" CASTED TO ${it.castToType}")
|
||||
}
|
||||
is Node.StaticCall -> buildString {
|
||||
append(" STATIC CALL ${node.callee}. Return type = ${node.returnType}")
|
||||
appendList(node.arguments) {
|
||||
append(" ARG #${ids[it.node]!!}")
|
||||
appendCastTo(it.castToType)
|
||||
}
|
||||
}
|
||||
|
||||
is Node.VtableCall -> {
|
||||
buildString {
|
||||
appendLine(" VIRTUAL CALL ${node.callee}. Return type = ${node.returnType}")
|
||||
appendLine(" RECEIVER: ${node.receiverType}")
|
||||
appendLine(" VTABLE INDEX: ${node.calleeVtableIndex}")
|
||||
node.arguments.forEach {
|
||||
append(" ARG #${ids[it.node]!!}")
|
||||
if (it.castToType == null)
|
||||
appendLine()
|
||||
else
|
||||
appendLine(" CASTED TO ${it.castToType}")
|
||||
}
|
||||
is Node.VtableCall -> buildString {
|
||||
appendLine(" VIRTUAL CALL ${node.callee}. Return type = ${node.returnType}")
|
||||
appendLine(" RECEIVER: ${node.receiverType}")
|
||||
append(" VTABLE INDEX: ${node.calleeVtableIndex}")
|
||||
appendList(node.arguments) {
|
||||
append(" ARG #${ids[it.node]!!}")
|
||||
appendCastTo(it.castToType)
|
||||
}
|
||||
}
|
||||
|
||||
is Node.ItableCall -> {
|
||||
buildString {
|
||||
appendLine(" INTERFACE CALL ${node.callee}. Return type = ${node.returnType}")
|
||||
appendLine(" RECEIVER: ${node.receiverType}")
|
||||
appendLine(" METHOD HASH: ${node.calleeHash}")
|
||||
node.arguments.forEach {
|
||||
append(" ARG #${ids[it.node]!!}")
|
||||
if (it.castToType == null)
|
||||
appendLine()
|
||||
else
|
||||
appendLine(" CASTED TO ${it.castToType}")
|
||||
}
|
||||
is Node.ItableCall -> buildString {
|
||||
appendLine(" INTERFACE CALL ${node.callee}. Return type = ${node.returnType}")
|
||||
appendLine(" RECEIVER: ${node.receiverType}")
|
||||
append(" METHOD HASH: ${node.calleeHash}")
|
||||
appendList(node.arguments) {
|
||||
append(" ARG #${ids[it.node]!!}")
|
||||
appendCastTo(it.castToType)
|
||||
}
|
||||
}
|
||||
|
||||
is Node.NewObject -> {
|
||||
buildString {
|
||||
appendLine(" NEW OBJECT ${node.callee}")
|
||||
appendLine(" CONSTRUCTED TYPE ${node.constructedType}")
|
||||
node.arguments.forEach {
|
||||
append(" ARG #${ids[it.node]!!}")
|
||||
if (it.castToType == null)
|
||||
appendLine()
|
||||
else
|
||||
appendLine(" CASTED TO ${it.castToType}")
|
||||
}
|
||||
is Node.NewObject -> buildString {
|
||||
appendLine(" NEW OBJECT ${node.callee}")
|
||||
append(" CONSTRUCTED TYPE ${node.constructedType}")
|
||||
appendList(node.arguments) {
|
||||
append(" ARG #${ids[it.node]!!}")
|
||||
appendCastTo(it.castToType)
|
||||
}
|
||||
}
|
||||
|
||||
is Node.FieldRead -> {
|
||||
buildString {
|
||||
appendLine(" FIELD READ ${node.field}")
|
||||
append(" RECEIVER #${node.receiver?.node?.let { ids[it]!! } ?: "null"}")
|
||||
if (node.receiver?.castToType == null)
|
||||
appendLine()
|
||||
else
|
||||
appendLine(" CASTED TO ${node.receiver.castToType}")
|
||||
is Node.FieldRead -> buildString {
|
||||
appendLine(" FIELD READ ${node.field}")
|
||||
append(" RECEIVER #${node.receiver?.node?.let { ids[it]!! } ?: "null"}")
|
||||
appendCastTo(node.receiver?.castToType)
|
||||
}
|
||||
|
||||
is Node.FieldWrite -> buildString {
|
||||
appendLine(" FIELD WRITE ${node.field}")
|
||||
append(" RECEIVER #${node.receiver?.node?.let { ids[it]!! } ?: "null"}")
|
||||
appendCastTo(node.receiver?.castToType)
|
||||
appendLine()
|
||||
append(" VALUE #${ids[node.value.node]!!}")
|
||||
appendCastTo(node.value.castToType)
|
||||
}
|
||||
|
||||
is Node.ArrayRead -> buildString {
|
||||
appendLine(" ARRAY READ")
|
||||
append(" ARRAY #${ids[node.array.node]}")
|
||||
appendCastTo(node.array.castToType)
|
||||
appendLine()
|
||||
append(" INDEX #${ids[node.index.node]!!}")
|
||||
appendCastTo(node.index.castToType)
|
||||
}
|
||||
|
||||
is Node.ArrayWrite -> buildString {
|
||||
appendLine(" ARRAY WRITE")
|
||||
append(" ARRAY #${ids[node.array.node]}")
|
||||
appendCastTo(node.array.castToType)
|
||||
appendLine()
|
||||
append(" INDEX #${ids[node.index.node]!!}")
|
||||
appendCastTo(node.index.castToType)
|
||||
appendLine()
|
||||
append(" VALUE #${ids[node.value.node]!!}")
|
||||
appendCastTo(node.value.castToType)
|
||||
}
|
||||
|
||||
is Node.Variable -> buildString {
|
||||
append(" ${node.kind}")
|
||||
appendList(node.values) {
|
||||
append(" VAL #${ids[it.node]!!}")
|
||||
appendCastTo(it.castToType)
|
||||
}
|
||||
}
|
||||
|
||||
is Node.FieldWrite -> {
|
||||
buildString {
|
||||
appendLine(" FIELD WRITE ${node.field}")
|
||||
append(" RECEIVER #${node.receiver?.node?.let { ids[it]!! } ?: "null"}")
|
||||
if (node.receiver?.castToType == null)
|
||||
appendLine()
|
||||
else
|
||||
appendLine(" CASTED TO ${node.receiver.castToType}")
|
||||
print(" VALUE #${ids[node.value.node]!!}")
|
||||
if (node.value.castToType == null)
|
||||
appendLine()
|
||||
else
|
||||
appendLine(" CASTED TO ${node.value.castToType}")
|
||||
is Node.Scope -> buildString {
|
||||
append(" SCOPE ${node.depth}")
|
||||
appendList(node.nodes.toList()) {
|
||||
append(" SUBNODE #${ids[it]!!}")
|
||||
}
|
||||
}
|
||||
|
||||
is Node.ArrayRead -> {
|
||||
buildString {
|
||||
appendLine(" ARRAY READ")
|
||||
append(" ARRAY #${ids[node.array.node]}")
|
||||
if (node.array.castToType == null)
|
||||
appendLine()
|
||||
else
|
||||
appendLine(" CASTED TO ${node.array.castToType}")
|
||||
append(" INDEX #${ids[node.index.node]!!}")
|
||||
if (node.index.castToType == null)
|
||||
appendLine()
|
||||
else
|
||||
appendLine(" CASTED TO ${node.index.castToType}")
|
||||
}
|
||||
}
|
||||
else -> " UNKNOWN: ${node::class.java}"
|
||||
}
|
||||
|
||||
is Node.ArrayWrite -> {
|
||||
buildString {
|
||||
appendLine(" ARRAY WRITE")
|
||||
append(" ARRAY #${ids[node.array.node]}")
|
||||
if (node.array.castToType == null)
|
||||
appendLine()
|
||||
else
|
||||
appendLine(" CASTED TO ${node.array.castToType}")
|
||||
append(" INDEX #${ids[node.index.node]!!}")
|
||||
if (node.index.castToType == null)
|
||||
appendLine()
|
||||
else
|
||||
appendLine(" CASTED TO ${node.index.castToType}")
|
||||
print(" VALUE #${ids[node.value.node]!!}")
|
||||
if (node.value.castToType == null)
|
||||
appendLine()
|
||||
else
|
||||
appendLine(" CASTED TO ${node.value.castToType}")
|
||||
}
|
||||
private fun <T> StringBuilder.appendList(list: List<T>, itemPrinter: StringBuilder.(T) -> Unit) {
|
||||
if (list.isEmpty()) return
|
||||
for (i in list.indices) {
|
||||
appendLine()
|
||||
itemPrinter(list[i])
|
||||
}
|
||||
}
|
||||
|
||||
is Node.Variable -> {
|
||||
buildString {
|
||||
appendLine(" ${node.kind}")
|
||||
node.values.forEach {
|
||||
append(" VAL #${ids[it.node]!!}")
|
||||
if (it.castToType == null)
|
||||
appendLine()
|
||||
else
|
||||
appendLine(" CASTED TO ${it.castToType}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is Node.Scope -> {
|
||||
val result = StringBuilder()
|
||||
result.appendLine(" SCOPE ${node.depth}")
|
||||
node.nodes.forEach {
|
||||
result.appendLine(" SUBNODE #${ids[it]!!}")
|
||||
}
|
||||
result.toString()
|
||||
}
|
||||
|
||||
else -> {
|
||||
" UNKNOWN: ${node::class.java}\n"
|
||||
}
|
||||
private fun StringBuilder.appendCastTo(type: Type?) {
|
||||
if (type != null)
|
||||
append(" CASTED TO ${type}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+82
-101
@@ -54,12 +54,6 @@ inline fun BitSet.forEachBit(block: (Int) -> Unit) {
|
||||
// See http://web.cs.ucla.edu/~palsberg/tba/papers/sundaresan-et-al-oopsla00.pdf for details.
|
||||
internal object Devirtualization {
|
||||
|
||||
private val DEBUG = 0
|
||||
|
||||
private inline fun DEBUG_OUTPUT(severity: Int, block: () -> Unit) {
|
||||
if (DEBUG > severity) block()
|
||||
}
|
||||
|
||||
private val TAKE_NAMES = false // Take fqNames for all functions and types (for debug purposes).
|
||||
|
||||
private inline fun takeName(block: () -> String) = if (TAKE_NAMES) block() else null
|
||||
@@ -276,7 +270,7 @@ internal object Devirtualization {
|
||||
|
||||
fun BitSet.copy() = BitSet(this.size()).apply { this.or(this@copy) }
|
||||
|
||||
fun printPathToType(reversedEdges: IntArray, node: Node, type: Int) {
|
||||
fun logPathToType(reversedEdges: IntArray, node: Node, type: Int) {
|
||||
val nodes = constraintGraph.nodes
|
||||
val visited = BitSet()
|
||||
val prev = mutableMapOf<Node, Node>()
|
||||
@@ -322,11 +316,11 @@ internal object Devirtualization {
|
||||
try {
|
||||
var cur: Node = source
|
||||
do {
|
||||
println(" #${cur.id}")
|
||||
context.log { " #${cur.id}" }
|
||||
cur = prev[cur]!!
|
||||
} while (cur != node)
|
||||
} catch (t: Throwable) {
|
||||
println("Unable to print path")
|
||||
context.log { "Unable to print path" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -416,6 +410,9 @@ internal object Devirtualization {
|
||||
}
|
||||
}
|
||||
|
||||
private fun DataFlowIR.Node.VirtualCall.debugString() =
|
||||
irCallSite?.let { ir2stringWhole(it).trimEnd() } ?: this.toString()
|
||||
|
||||
fun analyze(): AnalysisResult {
|
||||
val functions = moduleDFG.functions + externalModulesDFG.functionDFGs
|
||||
assert(DataFlowIR.Type.Virtual !in symbolTable.classMap.values) {
|
||||
@@ -435,21 +432,20 @@ internal object Devirtualization {
|
||||
|
||||
val (instantiatingClasses, directEdges, reversedEdges) = buildConstraintGraph(nodesMap, functions, typeHierarchy, rootSet)
|
||||
|
||||
DEBUG_OUTPUT(0) {
|
||||
println("FULL CONSTRAINT GRAPH")
|
||||
context.logMultiple {
|
||||
+"FULL CONSTRAINT GRAPH"
|
||||
constraintGraph.nodes.forEach {
|
||||
println(" NODE #${it.id}")
|
||||
directEdges.forEachEdge(it.id) {
|
||||
println(" EDGE: #${it}z")
|
||||
}
|
||||
+" NODE #${it.id}"
|
||||
directEdges.forEachEdge(it.id) { +" EDGE: #${it}z" }
|
||||
it.directCastEdges?.forEach {
|
||||
println(" CAST EDGE: #${it.node.id}z casted to ${it.suitableTypes.format(allTypes)}")
|
||||
+" CAST EDGE: #${it.node.id}z casted to ${it.suitableTypes.format(allTypes)}"
|
||||
}
|
||||
allTypes.forEachIndexed { index, type ->
|
||||
if (it.types[index])
|
||||
println(" TYPE: $type")
|
||||
+" TYPE: $type"
|
||||
}
|
||||
}
|
||||
+""
|
||||
}
|
||||
|
||||
constraintGraph.nodes.forEach {
|
||||
@@ -459,22 +455,24 @@ internal object Devirtualization {
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG_OUTPUT(0) {
|
||||
println("CONSTRAINT GRAPH: ${constraintGraph.nodes.size} nodes, " +
|
||||
"${constraintGraph.nodes.sumBy { (directEdges[it.id + 1] - directEdges[it.id]) + (it.directCastEdges?.size ?: 0) } } edges")
|
||||
context.logMultiple {
|
||||
val edgesCount = constraintGraph.nodes.sumBy {
|
||||
(directEdges[it.id + 1] - directEdges[it.id]) + (it.directCastEdges?.size ?: 0)
|
||||
}
|
||||
+"CONSTRAINT GRAPH: ${constraintGraph.nodes.size} nodes, $edgesCount edges"
|
||||
+""
|
||||
}
|
||||
|
||||
val condensation = CondensationBuilder(directEdges, reversedEdges).build()
|
||||
val topologicalOrder = condensation.topologicalOrder.map { constraintGraph.nodes[it] }
|
||||
|
||||
DEBUG_OUTPUT(0) {
|
||||
println("CONDENSATION")
|
||||
context.logMultiple {
|
||||
+"CONDENSATION"
|
||||
topologicalOrder.forEachIndexed { index, multiNode ->
|
||||
println(" MULTI-NODE #$index")
|
||||
condensation.forEachNode(multiNode) {
|
||||
println(" #${it.id}: ${it.toString(allTypes)}")
|
||||
}
|
||||
+" MULTI-NODE #$index"
|
||||
condensation.forEachNode(multiNode) { +" #${it.id}: ${it.toString(allTypes)}" }
|
||||
}
|
||||
+""
|
||||
}
|
||||
|
||||
topologicalOrder.forEachIndexed { index, multiNode ->
|
||||
@@ -587,17 +585,18 @@ internal object Devirtualization {
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG_OUTPUT(0) {
|
||||
context.logMultiple {
|
||||
topologicalOrder.forEachIndexed { index, multiNode ->
|
||||
println("Types of multi-node #$index")
|
||||
+"Types of multi-node #$index"
|
||||
condensation.forEachNode(multiNode) { node ->
|
||||
println(" Node #${node.id}")
|
||||
+" Node #${node.id}"
|
||||
allTypes.asSequence()
|
||||
.withIndex()
|
||||
.filter { node.types[it.index] }.toList()
|
||||
.forEach { println(" ${it.value}") }
|
||||
.forEach { +" ${it.value}" }
|
||||
}
|
||||
}
|
||||
+""
|
||||
}
|
||||
|
||||
val result = mutableMapOf<DataFlowIR.Node.VirtualCall, Pair<DevirtualizedCallSite, DataFlowIR.FunctionSymbol>>()
|
||||
@@ -610,76 +609,66 @@ internal object Devirtualization {
|
||||
val receiverNode = constraintGraph.virtualCallSiteReceivers[virtualCall]
|
||||
?: error("virtualCallSiteReceivers were not built for virtual call $virtualCall")
|
||||
if (receiverNode.types[VIRTUAL_TYPE_ID]) {
|
||||
|
||||
DEBUG_OUTPUT(0) {
|
||||
println("Unable to devirtualize callsite " +
|
||||
(virtualCall.irCallSite?.let { ir2stringWhole(it) }
|
||||
?: virtualCall.callee.toString()))
|
||||
println(" receiver is Virtual")
|
||||
printPathToType(reversedEdges, receiverNode, VIRTUAL_TYPE_ID)
|
||||
context.logMultiple {
|
||||
+"Unable to devirtualize callsite ${virtualCall.debugString()}"
|
||||
+" receiver is Virtual"
|
||||
logPathToType(reversedEdges, receiverNode, VIRTUAL_TYPE_ID)
|
||||
+""
|
||||
}
|
||||
|
||||
return@forEachNonScopeNode
|
||||
}
|
||||
|
||||
DEBUG_OUTPUT(0) {
|
||||
println("Devirtualized callsite " +
|
||||
(virtualCall.irCallSite?.let { ir2stringWhole(it) } ?: virtualCall.callee.toString()))
|
||||
}
|
||||
|
||||
context.log { "Devirtualized callsite ${virtualCall.debugString()}" }
|
||||
val receiverType = virtualCall.receiverType.resolved()
|
||||
val possibleReceivers = mutableListOf<DataFlowIR.Type.Declared>()
|
||||
forEachBitInBoth(receiverNode.types, typeHierarchy.inheritorsOf(receiverType)) {
|
||||
val type = allTypes[it]
|
||||
assert(instantiatingClasses[it]) { "Non-instantiating class $type" }
|
||||
if (type != nothing) {
|
||||
|
||||
DEBUG_OUTPUT(0) {
|
||||
println("Path to type $type")
|
||||
printPathToType(reversedEdges, receiverNode, it)
|
||||
context.logMultiple {
|
||||
+"Path to type $type"
|
||||
logPathToType(reversedEdges, receiverNode, it)
|
||||
}
|
||||
|
||||
possibleReceivers.add(type)
|
||||
}
|
||||
}
|
||||
context.log { "" }
|
||||
|
||||
result[virtualCall] = DevirtualizedCallSite(virtualCall.callee.resolved(),
|
||||
possibleReceivers.map { possibleReceiverType ->
|
||||
val callee = possibleReceiverType.calleeAt(virtualCall)
|
||||
if (callee is DataFlowIR.FunctionSymbol.Declared && callee.symbolTableIndex < 0)
|
||||
error("Function ${possibleReceiverType}.$callee cannot be called virtually," +
|
||||
" but actually is at call site: " +
|
||||
(virtualCall.irCallSite?.let { ir2stringWhole(it) }
|
||||
?: virtualCall.toString()))
|
||||
" but actually is at call site: ${virtualCall.debugString()}")
|
||||
DevirtualizedCallee(possibleReceiverType, callee)
|
||||
}) to function.symbol
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG_OUTPUT(0) {
|
||||
println("Devirtualized from current module:")
|
||||
context.logMultiple {
|
||||
+"Devirtualized from current module:"
|
||||
result.forEach { virtualCall, devirtualizedCallSite ->
|
||||
if (virtualCall.irCallSite != null) {
|
||||
println("DEVIRTUALIZED")
|
||||
println("FUNCTION: ${devirtualizedCallSite.second}")
|
||||
println("CALL SITE: ${virtualCall.irCallSite?.let { ir2stringWhole(it) } ?: virtualCall.toString()}")
|
||||
println("POSSIBLE RECEIVERS:")
|
||||
devirtualizedCallSite.first.possibleCallees.forEach { println(" TYPE: ${it.receiverType}") }
|
||||
devirtualizedCallSite.first.possibleCallees.forEach { println(" FUN: ${it.callee}") }
|
||||
println()
|
||||
+"DEVIRTUALIZED"
|
||||
+"FUNCTION: ${devirtualizedCallSite.second}"
|
||||
+"CALL SITE: ${virtualCall.debugString()}"
|
||||
+"POSSIBLE RECEIVERS:"
|
||||
devirtualizedCallSite.first.possibleCallees.forEach { +" TYPE: ${it.receiverType}" }
|
||||
devirtualizedCallSite.first.possibleCallees.forEach { +" FUN: ${it.callee}" }
|
||||
+""
|
||||
}
|
||||
}
|
||||
println("Devirtualized from external modules:")
|
||||
+"Devirtualized from external modules:"
|
||||
result.forEach { virtualCall, devirtualizedCallSite ->
|
||||
if (virtualCall.irCallSite == null) {
|
||||
println("DEVIRTUALIZED")
|
||||
println("FUNCTION: ${devirtualizedCallSite.second}")
|
||||
println("CALL SITE: ${virtualCall.irCallSite?.let { ir2stringWhole(it) } ?: virtualCall.toString()}")
|
||||
println("POSSIBLE RECEIVERS:")
|
||||
devirtualizedCallSite.first.possibleCallees.forEach { println(" TYPE: ${it.receiverType}") }
|
||||
devirtualizedCallSite.first.possibleCallees.forEach { println(" FUN: ${it.callee}") }
|
||||
println()
|
||||
+"DEVIRTUALIZED"
|
||||
+"FUNCTION: ${devirtualizedCallSite.second}"
|
||||
+"CALL SITE: ${virtualCall.debugString()}"
|
||||
+"POSSIBLE RECEIVERS:"
|
||||
devirtualizedCallSite.first.possibleCallees.forEach { +" TYPE: ${it.receiverType}" }
|
||||
devirtualizedCallSite.first.possibleCallees.forEach { +" FUN: ${it.callee}" }
|
||||
+""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -915,18 +904,17 @@ internal object Devirtualization {
|
||||
addEdge(functionNodesMap[body.returns]!!, functionConstraintGraph.returns)
|
||||
addEdge(functionNodesMap[body.throws]!!, functionConstraintGraph.throws)
|
||||
|
||||
DEBUG_OUTPUT(0) {
|
||||
println("CONSTRAINT GRAPH FOR $symbol")
|
||||
context.logMultiple {
|
||||
+"CONSTRAINT GRAPH FOR $symbol"
|
||||
val ids = function.body.allScopes.flatMap { it.nodes }.withIndex().associateBy({ it.value }, { it.index })
|
||||
function.body.forEachNonScopeNode { node ->
|
||||
println("FT NODE #${ids[node]}")
|
||||
DataFlowIR.Function.printNode(node, ids)
|
||||
+"FT NODE #${ids[node]}"
|
||||
+DataFlowIR.Function.nodeToString(node, ids)
|
||||
val constraintNode = functionNodesMap[node] ?: variables[node] ?: return@forEachNonScopeNode
|
||||
println(" CG NODE #${constraintNode.id}: ${constraintNode.toString(allTypes)}")
|
||||
println()
|
||||
+" CG NODE #${constraintNode.id}: ${constraintNode.toString(allTypes)}"
|
||||
}
|
||||
println("Returns: #${ids[function.body.returns]}")
|
||||
println()
|
||||
+"Returns: #${ids[function.body.returns]}"
|
||||
+""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -982,21 +970,17 @@ internal object Devirtualization {
|
||||
private fun addInstantiatingClass(type: DataFlowIR.Type.Declared) {
|
||||
if (instantiatingClasses[type.index]) return
|
||||
instantiatingClasses.set(type.index)
|
||||
|
||||
DEBUG_OUTPUT(1) { println("Adding instantiating class: $type") }
|
||||
|
||||
context.log { "Adding instantiating class: $type" }
|
||||
checkSupertypes(type, type, BitSet())
|
||||
}
|
||||
|
||||
private fun processVirtualCall(virtualCall: ConstraintGraphVirtualCall,
|
||||
receiverType: DataFlowIR.Type.Declared) {
|
||||
DEBUG_OUTPUT(1) {
|
||||
println("Processing virtual call: ${virtualCall.virtualCall.callee}")
|
||||
println("Receiver type: $receiverType")
|
||||
context.logMultiple {
|
||||
+"Processing virtual call: ${virtualCall.virtualCall.callee}"
|
||||
+"Receiver type: $receiverType"
|
||||
}
|
||||
|
||||
val callee = receiverType.calleeAt(virtualCall.virtualCall)
|
||||
|
||||
addEdge(doCall(virtualCall.caller, callee, virtualCall.arguments,
|
||||
callee.returnParameter.type.resolved()), virtualCall.returnsNode)
|
||||
}
|
||||
@@ -1006,18 +990,17 @@ internal object Devirtualization {
|
||||
seenTypes: BitSet) {
|
||||
seenTypes.set(type.index)
|
||||
|
||||
DEBUG_OUTPUT(1) {
|
||||
println("Checking supertype $type of $inheritor")
|
||||
context.logMultiple {
|
||||
+"Checking supertype $type of $inheritor"
|
||||
typesVirtualCallSites[type.index].let {
|
||||
if (it.isEmpty())
|
||||
println("None virtual call sites encountered yet")
|
||||
+"None virtual call sites encountered yet"
|
||||
else {
|
||||
println("Virtual call sites:")
|
||||
it.forEach {
|
||||
println(" ${it.virtualCall.callee}")
|
||||
}
|
||||
+"Virtual call sites:"
|
||||
it.forEach { +" ${it.virtualCall.callee}" }
|
||||
}
|
||||
}
|
||||
+""
|
||||
}
|
||||
|
||||
typesVirtualCallSites[type.index].let { virtualCallSites ->
|
||||
@@ -1175,19 +1158,17 @@ internal object Devirtualization {
|
||||
val callee = node.callee
|
||||
val receiverType = node.receiverType.resolved()
|
||||
|
||||
DEBUG_OUTPUT(0) {
|
||||
println("Virtual call")
|
||||
println("Caller: ${function.symbol}")
|
||||
println("Callee: $callee")
|
||||
println("Receiver type: $receiverType")
|
||||
}
|
||||
context.logMultiple {
|
||||
+"Virtual call"
|
||||
+"Caller: ${function.symbol}"
|
||||
+"Callee: $callee"
|
||||
+"Receiver type: $receiverType"
|
||||
|
||||
DEBUG_OUTPUT(0) {
|
||||
println("Possible callees:")
|
||||
+"Possible callees:"
|
||||
forEachBitInBoth(typeHierarchy.inheritorsOf(receiverType), instantiatingClasses) {
|
||||
println(allTypes[it].calleeAt(node))
|
||||
+allTypes[it].calleeAt(node).toString()
|
||||
}
|
||||
println()
|
||||
+""
|
||||
}
|
||||
|
||||
val returnType = node.returnType.resolved()
|
||||
|
||||
+103
-165
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.backend.konan.DirectedGraphMultiNode
|
||||
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.logMultiple
|
||||
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||
import kotlin.math.min
|
||||
|
||||
@@ -104,12 +105,6 @@ internal object EscapeAnalysis {
|
||||
* seeing all edges directed.
|
||||
*/
|
||||
|
||||
private val DEBUG = 0
|
||||
|
||||
private inline fun DEBUG_OUTPUT(severity: Int, block: () -> Unit) {
|
||||
if (DEBUG > severity) block()
|
||||
}
|
||||
|
||||
// A special marker field for external types implemented in the runtime (mainly, arrays).
|
||||
// The types being passed to the constructor are not used in the analysis - just put there anything.
|
||||
private val intestinesField = DataFlowIR.Field(null, DataFlowIR.Type.Virtual, 1L, "inte\$tines")
|
||||
@@ -317,16 +312,14 @@ internal object EscapeAnalysis {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun toString() = debugOutput(null)
|
||||
override fun toString() = debugString(null)
|
||||
|
||||
fun debugOutput(root: String?): String {
|
||||
val result = StringBuilder()
|
||||
result.append(root ?: kind.toString())
|
||||
fun debugString(root: String?) = buildString {
|
||||
append(root ?: kind.toString())
|
||||
path.forEach {
|
||||
result.append('.')
|
||||
result.append(it.name ?: "<no_name@${it.hash}>")
|
||||
append('.')
|
||||
append(it.name ?: "<no_name@${it.hash}>")
|
||||
}
|
||||
return result.toString()
|
||||
}
|
||||
|
||||
fun goto(field: DataFlowIR.Field?) = when (field) {
|
||||
@@ -447,7 +440,7 @@ internal object EscapeAnalysis {
|
||||
}
|
||||
|
||||
private class InterproceduralAnalysis(
|
||||
context: Context,
|
||||
val context: Context,
|
||||
val callGraph: CallGraph,
|
||||
val intraproceduralAnalysisResults: Map<DataFlowIR.FunctionSymbol, FunctionAnalysisResult>,
|
||||
val externalModulesDFG: ExternalModulesDFG,
|
||||
@@ -466,48 +459,44 @@ internal object EscapeAnalysis {
|
||||
val escapeAnalysisResults = mutableMapOf<DataFlowIR.FunctionSymbol.Declared, FunctionEscapeAnalysisResult>()
|
||||
|
||||
fun analyze() {
|
||||
|
||||
DEBUG_OUTPUT(0) {
|
||||
println("CALL GRAPH")
|
||||
callGraph.directEdges.forEach { t, u ->
|
||||
println(" FUN $t")
|
||||
context.logMultiple {
|
||||
+"CALL GRAPH"
|
||||
callGraph.directEdges.forEach { (t, u) ->
|
||||
+" FUN $t"
|
||||
u.callSites.forEach {
|
||||
val label = when {
|
||||
it.isVirtual -> "VIRTUAL"
|
||||
callGraph.directEdges.containsKey(it.actualCallee) -> "LOCAL"
|
||||
else -> "EXTERNAL"
|
||||
}
|
||||
println(" CALLS $label ${it.actualCallee}")
|
||||
}
|
||||
callGraph.reversedEdges[t]!!.forEach {
|
||||
println(" CALLED BY $it")
|
||||
+" CALLS $label ${it.actualCallee}"
|
||||
}
|
||||
callGraph.reversedEdges[t]!!.forEach { +" CALLED BY $it" }
|
||||
}
|
||||
+""
|
||||
}
|
||||
|
||||
val condensation = DirectedGraphCondensationBuilder(callGraph).build()
|
||||
|
||||
DEBUG_OUTPUT(0) {
|
||||
println("CONDENSATION")
|
||||
context.logMultiple {
|
||||
+"CONDENSATION"
|
||||
condensation.topologicalOrder.forEach { multiNode ->
|
||||
println(" MULTI-NODE")
|
||||
multiNode.nodes.forEach {
|
||||
println(" $it")
|
||||
}
|
||||
+" MULTI-NODE"
|
||||
multiNode.nodes.forEach { +" $it" }
|
||||
}
|
||||
println("CONDENSATION(DETAILED)")
|
||||
+""
|
||||
+"CONDENSATION(DETAILED)"
|
||||
condensation.topologicalOrder.forEach { multiNode ->
|
||||
println(" MULTI-NODE")
|
||||
+" MULTI-NODE"
|
||||
multiNode.nodes.forEach {
|
||||
println(" $it")
|
||||
+" $it"
|
||||
callGraph.directEdges[it]!!.callSites
|
||||
.filter { callGraph.directEdges.containsKey(it.actualCallee) }
|
||||
.forEach { println(" CALLS ${it.actualCallee}") }
|
||||
callGraph.reversedEdges[it]!!.forEach {
|
||||
println(" CALLED BY $it")
|
||||
}
|
||||
.forEach { +" CALLS ${it.actualCallee}" }
|
||||
callGraph.reversedEdges[it]!!.forEach { +" CALLED BY $it" }
|
||||
}
|
||||
}
|
||||
+""
|
||||
}
|
||||
|
||||
for (functionSymbol in callGraph.directEdges.keys) {
|
||||
@@ -519,9 +508,9 @@ internal object EscapeAnalysis {
|
||||
for (multiNode in condensation.topologicalOrder.reversed())
|
||||
analyze(callGraph, multiNode)
|
||||
|
||||
DEBUG_OUTPUT(1) {
|
||||
println("Managed to alloc on stack: ${stackAllocsCount * 100.0 / (globalAllocsCount + stackAllocsCount)}%")
|
||||
println("Total graph size: $totalGraphSize")
|
||||
context.logMultiple {
|
||||
+"Managed to alloc on stack: ${stackAllocsCount * 100.0 / (globalAllocsCount + stackAllocsCount)}%"
|
||||
+"Total graph size: $totalGraphSize"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -532,15 +521,15 @@ internal object EscapeAnalysis {
|
||||
private fun analyze(callGraph: CallGraph, multiNode: DirectedGraphMultiNode<DataFlowIR.FunctionSymbol.Declared>) {
|
||||
val nodes = multiNode.nodes.filter { intraproceduralAnalysisResults.containsKey(it) }.toMutableSet()
|
||||
|
||||
DEBUG_OUTPUT(0) {
|
||||
println("Analyzing multiNode:\n ${nodes.joinToString("\n ") { it.toString() }}")
|
||||
context.logMultiple {
|
||||
+"Analyzing multiNode:\n ${nodes.joinToString("\n ") { it.toString() }}"
|
||||
nodes.forEach { from ->
|
||||
println("DataFlowIR")
|
||||
+"DataFlowIR"
|
||||
intraproceduralAnalysisResults[from]!!.function.debugOutput()
|
||||
callGraph.directEdges[from]!!.callSites.forEach { to ->
|
||||
println("CALL")
|
||||
println(" from $from")
|
||||
println(" to ${to.actualCallee}")
|
||||
+"CALL"
|
||||
+" from $from"
|
||||
+" to ${to.actualCallee}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -553,30 +542,23 @@ internal object EscapeAnalysis {
|
||||
val function = toAnalyze.first()
|
||||
toAnalyze.remove(function)
|
||||
numberOfRuns[function] = numberOfRuns[function]!! + 1
|
||||
|
||||
DEBUG_OUTPUT(0) { println("Processing function $function") }
|
||||
context.log { "Processing function $function" }
|
||||
|
||||
val startResult = escapeAnalysisResults[function]!!
|
||||
|
||||
DEBUG_OUTPUT(0) { println("Start escape analysis result:\n$startResult") }
|
||||
context.log { "Start escape analysis result:\n$startResult" }
|
||||
|
||||
analyze(callGraph, pointsToGraphs[function]!!, function)
|
||||
val endResult = escapeAnalysisResults[function]!!
|
||||
|
||||
if (startResult == endResult) {
|
||||
|
||||
DEBUG_OUTPUT(0) { println("Escape analysis is not changed") }
|
||||
|
||||
context.log { "Escape analysis is not changed" }
|
||||
} else {
|
||||
|
||||
DEBUG_OUTPUT(0) { println("Escape analysis was refined:\n$endResult") }
|
||||
|
||||
context.log { "Escape analysis was refined:\n$endResult" }
|
||||
if (numberOfRuns[function]!! > 1) {
|
||||
|
||||
DEBUG_OUTPUT(0) {
|
||||
println("WARNING: Escape analysis for $function seems not to be converging." +
|
||||
" Assuming conservative results.")
|
||||
context.log {
|
||||
"WARNING: Escape analysis for $function seems not to be converging." +
|
||||
" Assuming conservative results."
|
||||
}
|
||||
|
||||
escapeAnalysisResults[function] = FunctionEscapeAnalysisResult.pessimistic(function.parameters.size)
|
||||
nodes.remove(function)
|
||||
}
|
||||
@@ -632,11 +614,9 @@ internal object EscapeAnalysis {
|
||||
pointerSize /* typeinfo */ + 4 /* size */ + itemSize * length
|
||||
|
||||
private fun analyze(callGraph: CallGraph, pointsToGraph: PointsToGraph, function: DataFlowIR.FunctionSymbol.Declared) {
|
||||
DEBUG_OUTPUT(0) {
|
||||
println("Before calls analysis")
|
||||
pointsToGraph.print()
|
||||
pointsToGraph.printDigraph(false)
|
||||
}
|
||||
context.log {"Before calls analysis" }
|
||||
pointsToGraph.log()
|
||||
pointsToGraph.logDigraph(false)
|
||||
|
||||
callGraph.directEdges[function]!!.callSites.forEach {
|
||||
val callee = it.actualCallee
|
||||
@@ -648,20 +628,16 @@ internal object EscapeAnalysis {
|
||||
pointsToGraph.processCall(it, calleeEAResult)
|
||||
}
|
||||
|
||||
DEBUG_OUTPUT(0) {
|
||||
println("After calls analysis")
|
||||
pointsToGraph.print()
|
||||
pointsToGraph.printDigraph(false)
|
||||
}
|
||||
context.log { "After calls analysis" }
|
||||
pointsToGraph.log()
|
||||
pointsToGraph.logDigraph(false)
|
||||
|
||||
// Build transitive closure.
|
||||
val eaResult = pointsToGraph.buildClosure()
|
||||
|
||||
DEBUG_OUTPUT(0) {
|
||||
println("After closure building")
|
||||
pointsToGraph.print()
|
||||
pointsToGraph.printDigraph(true)
|
||||
}
|
||||
context.log { "After closure building" }
|
||||
pointsToGraph.log()
|
||||
pointsToGraph.logDigraph(true)
|
||||
|
||||
escapeAnalysisResults[function] = eaResult
|
||||
|
||||
@@ -678,38 +654,29 @@ internal object EscapeAnalysis {
|
||||
val callee = callSite.actualCallee.resolved()
|
||||
|
||||
val calleeEAResult = if (callSite.isVirtual) {
|
||||
|
||||
DEBUG_OUTPUT(0) { println("A virtual call: $callee") }
|
||||
|
||||
context.log { "A virtual call: $callee" }
|
||||
FunctionEscapeAnalysisResult.pessimistic(callee.parameters.size)
|
||||
} else {
|
||||
|
||||
DEBUG_OUTPUT(0) { println("An external call: $callee") }
|
||||
|
||||
context.log { "An external call: $callee" }
|
||||
if (callee.name?.startsWith("kfun:kotlin.") == true
|
||||
// TODO: Is it possible to do it in a more fine-grained fashion?
|
||||
&& !callee.name.startsWith("kfun:kotlin.native.concurrent")) {
|
||||
|
||||
DEBUG_OUTPUT(0) { println("A function from K/N runtime - can use annotations") }
|
||||
|
||||
context.log { "A function from K/N runtime - can use annotations" }
|
||||
FunctionEscapeAnalysisResult.fromBits(
|
||||
callee.escapes ?: 0,
|
||||
(0..callee.parameters.size).map { callee.pointsTo?.elementAtOrNull(it) ?: 0 }
|
||||
)
|
||||
} else {
|
||||
|
||||
DEBUG_OUTPUT(0) { println("An unknown function - assume pessimistic result") }
|
||||
|
||||
context.log { "An unknown function - assume pessimistic result" }
|
||||
FunctionEscapeAnalysisResult.pessimistic(callee.parameters.size)
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG_OUTPUT(0) {
|
||||
println("Escape analysis result")
|
||||
println(calleeEAResult.toString())
|
||||
println()
|
||||
context.logMultiple {
|
||||
+"Escape analysis result"
|
||||
+calleeEAResult.toString()
|
||||
+""
|
||||
}
|
||||
|
||||
return calleeEAResult
|
||||
}
|
||||
|
||||
@@ -806,12 +773,11 @@ internal object EscapeAnalysis {
|
||||
|
||||
fun escapes(node: PointsToGraphNode) = node in reachableFromEscapeOrigins || node in referencingEscapeOrigins
|
||||
|
||||
val ids = if (DEBUG > 0)
|
||||
(listOf(functionAnalysisResult.function.body.rootScope)
|
||||
+ functionAnalysisResult.function.body.allScopes.flatMap { it.nodes }
|
||||
)
|
||||
.withIndex().associateBy({ it.value }, { it.index })
|
||||
else null
|
||||
val ids = (
|
||||
listOf(functionAnalysisResult.function.body.rootScope)
|
||||
+ functionAnalysisResult.function.body.allScopes.flatMap { it.nodes }
|
||||
)
|
||||
.withIndex().associateBy({ it.value }, { it.index })
|
||||
|
||||
fun lifetimeOf(node: DataFlowIR.Node) = nodes[node]!!.let { it.forcedLifetime ?: lifetimeOf(it) }
|
||||
|
||||
@@ -846,16 +812,12 @@ internal object EscapeAnalysis {
|
||||
private val returnValues: Set<DataFlowIR.Node>
|
||||
|
||||
init {
|
||||
|
||||
DEBUG_OUTPUT(0) {
|
||||
println("Building points-to graph for function $functionSymbol")
|
||||
println("Results of preliminary function analysis")
|
||||
context.logMultiple {
|
||||
+"Building points-to graph for function $functionSymbol"
|
||||
+"Results of preliminary function analysis"
|
||||
}
|
||||
|
||||
functionAnalysisResult.nodesRoles.forEach { (node, roles) ->
|
||||
|
||||
DEBUG_OUTPUT(0) { println("NODE ${nodeToString(node)}: $roles") }
|
||||
|
||||
context.log { "NODE ${nodeToString(node)}: $roles" }
|
||||
nodes[node] = newNode(roles, node)
|
||||
}
|
||||
|
||||
@@ -904,13 +866,13 @@ internal object EscapeAnalysis {
|
||||
}
|
||||
}
|
||||
|
||||
private fun nodeToStringWhole(node: DataFlowIR.Node) = DataFlowIR.Function.nodeToString(node, ids!!)
|
||||
private fun nodeToStringWhole(node: DataFlowIR.Node) = DataFlowIR.Function.nodeToString(node, ids)
|
||||
|
||||
private fun nodeToString(node: DataFlowIR.Node) = ids!![node].toString()
|
||||
private fun nodeToString(node: DataFlowIR.Node) = ids[node].toString()
|
||||
|
||||
fun print() {
|
||||
println("POINTS-TO GRAPH")
|
||||
println("NODES")
|
||||
fun log() = context.logMultiple {
|
||||
+"POINTS-TO GRAPH"
|
||||
+"NODES"
|
||||
val tempIds = mutableMapOf<PointsToGraphNode, Int>()
|
||||
var tempIndex = 0
|
||||
allNodes.forEach {
|
||||
@@ -919,18 +881,17 @@ internal object EscapeAnalysis {
|
||||
}
|
||||
allNodes.forEach {
|
||||
val tempId = tempIds[it]
|
||||
println(" ${lifetimeOf(it)} ${it.depth} ${if (it in escapeOrigins) "ESCAPES" else ""} ${it.node?.let { nodeToString(it) } ?: "t$tempId"}")
|
||||
print(it.node?.let { nodeToStringWhole(it) } ?: " t$tempId\n")
|
||||
+" ${lifetimeOf(it)} ${it.depth} ${if (it in escapeOrigins) "ESCAPES" else ""} ${it.node?.let { nodeToString(it) } ?: "t$tempId"}"
|
||||
+(it.node?.let { nodeToStringWhole(it) } ?: " t$tempId")
|
||||
}
|
||||
}
|
||||
|
||||
fun printDigraph(
|
||||
fun logDigraph(
|
||||
markDrains: Boolean,
|
||||
nodeFilter: (PointsToGraphNode) -> Boolean = { true },
|
||||
nodeLabel: ((PointsToGraphNode) -> String)? = null
|
||||
) {
|
||||
println("digraph {")
|
||||
val ids = ids!!
|
||||
) = context.logMultiple {
|
||||
+"digraph {"
|
||||
val tempIds = mutableMapOf<PointsToGraphNode, Int>()
|
||||
var tempIndex = 0
|
||||
allNodes.forEach {
|
||||
@@ -951,24 +912,23 @@ internal object EscapeAnalysis {
|
||||
if (!nodeFilter(to)) continue
|
||||
when (it) {
|
||||
is PointsToGraphEdge.Assignment ->
|
||||
println(" \"${from.format()}\" -> \"${to.format()}\";")
|
||||
+" \"${from.format()}\" -> \"${to.format()}\";"
|
||||
is PointsToGraphEdge.Field ->
|
||||
println(" \"${from.format()}\" -> \"${to.format()}\" [ label=\"${it.field.name}\"];")
|
||||
+" \"${from.format()}\" -> \"${to.format()}\" [ label=\"${it.field.name}\"];"
|
||||
}
|
||||
}
|
||||
}
|
||||
println("}")
|
||||
+"}"
|
||||
}
|
||||
|
||||
fun processCall(callSite: CallGraphNode.CallSite, calleeEscapeAnalysisResult: FunctionEscapeAnalysisResult) {
|
||||
val call = callSite.call
|
||||
|
||||
DEBUG_OUTPUT(0) {
|
||||
println("Processing callSite")
|
||||
println(nodeToStringWhole(call))
|
||||
println("Actual callee: ${callSite.actualCallee}")
|
||||
println("Callee escape analysis result:")
|
||||
println(calleeEscapeAnalysisResult.toString())
|
||||
context.logMultiple {
|
||||
+"Processing callSite"
|
||||
+nodeToStringWhole(call)
|
||||
+"Actual callee: ${callSite.actualCallee}"
|
||||
+"Callee escape analysis result:"
|
||||
+calleeEscapeAnalysisResult.toString()
|
||||
}
|
||||
|
||||
val arguments = if (call is DataFlowIR.Node.NewObject) {
|
||||
@@ -1009,56 +969,44 @@ internal object EscapeAnalysis {
|
||||
calleeEscapeAnalysisResult.escapes.forEach { escapingNode ->
|
||||
val (arg, node) = mapNode(escapingNode)
|
||||
if (node == null) {
|
||||
|
||||
DEBUG_OUTPUT(0) { println("WARNING: There is no node ${nodeToString(arg!!)}") }
|
||||
|
||||
context.log { "WARNING: There is no node ${nodeToString(arg!!)}" }
|
||||
return@forEach
|
||||
}
|
||||
escapeOrigins += node
|
||||
|
||||
DEBUG_OUTPUT(0) {
|
||||
println("Node ${escapingNode.debugOutput(arg?.let { nodeToString(it) })} escapes")
|
||||
}
|
||||
context.log { "Node ${escapingNode.debugString(arg?.let { nodeToString(it) })} escapes" }
|
||||
}
|
||||
|
||||
calleeEscapeAnalysisResult.pointsTo.edges.forEach { edge ->
|
||||
val (fromArg, fromNode) = mapNode(edge.from)
|
||||
if (fromNode == null) {
|
||||
|
||||
DEBUG_OUTPUT(0) { println("WARNING: There is no node ${nodeToString(fromArg!!)}") }
|
||||
|
||||
context.log { "WARNING: There is no node ${nodeToString(fromArg!!)}" }
|
||||
return@forEach
|
||||
}
|
||||
val (toArg, toNode) = mapNode(edge.to)
|
||||
if (toNode == null) {
|
||||
|
||||
DEBUG_OUTPUT(0) { println("WARNING: There is no node ${nodeToString(toArg!!)}") }
|
||||
|
||||
context.log { "WARNING: There is no node ${nodeToString(toArg!!)}" }
|
||||
return@forEach
|
||||
}
|
||||
fromNode.addAssignmentEdge(toNode)
|
||||
|
||||
DEBUG_OUTPUT(0) {
|
||||
println("Adding edge")
|
||||
println(" FROM ${edge.from.debugOutput(fromArg?.let { nodeToString(it) })}")
|
||||
println(" TO ${edge.to.debugOutput(toArg?.let { nodeToString(it) })}")
|
||||
context.logMultiple {
|
||||
+"Adding edge"
|
||||
+" FROM ${edge.from.debugString(fromArg?.let { nodeToString(it) })}"
|
||||
+" TO ${edge.to.debugString(toArg?.let { nodeToString(it) })}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun buildClosure(): FunctionEscapeAnalysisResult {
|
||||
|
||||
DEBUG_OUTPUT(0) {
|
||||
println("BUILDING CLOSURE")
|
||||
println("Return values:")
|
||||
returnValues.forEach {
|
||||
println(" ${nodeToString(it)}")
|
||||
}
|
||||
context.logMultiple {
|
||||
+"BUILDING CLOSURE"
|
||||
+"Return values:"
|
||||
returnValues.forEach { +" ${nodeToString(it)}" }
|
||||
}
|
||||
|
||||
buildDrains()
|
||||
|
||||
DEBUG_OUTPUT(0) { printDigraph(true) }
|
||||
logDigraph(true)
|
||||
|
||||
computeLifetimes()
|
||||
|
||||
@@ -1072,9 +1020,7 @@ internal object EscapeAnalysis {
|
||||
*/
|
||||
val (numberOfDrains, nodeIds) = paintInterestingNodes()
|
||||
|
||||
DEBUG_OUTPUT(0) {
|
||||
printDigraph(true, { nodeIds[it] != null }, { nodeIds[it].toString() })
|
||||
}
|
||||
logDigraph(true, { nodeIds[it] != null }, { nodeIds[it].toString() })
|
||||
|
||||
// TODO: Remove redundant edges.
|
||||
val compressedEdges = mutableListOf<CompressedPointsToGraph.Edge>()
|
||||
@@ -1616,11 +1562,7 @@ internal object EscapeAnalysis {
|
||||
|
||||
if (lifetime != computedLifetime) {
|
||||
if (propagateExiledToHeapObjects && node.isAlloc) {
|
||||
|
||||
DEBUG_OUTPUT(0) {
|
||||
println("Forcing node ${nodeToString(node)} to escape")
|
||||
}
|
||||
|
||||
context.log { "Forcing node ${nodeToString(node)} to escape" }
|
||||
escapeOrigins += ptgNode
|
||||
propagateEscapeOrigin(ptgNode)
|
||||
} else {
|
||||
@@ -1640,11 +1582,7 @@ internal object EscapeAnalysis {
|
||||
allowedToAlloc = 0
|
||||
// Do not exile primitive arrays - they ain't reference no object.
|
||||
if (irClass.symbol == symbols.array && propagateExiledToHeapObjects) {
|
||||
|
||||
DEBUG_OUTPUT(0) {
|
||||
println("Forcing node ${nodeToString(ptgNode.node!!)} to escape")
|
||||
}
|
||||
|
||||
context.log { "Forcing node ${nodeToString(ptgNode.node!!)} to escape" }
|
||||
escapeOrigins += ptgNode
|
||||
propagateEscapeOrigin(ptgNode)
|
||||
} else {
|
||||
|
||||
+8
-13
@@ -8,15 +8,10 @@ package org.jetbrains.kotlin.backend.konan.optimizations
|
||||
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.logMultiple
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.isArrayWithFixedSizeItems
|
||||
|
||||
internal object LocalEscapeAnalysis {
|
||||
private val DEBUG = 0
|
||||
|
||||
private inline fun DEBUG_OUTPUT(severity: Int, block: () -> Unit) {
|
||||
if (DEBUG > severity) block()
|
||||
}
|
||||
|
||||
private enum class EscapeState {
|
||||
GLOBAL_ESCAPE, // escape through global reference
|
||||
ARG_ESCAPE, // escapes through function arguments, return or throw
|
||||
@@ -187,7 +182,7 @@ internal object LocalEscapeAnalysis {
|
||||
}
|
||||
ir?.let {
|
||||
lifetimes.put(it, Lifetime.STACK)
|
||||
DEBUG_OUTPUT(3) { println("${ir} does not escape") }
|
||||
context.log { "$ir does not escape" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -195,18 +190,18 @@ internal object LocalEscapeAnalysis {
|
||||
|
||||
fun analyze(context: Context, moduleDFG: ModuleDFG, lifetimes: MutableMap<IrElement, Lifetime>) {
|
||||
moduleDFG.functions.forEach { (name, function) ->
|
||||
DEBUG_OUTPUT(5) {
|
||||
println("===============================================")
|
||||
println("Visiting $name")
|
||||
println("DATA FLOW IR:")
|
||||
function.debugOutput()
|
||||
context.logMultiple {
|
||||
+"==============================================="
|
||||
+"Visiting $name"
|
||||
+"DATA FLOW IR:"
|
||||
+function.debugString()
|
||||
}
|
||||
FunctionAnalyzer(function, context).analyze(lifetimes)
|
||||
}
|
||||
}
|
||||
|
||||
fun computeLifetimes(context: Context, moduleDFG: ModuleDFG, lifetimes: MutableMap<IrElement, Lifetime>) {
|
||||
DEBUG_OUTPUT(1) { println("In local EA") }
|
||||
context.log { "In local EA" }
|
||||
assert(lifetimes.isEmpty())
|
||||
analyze(context, moduleDFG, lifetimes)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user