Local escape analysis (#3625)

This commit is contained in:
LepilkinaElena
2019-12-09 15:37:24 +03:00
committed by GitHub
parent 7bae7adc4e
commit 60746d0438
17 changed files with 417 additions and 63 deletions
@@ -485,6 +485,8 @@ internal class Context(config: KonanConfig) : KonanBackendContext(config) {
producingCache = config.produce.isCache,
librariesToCache = config.librariesToCache
)
val declaredLocalArrays: MutableMap<String, LLVMTypeRef> = HashMap()
}
private fun MemberScope.getContributedClassifier(name: String) =
@@ -393,6 +393,7 @@ internal val bitcodePhase = namedIrModulePhase(
RTTIPhase then
generateDebugInfoHeaderPhase then
escapeAnalysisPhase then
localEscapeAnalysisPhase then
codegenPhase then
finalizeDebugInfoPhase then
cStubsPhase
@@ -458,6 +459,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(dcePhase, getBoolean(KonanConfigKeys.OPTIMIZATION))
disableUnless(ghaPhase, getBoolean(KonanConfigKeys.OPTIMIZATION))
disableUnless(verifyBitcodePhase, config.needCompilerVerification || getBoolean(KonanConfigKeys.VERIFY_BITCODE))
@@ -95,10 +95,22 @@ internal val arrayTypes = setOf(
"kotlin.native.internal.NativePtrArray"
)
internal val arraysWithFixedSizeItems = setOf(
"kotlin.ByteArray",
"kotlin.CharArray",
"kotlin.ShortArray",
"kotlin.IntArray",
"kotlin.LongArray",
"kotlin.FloatArray",
"kotlin.DoubleArray",
"kotlin.BooleanArray"
)
internal val IrClass.isArray: Boolean
get() = this.fqNameForIrSerialization.asString() in arrayTypes
internal val IrClass.isArrayWithFixedSizeItems: Boolean
get() = this.fqNameForIrSerialization.asString() in arraysWithFixedSizeItems
fun IrClass.isAbstract() = this.modality == Modality.SEALED || this.modality == Modality.ABSTRACT
@@ -211,6 +211,15 @@ internal val escapeAnalysisPhase = makeKonanModuleOpPhase(
}
)
internal val localEscapeAnalysisPhase = makeKonanModuleOpPhase(
name = "LocalEscapeAnalysis",
description = "Local escape analysis",
prerequisite = setOf(buildDFGPhase, devirtualizationPhase),
op = { context, _ ->
LocalEscapeAnalysis.computeLifetimes(context, context.moduleDFG!!, context.lifetimes)
}
)
internal val serializeDFGPhase = makeKonanModuleOpPhase(
name = "SerializeDFG",
description = "Data flow graph serializing",
@@ -190,7 +190,7 @@ internal class FunctionGenerationContext(val function: LLVMValueRef,
val vars = VariableManager(this)
private val basicBlockToLastLocation = mutableMapOf<LLVMBasicBlockRef, LocationInfoRange>()
private fun update(block:LLVMBasicBlockRef, startLocationInfo: LocationInfo?, endLocation: LocationInfo? = startLocationInfo) {
private fun update(block: LLVMBasicBlockRef, startLocationInfo: LocationInfo?, endLocation: LocationInfo? = startLocationInfo) {
startLocationInfo ?: return
basicBlockToLastLocation.put(block, LocationInfoRange(startLocationInfo, endLocation))
}
@@ -205,13 +205,14 @@ internal class FunctionGenerationContext(val function: LLVMValueRef,
(LLVMStoreSizeOfType(llvmTargetData, runtime.frameOverlayType) / runtime.pointerSize).toInt()
private var slotCount = frameOverlaySlotCount
private var localAllocs = 0
private var arenaSlot: LLVMValueRef? = null
private val slotToVariableLocation = mutableMapOf<Int,VariableDebugLocation>()
// TODO: remove if exactly unused.
//private var arenaSlot: LLVMValueRef? = null
private val slotToVariableLocation = mutableMapOf<Int, VariableDebugLocation>()
private val prologueBb = basicBlockInFunction("prologue", startLocation)
private val localsInitBb = basicBlockInFunction("locals_init", startLocation)
private val entryBb = basicBlockInFunction("entry", startLocation)
private val epilogueBb = basicBlockInFunction("epilogue", endLocation)
private val prologueBb = basicBlockInFunction("prologue", startLocation)
private val localsInitBb = basicBlockInFunction("locals_init", startLocation)
private val entryBb = basicBlockInFunction("entry", startLocation)
private val epilogueBb = basicBlockInFunction("epilogue", endLocation)
private val cleanupLandingpad = basicBlockInFunction("cleanup_landingpad", endLocation)
/**
@@ -238,7 +239,7 @@ internal class FunctionGenerationContext(val function: LLVMValueRef,
return bb
}
fun basicBlock(name:String = "label_", startLocationInfo:LocationInfo?, endLocationInfo: LocationInfo? = startLocationInfo): LLVMBasicBlockRef {
fun basicBlock(name: String = "label_", startLocationInfo: LocationInfo?, endLocationInfo: LocationInfo? = startLocationInfo): LLVMBasicBlockRef {
val result = LLVMInsertBasicBlockInContext(llvmContext, this.currentBlock, name)!!
update(result, startLocationInfo, endLocationInfo)
LLVMMoveBasicBlockAfter(result, this.currentBlock)
@@ -261,13 +262,13 @@ internal class FunctionGenerationContext(val function: LLVMValueRef,
val slotAddress = LLVMBuildAlloca(builder, type, name)!!
variableLocation?.let {
DIInsertDeclaration(
builder = codegen.context.debugInfo.builder,
value = slotAddress,
builder = codegen.context.debugInfo.builder,
value = slotAddress,
localVariable = it.localVariable,
location = it.location,
bb = prologueBb,
expr = null,
exprCount = 0)
location = it.location,
bb = prologueBb,
expr = null,
exprCount = 0)
}
return slotAddress
}
@@ -327,7 +328,7 @@ internal class FunctionGenerationContext(val function: LLVMValueRef,
fun freeze(value: LLVMValueRef, exceptionHandler: ExceptionHandler) {
if (isObjectRef(value))
call(context.llvm.freezeSubgraph, listOf(value), Lifetime.IRRELEVANT, exceptionHandler)
call(context.llvm.freezeSubgraph, listOf(value), Lifetime.IRRELEVANT, exceptionHandler)
}
fun checkMainThread(exceptionHandler: ExceptionHandler) {
@@ -368,7 +369,13 @@ internal class FunctionGenerationContext(val function: LLVMValueRef,
val resultSlot = when (resultLifetime.slotType) {
SlotType.ARENA -> {
localAllocs++
arenaSlot!!
// Case of local call. Use memory allocated on stack.
val type = LLVMGetReturnType(LLVMGetElementType(llvmFunction.type))!!
val stackPointer = alloca(type)
//val objectHeader = structGep(stackPointer, 0)
//setTypeInfoForLocalObject(objectHeader)
stackPointer
//arenaSlot!!
}
SlotType.RETURN -> returnSlot!!
@@ -385,7 +392,7 @@ internal class FunctionGenerationContext(val function: LLVMValueRef,
private fun callRaw(llvmFunction: LLVMValueRef, args: List<LLVMValueRef>,
exceptionHandler: ExceptionHandler): LLVMValueRef {
val rargs = args.toCValues()
if (LLVMIsAFunction(llvmFunction) != null /* the function declaration */ &&
if (LLVMIsAFunction(llvmFunction) != null /* the function declaration */ &&
isFunctionNoUnwind(llvmFunction)) {
return LLVMBuildCall(builder, llvmFunction, rargs, args.size, "")!!
} else {
@@ -439,13 +446,70 @@ internal class FunctionGenerationContext(val function: LLVMValueRef,
call(context.llvm.allocInstanceFunction, listOf(typeInfo), lifetime)
fun allocInstance(irClass: IrClass, lifetime: Lifetime): LLVMValueRef =
allocInstance(codegen.typeInfoForAllocation(irClass), lifetime)
if (lifetime == Lifetime.LOCAL) {
val stackSlot = alloca(context.llvmDeclarations.forClass(irClass).bodyType)
val objectHeader = structGep(stackSlot, 0, "objHeader")
setTypeInfoForLocalObject(objectHeader)
objectHeader
} else {
allocInstance(codegen.typeInfoForAllocation(irClass), lifetime)
}
fun allocArray(typeInfo: LLVMValueRef,
// 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 =
call(context.llvm.allocArrayFunction, listOf(typeInfo, count), lifetime, exceptionHandler)
if (lifetime == Lifetime.LOCAL) {
val className = irClass.fqNameForIrSerialization.asString()
assert(className in arrayToElementType)
allocArrayOnStack(className, count)
} else {
call(context.llvm.allocArrayFunction, listOf(codegen.typeInfoValue(irClass), count),
lifetime, exceptionHandler)
}
fun setTypeInfoForLocalObject(objectHeader: LLVMValueRef) {
val typeInfo = structGep(objectHeader, 0, "typeInfoOrMeta_")
// Set tag OBJECT_TAG_PERMANENT_CONTAINER.
val typeInfoValue = intToPtr(or(ptrToInt(alloca(kTypeInfo), int32Type), Int32(1).llvm), kTypeInfoPtr)
store(typeInfoValue, typeInfo)
}
fun allocArrayOnStack(arrayTypeName: String, count: LLVMValueRef): LLVMValueRef {
val arraySlot = alloca(localArrayType(arrayTypeName, extractConstUnsignedInt(count).toInt()))
// Set array size in ArrayHeader.
val arrayHeaderSlot = structGep(arraySlot, 0, "arrayHeader")
setTypeInfoForLocalObject(arrayHeaderSlot)
val sizeField = structGep(arrayHeaderSlot, 1, "count_")
store(count, sizeField)
return bitcast(kObjHeaderPtr, arrayHeaderSlot)
}
fun unreachable(): LLVMValueRef? {
if (context.config.debug) {
@@ -973,8 +1037,8 @@ internal class FunctionGenerationContext(val function: LLVMValueRef,
positionAtEnd(localsInitBb)
slotsPhi = phi(kObjHeaderPtrPtr)
// Is removed by DCE trivially, if not needed.
arenaSlot = intToPtr(
or(ptrToInt(slotsPhi, codegen.intPtrType), codegen.immOneIntPtrType), kObjHeaderPtrPtr)
/*arenaSlot = intToPtr(
or(ptrToInt(slotsPhi, codegen.intPtrType), codegen.immOneIntPtrType), kObjHeaderPtrPtr)*/
positionAtEnd(entryBb)
}
@@ -417,11 +417,6 @@ internal class IntrinsicGenerator(private val environment: IntrinsicGeneratorEnv
TODO("Implement me")
}
private fun extractConstUnsignedInt(value: LLVMValueRef): Long {
assert(LLVMIsConstant(value) != 0)
return LLVMConstIntGetZExtValue(value)
}
private fun FunctionGenerationContext.emitGetClassTypeInfo(callSite: IrCall): LLVMValueRef {
val typeArgument = callSite.getTypeArgument(0)!!
val typeArgumentClass = typeArgument.getClass()
@@ -2097,14 +2097,13 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
val thisValue = when {
constructedClass.isArray -> {
assert(args.isNotEmpty() && args[0].type == int32Type)
functionGenerationContext.allocArray(codegen.typeInfoValue(constructedClass), args[0],
functionGenerationContext.allocArray(constructedClass, args[0],
resultLifetime(callee), currentCodeContext.exceptionHandler)
}
constructedClass == context.ir.symbols.string.owner -> {
// TODO: consider returning the empty string literal instead.
assert(args.isEmpty())
functionGenerationContext.allocArray(codegen.typeInfoValue(constructedClass), count = kImmZero,
functionGenerationContext.allocArray(constructedClass, count = kImmZero,
lifetime = resultLifetime(callee), exceptionHandler = currentCodeContext.exceptionHandler)
}
@@ -204,6 +204,11 @@ internal fun ContextUtils.numParameters(functionType: LLVMTypeRef) : Int {
return LLVMCountParamTypes(LLVMGetElementType(functionType))
}
fun extractConstUnsignedInt(value: LLVMValueRef): Long {
assert(LLVMIsConstant(value) != 0)
return LLVMConstIntGetZExtValue(value)
}
internal fun ContextUtils.isObjectReturn(functionType: LLVMTypeRef) : Boolean {
// Note that type is usually function pointer, so we have to dereference it.
val returnType = LLVMGetReturnType(LLVMGetElementType(functionType))!!
@@ -129,7 +129,7 @@ internal class CallGraphBuilder(val context: Context,
is DataFlowIR.Node.ArrayRead ->
block(DataFlowIR.Node.Call(
callee = moduleDFG.symbolTable.mapFunction(arrayGet),
callee = node.callee,
arguments = listOf(node.array, node.index),
returnType = node.type,
irCallSite = null)
@@ -137,7 +137,7 @@ internal class CallGraphBuilder(val context: Context,
is DataFlowIR.Node.ArrayWrite ->
block(DataFlowIR.Node.Call(
callee = moduleDFG.symbolTable.mapFunction(arraySet),
callee = node.callee,
arguments = listOf(node.array, node.index, node.value),
returnType = moduleDFG.symbolTable.mapType(context.irBuiltIns.unitType),
irCallSite = null)
@@ -404,8 +404,8 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag
private val getContinuationSymbol = symbols.getContinuation
private val continuationType = getContinuationSymbol.owner.returnType
private val arrayGetSymbol = symbols.arrayGet[symbols.array]
private val arraySetSymbol = symbols.arraySet[symbols.array]
private val arrayGetSymbols = symbols.arrayGet.values
private val arraySetSymbols = symbols.arraySet.values
private val createUninitializedInstanceSymbol = symbols.createUninitializedInstance
private val initInstanceSymbol = symbols.initInstance
private val executeImplSymbol = symbols.executeImpl
@@ -574,7 +574,7 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag
if (value.value == null)
DataFlowIR.Node.Null
else
DataFlowIR.Node.Const(symbolTable.mapType(value.type))
DataFlowIR.Node.SimpleConst(symbolTable.mapType(value.type), value.value!!)
is IrGetObjectValue -> DataFlowIR.Node.Singleton(
symbolTable.mapType(value.type),
@@ -597,17 +597,30 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag
is IrCall -> when (value.symbol) {
getContinuationSymbol -> getContinuation()
arrayGetSymbol -> DataFlowIR.Node.ArrayRead(
array = expressionToEdge(value.dispatchReceiver!!),
index = expressionToEdge(value.getValueArgument(0)!!),
type = mapReturnType(value.type, context.irBuiltIns.anyType),
irCallSite = value)
in arrayGetSymbols -> {
val callee = value.symbol.owner
val actualCallee = (value.superQualifierSymbol?.owner?.getOverridingOf(callee)
?: callee).target
arraySetSymbol -> DataFlowIR.Node.ArrayWrite(
array = expressionToEdge(value.dispatchReceiver!!),
index = expressionToEdge(value.getValueArgument(0)!!),
value = expressionToEdge(value.getValueArgument(1)!!),
type = mapReturnType(value.getValueArgument(1)!!.type, context.irBuiltIns.anyType))
DataFlowIR.Node.ArrayRead(
symbolTable.mapFunction(actualCallee),
array = expressionToEdge(value.dispatchReceiver!!),
index = expressionToEdge(value.getValueArgument(0)!!),
type = mapReturnType(value.type, context.irBuiltIns.anyType),
irCallSite = value)
}
in arraySetSymbols -> {
val callee = value.symbol.owner
val actualCallee = (value.superQualifierSymbol?.owner?.getOverridingOf(callee)
?: callee).target
DataFlowIR.Node.ArrayWrite(
symbolTable.mapFunction(actualCallee),
array = expressionToEdge(value.dispatchReceiver!!),
index = expressionToEdge(value.getValueArgument(0)!!),
value = expressionToEdge(value.getValueArgument(1)!!),
type = mapReturnType(value.getValueArgument(1)!!.type, context.irBuiltIns.anyType))
}
createUninitializedInstanceSymbol ->
DataFlowIR.Node.AllocInstance(symbolTable.mapClassReferenceType(
@@ -565,22 +565,24 @@ internal object DFGSerializer {
}
}
class ArrayRead(val array: Edge, val index: Edge, val type: Int) {
class ArrayRead(val callee: Int, val array: Edge, val index: Edge, val type: Int) {
constructor(data: ArraySlice) : this(Edge(data), Edge(data), data.readInt())
constructor(data: ArraySlice) : this(data.readInt(), Edge(data), Edge(data), data.readInt())
fun write(result: ArraySlice) {
result.writeInt(callee)
array.write(result)
index.write(result)
result.writeInt(type)
}
}
class ArrayWrite(val array: Edge, val index: Edge, val value: Edge, val type: Int) {
class ArrayWrite(val callee: Int, val array: Edge, val index: Edge, val value: Edge, val type: Int) {
constructor(data: ArraySlice) : this(Edge(data), Edge(data), Edge(data), data.readInt())
constructor(data: ArraySlice) : this(data.readInt(), Edge(data), Edge(data), Edge(data), data.readInt())
fun write(result: ArraySlice) {
result.writeInt(callee)
array.write(result)
index.write(result)
value.write(result)
@@ -708,11 +710,11 @@ internal object DFGSerializer {
fun fieldWrite(receiver: Edge?, field: Field, value: Edge, type: Int) =
Node().also { it.fieldWrite = FieldWrite(receiver, field, value, type) }
fun arrayRead(array: Edge, index: Edge, type: Int) =
Node().also { it.arrayRead = ArrayRead(array, index, type) }
fun arrayRead(callee: Int, array: Edge, index: Edge, type: Int) =
Node().also { it.arrayRead = ArrayRead(callee, array, index, type) }
fun arrayWrite(array: Edge, index: Edge, value: Edge, type: Int) =
Node().also { it.arrayWrite = ArrayWrite(array, index, value, type) }
fun arrayWrite(callee: Int, array: Edge, index: Edge, value: Edge, type: Int) =
Node().also { it.arrayWrite = ArrayWrite(callee, array, index, value, type) }
fun variable(values: Array<Edge>, type: Int, kind: DataFlowIR.VariableKind) =
Node().also { it.variable = Variable(values, type, kind.ordinal.toByte()) }
@@ -948,10 +950,10 @@ internal object DFGSerializer {
Node.fieldWrite(node.receiver?.let { buildEdge(it) }, buildField(node.field), buildEdge(node.value), typeMap[node.type]!!)
is DataFlowIR.Node.ArrayRead ->
Node.arrayRead(buildEdge(node.array), buildEdge(node.index), typeMap[node.type]!!)
Node.arrayRead(functionSymbolMap[node.callee]!!, buildEdge(node.array), buildEdge(node.index), typeMap[node.type]!!)
is DataFlowIR.Node.ArrayWrite ->
Node.arrayWrite(buildEdge(node.array), buildEdge(node.index), buildEdge(node.value), typeMap[node.type]!!)
Node.arrayWrite(functionSymbolMap[node.callee]!!, buildEdge(node.array), buildEdge(node.index), buildEdge(node.value), typeMap[node.type]!!)
is DataFlowIR.Node.Variable ->
Node.variable(node.values.map { buildEdge(it) }.toTypedArray(), typeMap[node.type]!!, node.kind)
@@ -1213,12 +1215,12 @@ internal object DFGSerializer {
NodeType.ARRAY_READ -> {
val arrayRead = it.arrayRead!!
DataFlowIR.Node.ArrayRead(deserializeEdge(arrayRead.array), deserializeEdge(arrayRead.index), types[arrayRead.type], null)
DataFlowIR.Node.ArrayRead(functionSymbols[arrayRead.callee], deserializeEdge(arrayRead.array), deserializeEdge(arrayRead.index), types[arrayRead.type], null)
}
NodeType.ARRAY_WRITE -> {
val arrayWrite = it.arrayWrite!!
DataFlowIR.Node.ArrayWrite(deserializeEdge(arrayWrite.array), deserializeEdge(arrayWrite.index), deserializeEdge(arrayWrite.value), types[arrayWrite.type])
DataFlowIR.Node.ArrayWrite(functionSymbols[arrayWrite.callee], deserializeEdge(arrayWrite.array), deserializeEdge(arrayWrite.index), deserializeEdge(arrayWrite.value), types[arrayWrite.type])
}
NodeType.VARIABLE -> {
@@ -217,7 +217,9 @@ internal object DataFlowIR {
sealed class Node {
class Parameter(val index: Int) : Node()
class Const(val type: Type) : Node()
open class Const(val type: Type) : Node()
class SimpleConst<T : Any>(type: Type, val value: T) : Const(type)
object Null : Node()
@@ -255,9 +257,9 @@ internal object DataFlowIR {
class FieldWrite(val receiver: Edge?, val field: Field, val value: Edge, val type: Type) : Node()
class ArrayRead(val array: Edge, val index: Edge, val type: Type, val irCallSite: IrCall?) : Node()
class ArrayRead(val callee: FunctionSymbol, val array: Edge, val index: Edge, val type: Type, val irCallSite: IrCall?) : Node()
class ArrayWrite(val array: Edge, val index: Edge, val value: Edge, val type: Type) : Node()
class ArrayWrite(val callee: FunctionSymbol, val array: Edge, val index: Edge, val value: Edge, val type: Type) : Node()
class Variable(values: List<Edge>, val type: Type, val kind: VariableKind) : Node() {
val values = mutableListOf<Edge>().also { it += values }
@@ -506,7 +508,6 @@ internal object DataFlowIR {
val isFinal = irClass.isFinal()
val isAbstract = irClass.isAbstract()
val name = irClass.fqNameForIrSerialization.asString()
classMap[irClass]?.let { return it }
val placeToClassTable = true
@@ -0,0 +1,209 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
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.descriptors.isArrayWithFixedSizeItems
import org.jetbrains.kotlin.backend.konan.descriptors.isBuiltInOperator
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
NO_ESCAPE // object does not escape
}
class FunctionAnalyzer(val function: DataFlowIR.Function, val context: Context) {
// Escape states of nodes.
private val nodesEscapeStates = mutableMapOf<DataFlowIR.Node, EscapeState>()
// Connected objects map, escape state change of key node influences on all connected nodes states.
private val connectedObjects = mutableMapOf<DataFlowIR.Node, MutableSet<DataFlowIR.Node>>()
// Maximum size of array which is allowed to allocate on stack.
// TODO: replace into KonanConfigKeys?
private val stackAllocationArraySizeLimit = 64
private var DataFlowIR.Node.escapeState: EscapeState
set(value) {
// Write state only if it has more limitations.
val writeState = nodesEscapeStates[this]?.let {
value < it
} ?: true
if (writeState) {
nodesEscapeStates[this] = value
}
}
get() = nodesEscapeStates.getOrDefault(this, EscapeState.NO_ESCAPE)
private fun connectObjects(node: DataFlowIR.Node, connectedNode: DataFlowIR.Node) {
connectedObjects.getOrPut(node) { mutableSetOf() }.add(connectedNode)
}
private fun findOutArraySize(node: DataFlowIR.Node): Int? {
if (node is DataFlowIR.Node.SimpleConst<*>) {
return node.value as? Int
}
if (node is DataFlowIR.Node.Variable) {
// In case of several possible values, it's unknown what is used.
// TODO: if all values are constants which are less limit?
if (node.values.size == 1) {
return findOutArraySize(node.values.first().node)
}
}
return null
}
private fun evaluateEscapeState(node: DataFlowIR.Node) {
node.escapeState = EscapeState.NO_ESCAPE
when (node) {
is DataFlowIR.Node.Call -> {
val pointsToMasks = (0..node.callee.parameters.size)
.map { node.callee.pointsTo?.elementAtOrNull(it) ?: 0 }
val returnPointsToMask = pointsToMasks[node.callee.parameters.size]
node.arguments.forEachIndexed { index, arg ->
// Check information about arguments escaping.
val escapes = node.callee.escapes?.let {
it and (1 shl index) != 0
} ?: node.callee !is DataFlowIR.FunctionSymbol.External
// Connect with all arguments that return value points to.
if (returnPointsToMask and (1 shl index) != 0) {
connectObjects(node, arg.node)
}
// Connect current argument with other it points to.
(0..node.callee.parameters.size).filter { pointsToMasks[index] and (1 shl it) != 0 }.forEach {
if (it == node.callee.parameters.size) {
// Argument points to this.
connectObjects(arg.node, node)
} else {
connectObjects(arg.node, node.arguments[it].node)
}
}
arg.node.escapeState = if (escapes) EscapeState.ARG_ESCAPE else EscapeState.NO_ESCAPE
}
// Check size for array allocation.
if (node is DataFlowIR.Node.NewObject && node.constructedType is DataFlowIR.Type.Declared) {
node.constructedType.irClass?.let { irClass ->
// Work only with arrays which elements size is known.
if (irClass.isArrayWithFixedSizeItems) {
val sizeArgument = node.arguments.first().node
val arraySize = findOutArraySize(sizeArgument)
if (arraySize == null || arraySize > stackAllocationArraySizeLimit) {
node.escapeState = EscapeState.GLOBAL_ESCAPE
}
} else {
node.escapeState = EscapeState.GLOBAL_ESCAPE
}
}
}
}
is DataFlowIR.Node.Singleton -> {
node.escapeState = EscapeState.GLOBAL_ESCAPE
}
is DataFlowIR.Node.FieldRead -> {
node.receiver?.let { obj ->
connectObjects(node, obj.node)
} ?: run { node.escapeState = EscapeState.GLOBAL_ESCAPE }
}
is DataFlowIR.Node.FieldWrite -> {
node.receiver?.let { obj ->
connectObjects(obj.node, node.value.node)
} ?: run {
node.escapeState = EscapeState.GLOBAL_ESCAPE
node.value.node.escapeState = EscapeState.GLOBAL_ESCAPE
}
}
is DataFlowIR.Node.ArrayWrite -> {
connectObjects(node.array.node, node.value.node)
}
is DataFlowIR.Node.Variable -> {
node.values.forEach {
connectObjects(node, it.node)
}
}
is DataFlowIR.Node.ArrayRead -> {
// If element of array escapes then array also should escape.
connectObjects(node, node.array.node)
}
}
}
private inner class ConnectedObjectsVisitor {
val visitedObjects = mutableSetOf<DataFlowIR.Node>()
fun visit(node: DataFlowIR.Node, action: (DataFlowIR.Node) -> Unit) {
action(node)
visitedObjects.add(node)
connectedObjects[node]?.forEach {
if (!visitedObjects.contains(it)) {
visit(it, action)
}
}
}
}
private fun propagateState(state: EscapeState, visitor: ConnectedObjectsVisitor) {
connectedObjects.filter { it.key.escapeState == state }.forEach { (key, value) ->
value.forEach { obj ->
visitor.visit(obj) { it.escapeState = key.escapeState }
}
}
}
fun analyze(lifetimes: MutableMap<IrElement, Lifetime>) {
function.body.nodes.forEach { node ->
evaluateEscapeState(node)
}
function.body.returns.escapeState = EscapeState.ARG_ESCAPE
function.body.throws.escapeState = EscapeState.ARG_ESCAPE
// Change state of connected objects.
val visitor = ConnectedObjectsVisitor()
propagateState(EscapeState.GLOBAL_ESCAPE, visitor)
propagateState(EscapeState.ARG_ESCAPE, visitor)
nodesEscapeStates.filter {
it.value == EscapeState.NO_ESCAPE
}.forEach { (irNode, _) ->
val ir = when (irNode) {
is DataFlowIR.Node.Call -> irNode.irCallSite
else -> null
}
ir?.let {
lifetimes.put(it, Lifetime.LOCAL)
DEBUG_OUTPUT(3) { println("${ir} does not escape") }
}
}
}
}
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()
}
FunctionAnalyzer(function, context).analyze(lifetimes)
}
}
fun computeLifetimes(context: Context, moduleDFG: ModuleDFG, lifetimes: MutableMap<IrElement, Lifetime>) {
DEBUG_OUTPUT(1) { println("In local EA") }
assert(lifetimes.isEmpty())
analyze(context, moduleDFG, lifetimes)
}
}
+2 -1
View File
@@ -204,7 +204,8 @@ class RingLauncher : Launcher() {
"CoordinatesSolver.solve" to BenchmarkEntryWithInit.create(::CoordinatesSolverBenchmark, { solve() }),
"GraphSolver.solve" to BenchmarkEntryWithInit.create(::GraphSolverBenchmark, { solve() }),
"Casts.classCast" to BenchmarkEntryWithInit.create(::CastsBenchmark, { classCast() }),
"Casts.interfaceCast" to BenchmarkEntryWithInit.create(::CastsBenchmark, { interfaceCast() })
"Casts.interfaceCast" to BenchmarkEntryWithInit.create(::CastsBenchmark, { interfaceCast() }),
"LocalObjects.localArray" to BenchmarkEntryWithInit.create(::LocalObjectsBenchmark, { localArray() })
)
)
}
@@ -0,0 +1,38 @@
/*
* 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.ring
import org.jetbrains.benchmarksLauncher.Blackhole
open class LocalObjectsBenchmark {
//Benchmark
fun localArray(): Int {
val size = 48
val array = IntArray(size)
for (i in 1..size) {
array[i - 1] = i * 2
}
var result = 0
for (i in 0 until size) {
result += array[i]
}
if (result > 10) {
return 1
}
return 2
}
}
+1 -1
View File
@@ -29,7 +29,7 @@ namespace {
ALWAYS_INLINE inline void mutabilityCheck(KConstRef thiz) {
// TODO: optimize it!
if (thiz->container()->frozen()) {
if (thiz->container() != nullptr && thiz->container()->frozen()) {
ThrowInvalidMutabilityException(thiz);
}
}
+2
View File
@@ -12,6 +12,7 @@ import kotlin.internal.PureReifiable
import kotlin.native.internal.ExportTypeInfo
import kotlin.native.internal.IntrinsicType
import kotlin.native.internal.TypedIntrinsic
import kotlin.native.internal.PointsTo
/**
* An array of bytes.
@@ -527,6 +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.
public external inline fun <reified @PureReifiable T> arrayOf(vararg elements: T): Array<T>
@SymbolName("Kotlin_emptyArray")