Local escape analysis (#3625)
This commit is contained in:
+2
@@ -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) =
|
||||
|
||||
+2
@@ -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))
|
||||
|
||||
+12
@@ -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
|
||||
|
||||
|
||||
+9
@@ -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",
|
||||
|
||||
+86
-22
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
-5
@@ -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()
|
||||
|
||||
+2
-3
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
+5
@@ -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))!!
|
||||
|
||||
+2
-2
@@ -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)
|
||||
|
||||
+26
-13
@@ -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(
|
||||
|
||||
+14
-12
@@ -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 -> {
|
||||
|
||||
+5
-4
@@ -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
|
||||
|
||||
+209
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user