From 60746d043838801f7e9ef90c5df8f758e0e7a722 Mon Sep 17 00:00:00 2001 From: LepilkinaElena Date: Mon, 9 Dec 2019 15:37:24 +0300 Subject: [PATCH] Local escape analysis (#3625) --- .../jetbrains/kotlin/backend/konan/Context.kt | 2 + .../kotlin/backend/konan/ToplevelPhases.kt | 2 + .../konan/descriptors/DescriptorUtils.kt | 12 + .../backend/konan/llvm/BitcodePhases.kt | 9 + .../backend/konan/llvm/CodeGenerator.kt | 108 +++++++-- .../backend/konan/llvm/IntrinsicGenerator.kt | 5 - .../kotlin/backend/konan/llvm/IrToBitcode.kt | 5 +- .../kotlin/backend/konan/llvm/LlvmUtils.kt | 5 + .../konan/optimizations/CallGraphBuilder.kt | 4 +- .../backend/konan/optimizations/DFGBuilder.kt | 39 ++-- .../konan/optimizations/DFGSerializer.kt | 26 ++- .../backend/konan/optimizations/DataFlowIR.kt | 9 +- .../optimizations/LocalEscapeAnalysis.kt | 209 ++++++++++++++++++ performance/ring/src/main/kotlin/main.kt | 3 +- .../jetbrains/ring/LocalObjectsBenchmark.kt | 38 ++++ runtime/src/main/cpp/Arrays.cpp | 2 +- runtime/src/main/kotlin/kotlin/Arrays.kt | 2 + 17 files changed, 417 insertions(+), 63 deletions(-) create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/LocalEscapeAnalysis.kt create mode 100644 performance/ring/src/main/kotlin/org/jetbrains/ring/LocalObjectsBenchmark.kt diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt index 16aca5f1831..a77f12aa995 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt @@ -485,6 +485,8 @@ internal class Context(config: KonanConfig) : KonanBackendContext(config) { producingCache = config.produce.isCache, librariesToCache = config.librariesToCache ) + + val declaredLocalArrays: MutableMap = HashMap() } private fun MemberScope.getContributedClassifier(name: String) = diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ToplevelPhases.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ToplevelPhases.kt index 8baa897d45e..214f413ec13 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ToplevelPhases.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ToplevelPhases.kt @@ -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)) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt index 57681634671..9911eefd0c4 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt @@ -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 diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BitcodePhases.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BitcodePhases.kt index e2ac06dd199..a6f37985101 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BitcodePhases.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BitcodePhases.kt @@ -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", diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt index d1353a45066..f9a6f47a5eb 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt @@ -190,7 +190,7 @@ internal class FunctionGenerationContext(val function: LLVMValueRef, val vars = VariableManager(this) private val basicBlockToLastLocation = mutableMapOf() - 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() + // TODO: remove if exactly unused. + //private var arenaSlot: LLVMValueRef? = null + private val slotToVariableLocation = mutableMapOf() - 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, 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) } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IntrinsicGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IntrinsicGenerator.kt index 6d220697bfa..61e560d111f 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IntrinsicGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IntrinsicGenerator.kt @@ -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() diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt index 38038845791..ee8094879da 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt @@ -2097,14 +2097,13 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map { 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) } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmUtils.kt index 1ccee5a965b..64553f83f7f 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmUtils.kt @@ -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))!! diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/CallGraphBuilder.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/CallGraphBuilder.kt index 54bf13b3bb3..9baf79a4f2f 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/CallGraphBuilder.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/CallGraphBuilder.kt @@ -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) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGBuilder.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGBuilder.kt index 87876e18477..4dedbda9ceb 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGBuilder.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGBuilder.kt @@ -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( diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGSerializer.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGSerializer.kt index fe0590fa36a..b1c1a8774cc 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGSerializer.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGSerializer.kt @@ -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, 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 -> { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DataFlowIR.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DataFlowIR.kt index 111f1c87228..157f21e2261 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DataFlowIR.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DataFlowIR.kt @@ -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(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, val type: Type, val kind: VariableKind) : Node() { val values = mutableListOf().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 diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/LocalEscapeAnalysis.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/LocalEscapeAnalysis.kt new file mode 100644 index 00000000000..edc3b4adcfd --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/LocalEscapeAnalysis.kt @@ -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() + // Connected objects map, escape state change of key node influences on all connected nodes states. + private val connectedObjects = mutableMapOf>() + // 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() + + 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) { + 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) { + 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) { + DEBUG_OUTPUT(1) { println("In local EA") } + assert(lifetimes.isEmpty()) + analyze(context, moduleDFG, lifetimes) + } +} \ No newline at end of file diff --git a/performance/ring/src/main/kotlin/main.kt b/performance/ring/src/main/kotlin/main.kt index 0dd783bcd79..c9d7f1582ce 100644 --- a/performance/ring/src/main/kotlin/main.kt +++ b/performance/ring/src/main/kotlin/main.kt @@ -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() }) ) ) } diff --git a/performance/ring/src/main/kotlin/org/jetbrains/ring/LocalObjectsBenchmark.kt b/performance/ring/src/main/kotlin/org/jetbrains/ring/LocalObjectsBenchmark.kt new file mode 100644 index 00000000000..01f421f5a9b --- /dev/null +++ b/performance/ring/src/main/kotlin/org/jetbrains/ring/LocalObjectsBenchmark.kt @@ -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 + } +} diff --git a/runtime/src/main/cpp/Arrays.cpp b/runtime/src/main/cpp/Arrays.cpp index aa261dff4cb..c41030c2b50 100644 --- a/runtime/src/main/cpp/Arrays.cpp +++ b/runtime/src/main/cpp/Arrays.cpp @@ -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); } } diff --git a/runtime/src/main/kotlin/kotlin/Arrays.kt b/runtime/src/main/kotlin/kotlin/Arrays.kt index 3c84d237b08..d0eb2ad4f67 100644 --- a/runtime/src/main/kotlin/kotlin/Arrays.kt +++ b/runtime/src/main/kotlin/kotlin/Arrays.kt @@ -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 arrayOfNulls(size: Int): Array * Returns an array containing the specified elements. */ @TypedIntrinsic(IntrinsicType.IDENTITY) +@PointsTo(0, 1) // points to argument. public external inline fun arrayOf(vararg elements: T): Array @SymbolName("Kotlin_emptyArray")