Rewrote escape analysis

This commit is contained in:
Igor Chevdar
2017-08-02 18:36:49 +03:00
parent 7eb2572dc3
commit eabe2435bf
23 changed files with 1601 additions and 529 deletions
@@ -170,6 +170,9 @@ internal class Context(config: KonanConfig) : KonanBackendContext(config) {
// But we have to wait until the code generation phase,
// to dump this information into generated file.
var serializedLinkData: LinkData? = null
val moduleEscapeAnalysisResult: ModuleEscapeAnalysisResult.ModuleEAResult.Builder by lazy {
ModuleEscapeAnalysisResult.ModuleEAResult.newBuilder()
}
@Deprecated("")
lateinit var psi2IrGeneratorContext: GeneratorContext
@@ -0,0 +1,15 @@
syntax = "proto2";
package org.jetbrains.kotlin.backend.konan;
option java_outer_classname = "ModuleEscapeAnalysisResult";
option optimize_for = LITE_RUNTIME;
message FunctionEAResult {
required string fqName = 1;
required int32 escapes = 2;
repeated int32 pointsTo = 3;
}
message ModuleEAResult {
repeated FunctionEAResult functionEAResults = 1;
}
@@ -0,0 +1,99 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.backend.konan
internal interface DirectedGraphNode<out K> {
val key: K
val directEdges: List<K>
val reversedEdges: List<K>
}
internal interface DirectedGraph<K, out N: DirectedGraphNode<K>> {
val nodes: Collection<N>
fun get(key: K): N
}
internal class DirectedGraphMultiNode<out K>(val nodes: Set<K>)
internal class DirectedGraphCondensation<out K>(val topologicalOrder: List<DirectedGraphMultiNode<K>>)
internal class DirectedGraphCondensationBuilder<K, out N: DirectedGraphNode<K>>(val graph: DirectedGraph<K, N>) {
private val visited = mutableSetOf<K>()
private val order = mutableListOf<N>()
private val nodeToMultiNodeMap = mutableMapOf<N, DirectedGraphMultiNode<K>>()
private val multiNodesOrder = mutableListOf<DirectedGraphMultiNode<K>>()
fun build(): DirectedGraphCondensation<K> {
// First phase.
graph.nodes.forEach {
if (!visited.contains(it.key))
findOrder(it)
}
// Second phase.
visited.clear()
val multiNodes = mutableListOf<DirectedGraphMultiNode<K>>()
order.reversed().forEach {
if (!visited.contains(it.key)) {
val nodes = mutableSetOf<K>()
paint(it, nodes)
multiNodes += DirectedGraphMultiNode(nodes)
}
}
// Topsort of built condensation.
multiNodes.forEach { multiNode ->
multiNode.nodes.forEach { nodeToMultiNodeMap.put(graph.get(it), multiNode) }
}
visited.clear()
multiNodes.forEach {
if (!visited.contains(it.nodes.first()))
findMultiNodesOrder(it)
}
return DirectedGraphCondensation(multiNodesOrder)
}
private fun findOrder(node: N) {
visited += node.key
node.directEdges.forEach {
if (!visited.contains(it))
findOrder(graph.get(it))
}
order += node
}
private fun paint(node: N, multiNode: MutableSet<K>) {
visited += node.key
multiNode += node.key
node.reversedEdges.forEach {
if (!visited.contains(it))
paint(graph.get(it), multiNode)
}
}
private fun findMultiNodesOrder(node: DirectedGraphMultiNode<K>) {
visited.addAll(node.nodes)
node.nodes.forEach {
graph.get(it).directEdges.forEach {
if (!visited.contains(it))
findMultiNodesOrder(nodeToMultiNodeMap[graph.get(it)]!!)
}
}
multiNodesOrder += node
}
}
@@ -56,6 +56,7 @@ enum class KonanPhase(val description: String,
/* ... ... */ AUTOBOX("Autoboxing of primitive types", BRIDGES_BUILDING, LOWER_COROUTINES),
/* ... */ BITCODE("LLVM BitCode Generation"),
/* ... ... */ RTTI("RTTI Generation"),
/* ... ... */ ESCAPE_ANALYSIS("Escape analysis"),
/* ... ... */ CODEGEN("Code Generation"),
/* ... ... */ BITCODE_LINKER("Bitcode linking"),
/* */ LINK_STAGE("Link stage"),
@@ -41,7 +41,9 @@ interface KonanLibraryLayout {
get() = File(libDir, "linkdata")
val moduleHeaderFile
get() = File(linkdataDir, "module")
fun packageFile(packageName: String)
val escapeAnalysisFile
get() = File(linkdataDir, "module_escape_analysis")
fun packageFile(packageName: String)
= File(linkdataDir, if (packageName == "") "root_package" else "package_$packageName")
}
@@ -23,6 +23,7 @@ interface KonanLibraryReader {
val libraryName: String
val bitcodePaths: List<String>
val linkerOpts: List<String>
val escapeAnalysis: ByteArray?
fun moduleDescriptor(specifics: LanguageVersionSettings): ModuleDescriptor
}
@@ -23,6 +23,7 @@ interface KonanLibraryWriter {
fun addNativeBitcode(library: String)
fun addKotlinBitcode(llvmModule: LLVMModuleRef)
fun addManifestAddend(path: String)
fun addEscapeAnalysis(escapeAnalysis: ByteArray)
val mainBitcodeFileName: String
fun commit()
}
@@ -47,6 +47,7 @@ class LibraryReaderImpl(var libraryFile: File, val currentAbiVersion: Int, val t
}
val targetList = inPlace.targetsDir.listFiles.map{it.name}
override val escapeAnalysis: ByteArray? by lazy { inPlace.escapeAnalysisFile.let { if (it.exists) it.readBytes() else null } }
override val libraryName
get() = inPlace.libraryName
@@ -82,6 +82,10 @@ class LibraryWriterImpl(override val libDir: File, currentAbiVersion: Int,
println("manifest addend: ${properties.stringPropertyNames().joinToString(" ")}")
}
override fun addEscapeAnalysis(escapeAnalysis: ByteArray) {
escapeAnalysisFile.writeBytes(escapeAnalysis)
}
override fun commit() {
manifestProperties.saveToFile(manifestFile)
if (!nopack) {
@@ -99,7 +103,8 @@ internal fun buildLibrary(
output: String,
llvmModule: LLVMModuleRef,
nopack: Boolean,
manifest: String?): KonanLibraryWriter {
manifest: String?,
escapeAnalysis: ByteArray?): KonanLibraryWriter {
val library = LibraryWriterImpl(output, abiVersion, target, nopack)
@@ -109,6 +114,7 @@ internal fun buildLibrary(
library.addNativeBitcode(it)
}
manifest ?.let { library.addManifestAddend(it) }
escapeAnalysis?.let { library.addEscapeAnalysis(it) }
library.commit()
return library
@@ -226,7 +226,9 @@ internal class FunctionGenerationContext(val function: LLVMValueRef,
fun call(llvmFunction: LLVMValueRef, args: List<LLVMValueRef>,
resultLifetime: Lifetime = Lifetime.IRRELEVANT,
lazyLandingpad: () -> LLVMBasicBlockRef? = { null }): LLVMValueRef {
val callArgs = if (isObjectReturn(llvmFunction.type)) {
val callArgs = if (!isObjectReturn(llvmFunction.type)) {
args
} else {
// If function returns an object - create slot for the returned value or give local arena.
// This allows appropriate rootset accounting by just looking at the stack slots,
// along with ability to allocate in appropriate arena.
@@ -235,15 +237,28 @@ internal class FunctionGenerationContext(val function: LLVMValueRef,
localAllocs++
arenaSlot!!
}
SlotType.RETURN -> returnSlot!!
// TODO: for RETURN_IF_ARENA choose between created slot and arenaSlot
// dynamically.
SlotType.ANONYMOUS, SlotType.RETURN_IF_ARENA -> vars.createAnonymousSlot()
else -> throw Error("Incorrect slot type")
SlotType.ANONYMOUS -> vars.createAnonymousSlot()
SlotType.RETURN_IF_ARENA -> returnSlot.let {
if (it != null)
call(context.llvm.getReturnSlotIfArenaFunction, listOf(it, vars.createAnonymousSlot()))
else {
// Return type is not an object type - can allocate locally.
localAllocs++
arenaSlot!!
}
}
is SlotType.PARAM_IF_ARENA ->
call(context.llvm.getParamSlotIfArenaFunction,
listOf(vars.load(resultLifetime.slotType.parameter), vars.createAnonymousSlot()))
else -> throw Error("Incorrect slot type: ${resultLifetime.slotType}")
}
args + resultSlot
} else {
args
}
return callRaw(llvmFunction, callArgs, lazyLandingpad)
}
@@ -385,7 +400,7 @@ internal class FunctionGenerationContext(val function: LLVMValueRef,
internal fun prologue() {
assert(returns.isEmpty())
if (isObjectType(returnType!!)) {
this.returnSlot = LLVMGetParam(function, numParameters(function.type) - 1)
returnSlot = LLVMGetParam(function, numParameters(function.type) - 1)
}
positionAtEnd(localsInitBb)
slotsPhi = phi(kObjHeaderPtrPtr)
@@ -32,40 +32,105 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeUtils
internal enum class SlotType {
internal sealed class SlotType {
// Frame local arena slot can be used.
ARENA,
class ARENA: SlotType()
// Return slot can be used.
RETURN,
class RETURN: SlotType()
// Return slot, if it is an arena, can be used.
RETURN_IF_ARENA,
class RETURN_IF_ARENA: SlotType()
// Return slot, if it is an arena, can be used.
class PARAM_IF_ARENA(val parameter: Int): SlotType()
// Anonymous slot.
ANONYMOUS,
class ANONYMOUS: SlotType()
// Unknown slot type.
UNKNOWN
class UNKNOWN: SlotType()
companion object {
val ARENA = ARENA()
val RETURN = RETURN()
val RETURN_IF_ARENA = RETURN_IF_ARENA()
val ANONYMOUS = ANONYMOUS()
val UNKNOWN = UNKNOWN()
}
}
// Lifetimes class of reference, computed by escape analysis.
enum class Lifetime(val slotType: SlotType) {
internal sealed class Lifetime(val slotType: SlotType) {
// If reference is frame-local (only obtained from some call and never leaves).
LOCAL(SlotType.ARENA),
class LOCAL: Lifetime(SlotType.ARENA) {
override fun toString(): String {
return "LOCAL"
}
}
// If reference is only returned.
RETURN_VALUE(SlotType.RETURN),
class RETURN_VALUE: Lifetime(SlotType.RETURN) {
override fun toString(): String {
return "RETURN_VALUE"
}
}
// If reference is set as field of references of class RETURN_VALUE or INDIRECT_RETURN_VALUE.
INDIRECT_RETURN_VALUE(SlotType.RETURN_IF_ARENA),
class INDIRECT_RETURN_VALUE: Lifetime(SlotType.RETURN_IF_ARENA) {
override fun toString(): String {
return "INDIRECT_RETURN_VALUE"
}
}
// If reference is stored to the field of an incoming parameters.
PARAMETER_FIELD(SlotType.ANONYMOUS),
class PARAMETER_FIELD(val parameter: Int): Lifetime(SlotType.PARAM_IF_ARENA(parameter)) {
override fun toString(): String {
return "PARAMETER_FIELD($parameter)"
}
}
// If reference refers to the global (either global object or global variable).
GLOBAL(SlotType.ANONYMOUS),
class GLOBAL: Lifetime(SlotType.ANONYMOUS) {
override fun toString(): String {
return "GLOBAL"
}
}
// If reference used to throw.
THROW(SlotType.ANONYMOUS),
class THROW: Lifetime(SlotType.ANONYMOUS) {
override fun toString(): String {
return "THROW"
}
}
// If reference used as an argument of outgoing function. Class can be improved by escape analysis
// of called function.
ARGUMENT(SlotType.ANONYMOUS),
class ARGUMENT: Lifetime(SlotType.ANONYMOUS) {
override fun toString(): String {
return "ARGUMENT"
}
}
// If reference class is unknown.
UNKNOWN(SlotType.UNKNOWN),
class UNKNOWN: Lifetime(SlotType.UNKNOWN) {
override fun toString(): String {
return "UNKNOWN"
}
}
// If reference class is irrelevant.
IRRELEVANT(SlotType.UNKNOWN)
class IRRELEVANT: Lifetime(SlotType.UNKNOWN) {
override fun toString(): String {
return "IRRELEVANT"
}
}
companion object {
val LOCAL = LOCAL()
val RETURN_VALUE = RETURN_VALUE()
val INDIRECT_RETURN_VALUE = INDIRECT_RETURN_VALUE()
val GLOBAL = GLOBAL()
val THROW = THROW()
val ARGUMENT = ARGUMENT()
val UNKNOWN = UNKNOWN()
val IRRELEVANT = IRRELEVANT()
}
}
/**
@@ -253,6 +318,8 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) {
val setRefFunction = importRtFunction("SetRef")
val updateRefFunction = importRtFunction("UpdateRef")
val leaveFrameFunction = importRtFunction("LeaveFrame")
val getReturnSlotIfArenaFunction = importRtFunction("GetReturnSlotIfArena")
val getParamSlotIfArenaFunction = importRtFunction("GetParamSlotIfArena")
val lookupOpenMethodFunction = importRtFunction("LookupOpenMethod")
val isInstanceFunction = importRtFunction("IsInstance")
val checkInstanceFunction = importRtFunction("CheckInstance")
@@ -71,8 +71,13 @@ internal fun emitLLVM(context: Context) {
generateDebugInfoHeader(context)
val lifetimes = mutableMapOf<IrElement, Lifetime>()
phaser.phase(KonanPhase.ESCAPE_ANALYSIS) {
EscapeAnalysis.computeLifetimes(irModule, context, lifetimes)
}
phaser.phase(KonanPhase.CODEGEN) {
irModule.acceptVoid(CodeGeneratorVisitor(context))
irModule.acceptVoid(CodeGeneratorVisitor(context, lifetimes))
}
if (context.shouldContainDebugInfo()) {
@@ -118,7 +123,8 @@ internal fun produceOutput(context: Context) {
libraryName,
llvmModule,
nopack,
manifest)
manifest,
context.moduleEscapeAnalysisResult?.build()?.toByteArray())
context.library = library
context.bitcodeFileName = library.mainBitcodeFileName
@@ -232,10 +238,9 @@ internal interface CodeContext {
//-------------------------------------------------------------------------//
internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid {
internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrElement, Lifetime>) : IrElementVisitorVoid {
val codegen = CodeGenerator(context)
val resultLifetimes = mutableMapOf<IrElement, Lifetime>()
//-------------------------------------------------------------------------//
@@ -310,8 +315,6 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid
override fun visitModuleFragment(declaration: IrModuleFragment) {
context.log{"visitModule : ${ir2string(declaration)}"}
// computeLifetimes(module, this.codegen, resultLifetimes)
declaration.acceptChildrenVoid(this)
appendLlvmUsed(context.llvm.usedFunctions)
appendStaticInitializers(context.llvm.staticInitializers)
@@ -1847,7 +1850,7 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid
//-------------------------------------------------------------------------//
private fun resultLifetime(callee: IrElement): Lifetime {
return resultLifetimes.getOrElse(callee) { /* TODO: make IRRELEVANT */ Lifetime.GLOBAL }
return lifetimes.getOrElse(callee) { /* TODO: make IRRELEVANT */ Lifetime.GLOBAL }
}
private fun evaluateConstructorCall(callee: IrCall, args: List<LLVMValueRef>): LLVMValueRef {
@@ -39,11 +39,14 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
//-----------------------------------------------------------------------------//
private val inlineConstructor by lazy { FqName("konan.internal.InlineConstructor") }
internal val FunctionDescriptor.isInlineConstructor get() = annotations.hasAnnotation(inlineConstructor)
internal class InlineConstructorsTransformation(val context: Context): IrElementTransformerVoidWithContext() {
private val deserializer by lazy { DeserializerDriver(context) }
private val substituteMap by lazy { mutableMapOf<ValueDescriptor, IrExpression>() }
private val inlineConstructor by lazy { FqName("konan.internal.InlineConstructor") }
//-------------------------------------------------------------------------//
@@ -55,7 +58,7 @@ internal class InlineConstructorsTransformation(val context: Context): IrElement
val irCall = super.visitCall(expression) as IrCall
val functionDescriptor = irCall.descriptor
if (!functionDescriptor.annotations.hasAnnotation(inlineConstructor)) return irCall // This call does not need inlining.
if (!functionDescriptor.isInlineConstructor) return irCall // This call does not need inlining.
val functionDeclaration = getFunctionDeclaration(irCall) // Get declaration of the function to be inlined.
if (functionDeclaration == null) { // We failed to get the declaration.
+13 -18
View File
@@ -1,20 +1,13 @@
fun foo1(arg: String) : String = arg
//fun foo1(arg: String) : String = foo0(arg)
fun foo1(arg: Any) : Any = foo0(arg)
/*
fun foo2(arg: String) : String = arg + " foo2"
fun foo0(arg: Any) : Any = Any()
fun bar(arg1: String, arg2: String) : String = arg1 + " bar " + arg2
var global : Any = Any()
fun zoo1() : String {
var x = foo1("")
var y = 4
return x
}
fun zoo2() : String {
val x = foo1("")
var y = 5
return x
fun foo0_escape(arg: Any) : Any{
global = arg
return Any()
}
class Node(var previous: Node?)
@@ -38,10 +31,11 @@ fun zoo4(arg: Int) : Any {
else -> c
}
return a
} */
}
fun zoo5(arg: String) : String {
return arg + foo1(arg)
fun zoo5(arg: Any) : Any{
foo1(arg)
return arg
}
fun zoo6(arg: Any) : Any {
@@ -53,6 +47,7 @@ fun zoo7(arg1: Any, arg2: Any, selector: Int) : Any {
}
fun main(args : Array<String>) {
val z = zoo7(Any(), Any(), 1)
//val z = zoo7(Any(), Any(), 1)
val x = zoo5(Any())
//println(bar(foo1(foo2("")), foo2(foo1(""))))
}