From bdbb161cfa94f6767f18ad252a65f6db6999fe4f Mon Sep 17 00:00:00 2001 From: Dmitry Savvinov Date: Tue, 3 Oct 2017 15:02:44 +0300 Subject: [PATCH] Effects: support called-in-place lambdas Support initialization of local variables in capture, if that capture is anonymous lambda that was passed to function which explicitly expressed its intent to call closure "here and now" Changes: - LocalFunctionDeclarationInstruction made open - Introduce InlinedLocalFunctionDeclarationInstruction, which is subtype of LocalFunctionDeclarationInstruction with additional semantic: it is statically known that this function will be called in-place and maybe with some definite amount of invocations. - Next-instruction of InlinedLocalFunctionDeclarationInstruction depends on whether flow can exit its body normally, i.e. on wheter EXIT is reachable. If flow can reach EXIT, then .next is just straight next instruction, otherwise it's SINK. - Take non-local instructions into consideration when analyzing reachability. We didn't it before because no one ever needed proper analysis. Now we have InlinedLocalFunctionDeclarationInstruction which cares about proper reachability of EXIT. - Pull control-flow information from InlinedLocalFunctionDeclarationInstruction's EXIT into parent's pseudocode, thus allowing it to participate in initialization/use analysis. - Change logic of 'isCapturedWrite' to not report "CAPTURED_VAL_INITIALIZATION" if value is captured by the inline-lambda. ========== Introduction of EffectSystem: 12/18 --- .../kotlin/cfg/ControlFlowBuilder.kt | 8 ++- .../kotlin/cfg/ControlFlowBuilderAdapter.kt | 12 +++- .../cfg/ControlFlowInformationProvider.kt | 10 ++- .../kotlin/cfg/ControlFlowProcessor.kt | 67 ++++++++++++++++--- .../kotlin/cfg/PseudocodeTraverser.kt | 10 ++- .../ControlFlowInstructionsGenerator.kt | 39 +++++++---- .../kotlin/cfg/pseudocode/Pseudocode.kt | 1 + .../kotlin/cfg/pseudocode/PseudocodeImpl.kt | 56 ++++++++-------- .../instructions/InstructionVisitor.kt | 4 ++ .../InstructionVisitorWithResult.kt | 3 + ...inedLocalFunctionDeclarationInstruction.kt | 40 +++++++++++ .../LocalFunctionDeclarationInstruction.kt | 2 +- 12 files changed, 194 insertions(+), 58 deletions(-) create mode 100644 compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/instructions/special/InlinedLocalFunctionDeclarationInstruction.kt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowBuilder.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowBuilder.kt index a92dea36fc4..bed554d724b 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowBuilder.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowBuilder.kt @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.cfg import org.jetbrains.kotlin.cfg.pseudocode.PseudoValue import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.* +import org.jetbrains.kotlin.contracts.description.InvocationKind import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall @@ -27,9 +28,8 @@ import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue interface ControlFlowBuilder { // Subroutines - fun enterSubroutine(subroutine: KtElement) - - fun exitSubroutine(subroutine: KtElement): Pseudocode + fun enterSubroutine(subroutine: KtElement, invocationKind: InvocationKind? = null) + fun exitSubroutine(subroutine: KtElement, invocationKind: InvocationKind? = null): Pseudocode val currentSubroutine: KtElement val returnSubroutine: KtElement @@ -49,6 +49,8 @@ interface ControlFlowBuilder { fun declareVariable(property: KtVariableDeclaration) fun declareFunction(subroutine: KtElement, pseudocode: Pseudocode) + fun declareInlinedFunction(subroutine: KtElement, pseudocode: Pseudocode, invocationKind: InvocationKind) + fun declareEntryOrObject(entryOrObject: KtClassOrObject) // Labels diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowBuilderAdapter.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowBuilderAdapter.kt index b71dbba01c5..4957f148fcc 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowBuilderAdapter.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowBuilderAdapter.kt @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.cfg import org.jetbrains.kotlin.cfg.pseudocode.PseudoValue import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.* +import org.jetbrains.kotlin.contracts.description.InvocationKind import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall @@ -134,11 +135,12 @@ abstract class ControlFlowBuilderAdapter : ControlFlowBuilder { delegateBuilder.exitTryFinally() } - override fun enterSubroutine(subroutine: KtElement) { - delegateBuilder.enterSubroutine(subroutine) + override fun enterSubroutine(subroutine: KtElement, invocationKind: InvocationKind?) { + delegateBuilder.enterSubroutine(subroutine, invocationKind) } - override fun exitSubroutine(subroutine: KtElement): Pseudocode = delegateBuilder.exitSubroutine(subroutine) + override fun exitSubroutine(subroutine: KtElement, invocationKind: InvocationKind?): Pseudocode = + delegateBuilder.exitSubroutine(subroutine, invocationKind) override val currentSubroutine: KtElement get() = delegateBuilder.currentSubroutine @@ -178,6 +180,10 @@ abstract class ControlFlowBuilderAdapter : ControlFlowBuilder { delegateBuilder.declareFunction(subroutine, pseudocode) } + override fun declareInlinedFunction(subroutine: KtElement, pseudocode: Pseudocode, invocationKind: InvocationKind) { + delegateBuilder.declareInlinedFunction(subroutine, pseudocode, invocationKind) + } + override fun declareEntryOrObject(entryOrObject: KtClassOrObject) { delegateBuilder.declareEntryOrObject(entryOrObject) } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowInformationProvider.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowInformationProvider.kt index 94af80a4eb0..9c2f3afa34b 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowInformationProvider.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowInformationProvider.kt @@ -369,7 +369,8 @@ class ControlFlowInformationProvider private constructor( // Do not consider top-level properties if (containingDeclarationDescriptor is PackageFragmentDescriptor) return false var parentDeclaration = getElementParentDeclaration(writeValueInstruction.element) - while (true) { + + loop@ while (true) { val context = trace.bindingContext val parentDescriptor = getDeclarationDescriptorIncludingConstructors(context, parentDeclaration) if (parentDescriptor == containingDeclarationDescriptor) { @@ -381,6 +382,13 @@ class ControlFlowInformationProvider private constructor( parentDeclaration = getElementParentDeclaration(parentDeclaration) } is KtDeclarationWithBody -> { + // If it is captured write in lambda that is called in-place, then skip it (treat as parent) + val maybeEnclosingLambdaExpr = parentDeclaration.parent + if (maybeEnclosingLambdaExpr is KtLambdaExpression && trace[BindingContext.LAMBDA_INVOCATIONS, maybeEnclosingLambdaExpr] != null) { + parentDeclaration = getElementParentDeclaration(parentDeclaration) + continue@loop + } + if (parentDeclaration is KtFunction && parentDeclaration.isLocal) return true // miss non-local function or accessor just once parentDeclaration = getElementParentDeclaration(parentDeclaration) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowProcessor.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowProcessor.kt index 081718f7685..3afaeb581d8 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowProcessor.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowProcessor.kt @@ -30,6 +30,9 @@ import org.jetbrains.kotlin.cfg.pseudocode.PseudocodeImpl import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.AccessTarget import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.InstructionWithValue import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.MagicKind +import org.jetbrains.kotlin.contracts.description.InvocationKind +import org.jetbrains.kotlin.contracts.description.canBeRevisited +import org.jetbrains.kotlin.contracts.description.isDefinitelyVisited import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor import org.jetbrains.kotlin.diagnostics.Errors.* @@ -58,18 +61,20 @@ import org.jetbrains.kotlin.types.expressions.OperatorConventions import org.jetbrains.kotlin.util.slicedMap.ReadOnlySlice import java.util.* +typealias DeferredGenerator = (ControlFlowBuilder) -> Unit + class ControlFlowProcessor(private val trace: BindingTrace) { private val builder: ControlFlowBuilder = ControlFlowInstructionsGenerator() fun generatePseudocode(subroutine: KtElement): Pseudocode { - val pseudocode = generate(subroutine) + val pseudocode = generate(subroutine, null) (pseudocode as PseudocodeImpl).postProcess() return pseudocode } - private fun generate(subroutine: KtElement): Pseudocode { - builder.enterSubroutine(subroutine) + private fun generate(subroutine: KtElement, invocationKind: InvocationKind? = null): Pseudocode { + builder.enterSubroutine(subroutine, invocationKind) val cfpVisitor = CFPVisitor(builder) if (subroutine is KtDeclarationWithBody && subroutine !is KtSecondaryConstructor) { val valueParameters = subroutine.valueParameters @@ -87,7 +92,7 @@ class ControlFlowProcessor(private val trace: BindingTrace) { else { cfpVisitor.generateInstructions(subroutine) } - return builder.exitSubroutine(subroutine) + return builder.exitSubroutine(subroutine, invocationKind) } private fun generateImplicitReturnValue(bodyExpression: KtExpression, subroutine: KtElement) { @@ -105,7 +110,7 @@ class ControlFlowProcessor(private val trace: BindingTrace) { val afterDeclaration = builder.createUnboundLabel("after local declaration") builder.nondeterministicJump(afterDeclaration, subroutine, null) - generate(subroutine) + generate(subroutine, null) builder.bindLabel(afterDeclaration) } @@ -115,6 +120,13 @@ class ControlFlowProcessor(private val trace: BindingTrace) { private val catchFinallyStack = Stack() + // Some language constructs (e.g. inlined lambdas) should be partially processed before call + // (to provide argument for call itself), and partially - after (in case of inlined lambdas, + // their body should be generated after call). To do so, we store deferred generators, which + // will be called after call instruction is emitted. + // Stack is necessary to store generators across nested calls + private val deferredGeneratorsStack = Stack>() + private val conditionVisitor = object : KtVisitorVoid() { private fun getSubjectExpression(condition: KtWhenCondition): KtExpression? = @@ -1020,14 +1032,42 @@ class ControlFlowProcessor(private val trace: BindingTrace) { return parent.parent is KtDoWhileExpression } - private fun visitFunction(function: KtFunction) { - processLocalDeclaration(function) + private fun visitFunction(function: KtFunction, invocationKind: InvocationKind? = null) { + if (invocationKind == null) { + processLocalDeclaration(function) + } + else { + visitInlinedFunction(function, invocationKind) + } + val isAnonymousFunction = function is KtFunctionLiteral || function.name == null if (isAnonymousFunction || function.isLocal && function.parent !is KtBlockExpression) { builder.createLambda(function) } } + private fun visitInlinedFunction(lambdaFunctionLiteral: KtFunction, invocationKind: InvocationKind) { + // Defer emitting of inlined declaration + deferredGeneratorsStack.peek().add({ builder -> + val beforeDeclaration = builder.createUnboundLabel("before inlined declaration") + val afterDeclaration = builder.createUnboundLabel("after inlined declaration") + + builder.bindLabel(beforeDeclaration) + + if (!invocationKind.isDefinitelyVisited()) { + builder.nondeterministicJump(afterDeclaration, lambdaFunctionLiteral, null) + } + + generate(lambdaFunctionLiteral, invocationKind) + + if (invocationKind.canBeRevisited()) { + builder.nondeterministicJump(beforeDeclaration, lambdaFunctionLiteral, null) + } + + builder.bindLabel(afterDeclaration) + }) + } + override fun visitNamedFunction(function: KtNamedFunction) { visitFunction(function) } @@ -1035,7 +1075,11 @@ class ControlFlowProcessor(private val trace: BindingTrace) { override fun visitLambdaExpression(lambdaExpression: KtLambdaExpression) { mark(lambdaExpression) val functionLiteral = lambdaExpression.functionLiteral - visitFunction(functionLiteral) + + // NB. Behaviour here is implicitly controlled by the LanguageFeature 'CallsInPlaceEffect' + // If this feature is turned off, then slice LAMBDA_INVOCATIONS is never written and invocationKind + // in all subsequent calls always 'null', resulting in falling back to old behaviour + visitFunction(functionLiteral, trace[BindingContext.LAMBDA_INVOCATIONS, lambdaExpression]) copyValue(functionLiteral, lambdaExpression) } @@ -1485,6 +1529,8 @@ class ControlFlowProcessor(private val trace: BindingTrace) { val receivers = getReceiverValues(resolvedCall) + deferredGeneratorsStack.push(mutableListOf()) + var parameterValues = SmartFMap.emptyMap() for (argument in resolvedCall.call.valueArguments) { val argumentMapping = resolvedCall.getArgumentMapping(argument) @@ -1507,7 +1553,10 @@ class ControlFlowProcessor(private val trace: BindingTrace) { } mark(resolvedCall.call.callElement) - return builder.call(callElement, resolvedCall, receivers, parameterValues) + val callInstruction = builder.call(callElement, resolvedCall, receivers, parameterValues) + val deferredGeneratorsForCall = deferredGeneratorsStack.pop() + deferredGeneratorsForCall.forEach { it.invoke(builder) } + return callInstruction } private fun getReceiverValues(resolvedCall: ResolvedCall<*>): Map { diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/PseudocodeTraverser.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/PseudocodeTraverser.kt index 2cf67c49703..5d066880518 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/PseudocodeTraverser.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/PseudocodeTraverser.kt @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.cfg.pseudocodeTraverser import org.jetbrains.kotlin.cfg.ControlFlowInfo import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode import org.jetbrains.kotlin.cfg.pseudocode.instructions.Instruction +import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.InlinedLocalFunctionDeclarationInstruction import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.LocalFunctionDeclarationInstruction import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.SubroutineEnterInstruction import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.SubroutineSinkInstruction @@ -96,8 +97,13 @@ private fun > Pseudocode.collectDataFromSubgraph( if (instruction is LocalFunctionDeclarationInstruction) { val subroutinePseudocode = instruction.body subroutinePseudocode.collectDataFromSubgraph( - traversalOrder, edgesMap, mergeEdges, updateEdge, previousInstructions, changed, true) - val lastInstruction = subroutinePseudocode.getLastInstruction(traversalOrder) + traversalOrder, edgesMap, mergeEdges, updateEdge, previousInstructions, changed, true + ) + // Special case for inlined functions: take flow from EXIT instructions (it contains flow which exits declaration normally) + val lastInstruction = if (instruction is InlinedLocalFunctionDeclarationInstruction && traversalOrder == FORWARD) + subroutinePseudocode.exitInstruction + else + subroutinePseudocode.getLastInstruction(traversalOrder) val previousValue = edgesMap[instruction] val newValue = edgesMap[lastInstruction] val updatedValue = newValue?.let { diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/ControlFlowInstructionsGenerator.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/ControlFlowInstructionsGenerator.kt index 487edc5d865..fba85f518ed 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/ControlFlowInstructionsGenerator.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/ControlFlowInstructionsGenerator.kt @@ -24,6 +24,7 @@ import org.jetbrains.kotlin.cfg.pseudocode.instructions.Instruction import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.* import org.jetbrains.kotlin.cfg.pseudocode.instructions.jumps.* import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.* +import org.jetbrains.kotlin.contracts.description.InvocationKind import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall @@ -47,8 +48,8 @@ class ControlFlowInstructionsGenerator : ControlFlowBuilderAdapter() { private val allBlocks = Stack() - private fun pushBuilder(scopingElement: KtElement, subroutine: KtElement) { - val worker = ControlFlowInstructionsGeneratorWorker(scopingElement, subroutine) + private fun pushBuilder(scopingElement: KtElement, subroutine: KtElement, shouldInline: Boolean) { + val worker = ControlFlowInstructionsGeneratorWorker(scopingElement, subroutine, shouldInline) builders.push(worker) builder = worker } @@ -64,32 +65,42 @@ class ControlFlowInstructionsGenerator : ControlFlowBuilderAdapter() { return worker } - override fun enterSubroutine(subroutine: KtElement) { + override fun enterSubroutine(subroutine: KtElement, invocationKind: InvocationKind?) { val builder = builder + val shouldInlnie = invocationKind != null if (builder != null && subroutine is KtFunctionLiteral) { - pushBuilder(subroutine, builder.returnSubroutine) + pushBuilder(subroutine, builder.returnSubroutine, shouldInlnie) } else { - pushBuilder(subroutine, subroutine) + pushBuilder(subroutine, subroutine, shouldInlnie) } delegateBuilder.enterBlockScope(subroutine) delegateBuilder.enterSubroutine(subroutine) } - override fun exitSubroutine(subroutine: KtElement): Pseudocode { - super.exitSubroutine(subroutine) + override fun exitSubroutine(subroutine: KtElement, invocationKind: InvocationKind?): Pseudocode { + super.exitSubroutine(subroutine, invocationKind) delegateBuilder.exitBlockScope(subroutine) val worker = popBuilder() if (!builders.empty()) { val builder = builders.peek() - builder.declareFunction(subroutine, worker.pseudocode) + if (invocationKind == null) { + builder.declareFunction(subroutine, worker.pseudocode) + } + else { + builder.declareInlinedFunction(subroutine, worker.pseudocode, invocationKind) + } } return worker.pseudocode } - private inner class ControlFlowInstructionsGeneratorWorker(scopingElement: KtElement, override val returnSubroutine: KtElement) : ControlFlowBuilder { + private inner class ControlFlowInstructionsGeneratorWorker( + scopingElement: KtElement, + override val returnSubroutine: KtElement, + shouldInline: Boolean + ) : ControlFlowBuilder { - val pseudocode: PseudocodeImpl = PseudocodeImpl(scopingElement) + val pseudocode: PseudocodeImpl = PseudocodeImpl(scopingElement, shouldInline) private val error: Label = pseudocode.createLabel("error", null) private val sink: Label = pseudocode.createLabel("sink", null) @@ -145,7 +156,7 @@ class ControlFlowInstructionsGenerator : ControlFlowBuilderAdapter() { override val currentLoop: KtLoopExpression? get() = if (loopInfo.empty()) null else loopInfo.peek().element - override fun enterSubroutine(subroutine: KtElement) { + override fun enterSubroutine(subroutine: KtElement, invocationKind: InvocationKind?) { val blockInfo = SubroutineInfo( subroutine, /* entry point */ createUnboundLabel(), @@ -204,7 +215,7 @@ class ControlFlowInstructionsGenerator : ControlFlowBuilderAdapter() { } } - override fun exitSubroutine(subroutine: KtElement): Pseudocode { + override fun exitSubroutine(subroutine: KtElement, invocationKind: InvocationKind?): Pseudocode { getSubroutineExitPoint(subroutine)?.let { bindLabel(it) } pseudocode.addExitInstruction(SubroutineExitInstruction(subroutine, currentScope, false)) bindLabel(error) @@ -261,6 +272,10 @@ class ControlFlowInstructionsGenerator : ControlFlowBuilderAdapter() { add(LocalFunctionDeclarationInstruction(subroutine, pseudocode, currentScope)) } + override fun declareInlinedFunction(subroutine: KtElement, pseudocode: Pseudocode, invocationKind: InvocationKind) { + add(InlinedLocalFunctionDeclarationInstruction(subroutine, pseudocode, currentScope, invocationKind)) + } + override fun declareEntryOrObject(entryOrObject: KtClassOrObject) { add(VariableDeclarationInstruction(entryOrObject, currentScope)) } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/Pseudocode.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/Pseudocode.kt index 7ab4903baf6..12820e0fd8d 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/Pseudocode.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/Pseudocode.kt @@ -45,6 +45,7 @@ interface Pseudocode { val enterInstruction: SubroutineEnterInstruction + val isInlined: Boolean val containsDoWhile: Boolean val rootPseudocode: Pseudocode diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/PseudocodeImpl.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/PseudocodeImpl.kt index cb7972476cf..82647f1afaf 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/PseudocodeImpl.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/PseudocodeImpl.kt @@ -27,10 +27,7 @@ import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.MergeInstruction import org.jetbrains.kotlin.cfg.pseudocode.instructions.jumps.AbstractJumpInstruction import org.jetbrains.kotlin.cfg.pseudocode.instructions.jumps.ConditionalJumpInstruction import org.jetbrains.kotlin.cfg.pseudocode.instructions.jumps.NondeterministicJumpInstruction -import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.LocalFunctionDeclarationInstruction -import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.SubroutineEnterInstruction -import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.SubroutineExitInstruction -import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.SubroutineSinkInstruction +import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.* import org.jetbrains.kotlin.cfg.pseudocodeTraverser.TraversalOrder.BACKWARD import org.jetbrains.kotlin.cfg.pseudocodeTraverser.TraversalOrder.FORWARD import org.jetbrains.kotlin.cfg.pseudocodeTraverser.TraverseInstructionResult @@ -38,7 +35,7 @@ import org.jetbrains.kotlin.cfg.pseudocodeTraverser.traverseFollowingInstruction import org.jetbrains.kotlin.psi.KtElement import java.util.* -class PseudocodeImpl(override val correspondingElement: KtElement) : Pseudocode { +class PseudocodeImpl(override val correspondingElement: KtElement, override val isInlined: Boolean) : Pseudocode { internal val mutableInstructionList = ArrayList() override val instructions = ArrayList() @@ -56,6 +53,8 @@ class PseudocodeImpl(override val correspondingElement: KtElement) : Pseudocode getLocalDeclarations(this) } + val reachableInstructions = hashSetOf() + private val representativeInstructions = HashMap() private val labels = ArrayList() @@ -110,7 +109,7 @@ class PseudocodeImpl(override val correspondingElement: KtElement) : Pseudocode override val reversedInstructions: List get() { val traversedInstructions = linkedSetOf() - traverseFollowingInstructions(sinkInstruction, traversedInstructions, BACKWARD, null) + traverseFollowingInstructions(if (this.isInlined) instructions.last() else sinkInstruction, traversedInstructions, BACKWARD, null) if (traversedInstructions.size < instructions.size) { val simplyReversedInstructions = instructions.reversed() for (instruction in simplyReversedInstructions) { @@ -224,22 +223,17 @@ class PseudocodeImpl(override val correspondingElement: KtElement) : Pseudocode postPrecessed = true errorInstruction.sink = sinkInstruction exitInstruction.sink = sinkInstruction + for ((index, instruction) in mutableInstructionList.withIndex()) { - //recursively invokes 'postProcess' for local declarations + //recursively invokes 'postProcess' for local declarations, thus it needs global set of reachable instructions instruction.processInstruction(index) } - if (parent != null) return - // Collecting reachable instructions should be done after processing all instructions - // (including instructions in local declarations) to avoid being in incomplete state. collectAndCacheReachableInstructions() - for (localFunctionDeclarationInstruction in localDeclarations) { - (localFunctionDeclarationInstruction.body as PseudocodeImpl).collectAndCacheReachableInstructions() - } } private fun collectAndCacheReachableInstructions() { - val reachableInstructions = collectReachableInstructions() + collectReachableInstructions() for (instruction in mutableInstructionList) { if (reachableInstructions.contains(instruction)) { instructions.add(instruction) @@ -287,6 +281,14 @@ class PseudocodeImpl(override val correspondingElement: KtElement) : Pseudocode instruction.next = sinkInstruction } + override fun visitInlinedLocalFunctionDeclarationInstruction(instruction: InlinedLocalFunctionDeclarationInstruction) { + val body = instruction.body as PseudocodeImpl + body.parent = this@PseudocodeImpl + body.postProcess() + // Don't add edge to next instruction if flow can't reach exit of inlined declaration + instruction.next = if (body.instructions.contains(body.exitInstruction)) getNextPosition(currentPosition) else sinkInstruction + } + override fun visitSubroutineExit(instruction: SubroutineExitInstruction) { // Nothing } @@ -301,25 +303,25 @@ class PseudocodeImpl(override val correspondingElement: KtElement) : Pseudocode }) } - private fun collectReachableInstructions(): Set { - val visited = hashSetOf() - traverseFollowingInstructions(enterInstruction, visited, FORWARD + private fun collectReachableInstructions() { + val reachableFromThisPseudocode = hashSetOf() + traverseFollowingInstructions(enterInstruction, reachableFromThisPseudocode, FORWARD ) { instruction -> if (instruction is MagicInstruction && instruction.kind === MagicKind.EXHAUSTIVE_WHEN_ELSE) { return@traverseFollowingInstructions TraverseInstructionResult.SKIP } TraverseInstructionResult.CONTINUE } - if (!visited.contains(exitInstruction)) { - visited.add(exitInstruction) + + // Don't force-add EXIT and ERROR for inlined pseudocodes because for such + // declarations those instructions has special semantic + if (!isInlined) { + reachableFromThisPseudocode.add(exitInstruction) + reachableFromThisPseudocode.add(errorInstruction) + reachableFromThisPseudocode.add(sinkInstruction) } - if (!visited.contains(errorInstruction)) { - visited.add(errorInstruction) - } - if (!visited.contains(sinkInstruction)) { - visited.add(sinkInstruction) - } - return visited + + reachableFromThisPseudocode.forEach { (it.owner as PseudocodeImpl).reachableInstructions.add(it) } } private fun markDeadInstructions() { @@ -343,7 +345,7 @@ class PseudocodeImpl(override val correspondingElement: KtElement) : Pseudocode } override fun copy(): PseudocodeImpl { - val result = PseudocodeImpl(correspondingElement) + val result = PseudocodeImpl(correspondingElement, isInlined) result.repeatWhole(this) return result } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/instructions/InstructionVisitor.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/instructions/InstructionVisitor.kt index f1cd8c76ac8..aa872381d84 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/instructions/InstructionVisitor.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/instructions/InstructionVisitor.kt @@ -33,6 +33,10 @@ open class InstructionVisitor { visitInstructionWithNext(instruction) } + open fun visitInlinedLocalFunctionDeclarationInstruction(instruction: InlinedLocalFunctionDeclarationInstruction) { + visitLocalFunctionDeclarationInstruction(instruction) + } + open fun visitVariableDeclarationInstruction(instruction: VariableDeclarationInstruction) { visitInstructionWithNext(instruction) } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/instructions/InstructionVisitorWithResult.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/instructions/InstructionVisitorWithResult.kt index 9b160cb234d..a5b9b96f947 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/instructions/InstructionVisitorWithResult.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/instructions/InstructionVisitorWithResult.kt @@ -30,6 +30,9 @@ abstract class InstructionVisitorWithResult { open fun visitLocalFunctionDeclarationInstruction(instruction: LocalFunctionDeclarationInstruction): R = visitInstructionWithNext(instruction) + open fun visitInlinedFunctionDeclarationInstruction(instruction: InlinedLocalFunctionDeclarationInstruction): R = + visitLocalFunctionDeclarationInstruction(instruction) + open fun visitVariableDeclarationInstruction(instruction: VariableDeclarationInstruction): R = visitInstructionWithNext(instruction) open fun visitUnconditionalJump(instruction: UnconditionalJumpInstruction): R = visitJump(instruction) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/instructions/special/InlinedLocalFunctionDeclarationInstruction.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/instructions/special/InlinedLocalFunctionDeclarationInstruction.kt new file mode 100644 index 00000000000..ce3b645e207 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/instructions/special/InlinedLocalFunctionDeclarationInstruction.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.cfg.pseudocode.instructions.special + +import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode +import org.jetbrains.kotlin.cfg.pseudocode.instructions.BlockScope +import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionImpl +import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionVisitor +import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionVisitorWithResult +import org.jetbrains.kotlin.contracts.description.InvocationKind +import org.jetbrains.kotlin.psi.KtElement + +class InlinedLocalFunctionDeclarationInstruction( + element: KtElement, + body: Pseudocode, + blockScope: BlockScope, + val kind: InvocationKind +) : LocalFunctionDeclarationInstruction(element, body, blockScope) { + override fun createCopy(): InstructionImpl = InlinedLocalFunctionDeclarationInstruction(element, body, blockScope, kind) + + override fun accept(visitor: InstructionVisitor) = visitor.visitInlinedLocalFunctionDeclarationInstruction(this) + + override fun accept(visitor: InstructionVisitorWithResult): R = visitor.visitInlinedFunctionDeclarationInstruction(this) + + override fun toString(): String = "inlined(${render(element)})" +} \ No newline at end of file diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/instructions/special/LocalFunctionDeclarationInstruction.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/instructions/special/LocalFunctionDeclarationInstruction.kt index f7ac71e85ea..88e1d5f3062 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/instructions/special/LocalFunctionDeclarationInstruction.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/instructions/special/LocalFunctionDeclarationInstruction.kt @@ -25,7 +25,7 @@ import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionVisitor import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionVisitorWithResult import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionImpl -class LocalFunctionDeclarationInstruction( +open class LocalFunctionDeclarationInstruction( element: KtElement, val body: Pseudocode, blockScope: BlockScope